mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-11 10:33:21 +08:00
Add Telegram and Discord IM channels
This commit is contained in:
parent
7123fce47a
commit
cb74e1c4c7
66 changed files with 8579 additions and 1910 deletions
120
src/channels/telegram/telegram-api.mjs
Normal file
120
src/channels/telegram/telegram-api.mjs
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
const DEFAULT_BASE_URL = 'https://api.telegram.org/';
|
||||
|
||||
function cleanString(value) {
|
||||
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
||||
}
|
||||
|
||||
function requestSignal(signal, timeoutMs) {
|
||||
const timeout = AbortSignal.timeout(timeoutMs);
|
||||
return signal ? AbortSignal.any([signal, timeout]) : timeout;
|
||||
}
|
||||
|
||||
export function validTelegramToken(value) {
|
||||
return typeof value === 'string' && /^\d{5,20}:[A-Za-z0-9_-]{20,}$/.test(value.trim());
|
||||
}
|
||||
|
||||
export class TelegramApi {
|
||||
#token;
|
||||
#fetch;
|
||||
#baseUrl;
|
||||
|
||||
constructor({ token, fetchImpl = fetch, baseUrl = DEFAULT_BASE_URL }) {
|
||||
if (!validTelegramToken(token)) throw new TypeError('Telegram Bot Token is invalid');
|
||||
if (typeof fetchImpl !== 'function') throw new TypeError('TelegramApi requires fetch');
|
||||
this.#token = token.trim();
|
||||
this.#fetch = fetchImpl;
|
||||
this.#baseUrl = new URL(baseUrl);
|
||||
}
|
||||
|
||||
async getMe(options = {}) {
|
||||
return this.#call('getMe', {}, options);
|
||||
}
|
||||
|
||||
async getWebhookInfo(options = {}) {
|
||||
return this.#call('getWebhookInfo', {}, options);
|
||||
}
|
||||
|
||||
async getUpdates({ offset, timeout = 25, signal } = {}) {
|
||||
const payload = {
|
||||
timeout,
|
||||
limit: 100,
|
||||
allowed_updates: ['message'],
|
||||
...(Number.isSafeInteger(offset) ? { offset } : {}),
|
||||
};
|
||||
return this.#call('getUpdates', payload, {
|
||||
signal,
|
||||
timeoutMs: Math.max(10_000, (timeout + 10) * 1_000),
|
||||
});
|
||||
}
|
||||
|
||||
async sendMessage({ chatId, text, replyToMessageId, messageThreadId, signal }) {
|
||||
return this.#call('sendMessage', {
|
||||
chat_id: chatId,
|
||||
text,
|
||||
link_preview_options: { is_disabled: true },
|
||||
...(replyToMessageId ? {
|
||||
reply_parameters: { message_id: replyToMessageId, allow_sending_without_reply: true },
|
||||
} : {}),
|
||||
...(messageThreadId ? { message_thread_id: messageThreadId } : {}),
|
||||
}, { signal });
|
||||
}
|
||||
|
||||
async editMessageText({ chatId, messageId, text, signal }) {
|
||||
return this.#call('editMessageText', {
|
||||
chat_id: chatId,
|
||||
message_id: messageId,
|
||||
text,
|
||||
link_preview_options: { is_disabled: true },
|
||||
}, { signal });
|
||||
}
|
||||
|
||||
async sendChatAction({ chatId, messageThreadId, signal }) {
|
||||
return this.#call('sendChatAction', {
|
||||
chat_id: chatId,
|
||||
action: 'typing',
|
||||
...(messageThreadId ? { message_thread_id: messageThreadId } : {}),
|
||||
}, { signal });
|
||||
}
|
||||
|
||||
async #call(method, payload, { signal, timeoutMs = 15_000 } = {}) {
|
||||
const url = new URL(this.#baseUrl);
|
||||
url.pathname = `${url.pathname.replace(/\/$/, '')}/bot${this.#token}/${method}`;
|
||||
let response;
|
||||
try {
|
||||
response = await this.#fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
signal: requestSignal(signal, timeoutMs),
|
||||
redirect: 'error',
|
||||
});
|
||||
} catch (error) {
|
||||
if (error?.name === 'AbortError' || error?.name === 'TimeoutError') throw error;
|
||||
throw new Error(`Telegram ${method} transport failed`);
|
||||
}
|
||||
let body;
|
||||
try {
|
||||
body = await response.json();
|
||||
} catch {
|
||||
throw new Error(`Telegram ${method} returned invalid JSON`);
|
||||
}
|
||||
if (!response.ok || body?.ok !== true) {
|
||||
const description = cleanString(body?.description);
|
||||
const error = new Error(description ?? `Telegram ${method} failed`);
|
||||
error.code = Number.isInteger(body?.error_code) ? `telegram-${body.error_code}` : 'telegram-api-error';
|
||||
throw error;
|
||||
}
|
||||
return body.result;
|
||||
}
|
||||
}
|
||||
|
||||
export async function inspectTelegramToken(token, options = {}) {
|
||||
const api = new TelegramApi({ token, ...options });
|
||||
const bot = await api.getMe();
|
||||
if (!bot?.id || bot?.is_bot !== true) throw new Error('Telegram token does not belong to a bot');
|
||||
return {
|
||||
platformId: String(bot.id),
|
||||
name: cleanString([bot.first_name, bot.last_name].filter(Boolean).join(' ')) ?? 'Telegram机器人',
|
||||
username: cleanString(bot.username),
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue