Skip to content

Instantly share code, notes, and snippets.

@Aleksey-Danchin
Last active May 3, 2023 15:48
Show Gist options
  • Save Aleksey-Danchin/7ab42657fc4ba04aa67221a58ee995ec to your computer and use it in GitHub Desktop.
Save Aleksey-Danchin/7ab42657fc4ba04aa67221a58ee995ec to your computer and use it in GitHub Desktop.
import TelegramBot from "node-telegram-bot-api";
export type UseHandler<ContextType> = (
message: TelegramBot.Message | undefined,
metadata: TelegramBot.Metadata | undefined,
query: TelegramBot.CallbackQuery | undefined,
context: ContextType,
next: () => void
) => void;
export type MessageHandler<ContextType> = (
message: TelegramBot.Message,
metadata: TelegramBot.Metadata,
context: ContextType,
next: () => void
) => void;
export type CallbackHandler<ContextType> = (
query: TelegramBot.CallbackQuery,
context: ContextType,
next: () => void
) => void;
export type CommandHandler<ContextType> = (
message: TelegramBot.Message,
match: RegExpExecArray | null,
context: ContextType,
next: () => void
) => void;
export type messageArg<ContextType> =
| MessageHandler<ContextType>
| messageArg<ContextType>[];
export type callbackArg<ContextType> =
| CallbackHandler<ContextType>
| callbackArg<ContextType>[];
export type commandArg<ContextType> =
| CommandHandler<ContextType>
| commandArg<ContextType>[];
export class SmartTelegramBot<ContextType extends Object> extends TelegramBot {
private useHandlers = [] as UseHandler<ContextType>[][];
private messageHandlers = [] as MessageHandler<ContextType>[][];
private callbackHandlers = new Map<
string,
CallbackHandler<ContextType>[][]
>();
private commandHandlers = new Map<
string,
CommandHandler<ContextType>[][]
>();
private contextBuffer = new WeakMap<
TelegramBot.Message | TelegramBot.CallbackQuery,
ContextType
>();
constructor(...args: ConstructorParameters<typeof TelegramBot>) {
super(...args);
this.on("message", async (message, metadata) => {
if (!this.contextBuffer.has(message)) {
this.contextBuffer.set(message, {} as ContextType);
}
const context = this.contextBuffer.get(message) as ContextType;
await this.runUse(message, metadata, undefined, context);
this.runMessage(message, metadata, context);
});
this.on("callback_query", async (query) => {
if (!query.data) {
return;
}
const collection = this.callbackHandlers.get(query.data);
if (!collection) {
return;
}
if (!this.contextBuffer.has(query)) {
this.contextBuffer.set(query, {} as ContextType);
}
const context = this.contextBuffer.get(query) as ContextType;
await this.runUse(undefined, undefined, query, context);
this.runQuery(collection, query, context);
});
}
use(...handlers: UseHandler<ContextType>[]) {
handlers = handlers.flat(21); // wow 0_o, 21 is deep limit
if (!handlers.length) {
throw Error("Unexpectedly empty number of handlers.");
}
this.useHandlers.push(handlers as UseHandler<ContextType>[]);
return this;
}
message(...handlers: messageArg<ContextType>[]) {
handlers = handlers.flat(21); // wow 0_o, 21 is deep limit
if (!handlers.length) {
throw Error("Unexpectedly empty number of handlers.");
}
this.messageHandlers.push(handlers as MessageHandler<ContextType>[]);
return this;
}
callback(name: string, ...handlers: callbackArg<ContextType>[]) {
handlers = handlers.flat(21); // wow 0_o, 21 is deep limit
if (!handlers.length) {
throw Error("Unexpectedly empty number of handlers.");
}
if (!this.callbackHandlers.has(name)) {
this.callbackHandlers.set(name, []);
}
this.callbackHandlers
.get(name)
?.push(handlers as CallbackHandler<ContextType>[]);
return this;
}
command(reg: RegExp, ...handlers: messageArg<ContextType>[]) {
handlers = handlers.flat(21); // wow 0_o, 21 is deep limit
if (!handlers.length) {
throw Error("Unexpectedly empty number of handlers.");
}
this.message((message, meta, context, next) => {
const { text } = message;
if (text && reg.test(text)) {
next();
}
}, ...handlers);
return this;
}
private async runUse(
message: TelegramBot.Message | undefined,
metadata: TelegramBot.Metadata | undefined,
query: TelegramBot.CallbackQuery | undefined,
context: ContextType,
row: number = 0,
column: number = 0
) {
const handler = (this.useHandlers[row] || [])[column] as
| UseHandler<ContextType>
| undefined;
if (!handler) {
return;
}
let flag = false;
let promise: Promise<any> | null = null;
const next = () => {
if (flag) {
throw Error("Unexpected double calling.");
}
flag = true;
const handler1 = (this.useHandlers[row] || [])[column + 1] as
| UseHandler<ContextType>
| undefined;
if (handler1) {
promise = this.runUse(
message,
metadata,
query,
context,
row,
column + 1
);
} else {
promise = this.runUse(
message,
metadata,
query,
context,
row + 1,
0
);
}
};
await handler(message, metadata, query, context, next);
if (!flag && column === 0) {
promise = this.runUse(
message,
metadata,
query,
context,
row + 1,
0
);
}
await promise;
}
private async runMessage(
message: TelegramBot.Message,
metadata: TelegramBot.Metadata,
context: ContextType,
row: number = 0,
column: number = 0
) {
const handler = (this.messageHandlers[row] || [])[column] as
| MessageHandler<ContextType>
| undefined;
if (!handler) {
return;
}
let flag = false;
let promise: Promise<any> | null = null;
const next = () => {
if (flag) {
throw Error("Unexpected double calling.");
}
flag = true;
const handler1 = (this.messageHandlers[row] || [])[column + 1] as
| MessageHandler<ContextType>
| undefined;
if (handler1) {
promise = this.runMessage(
message,
metadata,
context,
row,
column + 1
);
} else {
promise = this.runMessage(
message,
metadata,
context,
row + 1,
0
);
}
};
await handler(message, metadata, context, next);
if (!flag && column === 0) {
promise = this.runMessage(message, metadata, context, row + 1, 0);
}
await promise;
}
private async runQuery(
collection: CallbackHandler<ContextType>[][],
query: TelegramBot.CallbackQuery,
context: ContextType,
row: number = 0,
column: number = 0
) {
const handler = (collection[row] || [])[column] as
| CallbackHandler<ContextType>
| undefined;
if (!handler) {
return;
}
let flag = false;
let promise: Promise<any> | null = null;
const next = () => {
if (flag) {
throw Error("Unexpected double calling.");
}
flag = true;
const handler1 = (collection[row] || [])[column + 1] as
| CallbackHandler<ContextType>
| undefined;
if (handler1) {
promise = this.runQuery(
collection,
query,
context,
row,
column + 1
);
} else {
promise = this.runQuery(collection, query, context, row + 1, 0);
}
};
await handler(query, context, next);
if (!flag && column === 0) {
promise = this.runQuery(collection, query, context, row + 1, 0);
}
await promise;
}
private async runCommand(
collection: CommandHandler<ContextType>[][],
message: TelegramBot.Message,
match: RegExpExecArray | null,
context: ContextType,
row: number = 0,
column: number = 0
) {
const handler = (collection[row] || [])[column] as
| CommandHandler<ContextType>
| undefined;
if (!handler) {
return;
}
let flag = false;
let promise: Promise<any> | null = null;
const next = () => {
if (flag) {
throw Error("Unexpected double calling.");
}
flag = true;
const handler1 = (collection[row] || [])[column + 1] as
| CommandHandler<ContextType>
| undefined;
if (handler1) {
promise = this.runCommand(
collection,
message,
match,
context,
row,
column + 1
);
} else {
promise = this.runCommand(
collection,
message,
match,
context,
row + 1,
0
);
}
};
await handler(message, match, context, next);
if (!flag && column === 0) {
promise = this.runCommand(
collection,
message,
match,
context,
row + 1,
0
);
}
await promise;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment