AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Cf Auto Checkin

skill-18798aa12-claude-code-skills-cf-auto-checkin · by 18798aa12

Auto-checkin CF Workers with Telegram notifications. Two login patterns (API token and Cookie-based), cron triggers, multi-target TG push, and production-tested cookie parsing strategies.

No reviews yet
0 installs
18 views
0.0% view→install

Install

$ agentstack add skill-18798aa12-claude-code-skills-cf-auto-checkin

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-18798aa12-claude-code-skills-cf-auto-checkin)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
4mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Cf Auto Checkin? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Cloudflare Workers Auto-Checkin

Deploy automated daily checkin Workers on Cloudflare with Telegram notifications. Supports two battle-tested login patterns for different site architectures, with cron triggers and multi-target push.

When to use

  • Automating daily checkin/sign-in on web services
  • Need Telegram notifications for checkin results
  • Want zero-maintenance auto-login that never expires (API pattern)
  • Site uses PHP sessions and needs robust cookie handling (Cookie pattern)
  • Running scheduled tasks on Cloudflare Workers with cron triggers

Login Patterns

Pattern A: API Auto-Login (Recommended)

Best for sites with a REST API that returns an auth token on login. The Worker logs in fresh every time, so tokens never expire.

Flow: POST login → get token → POST checkin → TG notify

Pattern B: Cookie-Based Login

Best for traditional PHP sites that use session cookies (PHPSESSID). Includes 5 parallel cookie parsing strategies for maximum compatibility with different server behaviors.

Flow: GET login page (cookie) → POST login (merge cookies) → POST checkin → TG notify

Auto-Setup: Pattern A (API Login)

Step 1: Create Project

mkdir my-checkin && cd my-checkin
npm init -y

Step 2: Create Worker

Create src/worker.js:

/**
 * Auto-Checkin CF Worker — API Login Pattern
 * Logs in with email/password to get a fresh token, then checks in.
 * Token is never stored — obtained fresh each run, so it never expires.
 */

export default {
    async scheduled(event, env, ctx) {
        ctx.waitUntil(runCheckin(env));
    },

    async fetch(request, env) {
        const result = await runCheckin(env);
        return new Response(JSON.stringify(result, null, 2), {
            headers: { "Content-Type": "application/json" },
        });
    },
};

async function runCheckin(env) {
    const results = [];

    // ── Add your sites here ──
    const sites = [
        {
            name: "MY_SITE_NAME",
            baseUrl: "https://YOUR_SITE_DOMAIN",
            loginPath: "/api/v1/passport/auth/login",
            checkinPath: "/api/v1/user/checkin",
            email: env.SITE_EMAIL,
            password: env.SITE_PASSWORD,
        },
        // Add more sites as needed:
        // {
        //     name: "Another Site",
        //     baseUrl: "https://another-site.com",
        //     loginPath: "/api/login",
        //     checkinPath: "/api/checkin",
        //     email: env.SITE2_EMAIL,
        //     password: env.SITE2_PASSWORD,
        // },
    ];

    for (const site of sites) {
        if (!site.email || !site.password) {
            results.push({ name: site.name, ok: false, msg: "Email/password not configured" });
            continue;
        }
        const r = await loginAndCheckin(site);
        results.push(r);
    }

    await sendTelegram(env, results);
    return results;
}

