vermilion10

Back

vermilion10/akef-checkin Loading repository info… ★ –⑂ –

Abstract#

This article presents the design and implementation of akef-checkin, an automated daily check-in system for Arknights Endfield (SKport platform). The system employs a full OAuth 2.0 authorization flow with HMAC-SHA256 request signing to authenticate against Hypergryph and SKport APIs, eliminating the need for manual token refresh. Deployed as a serverless workflow via GitHub Actions, the system executes daily at 16:00 UTC with multi-account support and Discord webhook notifications. We detail the cryptographic signing mechanism, the four-step authentication pipeline, and the deployment architecture.


1 Introduction#

Daily check-in systems for live-service games typically require authenticated API requests to claim rewards. The Arknights Endfield platform (operated by SKport/Hypergryph)12 implements a multi-layer authentication scheme involving:

  1. Long-lived account token (ACCOUNT_TOKEN) — stored by user, valid for weeks to months
  2. Short-lived OAuth code — obtained via POST /user/oauth2/v2/grant
  3. Session credential (cred) — generated via POST /user/auth/generate_cred_by_code
  4. Signing token (signToken) — retrieved via GET /auth/refresh
  5. Game role identifier — resolved via GET /game/player/binding

Traditional approaches store short-lived tokens, requiring frequent manual updates. Our system regenerates all transient credentials on every execution using only the long-lived ACCOUNT_TOKEN, achieving zero-maintenance operation3.


2 System Architecture#

2.1 High-Level Overview#

The system comprises four modular components:

ComponentFileResponsibility
Entry Pointsrc/index.tsOrchestration, multi-account iteration, result aggregation
Authenticationsrc/auth.tsFour-step OAuth flow, cryptographic signing
Notificationsrc/discord.tsDiscord webhook delivery
Configurationsrc/constants.tsAPI endpoints, headers, platform constants

2.2 Execution Pipeline#

flowchart TD
    A[Start: Read SKPORT_TOKENS] --> B{For each account}
    B --> C[Step 1: Get OAuth Code]
    C --> D[Step 2: Generate Cred]
    D --> E[Step 3: Get Sign Token]
    E --> F[Step 4: Get Game Role]
    F --> G[Sign Attendance Request]
    G --> H[POST /game/endfield/attendance]
    H --> I{Response Code}
    I -->|0| J[Success: Parse Rewards]
    I -->|1001, 10001| K[Already Signed In]
    I -->|10002| L[Token Expired]
    I -->|Other| M[Failure: Log Error]
    J --> N[Aggregate Result]
    K --> N
    L --> N
    M --> N
    N --> O{More Accounts?}
    O -->|Yes| B
    O -->|No| P[Send Discord Webhook]
    P --> Q[Exit]
    
    style A fill:#f97316,color:#fff
    style Q fill:#22c55e,color:#fff
    style J fill:#3b82f6,color:#fff
    style K fill:#3b82f6,color:#fff
    style L fill:#f97316,color:#fff
    style M fill:#ef4444,color:#fff

3 Authentication Flow#

3.1 Sequence Diagram#

The complete authentication sequence involves two identity providers: Hypergryph (as.gryphline.com) for OAuth grant and SKport (zonai.skport.com) for game-specific credentials.

sequenceDiagram
    participant Script
    participant Gryphline as as.gryphline.com
    participant SKport as zonai.skport.com

    Script->>Gryphline: POST /user/oauth2/v2/grant<br/>{token: ACCOUNT_TOKEN, appCode, type: 0}
    Gryphline-->>Script: {code: "oauth_code"} (one-time use)

    Script->>SKport: POST /web/v1/user/auth/generate_cred_by_code<br/>{code: oauth_code, kind: 1}
    SKport-->>Script: {cred: "session_credential", token: "..."}

    Script->>SKport: GET /web/v1/auth/refresh<br/>(cred, timestamp, platform, vName)
    SKport-->>Script: {token: "sign_token"}

    Script->>SKport: GET /api/v1/game/player/binding<br/>(cred + signed request)
    SKport-->>Script: {list: [{appCode: "endfield", bindingList: [...]}]}

    Script->>SKport: POST /web/v1/game/endfield/attendance<br/>(cred + gameRole + signed request)
    SKport-->>Script: {code: 0, data: {signInCount, awardIds, resourceInfoMap}}

