forked from pinks/eris
1
0
Fork 0
nyx/bot/mod.ts

229 lines
6.8 KiB
TypeScript
Raw Permalink Normal View History

2023-09-24 13:08:35 +00:00
import { Api, Bot, Context, RawApi, session, SessionFlavor } from "grammy";
import { FileFlavor, hydrateFiles } from "grammy_files";
import { hydrateReply, ParseModeFlavor } from "grammy_parse_mode";
import { sequentialize } from "grammy_runner";
2023-10-15 19:13:38 +00:00
import { error, info, warning } from "std/log/mod.ts";
2023-10-13 16:10:16 +00:00
import { sessions } from "../api/sessionsRoute.ts";
2023-09-23 18:49:05 +00:00
import { formatUserChat } from "../utils/formatUserChat.ts";
2023-10-19 21:37:03 +00:00
import { omitUndef } from "../utils/omitUndef.ts";
2023-09-24 19:58:09 +00:00
import { broadcastCommand } from "./broadcastCommand.ts";
2023-09-24 13:08:35 +00:00
import { cancelCommand } from "./cancelCommand.ts";
import { img2imgCommand, img2imgQuestion } from "./img2imgCommand.ts";
import { pnginfoCommand, pnginfoQuestion } from "./pnginfoCommand.ts";
2023-09-10 18:56:17 +00:00
import { queueCommand } from "./queueCommand.ts";
2023-09-10 23:59:33 +00:00
import { txt2imgCommand, txt2imgQuestion } from "./txt2imgCommand.ts";
import { setConfig, getConfig } from "../app/config.ts";
// Set the new configuration
await setConfig({ maxUserJobs: 1, maxJobs: 500 });
// Fetch the updated configuration
const updatedConfig = await getConfig();
// Log the updated configuration to the console
console.log("Updated Configuration:", updatedConfig);
2023-09-10 18:56:17 +00:00
2023-09-22 02:59:22 +00:00
interface SessionData {
2023-09-24 13:08:35 +00:00
chat: ErisChatData;
user: ErisUserData;
2023-09-22 02:59:22 +00:00
}
2023-09-24 13:08:35 +00:00
interface ErisChatData {
2023-10-19 21:37:03 +00:00
language?: string | undefined;
2023-09-22 02:59:22 +00:00
}
2023-09-24 13:08:35 +00:00
interface ErisUserData {
2023-10-19 21:37:03 +00:00
params?: Record<string, string> | undefined;
2023-09-22 02:59:22 +00:00
}
2023-09-24 13:08:35 +00:00
export type ErisContext =
& FileFlavor<ParseModeFlavor<Context>>
& SessionFlavor<SessionData>;
2023-09-22 02:59:22 +00:00
2023-09-24 13:08:35 +00:00
type WithRetryApi<T extends RawApi> = {
2023-09-11 17:07:46 +00:00
[M in keyof T]: T[M] extends (args: infer P, ...rest: infer A) => infer R
? (args: P extends object ? P & { maxAttempts?: number; maxWait?: number } : P, ...rest: A) => R
2023-09-11 17:07:46 +00:00
: T[M];
};
2023-09-24 13:08:35 +00:00
type ErisApi = Api<WithRetryApi<RawApi>>;
2023-09-22 02:59:22 +00:00
2023-09-24 13:08:35 +00:00
export const bot = new Bot<ErisContext, ErisApi>(
Deno.env.get("TG_BOT_TOKEN")!,
{
client: { timeoutSeconds: 20 },
},
);
2023-09-22 02:59:22 +00:00
2023-09-24 13:08:35 +00:00
bot.use(hydrateReply);
2023-10-13 16:10:16 +00:00
bot.use(sequentialize((ctx) => ctx.chat?.id.toString()));
2023-09-24 13:08:35 +00:00
bot.use(session<SessionData, ErisContext>({
2023-09-22 02:59:22 +00:00
type: "multi",
chat: {
initial: () => ({}),
},
user: {
getSessionKey: (ctx) => ctx.from?.id.toFixed(),
initial: () => ({}),
},
}));
2023-09-10 18:56:17 +00:00
2023-09-24 13:08:35 +00:00
bot.api.config.use(hydrateFiles(bot.token));
2023-09-10 18:56:17 +00:00
// Automatically retry bot requests if we get a "too many requests" or telegram internal error
bot.api.config.use(async (prev, method, payload, signal) => {
2023-09-11 17:07:46 +00:00
const maxAttempts = payload && ("maxAttempts" in payload) ? payload.maxAttempts ?? 3 : 3;
const maxWait = payload && ("maxWait" in payload) ? payload.maxWait ?? 10 : 10;
2023-09-10 18:56:17 +00:00
let attempt = 0;
while (true) {
attempt++;
const result = await prev(method, payload, signal);
if (result.ok) return result;
if (result.error_code !== 429) return result;
if (attempt >= maxAttempts) return result;
const retryAfter = result.parameters?.retry_after ?? (attempt * 5);
if (retryAfter > maxWait) return result;
2023-10-15 19:13:38 +00:00
warning(
2023-09-11 20:48:38 +00:00
`${method} (attempt ${attempt}) failed: ${result.error_code} ${result.description}`,
2023-09-11 17:07:46 +00:00
);
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
2023-09-10 18:56:17 +00:00
}
});
2023-09-11 20:43:12 +00:00
bot.catch((err) => {
2023-10-15 19:13:38 +00:00
error(
2023-09-11 20:48:38 +00:00
`Handling update from ${formatUserChat(err.ctx)} failed: ${err.name} ${err.message}`,
);
2023-09-11 20:43:12 +00:00
});
2023-09-10 18:56:17 +00:00
// if error happened, try to reply to the user with the error
bot.use(async (ctx, next) => {
try {
await next();
} catch (err) {
try {
2023-10-19 21:37:03 +00:00
await ctx.reply(
`Handling update failed: ${err}`,
omitUndef({
reply_to_message_id: ctx.message?.message_id,
allow_sending_without_reply: true,
}),
);
2023-09-10 18:56:17 +00:00
} catch {
throw err;
}
}
});
// Wrap the calls in try-catch for error handling
async function setupBotCommands() {
try {
await bot.api.setMyShortDescription("Generate furry images in '704x704', '576x832', '832x576'. https://ko-fi.com/nyxthebot https://nyx.akiru.de/");
} catch (err) {
error(`Failed to set short description: ${err.message}`);
}
try {
await bot.api.setMyDescription(
"I can generate furry images from text. If you want a different size, use 'Size: 576x832' for example." +
"Send /txt2img to generate an image.",
);
} catch (err) {
error(`Failed to set description: ${err.message}`);
}
try {
await bot.api.setMyCommands([
{ command: "txt2img", description: "Generate image from text" },
{ command: "img2img", description: "Generate image from image" },
{ command: "pnginfo", description: "Try to extract prompt from raw file" },
{ command: "queue", description: "Show the current queue" },
{ command: "cancel", description: "Cancel all your requests" },
]);
} catch (err) {
error(`Failed to set commands: ${err.message}`);
}
}
// Call the setup function
setupBotCommands();
2023-09-10 18:56:17 +00:00
2023-10-05 09:00:51 +00:00
bot.command("start", async (ctx) => {
if (ctx.match) {
const id = ctx.match.trim();
const session = sessions.get(id);
if (session == null) {
2023-10-19 21:37:03 +00:00
await ctx.reply(
"Login failed: Invalid session ID",
omitUndef({
reply_to_message_id: ctx.message?.message_id,
}),
);
2023-10-05 09:00:51 +00:00
return;
}
session.userId = ctx.from?.id;
sessions.set(id, session);
2023-10-15 19:13:38 +00:00
info(`User ${formatUserChat(ctx)} logged in`);
2023-10-05 09:00:51 +00:00
// TODO: show link to web ui
2023-10-19 21:37:03 +00:00
await ctx.reply(
"Login successful! You can now return to the WebUI.",
omitUndef({
reply_to_message_id: ctx.message?.message_id,
}),
);
2023-10-05 09:00:51 +00:00
return;
}
2023-10-19 21:37:03 +00:00
await ctx.reply(
"Hello! Use the /txt2img command to generate an image",
omitUndef({
reply_to_message_id: ctx.message?.message_id,
}),
);
2023-10-05 09:00:51 +00:00
});
2023-09-10 18:56:17 +00:00
bot.command("txt2img", txt2imgCommand);
2023-09-11 16:03:07 +00:00
bot.use(txt2imgQuestion.middleware());
2023-09-12 01:57:44 +00:00
bot.command("img2img", img2imgCommand);
bot.use(img2imgQuestion.middleware());
2023-09-10 18:56:17 +00:00
2023-11-12 02:33:35 +00:00
bot.command("pnginfo", pnginfoCommand);
2023-09-11 20:43:12 +00:00
bot.use(pnginfoQuestion.middleware());
2023-09-10 18:56:17 +00:00
bot.command("queue", queueCommand);
2023-09-18 15:51:19 +00:00
bot.command("cancel", cancelCommand);
2023-09-24 19:58:09 +00:00
bot.command("broadcast", broadcastCommand);
2023-09-10 18:56:17 +00:00
bot.command("crash", () => {
throw new Error("Crash command used");
});
2023-10-13 16:10:16 +00:00
2024-01-26 07:40:32 +00:00
// Set up the webhook in the telegram API and initialize the bot
await bot.api.setWebhook('https://nyx.akiru.de/webhook');
2024-01-25 09:08:16 +00:00
await bot.init();
2024-01-26 07:40:32 +00:00
// Function to handle incoming webhook requests
export async function handleWebhook(req: Request): Promise<Response> {
try {
const body = await req.json();
// console.log("Received webhook data:", JSON.stringify(body, null, 2));
2024-01-26 07:40:32 +00:00
// Log before processing update
// console.log("Processing update through handleUpdate...");
2024-01-26 07:40:32 +00:00
await bot.handleUpdate(body); // Process the update
// Log after processing update
// console.log("Update processed successfully.");
2024-01-26 07:40:32 +00:00
return new Response(JSON.stringify({ status: 'ok' }), { headers: { 'Content-Type': 'application/json' } });
} catch (error) {
// Detailed error logging
console.error("Error in handleWebhook:", error);
return new Response("Error", { status: 500 });
2024-01-25 09:08:16 +00:00
}
}