Add Telegram answer learning: poller + applier safety net
- New lib/telegram_answers.mjs: shared module that polls Telegram getUpdates, matches replies to needs_answer jobs, saves to answers.json, flips job to new - telegram_poller.mjs: lightweight cron script (every minute via OpenClaw) - Applier also processes replies at start of each run as safety net - sendTelegram now returns message_id, stored on job for reply matching - User replies "ACCEPT" to use AI answer, or types their own - Answers persist in answers.json and apply to ALL future jobs - Also includes: selectOptionFuzzy, multiple dialog handling, browser recovery, answers reload, per-job timeout bump to 10min Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,11 +6,14 @@ import { TELEGRAM_API_BASE, NOTIFY_RATE_LIMIT_MS } from './constants.mjs';
|
||||
|
||||
let lastSentAt = 0;
|
||||
|
||||
/**
|
||||
* Send a Telegram message. Returns the message_id on success (useful for tracking replies).
|
||||
*/
|
||||
export async function sendTelegram(settings, message) {
|
||||
const { bot_token, telegram_user_id } = settings.notifications;
|
||||
const { bot_token, telegram_user_id } = settings.notifications || {};
|
||||
if (!bot_token || !telegram_user_id) {
|
||||
console.log(`[notify] No Telegram config — would send: ${message.substring(0, 80)}`);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Rate limit to avoid Telegram API throttling
|
||||
@@ -32,14 +35,58 @@ export async function sendTelegram(settings, message) {
|
||||
}),
|
||||
});
|
||||
lastSentAt = Date.now();
|
||||
if (!res.ok) { console.error(`[notify] Telegram HTTP error: ${res.status}`); return; }
|
||||
if (!res.ok) { console.error(`[notify] Telegram HTTP error: ${res.status}`); return null; }
|
||||
const data = await res.json();
|
||||
if (!data.ok) console.error('[notify] Telegram error:', data.description);
|
||||
if (!data.ok) { console.error('[notify] Telegram error:', data.description); return null; }
|
||||
return data.result?.message_id || null;
|
||||
} catch (e) {
|
||||
console.error('[notify] Failed to send Telegram message:', e.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get updates from Telegram Bot API (long polling).
|
||||
* @param {string} botToken
|
||||
* @param {number} offset - Update ID offset (pass last_update_id + 1)
|
||||
* @param {number} timeout - Long poll timeout in seconds
|
||||
* @returns {Array} Array of update objects
|
||||
*/
|
||||
export async function getTelegramUpdates(botToken, offset = 0, timeout = 5) {
|
||||
const url = `${TELEGRAM_API_BASE}${botToken}/getUpdates`;
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ offset, timeout }),
|
||||
});
|
||||
if (!res.ok) return [];
|
||||
const data = await res.json();
|
||||
return data.ok ? (data.result || []) : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reply to a specific Telegram message.
|
||||
*/
|
||||
export async function replyTelegram(botToken, chatId, replyToMessageId, text) {
|
||||
const url = `${TELEGRAM_API_BASE}${botToken}/sendMessage`;
|
||||
try {
|
||||
await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
chat_id: chatId,
|
||||
text,
|
||||
reply_to_message_id: replyToMessageId,
|
||||
parse_mode: 'Markdown',
|
||||
}),
|
||||
});
|
||||
} catch { /* best effort */ }
|
||||
}
|
||||
|
||||
export function formatSearchSummary(added, skipped, platforms) {
|
||||
if (added === 0) return `🔍 *Job Search Complete*\nNo new jobs found this run.`;
|
||||
return `🔍 *Job Search Complete*\n${added} new job${added !== 1 ? 's' : ''} added to queue (${skipped} already seen)\nPlatforms: ${platforms.join(', ')}`;
|
||||
|
||||
Reference in New Issue
Block a user