From 54fb2578b949616b47024a5aa06f132011cd0c77 Mon Sep 17 00:00:00 2001 From: Seya <31957516+sglkc@users.noreply.github.com> Date: Wed, 28 Jan 2026 17:52:59 +0700 Subject: [PATCH] init commit --- .github/workflows/login.yml | 33 ++++++ .github/workflows/version.yml | 46 +++++++++ LICENSE | 21 ++++ index.js | 183 ++++++++++++++++++++++++++++++++++ package.json | 14 +++ 5 files changed, 297 insertions(+) create mode 100644 .github/workflows/login.yml create mode 100644 .github/workflows/version.yml create mode 100644 LICENSE create mode 100644 index.js create mode 100644 package.json diff --git a/.github/workflows/login.yml b/.github/workflows/login.yml new file mode 100644 index 0000000..010a4fb --- /dev/null +++ b/.github/workflows/login.yml @@ -0,0 +1,33 @@ +name: Daily check in + +on: + schedule: + - cron: "0 22 * * *" # scheduled at 06:00 (UTC+8) everyday + workflow_dispatch: + +jobs: + check-in: + runs-on: ubuntu-latest + steps: + - name: Clone Repo + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Check In + run: node index.js + env: + COOKIE: ${{ secrets.COOKIE }} + GAMES: ${{ vars.GAMES }} + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} + DISCORD_USER: ${{ secrets.DISCORD_USER }} + workflow-keepalive: + if: github.event_name == 'schedule' + runs-on: ubuntu-latest + permissions: + actions: write + steps: + - uses: liskin/gh-workflow-keepalive@v1 diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml new file mode 100644 index 0000000..e96747e --- /dev/null +++ b/.github/workflows/version.yml @@ -0,0 +1,46 @@ +name: Latest version + +on: + schedule: + - cron: "0 18 * * *" # scheduled at 06:00 (UTC+8) everyday + workflow_dispatch: + +jobs: + check-version: + runs-on: ubuntu-latest + steps: + - name: Clone Repo + uses: actions/checkout@v4 + + - name: Fetch package.json from original repo + run: | + curl -o remote-package.json \ + https://raw.githubusercontent.com/sglkc/hoyolab-auto-daily/master/package.json + + - name: Compare versions + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} + run: | + LOCAL_VERSION=$(jq -r .version package.json) + REMOTE_VERSION=$(jq -r .version remote-package.json) + echo "Local version: $LOCAL_VERSION" + echo "Remote version: $REMOTE_VERSION" + + if [ "$(printf '%s\n' "$REMOTE_VERSION" "$LOCAL_VERSION" | sort -V | head -n1)" != "$REMOTE_VERSION" ]; then + echo "Repository version is outdated. Do sync your fork!" + + if [ ! -z "$DISCORD_WEBHOOK" ]; then + curl -H "Content-Type: application/json" \ + -d '{"content": "(INFO) New version released. Your repository is outdated."}' \ + $DISCORD_WEBHOOK + fi + + exit 1 + fi + workflow-keepalive: + if: github.event_name == 'schedule' + runs-on: ubuntu-latest + permissions: + actions: write + steps: + - uses: liskin/gh-workflow-keepalive@v1 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..add7468 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 sglkc + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/index.js b/index.js new file mode 100644 index 0000000..cba8f77 --- /dev/null +++ b/index.js @@ -0,0 +1,183 @@ +#!/usr/bin/env node + +/** + * Arknights: Endfield Auto Daily Check-in + * Simple script for automated daily attendance via SKPort API + */ + +const creds = process.env.CRED.split('\n').map(s => s.trim()).filter(Boolean) +const discordWebhook = process.env.DISCORD_WEBHOOK +const discordUser = process.env.DISCORD_USER + +const ATTENDANCE_URL = 'https://zonai.skport.com/web/v1/game/endfield/attendance' +const messages = [] +let hasErrors = false + +/** + * Build headers for SKPort API + */ +function buildHeaders(cred) { + // cred format: "cred|sk_game_role" or just "cred" if role is embedded + const [credToken, gameRole] = cred.includes('|') ? cred.split('|') : [cred, null] + + const headers = { + 'accept': 'application/json, text/plain, */*', + 'content-type': 'application/json', + 'origin': 'https://game.skport.com', + 'referer': 'https://game.skport.com/', + 'cred': credToken.trim(), + 'platform': '3', + 'sk-language': 'en', + 'timestamp': Math.floor(Date.now() / 1000).toString(), + 'vname': '1.0.0', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', + } + + if (gameRole) { + headers['sk-game-role'] = gameRole.trim() + } + + return headers +} + +/** + * Check if already signed in today + */ +async function checkAttendance(headers) { + const res = await fetch(ATTENDANCE_URL, { method: 'GET', headers }) + const json = await res.json() + + if (json.code !== 0) { + throw new Error(json.message || `API error code: ${json.code}`) + } + + return { + hasToday: json.data?.hasToday ?? false, + totalSignIns: json.data?.records?.length ?? 0 + } +} + +/** + * Claim daily attendance + */ +async function claimAttendance(headers) { + const res = await fetch(ATTENDANCE_URL, { method: 'POST', headers, body: null }) + const json = await res.json() + + if (json.code !== 0) { + throw new Error(json.message || `API error code: ${json.code}`) + } + + // Parse rewards + const rewards = [] + const awardIds = json.data?.awardIds ?? [] + const resourceMap = json.data?.resourceInfoMap ?? {} + + for (const award of awardIds) { + const info = resourceMap[award.id] + if (info) { + rewards.push(`${info.name} x${info.count}`) + } + } + + return { rewards } +} + +/** + * Run check-in for a single account + */ +async function run(cred, accountIndex) { + log('debug', `\n----- CHECKING IN FOR ACCOUNT ${accountIndex} -----`) + + try { + const headers = buildHeaders(cred) + + // Step 1: Check status + const status = await checkAttendance(headers) + + if (status.hasToday) { + log('info', `Account ${accountIndex}:`, 'Already checked in today') + return + } + + // Step 2: Claim if not signed in + const result = await claimAttendance(headers) + + if (result.rewards.length > 0) { + log('info', `Account ${accountIndex}:`, `Successfully checked in! Rewards: ${result.rewards.join(', ')}`) + } else { + log('info', `Account ${accountIndex}:`, 'Successfully checked in!') + } + + } catch (error) { + log('error', `Account ${accountIndex}:`, error.message) + } +} + +/** + * Custom log function to store messages + */ +function log(type, ...data) { + console[type](...data) + + switch (type) { + case 'debug': return + case 'error': hasErrors = true + } + + const string = data + .map(value => typeof value === 'object' ? JSON.stringify(value, null, 2) : value) + .join(' ') + + messages.push({ type, string }) +} + +/** + * Send results to Discord webhook + */ +async function discordWebhookSend() { + log('debug', '\n----- DISCORD WEBHOOK -----') + + if (!discordWebhook.toLowerCase().trim().startsWith('https://discord.com/api/webhooks/')) { + log('error', 'DISCORD_WEBHOOK is not a Discord webhook URL') + return + } + + let discordMsg = '' + if (discordUser) { + discordMsg = `<@${discordUser}>\n` + } + discordMsg += '**Endfield Daily Check-in**\n' + discordMsg += messages.map(msg => `(${msg.type.toUpperCase()}) ${msg.string}`).join('\n') + + const res = await fetch(discordWebhook, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ content: discordMsg }) + }) + + if (res.status === 204) { + log('info', 'Successfully sent message to Discord webhook!') + return + } + + log('error', 'Error sending message to Discord webhook') +} + +// Main execution +if (!creds || !creds.length) { + throw new Error('CRED environment variable not set!') +} + +for (const index in creds) { + await run(creds[index], Number(index) + 1) +} + +if (discordWebhook && URL.canParse(discordWebhook)) { + await discordWebhookSend() +} + +if (hasErrors) { + console.log('') + throw new Error('Error(s) occurred.') +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..965d705 --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "endfield-auto-daily", + "version": "1.0.0", + "description": "Easiest, full free, and no BS SKPORT Arknights: Endfield daily check-in using GitHub Actions", + "private": true, + "main": "index.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "sglkc", + "license": "MIT" +}