easyoref 1.27.9 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/types.d.ts +1 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/bot.js +153 -71
- package/dist/bot.js.map +1 -1
- package/dist/gif-state.js +1 -1
- package/dist/gif-state.js.map +1 -1
- package/dist/handlers/admin.d.ts +3 -0
- package/dist/handlers/admin.d.ts.map +1 -0
- package/dist/handlers/admin.js +119 -0
- package/dist/handlers/admin.js.map +1 -0
- package/dist/handlers/inline.d.ts +3 -0
- package/dist/handlers/inline.d.ts.map +1 -0
- package/dist/handlers/inline.js +88 -0
- package/dist/handlers/inline.js.map +1 -0
- package/dist/handlers/qa.d.ts +3 -0
- package/dist/handlers/qa.d.ts.map +1 -0
- package/dist/handlers/qa.js +47 -0
- package/dist/handlers/qa.js.map +1 -0
- package/dist/handlers/settings.d.ts +3 -0
- package/dist/handlers/settings.d.ts.map +1 -0
- package/dist/handlers/settings.js +110 -0
- package/dist/handlers/settings.js.map +1 -0
- package/dist/handlers/shelter.d.ts +7 -0
- package/dist/handlers/shelter.d.ts.map +1 -0
- package/dist/handlers/shelter.js +60 -0
- package/dist/handlers/shelter.js.map +1 -0
- package/dist/handlers/start.d.ts +18 -0
- package/dist/handlers/start.d.ts.map +1 -0
- package/dist/handlers/start.js +182 -0
- package/dist/handlers/start.js.map +1 -0
- package/dist/middleware/tier.d.ts +7 -0
- package/dist/middleware/tier.d.ts.map +1 -0
- package/dist/middleware/tier.js +17 -0
- package/dist/middleware/tier.js.map +1 -0
- package/package.json +1 -2
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { config, getAllUsers, getBotStrings, getUser, setUserTier, } from "@easyoref/shared";
|
|
2
|
+
import * as logger from "@easyoref/shared/logger";
|
|
3
|
+
function isAdmin(chatId) {
|
|
4
|
+
return config.adminChatIds.includes(Number(chatId));
|
|
5
|
+
}
|
|
6
|
+
function getAdminLang(chatId) {
|
|
7
|
+
// Admin lang: we don't async lookup here, default to "ru"
|
|
8
|
+
return "ru";
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Resolve a grant/revoke target to a chatId string.
|
|
12
|
+
* Accepts: numeric ID, @username, t.me/username URL.
|
|
13
|
+
*/
|
|
14
|
+
async function resolveTarget(bot, input) {
|
|
15
|
+
const trimmed = input.trim();
|
|
16
|
+
// Numeric chat ID
|
|
17
|
+
if (/^-?\d+$/.test(trimmed)) {
|
|
18
|
+
return { chatId: trimmed, display: trimmed };
|
|
19
|
+
}
|
|
20
|
+
// Extract username from @mention or t.me/ link
|
|
21
|
+
let username;
|
|
22
|
+
if (trimmed.startsWith("@")) {
|
|
23
|
+
username = trimmed;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
const match = trimmed.match(/(?:https?:\/\/)?t(?:elegram)?\.me\/([a-zA-Z_]\w{3,})/);
|
|
27
|
+
if (match) {
|
|
28
|
+
username = `@${match[1]}`;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (!username) {
|
|
32
|
+
return { error: "Invalid target. Use chat ID, @username, or t.me/link." };
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const chat = await bot.api.getChat(username);
|
|
36
|
+
return { chatId: String(chat.id), display: `${username} (${chat.id})` };
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return { error: `Could not resolve ${username}. Bot must have interacted with this user/group.` };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export function registerAdminHandler(bot) {
|
|
43
|
+
// /grant <target> — upgrade user to Pro
|
|
44
|
+
bot.command("grant", async (ctx) => {
|
|
45
|
+
const adminId = String(ctx.chat.id);
|
|
46
|
+
if (!isAdmin(adminId)) {
|
|
47
|
+
const user = await getUser(adminId);
|
|
48
|
+
const lang = (user?.language ?? "ru");
|
|
49
|
+
await ctx.reply(getBotStrings(lang).adminUnauthorized);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const s = getBotStrings(getAdminLang(adminId));
|
|
53
|
+
const args = ctx.match?.trim();
|
|
54
|
+
if (!args) {
|
|
55
|
+
await ctx.reply(s.adminGrantUsage);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const resolved = await resolveTarget(bot, args);
|
|
59
|
+
if ("error" in resolved) {
|
|
60
|
+
await ctx.reply(resolved.error);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const success = await setUserTier(resolved.chatId, "pro");
|
|
64
|
+
if (success) {
|
|
65
|
+
await ctx.reply(s.adminGranted.replace("{target}", resolved.display));
|
|
66
|
+
logger.info("Admin: granted pro", { adminId, target: resolved.display });
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
await ctx.reply(s.adminUserNotFound.replace("{target}", resolved.display));
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
// /revoke <target> — downgrade user to Free
|
|
73
|
+
bot.command("revoke", async (ctx) => {
|
|
74
|
+
const adminId = String(ctx.chat.id);
|
|
75
|
+
if (!isAdmin(adminId)) {
|
|
76
|
+
const user = await getUser(adminId);
|
|
77
|
+
const lang = (user?.language ?? "ru");
|
|
78
|
+
await ctx.reply(getBotStrings(lang).adminUnauthorized);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const s = getBotStrings(getAdminLang(adminId));
|
|
82
|
+
const args = ctx.match?.trim();
|
|
83
|
+
if (!args) {
|
|
84
|
+
await ctx.reply("Usage: /revoke <chatId or @username or t.me/link>");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const resolved = await resolveTarget(bot, args);
|
|
88
|
+
if ("error" in resolved) {
|
|
89
|
+
await ctx.reply(resolved.error);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const success = await setUserTier(resolved.chatId, "free");
|
|
93
|
+
if (success) {
|
|
94
|
+
await ctx.reply(s.adminRevoked.replace("{target}", resolved.display));
|
|
95
|
+
logger.info("Admin: revoked pro", { adminId, target: resolved.display });
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
await ctx.reply(s.adminUserNotFound.replace("{target}", resolved.display));
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
// /users — list all registered users
|
|
102
|
+
bot.command("users", async (ctx) => {
|
|
103
|
+
const adminId = String(ctx.chat.id);
|
|
104
|
+
if (!isAdmin(adminId)) {
|
|
105
|
+
const user = await getUser(adminId);
|
|
106
|
+
const lang = (user?.language ?? "ru");
|
|
107
|
+
await ctx.reply(getBotStrings(lang).adminUnauthorized);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const allUsers = await getAllUsers();
|
|
111
|
+
if (allUsers.length === 0) {
|
|
112
|
+
await ctx.reply("No registered users.");
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const lines = allUsers.map((u) => `• <code>${u.chatId}</code> — ${u.tier} — ${u.language} — ${u.areas.join(", ")}`);
|
|
116
|
+
await ctx.reply(`<b>Registered Users (${allUsers.length}):</b>\n\n${lines.join("\n")}`, { parse_mode: "HTML" });
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=admin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin.js","sourceRoot":"","sources":["../../src/handlers/admin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inline.d.ts","sourceRoot":"","sources":["../../src/handlers/inline.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAGlC,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAmFpD"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { runQa } from "@easyoref/agent";
|
|
2
|
+
import { getActiveSession, getUser } from "@easyoref/shared";
|
|
3
|
+
import * as logger from "@easyoref/shared/logger";
|
|
4
|
+
export function registerInlineHandler(bot) {
|
|
5
|
+
bot.on("inline_query", async (ctx) => {
|
|
6
|
+
const query = ctx.inlineQuery.query.trim();
|
|
7
|
+
const results = [];
|
|
8
|
+
try {
|
|
9
|
+
if (!query) {
|
|
10
|
+
// Status widget — show current alert status from Redis
|
|
11
|
+
const session = await getActiveSession();
|
|
12
|
+
if (session) {
|
|
13
|
+
const phase = session.phase;
|
|
14
|
+
const areas = session.alertAreas.slice(0, 3).join(", ");
|
|
15
|
+
results.push({
|
|
16
|
+
type: "article",
|
|
17
|
+
id: "current_status",
|
|
18
|
+
title: `\u{1F6A8} Active Alert (${phase})`,
|
|
19
|
+
description: areas,
|
|
20
|
+
input_message_content: {
|
|
21
|
+
message_text: `<b>\u{1F6A8} Active Alert</b>\nPhase: ${phase}\nAreas: ${areas}`,
|
|
22
|
+
parse_mode: "HTML",
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
results.push({
|
|
28
|
+
type: "article",
|
|
29
|
+
id: "no_alerts",
|
|
30
|
+
title: "\u2705 No Active Alerts",
|
|
31
|
+
description: "All clear",
|
|
32
|
+
input_message_content: {
|
|
33
|
+
message_text: "<b>\u2705 No Active Alerts</b>\nEasyOref is monitoring for threats.",
|
|
34
|
+
parse_mode: "HTML",
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
// Q&A — run the QA graph
|
|
41
|
+
const chatId = String(ctx.from.id);
|
|
42
|
+
const user = await getUser(chatId);
|
|
43
|
+
if (user) {
|
|
44
|
+
const answer = await runQa(query, chatId);
|
|
45
|
+
const preview = answer.slice(0, 50) + (answer.length > 50 ? "…" : "");
|
|
46
|
+
results.push({
|
|
47
|
+
type: "article",
|
|
48
|
+
id: "qa_answer",
|
|
49
|
+
title: preview,
|
|
50
|
+
description: answer.slice(0, 100),
|
|
51
|
+
input_message_content: {
|
|
52
|
+
message_text: answer,
|
|
53
|
+
parse_mode: "HTML",
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
results.push({
|
|
59
|
+
type: "article",
|
|
60
|
+
id: "not_registered",
|
|
61
|
+
title: "Not registered",
|
|
62
|
+
description: "Use /start to register",
|
|
63
|
+
input_message_content: {
|
|
64
|
+
message_text: "Please start the bot first: @easyorefbot",
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
logger.error("Inline query failed", {
|
|
72
|
+
error: String(err),
|
|
73
|
+
query: query.slice(0, 100),
|
|
74
|
+
});
|
|
75
|
+
results.push({
|
|
76
|
+
type: "article",
|
|
77
|
+
id: "error",
|
|
78
|
+
title: "Error",
|
|
79
|
+
description: "Could not process query",
|
|
80
|
+
input_message_content: {
|
|
81
|
+
message_text: "An error occurred. Please try again.",
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
await ctx.answerInlineQuery(results, { cache_time: 30 });
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=inline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inline.js","sourceRoot":"","sources":["../../src/handlers/inline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qa.d.ts","sourceRoot":"","sources":["../../src/handlers/qa.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAElC,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CA6ChD"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { runQa } from "@easyoref/agent";
|
|
2
|
+
import { config, getBotStrings, getRedis, getUser, } from "@easyoref/shared";
|
|
3
|
+
import * as logger from "@easyoref/shared/logger";
|
|
4
|
+
export function registerQaHandler(bot) {
|
|
5
|
+
bot.on("message:text", async (ctx) => {
|
|
6
|
+
if (ctx.chat.type !== "private")
|
|
7
|
+
return;
|
|
8
|
+
const chatId = String(ctx.chat.id);
|
|
9
|
+
// Skip /commands — let grammY command handlers handle them
|
|
10
|
+
if (ctx.message.text.startsWith("/"))
|
|
11
|
+
return;
|
|
12
|
+
// Skip reply keyboard button presses (shelter/settings buttons)
|
|
13
|
+
const btnTexts = ["ru", "en", "he", "ar"].flatMap((l) => {
|
|
14
|
+
const s = getBotStrings(l);
|
|
15
|
+
return [s.btnShelter, s.btnSettings, s.skipLocationBtn];
|
|
16
|
+
});
|
|
17
|
+
if (btnTexts.includes(ctx.message.text))
|
|
18
|
+
return;
|
|
19
|
+
const user = await getUser(chatId);
|
|
20
|
+
const lang = (user?.language ?? "ru");
|
|
21
|
+
const bs = getBotStrings(lang);
|
|
22
|
+
if (!user) {
|
|
23
|
+
await ctx.reply(bs.qaNotRegistered);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
// Rate limit: qaRateLimitPerMin per user (Redis INCR + EXPIRE)
|
|
27
|
+
const redis = getRedis();
|
|
28
|
+
const rateLimitKey = `qa:rate:${chatId}`;
|
|
29
|
+
const count = await redis.incr(rateLimitKey);
|
|
30
|
+
if (count === 1)
|
|
31
|
+
await redis.expire(rateLimitKey, 60);
|
|
32
|
+
if (count > config.agent.qaRateLimitPerMin) {
|
|
33
|
+
await ctx.reply(bs.qaRateLimit);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
await ctx.replyWithChatAction("typing");
|
|
37
|
+
try {
|
|
38
|
+
const answer = await runQa(ctx.message.text, chatId);
|
|
39
|
+
await ctx.reply(answer, { parse_mode: "HTML" });
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
logger.error("Q&A failed", { error: String(err), chatId });
|
|
43
|
+
await ctx.reply(bs.qaError);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=qa.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qa.js","sourceRoot":"","sources":["../../src/handlers/qa.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACxC,OAAO,EACL,MAAM,EACN,aAAa,EACb,QAAQ,EACR,OAAO,GAER,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,MAAM,MAAM,yBAAyB,CAAC;AAGlD,MAAM,UAAU,iBAAiB,CAAC,GAAQ;IACxC,GAAG,CAAC,EAAE,CAAC,cAAc,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QACnC,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS;YAAE,OAAO;QAExC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAEnC,2DAA2D;QAC3D,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO;QAE7C,gEAAgE;QAChE,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;YACtD,MAAM,CAAC,GAAG,aAAa,CAAC,CAAa,CAAC,CAAC;YACvC,OAAO,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,eAAe,CAAC,CAAC;QAC1D,CAAC,CAAC,CAAC;QACH,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC;YAAE,OAAO;QAEhD,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;QACnC,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,QAAQ,IAAI,IAAI,CAAa,CAAC;QAClD,MAAM,EAAE,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;QAE/B,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,eAAe,CAAC,CAAC;YACpC,OAAO;QACT,CAAC;QAED,+DAA+D;QAC/D,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;QACzB,MAAM,YAAY,GAAG,WAAW,MAAM,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC7C,IAAI,KAAK,KAAK,CAAC;YAAE,MAAM,KAAK,CAAC,MAAM,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;QACtD,IAAI,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAC;YAC3C,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC;YAChC,OAAO;QACT,CAAC;QAED,MAAM,GAAG,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC;QAExC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YACrD,MAAM,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC;QAClD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CAAC,YAAY,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;YAC3D,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/handlers/settings.ts"],"names":[],"mappings":"AASA,OAAO,EAA4B,KAAK,GAAG,EAAE,MAAM,QAAQ,CAAC;AAmB5D,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAuGtD"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { getBotStrings, getUser, isValidLanguage, saveUser, translateAreas, } from "@easyoref/shared";
|
|
2
|
+
import * as logger from "@easyoref/shared/logger";
|
|
3
|
+
import { InlineKeyboard, Keyboard } from "grammy";
|
|
4
|
+
import { mainMenuKeyboard, setAwaitingLocation } from "./start.js";
|
|
5
|
+
const settingsKeyboard = (lang) => {
|
|
6
|
+
const s = getBotStrings(lang);
|
|
7
|
+
return new InlineKeyboard()
|
|
8
|
+
.text(s.settingsLanguage, "settings:lang")
|
|
9
|
+
.text(s.settingsLocation, "settings:location")
|
|
10
|
+
.row()
|
|
11
|
+
.text(s.settingsInfo, "settings:info");
|
|
12
|
+
};
|
|
13
|
+
const langKeyboard = new InlineKeyboard()
|
|
14
|
+
.text("🇷🇺 Русский", "setlang:ru")
|
|
15
|
+
.text("🇬🇧 English", "setlang:en")
|
|
16
|
+
.row()
|
|
17
|
+
.text("🇮🇱 עברית", "setlang:he")
|
|
18
|
+
.text("🇸🇦 العربية", "setlang:ar");
|
|
19
|
+
export function registerSettingsHandler(bot) {
|
|
20
|
+
// ── /settings command ───────────────────────────────
|
|
21
|
+
bot.command("settings", async (ctx) => {
|
|
22
|
+
const chatId = String(ctx.chat.id);
|
|
23
|
+
const user = await getUser(chatId);
|
|
24
|
+
if (!user) {
|
|
25
|
+
await ctx.reply(getBotStrings("ru").qaNotRegistered);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const lang = user.language;
|
|
29
|
+
const s = getBotStrings(lang);
|
|
30
|
+
await ctx.reply(s.settingsTitle, { reply_markup: settingsKeyboard(lang) });
|
|
31
|
+
});
|
|
32
|
+
// ── Settings button from reply keyboard ─────────────
|
|
33
|
+
bot.hears([
|
|
34
|
+
getBotStrings("ru").btnSettings,
|
|
35
|
+
getBotStrings("en").btnSettings,
|
|
36
|
+
getBotStrings("he").btnSettings,
|
|
37
|
+
getBotStrings("ar").btnSettings,
|
|
38
|
+
], async (ctx) => {
|
|
39
|
+
const chatId = String(ctx.chat.id);
|
|
40
|
+
const user = await getUser(chatId);
|
|
41
|
+
if (!user)
|
|
42
|
+
return;
|
|
43
|
+
const lang = user.language;
|
|
44
|
+
const s = getBotStrings(lang);
|
|
45
|
+
await ctx.reply(s.settingsTitle, {
|
|
46
|
+
reply_markup: settingsKeyboard(lang),
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
// ── Language sub-flow ───────────────────────────────
|
|
50
|
+
bot.callbackQuery("settings:lang", async (ctx) => {
|
|
51
|
+
await ctx.answerCallbackQuery();
|
|
52
|
+
const chatId = String(ctx.from.id);
|
|
53
|
+
const user = await getUser(chatId);
|
|
54
|
+
const lang = (user?.language ?? "ru");
|
|
55
|
+
const s = getBotStrings(lang);
|
|
56
|
+
await ctx.editMessageText(s.askLanguage, { reply_markup: langKeyboard });
|
|
57
|
+
});
|
|
58
|
+
bot.callbackQuery(/^setlang:/, async (ctx) => {
|
|
59
|
+
const lang = ctx.callbackQuery.data.slice(8);
|
|
60
|
+
if (!isValidLanguage(lang)) {
|
|
61
|
+
await ctx.answerCallbackQuery("Invalid language");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
await ctx.answerCallbackQuery();
|
|
65
|
+
const chatId = String(ctx.from.id);
|
|
66
|
+
const user = await getUser(chatId);
|
|
67
|
+
if (!user)
|
|
68
|
+
return;
|
|
69
|
+
await saveUser({ ...user, language: lang, lastActiveAt: Date.now() });
|
|
70
|
+
const s = getBotStrings(lang);
|
|
71
|
+
await ctx.editMessageReplyMarkup({ reply_markup: undefined }).catch(() => { });
|
|
72
|
+
await ctx.reply(s.languageSaved, {
|
|
73
|
+
reply_markup: mainMenuKeyboard(lang),
|
|
74
|
+
});
|
|
75
|
+
logger.info("Language changed via settings", { chatId, lang });
|
|
76
|
+
});
|
|
77
|
+
// ── Location sub-flow ──────────────────────────────
|
|
78
|
+
bot.callbackQuery("settings:location", async (ctx) => {
|
|
79
|
+
await ctx.answerCallbackQuery();
|
|
80
|
+
const chatId = String(ctx.from.id);
|
|
81
|
+
const user = await getUser(chatId);
|
|
82
|
+
const lang = (user?.language ?? "ru");
|
|
83
|
+
const s = getBotStrings(lang);
|
|
84
|
+
const locationKb = new Keyboard()
|
|
85
|
+
.requestLocation(s.shareLocationBtn)
|
|
86
|
+
.resized()
|
|
87
|
+
.oneTime();
|
|
88
|
+
await ctx.editMessageReplyMarkup({ reply_markup: undefined }).catch(() => { });
|
|
89
|
+
setAwaitingLocation(chatId);
|
|
90
|
+
await ctx.reply(s.askLocation, { reply_markup: locationKb });
|
|
91
|
+
});
|
|
92
|
+
// ── Info sub-flow ──────────────────────────────────
|
|
93
|
+
bot.callbackQuery("settings:info", async (ctx) => {
|
|
94
|
+
await ctx.answerCallbackQuery();
|
|
95
|
+
const chatId = String(ctx.from.id);
|
|
96
|
+
const user = await getUser(chatId);
|
|
97
|
+
if (!user)
|
|
98
|
+
return;
|
|
99
|
+
const lang = user.language;
|
|
100
|
+
const s = getBotStrings(lang);
|
|
101
|
+
const translatedAreas = translateAreas(user.areas.join(", "), lang);
|
|
102
|
+
const text = s.infoDisplay
|
|
103
|
+
.replace("{chatId}", user.chatId)
|
|
104
|
+
.replace("{lang}", lang)
|
|
105
|
+
.replace("{tier}", user.tier)
|
|
106
|
+
.replace("{areas}", translatedAreas);
|
|
107
|
+
await ctx.editMessageText(text, { parse_mode: "HTML" });
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=settings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/handlers/settings.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shelter search handler — responds to location messages with nearest bomb shelters.
|
|
3
|
+
* Safety-critical feature: available to ALL tiers (free + pro).
|
|
4
|
+
*/
|
|
5
|
+
import { type Bot } from "grammy";
|
|
6
|
+
export declare function registerShelterHandler(bot: Bot): void;
|
|
7
|
+
//# sourceMappingURL=shelter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shelter.d.ts","sourceRoot":"","sources":["../../src/handlers/shelter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAUH,OAAO,EAAY,KAAK,GAAG,EAAE,MAAM,QAAQ,CAAC;AAiB5C,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAmDrD"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shelter search handler — responds to location messages with nearest bomb shelters.
|
|
3
|
+
* Safety-critical feature: available to ALL tiers (free + pro).
|
|
4
|
+
*/
|
|
5
|
+
import { fetchNearestShelters, getBotStrings, getUser, } from "@easyoref/shared";
|
|
6
|
+
import * as logger from "@easyoref/shared/logger";
|
|
7
|
+
import { Keyboard } from "grammy";
|
|
8
|
+
function formatShelterList(title, nearest) {
|
|
9
|
+
const lines = nearest.map((s, i) => {
|
|
10
|
+
const dist = s.distanceKm < 1
|
|
11
|
+
? `${Math.round(s.distanceKm * 1000)}m`
|
|
12
|
+
: `${s.distanceKm.toFixed(1)}km`;
|
|
13
|
+
return (`${i + 1}. <b>${s.name}</b>${s.address ? ` — ${s.address}` : ""}\n` +
|
|
14
|
+
` 📍 ${dist} (~${s.walkingMinutes} min walk)\n` +
|
|
15
|
+
` <a href="${s.googleMapsUrl}">Open in Maps</a>`);
|
|
16
|
+
});
|
|
17
|
+
return `${title}\n\n${lines.join("\n\n")}`;
|
|
18
|
+
}
|
|
19
|
+
export function registerShelterHandler(bot) {
|
|
20
|
+
// Reply keyboard "Shelter" button handler
|
|
21
|
+
bot.hears([
|
|
22
|
+
getBotStrings("ru").btnShelter,
|
|
23
|
+
getBotStrings("en").btnShelter,
|
|
24
|
+
getBotStrings("he").btnShelter,
|
|
25
|
+
getBotStrings("ar").btnShelter,
|
|
26
|
+
], async (ctx) => {
|
|
27
|
+
const chatId = String(ctx.chat.id);
|
|
28
|
+
const user = await getUser(chatId);
|
|
29
|
+
const lang = (user?.language ?? "ru");
|
|
30
|
+
const s = getBotStrings(lang);
|
|
31
|
+
const locationKb = new Keyboard()
|
|
32
|
+
.requestLocation(s.shareLocationBtn)
|
|
33
|
+
.resized()
|
|
34
|
+
.oneTime();
|
|
35
|
+
await ctx.reply(s.askLocation, { reply_markup: locationKb });
|
|
36
|
+
});
|
|
37
|
+
// Note: location messages are handled in start.ts (onboarding + area detection)
|
|
38
|
+
// This handler runs AFTER start.ts location handler via next()
|
|
39
|
+
bot.on("message:location", async (ctx) => {
|
|
40
|
+
const { latitude, longitude } = ctx.message.location;
|
|
41
|
+
const chatId = String(ctx.chat.id);
|
|
42
|
+
const user = await getUser(chatId);
|
|
43
|
+
const lang = (user?.language ?? "en");
|
|
44
|
+
const bs = getBotStrings(lang);
|
|
45
|
+
logger.info("Shelter search requested", { chatId, latitude, longitude });
|
|
46
|
+
await ctx.replyWithChatAction("find_location").catch(() => { });
|
|
47
|
+
const nearest = await fetchNearestShelters(latitude, longitude);
|
|
48
|
+
if (nearest.length === 0) {
|
|
49
|
+
await ctx.reply(bs.shelterNone, {
|
|
50
|
+
link_preview_options: { is_disabled: true },
|
|
51
|
+
});
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
await ctx.reply(formatShelterList(bs.shelterTitle, nearest), {
|
|
55
|
+
parse_mode: "HTML",
|
|
56
|
+
link_preview_options: { is_disabled: true },
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=shelter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shelter.js","sourceRoot":"","sources":["../../src/handlers/shelter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Language } from "@easyoref/shared";
|
|
2
|
+
import { Keyboard, type Bot } from "grammy";
|
|
3
|
+
export declare function initDefaultAreas(): void;
|
|
4
|
+
/** Persistent reply keyboard shown after registration */
|
|
5
|
+
export declare function mainMenuKeyboard(lang: Language): Keyboard;
|
|
6
|
+
/** Mark a chatId as awaiting location (used by settings handler too) */
|
|
7
|
+
export declare function setAwaitingLocation(chatId: string): void;
|
|
8
|
+
/**
|
|
9
|
+
* Interactive /start onboarding:
|
|
10
|
+
* 1. Show welcome + language inline keyboard
|
|
11
|
+
* 2. User picks language → ask for location (reply keyboard with 📍)
|
|
12
|
+
* 3. User sends location → detect area via polygon → save → confirm
|
|
13
|
+
* 4. Or user skips → use default area → save → confirm
|
|
14
|
+
*
|
|
15
|
+
* In groups: /start [lang] — simple registration (no interactive flow).
|
|
16
|
+
*/
|
|
17
|
+
export declare function registerStartHandler(bot: Bot): void;
|
|
18
|
+
//# sourceMappingURL=start.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/handlers/start.ts"],"names":[],"mappings":"AAAA,OAAO,EASL,KAAK,QAAQ,EAEd,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAkB,QAAQ,EAAE,KAAK,GAAG,EAAE,MAAM,QAAQ,CAAC;AAK5D,wBAAgB,gBAAgB,IAAI,IAAI,CAIvC;AAQD,yDAAyD;AACzD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAOzD;AAKD,wEAAwE;AACxE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAExD;AASD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAsKnD"}
|