DIYgod/RSSHub · error · Error

Failed to solve the SafeLine challenge of weather.cma.cn

Error message

Failed to solve the SafeLine challenge of weather.cma.cn

What it means

weather.cma.cn is protected by the SafeLine WAF, which serves a JavaScript proof-of-work challenge: find a hex suffix so that SHA1(prefix+suffix) starts with `leading_zero_bit` zero bits. The handler brute-forces up to 100,000,000 suffixes; if none satisfies the difficulty within that cap it throws a generic Error. This is a runtime/anti-bot failure, not bad user input.

Source

Thrown at lib/routes/cma/channel.tsx:23

import { renderToString } from 'hono/jsx/dom/server';

import type { Language, Route } from '@/types';
import got from '@/utils/got';
import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';
import timezone from '@/utils/timezone';

const solveChallenge = (prefix: string, leadingZeroBits: number) => {
    for (let count = 0; count < 100_000_000; count++) {
        const suffix = count.toString(16);
        const hash = createHash('sha1')
            .update(prefix + suffix)
            .digest();
        if (hash.readUInt32BE(0) >>> (32 - leadingZeroBits) === 0) {
            return suffix;
        }
    }
    throw new Error('Failed to solve the SafeLine challenge of weather.cma.cn');
};

const fetchPageWithChallenge = async (url: string) => {
    const response = await ofetch.raw<string>(url);
    const html = response._data ?? '';
    const challenge = response.headers
        .getSetCookie()
        .find((cookie) => cookie.startsWith('safeline_bot_challenge='))
        ?.split(';', 1)[0];
    const prefix = html.match(/var prefix = '(\w+)';/)?.[1];
    const leadingZeroBits = Number(html.match(/var leading_zero_bit = (\d+);/)?.[1]);

    if (!challenge || !prefix || !leadingZeroBits) {
        return html;
    }

    const answer = challenge.replace('safeline_bot_challenge=', 'safeline_bot_challenge_ans=') + solveChallenge(prefix, leadingZeroBits);

View on GitHub (pinned to bed535e087)

Solutions

  1. Retry the request — the challenge prefix and difficulty rotate, so a fresh page often solves quickly.
  2. If the regexes stopped matching, inspect the served HTML and update the two match patterns in solveChallenge's caller.
  3. Raise the iteration cap (the 100_000_000 literal) if difficulty is consistently high.
  4. Report an upstream SafeLine format change to the route maintainer.

Example fix

// before
//   for (let count = 0; count < 100_000_000; count++) {
// after
//   for (let count = 0; count < 500_000_000; count++) {
Defensive patterns

Strategy: retry

Validate before calling

// No caller-side validation prevents this; the challenge is server-issued.
// Pre-flight: fetch the page once and confirm the two regexes match before solving.
const html = await ofetch.raw<string>(url);
const hasChallenge = /var prefix = '/.test(html._data ?? '') && /var leading_zero_bit = /.test(html._data ?? '');
if (!hasChallenge) { /* expect solve to fail; skip or alert */ }

Try / catch

try {
  await fetchPageWithChallenge(url);
} catch (e) {
  // challenge difficulty/HTML may have rotated; one bounded retry often succeeds
  await fetchPageWithChallenge(url);
}

Prevention

When it happens

Trigger: SafeLine raises `leading_zero_bit` so the 100M-iteration budget is exhausted; the page regexes (`var prefix = ...`, `var leading_zero_bit = ...`) no longer match because SafeLine changed its challenge HTML; the challenge cookie is stale.

Common situations: A SafeLine version bump changes the challenge page structure; a transient high-difficulty challenge; running on a slow CPU where the loop cannot complete in time.

Related errors


AI-assisted analysis of DIYgod/RSSHub@bed535e087 (2026-08-12). Data as JSON: /api/errors/9ddb27464b875376. Report an issue: GitHub.