async function loginAndCheckin(site) {
    const ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36";

    try {
        // Step 1: Login to get fresh token
        const loginResp = await fetch(`${site.baseUrl}${site.loginPath}`, {
            method: "POST",
            headers: { "Content-Type": "application/json", "User-Agent": ua },
            body: JSON.stringify({ email: site.email, password: site.password }),
        });

        const loginData = await loginResp.json();

        // Adjust this based on your site's API response format
        if (!loginData.data || !loginData.data.auth_data) {
            return { name: site.name, ok: false, msg: `Login failed: ${loginData.message || "unknown"}` };
        }

        const token = loginData.data.auth_data;

        // Step 2: Checkin with fresh token
        const checkinResp = await fetch(`${site.baseUrl}${site.checkinPath}`, {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                Authorization: token,
                "User-Agent": ua,
                Origin: site.baseUrl,
                Referer: site.baseUrl + "/",
            },
        });

        const checkinData = await checkinResp.json().catch(() => ({ message: "Parse error" }));

        if (checkinResp.status === 200) {
            return { name: site.name, ok: true, msg: checkinData.data || checkinData.message || "Checkin success" };
        } else if (checkinResp.status === 400) {
            return { name: site.name, ok: true, msg: checkinData.message || "Already checked in today" };
        } else {
            return { name: site.name, ok: false, msg: `HTTP ${checkinResp.status}: ${checkinData.message || "error"}` };
        }
    } catch (e) {
        return { name: site.name, ok: false, msg: `Request error: ${e.message}` };
    }
}

async function sendTelegram(env, results) {
    const token = env.TG_BOT_TOKEN;
    if (!token) return;

    // Configure your notification targets
    const chatIds = [
        env.TG_CHAT_ID,       // Primary user
        // env.TG_CHAT_ID_2,  // Secondary user (optional)
        // env.TG_CHANNEL_ID, // Channel (optional)
    ].filter(Boolean);

    if (chatIds.length === 0) return;

    const now = new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
    const lines = results.map((r) => {
        const icon = r.ok ? "\u2705" : "\u274c";
        return `${icon} ${r.name}\n   ${r.msg}`;
    });

    const text = `\ud83d\udd50 Auto-Checkin Report\n\ud83d\udcc5 ${now}\n\n${lines.join("\n\n")}`;

    for (const chatId of chatIds) {
        await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ chat_id: chatId, text, parse_mode: "HTML" }),
        });
    }
}

Step 3: Configure wrangler.toml

name = "my-checkin"
main = "src/worker.js"
compatibility_date = "2024-12-01"
account_id = "YOUR_CF_ACCOUNT_ID"

# Cron schedule (UTC). Examples:
# "0 21 * * *"  = Beijing 5:00 AM daily
# "0 0 * * *"   = Beijing 8:00 AM daily
# "30 16 * * *" = Beijing 0:30 AM daily
[triggers]
crons = ["0 21 * * *"]

# Optional: plain-text vars (non-sensitive)
# [vars]
# TG_CHAT_ID = "YOUR_CHAT_ID"

Step 4: Set Secrets and Deploy

# Set secrets (never put these in wrangler.toml)
echo "YOUR_EMAIL" | npx wrangler secret put SITE_EMAIL
echo "YOUR_PASSWORD" | npx wrangler secret put SITE_PASSWORD
echo "YOUR_BOT_TOKEN" | npx wrangler secret put TG_BOT_TOKEN
echo "YOUR_CHAT_ID" | npx wrangler secret put TG_CHAT_ID

# Deploy
npx wrangler deploy

Auto-Setup: Pattern B (Cookie Login)

Step 1: Create Project

mkdir my-cookie-checkin && cd my-cookie-checkin
npm init -y

Step 2: Create Worker

Create src/worker.js:

/**
 * Auto-Checkin CF Worker — Cookie Login Pattern
 * For PHP sites using session cookies (PHPSESSID).
 * Runs 5 cookie parsing strategies in parallel for maximum compatibility.
 */

let domain = "";
let user = "";
let pass = "";
let BotToken = "";
let ChatID = "";

export default {
    async fetch(request, env, ctx) {
        await initVars(env);
        const url = new URL(request.url);
        if (url.pathname === "/checkin") {
            await checkin();
        }
        return new Response("OK", { status: 200 });
    },

    async scheduled(controller, env, ctx) {
        try {
            await initVars(env);
            await checkin();
        } catch (error) {
            console.error("Cron failed:", error);
        }
    },
};

async function initVars(env) {
    domain = env.SITE_DOMAIN || domain;
    user = env.SITE_EMAIL || user;
    pass = env.SITE_PASSWORD || pass;
    if (domain && !domain.includes("//")) domain = `https://${domain}`;
    BotToken = env.TG_BOT_TOKEN || BotToken;
    ChatID = env.TG_CHAT_ID || ChatID;
}

