Merge branch 'sglkc:master' into master

This commit is contained in:
system32
2026-08-20 13:51:29 -07:00
committed by GitHub
6 changed files with 124 additions and 29 deletions

View File

@@ -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
@@ -15,7 +19,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20"
node-version: "24"
- name: Check In
run: node index.js
@@ -24,10 +28,13 @@ 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 }}
HOYOLAB_LANG: ${{ vars.HOYOLAB_LANG }}
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

View File

@@ -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
View File

@@ -0,0 +1,4 @@
node_modules/
.env
*.log
.DS_Store

View File

@@ -11,7 +11,9 @@ Repository version:
- [Getting your cookie](#getting-your-cookie)
- [Usage](#usage)
- [Multiple Accounts](#multiple-accounts)
- [Language](#language)
- [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 +24,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 +53,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...`
@@ -118,6 +120,20 @@ You have to check in manually first to get your cookie, follow these steps (clic
<img src="https://github.com/user-attachments/assets/4a56f4e1-8fb4-4137-acc6-ac30cade78f1" />
</details>
## Language
Check in requests are made in English (`en-us`) by default. The language decides how
HoYoLAB localizes the check in, so the reward messages you see in game and in the
check in history follow it.
To change it, create a new repository *variable* (Settings > Secrets and variables >
Actions > Variables) named `HOYOLAB_LANG` with one of the values HoYoLAB supports:
`zh-cn`, `zh-tw`, `de-de`, `en-us`, `es-es`, `fr-fr`, `id-id`, `it-it`, `ja-jp`,
`ko-kr`, `pt-pt`, `ru-ru`, `th-th`, `tr-tr`, `vi-vn`
Leaving the variable unset keeps the current `en-us` behaviour.
## Discord Webhook
You may use Discord webhook to send notifications to your channel!
@@ -150,6 +166,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?

View File

@@ -1,10 +1,21 @@
#!/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
// Locale sent to the check-in API, which decides the language of the reward
// messages shown in-game and in the HoYoLAB check-in history.
// `||` (not `??`) is intentional: an unset repository variable is injected as an
// empty string by Actions, and an empty `lang` would be sent to the API.
const language = process.env.HOYOLAB_LANG?.trim() || 'en-us'
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 +28,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,15 +53,14 @@ 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')
url.searchParams.set('lang', 'en-us')
url.searchParams.set('lang', language)
const body = JSON.stringify({
lang: 'en-us',
lang: language,
act_id: actId
})
@@ -57,14 +74,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 +89,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 +126,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 +172,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 +185,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 +226,10 @@ if (discordWebhook && URL.canParse(discordWebhook)) {
await discordWebhookSend()
}
if (telegramToken) {
await telegramSend()
}
if (hasErrors) {
console.log('')
throw new Error('Error(s) occured.')

View File

@@ -1,6 +1,6 @@
{
"name": "hoyolab-auto-daily",
"version": "1.1.0",
"version": "1.2.1",
"description": "Easiest, full free, and no BS Hoyolab games daily check-in using GitHub Actions",
"private": true,
"main": "index.js",