Missions may place the token with {token} and name the embed host explicitly (Telegram Mini App launch links)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-19 16:37:34 -05:00
parent 693a04f08d
commit 3199347ca7
+6 -2
View File
@@ -124,7 +124,9 @@ const server = http.createServer(async (req, res) => {
if (rewards.completed(me.memberId, m.id)) return json(res, 400, { error: 'You already completed this one.' }); if (rewards.completed(me.memberId, m.id)) return json(res, 400, { error: 'You already completed this one.' });
if (limited('start:' + me.memberId, 20, 3600000)) return json(res, 429, { error: 'Easy. Twenty starts an hour is plenty.' }); if (limited('start:' + me.memberId, 20, 3600000)) return json(res, 429, { error: 'Easy. Twenty starts an hour is plenty.' });
const t = missions.issue(me.memberId, m.id); const t = missions.issue(me.memberId, m.id);
const url = m.url + (m.url.includes('?') ? '&' : '?') + 'ph=' + t.t; // a mission URL may place the token itself with {token} (a Telegram Mini App takes it in
// ?startapp=, not as our own query string); otherwise it is appended as ?ph=
const url = m.url.includes('{token}') ? m.url.replace('{token}', t.t) : m.url + (m.url.includes('?') ? '&' : '?') + 'ph=' + t.t;
return json(res, 200, { ok: true, token: t.t, url, dwell: m.dwell || 30, expires: t.exp }); return json(res, 200, { ok: true, token: t.t, url, dwell: m.dwell || 30, expires: t.exp });
} }
if (p === '/api/my/submit' && req.method === 'POST') { if (p === '/api/my/submit' && req.method === 'POST') {
@@ -147,6 +149,8 @@ const server = http.createServer(async (req, res) => {
const b = await readBody(req); const b = await readBody(req);
const id = String(b.id || '').trim().toLowerCase().replace(/[^a-z0-9-]/g, '').slice(0, 40); if (!id) return json(res, 400, { error: 'id required' }); const id = String(b.id || '').trim().toLowerCase().replace(/[^a-z0-9-]/g, '').slice(0, 40); if (!id) return json(res, 400, { error: 'id required' });
let host = ''; try { host = new URL(String(b.url)).hostname.replace(/^www\./, ''); } catch (e) { return json(res, 400, { error: 'url must be a full https URL' }); } let host = ''; try { host = new URL(String(b.url)).hostname.replace(/^www\./, ''); } catch (e) { return json(res, 400, { error: 'url must be a full https URL' }); }
// the origin the embed calls from can differ from the link (a t.me launch link opens a Mini App on its own host)
if (b.host) host = String(b.host).trim().toLowerCase().replace(/^https?:\/\//, '').replace(/^www\./, '').replace(/\/.*$/, '');
missions.save({ id, site: String(b.site || host).slice(0, 60), host, name: String(b.name || '').slice(0, 80), brief: String(b.brief || '').slice(0, 400), url: String(b.url), dwell: Math.max(5, Number(b.dwell) || 45), slots: Math.max(1, Number(b.slots) || 1), budget: Math.max(0, Number(b.budget) || 0), active: b.active !== false }); missions.save({ id, site: String(b.site || host).slice(0, 60), host, name: String(b.name || '').slice(0, 80), brief: String(b.brief || '').slice(0, 400), url: String(b.url), dwell: Math.max(5, Number(b.dwell) || 45), slots: Math.max(1, Number(b.slots) || 1), budget: Math.max(0, Number(b.budget) || 0), active: b.active !== false });
return json(res, 200, { ok: true, missions: missions.list() }); return json(res, 200, { ok: true, missions: missions.list() });
} }
@@ -155,7 +159,7 @@ const server = http.createServer(async (req, res) => {
if (p === '/api/admin/faucet/tick' && req.method === 'POST') { const r = await faucet.tick(notify); return json(res, 200, Object.assign(r, { balance: await faucet.balance() })); } if (p === '/api/admin/faucet/tick' && req.method === 'POST') { const r = await faucet.tick(notify); return json(res, 200, Object.assign(r, { balance: await faucet.balance() })); }
if (p === '/api/admin/embed-test') { // mint a token for any mission so the embed can be tried without a hunter if (p === '/api/admin/embed-test') { // mint a token for any mission so the embed can be tried without a hunter
const m = missions.get(String(u.searchParams.get('id') || '')); if (!m) return json(res, 404, { error: 'no such mission' }); const m = missions.get(String(u.searchParams.get('id') || '')); if (!m) return json(res, 404, { error: 'no such mission' });
const t = missions.issue(0, m.id); return json(res, 200, { url: m.url + (m.url.includes('?') ? '&' : '?') + 'ph=' + t.t, token: t.t, dwell: m.dwell }); const t = missions.issue(0, m.id); return json(res, 200, { url: m.url.includes('{token}') ? m.url.replace('{token}', t.t) : m.url + (m.url.includes('?') ? '&' : '?') + 'ph=' + t.t, token: t.t, dwell: m.dwell });
} }
return json(res, 404, { error: 'No such admin call.' }); return json(res, 404, { error: 'No such admin call.' });
} }