async function sendMessage(msg = "") {
    const now = new Date(Date.now() + 8 * 3600000).toISOString().slice(0, 19).replace("T", " ");
    if (BotToken && ChatID) {
        const url = `https://api.telegram.org/bot${BotToken}/sendMessage`;
        await fetch(url, {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
                chat_id: ChatID,
                parse_mode: "HTML",
                text: `\ud83d\udd50 ${now}\n\n${msg}`,
            }),
        });
    }
}

// ── 5 Cookie Parsing Strategies ──
// Different servers serialize Set-Cookie headers differently.
// Running all 5 in parallel maximizes compatibility.

const COOKIE_ATTRS = new Set(["expires", "max-age", "path", "domain", "secure", "httponly", "samesite"]);

const COOKIE_STRATEGIES = [
    // Strategy 1: getSetCookie() — CF Workers standard API
    function strategyGetSetCookie(response) {
        if (typeof response.headers.getSetCookie !== "function") return "";
        const cookies = [];
        for (const sc of response.headers.getSetCookie()) {
            const semi = sc.indexOf(";");
            const pair = (semi > 0 ? sc.substring(0, semi) : sc).trim();
            if (pair.includes("=")) cookies.push(pair);
        }
        return cookies.join("; ");
    },

    // Strategy 2: headers.entries() iteration
    function strategyEntries(response) {
        const cookies = [];
        try {
            for (const [name, value] of response.headers.entries()) {
                if (name.toLowerCase() !== "set-cookie") continue;
                const matches = value.matchAll(/(?:^|[,;]\s*)([a-zA-Z_][a-zA-Z0-9_-]*=[^;,]*)/g);
                for (const m of matches) {
                    const pair = m[1].trim();
                    const eq = pair.indexOf("=");
                    if (eq > 0 && !COOKIE_ATTRS.has(pair.substring(0, eq).toLowerCase())) cookies.push(pair);
                }
            }
        } catch (e) {}
        return cookies.join("; ");
    },

    // Strategy 3: raw header + matchAll regex
    function strategyMatchAll(response) {
        const raw = response.headers.get("set-cookie") || "";
        if (!raw) return "";
        const cookies = [];
        const matches = raw.matchAll(/(?:^|[,;]\s*)([a-zA-Z_][a-zA-Z0-9_-]*=[^;,]*)/g);
        for (const m of matches) {
            const pair = m[1].trim();
            const eq = pair.indexOf("=");
            if (eq > 0 && !COOKIE_ATTRS.has(pair.substring(0, eq).toLowerCase())) cookies.push(pair);
        }
        return cookies.join("; ");
    },

    // Strategy 4: split by [;,] + attribute blacklist
    function strategySplitFilter(response) {
        const raw = response.headers.get("set-cookie") || "";
        if (!raw) return "";
        const cookies = [];
        for (const part of raw.split(/[;,]\s*/)) {
            const pair = part.trim();
            const eq = pair.indexOf("=");
            if (eq  0) map.set(c.substring(0, eq).trim(), c);
    }
    for (const c of (newStr || "").split("; ").filter(Boolean)) {
        const eq = c.indexOf("=");
        if (eq > 0) map.set(c.substring(0, eq).trim(), c);
    }
    return Array.from(map.values()).join("; ");
}

async function checkin() {
    if (!domain || !user || !pass) return;

    const tasks = COOKIE_STRATEGIES.map((strategy, i) => attemptCheckin(i + 1, strategy));
    const results = await Promise.allSettled(tasks);

    for (const r of results) {
        if (r.status === "fulfilled" && r.value && r.value.success) {
            await sendMessage(r.value.message);
            return;
        }
    }

    const errors = results.map((r) =>
        r.status === "fulfilled" ? r.value?.message : r.reason?.message || String(r.reason)
    );
    await sendMessage(`All 5 attempts failed:\n${errors.join("\n")}`);
}

async function attemptCheckin(attemptNo, extractCookies) {
    const ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36";

    try {
        // Step 1: GET login page for initial session cookie
        const initResp = await fetch(`${domain}/auth/login`, {
            method: "GET",
            headers: { "User-Agent": ua },
            redirect: "manual",
        });
        let allCookies = extractCookies(initResp);

        // Step 2: POST login
        const loginResp = await fetch(`${domain}/auth/login`, {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "User-Agent": ua,
                Cookie: allCookies,
                Origin: domain,
                Referer: `${domain}/auth/login`,
            },
            body: JSON.stringify({ email: user, passwd: pass, remember_me: "on", code: "" }),
            redirect: "manual",
        });

        allCookies = mergeCookies(allCookies, extractCookies(loginResp));

        const loginText = await loginResp.text();
        try {
            const loginJson = JSON.parse(loginText);
            if (loginJson.ret !== 1) throw new Error(`Login failed: ${loginJson.msg || "unknown"}`);
        } catch (e) {
            if (e.message.startsWith("Login failed")) throw e;
            if (loginResp.status >= 400) throw new Error(`Login failed: HTTP ${loginResp.status}`);
        }

        if (!allCookies || !allCookies.toLowerCase().includes("phpsessid")) {
            throw new Error("Missing PHPSESSID after login");
        }

        // Step 3: POST checkin
        const checkinResp = await fetch(`${domain}/user/checkin`, {
            method: "POST",
            headers: {
                Cookie: allCookies,
                "User-Agent": ua,
                "Content-Type": "application/json",
                Origin: domain,
                Referer: `${domain}/user`,
                "X-Requested-With": "XMLHttpRequest",
            },
        });

        const respText = await checkinResp.text();
        try {
            const result = JSON.parse(respText);
            return { success: true, message: `Checkin result: ${result.msg || "done"}` };
        } catch (e) {
            return { success: false, message: `[Attempt ${attemptNo}] Parse error: ${respText.substring(0, 100)}` };
        }
    } catch (error) {
        return { success: false, message: `[Attempt ${attemptNo}] ${error.message}` };
    }
}

