add ci for tg bot
Deploy app frontend / app frontend (push) Failing after 54s
Deploy service couchdb / service couchdb (push) Successful in 38s
Deploy db-migrations / db-migrations (push) Failing after 50s
Deploy service message-delivery-method-web-push / service message-delivery-method-web-push (push) Failing after 1m27s
Deploy service tg-bot / service tg-bot (push) Failing after 44s
Deploy app frontend / app frontend (push) Failing after 54s
Deploy service couchdb / service couchdb (push) Successful in 38s
Deploy db-migrations / db-migrations (push) Failing after 50s
Deploy service message-delivery-method-web-push / service message-delivery-method-web-push (push) Failing after 1m27s
Deploy service tg-bot / service tg-bot (push) Failing after 44s
This commit is contained in:
@@ -50,7 +50,7 @@ interface CreateQRCodeOptions {
|
||||
userUuid: string;
|
||||
name: string;
|
||||
placement: QRCodeDocument["placement"];
|
||||
actions: string[];
|
||||
actions: QRCodeDocument["actions"];
|
||||
messageDeliveryMethod?: QRCodeDocument["messageDeliveryMethod"];
|
||||
browser_uuid?: string;
|
||||
telegram_id?: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MyContext } from "@/types/myContext";
|
||||
import { getQRCodesByUser } from "@/api/qrCodes";
|
||||
import { buildManageKeyboard } from "@/bot/utils/keyboards";
|
||||
import { MyContext } from "../../types/myContext";
|
||||
import { getQRCodesByUser } from "../../api/qrCodes";
|
||||
import { buildManageKeyboard } from "../../bot/utils/keyboards";
|
||||
|
||||
/**
|
||||
* Обработка команды /manage с выводом подменю
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { Bot } from "grammy";
|
||||
import { MyContext } from "../../types/myContext";
|
||||
import { bot } from "old_bot"; // Ваш кастомный контекст
|
||||
import { MyContext } from "../../types/myContext"; // Ваш кастомный контекст
|
||||
|
||||
/**
|
||||
* Устанавливает команды для бота.
|
||||
* @param bot - Экземпляр бота.
|
||||
*/
|
||||
export async function setupCommands(bot: Bot<MyContext>): Promise<void> {
|
||||
// Настройка команд для английского языка (по умолчанию)
|
||||
@@ -32,6 +30,4 @@ export async function setupCommands(bot: Bot<MyContext>): Promise<void> {
|
||||
],
|
||||
{ language_code: "ru" },
|
||||
);
|
||||
|
||||
console.log("Команды успешно установлены.");
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from "../utils/keyboards";
|
||||
import { createQRCode } from "../../api/qrCodes";
|
||||
import { generateQRCodeImage } from "../../services/qrGenerator";
|
||||
import { selectMultipleActions } from "./selectMultipleActions";
|
||||
|
||||
// Тип разговора (конверсии)
|
||||
type MyConversation = Conversation<MyContext>;
|
||||
@@ -67,34 +68,25 @@ export async function newQRCodeConversation(
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Выбор действий, доступных при сканировании
|
||||
await ctx.reply(
|
||||
"Выберите одно или несколько действий, которые будут доступны при сканировании этого QR-кода:",
|
||||
{
|
||||
reply_markup: {
|
||||
inline_keyboard: buildActionsKeyboard([
|
||||
{ action: "sendMessage", label: "Отправить сообщение" },
|
||||
{ action: "viewContactInfo", label: "Посмотреть контактные данные" },
|
||||
]),
|
||||
},
|
||||
},
|
||||
// 3. Выбор действий
|
||||
const availableActions = [
|
||||
{ action: "sendMessage", label: "📩 Отправить сообщение" },
|
||||
{ action: "viewContactInfo", label: "📞 Посмотреть контактные данные" },
|
||||
] as const;
|
||||
|
||||
const chosenActions = await selectMultipleActions(
|
||||
conversation,
|
||||
ctx,
|
||||
availableActions,
|
||||
);
|
||||
|
||||
const actionsMsg = await conversation.waitFor("callback_query:data");
|
||||
const chosenAction = actionsMsg.update.callback_query?.data;
|
||||
if (!chosenAction) {
|
||||
await ctx.reply("Вы не выбрали действие. Попробуйте снова /new.");
|
||||
if (chosenActions.length === 0) {
|
||||
await ctx.reply("Вы не выбрали ни одного действия. Попробуйте снова /new.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Подтверждаем выбор
|
||||
await actionsMsg.answerCallbackQuery({ text: "Действие выбрано!" });
|
||||
|
||||
// Пока для MVP можно выбрать только одно действие, в будущем можно расширить
|
||||
const chosenActions = [chosenAction];
|
||||
|
||||
// 4. Создание документа в БД
|
||||
const { url, uri } = await createQRCode({
|
||||
const { url } = await createQRCode({
|
||||
userUuid,
|
||||
name,
|
||||
placement: typedPlacement,
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
import { MyContext } from "../../types/myContext";
|
||||
import { Conversation } from "@grammyjs/conversations";
|
||||
|
||||
// Тип разговора
|
||||
type MyConversation = Conversation<MyContext>;
|
||||
|
||||
interface Action {
|
||||
readonly action: string;
|
||||
readonly label: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработчик выбора нескольких действий
|
||||
* @param conversation - Инстанс Conversation
|
||||
* @param ctx - Контекст
|
||||
* @param availableActions - Список доступных действий
|
||||
* @returns Список выбранных действий
|
||||
*/
|
||||
export async function selectMultipleActions<T extends readonly Action[]>(
|
||||
conversation: MyConversation,
|
||||
ctx: MyContext,
|
||||
availableActions: T,
|
||||
): Promise<T[number]["action"][]> {
|
||||
try {
|
||||
if (!Array.isArray(availableActions) || availableActions.length === 0) {
|
||||
throw new Error("Список доступных действий пуст или не определён.");
|
||||
}
|
||||
|
||||
const selectedActions = new Set<T[number]["action"]>();
|
||||
|
||||
const updateKeyboard = () => {
|
||||
return availableActions.map(({ action, label }) => [
|
||||
{
|
||||
text: `${selectedActions.has(action) ? "✔️ " : ""}${label}`,
|
||||
callback_data: `action:${action}`,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
// Отправляем начальное сообщение
|
||||
const replyMessage = await ctx.reply(
|
||||
"Выберите одно или несколько действий, которые будут доступны при сканировании этого QR-кода. Нажмите 'Готово', когда закончите выбор:",
|
||||
{
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
...updateKeyboard(),
|
||||
[{ text: "Готово", callback_data: "action:done" }],
|
||||
],
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
while (true) {
|
||||
let actionChoice;
|
||||
try {
|
||||
actionChoice = await conversation.waitFor("callback_query:data");
|
||||
} catch (err) {
|
||||
console.error("Ошибка ожидания callback_query:", err);
|
||||
await ctx.reply(
|
||||
"Произошла ошибка при ожидании вашего выбора. Попробуйте снова.",
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const choice = actionChoice.update.callback_query?.data;
|
||||
|
||||
if (!choice || !choice.startsWith("action:")) {
|
||||
// Игнорируем callback_data, которые не относятся к текущему этапу
|
||||
console.warn(`Игнорируем callback_data: ${choice}`);
|
||||
await actionChoice.answerCallbackQuery({
|
||||
text: "Неизвестное действие.",
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const action = choice.replace("action:", "");
|
||||
|
||||
if (action === "done") {
|
||||
await actionChoice.answerCallbackQuery({ text: "Выбор завершён!" });
|
||||
break;
|
||||
}
|
||||
|
||||
// Проверка: является ли выбор валидным
|
||||
const validChoice = availableActions.find((a) => a.action === action);
|
||||
if (!validChoice) {
|
||||
console.error(`Недопустимое действие: ${action}`);
|
||||
await actionChoice.answerCallbackQuery({
|
||||
text: "Неверный выбор. Попробуйте снова.",
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Добавляем или убираем действие
|
||||
if (selectedActions.has(action as T[number]["action"])) {
|
||||
selectedActions.delete(action as T[number]["action"]);
|
||||
} else {
|
||||
selectedActions.add(action as T[number]["action"]);
|
||||
}
|
||||
|
||||
await actionChoice.answerCallbackQuery({
|
||||
text: `Вы ${
|
||||
selectedActions.has(action as T[number]["action"])
|
||||
? "добавили"
|
||||
: "убрали"
|
||||
} действие.`,
|
||||
});
|
||||
|
||||
// Перерисовываем клавиатуру
|
||||
try {
|
||||
await actionChoice.editMessageReplyMarkup({
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
...updateKeyboard(),
|
||||
[{ text: "Готово", callback_data: "action:done" }],
|
||||
],
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Ошибка обновления клавиатуры:", err);
|
||||
await ctx.reply(
|
||||
"Произошла ошибка при обновлении выбора. Попробуйте снова.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(selectedActions);
|
||||
} catch (err) {
|
||||
console.error("Общая ошибка в selectMultipleActions:", err);
|
||||
await ctx.reply(
|
||||
"Произошла непредвиденная ошибка. Попробуйте снова или обратитесь в поддержку.",
|
||||
);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Bot, session } from "grammy";
|
||||
import { conversations, createConversation } from "@grammyjs/conversations";
|
||||
import { MyContext } from "@/types/myContext";
|
||||
import { env } from "@/config/env";
|
||||
import { startHealthServer } from "@hereconnect/lib-service-health-checkserver";
|
||||
import { MyContext } from "../types/myContext";
|
||||
import { env } from "../config/env";
|
||||
import { CouchDBStorageAdapter } from "./storageAdapters/CouchDBStorageAdapter";
|
||||
import { loadUserMiddleware } from "./middleware/loadUser";
|
||||
import { tgBotSessionsDb } from "@/dbs";
|
||||
import { tgBotSessionsDb } from "../dbs";
|
||||
import { setupCommands } from "./commands/setupCommands";
|
||||
import { startCommand } from "./commands/start";
|
||||
import { helpCommand } from "./commands/help";
|
||||
@@ -13,12 +14,21 @@ import { manageCommand } from "./commands/manage";
|
||||
import { callbackQueryHandler } from "./callbackHandlers";
|
||||
import { newQRCodeConversation } from "./conversations/newQRCodeConversation";
|
||||
|
||||
const abortController = new AbortController();
|
||||
|
||||
const storage = new CouchDBStorageAdapter(tgBotSessionsDb, {
|
||||
softDelete: true,
|
||||
type: "tg_bot_session",
|
||||
});
|
||||
|
||||
// Инициализируем бота с нужным типом контекста
|
||||
const bot = new Bot<MyContext>(env.TELEGRAM_BOT_TOKEN);
|
||||
const bot = new Bot<MyContext>(env.TELEGRAM_BOT_TOKEN, {
|
||||
client: {
|
||||
baseFetchConfig: {
|
||||
signal: abortController.signal,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Подключаем сессии. По необходимости определите initial state для сессии
|
||||
bot.use(session({ storage, initial: () => ({}) }));
|
||||
@@ -44,7 +54,10 @@ bot.on("callback_query:data", callbackQueryHandler);
|
||||
await setupCommands(bot);
|
||||
|
||||
// Запуск бота
|
||||
bot.start();
|
||||
await bot.start();
|
||||
|
||||
await startHealthServer(abortController);
|
||||
|
||||
console.log("Бот запущен!");
|
||||
})();
|
||||
|
||||
|
||||
@@ -6,7 +6,12 @@ if (!process.env.READ_BASE_URL) {
|
||||
throw new Error("Необходимо указать READ_BASE_URL в переменных окружения.");
|
||||
}
|
||||
|
||||
if (!process.env.API_URL) {
|
||||
throw new Error("Environment variable API_URL is not set");
|
||||
}
|
||||
|
||||
export const env = {
|
||||
TELEGRAM_BOT_TOKEN: process.env.BOT_TOKEN,
|
||||
READ_BASE_URL: process.env.READ_BASE_URL,
|
||||
API_URL: process.env.API_URL,
|
||||
} as const;
|
||||
|
||||
+15
-17
@@ -12,45 +12,46 @@ import type {
|
||||
} from "@hereconnect/types";
|
||||
|
||||
import type { TelegramWebhookDesignDocument } from "@hereconnect/tg-bot-webhook-ddoc";
|
||||
import { CouchDBSessionDocument } from "@/bot/storageAdapters/CouchDBStorageAdapter";
|
||||
import type { SessionData } from "@/types/myContext";
|
||||
import { CouchDBSessionDocument } from "./bot/storageAdapters/CouchDBStorageAdapter";
|
||||
import type { SessionData } from "./types/myContext";
|
||||
import { env } from "./config/env";
|
||||
// import type { TgUpdateDocument } from "./types";
|
||||
|
||||
if (!process.env.API_URL) {
|
||||
if (!env.API_URL) {
|
||||
throw new Error("Environment variable API_URL is not set");
|
||||
}
|
||||
|
||||
export const TG_BOT_DB_NAME = "tg_bot";
|
||||
export const TG_BOT_DB_URL = `${process.env.API_URL}/${TG_BOT_DB_NAME}`;
|
||||
export const TG_BOT_DB_URL = `${env.API_URL}/${TG_BOT_DB_NAME}`;
|
||||
|
||||
export const messagesDb = new PouchDB<MessageDocument>(
|
||||
`${process.env.API_URL}/messages`,
|
||||
`${env.API_URL}/messages`,
|
||||
{
|
||||
skip_setup: true,
|
||||
},
|
||||
);
|
||||
|
||||
export const serverSettingsDb = new PouchDB<MessageDeliveryMethodWebPushVapid>(
|
||||
`${process.env.API_URL}/server_settings`,
|
||||
`${env.API_URL}/server_settings`,
|
||||
{
|
||||
skip_setup: true,
|
||||
},
|
||||
);
|
||||
|
||||
export const publicSettingsDb = new PouchDB<PublicSettingsDocument>(
|
||||
`${process.env.API_URL}/public_settings`,
|
||||
`${env.API_URL}/public_settings`,
|
||||
{
|
||||
skip_setup: true,
|
||||
},
|
||||
);
|
||||
|
||||
export const qrDb = new PouchDB<QRCodeDocument>(`${process.env.API_URL}/qr`, {
|
||||
export const qrDb = new PouchDB<QRCodeDocument>(`${env.API_URL}/qr`, {
|
||||
skip_setup: true,
|
||||
});
|
||||
|
||||
export const webPushSubscriptionsDb = new PouchDB<
|
||||
WebPushSubscriptionDocument<PushSubscription>
|
||||
>(`${process.env.API_URL}/web_push_subscriptions`, {
|
||||
>(`${env.API_URL}/web_push_subscriptions`, {
|
||||
skip_setup: true,
|
||||
});
|
||||
|
||||
@@ -63,25 +64,22 @@ export const tgBotDesignDocumentsDb =
|
||||
export const serviceSeqTrackerDb = tgBotDb as ServiceSeqTrackerDatabase;
|
||||
|
||||
export const tgBotSessionsDb = new PouchDB<CouchDBSessionDocument<SessionData>>(
|
||||
`${process.env.API_URL}/tg_bot_sessions`,
|
||||
`${env.API_URL}/tg_bot_sessions`,
|
||||
{
|
||||
skip_setup: true,
|
||||
},
|
||||
);
|
||||
|
||||
export const userChatSettingsDb = new PouchDB<UserChatSettingsDocument>(
|
||||
`${process.env.API_URL}/user_chat_settings`,
|
||||
`${env.API_URL}/user_chat_settings`,
|
||||
{
|
||||
skip_setup: true,
|
||||
},
|
||||
);
|
||||
|
||||
export const usersDb = new PouchDB<UserDocument>(
|
||||
`${process.env.API_URL}/_users`,
|
||||
{
|
||||
skip_setup: true,
|
||||
},
|
||||
);
|
||||
export const usersDb = new PouchDB<UserDocument>(`${env.API_URL}/_users`, {
|
||||
skip_setup: true,
|
||||
});
|
||||
|
||||
export const checkDatabases = async () => {
|
||||
const entries = await Promise.all(
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
import { Bot, Context, InlineKeyboard } from "grammy";
|
||||
import { type AbortSignal } from "grammy/out/shim.node";
|
||||
import { qrCodePlacements } from "@hereconnect/constants";
|
||||
import type { QRCodeDocument } from "@hereconnect/types";
|
||||
|
||||
const BOT_TOKEN = process.env.BOT_TOKEN;
|
||||
if (!BOT_TOKEN) {
|
||||
throw new Error("Необходимо указать BOT_TOKEN в переменных окружения.");
|
||||
}
|
||||
|
||||
export const bot = new Bot(BOT_TOKEN);
|
||||
|
||||
export const init = async (signal: AbortSignal) => {
|
||||
await bot.init(signal);
|
||||
|
||||
// Настройка команд для английского языка (по умолчанию)
|
||||
await bot.api.setMyCommands([
|
||||
{ command: "create_qr", description: "Create a QR code" },
|
||||
{ command: "help", description: "Get help" },
|
||||
]);
|
||||
|
||||
// Настройка команд для русского языка
|
||||
await bot.api.setMyCommands(
|
||||
[
|
||||
{ command: "create_qr", description: "Создать QR-код" },
|
||||
{ command: "help", description: "Получить помощь" },
|
||||
],
|
||||
{ language_code: "ru" },
|
||||
);
|
||||
};
|
||||
|
||||
const actionsOptions = [
|
||||
{
|
||||
type: "sendMessage",
|
||||
label: "Отправить сообщение",
|
||||
},
|
||||
{ type: "viewContactInfo", label: "Посмотреть ваши контактные данные" },
|
||||
];
|
||||
|
||||
bot.command("start", async (ctx) => {
|
||||
const user = ctx.from;
|
||||
|
||||
const message = `Привет${user?.first_name ? ", " + user?.first_name : ""}!\n\nЯ бот сервиса "НаСвязи". Помогу вам создать QR-код (для автомобиля, питомца, входной двери и т.д.).!`;
|
||||
|
||||
// Отправляем сообщение
|
||||
return ctx.reply(message, {
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{
|
||||
text: "Создать QR-код",
|
||||
callback_data: "create_qr",
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const createQrCode = async (ctx: Context) => {
|
||||
const keyboard = new InlineKeyboard();
|
||||
for (const { type, label } of qrCodePlacements) {
|
||||
keyboard.text(label, `select_placement=${type}`);
|
||||
}
|
||||
|
||||
await ctx.reply(
|
||||
"Где вы хотите разместить QR‑код?\n\nЭтот выбор поможет настроить QR‑код с подходящими действиями.",
|
||||
{ reply_markup: { inline_keyboard: keyboard.toFlowed(1).inline_keyboard } },
|
||||
);
|
||||
};
|
||||
|
||||
bot.command("create_qr", createQrCode);
|
||||
|
||||
bot.callbackQuery("cancel", async (ctx) => {
|
||||
await ctx.deleteMessage();
|
||||
});
|
||||
|
||||
bot.callbackQuery(
|
||||
/^(select_action=|select_placement=|create_qr$)/,
|
||||
async (ctx) => {
|
||||
const selectedActions: QRCodeDocument["actions"] = [];
|
||||
const keyboard = new InlineKeyboard();
|
||||
for (const { type, label } of actionsOptions) {
|
||||
keyboard.text(
|
||||
(selectedActions.includes(type) ? "✓ " : "") + label,
|
||||
`select_action=${type}`,
|
||||
);
|
||||
}
|
||||
|
||||
await ctx.reply(
|
||||
"Выберите доступные действия?\n\nУкажите, какие действия будут доступны для сканирующих QR‑код.\nВы сможете изменить эти настройки позже.",
|
||||
{ reply_markup: { inline_keyboard: keyboard.inline_keyboard } },
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (process.argv.includes("start")) {
|
||||
const abortController = new AbortController();
|
||||
|
||||
process.on("SIGINT", async () => {
|
||||
abortController.abort();
|
||||
setTimeout(() => process.exit(1), 1_000).unref();
|
||||
});
|
||||
|
||||
init(abortController.signal as AbortSignal)
|
||||
.then(() => bot.start())
|
||||
.then(() => console.log("Bot started"))
|
||||
.catch((error) => console.error(error));
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
import { startHealthServer } from "@hereconnect/lib-service-health-checkserver";
|
||||
import { ServiceSeqTracker } from "@hereconnect/lib-service-seq-tracker";
|
||||
import { type AbortSignal } from "grammy/out/shim.node";
|
||||
import { setupWebHook } from "setup/setupWebHook";
|
||||
|
||||
const heartbeat = Number(process.env.HEARTBEAT_INTERVAL ?? 30_000);
|
||||
|
||||
import { CouchDBChangesStream } from "@hereconnect/couchdb-changes-stream";
|
||||
import { TG_BOT_DB_URL, serviceSeqTrackerDb, checkDatabases } from "dbs";
|
||||
import type { TgUpdateDocument } from "types";
|
||||
import { init, bot } from "old_bot";
|
||||
|
||||
const abortController = new AbortController();
|
||||
|
||||
const start = async () => {
|
||||
const checkedDatabases = await checkDatabases();
|
||||
|
||||
await init(abortController.signal as AbortSignal);
|
||||
await setupWebHook();
|
||||
|
||||
const serviceSeqTracker = await ServiceSeqTracker.Init(
|
||||
"_local/service:tg-bot",
|
||||
serviceSeqTrackerDb,
|
||||
);
|
||||
|
||||
const changesStream = new CouchDBChangesStream<TgUpdateDocument>(
|
||||
TG_BOT_DB_URL,
|
||||
{
|
||||
abortController,
|
||||
since: serviceSeqTracker.lastSeq,
|
||||
feed: "continuous",
|
||||
include_docs: true,
|
||||
heartbeat,
|
||||
filter: "_selector",
|
||||
selector: {
|
||||
type: "tg_update" satisfies TgUpdateDocument["type"],
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Gracefully handle SIGINT
|
||||
process.on("SIGINT", async () => {
|
||||
changesStream?.stop();
|
||||
setTimeout(() => process.exit(1), 5_000).unref();
|
||||
});
|
||||
|
||||
console.info("Service started from sequence", serviceSeqTracker.lastSeq);
|
||||
if (
|
||||
parseInt(serviceSeqTracker.lastSeq.toString(), 10) ===
|
||||
parseInt(checkedDatabases.tgBotDb.update_seq.toString(), 10)
|
||||
) {
|
||||
console.log("Since is the last seq");
|
||||
} else {
|
||||
console.log("Last seq is", checkedDatabases.tgBotDb.update_seq);
|
||||
}
|
||||
|
||||
await startHealthServer(abortController);
|
||||
|
||||
for await (const change of changesStream) {
|
||||
const { seq, doc: changeDoc } = change;
|
||||
|
||||
if (!changeDoc) {
|
||||
console.error("Change missing document:", change);
|
||||
continue;
|
||||
}
|
||||
|
||||
serviceSeqTracker.checkNextSeq(seq);
|
||||
await bot.handleUpdate(changeDoc.data);
|
||||
|
||||
// const ctx = new Context(changeDoc.data, bot.api, bot.me);
|
||||
// if (ctx.hasCommand("start")) {
|
||||
// }
|
||||
|
||||
try {
|
||||
console.log(
|
||||
`Processed change: ${JSON.stringify({ _id: change.id, ref: changeDoc._rev, seq: change.seq })}`,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Failed processing: ${JSON.stringify(error)} change: ${JSON.stringify({ id: changeDoc._id, rev: changeDoc._rev, seq: seq })}`,
|
||||
);
|
||||
} finally {
|
||||
await serviceSeqTracker.saveSeq(seq);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
start().catch((error) => {
|
||||
console.error("error", error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import QRCodeStyling from "qr-code-styling";
|
||||
import { SvgProcessor } from "@/utils/images/svg/SvgProcessor";
|
||||
import { SvgProcessor } from "../utils/images/svg/SvgProcessor";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { setupWebhookDesignDocument } from "./setupWebhookDesignDocument";
|
||||
import { TG_BOT_DB_NAME, tgBotDesignDocumentsDb } from "dbs";
|
||||
import { bot } from "old_bot";
|
||||
|
||||
const PUBLIC_API_URL = process.env.PUBLIC_API_URL;
|
||||
if (!PUBLIC_API_URL) {
|
||||
throw new Error("Необходимо указать PUBLIC_API_URL в переменных окружения.");
|
||||
}
|
||||
|
||||
export const setupWebHook = async () => {
|
||||
const designDocument = await setupWebhookDesignDocument();
|
||||
const update: keyof typeof designDocument.updates = "saveUpdate";
|
||||
const webhookUrl = `${process.env.PUBLIC_API_URL}/${TG_BOT_DB_NAME}/${designDocument._id}/_update/${update}`;
|
||||
if (designDocument.constants.telegram_webhook_url !== webhookUrl) {
|
||||
await bot.api.setWebhook(webhookUrl, {
|
||||
secret_token: designDocument.constants.telegram_secret_token,
|
||||
});
|
||||
designDocument.constants.telegram_webhook_url = webhookUrl;
|
||||
const { rev } = await tgBotDesignDocumentsDb.put(designDocument);
|
||||
designDocument._rev = rev;
|
||||
}
|
||||
return designDocument;
|
||||
};
|
||||
@@ -1,68 +0,0 @@
|
||||
import { tgBotDesignDocumentsDb } from "../dbs";
|
||||
import { saveUpdateString as saveUpdate } from "@hereconnect/tg-bot-webhook-ddoc";
|
||||
import { type TelegramWebhookDesignDocument } from "@hereconnect/tg-bot-webhook-ddoc";
|
||||
|
||||
function generateTelegramSecretToken(length = 256) {
|
||||
const characters =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";
|
||||
let token = "";
|
||||
for (let i = 0; i < length; i++) {
|
||||
const randomIndex = Math.floor(Math.random() * characters.length);
|
||||
token += characters[randomIndex];
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
export const setupWebhookDesignDocument = async () => {
|
||||
const _id = "_design/webhook";
|
||||
|
||||
const result: TelegramWebhookDesignDocument = {
|
||||
_id,
|
||||
type: "_design/webhook",
|
||||
created_at: "",
|
||||
updated_at: "",
|
||||
updates: {
|
||||
saveUpdate,
|
||||
},
|
||||
constants: {
|
||||
telegram_secret_token: "",
|
||||
},
|
||||
};
|
||||
|
||||
const { rev } = await tgBotDesignDocumentsDb.upsert(_id, (ddoc) => {
|
||||
const isEmptyTelegramSecretToken =
|
||||
!ddoc.constants?.telegram_secret_token?.length;
|
||||
const needToSetSaveUpdateFunction =
|
||||
ddoc.updates?.saveUpdate !== result.updates.saveUpdate;
|
||||
|
||||
if (!needToSetSaveUpdateFunction && !isEmptyTelegramSecretToken) {
|
||||
Object.assign(result, ddoc);
|
||||
return false;
|
||||
}
|
||||
|
||||
ddoc.updated_at = new Date().toISOString();
|
||||
if (!ddoc.created_at) {
|
||||
ddoc.created_at = ddoc.updated_at;
|
||||
}
|
||||
|
||||
if (isEmptyTelegramSecretToken) {
|
||||
if (!ddoc.constants) {
|
||||
ddoc.constants = result.constants;
|
||||
}
|
||||
ddoc.constants.telegram_secret_token = generateTelegramSecretToken();
|
||||
}
|
||||
|
||||
if (needToSetSaveUpdateFunction) {
|
||||
if (ddoc.updates) {
|
||||
ddoc.updates.saveUpdate = result.updates.saveUpdate;
|
||||
} else {
|
||||
ddoc.updates = result.updates;
|
||||
}
|
||||
}
|
||||
|
||||
return Object.assign(result, ddoc) satisfies TelegramWebhookDesignDocument;
|
||||
});
|
||||
|
||||
result._rev = rev;
|
||||
return result;
|
||||
};
|
||||
Reference in New Issue
Block a user