chore: fork @llblab/[email protected].0 as @evandrodevbr/oh-my-pi-telegram
Fork of upstream llblab/[email protected].0 (SHA afe09c5) repackaged as a drop-in extension for can1357/oh-my-pi (omp >= 16.5.2). Changes vs upstream: - Rename package: @llblab/pi-telegram -> @evandrodevbr/oh-my-pi-telegram - Bump version: 0.22.0 -> 0.22.0-evandro.1 - Update repository/homepage/bugs URLs to evandrodevbr - Add omp field in package.json (displayName + compatibleWith) - README: prepend 'Oh My Pi (omp) Compatibility' section with install commands, verified-components table, upstream lineage, and rationale - CHANGELOG: add '0.22.0-evandro.1' entry under Unreleased - docs/: rename @llblab/pi-telegram imports to @evandrodevbr/oh-my-pi-telegram - tests/public-api.test.ts: rename package import paths Gates re-run on the fork: - npm run typecheck -> tsc --noEmit (success) - npm test -> 1229 pass / 0 fail / 1 platform-only skip - npm run audit -> found 0 vulnerabilities - npm run pack:check -> evandrodevbr-oh-my-pi-telegram-0.22.0-evandro.1.tgz Verified: - omp install --link ./pi-telegram -> Linked OK - omp plugin list -> @evandrodevbr/[email protected].0-evandro.1 Upstream lineage: badlogic/pi-telegram -> llblab/pi-telegram (0.22.0) -> evandrodevbr/oh-my-pi-telegram (this fork)
This commit is contained in:
commit
d1d04001d0
155 files changed
+107848
No files matched your search
@@ -0,0 +1,919 @@
|
||||
/**
|
||||
* Telegram queue menu UI helpers
|
||||
* Zones: telegram ui, queue controls, menu composition
|
||||
* Owns queue-menu rendering, queue item callbacks, and queue-menu runtime adapters while core queue mechanics stay in queue
|
||||
*/
|
||||
|
||||
import type { TelegramInlineKeyboardMarkup } from "./keyboard.ts";
|
||||
import type { TelegramModelMenuState } from "./menu.ts";
|
||||
import type { MenuModel } from "./model.ts";
|
||||
import * as Queue from "./queue.ts";
|
||||
|
||||
// --- Queue Menu ---
|
||||
|
||||
const QUEUE_ITEM_PROMPT_HTML_LIMIT = 3600;
|
||||
const QUEUE_ITEM_PROMPT_TRUNCATION_SUFFIX = "\n… [truncated]";
|
||||
const EMPTY_QUEUE_REFRESH_TITLES = [
|
||||
"<b>⌛ Queue is still empty.</b>",
|
||||
"<b>🫙 Still nothing in queue.</b>",
|
||||
"<b>🍃 Queue remains empty.</b>",
|
||||
"<b>🕳 Nothing queued yet.</b>",
|
||||
"<b>🦗 Queue crickets continue.</b>",
|
||||
"<b>🌙 Queue is peacefully idle.</b>",
|
||||
"<b>🧘 Nothing waiting. Very zen.</b>",
|
||||
"<b>🪐 Queue orbit is clear.</b>",
|
||||
"<b>🧺 Basket is empty.</b>",
|
||||
"<b>🔭 No prompts on the horizon.</b>",
|
||||
"<b>🫧 Queue bubbles: none.</b>",
|
||||
"<b>🛸 No queued signals detected.</b>",
|
||||
] as const;
|
||||
type TelegramQueueMenuReplyMarkup = TelegramInlineKeyboardMarkup;
|
||||
interface TelegramQueueMenuItem {
|
||||
chatId: number;
|
||||
replyToMessageId: number;
|
||||
queuePosition: number;
|
||||
isPriority: boolean;
|
||||
priorityEmoji?: string;
|
||||
hasAttachments: boolean;
|
||||
statusSummary: string;
|
||||
promptText: string;
|
||||
}
|
||||
|
||||
function getTelegramQueueItemPromptText<Context>(
|
||||
item: Queue.TelegramQueueItem<Context>,
|
||||
): string {
|
||||
if (item.kind !== "prompt") return item.statusSummary;
|
||||
return (
|
||||
item.content
|
||||
.filter((block) => block.type === "text")
|
||||
.map((block) => block.text)
|
||||
.join("\n")
|
||||
.trim() || item.statusSummary
|
||||
);
|
||||
}
|
||||
|
||||
function toTelegramQueueMenuItems<Context>(
|
||||
items: readonly Queue.TelegramQueueItem<Context>[],
|
||||
): TelegramQueueMenuItem[] {
|
||||
return items.map((item, index) => {
|
||||
return {
|
||||
chatId: item.chatId,
|
||||
replyToMessageId: item.replyToMessageId,
|
||||
queuePosition: index + 1,
|
||||
isPriority: item.queueLane === "priority",
|
||||
priorityEmoji: item.kind === "prompt" ? item.priorityEmoji : undefined,
|
||||
hasAttachments:
|
||||
item.kind === "prompt" && item.queuedAttachments.length > 0,
|
||||
statusSummary: item.statusSummary,
|
||||
promptText: getTelegramQueueItemPromptText(item),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function buildTelegramQueueMenuReplyMarkup(
|
||||
items: readonly TelegramQueueMenuItem[],
|
||||
emptyRefreshIndex = 0,
|
||||
): TelegramQueueMenuReplyMarkup {
|
||||
const backRow = [{ text: "⬆️ Main menu", callback_data: "menu:back" }];
|
||||
const nextEmptyRefreshIndex =
|
||||
(emptyRefreshIndex + 1) % EMPTY_QUEUE_REFRESH_TITLES.length;
|
||||
const refreshData =
|
||||
items.length === 0
|
||||
? `queue:refresh:${nextEmptyRefreshIndex}`
|
||||
: "queue:refresh";
|
||||
const refreshRow = [{ text: "🌀 Refresh", callback_data: refreshData }];
|
||||
if (items.length === 0) return { inline_keyboard: [backRow, refreshRow] };
|
||||
const rows = items.map((item, index) => {
|
||||
const prefix = item.isPriority
|
||||
? `${item.priorityEmoji ?? "⚡"} `
|
||||
: item.hasAttachments
|
||||
? "📎 "
|
||||
: "";
|
||||
const label = `${index + 1}. ${prefix}${item.statusSummary}`;
|
||||
return [
|
||||
{
|
||||
text: label,
|
||||
callback_data: `queue:pick:${item.chatId}:${item.replyToMessageId}`,
|
||||
},
|
||||
];
|
||||
});
|
||||
return { inline_keyboard: [backRow, refreshRow, ...rows] };
|
||||
}
|
||||
|
||||
function findTelegramQueueItem<Context>(
|
||||
items: readonly Queue.TelegramQueueItem<Context>[],
|
||||
chatId: number,
|
||||
replyToMessageId: number,
|
||||
): Queue.TelegramQueueItem<Context> | undefined {
|
||||
return items.find((item) => {
|
||||
return item.chatId === chatId && item.replyToMessageId === replyToMessageId;
|
||||
});
|
||||
}
|
||||
|
||||
function findTelegramQueueMenuItem(
|
||||
items: readonly TelegramQueueMenuItem[],
|
||||
chatId: number,
|
||||
replyToMessageId: number,
|
||||
): TelegramQueueMenuItem | undefined {
|
||||
return items.find((item) => {
|
||||
return item.chatId === chatId && item.replyToMessageId === replyToMessageId;
|
||||
});
|
||||
}
|
||||
|
||||
function escapeTelegramQueueMenuHtmlChar(char: string): string {
|
||||
if (char === "&") return "&";
|
||||
if (char === "<") return "<";
|
||||
if (char === ">") return ">";
|
||||
return char;
|
||||
}
|
||||
|
||||
function escapeTelegramQueueMenuHtml(text: string): string {
|
||||
return Array.from(text).map(escapeTelegramQueueMenuHtmlChar).join("");
|
||||
}
|
||||
|
||||
function escapeTelegramQueueMenuHtmlPreview(text: string): string {
|
||||
const suffix = escapeTelegramQueueMenuHtml(
|
||||
QUEUE_ITEM_PROMPT_TRUNCATION_SUFFIX,
|
||||
);
|
||||
let escaped = "";
|
||||
let truncated = false;
|
||||
for (const char of text) {
|
||||
const next = escapeTelegramQueueMenuHtmlChar(char);
|
||||
if (
|
||||
escaped.length + next.length + suffix.length >
|
||||
QUEUE_ITEM_PROMPT_HTML_LIMIT
|
||||
) {
|
||||
truncated = true;
|
||||
break;
|
||||
}
|
||||
escaped += next;
|
||||
}
|
||||
return truncated ? escaped + suffix : escaped;
|
||||
}
|
||||
|
||||
function getTelegramQueueMenuItemText(item: TelegramQueueMenuItem): string {
|
||||
const badge = item.isPriority ? ` ${item.priorityEmoji ?? "⚡"}` : "";
|
||||
const heading = `<b>${item.queuePosition}.</b>${badge}`;
|
||||
const preview = `<pre>${escapeTelegramQueueMenuHtmlPreview(item.promptText)}</pre>`;
|
||||
return `${heading}\n${preview}`;
|
||||
}
|
||||
|
||||
function buildTelegramQueueItemSubmenuReplyMarkup(
|
||||
chatId: number,
|
||||
replyToMessageId: number,
|
||||
isPriority: boolean,
|
||||
): TelegramQueueMenuReplyMarkup {
|
||||
return {
|
||||
inline_keyboard: [
|
||||
[{ text: "⬆️ Back", callback_data: "queue:list" }],
|
||||
[
|
||||
{
|
||||
text: isPriority ? "🟡 Priority" : "⚫️ Priority",
|
||||
callback_data: `queue:prio-set:${chatId}:${replyToMessageId}:priority`,
|
||||
},
|
||||
{
|
||||
text: isPriority ? "⚫️ Normal" : "🟣 Normal",
|
||||
callback_data: `queue:prio-set:${chatId}:${replyToMessageId}:normal`,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
text: "🗑 Delete",
|
||||
callback_data: `queue:delete:${chatId}:${replyToMessageId}`,
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function buildTelegramQueueDeleteConfirmationReplyMarkup(
|
||||
chatId: number,
|
||||
replyToMessageId: number,
|
||||
): TelegramQueueMenuReplyMarkup {
|
||||
return {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{
|
||||
text: "🗑 Yes, delete",
|
||||
callback_data: `queue:confirm-delete:${chatId}:${replyToMessageId}`,
|
||||
},
|
||||
{
|
||||
text: "❌ No",
|
||||
callback_data: `queue:keep:${chatId}:${replyToMessageId}`,
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
}
|
||||
interface TelegramQueueMenuCallbackDeps<Context = unknown> {
|
||||
getQueuedItems: () => TelegramQueueMenuItem[];
|
||||
findItem: (
|
||||
chatId: number,
|
||||
replyToMessageId: number,
|
||||
) => TelegramQueueMenuItem | undefined;
|
||||
togglePriority: (chatId: number, replyToMessageId: number) => boolean;
|
||||
setPriority: (
|
||||
chatId: number,
|
||||
replyToMessageId: number,
|
||||
enabled: boolean,
|
||||
) => boolean;
|
||||
cancelItem: (
|
||||
chatId: number,
|
||||
replyToMessageId: number,
|
||||
ctx: Context,
|
||||
) => boolean;
|
||||
updateQueueMessage: (
|
||||
chatId: number,
|
||||
messageId: number,
|
||||
text: string,
|
||||
replyMarkup: TelegramQueueMenuReplyMarkup,
|
||||
) => Promise<number | undefined>;
|
||||
answerCallbackQuery: (
|
||||
callbackQueryId: string,
|
||||
text?: string,
|
||||
) => Promise<void>;
|
||||
updateStatus: (ctx: Context) => void;
|
||||
}
|
||||
|
||||
async function handleTelegramQueueMenuCallback<Context>(
|
||||
callbackQueryId: string,
|
||||
data: string,
|
||||
replyChatId: number,
|
||||
replyMessageId: number,
|
||||
ctx: Context,
|
||||
deps: TelegramQueueMenuCallbackDeps<Context>,
|
||||
): Promise<boolean> {
|
||||
if (!data.startsWith("queue:")) return false;
|
||||
if (data === "queue:noop") {
|
||||
await deps.answerCallbackQuery(callbackQueryId);
|
||||
return true;
|
||||
}
|
||||
if (data === "queue:list") {
|
||||
await updateTelegramQueueMenuList(
|
||||
callbackQueryId,
|
||||
replyChatId,
|
||||
replyMessageId,
|
||||
deps,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
const refreshMatch = data.match(/^queue:refresh(?::(\d+))?$/);
|
||||
if (refreshMatch) {
|
||||
await updateTelegramQueueMenuList(
|
||||
callbackQueryId,
|
||||
replyChatId,
|
||||
replyMessageId,
|
||||
deps,
|
||||
undefined,
|
||||
refreshMatch[1] === undefined ? 0 : Number(refreshMatch[1]),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
const pickMatch = data.match(/^queue:pick:(\d+):(\d+)$/);
|
||||
if (pickMatch) {
|
||||
await handleTelegramQueueMenuPick(
|
||||
callbackQueryId,
|
||||
replyChatId,
|
||||
replyMessageId,
|
||||
Number(pickMatch[1]),
|
||||
Number(pickMatch[2]),
|
||||
deps,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
const prioSetMatch = data.match(
|
||||
/^queue:prio-set:(\d+):(\d+):(priority|normal)$/,
|
||||
);
|
||||
if (prioSetMatch) {
|
||||
await handleTelegramQueueMenuPrioritySet(
|
||||
callbackQueryId,
|
||||
replyChatId,
|
||||
replyMessageId,
|
||||
Number(prioSetMatch[1]),
|
||||
Number(prioSetMatch[2]),
|
||||
prioSetMatch[3] === "priority",
|
||||
ctx,
|
||||
deps,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
const prioMatch = data.match(/^queue:prio:(\d+):(\d+)$/);
|
||||
if (prioMatch) {
|
||||
await handleTelegramQueueMenuPriority(
|
||||
callbackQueryId,
|
||||
replyChatId,
|
||||
replyMessageId,
|
||||
Number(prioMatch[1]),
|
||||
Number(prioMatch[2]),
|
||||
ctx,
|
||||
deps,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
const deleteMatch = data.match(/^queue:(?:delete|cancel):(\d+):(\d+)$/);
|
||||
if (deleteMatch) {
|
||||
await handleTelegramQueueMenuDeleteRequest(
|
||||
callbackQueryId,
|
||||
replyChatId,
|
||||
replyMessageId,
|
||||
Number(deleteMatch[1]),
|
||||
Number(deleteMatch[2]),
|
||||
deps,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
const keepMatch = data.match(/^queue:keep:(\d+):(\d+)$/);
|
||||
if (keepMatch) {
|
||||
await handleTelegramQueueMenuKeep(
|
||||
callbackQueryId,
|
||||
replyChatId,
|
||||
replyMessageId,
|
||||
Number(keepMatch[1]),
|
||||
Number(keepMatch[2]),
|
||||
deps,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
const confirmDeleteMatch = data.match(/^queue:confirm-delete:(\d+):(\d+)$/);
|
||||
if (confirmDeleteMatch) {
|
||||
await handleTelegramQueueMenuConfirmDelete(
|
||||
callbackQueryId,
|
||||
replyChatId,
|
||||
replyMessageId,
|
||||
Number(confirmDeleteMatch[1]),
|
||||
Number(confirmDeleteMatch[2]),
|
||||
ctx,
|
||||
deps,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getTelegramQueueMenuListText(
|
||||
items: readonly TelegramQueueMenuItem[],
|
||||
emptyRefreshIndex?: number,
|
||||
): string {
|
||||
if (items.length > 0) return "<b>⏳ Queue:</b>";
|
||||
if (emptyRefreshIndex === undefined) return "<b>⌛ Queue is empty.</b>";
|
||||
return EMPTY_QUEUE_REFRESH_TITLES[
|
||||
emptyRefreshIndex % EMPTY_QUEUE_REFRESH_TITLES.length
|
||||
];
|
||||
}
|
||||
|
||||
async function updateTelegramQueueMenuList<Context>(
|
||||
callbackQueryId: string,
|
||||
replyChatId: number,
|
||||
replyMessageId: number,
|
||||
deps: TelegramQueueMenuCallbackDeps<Context>,
|
||||
notice?: string,
|
||||
emptyRefreshIndex?: number,
|
||||
): Promise<void> {
|
||||
const items = deps.getQueuedItems();
|
||||
await deps.updateQueueMessage(
|
||||
replyChatId,
|
||||
replyMessageId,
|
||||
getTelegramQueueMenuListText(items, emptyRefreshIndex),
|
||||
buildTelegramQueueMenuReplyMarkup(items, emptyRefreshIndex),
|
||||
);
|
||||
await deps.answerCallbackQuery(callbackQueryId, notice);
|
||||
}
|
||||
|
||||
async function refreshStaleTelegramQueueMenuItem<Context>(
|
||||
callbackQueryId: string,
|
||||
replyChatId: number,
|
||||
replyMessageId: number,
|
||||
deps: TelegramQueueMenuCallbackDeps<Context>,
|
||||
): Promise<void> {
|
||||
await updateTelegramQueueMenuList(
|
||||
callbackQueryId,
|
||||
replyChatId,
|
||||
replyMessageId,
|
||||
deps,
|
||||
"Item no longer in queue.",
|
||||
);
|
||||
}
|
||||
|
||||
async function handleTelegramQueueMenuPick<Context>(
|
||||
callbackQueryId: string,
|
||||
replyChatId: number,
|
||||
replyMessageId: number,
|
||||
chatId: number,
|
||||
msgId: number,
|
||||
deps: TelegramQueueMenuCallbackDeps<Context>,
|
||||
): Promise<void> {
|
||||
const item = deps.findItem(chatId, msgId);
|
||||
if (!item) {
|
||||
return refreshStaleTelegramQueueMenuItem(
|
||||
callbackQueryId,
|
||||
replyChatId,
|
||||
replyMessageId,
|
||||
deps,
|
||||
);
|
||||
}
|
||||
await deps.updateQueueMessage(
|
||||
replyChatId,
|
||||
replyMessageId,
|
||||
getTelegramQueueMenuItemText(item),
|
||||
buildTelegramQueueItemSubmenuReplyMarkup(chatId, msgId, item.isPriority),
|
||||
);
|
||||
await deps.answerCallbackQuery(callbackQueryId);
|
||||
}
|
||||
|
||||
async function handleTelegramQueueMenuPriority<Context>(
|
||||
callbackQueryId: string,
|
||||
replyChatId: number,
|
||||
replyMessageId: number,
|
||||
chatId: number,
|
||||
msgId: number,
|
||||
ctx: Context,
|
||||
deps: TelegramQueueMenuCallbackDeps<Context>,
|
||||
): Promise<void> {
|
||||
const item = deps.findItem(chatId, msgId);
|
||||
if (!item) {
|
||||
return refreshStaleTelegramQueueMenuItem(
|
||||
callbackQueryId,
|
||||
replyChatId,
|
||||
replyMessageId,
|
||||
deps,
|
||||
);
|
||||
}
|
||||
await updateTelegramQueueMenuPriority(
|
||||
callbackQueryId,
|
||||
replyChatId,
|
||||
replyMessageId,
|
||||
chatId,
|
||||
msgId,
|
||||
!item.isPriority,
|
||||
ctx,
|
||||
deps,
|
||||
);
|
||||
}
|
||||
|
||||
async function handleTelegramQueueMenuPrioritySet<Context>(
|
||||
callbackQueryId: string,
|
||||
replyChatId: number,
|
||||
replyMessageId: number,
|
||||
chatId: number,
|
||||
msgId: number,
|
||||
enabled: boolean,
|
||||
ctx: Context,
|
||||
deps: TelegramQueueMenuCallbackDeps<Context>,
|
||||
): Promise<void> {
|
||||
const item = deps.findItem(chatId, msgId);
|
||||
if (!item) {
|
||||
return refreshStaleTelegramQueueMenuItem(
|
||||
callbackQueryId,
|
||||
replyChatId,
|
||||
replyMessageId,
|
||||
deps,
|
||||
);
|
||||
}
|
||||
await updateTelegramQueueMenuPriority(
|
||||
callbackQueryId,
|
||||
replyChatId,
|
||||
replyMessageId,
|
||||
chatId,
|
||||
msgId,
|
||||
enabled,
|
||||
ctx,
|
||||
deps,
|
||||
);
|
||||
}
|
||||
|
||||
async function updateTelegramQueueMenuPriority<Context>(
|
||||
callbackQueryId: string,
|
||||
replyChatId: number,
|
||||
replyMessageId: number,
|
||||
chatId: number,
|
||||
msgId: number,
|
||||
enabled: boolean,
|
||||
ctx: Context,
|
||||
deps: TelegramQueueMenuCallbackDeps<Context>,
|
||||
): Promise<void> {
|
||||
deps.setPriority(chatId, msgId, enabled);
|
||||
deps.updateStatus(ctx);
|
||||
const updated = deps.findItem(chatId, msgId);
|
||||
if (!updated) {
|
||||
return refreshStaleTelegramQueueMenuItem(
|
||||
callbackQueryId,
|
||||
replyChatId,
|
||||
replyMessageId,
|
||||
deps,
|
||||
);
|
||||
}
|
||||
await deps.updateQueueMessage(
|
||||
replyChatId,
|
||||
replyMessageId,
|
||||
getTelegramQueueMenuItemText(updated),
|
||||
buildTelegramQueueItemSubmenuReplyMarkup(chatId, msgId, updated.isPriority),
|
||||
);
|
||||
await deps.answerCallbackQuery(
|
||||
callbackQueryId,
|
||||
updated.isPriority ? "Prioritized." : "Normal priority.",
|
||||
);
|
||||
}
|
||||
|
||||
async function handleTelegramQueueMenuDeleteRequest<Context>(
|
||||
callbackQueryId: string,
|
||||
replyChatId: number,
|
||||
replyMessageId: number,
|
||||
chatId: number,
|
||||
msgId: number,
|
||||
deps: TelegramQueueMenuCallbackDeps<Context>,
|
||||
): Promise<void> {
|
||||
const item = deps.findItem(chatId, msgId);
|
||||
if (!item) {
|
||||
return refreshStaleTelegramQueueMenuItem(
|
||||
callbackQueryId,
|
||||
replyChatId,
|
||||
replyMessageId,
|
||||
deps,
|
||||
);
|
||||
}
|
||||
await deps.updateQueueMessage(
|
||||
replyChatId,
|
||||
replyMessageId,
|
||||
"<b>Delete this queued prompt?</b>",
|
||||
buildTelegramQueueDeleteConfirmationReplyMarkup(chatId, msgId),
|
||||
);
|
||||
await deps.answerCallbackQuery(callbackQueryId);
|
||||
}
|
||||
|
||||
async function handleTelegramQueueMenuKeep<Context>(
|
||||
callbackQueryId: string,
|
||||
replyChatId: number,
|
||||
replyMessageId: number,
|
||||
chatId: number,
|
||||
msgId: number,
|
||||
deps: TelegramQueueMenuCallbackDeps<Context>,
|
||||
): Promise<void> {
|
||||
const item = deps.findItem(chatId, msgId);
|
||||
if (!item) {
|
||||
return refreshStaleTelegramQueueMenuItem(
|
||||
callbackQueryId,
|
||||
replyChatId,
|
||||
replyMessageId,
|
||||
deps,
|
||||
);
|
||||
}
|
||||
await deps.updateQueueMessage(
|
||||
replyChatId,
|
||||
replyMessageId,
|
||||
getTelegramQueueMenuItemText(item),
|
||||
buildTelegramQueueItemSubmenuReplyMarkup(chatId, msgId, item.isPriority),
|
||||
);
|
||||
await deps.answerCallbackQuery(callbackQueryId, "Kept in queue.");
|
||||
}
|
||||
|
||||
async function handleTelegramQueueMenuConfirmDelete<Context>(
|
||||
callbackQueryId: string,
|
||||
replyChatId: number,
|
||||
replyMessageId: number,
|
||||
chatId: number,
|
||||
msgId: number,
|
||||
ctx: Context,
|
||||
deps: TelegramQueueMenuCallbackDeps<Context>,
|
||||
): Promise<void> {
|
||||
const removed = deps.cancelItem(chatId, msgId, ctx);
|
||||
deps.updateStatus(ctx);
|
||||
await updateTelegramQueueMenuList(
|
||||
callbackQueryId,
|
||||
replyChatId,
|
||||
replyMessageId,
|
||||
deps,
|
||||
removed ? "Deleted from queue." : "Item not found.",
|
||||
);
|
||||
}
|
||||
|
||||
interface TelegramQueueMenuCallbackQuery {
|
||||
id: string;
|
||||
data?: string;
|
||||
message?: { chat?: { id?: number }; message_id?: number };
|
||||
}
|
||||
interface TelegramQueueMenuRuntime<Context> {
|
||||
openQueueMenu: (
|
||||
chatId: number,
|
||||
replyToMessageId: number,
|
||||
ctx: Context,
|
||||
) => Promise<void>;
|
||||
handleCallbackQuery: (
|
||||
query: TelegramQueueMenuCallbackQuery,
|
||||
ctx: Context,
|
||||
) => Promise<boolean>;
|
||||
}
|
||||
export function createTelegramQueueMenuRuntime<
|
||||
Context,
|
||||
TModel extends MenuModel = MenuModel,
|
||||
>(deps: {
|
||||
telegramQueueStore: Queue.TelegramQueueStateStore<Context>;
|
||||
queueMutationRuntime: Queue.TelegramQueueMutationController<Context>;
|
||||
sendInteractiveMessage: (
|
||||
chatId: number,
|
||||
text: string,
|
||||
mode: "html",
|
||||
replyMarkup: TelegramQueueMenuReplyMarkup,
|
||||
) => Promise<number | undefined>;
|
||||
editInteractiveMessage: (
|
||||
chatId: number,
|
||||
messageId: number,
|
||||
text: string,
|
||||
mode: "html",
|
||||
replyMarkup: TelegramQueueMenuReplyMarkup,
|
||||
) => Promise<void>;
|
||||
answerCallbackQuery: (
|
||||
callbackQueryId: string,
|
||||
text?: string,
|
||||
) => Promise<void>;
|
||||
getModelMenuState: (
|
||||
chatId: number,
|
||||
ctx: Context,
|
||||
) => Promise<TelegramModelMenuState<TModel>>;
|
||||
getStoredModelMenuState: (
|
||||
messageId: number | undefined,
|
||||
chatId?: number,
|
||||
) => TelegramModelMenuState<TModel> | undefined;
|
||||
storeModelMenuState: (state: TelegramModelMenuState<TModel>) => void;
|
||||
updateStatusMessage: (
|
||||
state: TelegramModelMenuState<TModel>,
|
||||
ctx: Context,
|
||||
) => Promise<void>;
|
||||
updateStatus: (ctx: Context) => void;
|
||||
}): TelegramQueueMenuRuntime<Context> {
|
||||
const sendQueueMenuMessage = createQueueMenuSendMessageAdapter(
|
||||
deps.sendInteractiveMessage,
|
||||
);
|
||||
const editQueueMenuMessage = createQueueMenuEditMessageAdapter(
|
||||
deps.editInteractiveMessage,
|
||||
);
|
||||
return {
|
||||
openQueueMenu: createOpenQueueMenu<Context, TModel>({
|
||||
getQueuedItems: deps.telegramQueueStore.getQueuedItems,
|
||||
getModelMenuState: deps.getModelMenuState,
|
||||
storeModelMenuState: deps.storeModelMenuState,
|
||||
sendInteractiveMessage: sendQueueMenuMessage,
|
||||
}),
|
||||
handleCallbackQuery: createQueueMenuCallbackHandler<Context, TModel>({
|
||||
telegramQueueStore: deps.telegramQueueStore,
|
||||
queueMutationRuntime: deps.queueMutationRuntime,
|
||||
editInteractiveMessage: editQueueMenuMessage,
|
||||
getStoredModelMenuState: deps.getStoredModelMenuState,
|
||||
updateStatusMessage: deps.updateStatusMessage,
|
||||
answerCallbackQuery: deps.answerCallbackQuery,
|
||||
updateStatus: deps.updateStatus,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function createOpenQueueMenu<
|
||||
Context,
|
||||
TModel extends MenuModel = MenuModel,
|
||||
>(deps: {
|
||||
getQueuedItems: () => Queue.TelegramQueueItem<Context>[];
|
||||
getModelMenuState: (
|
||||
chatId: number,
|
||||
ctx: Context,
|
||||
) => Promise<TelegramModelMenuState<TModel>>;
|
||||
storeModelMenuState: (state: TelegramModelMenuState<TModel>) => void;
|
||||
sendInteractiveMessage: (
|
||||
chatId: number,
|
||||
replyToMessageId: number,
|
||||
text: string,
|
||||
replyMarkup: TelegramQueueMenuReplyMarkup,
|
||||
) => Promise<number | undefined>;
|
||||
}) {
|
||||
return async (
|
||||
chatId: number,
|
||||
replyToMessageId: number,
|
||||
ctx: Context,
|
||||
): Promise<void> => {
|
||||
const state = await deps.getModelMenuState(chatId, ctx);
|
||||
const menuItems = toTelegramQueueMenuItems(deps.getQueuedItems());
|
||||
const text = getTelegramQueueMenuListText(menuItems);
|
||||
const messageId = await deps.sendInteractiveMessage(
|
||||
chatId,
|
||||
replyToMessageId,
|
||||
text,
|
||||
buildTelegramQueueMenuReplyMarkup(menuItems),
|
||||
);
|
||||
if (messageId === undefined) return;
|
||||
state.messageId = messageId;
|
||||
state.mode = "queue";
|
||||
deps.storeModelMenuState(state);
|
||||
};
|
||||
}
|
||||
|
||||
function createQueueMenuCallbackHandler<
|
||||
Context,
|
||||
TModel extends MenuModel = MenuModel,
|
||||
>(deps: {
|
||||
telegramQueueStore: Queue.TelegramQueueStateStore<Context>;
|
||||
queueMutationRuntime: Queue.TelegramQueueMutationController<Context>;
|
||||
editInteractiveMessage: (
|
||||
chatId: number,
|
||||
messageId: number,
|
||||
text: string,
|
||||
replyMarkup: TelegramQueueMenuReplyMarkup,
|
||||
) => Promise<number | undefined>;
|
||||
getStoredModelMenuState: (
|
||||
messageId: number | undefined,
|
||||
chatId?: number,
|
||||
) => TelegramModelMenuState<TModel> | undefined;
|
||||
updateStatusMessage: (
|
||||
state: TelegramModelMenuState<TModel>,
|
||||
ctx: Context,
|
||||
) => Promise<void>;
|
||||
answerCallbackQuery: (
|
||||
callbackQueryId: string,
|
||||
text?: string,
|
||||
) => Promise<void>;
|
||||
updateStatus: (ctx: Context) => void;
|
||||
}) {
|
||||
return async (
|
||||
query: TelegramQueueMenuCallbackQuery,
|
||||
ctx: Context,
|
||||
): Promise<boolean> => {
|
||||
const data = query.data;
|
||||
const chatId = query.message?.chat?.id;
|
||||
const messageId = query.message?.message_id;
|
||||
if (!data || typeof chatId !== "number" || typeof messageId !== "number")
|
||||
return false;
|
||||
if (data === "menu:queue" || data === "status:queue") {
|
||||
const state = deps.getStoredModelMenuState(messageId, chatId);
|
||||
if (!state) {
|
||||
await deps.answerCallbackQuery(
|
||||
query.id,
|
||||
"Interactive message expired.",
|
||||
);
|
||||
return true;
|
||||
}
|
||||
const menuItems = toTelegramQueueMenuItems(
|
||||
deps.telegramQueueStore.getQueuedItems(),
|
||||
);
|
||||
await deps.editInteractiveMessage(
|
||||
chatId,
|
||||
messageId,
|
||||
getTelegramQueueMenuListText(menuItems),
|
||||
buildTelegramQueueMenuReplyMarkup(menuItems),
|
||||
);
|
||||
state.mode = "queue";
|
||||
await deps.answerCallbackQuery(query.id);
|
||||
return true;
|
||||
}
|
||||
if (!data.startsWith("queue:")) return false;
|
||||
const getQueueSnapshot = () => {
|
||||
return deps.telegramQueueStore.getQueuedItems();
|
||||
};
|
||||
const toMenuItems = () => {
|
||||
return toTelegramQueueMenuItems(getQueueSnapshot());
|
||||
};
|
||||
const findItem = (cId: number, rId: number) => {
|
||||
return findTelegramQueueMenuItem(toMenuItems(), cId, rId);
|
||||
};
|
||||
return handleTelegramQueueMenuCallback(
|
||||
query.id,
|
||||
data,
|
||||
chatId,
|
||||
messageId,
|
||||
ctx,
|
||||
{
|
||||
getQueuedItems: toMenuItems,
|
||||
findItem,
|
||||
togglePriority: (cId, rId) => {
|
||||
return toggleQueuedTelegramPromptPriority(cId, rId, ctx, {
|
||||
getQueueSnapshot,
|
||||
queueMutationRuntime: deps.queueMutationRuntime,
|
||||
});
|
||||
},
|
||||
setPriority: (cId, rId, enabled) => {
|
||||
return setQueuedTelegramPromptPriority(cId, rId, enabled, ctx, {
|
||||
getQueueSnapshot,
|
||||
queueMutationRuntime: deps.queueMutationRuntime,
|
||||
});
|
||||
},
|
||||
cancelItem: (cId, rId, c) => {
|
||||
return cancelQueuedTelegramItem(cId, rId, c, {
|
||||
getQueueSnapshot,
|
||||
queueMutationRuntime: deps.queueMutationRuntime,
|
||||
});
|
||||
},
|
||||
updateQueueMessage: deps.editInteractiveMessage,
|
||||
answerCallbackQuery: deps.answerCallbackQuery,
|
||||
updateStatus: deps.updateStatus,
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function toggleQueuedTelegramPromptPriority<Context>(
|
||||
chatId: number,
|
||||
replyToMessageId: number,
|
||||
ctx: Context,
|
||||
deps: {
|
||||
getQueueSnapshot: () => Queue.TelegramQueueItem<Context>[];
|
||||
queueMutationRuntime: Queue.TelegramQueueMutationController<Context>;
|
||||
},
|
||||
): boolean {
|
||||
const item = findTelegramQueueItem(
|
||||
deps.getQueueSnapshot(),
|
||||
chatId,
|
||||
replyToMessageId,
|
||||
);
|
||||
if (!item) return false;
|
||||
if (item.queueLane === "priority") {
|
||||
deps.queueMutationRuntime.clearPriorityByMessageId(replyToMessageId, ctx);
|
||||
} else {
|
||||
deps.queueMutationRuntime.prioritizeByMessageId(replyToMessageId, ctx);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function setQueuedTelegramPromptPriority<Context>(
|
||||
chatId: number,
|
||||
replyToMessageId: number,
|
||||
enabled: boolean,
|
||||
ctx: Context,
|
||||
deps: {
|
||||
getQueueSnapshot: () => Queue.TelegramQueueItem<Context>[];
|
||||
queueMutationRuntime: Queue.TelegramQueueMutationController<Context>;
|
||||
},
|
||||
): boolean {
|
||||
const item = findTelegramQueueItem(
|
||||
deps.getQueueSnapshot(),
|
||||
chatId,
|
||||
replyToMessageId,
|
||||
);
|
||||
if (!item) return false;
|
||||
if (enabled) {
|
||||
deps.queueMutationRuntime.prioritizeByMessageId(replyToMessageId, ctx);
|
||||
} else {
|
||||
deps.queueMutationRuntime.clearPriorityByMessageId(replyToMessageId, ctx);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function cancelQueuedTelegramItem<Context>(
|
||||
chatId: number,
|
||||
replyToMessageId: number,
|
||||
ctx: Context,
|
||||
deps: {
|
||||
getQueueSnapshot: () => Queue.TelegramQueueItem<Context>[];
|
||||
queueMutationRuntime: Queue.TelegramQueueMutationController<Context>;
|
||||
},
|
||||
): boolean {
|
||||
const item = findTelegramQueueItem(
|
||||
deps.getQueueSnapshot(),
|
||||
chatId,
|
||||
replyToMessageId,
|
||||
);
|
||||
if (!item) return false;
|
||||
return (
|
||||
deps.queueMutationRuntime.removeByMessageIds([item.replyToMessageId], ctx) >
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
function createQueueMenuSendMessageAdapter(
|
||||
sendInteractiveMessage: (
|
||||
chatId: number,
|
||||
text: string,
|
||||
mode: "html",
|
||||
replyMarkup: TelegramQueueMenuReplyMarkup,
|
||||
) => Promise<number | undefined>,
|
||||
) {
|
||||
return (
|
||||
chatId: number,
|
||||
_replyToMessageId: number,
|
||||
text: string,
|
||||
replyMarkup: TelegramQueueMenuReplyMarkup,
|
||||
): Promise<number | undefined> => {
|
||||
return sendInteractiveMessage(chatId, text, "html", replyMarkup);
|
||||
};
|
||||
}
|
||||
|
||||
function createQueueMenuEditMessageAdapter(
|
||||
editInteractiveMessage: (
|
||||
chatId: number,
|
||||
messageId: number,
|
||||
text: string,
|
||||
mode: "html",
|
||||
replyMarkup: TelegramQueueMenuReplyMarkup,
|
||||
) => Promise<void>,
|
||||
) {
|
||||
return (
|
||||
chatId: number,
|
||||
messageId: number,
|
||||
text: string,
|
||||
replyMarkup: TelegramQueueMenuReplyMarkup,
|
||||
): Promise<number | undefined> => {
|
||||
return editInteractiveMessage(
|
||||
chatId,
|
||||
messageId,
|
||||
text,
|
||||
"html",
|
||||
replyMarkup,
|
||||
).then(() => {
|
||||
return undefined as number | undefined;
|
||||
});
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user