Step 3: Configure wrangler.toml

name = "my-cookie-checkin"
main = "src/worker.js"
compatibility_date = "2024-12-01"
account_id = "YOUR_CF_ACCOUNT_ID"

[triggers]
crons = ["0 21 * * *"]

Step 4: Set Secrets and Deploy

echo "example.com" | npx wrangler secret put SITE_DOMAIN
echo "user@example.com" | npx wrangler secret put SITE_EMAIL
echo "your_password" | npx wrangler secret put SITE_PASSWORD
echo "123456789:AABBccDDeeFFgg" | npx wrangler secret put TG_BOT_TOKEN
echo "123456789" | npx wrangler secret put TG_CHAT_ID

npx wrangler deploy

Telegram Bot Setup

Step 1: Create Bot

  1. Message @BotFather on Telegram
  2. Send /newbot, follow prompts
  3. Copy the bot token (format: 123456789:AABBccDDeeFFgg...)

Step 2: Get Chat ID

# Send any message to your bot first, then:
curl -s "https://api.telegram.org/bot/getUpdates" | python3 -m json.tool

# Look for "chat": {"id": 123456789} in the response

Step 3: Multi-Target Push (Optional)

To send to multiple users or channels:

// In sendTelegram(), add more targets:
const chatIds = [
    env.TG_CHAT_ID,       // User 1
    env.TG_CHAT_ID_2,     // User 2
    env.TG_CHANNEL_ID,    // Channel (use negative ID, e.g., -100xxx)
].filter(Boolean);

Set each as a secret or [vars] in wrangler.toml:

echo "USER1_CHAT_ID" | npx wrangler secret put TG_CHAT_ID
echo "USER2_CHAT_ID" | npx wrangler secret put TG_CHAT_ID_2
echo "-100CHANNEL_ID" | npx wrangler secret put TG_CHANNEL_ID

Cron Schedule Reference

Cloudflare cron triggers use UTC time.

| Beijing Time | UTC Cron | Expression | |-------------|------

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.