From 363d8fcfa0c977fb704577834e65b39811e17954 Mon Sep 17 00:00:00 2001 From: Aleksei Rybin Date: Thu, 30 Jan 2025 17:10:19 +0700 Subject: [PATCH] add webhook support --- .env.sample | 1 + package.json | 1 + pnpm-lock.yaml | 56 +++++++++++++++++++++++++++++ src/env.ts | 1 + src/index.ts | 14 +++++++- src/senderBot.ts | 92 +++++++++++++++++++++++++++++++----------------- 6 files changed, 131 insertions(+), 34 deletions(-) diff --git a/.env.sample b/.env.sample index 9d406d9..274936a 100644 --- a/.env.sample +++ b/.env.sample @@ -11,3 +11,4 @@ TELEGRAM_CHAT_ID= # output to discord webhook # OUTPUT_BACKEND=discord_webhook +# DISCORD_WEBHOOK_URL=https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks diff --git a/package.json b/package.json index b476938..e59250a 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ }, "dependencies": { "@grammyjs/auto-retry": "^2.0.2", + "discord-webhook-node": "^1.1.8", "discord.js": "^14.17.3", "discord.js-selfbot-v13": "^3.5.1", "dotenv": "^16.4.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b77c26c..16e2d9c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@grammyjs/auto-retry': specifier: ^2.0.2 version: 2.0.2(grammy@1.34.1) + discord-webhook-node: + specifier: ^1.1.8 + version: 1.1.8 discord.js: specifier: ^14.17.3 version: 14.17.3 @@ -280,6 +283,9 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -315,6 +321,10 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + commander@12.1.0: resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} engines: {node: '>=18'} @@ -342,6 +352,10 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dijkstrajs@1.0.3: resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} @@ -351,6 +365,9 @@ packages: discord-api-types@0.37.117: resolution: {integrity: sha512-d+Z6RKd7v3q22lsil7yASucqMfVVV0s0XSqu3cw7kyHVXiDO/mAnqMzqma26IYnIm2mk3TlupYJDGrdL908ZKA==} + discord-webhook-node@1.1.8: + resolution: {integrity: sha512-3u0rrwywwYGc6HrgYirN/9gkBYqmdpvReyQjapoXARAHi0P0fIyf3W5tS5i3U3cc7e44E+e7dIHYUeec7yWaug==} + discord.js-selfbot-v13@3.5.1: resolution: {integrity: sha512-AG4bYnymw8Ji0fsjVKG0f3gKoH/xuGTKn2TpxGtLhmiL8JwYywsoF7IFQ/Cl3avOPY+MKmeaR0JNScImQvG0tw==} engines: {node: '>=18.17'} @@ -485,6 +502,10 @@ packages: flatted@3.3.2: resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} + form-data@3.0.2: + resolution: {integrity: sha512-sJe+TQb2vIaIyO783qN6BlMYWMw3WBOHA1Ay2qxsnjuafEOQFJ2JakedOQirT6D5XPRxDvS7AHYyem9fTpb4LQ==} + engines: {node: '>= 6'} + get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -599,6 +620,14 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -1146,6 +1175,8 @@ snapshots: argparse@2.0.1: {} + asynckit@0.4.0: {} + balanced-match@1.0.2: {} brace-expansion@1.1.11: @@ -1182,6 +1213,10 @@ snapshots: color-name@1.1.4: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + commander@12.1.0: {} concat-map@0.0.1: {} @@ -1200,12 +1235,21 @@ snapshots: deep-is@0.1.4: {} + delayed-stream@1.0.0: {} + dijkstrajs@1.0.3: {} discord-api-types@0.37.115: {} discord-api-types@0.37.117: {} + discord-webhook-node@1.1.8: + dependencies: + form-data: 3.0.2 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + discord.js-selfbot-v13@3.5.1: dependencies: '@discordjs/builders': 1.10.0 @@ -1390,6 +1434,12 @@ snapshots: flatted@3.3.2: {} + form-data@3.0.2: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + get-caller-file@2.0.5: {} glob-parent@5.1.2: @@ -1483,6 +1533,12 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 diff --git a/src/env.ts b/src/env.ts index c17e8a2..83b4ff0 100644 --- a/src/env.ts +++ b/src/env.ts @@ -13,6 +13,7 @@ export interface Env { TELEGRAM_TOPIC_ID?: string; DISCORD_BOT_BACKEND?: BotBackend; OUTPUT_BACKEND?: BotType; + DISCORD_WEBHOOK_URL?: string; } export function getEnv(): Env { diff --git a/src/index.ts b/src/index.ts index f11bba4..7d6660d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ import { Client as BotClient, GatewayIntentBits } from "discord.js"; import { Client as SelfBotClient } from "discord.js-selfbot-v13"; +import { Webhook } from "discord-webhook-node"; import { Bot as GrammyBot } from "grammy"; @@ -19,6 +20,16 @@ const grammyClient = ? new GrammyBot(env.TELEGRAM_TOKEN) : null; +const webhookClient = + env.OUTPUT_BACKEND == BotType.DiscordWebhook + ? new Webhook(env.DISCORD_WEBHOOK_URL) + : null; + +if (env.DISCORD_WEBHOOK_URL) { + const match = env.DISCORD_WEBHOOK_URL.match(/webhooks\/(\d+)\//); + if (match) config.mutedUsersIds?.push(match[1]); +} + const senderBot = new SenderBot({ chatsToSend, disableLinkPreview: config.disableLinkPreview, @@ -26,7 +37,8 @@ const senderBot = new SenderBot({ botType: env.OUTPUT_BACKEND, grammyClient, - telegramTopicId: env.TELEGRAM_TOPIC_ID ? Number(env.TELEGRAM_TOPIC_ID) : null + telegramTopicId: env.TELEGRAM_TOPIC_ID ? Number(env.TELEGRAM_TOPIC_ID) : null, + webhookClient }); senderBot.prepare(); diff --git a/src/senderBot.ts b/src/senderBot.ts index 766972d..aa10335 100644 --- a/src/senderBot.ts +++ b/src/senderBot.ts @@ -1,6 +1,7 @@ import { autoRetry } from "@grammyjs/auto-retry"; import { Bot } from "grammy"; import { InputMediaPhoto } from "grammy/types"; +import { Webhook } from "discord-webhook-node"; import { ChannelId } from "./config.js"; @@ -17,6 +18,7 @@ export class SenderBot { botType: BotType = BotType.Telegram; grammyClient?: Bot; + webhookClient?: Webhook; constructor(options: { chatsToSend: ChannelId[]; @@ -26,6 +28,8 @@ export class SenderBot { grammyClient?: Bot; telegramTopicId?: number; + + webhookClient?: Webhook; }) { this.chatsToSend = options.chatsToSend; this.disableLinkPreview = options.disableLinkPreview; @@ -42,54 +46,76 @@ export class SenderBot { this.grammyClient.catch((err) => { console.error(err); }); + + break; + + case BotType.DiscordWebhook: + this.webhookClient = options.webhookClient; + break; } } async prepare() { - const me = await this.grammyClient.api.getMe(); - return console.log(`Logged into Telegram as @${me.username}`); + switch (this.botType) { + case BotType.Telegram: { + const me = await this.grammyClient.api.getMe(); + return console.log(`Logged into Telegram as @${me.username}`); + } + } } - async sendData(messagesToSend: string[], imagesToSend: InputMediaPhoto[]) { - if (messagesToSend.length == 0) return; - - for (const chatId of this.chatsToSend) { - try { - if (imagesToSend.length != 0) - await this.grammyClient.api.sendMediaGroup(chatId, imagesToSend, { + async sendMessage(text: string) { + switch (this.botType) { + case BotType.Telegram: { + const messageChunks: string[] = []; + const MESSAGE_CHUNK = 4096; + + for ( + let i = 0, charsLength = text.length; + i < charsLength; + i += MESSAGE_CHUNK + ) { + messageChunks.push(text.substring(i, i + MESSAGE_CHUNK)); + } + + for (const messageChunk of messageChunks.reverse()) { + await this.grammyClient.api.sendMessage(text, messageChunk, { + link_preview_options: { + is_disabled: this.disableLinkPreview + }, reply_parameters: { message_id: this.telegramTopicId } }); - } catch (err) { - console.error(err); + } + break; } - if (messagesToSend.length == 0 || messagesToSend.join("") == "") return; - - const renderedMessage = messagesToSend.join("\n"); + case BotType.DiscordWebhook: { + this.webhookClient.send(text); + break; + } + } + } - const messageChunks: string[] = []; - const MESSAGE_CHUNK = 4096; + async sendData(messagesToSend: string[], imagesToSend: InputMediaPhoto[]) { + if (messagesToSend.length == 0) return; - for ( - let i = 0, charsLength = renderedMessage.length; - i < charsLength; - i += MESSAGE_CHUNK - ) { - messageChunks.push(renderedMessage.substring(i, i + MESSAGE_CHUNK)); - } + for (const chatId of this.chatsToSend) { + if (this.botType == BotType.Telegram) + try { + if (imagesToSend.length != 0) + await this.grammyClient.api.sendMediaGroup(chatId, imagesToSend, { + reply_parameters: { + message_id: this.telegramTopicId + } + }); + } catch (err) { + console.error(err); + } - for (const messageChunk of messageChunks.reverse()) { - await this.grammyClient.api.sendMessage(chatId, messageChunk, { - link_preview_options: { - is_disabled: this.disableLinkPreview - }, - reply_parameters: { - message_id: this.telegramTopicId - } - }); - } + if (messagesToSend.length == 0 || messagesToSend.join("") == "") return; + this.sendMessage(messagesToSend.join("\n")); } } }