3.2 Cryptographic Signing (V2)#

All signed requests use the V2 signature algorithm: HMAC-SHA256 followed by MD5.

export function generateSignV2(
    path: string,
    timestamp: string,
    platform: string,
    vName: string,
    salt: string
): string {
    const headerJson = JSON.stringify({ platform, timestamp, dId: "", vName });
    const s = `${path}${timestamp}${headerJson}`;
    const hmac = crypto.createHmac("sha256", salt).update(s).digest("hex");
    return crypto.createHash("md5").update(hmac).digest("hex");
}
typescript

Mathematical formulation:

sign=MD5(HMAC-SHA256(salt,path∥timestamp∥JSON({platform,timestamp,dId,vName})))\text{sign} = \text{MD5}\big(\text{HMAC-SHA256}(\text{salt}, \text{path} \parallel \text{timestamp} \parallel \text{JSON}(\{\text{platform}, \text{timestamp}, \text{dId}, \text{vName}\}))\big)

Where:

  • ∥\parallel denotes string concatenation
  • salt=signToken\text{salt} = \text{signToken} (per-session secret)
  • path\text{path} = API endpoint path (e.g., /web/v1/game/endfield/attendance)
  • timestamp\text{timestamp} = Unix epoch seconds as string

Note

The double-hash construction (HMAC-SHA256 → MD5) provides defense-in-depth: even if MD5 collision resistance is compromised, the HMAC-SHA256 layer preserves integrity assuming the salt remains secret.

3.3 Request Structure#

Each signed request includes the following headers:

HeaderValue
credSession credential from Step 2
platform"3" (constant)
vName"1.2.0" (client version)
timestampCurrent Unix timestamp (seconds)
signV2 signature (hex)
sk-game-roleGame role identifier (format: 3_{roleId}_{serverId})
sk-languageLocale code (en, ja, zh_Hans, etc.)
dIdEmpty string (device ID placeholder)

4 Implementation Details#

4.1 Multi-Account Processing#

The entry point processes accounts sequentially with a 2-second delay between requests to avoid rate limiting:

for (let i = 0; i < accounts.length; i++) {
    const account = accounts[i];
    const result = await performCheckin(account);
    results.push(result);

    if (i < accounts.length - 1) {
        await new Promise(resolve => setTimeout(resolve, 2000));
    }
}
typescript

4.2 Response Parsing#

On successful check-in (HTTP 200, code === 0), the response contains reward information:

interface AttendanceResponse {
    code: number;
    message?: string;
    data?: {
        signInCount?: number;
        awardIds?: Array<{ id: string }>;
        resourceInfoMap?: Record<string, { name: string; count: number }>;
    };
}
typescript

Rewards are mapped by awardIds → resourceInfoMap and formatted as ItemName xCount.

4.3 Error Handling#

The system categorizes responses into four classes:

Code / ConditionCategoryAction
code === 0SuccessParse rewards, report day count
code ∈ {1001, 10001} or “already” in messageAlready Signed InReport success (idempotent)
code === 10002Token ExpiredAlert user to update ACCOUNT_TOKEN
Other / Network ErrorFailureLog error, continue to next account

5 Deployment Architecture#

5.1 GitHub Actions Workflow#

The system runs serverlessly via GitHub Actions4 on ubuntu-latest with Bun runtime:

Schedule interpretation:

  • 0 16 * * * → Minute 0, Hour 16 (UTC) → Midnight WIB (UTC+7)
  • workflow_dispatch enables manual triggering via GitHub UI

5.2 Secrets Management#

Two repository secrets are required:

SecretRequiredFormat
SKPORT_TOKENSYesJSON array of account objects
DISCORD_WEBHOOK_URLNoDiscord webhook URL

Account object schema:

{
  "accountToken": "string (from Network tab)",
  "accountName": "string (display name)",
  "language": "string (optional, default: en)"
}
json

6 Token Acquisition Procedure#

