Spaces:
Sleeping
Sleeping
File size: 7,663 Bytes
1dc6052 f057ca2 1dc6052 42d2d15 1dc6052 42d2d15 1dc6052 42d2d15 1dc6052 f057ca2 58ed9c6 f057ca2 58ed9c6 f057ca2 1dc6052 58ed9c6 1dc6052 58ed9c6 1dc6052 f057ca2 1dc6052 348ccc1 1dc6052 348ccc1 1dc6052 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | /**
* Telegram β resume PDF relay (Cloudflare Worker).
*
* WHY: Hugging Face Spaces block outbound traffic to api.telegram.org, so the HF
* app can RECEIVE Telegram webhooks but can't SEND replies. This tiny Worker runs
* on Cloudflare (which can reach Telegram), receives the webhook, calls the HF
* /api/generate endpoint (which works publicly), and sends the PDF back to Telegram.
*
* It acks Telegram instantly and finishes the ~30β60s job via ctx.waitUntil so
* Telegram never times out and retries.
*
* Deploy: paste into a Cloudflare Worker. Set these Variables/Secrets:
* TELEGRAM_BOT_TOKEN β from @BotFather
* ALLOWED_USER_IDS β comma-separated numeric Telegram user IDs (allowlist)
* HF_API_URL β https://<your-space>.hf.space
* HF_API_TOKEN β your API_SECRET_TOKEN (matches the HF secret)
* WEBHOOK_SECRET β optional; must match the setWebhook secret_token
* Then point the Telegram webhook at the Worker URL (see relay/README.md).
*/
const HELP =
"π Send me a *job link* (or paste the full job description) and I'll send back " +
"your tailored, ATS-optimized resume PDF.\n\n" +
"β’ Company/ATS links (Greenhouse, Lever, Ashby, Naukri) usually work directly.\n" +
"β’ If I can't read a LinkedIn/Indeed link, copy the description text and send that.\n\n" +
"Commands: /start, /help, /v1, /v2, /mode";
export default {
async fetch(request, env, ctx) {
if (request.method !== "POST") return new Response("ok"); // health/GET
if (env.WEBHOOK_SECRET &&
request.headers.get("x-telegram-bot-api-secret-token") !== env.WEBHOOK_SECRET) {
return new Response("forbidden", { status: 403 });
}
let update;
try { update = await request.json(); } catch { return new Response("ok"); }
ctx.waitUntil(handle(update, env)); // finish in background; ack now
return new Response("ok");
},
};
function parseAllowed(env) {
// Robust: tolerate quotes, spaces, newlines around the comma-separated ids.
return (env.ALLOWED_USER_IDS || "")
.replace(/["']/g, "")
.split(/[,\s]+/)
.map((s) => s.trim())
.filter(Boolean)
.map(Number)
.filter((n) => !Number.isNaN(n));
}
function tgUrl(env, method) {
return `https://api.telegram.org/bot${env.TELEGRAM_BOT_TOKEN}/${method}`;
}
async function tg(env, method, payload) {
try {
await fetch(tgUrl(env, method), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
} catch (_) { /* best-effort */ }
}
async function handle(update, env) {
const msg = update.message || update.edited_message;
if (!msg) return;
const chatId = msg.chat && msg.chat.id;
const userId = msg.from && msg.from.id;
const text = (msg.text || "").trim();
if (chatId == null) return;
// Diagnostic: /whoami always answers (before the allowlist) so you can compare
// your real Telegram id against what the Worker actually sees in its env var.
if (/^\/(whoami|id)\b/i.test(text)) {
await tg(env, "sendMessage", { chat_id: chatId, text:
`your id: ${userId}\n` +
`ALLOWED_USER_IDS (raw): ${JSON.stringify(env.ALLOWED_USER_IDS ?? null)}\n` +
`parsed: ${JSON.stringify(parseAllowed(env))}` });
return;
}
const allowed = parseAllowed(env);
if (!allowed.length || !allowed.includes(Number(userId))) {
await tg(env, "sendMessage", { chat_id: chatId, text:
`β Not authorized.\nyour id: ${userId}\n` +
`ALLOWED_USER_IDS seen: ${JSON.stringify(env.ALLOWED_USER_IDS ?? null)}` });
return;
}
// ββ Per-user version mode (Workers KV optional) ββββββββββββββββββββββββββ
const verKey = `ver:${userId}`;
let userVer = 'v2';
if (env.USER_STATE) {
try { userVer = (await env.USER_STATE.get(verKey)) || 'v2'; } catch (_) {}
}
if (/^\/v1\b/i.test(text)) {
if (env.USER_STATE) { try { await env.USER_STATE.put(verKey, 'v1'); } catch (_) {} }
await tg(env, "sendMessage", { chat_id: chatId, text: "β
Mode set to V1 (Structured keyword placement)." });
return;
}
if (/^\/v2\b/i.test(text)) {
if (env.USER_STATE) { try { await env.USER_STATE.put(verKey, 'v2'); } catch (_) {} }
await tg(env, "sendMessage", { chat_id: chatId, text: "β
Mode set to V2 (Natural AI sentence integration)." });
return;
}
if (/^\/mode\b/i.test(text)) {
await tg(env, "sendMessage", { chat_id: chatId, text: `Current mode: ${userVer.toUpperCase()}` });
return;
}
if (!text || /^\/(start|help)\b/i.test(text)) {
await tg(env, "sendMessage", { chat_id: chatId, text: HELP, parse_mode: "Markdown" });
return;
}
const urlMatch = text.match(/https?:\/\/\S+/);
const form = new FormData();
if (urlMatch) {
await tg(env, "sendMessage", { chat_id: chatId, text: "π Reading the job & generating your resumeβ¦ V2 runs several AI models, this can take 1β2 min." });
form.append("jd_url", urlMatch[0].replace(/[).,]+$/, ""));
} else if (text.length >= 200) {
await tg(env, "sendMessage", { chat_id: chatId, text: "βοΈ Generating your ATS resumeβ¦ V2 runs several AI models, this can take 1β2 min." });
form.append("jd_text", text);
} else {
await tg(env, "sendMessage", { chat_id: chatId, text: "Send a job link, or paste the full job description (a paragraph or more)." });
return;
}
form.append("maximum_ats_mode", "1");
form.append("version", userVer);
let res;
try {
const r = await fetch(`${env.HF_API_URL.replace(/\/$/, "")}/api/generate`, {
method: "POST",
headers: { "X-Api-Token": env.HF_API_TOKEN || "" },
body: form,
});
res = await r.json();
} catch (e) {
await tg(env, "sendMessage", { chat_id: chatId, text: "β Generation service is waking up or busy. Try again in a minute." });
return;
}
if (res.error === "jd_unreadable") {
await tg(env, "sendMessage", { chat_id: chatId, text: "β οΈ I couldn't read that link (LinkedIn/Indeed block server access). Please copy the *job description text* and send it to me." , parse_mode: "Markdown" });
return;
}
if (res.error) {
await tg(env, "sendMessage", { chat_id: chatId, text: `β ${res.detail || res.error}` });
return;
}
const cov = res.external_coverage_pct != null ? res.external_coverage_pct
: (res.scores && res.scores.jd_match);
if (res.pdf_b64) {
await sendDoc(env, chatId, res.pdf_b64, "Saiteja_Tirunagari_Resume.pdf", "application/pdf",
`β
Tailored resume β ~${cov ?? "?"}% JD keyword coverage.`);
} else if (res.tex_b64) {
await sendDoc(env, chatId, res.tex_b64, "Saiteja_Tirunagari_Resume.tex", "application/x-tex",
"β οΈ PDF compile failed β here's the .tex (compile at overleaf.com).");
} else {
await tg(env, "sendMessage", { chat_id: chatId, text: "β Generation produced no file. Try again." });
}
}
function b64ToBytes(b64) {
const bin = atob(b64);
const bytes = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
return bytes;
}
async function sendDoc(env, chatId, b64, filename, mime, caption) {
try {
const fd = new FormData();
fd.append("chat_id", String(chatId));
fd.append("caption", caption.slice(0, 1024));
fd.append("document", new Blob([b64ToBytes(b64)], { type: mime }), filename);
await fetch(tgUrl(env, "sendDocument"), { method: "POST", body: fd });
} catch (_) {
await tg(env, "sendMessage", { chat_id: chatId, text: "β Couldn't upload the file to Telegram. Try again." });
}
}
|