Repository navigation
Expand file tree
/
Copy pathmessage.ts
More file actions
85 lines (71 loc) · 2.98 KB
/
Copy pathmessage.ts
File metadata and controls
85 lines (71 loc) · 2.98 KB
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
// Handles plain (non-command) text messages: continuing an in-progress
// owner conversation (e.g. "waiting_for_url"), or a normal user's message,
// which follows the no-permanent-record path from spec section 6.
import type { Env } from "../env";
import { isOwner } from "../telegram";
import { changeButtonUrl } from "../admin/buttons";
import { clearConversationState, getConversationState } from "../storage/kv";
function senderId(context: any): number | undefined {
return context.payload?.from?.id;
}
async function continueOwnerConversation(context: any, env: Env, userId: number): Promise<boolean> {
const state = await getConversationState(env, userId);
if (!state) return false;
if (state.state === "waiting_for_url") {
const newUrl = (context.text ?? "").trim();
const buttonId = state.data?.buttonId as string;
if (!newUrl || !buttonId) {
await clearConversationState(env, userId);
return true;
}
const result = await changeButtonUrl(context.bot, env, buttonId, newUrl);
await clearConversationState(env, userId);
if (!result) {
await context.send("That button no longer exists.");
} else {
await context.send(
`Updated the button. ${result.updated} published message(s) now point to the new URL.`,
);
}
return true;
}
return false;
}
// Random short id, good enough for a temporary, non-guessable ticket
// reference (not a security boundary — the owner is the only reader).
function newTicketId(): string {
return Math.random().toString(36).slice(2, 8);
}
async function handleNormalUserMessage(context: any, env: Env): Promise<void> {
const text = (context.text ?? "").trim();
if (!text) return;
// Lightweight anonymous support workflow (spec section 18): the owner
// sees a ticket number and the message, never the sender's Telegram ID
// or username, unless they explicitly look it up.
const ticketId = newTicketId();
await env.STATE.put(
`support:${ticketId}`,
JSON.stringify({ userId: senderId(context) }),
{ expirationTtl: 24 * 60 * 60 },
);
await context.bot.api.sendMessage({
chat_id: Number(env.OWNER_ID),
text: `Anonymous request #${ticketId}\n\n${text}\n\n(Reply with /reply ${ticketId} <message>)`,
});
await context.send("Thanks — your message has been passed along. You'll get a reply here.");
}
export async function handleMessage(context: any, env: Env): Promise<void> {
const userId = senderId(context);
if (!userId) return;
const text = (context.text ?? "").trim();
// Commands are handled by their own bot.command() registrations; don't
// let an unrecognized/duplicate pass-through also treat it as plain text.
if (text.startsWith("/")) return;
if (isOwner(env, userId)) {
// Owner sent plain text — only meaningful if it's continuing a flow
// like /setbuttonurl. Otherwise, stay quiet rather than being noisy.
await continueOwnerConversation(context, env, userId);
return;
}
await handleNormalUserMessage(context, env);
}