The ACCOUNT_TOKEN is not visible in browser cookies. It must be extracted from the OAuth grant request:

  1. Navigate to https://game.skport.com/endfield/sign-in while logged in
  2. Open DevTools (F12) → Network tab
  3. Filter for grant
  4. Reload page (F5)
  5. Select POST https://as.gryphline.com/user/oauth2/v2/grant
  6. Open Payload tab (not Response)
  7. Copy token value from JSON body:
    {
      "token": "ACCOUNT_TOKEN_HERE",
      "appCode": "6eb76d4e13aa36e6",
      "type": 0
    }
    json

Important

The ACCOUNT_TOKEN is long-lived (weeks to months). The script regenerates all short-lived credentials (cred, signToken, gameRole) on every run, so token rotation is rarely needed.


7 Local Development#

7.1 Prerequisites#

7.2 Configuration#

cp .env.example .env
# Edit .env with your ACCOUNT_TOKEN
bash

7.3 Execution#

bun run start
bash

Expected output:


8 Security Analysis#

8.1 Threat Model#

AssetThreatMitigation
ACCOUNT_TOKENExfiltration via logsNever logged; only used in memory
signTokenReplay attacksBound to timestamp; expires quickly
credSession hijackingShort-lived; regenerated per run
Discord webhookSpam/abuseOptional; rate-limited by Discord

8.2 Cryptographic Properties#

The V2 signature5 provides:

  • Integrity: Any modification to path, timestamp, or headers invalidates signature
  • Replay resistance: Timestamp inclusion limits window to ~1 second (server-side validation)
  • Key separation: signToken derived per-session, not reusable across accounts

8.3 Operational Security#

  • No persistent storage of credentials
  • All secrets injected via GitHub Actions encrypted secrets
  • Network requests use HTTPS with certificate validation (default in fetch)
  • User-Agent mimics legitimate browser (Firefox 147)

ProjectPlatformApproach
akef-checkin (this work)Arknights Endfield (SKport)Full OAuth flow regeneration
hoyolab-auto-checkinGenshin Impact / Star RailCookie-based with manual refresh
arknights-auto-signArknights (Global/CN)Token caching with TTL

Our approach differs by eliminating token caching entirely — every execution performs a fresh OAuth grant, trading ~3 extra HTTP requests for zero-maintenance operation.


10 Conclusion#

The akef-checkin system demonstrates a practical application of OAuth 2.0 and request signing for game automation. Key contributions:

  1. Zero-maintenance authentication — single long-lived token generates all ephemeral credentials
  2. Cryptographically sound signing — HMAC-SHA256 + MD5 V2 algorithm with per-session keys
  3. Serverless deployment — GitHub Actions cron with Bun runtime, no infrastructure management
  4. Multi-account support — sequential processing with rate-limit awareness
  5. Observability — structured logging and Discord notifications

Future work includes: exponential backoff for transient failures, Prometheus metrics export, and support for additional SKport game titles.


Appendix A: API Endpoint Reference#

ConstantURL
GRANThttps://as.gryphline.com/user/oauth2/v2/grant
GENERATE_CREDhttps://zonai.skport.com/web/v1/user/auth/generate_cred_by_code
REFRESH_TOKENhttps://zonai.skport.com/web/v1/auth/refresh
BINDINGhttps://zonai.skport.com/api/v1/game/player/binding
ATTENDANCEhttps://zonai.skport.com/web/v1/game/endfield/attendance

Appendix B: Type Definitions#


References#


This article documents the akef-checkin repository as of version 1.0.0. Source code available at the project repository.

Footnotes#

  1. Hypergryph OAuth 2.0 Implementation, as.gryphline.com (reverse-engineered) ↩

  2. SKport Web API v1, zonai.skport.com (reverse-engineered) ↩

  3. RFC 6749 — The OAuth 2.0 Authorization Framework ↩

  4. GitHub Actions Documentation — Scheduled Events (on.schedule) ↩

  5. RFC 2104 — HMAC: Keyed-Hashing for Message Authentication ↩

Automated Daily Check-in System for Arknights Endfield: Architecture and Implementation
https://pure.vermilion10.dev/blog/article-akefcheckin
Author vermilion10
Published at August 15, 2026