mirror of
https://github.com/tiennm99/store-scraper-bot.git
synced 2026-09-03 08:19:22 +00:00
feat: Cloudflare Workers code port (deploy pending)
Refactors source to be Worker-shaped. No live deploy yet — sister deploy plan
runs Atlas provisioning + smoke later.
- wrangler.toml with nodejs_compat_v2, daily UTC 0 cron (= 7am Asia/Ho_Chi_Minh)
- package.json: drop node-telegram-bot-api, node-cron, dotenv, pino,
pino-pretty; add wrangler devDep; bump to 0.2.0
- src/bot/telegram-api.js: raw fetch wrapper for Telegram Bot API
- src/bot/dispatch.js: per-message dispatcher extracted from polling loop
- src/repository/mongodb.js: memoized MongoClient per warm isolate, typed
MongoUnavailable error, fast-fail timeouts
- src/repository/store.js: factory binding env once
- All 4 repositories converted to factory shape
- src/api/{apple,google}-scraper.js: take store instead of importing repos
- src/index.js: Worker entry exporting { fetch, scheduled }; webhook validates
X-Telegram-Bot-Api-Secret-Token; ack-then-waitUntil pattern
- src/scheduler/scheduler.js: trimmed; runDailyCheck only (no node-cron)
- src/config.js, src/logger.js: env-driven, console.log JSON output
- scripts/register-webhook.js: setWebhook + setMyCommands; --dry-run supported
- scripts/check-secret-leaks.js: lint blocks console.log(env.<SECRET>)
- plans/260426-2015-cloudflare-worker-code-port: this code port plan
- plans/260426-2327-cloudflare-deploy-and-smoke: sister deploy plan
Validated via node --check on all 32 source files; lint clean. Real deploy
gates (bundle size, cold-start CPU) run in deploy plan.
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env node
|
||||
// Fails CI if any source file logs a secret via env.
|
||||
// Pattern: console.{log,info,warn,error,debug}(... env.<SECRET> ...)
|
||||
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
const SECRETS = ['MONGODB_URI', 'TELEGRAM_BOT_TOKEN', 'TELEGRAM_WEBHOOK_SECRET', 'ADMIN_IDS'];
|
||||
const ROOTS = ['src', 'scripts'];
|
||||
|
||||
function* walk(dir) {
|
||||
let entries;
|
||||
try {
|
||||
entries = readdirSync(dir);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
for (const entry of entries) {
|
||||
const p = join(dir, entry);
|
||||
const s = statSync(p);
|
||||
if (s.isDirectory()) yield* walk(p);
|
||||
else if (/\.(js|mjs|ts)$/.test(p)) yield p;
|
||||
}
|
||||
}
|
||||
|
||||
const violations = [];
|
||||
for (const root of ROOTS) {
|
||||
for (const file of walk(root)) {
|
||||
const text = readFileSync(file, 'utf8');
|
||||
for (const secret of SECRETS) {
|
||||
const re = new RegExp(`console\\.(log|info|warn|error|debug)\\([^)]*\\benv\\.${secret}\\b`);
|
||||
if (re.test(text)) violations.push({ file, secret });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (violations.length > 0) {
|
||||
console.error('Secret-leak violations:');
|
||||
for (const v of violations) console.error(` ${v.file}: env.${v.secret} in console.*`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('check-secret-leaks: clean');
|
||||
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env node
|
||||
// Post-deploy registration: setWebhook (with secret_token) + setMyCommands.
|
||||
// Run via: npm run register (reads .env.deploy)
|
||||
// Dry run via: npm run register:dry
|
||||
|
||||
const TOKEN = process.env.TELEGRAM_BOT_TOKEN;
|
||||
const SECRET = process.env.TELEGRAM_WEBHOOK_SECRET;
|
||||
const URL_ = process.env.WORKER_URL;
|
||||
const DRY = process.argv.includes('--dry-run');
|
||||
|
||||
for (const [k, v] of Object.entries({
|
||||
TELEGRAM_BOT_TOKEN: TOKEN,
|
||||
TELEGRAM_WEBHOOK_SECRET: SECRET,
|
||||
WORKER_URL: URL_,
|
||||
})) {
|
||||
if (!v) {
|
||||
console.error(`${k} is required`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const COMMANDS = [
|
||||
{ command: 'info', description: 'Show this group ID' },
|
||||
{ command: 'addgroup', description: '[admin] Authorize a group' },
|
||||
{ command: 'delgroup', description: '[admin] Deauthorize a group' },
|
||||
{ command: 'listgroup', description: '[admin] List authorized groups' },
|
||||
{ command: 'addapple', description: 'Track an Apple App Store app' },
|
||||
{ command: 'delapple', description: 'Stop tracking an Apple app' },
|
||||
{ command: 'addgoogle', description: 'Track a Google Play app' },
|
||||
{ command: 'delgoogle', description: 'Stop tracking a Google app' },
|
||||
{ command: 'listapp', description: 'List tracked apps in this group' },
|
||||
{ command: 'checkapp', description: 'Check update status of tracked apps' },
|
||||
{ command: 'checkappscore', description: 'Check scores + ratings of tracked apps' },
|
||||
{ command: 'rawappleapp', description: 'Dump raw Apple API JSON for an app' },
|
||||
{ command: 'rawgoogleapp', description: 'Dump raw Google API JSON for an app' },
|
||||
];
|
||||
|
||||
async function tg(method, payload) {
|
||||
if (DRY) {
|
||||
console.log(`[dry-run] ${method}`, JSON.stringify(payload, null, 2));
|
||||
return { ok: true, result: '(dry)' };
|
||||
}
|
||||
const res = await fetch(`https://api.telegram.org/bot${TOKEN}/${method}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
const body = await res.json();
|
||||
if (!body.ok) {
|
||||
console.error(`${method} failed`, body);
|
||||
process.exit(1);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
await tg('setWebhook', {
|
||||
url: URL_,
|
||||
secret_token: SECRET,
|
||||
allowed_updates: ['message'],
|
||||
});
|
||||
await tg('setMyCommands', { commands: COMMANDS });
|
||||
const info = await tg('getWebhookInfo', {});
|
||||
console.log('Webhook state:', JSON.stringify(info.result, null, 2));
|
||||
Reference in New Issue
Block a user