├── .gitignore ├── .vscode └── settings.json ├── COPYING ├── README.md ├── deno.json ├── import_map.json ├── src ├── bots.ts ├── consts.ts ├── ctx.flavour.ts └── handlers │ ├── app.ts │ ├── bc.ts │ ├── cbdata.ts │ ├── cir.ts │ ├── cjr.ts │ ├── cm.ts │ ├── inline.ts │ ├── mr.ts │ ├── msg.ts │ ├── tgbte.ts │ ├── yTestRm.ts │ └── yTestRr.ts └── start.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | 132 | *.lock 133 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.lint": true 4 | } 5 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## JsonBot 2 | 3 | - a simple telegram bot that returns the `telegram-bot-api` json of all the sent messages 4 | 5 | Can be found on **Telegram Production Server** as 6 | 👉 [@useTGxBot](https://useTGxBot.t.me) 7 | 8 | ### OR 9 | 10 | Can be found on **Telegram Test Server** as 11 | 👉 [@ShowJsonBot](https://t.me/ShowJsonBot) 12 | 13 | ### 🤔 why ~~yet another~~ Telegram Bot ? 14 | 15 | did not find a telegram bot, which could return with all the latest [telegram features](https://t.me/BotNews/64). 16 | 17 | ### Why grammY ? 18 | 19 | This project started by using plain 20 | [node-fetch](https://github.com/SpEcHiDe/JsonBot/blob/71dd2de533ae2a3cee777e75006b5f54b0fe274d/src/index.ts) 21 | and all was in a single file. Since, this is a small telegram bot project it was 22 | fine to have it in a single file. Adding **grammY** was an attempt to check if 23 | re-using the existing libraries could reduce the effort in creating this simple 24 | bot. 25 | 26 | ## Contibuting 27 | 28 | PRs are welcome! 29 | 30 | ## Contact 31 | 32 | - [Telegram](https://t.me/ContactMeRoBot). 33 | - [Website](https://www.shrimadhavuk.me). 34 | -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": { 3 | "start": "deno run --allow-net --allow-read --allow-env start.ts", 4 | "serverless": "deno run --allow-net --allow-read --allow-env start.ts" 5 | }, 6 | "importMap": "./import_map.json", 7 | "fmt": { 8 | "options": { 9 | "useTabs": false, 10 | "lineWidth": 80, 11 | "indentWidth": 4, 12 | "singleQuote": false, 13 | "proseWrap": "preserve" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /import_map.json: -------------------------------------------------------------------------------- 1 | { 2 | "imports": { 3 | "std/": "https://deno.land/std@0.224.0/", 4 | "grammy/": "https://ghc.deno.dev/grammyjs/grammY@0f0c320/src/", 5 | "parse_mode": "https://ghc.deno.dev/grammyjs/parse-mode@713f1a6/src/mod.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/bots.ts: -------------------------------------------------------------------------------- 1 | import { Bot, GrammyError, HttpError } from "grammy/mod.ts"; 2 | import { parseMode } from "parse_mode"; 3 | import { MyContext } from "./ctx.flavour.ts"; 4 | 5 | const bots = new Map>(); 6 | 7 | export function getBot(mode: string, token: string) { 8 | let bot = bots.get(token); 9 | if (!bot) { 10 | try { 11 | bot = new Bot(token, { 12 | client: { 13 | // We accept the drawback of webhook replies for typing status. 14 | canUseWebhookReply: (method) => method === "sendChatAction", 15 | // customized build urls 16 | buildUrl: (root: string, token: string, method: string) => { 17 | if (mode === "B") { 18 | return `${root}/beta/bot${token}/${method}`; 19 | } else if (mode === "BT") { 20 | return `${root}/beta/bot${token}/test/${method}`; 21 | } /* https://t.me/c/1014048870/115877 */ 22 | else if (mode === "T") { 23 | return `${root}/bot${token}/test/${method}`; 24 | } else { 25 | return `${root}/bot${token}/${method}`; 26 | } 27 | }, 28 | }, 29 | }); 30 | // Sets default parse_mode for ctx.reply 31 | bot.api.config.use(parseMode("HTML")); 32 | // https://t.me/grammyjs/116198 33 | bot.use(async (ctx, next): Promise => { 34 | // take time before 35 | // const before = Date.now(); // milliseconds 36 | // set token attribute 37 | ctx.botConfig = { 38 | // TODO 39 | botToken: token, 40 | botMode: mode, 41 | }; 42 | // invoke downstream middleware 43 | await next(); // make sure to `await`! 44 | // take time after 45 | // const after = Date.now(); // milliseconds 46 | // log difference 47 | // console.log(`Response time: ${after - before} ms`); 48 | }); 49 | // https://grammy.dev/guide/errors.html#catching-errors 50 | bot.catch((err) => { 51 | const ctx = err.ctx; 52 | console.error("Error while handling update ", ctx.update); 53 | const e = err.error; 54 | if (e instanceof GrammyError) { 55 | console.error("Error in request:", e.description); 56 | } else if (e instanceof HttpError) { 57 | console.error("Could not contact Telegram:", e); 58 | } else { 59 | console.error("Unknown error:", e); 60 | } 61 | }); 62 | // save the token 63 | bots.set(token, bot); 64 | } catch (_e) { 65 | // console.log(_e); 66 | } 67 | } 68 | return bot; 69 | } 70 | -------------------------------------------------------------------------------- /src/consts.ts: -------------------------------------------------------------------------------- 1 | import { Update } from "grammy/types.ts"; 2 | 3 | // Telegram Constants 4 | export const TG_MAX_MESSAGE_LENGTH = 4096; 5 | export const TG_MAX_CAPTION_LENGTH = 1024; 6 | const TG_FILE_ID_BASEURL = "https://slow.transload.workers.dev"; 7 | 8 | // BEGIN: MEME RESOURCES 9 | export const CLONE_MANGO_STICKER_FILE_ID = "CAADBQAD3wEAAjzLfB_2ory8DFKOUwI"; 10 | export const A_STICKER_FILE_ID = "CAADAgADG3UBAAFji0YMqkd3zY0-GJUC"; 11 | export const STICKER_FILE_ID = "CAADAgADpAwAAqoUyEoBbu6msnyOHAI"; 12 | export const V_STICKER_FILE_ID = "BAADAgAD_xMAAkZPsUs6D0o2CG9D6wI"; 13 | export const STICKER_URL = 14 | `${TG_FILE_ID_BASEURL}/${STICKER_FILE_ID}/sticker.jpg`; 15 | export const STICKER_WIDTH = 512; 16 | export const STICKER_HEIGHT = 325; 17 | export const SMAII_STICKER_WIDTH = 100; 18 | export const SMAII_STICKER_HEIGHT = 100; 19 | export const SMAII_STICKER_URL = 20 | `https://wsrv.nl/?url=${STICKER_URL}&w=${SMAII_STICKER_WIDTH}&h=${SMAII_STICKER_HEIGHT}`; 21 | export const AUDIO_URL = 22 | `${TG_FILE_ID_BASEURL}/CQACAgQAAx0ETVHxPQACEppko5nyI2SNuzh0_ac1j1qUZU-bgAACaQwAAhEnsVJKki-yo4Ii7S8E/audio.mp3`; 23 | export const AUDIO_DURATION = 19; 24 | export const GIF_URL = 25 | `${TG_FILE_ID_BASEURL}/CgACAgIAAx0ETVHxPQACEp1ko5nzGJuPS7bxWaJzP0NAvhLngAACvhEAAh3uYEq2tzRs0BHTBy8E/animation.gif`; 26 | export const GIF_WIDTH = 320; 27 | export const GIF_HEIGHT = 134; 28 | export const GIF_DURATION = 3; 29 | export const GIF_2_URL = 30 | `${TG_FILE_ID_BASEURL}/CgACAgIAAx0ETVHxPQACEp1ko5nzGJuPS7bxWaJzP0NAvhLngAACvhEAAh3uYEq2tzRs0BHTBy8E/mpeg4.mp4`; 31 | export const GIF_2_WIDTH = 848; 32 | export const GIF_2_HEIGHT = 384; 33 | export const GIF_2_DURATION = 1; 34 | export const VIDEO_URL = 35 | `${TG_FILE_ID_BASEURL}/BAACAgEAAx0ETVHxPQACEptko5nyp8wNcA3weMwSNU49QXDjJQACYgQAAlxzsUaAS1fnZpA2nC8E/video.mp4`; 36 | export const VOICE_URL = 37 | `${TG_FILE_ID_BASEURL}/AwACAgQAAx0ETVHxPQACEplko5nylwK28UAWcx5KRiucCtIvlAACaAwAAhEnsVI0A3WL3R36-i8E/voice.ogg`; 38 | export const YT_VIDEO_URL = "https://youtu.be/JmvCpR45LKA"; 39 | export const YT_VIDEO_DURATION = 103; 40 | export const MR_INVALID_SYNTAX = "SYNTAX_IN_VALID"; 41 | // END: MEME RESOURCES 42 | 43 | export const TG_ALLOWED_UPDATES: Array = [ 44 | "message", 45 | "edited_message", 46 | "channel_post", 47 | "edited_channel_post", 48 | "business_connection", 49 | "business_message", 50 | "edited_business_message", 51 | "deleted_business_messages", 52 | "message_reaction", 53 | "message_reaction_count", 54 | "inline_query", 55 | "chosen_inline_result", 56 | "callback_query", 57 | "shipping_query", 58 | "pre_checkout_query", 59 | "poll", 60 | "poll_answer", 61 | "my_chat_member", 62 | "chat_member", 63 | "chat_join_request", 64 | "chat_boost", 65 | "removed_chat_boost", 66 | ]; 67 | export const TG_ENV_S = Deno.env.toObject(); 68 | const KW_TG_ERR = (msg: string): string => { 69 | return msg 70 | // 128207 71 | .replaceAll("&", "&") 72 | .replaceAll("<", "<"); 73 | // not sure why this is not required 74 | // .replaceAll(">", "&rt;"); 75 | }; 76 | export const TG_MES_PR = (upd: Update) => { 77 | // https://stackoverflow.com/a/3515761/4723940 78 | return KW_TG_ERR( 79 | JSON.stringify( 80 | upd, 81 | null, 82 | 2, 83 | ), 84 | ); 85 | // <= https://t.me/c/1220993104/1/1353555 86 | }; 87 | export const TG_PR_MES = (msg: string) => { 88 | // 1094034 89 | return `
${msg}
`; 90 | }; 91 | export const TG_ERR_MES = (error: any) => { 92 | try { 93 | delete error.parameters; 94 | delete error.method; 95 | delete error.payload; 96 | delete error.name; 97 | } catch (_) { 98 | /* https://csswizardry.com/2013/04/shame-css/ */ 99 | } 100 | return JSON.stringify( 101 | error, 102 | null, 103 | 2, 104 | ); 105 | }; 106 | -------------------------------------------------------------------------------- /src/ctx.flavour.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "grammy/mod.ts"; 2 | 3 | export interface BotConfig { 4 | botToken: string; 5 | botMode: string; 6 | } 7 | 8 | export type MyContext = Context & { 9 | botConfig: BotConfig; 10 | }; 11 | -------------------------------------------------------------------------------- /src/handlers/app.ts: -------------------------------------------------------------------------------- 1 | import { Composer } from "grammy/mod.ts"; 2 | 3 | // import tgbte from "./tgbte.ts"; 4 | import rm from "./yTestRm.ts"; 5 | import rr from "./yTestRr.ts"; 6 | import msg from "./msg.ts"; 7 | import bc from "./bc.ts"; 8 | import mr from "./mr.ts"; 9 | import inline from "./inline.ts"; 10 | import cir from "./cir.ts"; 11 | import cbdata from "./cbdata.ts"; 12 | import cm from "./cm.ts"; 13 | import cjr from "./cjr.ts"; 14 | import { MyContext } from "../bots.ts"; 15 | 16 | export const composer = new Composer(); 17 | 18 | // composer.use(async (ctx, next) => { 19 | // console.log(ctx.update); 20 | // await next(); 21 | // }) 22 | 23 | // composer.use(tgbte); 24 | composer.use(rm); 25 | composer.use(rr); 26 | composer.use(msg); 27 | composer.use(bc); 28 | composer.use(mr); 29 | composer.use(inline); 30 | composer.use(cir); 31 | composer.use(cbdata); 32 | // TODO: 4 33 | composer.use(cm); 34 | composer.use(cjr); 35 | // TODO: 2 36 | 37 | composer.use( 38 | (ctx) => console.log("UnHandled update", JSON.stringify(ctx)) 39 | ); 40 | -------------------------------------------------------------------------------- /src/handlers/bc.ts: -------------------------------------------------------------------------------- 1 | import { Composer } from "grammy/mod.ts"; 2 | import { TG_MES_PR, TG_PR_MES } from "./../consts.ts"; 3 | 4 | export const composer = new Composer(); 5 | 6 | export default composer; 7 | 8 | composer.on("business_connection", (ctx) => { 9 | return ctx.api.sendMessage( 10 | ctx.businessConnection.user_chat_id, 11 | TG_PR_MES( 12 | TG_MES_PR( 13 | ctx.update, 14 | ), 15 | ) 16 | ); 17 | }); 18 | -------------------------------------------------------------------------------- /src/handlers/cbdata.ts: -------------------------------------------------------------------------------- 1 | import { Composer } from "grammy/mod.ts"; 2 | import { TG_MES_PR, TG_PR_MES } from "./../consts.ts"; 3 | 4 | export const composer = new Composer(); 5 | 6 | export default composer; 7 | 8 | composer.on("callback_query", async (ctx) => { 9 | // NOTE: You should always answer, 10 | // but we want different conditionals to 11 | // be able to answer to differently 12 | // (and we can only answer once) 13 | // so we don't always answer here. 14 | await ctx.answerCallbackQuery(); 15 | return ctx.api.sendMessage( 16 | ctx.callbackQuery.from.id, 17 | TG_PR_MES( 18 | TG_MES_PR( 19 | ctx.update, 20 | ), 21 | ), 22 | ); 23 | }); 24 | -------------------------------------------------------------------------------- /src/handlers/cir.ts: -------------------------------------------------------------------------------- 1 | import { Composer } from "grammy/mod.ts"; 2 | import { TG_MES_PR, TG_PR_MES } from "./../consts.ts"; 3 | 4 | export const composer = new Composer(); 5 | 6 | export default composer; 7 | 8 | composer.on("chosen_inline_result", (ctx) => { 9 | return ctx.api.sendMessage( 10 | ctx.chosenInlineResult.from.id, 11 | TG_PR_MES( 12 | TG_MES_PR( 13 | ctx.update, 14 | ), 15 | ), 16 | ); 17 | }); 18 | -------------------------------------------------------------------------------- /src/handlers/cjr.ts: -------------------------------------------------------------------------------- 1 | import { Composer } from "grammy/mod.ts"; 2 | import { TG_MES_PR, TG_PR_MES } from "./../consts.ts"; 3 | 4 | export const composer = new Composer(); 5 | 6 | export default composer; 7 | 8 | composer.on("chat_join_request", (ctx) => { 9 | return ctx.api.sendMessage( 10 | ctx.chatJoinRequest.user_chat_id, 11 | TG_PR_MES( 12 | TG_MES_PR( 13 | ctx.update, 14 | ), 15 | ), 16 | ); 17 | }); 18 | -------------------------------------------------------------------------------- /src/handlers/cm.ts: -------------------------------------------------------------------------------- 1 | import { Composer } from "grammy/mod.ts"; 2 | import { TG_MAX_MESSAGE_LENGTH, TG_MES_PR, TG_PR_MES } from "./../consts.ts"; 3 | 4 | export const composer = new Composer(); 5 | 6 | export default composer; 7 | 8 | composer.on( 9 | [ 10 | "my_chat_member", 11 | "chat_member", 12 | "chat_boost", 13 | "removed_chat_boost", 14 | ], 15 | async (ctx) => { 16 | try { 17 | const targetChat = ctx.chat?.id || undefined; 18 | if (!targetChat) { 19 | return; 20 | } 21 | let msgToSend = TG_MES_PR(ctx.update); 22 | if (msgToSend.length > TG_MAX_MESSAGE_LENGTH) { 23 | while (msgToSend.length > TG_MAX_MESSAGE_LENGTH) { 24 | const io: string = msgToSend.substring( 25 | 0, 26 | TG_MAX_MESSAGE_LENGTH, 27 | ); 28 | await ctx.api.sendMessage( 29 | targetChat, 30 | TG_PR_MES(io), 31 | ); 32 | msgToSend = msgToSend.substring(TG_MAX_MESSAGE_LENGTH); 33 | } 34 | } 35 | return await ctx.api.sendMessage( 36 | targetChat, 37 | TG_PR_MES(msgToSend), 38 | ); 39 | } catch (_) { 40 | // TODO: figure out a better logik 41 | } 42 | }, 43 | ); 44 | -------------------------------------------------------------------------------- /src/handlers/inline.ts: -------------------------------------------------------------------------------- 1 | import { Composer } from "grammy/mod.ts"; 2 | import { 3 | A_STICKER_FILE_ID, 4 | AUDIO_DURATION, 5 | AUDIO_URL, 6 | GIF_2_DURATION, 7 | GIF_2_HEIGHT, 8 | GIF_2_URL, 9 | GIF_2_WIDTH, 10 | GIF_DURATION, 11 | GIF_HEIGHT, 12 | GIF_URL, 13 | GIF_WIDTH, 14 | SMAII_STICKER_HEIGHT, 15 | SMAII_STICKER_URL, 16 | SMAII_STICKER_WIDTH, 17 | STICKER_FILE_ID, 18 | STICKER_HEIGHT, 19 | STICKER_URL, 20 | STICKER_WIDTH, 21 | TG_MAX_CAPTION_LENGTH, 22 | TG_MAX_MESSAGE_LENGTH, 23 | TG_MES_PR, 24 | TG_PR_MES, 25 | V_STICKER_FILE_ID, 26 | VIDEO_URL, 27 | VOICE_URL, 28 | YT_VIDEO_DURATION, 29 | YT_VIDEO_URL, 30 | } from "./../consts.ts"; 31 | 32 | const composer = new Composer(); 33 | 34 | export default composer; 35 | 36 | composer.on("inline_query", (ctx) => { 37 | const msgToSend = TG_MES_PR(ctx.update); 38 | // 39 | const reply_markup = { 40 | inline_keyboard: [ 41 | [ 42 | { 43 | text: "(string) Label text on the button", 44 | url: STICKER_URL, 45 | }, 46 | ], 47 | [ 48 | { 49 | text: "(string) Label text on the button", 50 | switch_inline_query: 51 | "(string) Optional. If set, pressing the button will prompt the user to select one of their chats, open that chat and insert the bot's username and the specified inline query in the input field. May be empty, in which case just the bot's username will be inserted.", 52 | }, 53 | { 54 | text: "(string) Label text on the button", 55 | switch_inline_query_current_chat: 56 | "(string) Optional. If set, pressing the button will insert the bot's username and the specified inline query in the current chat's input field. May be empty, in which case only the bot's username will be inserted.", 57 | }, 58 | ], 59 | [ 60 | { 61 | text: "(string) Label text on the button", 62 | callback_data: 63 | "(Optional). Data associated with the callback button.", 64 | }, 65 | ], 66 | ], 67 | }; 68 | // 69 | const input_text_message_content = { 70 | message_text: TG_PR_MES( 71 | msgToSend.substring(0, TG_MAX_CAPTION_LENGTH), 72 | ), 73 | }; 74 | const inputTextMessageContent = { 75 | message_text: `${ 76 | TG_PR_MES( 77 | msgToSend.substring(0, TG_MAX_MESSAGE_LENGTH), 78 | ) 79 | }`, 80 | }; 81 | const msgCaption = TG_PR_MES( 82 | msgToSend.substring(0, TG_MAX_MESSAGE_LENGTH), 83 | ); 84 | // 85 | /** 86 | * https://grammy.dev/guide/inline-queries.html 87 | */ 88 | return ctx.answerInlineQuery( 89 | [ 90 | { 91 | type: "article", 92 | id: "1", 93 | title: "(string) Title of the result", 94 | input_message_content: input_text_message_content, 95 | reply_markup: reply_markup, 96 | url: STICKER_URL, 97 | hide_url: true, 98 | description: 99 | "(string) Optional. Short description of the result", 100 | thumbnail_url: SMAII_STICKER_URL, 101 | thumbnail_width: SMAII_STICKER_WIDTH, 102 | thumbnail_height: SMAII_STICKER_HEIGHT, 103 | }, 104 | { 105 | type: "article", 106 | id: "2", 107 | title: "(string) Title of the result", 108 | input_message_content: { 109 | phone_number: "+424314159", 110 | first_name: "(string) Contact's first name", 111 | last_name: "(string) Optional. Contact's last name", 112 | vcard: "", 113 | }, 114 | reply_markup: reply_markup, 115 | url: STICKER_URL, 116 | hide_url: true, 117 | description: 118 | "(string) Optional. Short description of the result", 119 | thumbnail_url: SMAII_STICKER_URL, 120 | thumbnail_width: SMAII_STICKER_WIDTH, 121 | thumbnail_height: SMAII_STICKER_HEIGHT, 122 | }, 123 | { 124 | type: "photo", 125 | id: "3", 126 | photo_url: STICKER_URL, 127 | thumbnail_url: SMAII_STICKER_URL, 128 | photo_width: STICKER_WIDTH, 129 | photo_height: STICKER_HEIGHT, 130 | title: "(string) Title of the result", 131 | description: 132 | "(string) Optional. Short description of the result", 133 | caption: msgCaption, 134 | reply_markup: reply_markup, 135 | // input_message_content: 136 | }, 137 | { 138 | type: "gif", 139 | id: "4", 140 | gif_url: GIF_URL, 141 | gif_width: GIF_WIDTH, 142 | gif_height: GIF_HEIGHT, 143 | gif_duration: GIF_DURATION, 144 | thumbnail_url: SMAII_STICKER_URL, 145 | thumbnail_mime_type: "image/jpeg", 146 | title: "(string) Title of the result", 147 | caption: msgCaption, 148 | reply_markup: reply_markup, 149 | // input_message_content: 150 | }, 151 | { 152 | type: "mpeg4_gif", 153 | id: "5", 154 | mpeg4_url: GIF_2_URL, 155 | mpeg4_width: GIF_2_WIDTH, 156 | mpeg4_height: GIF_2_HEIGHT, 157 | mpeg4_duration: GIF_2_DURATION, 158 | thumbnail_url: SMAII_STICKER_URL, 159 | thumbnail_mime_type: "image/jpeg", 160 | title: "(string) Title of the result", 161 | caption: msgCaption, 162 | reply_markup: reply_markup, 163 | // input_message_content: 164 | }, 165 | { 166 | type: "video", 167 | id: "6", 168 | video_url: VIDEO_URL, 169 | mime_type: "video/mp4", 170 | thumbnail_url: SMAII_STICKER_URL, 171 | title: "(string) Title of the result", 172 | caption: msgCaption, 173 | video_width: 0, 174 | video_height: 0, 175 | video_duration: YT_VIDEO_DURATION, 176 | description: 177 | "(string) Optional. Short description of the result", 178 | reply_markup: reply_markup, 179 | // input_message_content: 180 | }, 181 | { 182 | type: "video", 183 | id: "7", 184 | video_url: YT_VIDEO_URL, 185 | mime_type: "text/html", 186 | thumbnail_url: SMAII_STICKER_URL, 187 | title: "(string) Title of the result", 188 | caption: msgCaption, 189 | video_width: 0, 190 | video_height: 0, 191 | video_duration: YT_VIDEO_DURATION, 192 | description: 193 | "(string) Optional. Short description of the result", 194 | reply_markup: reply_markup, 195 | input_message_content: inputTextMessageContent, 196 | }, 197 | { 198 | type: "audio", 199 | id: "8", 200 | audio_url: AUDIO_URL, 201 | title: "(string) Title of the result", 202 | caption: msgCaption, 203 | performer: "(string) Optional. Performer", 204 | audio_duration: AUDIO_DURATION, 205 | reply_markup: reply_markup, 206 | // input_message_content: 207 | }, 208 | { 209 | type: "voice", 210 | id: "9", 211 | voice_url: VOICE_URL, 212 | title: "(string) Title of the result", 213 | caption: msgCaption, 214 | voice_duration: AUDIO_DURATION, 215 | reply_markup: reply_markup, 216 | // input_message_content: 217 | }, 218 | { 219 | type: "sticker", 220 | id: "13TG93AT43", 221 | sticker_file_id: A_STICKER_FILE_ID, 222 | }, 223 | { 224 | type: "sticker", 225 | id: "ShitDevsSay", 226 | sticker_file_id: STICKER_FILE_ID, 227 | }, 228 | { 229 | type: "sticker", 230 | id: "139343", 231 | sticker_file_id: V_STICKER_FILE_ID, 232 | }, 233 | ], 234 | { 235 | cache_time: 300, 236 | is_personal: true, 237 | next_offset: "", 238 | button: { 239 | text: 240 | "(string) Optional. If passed, clients will display a button with specified text that switches the user to a private chat with the bot and sends the bot a start message with the parameter switch_pm_parameter", 241 | start_parameter: "inline-deep-link", 242 | }, 243 | }, 244 | ); 245 | }); 246 | -------------------------------------------------------------------------------- /src/handlers/mr.ts: -------------------------------------------------------------------------------- 1 | import { Composer } from "grammy/mod.ts"; 2 | import { TG_MAX_MESSAGE_LENGTH, TG_MES_PR, TG_PR_MES } from "./../consts.ts"; 3 | 4 | export const composer = new Composer(); 5 | 6 | export default composer; 7 | 8 | composer.on( 9 | [ 10 | "message_reaction", 11 | "message_reaction_count", 12 | ], 13 | async (ctx) => { 14 | try { 15 | const targetChat = ctx.messageReaction?.chat.id ?? 16 | ctx.messageReactionCount?.chat.id ?? undefined; 17 | if (targetChat === undefined) { 18 | return; 19 | } 20 | let msgToSend = TG_MES_PR(ctx.update); 21 | if (msgToSend.length > TG_MAX_MESSAGE_LENGTH) { 22 | while (msgToSend.length > TG_MAX_MESSAGE_LENGTH) { 23 | const io: string = msgToSend.substring( 24 | 0, 25 | TG_MAX_MESSAGE_LENGTH, 26 | ); 27 | await ctx.api.sendMessage( 28 | targetChat, 29 | TG_PR_MES(io), 30 | ); 31 | msgToSend = msgToSend.substring(TG_MAX_MESSAGE_LENGTH); 32 | } 33 | } 34 | return await ctx.api.sendMessage( 35 | targetChat, 36 | TG_PR_MES(msgToSend), 37 | ); 38 | } catch (_) { 39 | // TODO: figure out a better logik 40 | } 41 | }, 42 | ); 43 | -------------------------------------------------------------------------------- /src/handlers/msg.ts: -------------------------------------------------------------------------------- 1 | import { Composer, Context } from "grammy/mod.ts"; 2 | import { TG_MAX_MESSAGE_LENGTH, TG_MES_PR, TG_PR_MES } from "./../consts.ts"; 3 | 4 | export const composer = new Composer(); 5 | 6 | export default composer; 7 | 8 | export async function msgUpdate(ctx: Context) { 9 | let msgToSend = TG_MES_PR(ctx.update); 10 | if (msgToSend.length > TG_MAX_MESSAGE_LENGTH) { 11 | while (msgToSend.length > TG_MAX_MESSAGE_LENGTH) { 12 | const io: string = msgToSend.substring( 13 | 0, 14 | TG_MAX_MESSAGE_LENGTH, 15 | ); 16 | await ctx.reply( 17 | TG_PR_MES(io), 18 | ); 19 | msgToSend = msgToSend.substring(TG_MAX_MESSAGE_LENGTH); 20 | } 21 | } 22 | return await ctx.reply( 23 | TG_PR_MES(msgToSend), 24 | ); 25 | } 26 | 27 | composer.on( 28 | // https://t.me/c/1493653006/107322 29 | [ 30 | "message", 31 | "edited_message", 32 | "channel_post", 33 | "edited_channel_post", 34 | "business_message", 35 | "edited_business_message", 36 | "deleted_business_messages", 37 | ], 38 | msgUpdate, 39 | ); 40 | -------------------------------------------------------------------------------- /src/handlers/tgbte.ts: -------------------------------------------------------------------------------- 1 | import { Composer } from "grammy/mod.ts"; 2 | import { MessageEntity } from "grammy/types.ts"; 3 | import { getBot } from "../bots.ts"; 4 | import { 5 | CLONE_MANGO_STICKER_FILE_ID, 6 | TG_ALLOWED_UPDATES, 7 | TG_ENV_S, 8 | } from "../consts.ts"; 9 | import { MyContext } from "../ctx.flavour.ts"; 10 | 11 | const composer = new Composer(); 12 | 13 | export default composer; 14 | 15 | composer.on("msg:text").filter( 16 | (ctx) => ( 17 | ctx.msg.forward_origin !== undefined && 18 | ctx.msg.forward_origin.type == "user" && 19 | ctx.msg.forward_origin.sender_user.username?.toLowerCase() === "botfather" 20 | ), 21 | async (ctx, next) => { 22 | const entities = ctx.message?.entities || []; 23 | const msgText = ctx.message?.text || ""; 24 | // extract bot token 25 | const bot_token = extractBotToken(msgText, entities); 26 | if (bot_token !== undefined) { 27 | // Create an instance of the `Bot` class and pass your authentication token to it. 28 | const bot = getBot(ctx.botConfig.botMode, bot_token); 29 | if (bot) { 30 | try { 31 | // Make sure it is `https` not `http`! 32 | await bot.api.setWebhook( 33 | `${TG_ENV_S.URL}/${bot_token}`, 34 | { 35 | drop_pending_updates: true, 36 | allowed_updates: TG_ALLOWED_UPDATES, 37 | }, 38 | ); 39 | } catch (_e) { 40 | // console.log(_e); 41 | } 42 | } 43 | } 44 | // finally reply done to the user 45 | await ctx.replyWithSticker( 46 | CLONE_MANGO_STICKER_FILE_ID, 47 | { 48 | reply_to_message_id: ctx.message?.message_id, 49 | }, 50 | ); 51 | // https://t.me/c/1493653006/116753 52 | await next(); 53 | }, 54 | ); 55 | 56 | function extractBotToken(msgText: string, entities: Array) { 57 | // https://github.com/wjclub/telegram-bot-tokenextract/pull/1 58 | for (const entity_ in entities) { 59 | const entity = entities[Number(entity_)]; 60 | if (entity.type == "code") { 61 | return msgText?.substring( 62 | entity.offset, 63 | entity.offset + entity.length, 64 | ); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/handlers/yTestRm.ts: -------------------------------------------------------------------------------- 1 | import { Composer } from "grammy/mod.ts"; 2 | import { Message } from "grammy/types.ts"; 3 | import { 4 | MR_INVALID_SYNTAX, 5 | STICKER_FILE_ID, 6 | TG_ERR_MES, 7 | TG_PR_MES, 8 | } from "../consts.ts"; 9 | import { MyContext } from "../ctx.flavour.ts"; 10 | // import { msgUpdate } from "./msg.ts"; 11 | 12 | export const composer = new Composer(); 13 | 14 | export default composer; 15 | 16 | composer.on( 17 | [ 18 | "message:text", 19 | "channel_post:text", 20 | "business_message:text", 21 | ], 22 | async (ctx, next) => { 23 | if ( 24 | ctx?.msg?.text !== undefined && 25 | ctx.msg.text.startsWith("/rm ") 26 | ) { 27 | const io = ctx.msg.text.substring(4).trim(); 28 | let oi = {}; 29 | // 30 | try { 31 | oi = JSON.parse(io); 32 | } catch (error) { 33 | return await ctx.reply( 34 | TG_PR_MES( 35 | TG_ERR_MES(error.toString()), 36 | ), 37 | ); 38 | } 39 | try { 40 | let ishow = undefined; 41 | if (ctx.botConfig.botMode.indexOf("T") === -1) { 42 | ishow = await ctx.replyWithSticker( 43 | STICKER_FILE_ID, 44 | { 45 | // @ts-ignore 46 | reply_markup: oi, 47 | }, 48 | ); 49 | } 50 | else { 51 | ishow = await ctx.reply( 52 | `${STICKER_FILE_ID}`, 53 | { 54 | // @ts-ignore 55 | reply_markup: oi, 56 | parse_mode: "HTML" 57 | }, 58 | ); 59 | } 60 | 61 | // https://t.me/c/1493653006/116801 62 | // return await msgUpdate( 63 | // { 64 | // update: { 65 | // message: ishow, 66 | // } 67 | // } 68 | // ); 69 | } catch (error) { 70 | await ctx.reply( 71 | TG_PR_MES( 72 | TG_ERR_MES(error), 73 | ), 74 | ); 75 | } 76 | } 77 | // https://t.me/c/1493653006/116753 78 | await next(); 79 | }, 80 | ); 81 | -------------------------------------------------------------------------------- /src/handlers/yTestRr.ts: -------------------------------------------------------------------------------- 1 | import { Composer } from "grammy/mod.ts"; 2 | import { ReactionTypeCustomEmoji, ReactionTypeEmoji } from "grammy/types.ts"; 3 | import { MR_INVALID_SYNTAX, TG_ERR_MES, TG_PR_MES } from "../consts.ts"; 4 | 5 | export const composer = new Composer(); 6 | 7 | export default composer; 8 | 9 | composer.on( 10 | [ 11 | "message:text", 12 | "channel_post:text", 13 | "business_message:text", 14 | ], 15 | async (ctx, next) => { 16 | if ( 17 | ctx?.msg?.text !== undefined && 18 | ctx.msg.text.startsWith("/rr ") 19 | ) { 20 | const io = ctx.msg.text.substring(4).trim(); 21 | let oi: ReactionTypeEmoji | ReactionTypeCustomEmoji | undefined = 22 | undefined; 23 | // try { 24 | // parseInt(io); 25 | // oi = { 26 | // type: "custom_emoji", 27 | // custom_emoji_id: io, 28 | // }; 29 | // } catch (_) { 30 | oi = { 31 | type: "emoji", 32 | // @ts-ignore 33 | emoji: io, 34 | }; 35 | // } 36 | if (oi !== undefined) { 37 | try { 38 | await ctx.api.setMessageReaction( 39 | ctx.msg.chat.id, 40 | ctx.msg.message_id, 41 | [oi], 42 | { 43 | is_big: true, 44 | }, 45 | ); 46 | } catch (error) { 47 | await ctx.reply( 48 | TG_PR_MES( 49 | TG_ERR_MES(error), 50 | ), 51 | ); 52 | } 53 | } else { 54 | await ctx.reply( 55 | TG_PR_MES( 56 | TG_ERR_MES({ 57 | "error": true, 58 | "message": "invalid ReactionType", 59 | }), 60 | ), 61 | ); 62 | } 63 | } 64 | // https://t.me/c/1493653006/116753 65 | await next(); 66 | }, 67 | ); 68 | -------------------------------------------------------------------------------- /start.ts: -------------------------------------------------------------------------------- 1 | import { webhookCallback } from "grammy/mod.ts"; 2 | import { composer } from "./src/handlers/app.ts"; 3 | import { getBot } from "./src/bots.ts"; 4 | import { TG_ALLOWED_UPDATES, TG_ENV_S } from "./src/consts.ts"; 5 | 6 | if (TG_ENV_S.LP) { 7 | // Create an instance of the `Bot` class and pass your authentication token to it. 8 | const bot = getBot(TG_ENV_S.BOT_MODE, TG_ENV_S.TG_BOT_TOKEN); 9 | if (bot) { 10 | // You can now register listeners on your bot object `bot`. 11 | // grammY will call the listeners when users send messages to your bot. 12 | // Handle the /start command. 13 | bot.use(composer); 14 | // start bot 15 | bot.start({ 16 | drop_pending_updates: true, 17 | allowed_updates: TG_ALLOWED_UPDATES, 18 | }); 19 | // stop bot 20 | Deno.addSignalListener("SIGINT", () => bot.stop()); 21 | Deno.addSignalListener("SIGTERM", () => bot.stop()); 22 | } 23 | } else { 24 | Deno.serve(async (req) => { 25 | // 2016e33165779f658433ef106e12e70d4e5bc2da 26 | console.log("received ", req); 27 | if (req.method === "POST") { 28 | const { pathname } = new URL(req.url); 29 | let botToken = pathname.substring(1); 30 | let botMode = "P"; 31 | if (botToken.indexOf("/") > -1) { 32 | const botOtherTokens = botToken.split("/"); 33 | botToken = botOtherTokens[1]; 34 | botMode = botOtherTokens[0]; 35 | } 36 | try { 37 | // Create an instance of the `Bot` class and pass your authentication token to it. 38 | const bot = getBot(botMode, botToken); 39 | if (bot) { 40 | // You can now register listeners on your bot object `bot`. 41 | // grammY will call the listeners when users send messages to your bot. 42 | // Handle the /start command. 43 | bot.use(composer); 44 | // finally, register the webhook 45 | // https://t.me/c/1493653006/49880 46 | return await webhookCallback(bot, "std/http")(req); 47 | // https://t.me/c/1493653006/106981 48 | } 49 | } catch (err) { 50 | console.error(err); 51 | return new Response( 52 | JSON.stringify({}), 53 | { 54 | status: 200, 55 | }, 56 | ); 57 | } 58 | } 59 | return Response.redirect("https://t.me/SpEcHlDe/1348"); 60 | }); 61 | } 62 | --------------------------------------------------------------------------------