mirror of
https://github.com/sys-32Dev/hoyolab-auto-daily.git
synced 2026-10-07 20:10:03 +00:00
Add Telegram notifications, harden workflows, fix request handling
Features - Telegram notifier via TELEGRAM_TOKEN / TELEGRAM_CHAT_ID, sent alongside the existing Discord webhook (either, both, or neither) - emoji status prefixes in notifications for faster scanning Fixes - wrap each game's fetch/json in try/catch so a network error or non-JSON response logs and continues instead of aborting the whole run - correct Content-Type (application/json) and sec-fetch-mode headers - fix quote-stripping regex (missing global flag) - throw the friendly COOKIE/GAMES errors before use instead of a raw TypeError - keep send-confirmation lines out of the notification body Hardening - pin liskin/gh-workflow-keepalive to a commit SHA - add least-privilege permissions to both workflows Chores - add .gitignore (node_modules, .env, logs) to prevent committing local cookies Docs - document Telegram setup in README
This commit is contained in:
8
.github/workflows/login.yml
vendored
8
.github/workflows/login.yml
vendored
@@ -5,9 +5,13 @@ on:
|
||||
- cron: "0 22 * * *" # scheduled at 06:00 (UTC+8) everyday
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
check-in:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Clone Repo
|
||||
uses: actions/checkout@v4
|
||||
@@ -24,10 +28,12 @@ jobs:
|
||||
GAMES: ${{ vars.GAMES }}
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
DISCORD_USER: ${{ secrets.DISCORD_USER }}
|
||||
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
|
||||
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||
workflow-keepalive:
|
||||
if: github.event_name == 'schedule'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: write
|
||||
steps:
|
||||
- uses: liskin/gh-workflow-keepalive@v1
|
||||
- uses: liskin/gh-workflow-keepalive@f72ff1a1336129f29bf0166c0fd0ca6cf1bcb38c # v1
|
||||
|
||||
6
.github/workflows/version.yml
vendored
6
.github/workflows/version.yml
vendored
@@ -5,9 +5,13 @@ on:
|
||||
- cron: "0 18 * * *" # scheduled at 06:00 (UTC+8) everyday
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
check-version:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Clone Repo
|
||||
uses: actions/checkout@v4
|
||||
@@ -43,4 +47,4 @@ jobs:
|
||||
permissions:
|
||||
actions: write
|
||||
steps:
|
||||
- uses: liskin/gh-workflow-keepalive@v1
|
||||
- uses: liskin/gh-workflow-keepalive@f72ff1a1336129f29bf0166c0fd0ca6cf1bcb38c # v1
|
||||
|
||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
.env
|
||||
*.log
|
||||
.DS_Store
|
||||
23
README.md
23
README.md
@@ -12,6 +12,7 @@ Repository version:
|
||||
- [Usage](#usage)
|
||||
- [Multiple Accounts](#multiple-accounts)
|
||||
- [Discord Webhook](#discord-webhook)
|
||||
- [Telegram](#telegram)
|
||||
- [FAQ](#faq)
|
||||
- [Is this safe?](#is-this-safe)
|
||||
- [How to update my (fork) repository version?](#how-to-update-my-fork-repository-version)
|
||||
@@ -22,7 +23,7 @@ Repository version:
|
||||
|
||||
You have to check in manually first to get your cookie, follow these steps (click to open screenshot):
|
||||
|
||||
1. Open [HoYoLAB](https://www.hoyolab.com/home) and login if you haven't (obviously)
|
||||
1. Open [HoYoLAB](https://www.hoyolab.com/home) and login if you haven't
|
||||
|
||||
2. <details>
|
||||
<summary>Open dev tool (<kbd>Ctrl+Shift+I</kbd> or right click > Inspect)</summary>
|
||||
@@ -51,7 +52,7 @@ You have to check in manually first to get your cookie, follow these steps (clic
|
||||
7. <details open>
|
||||
<summary>Write <code>ltuid_v2=PASTE_ltuid_v2; ltoken_v2=PASTE_ltoken_v2</code> like the screenshot.</summary>
|
||||
|
||||
I write the line on the browser url to make it easier. Pay attention, use semicolon (;) and not colon (:)
|
||||
Writing the line in the browser URL bar makes it easier. Pay attention: use a semicolon (;), not a colon (:)
|
||||
|
||||
It should look like this: `ltuid_v2=249806310; ltoken_v2=v2_CAISDG...`
|
||||
|
||||
@@ -150,6 +151,24 @@ You may use Discord webhook to send notifications to your channel!
|
||||
|
||||
6. You may trigger the check in manually and see if the messages got sent
|
||||
|
||||
## Telegram
|
||||
|
||||
You may use a Telegram bot to send notifications to yourself or a group.
|
||||
|
||||
1. Open [@BotFather](https://t.me/BotFather) in Telegram, send `/newbot`, and follow the
|
||||
prompts. It gives you a **bot token** like `123456789:AAE...`
|
||||
2. Start a chat with your new bot and send it any message (the bot can't message you first).
|
||||
3. Get your **chat ID**: open
|
||||
`https://api.telegram.org/bot<YOUR_TOKEN>/getUpdates` in your browser (replace
|
||||
`<YOUR_TOKEN>`) and copy the number in `"chat":{"id":...}`. For a group, add the bot
|
||||
to the group first, then read the group's negative chat ID from the same URL.
|
||||
4. Add two repository *secrets* (Settings > Secrets and variables > Actions):
|
||||
- `TELEGRAM_TOKEN` with the bot token
|
||||
- `TELEGRAM_CHAT_ID` with the chat ID
|
||||
5. Trigger the check in manually and see if the message arrives.
|
||||
|
||||
Discord and Telegram are independent — set up either, both, or neither.
|
||||
|
||||
## FAQ
|
||||
|
||||
### Is this safe?
|
||||
|
||||
83
index.js
83
index.js
@@ -1,10 +1,16 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
if (!process.env.COOKIE) throw new Error('COOKIE environment variable not set!')
|
||||
if (!process.env.GAMES) throw new Error('GAMES environment variable not set!')
|
||||
|
||||
const cookies = process.env.COOKIE.split('\n').map(s => s.trim())
|
||||
const games = process.env.GAMES.split('\n').map(s => s.trim())
|
||||
const discordWebhook = process.env.DISCORD_WEBHOOK
|
||||
const discordUser = process.env.DISCORD_USER
|
||||
const telegramToken = process.env.TELEGRAM_TOKEN
|
||||
const telegramChat = process.env.TELEGRAM_CHAT_ID
|
||||
const msgDelimiter = ':'
|
||||
const icon = { info: '✅', error: '❌' }
|
||||
const messages = []
|
||||
const endpoints = {
|
||||
zzz: 'https://sg-act-nap-api.hoyolab.com/event/luna/zzz/os/sign?act_id=e202406031448091',
|
||||
@@ -17,6 +23,13 @@ const endpoints = {
|
||||
let hasErrors = false
|
||||
let latestGames = []
|
||||
|
||||
/**
|
||||
* Checks in one account across the requested games, recording each result via log().
|
||||
* A falsy `games` reuses the previous account's list, so multiple accounts sharing
|
||||
* the same games need the GAMES line specified only once.
|
||||
* @param {string} cookie Cookie header for the account (`ltuid_v2=...; ltoken_v2=...`).
|
||||
* @param {string} [games] Space-separated game codes; falsy reuses the prior account's games.
|
||||
*/
|
||||
async function run(cookie, games) {
|
||||
if (!games) {
|
||||
games = latestGames
|
||||
@@ -35,7 +48,6 @@ async function run(cookie, games) {
|
||||
continue
|
||||
}
|
||||
|
||||
// begin check in
|
||||
const endpoint = endpoints[game]
|
||||
const url = new URL(endpoint)
|
||||
const actId = url.searchParams.get('act_id')
|
||||
@@ -57,14 +69,14 @@ async function run(cookie, games) {
|
||||
|
||||
headers.set('origin', 'https://act.hoyolab.com')
|
||||
headers.set('referrer', 'https://act.hoyolab.com')
|
||||
headers.set('content-type', 'application.json;charset=UTF-8')
|
||||
headers.set('content-type', 'application/json;charset=UTF-8')
|
||||
headers.set('cookie', cookie)
|
||||
|
||||
headers.set('sec-ch-ua', '"Not/A)Brand";v="8", "Chromium";v="126", "Brave";v="126"')
|
||||
headers.set('sec-ch-ua-mobile', '?0')
|
||||
headers.set('sec-ch-ua-platform', '"Linux"')
|
||||
headers.set('sec-fetch-dest', 'empty')
|
||||
headers.set('sec-fech-mode', 'cors')
|
||||
headers.set('sec-fetch-mode', 'cors')
|
||||
headers.set('sec-fetch-site', 'same-site')
|
||||
headers.set('sec-gpc', '1')
|
||||
|
||||
@@ -72,21 +84,26 @@ async function run(cookie, games) {
|
||||
|
||||
headers.set('user-agent', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36')
|
||||
|
||||
const res = await fetch(url, { method: 'POST', headers, body })
|
||||
const json = await res.json()
|
||||
let res, json
|
||||
try {
|
||||
res = await fetch(url, { method: 'POST', headers, body })
|
||||
json = await res.json()
|
||||
} catch (e) {
|
||||
log('error', game, `Request failed: ${e.message}`)
|
||||
continue
|
||||
}
|
||||
|
||||
const code = String(json.retcode)
|
||||
const successCodes = {
|
||||
'0': 'Successfully checked in!',
|
||||
'-5003': 'Already checked in for today',
|
||||
}
|
||||
|
||||
// success responses
|
||||
if (code in successCodes) {
|
||||
log('info', game, `${successCodes[code]}`)
|
||||
continue
|
||||
}
|
||||
|
||||
// error responses
|
||||
const errorCodes = {
|
||||
'-100': 'Error not logged in. Your cookie is invalid, try setting up again',
|
||||
'-10002': 'Error not found. You haven\'t played this game'
|
||||
@@ -104,28 +121,31 @@ async function run(cookie, games) {
|
||||
}
|
||||
}
|
||||
|
||||
// custom log function to store messages
|
||||
/**
|
||||
* Logs to the console and, for non-debug entries, records the message for the
|
||||
* Discord and Telegram notifiers. Debug entries print but are never sent, keeping
|
||||
* raw responses and cookies out of notifications. An `error` type also sets the
|
||||
* process-wide `hasErrors` flag, which makes the run exit non-zero at the end.
|
||||
* @param {'info'|'error'|'debug'} type Console method and notification severity.
|
||||
* @param {...any} data Message parts; a leading game code is upcased and delimited.
|
||||
*/
|
||||
function log(type, ...data) {
|
||||
|
||||
// log to real console
|
||||
console[type](...data)
|
||||
|
||||
// ignore debug and toggle hasErrors
|
||||
switch (type) {
|
||||
case 'debug': return
|
||||
case 'error': hasErrors = true
|
||||
}
|
||||
|
||||
// check if it's a game specific message, and set it as uppercase for clarity, and add delimiter
|
||||
// Prefix game-specific lines with the upcased code (e.g. `GI:`) for scannability
|
||||
if(data[0] in endpoints) {
|
||||
data[0] = data[0].toUpperCase() + msgDelimiter
|
||||
}
|
||||
|
||||
// serialize data and add to messages
|
||||
const string = data
|
||||
.map(value => {
|
||||
if (typeof value === 'object') {
|
||||
return JSON.stringify(value, null, 2).replace(/^"|"$/, '')
|
||||
return JSON.stringify(value, null, 2).replace(/^"|"$/g, '')
|
||||
}
|
||||
|
||||
return value
|
||||
@@ -147,7 +167,7 @@ async function discordWebhookSend() {
|
||||
if (discordUser) {
|
||||
discordMsg = `<@${discordUser}>\n`
|
||||
}
|
||||
discordMsg += messages.map(msg => `(${msg.type.toUpperCase()}) ${msg.string}`).join('\n')
|
||||
discordMsg += messages.map(msg => `${icon[msg.type] ?? ''} ${msg.string}`).join('\n')
|
||||
|
||||
const res = await fetch(discordWebhook, {
|
||||
method: 'POST',
|
||||
@@ -160,19 +180,36 @@ async function discordWebhookSend() {
|
||||
})
|
||||
|
||||
if (res.status === 204) {
|
||||
log('info', 'Successfully sent message to Discord webhook!')
|
||||
console.log('Successfully sent message to Discord webhook!')
|
||||
return
|
||||
}
|
||||
|
||||
log('error', 'Error sending message to Discord webhook, please check URL and permissions')
|
||||
}
|
||||
|
||||
if (!cookies || !cookies.length) {
|
||||
throw new Error('COOKIE environment variable not set!')
|
||||
}
|
||||
// must be function to return early
|
||||
async function telegramSend() {
|
||||
log('debug', '\n----- TELEGRAM -----')
|
||||
|
||||
if (!games || !games.length) {
|
||||
throw new Error('GAMES environment variable not set!')
|
||||
if (!telegramChat) {
|
||||
log('error', 'TELEGRAM_TOKEN is set but TELEGRAM_CHAT_ID is missing')
|
||||
return
|
||||
}
|
||||
|
||||
const text = messages.map(msg => `${icon[msg.type] ?? ''} ${msg.string}`).join('\n')
|
||||
|
||||
const res = await fetch(`https://api.telegram.org/bot${telegramToken}/sendMessage`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ chat_id: telegramChat, text })
|
||||
})
|
||||
|
||||
if (res.ok) {
|
||||
console.log('Successfully sent message to Telegram!')
|
||||
return
|
||||
}
|
||||
|
||||
log('error', 'Error sending message to Telegram, please check TELEGRAM_TOKEN and TELEGRAM_CHAT_ID')
|
||||
}
|
||||
|
||||
for (const index in cookies) {
|
||||
@@ -184,6 +221,10 @@ if (discordWebhook && URL.canParse(discordWebhook)) {
|
||||
await discordWebhookSend()
|
||||
}
|
||||
|
||||
if (telegramToken) {
|
||||
await telegramSend()
|
||||
}
|
||||
|
||||
if (hasErrors) {
|
||||
console.log('')
|
||||
throw new Error('Error(s) occured.')
|
||||
|
||||
Reference in New Issue
Block a user