init commit

This commit is contained in:
Seya
2026-01-28 17:52:59 +07:00
commit 54fb2578b9
5 changed files with 297 additions and 0 deletions
+33
View File
@@ -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
+46
View File
@@ -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
+21
View File
@@ -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.
+183
View File
@@ -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.')
}
+14
View File
@@ -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"
}