├── .dockerignore ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ ├── lint.yml │ ├── dockerbuild.yml │ └── codeql-analysis.yml ├── .eslintrc.yml ├── Dockerfile ├── .vscode └── launch.json ├── package.json ├── README.md └── index.js /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | .env* -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | browser: false 3 | es2021: true 4 | node: true 5 | extends: "eslint:recommended" 6 | parserOptions: 7 | ecmaVersion: 12 8 | sourceType: module 9 | rules: {} 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:15-alpine AS build 2 | 3 | COPY . /app 4 | 5 | WORKDIR /app 6 | 7 | RUN npm i --only=prod 8 | 9 | FROM node:15-alpine 10 | 11 | COPY --from=build /app /app 12 | 13 | USER node 14 | 15 | CMD ["node", "/app/index.js"] 16 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": ["/**"], 12 | "program": "${workspaceFolder}/index.js", 13 | "envFile": "${workspaceFolder}/.env" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: push 4 | 5 | jobs: 6 | run-linters: 7 | name: Run linters 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Check out Git repository 12 | uses: actions/checkout@v2 13 | 14 | - name: Set up Node.js 15 | uses: actions/setup-node@v1 16 | with: 17 | node-version: 12 18 | 19 | - name: Install linting dependencies 20 | run: npm install eslint prettier 21 | 22 | - name: Install package dependencies 23 | run: npm install 24 | 25 | - name: Run linters 26 | uses: wearerequired/lint-action@v1 27 | with: 28 | github_token: ${{ secrets.github_token }} 29 | eslint: true 30 | prettier: true 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "telegram-bot-amazon", 3 | "version": "1.0.0", 4 | "description": "Telegram bot to replace Amazon affiliate tags in links", 5 | "main": "index.js", 6 | "dependencies": { 7 | "node-fetch": ">=2.6.1 <=2.9.9", 8 | "node-telegram-bot-api": "^0.58.0" 9 | }, 10 | "devDependencies": { 11 | "eslint": "^8.19.0", 12 | "prettier": "^2.2.0" 13 | }, 14 | "scripts": { 15 | "test": "echo \"Error: no test specified\" && exit 1", 16 | "start": "node index.js" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/LucaTNT/telegram-bot-amazon.git" 21 | }, 22 | "keywords": [ 23 | "telegram", 24 | "bot", 25 | "amazon" 26 | ], 27 | "author": "LucaTNT", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/LucaTNT/telegram-bot-amazon/issues" 31 | }, 32 | "homepage": "https://github.com/LucaTNT/telegram-bot-amazon#readme" 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/dockerbuild.yml: -------------------------------------------------------------------------------- 1 | name: Docker Build/Publish Image 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | tags: 8 | - '[0-9]+\.[0-9]+' 9 | 10 | jobs: 11 | docker: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | - name: Docker meta 17 | id: docker_meta 18 | uses: crazy-max/ghaction-docker-meta@v1 19 | with: 20 | images: lucatnt/telegram-bot-amazon 21 | tag-match: '[0-9]+\.[0-9]+' 22 | - name: Set up QEMU 23 | uses: docker/setup-qemu-action@v1 24 | - name: Set up Docker Buildx 25 | id: buildx 26 | uses: docker/setup-buildx-action@v1 27 | - name: Available platforms 28 | run: echo ${{ steps.buildx.outputs.platforms }} 29 | - name: Login to DockerHub 30 | if: github.ref != 'refs/heads/main' # Only on tag, not regular push 31 | uses: docker/login-action@v1 32 | with: 33 | username: ${{ secrets.DOCKERHUB_USERNAME }} 34 | password: ${{ secrets.DOCKERHUB_TOKEN }} 35 | - name: Build and push 36 | id: docker_build 37 | uses: docker/build-push-action@v2 38 | with: 39 | context: . 40 | file: ./Dockerfile 41 | platforms: linux/amd64,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le,linux/s390x 42 | push: ${{ github.ref != 'refs/heads/main' }} # Only on tag, not regular push 43 | tags: ${{ steps.docker_meta.outputs.tags }} 44 | labels: ${{ steps.docker_meta.outputs.labels }} 45 | - name: Update repo description 46 | uses: peter-evans/dockerhub-description@v2 47 | with: 48 | username: ${{ secrets.DOCKERHUB_USERNAME }} 49 | password: ${{ secrets.DOCKERHUB_PASSWORD }} 50 | repository: lucatnt/telegram-bot-amazon 51 | if: github.ref != 'refs/heads/main' # Only on tag, not regular push 52 | 53 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # ******** NOTE ******** 12 | 13 | name: "CodeQL" 14 | 15 | on: 16 | push: 17 | branches: [main] 18 | pull_request: 19 | # The branches below must be a subset of the branches above 20 | branches: [main] 21 | schedule: 22 | - cron: "27 20 * * 0" 23 | 24 | jobs: 25 | analyze: 26 | name: Analyze 27 | runs-on: ubuntu-latest 28 | 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | language: ["javascript"] 33 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 34 | # Learn more: 35 | # https://docs.github.com/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 36 | 37 | steps: 38 | - name: Checkout repository 39 | uses: actions/checkout@v2 40 | 41 | # Initializes the CodeQL tools for scanning. 42 | - name: Initialize CodeQL 43 | uses: github/codeql-action/init@v1 44 | with: 45 | languages: ${{ matrix.language }} 46 | # If you wish to specify custom queries, you can do so here or in a config file. 47 | # By default, queries listed here will override any specified in a config file. 48 | # Prefix the list here with "+" to use these queries and those in the config file. 49 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 50 | 51 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 52 | # If this step fails, then you should remove it and run the build manually (see below) 53 | - name: Autobuild 54 | uses: github/codeql-action/autobuild@v1 55 | 56 | # ℹ️ Command-line programs to run using the OS shell. 57 | # 📚 https://git.io/JvXDl 58 | 59 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 60 | # and modify them (or add more) to build your code if your project 61 | # uses a compiled language 62 | 63 | #- run: | 64 | # make bootstrap 65 | # make release 66 | 67 | - name: Perform CodeQL Analysis 68 | uses: github/codeql-action/analyze@v1 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![](https://img.shields.io/github/issues/lucatnt/telegram-bot-amazon.svg)](https://github.com/LucaTNT/telegram-bot-amazon/issues) [![](https://img.shields.io/github/issues-pr-raw/lucatnt/telegram-bot-amazon.svg)](https://github.com/LucaTNT/telegram-bot-amazon/pulls) [![](https://img.shields.io/docker/pulls/lucatnt/telegram-bot-amazon.svg)](https://hub.docker.com/r/lucatnt/telegram-bot-amazon) [![](https://img.shields.io/docker/cloud/build/lucatnt/telegram-bot-amazon.svg)](https://hub.docker.com/r/lucatnt/telegram-bot-amazon) [![](https://img.shields.io/docker/image-size/lucatnt/telegram-bot-amazon/latest.svg)](https://hub.docker.com/r/lucatnt/telegram-bot-amazon) 2 | 3 | This is a Telegram bot that, if made admin of a group, will delete any message 4 | containing an Amazon link and re-post it tagged with the specified affiliate tag. 5 | 6 | It can be either messaged directly, or added **as an administrator** to a group or supergroup. 7 | 8 | If messaged directly, it replies with the affiliate link, while in a group it will delete any message containing an Amazon link and replace it with a new message, with a format that is customizable through the `GROUP_REPLACEMENT_MESSAGE` environment variables. 9 | 10 | ## Configuration 11 | 12 | It requires two parameters through environment variables: 13 | 14 | - `TELEGRAM_BOT_TOKEN` (required) is the token obtained from [@Botfather](https://t.me/botfather). 15 | - `AMAZON_TAG` (required) is the Amazon affiliate tag to be used when rewriting URLs. 16 | 17 | You can set two optional parameters through environment variables: 18 | 19 | - `SHORTEN_LINKS`: if set to `"true"`, all the sponsored links generated by the bot will be passed through the bitly shortener, which generates amzn.to links. 20 | - `BITLY_TOKEN` (required if `SHORTEN_LINKS` is `"true"`) is the [Generic Access Token](https://bitly.is/accesstoken) you can get from bitly. 21 | - `AMAZON_TLD` is the Amazon TLD for affiliate links (it defaults to "com", but you can set it to "it", "de", "fr" or whatever). 22 | - `GROUP_REPLACEMENT_MESSAGE` specifies the format for the message that gets posted to groups after deleting the original one. If not set, it will default to `Message by {USER} with Amazon affiliate link:\n\n{MESSAGE}`. In the following table you'll find variables you can use. 23 | - `RAW_LINKS`: if set to `"true"` disables this bot's "URL beautifier" (which removes all the URL parameters aside from the ASIN and the affiliate tag) and just adds/replaces the tag to the URL. This allows to link to arbitrary pages on Amazon, even non-product ones (e.g. search pages, category pages, etc.) 24 | - `CHECK_FOR_REDIRECTS`: if set to `"true"` the bot will look for redirects in the provided links. This allows it to find links to Amazon even if they are hidden behind URL shorteners. Please note that this check requires a bit more time than looking for "regular" Amazon URLs, since the bot needs to connect to each URL. 25 | - `CHECK_FOR_REDIRECT_CHAINS`: if set to `"true"` the bot will perform a recursive search for redirects. This allows it to catch Amazon URLs even if they are hidden behind a chain of redirects. This will slow down the processing of redirects. 26 | - `MAX_REDIRECT_CHAIN_DEPTH`: if `CHECK_FOR_REDIRECT_CHAINS` is enabled, it will limit the number of redirect levels the bot will try to go through. It default to 2, and it should be kept at a low number to avoid wasting time going through endless redirect chains a user might provide. 27 | - `IGNORE_USERS`: a comma-separated list of usernames (starting with the "@" character) and numeric user IDs whose messages won't be acted upon by the bot, even if they contain matching Amazon links. A valid list would be `"@Yourusername,12345678,@IgnoreMeAsWell123"`. Numeric user IDs are useful for users who do not have Telegram user names defined. You can get yours by contacting [userinfobot](https://t.me/useridinfobot). 28 | - `CHANNEL_NAME`: the name of a channel to relay affiliated links to. You must first add your bot as an admin for that channel. Feature added by adapting [@nsniteshsahni's commit](https://github.com/nsniteshsahni/telegram-bot-amazon/commit/b1b814083c83089f44293adbd622ac87be8f19e8) 29 | 30 | | String | Replacement | 31 | | -------------------- | -------------------------------------------------------------------------------------------------------------------------- | 32 | | `{USER}` | The user that posted the message, as `@username` if they created a Telegram username, as `first_name last_name` otherwise. | 33 | | `{ORIGINAL_MESSAGE}` | The user's original message, with no replacements (so it will contain the non-affiliated Amazon link). | 34 | | `{MESSAGE}` | The user's message, with the affiliated Amazon link the bot created. | 35 | 36 | ## Running the app 37 | 38 | You can either run the app directly through NodeJS 39 | 40 | TELEGRAM_BOT_TOKEN=your-token AMAZON_TAG=your-tag node index.js 41 | 42 | Or you can run it in Docker 43 | 44 | docker run -e TELEGRAM_BOT_TOKEN=your-token -e AMAZON_TAG=your-tag --init lucatnt/telegram-bot-amazon 45 | 46 | Note that the `--init` option is highly recommended because it allows you to stop the container through a simple Ctrl+C when running in the foreground. Without it you need to use `docker stop`. 47 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | telegram-bot-amazon 3 | 4 | Author: Luca Zorzi (@LucaTNT) 5 | Contributers: 6 | - Nitesh Sahni (@nsniteshsahni) 7 | License: MIT 8 | */ 9 | 10 | const TelegramBot = require("node-telegram-bot-api"); 11 | const fetch = require("node-fetch"); 12 | 13 | const fullURLRegex = 14 | /https?:\/\/(([^\s]*)\.)?amazon\.([a-z.]{2,5})(\/d\/([^\s]*)|\/([^\s]*)\/?(?:dp|o|gp|-)\/)(aw\/d\/|product\/)?(B[0-9A-Z]{9})([^\s]*)/gi; 15 | const shortURLRegex = /https?:\/\/(([^\s]*)\.)?amzn\.to\/([0-9A-Za-z]+)/gi; 16 | const URLRegex = 17 | /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/gi; 18 | 19 | const channelName = process.env.CHANNEL_NAME 20 | ? `@${process.env.CHANNEL_NAME}` 21 | : false; 22 | 23 | if (!process.env.TELEGRAM_BOT_TOKEN) { 24 | console.log("Missing TELEGRAM_BOT_TOKEN env variable"); 25 | process.exit(1); 26 | } 27 | 28 | if (!process.env.AMAZON_TAG) { 29 | console.log("Missing AMAZON_TAG env variable"); 30 | process.exit(1); 31 | } 32 | 33 | const shorten_links = 34 | process.env.SHORTEN_LINKS && process.env.SHORTEN_LINKS == "true"; 35 | const bitly_token = process.env.BITLY_TOKEN; 36 | if (shorten_links && !bitly_token) { 37 | console.log( 38 | "Missing BITLY_TOKEN env variable (required when SHORTEN_LINKS is true)" 39 | ); 40 | process.exit(1); 41 | } 42 | 43 | const raw_links = process.env.RAW_LINKS && process.env.RAW_LINKS == "true"; 44 | const check_for_redirects = 45 | process.env.CHECK_FOR_REDIRECTS && process.env.CHECK_FOR_REDIRECTS == "true"; 46 | const check_for_redirect_chains = 47 | process.env.CHECK_FOR_REDIRECT_CHAINS && 48 | process.env.CHECK_FOR_REDIRECT_CHAINS == "true"; 49 | const max_redirect_chain_depth = process.env.MAX_REDIRECT_CHAIN_DEPTH || 2; 50 | 51 | var group_replacement_message; 52 | 53 | if (!process.env.GROUP_REPLACEMENT_MESSAGE) { 54 | console.log( 55 | "Missing GROUP_REPLACEMENT_MESSAGE env variable, using the default one" 56 | ); 57 | group_replacement_message = 58 | "Message by {USER} with Amazon affiliate link:\n\n{MESSAGE}"; 59 | } else { 60 | group_replacement_message = process.env.GROUP_REPLACEMENT_MESSAGE; 61 | } 62 | 63 | var amazon_tld; 64 | 65 | if (!process.env.AMAZON_TLD) { 66 | console.log("Missing AMAZON_TLD env variable, using the default one (.com)"); 67 | amazon_tld = "com"; 68 | } else { 69 | amazon_tld = process.env.AMAZON_TLD; 70 | } 71 | 72 | const token = process.env.TELEGRAM_BOT_TOKEN; 73 | const amazon_tag = process.env.AMAZON_TAG; 74 | const rawUrlRegex = new RegExp( 75 | `https?://(([^\\s]*)\\.)?amazon\\.${amazon_tld}/?([^\\s]*)`, 76 | "ig" 77 | ); 78 | 79 | var usernames_to_ignore = []; 80 | var user_ids_to_ignore = []; 81 | 82 | if (process.env.IGNORE_USERS) { 83 | const usernameRegex = /@([^\s]+)/gi; 84 | const userIdRegex = /([0-9]+)/gi; 85 | let to_ignore = process.env.IGNORE_USERS.split(","); 86 | to_ignore.forEach((ignore) => { 87 | let usernameResult = usernameRegex.exec(ignore.trim()); 88 | if (usernameResult) { 89 | usernames_to_ignore.push(usernameResult[1].toLowerCase()); 90 | } else { 91 | let userIdResult = userIdRegex.exec(ignore.trim()); 92 | if (userIdResult) { 93 | user_ids_to_ignore.push(parseInt(userIdResult[1])); 94 | } 95 | } 96 | }); 97 | } 98 | 99 | const bot = new TelegramBot(token, { polling: true }); 100 | 101 | function log(msg) { 102 | const date = new Date().toISOString().replace(/T/, " ").replace(/\..+/, ""); 103 | console.log(date + " " + msg); 104 | } 105 | 106 | async function shortenURL(url) { 107 | const headers = { 108 | Authorization: `Bearer ${bitly_token}`, 109 | "Content-Type": "application/json", 110 | }; 111 | const body = { long_url: url, domain: "bit.ly" }; 112 | try { 113 | const res = await fetch("https://api-ssl.bitly.com/v4/shorten", { 114 | method: "post", 115 | headers: headers, 116 | body: JSON.stringify(body), 117 | }); 118 | const result = await res.json(); 119 | if (result.link) { 120 | return result.link; 121 | } else { 122 | log("Error in bitly response " + JSON.stringify(result)); 123 | return url; 124 | } 125 | } catch (err) { 126 | log(`Error in bitly response ${err}`); 127 | return url; 128 | } 129 | } 130 | 131 | function buildAmazonUrl(asin) { 132 | return `https://www.amazon.${amazon_tld}/dp/${asin}?tag=${amazon_tag}`; 133 | } 134 | 135 | function buildRawAmazonUrl(element) { 136 | const url = element.expanded_url ? element.expanded_url : element.fullURL; 137 | const strucutredURL = new URL(url); 138 | strucutredURL.searchParams.set("tag", amazon_tag); 139 | 140 | return strucutredURL.toString(); 141 | } 142 | 143 | async function getAmazonURL(element) { 144 | const url = 145 | element.asin != null 146 | ? buildAmazonUrl(element.asin) 147 | : buildRawAmazonUrl(element); 148 | return shorten_links ? await shortenURL(url) : url; 149 | } 150 | 151 | function buildMention(user) { 152 | return user.username 153 | ? "@" + user.username 154 | : user.first_name + (user.last_name ? " " + user.last_name : ""); 155 | } 156 | 157 | async function buildMessage(chat, message, replacements, user) { 158 | if (isGroup(chat)) { 159 | var affiliate_message = message; 160 | for await (const element of replacements) { 161 | const sponsored_url = await getAmazonURL(element); 162 | affiliate_message = affiliate_message.replace( 163 | element.fullURL, 164 | sponsored_url 165 | ); 166 | } 167 | 168 | return group_replacement_message 169 | .replace(/\\n/g, "\n") 170 | .replace("{USER}", buildMention(user)) 171 | .replace("{MESSAGE}", affiliate_message) 172 | .replace("{ORIGINAL_MESSAGE}", message); 173 | } else { 174 | var text = ""; 175 | if (replacements.length > 1) { 176 | for await (const element of replacements) { 177 | text += "• " + (await getAmazonURL(element)) + "\n"; 178 | } 179 | } else { 180 | text = await getAmazonURL(replacements[0]); 181 | } 182 | 183 | return text; 184 | } 185 | } 186 | 187 | function isGroup(chat) { 188 | return chat.type == "group" || chat.type == "supergroup"; 189 | } 190 | 191 | function deleteAndSend(msg, text) { 192 | const chat = msg.chat; 193 | const messageId = msg.message_id; 194 | const chatId = chat.id; 195 | var deleted = false; 196 | 197 | if (isGroup(chat)) { 198 | bot.deleteMessage(chatId, messageId); 199 | deleted = true; 200 | } 201 | const options = msg.reply_to_message 202 | ? { reply_to_message_id: msg.reply_to_message.message_id } 203 | : {}; 204 | 205 | if (msg.captionSavedAsText && isGroup(chat)) { 206 | bot.sendPhoto(chatId, msg.photo[0].file_id, { ...options, caption: text }); 207 | if (channelName) { 208 | bot.sendPhoto(channelName, msg.photo[0].file_id, { 209 | ...options, 210 | caption: text, 211 | }); 212 | } 213 | } else { 214 | bot.sendMessage(chatId, text, options); 215 | if (channelName) { 216 | bot.sendMessage(channelName, text, options); 217 | } 218 | } 219 | 220 | return deleted; 221 | } 222 | 223 | function getASINFromFullUrl(url) { 224 | const match = fullURLRegex.exec(url); 225 | 226 | return match != null ? match[8] : url; 227 | } 228 | 229 | async function getLongUrl(shortURL, chain_depth = 0) { 230 | try { 231 | chain_depth++; 232 | let res = await fetch(shortURL, { redirect: "manual" }); 233 | let fullURL = res.headers.get("location"); 234 | 235 | if (check_for_redirect_chains && chain_depth < max_redirect_chain_depth) { 236 | var nextRedirect = null; 237 | if (fullURL !== null) { 238 | nextRedirect = await getLongUrl(fullURL, chain_depth); 239 | } 240 | 241 | if (fullURL === null) { 242 | return { fullURL: shortURL, shortURL: shortURL }; 243 | } else { 244 | return { fullURL: nextRedirect["fullURL"], shortURL: shortURL }; 245 | } 246 | } else { 247 | if (fullURL === null) { 248 | return { fullURL: shortURL, shortURL: shortURL }; 249 | } else { 250 | return { fullURL: fullURL, shortURL: shortURL }; 251 | } 252 | } 253 | } catch (err) { 254 | log("Short URL " + shortURL + " -> ERROR"); 255 | return null; 256 | } 257 | } 258 | 259 | function replaceTextLinks(msg) { 260 | if (msg.entities) { 261 | var offset_shift = 0; 262 | msg.entities.forEach((entity) => { 263 | if (entity.type == "text_link") { 264 | let offset = entity.offset + offset_shift; 265 | let length = entity.length; 266 | 267 | var new_text = ""; 268 | 269 | if (offset > 0) { 270 | new_text += msg.text.substring(0, offset); 271 | } 272 | 273 | new_text += entity.url; 274 | offset_shift = entity.url.length - length; 275 | 276 | new_text += msg.text.substring(offset + length); 277 | 278 | msg.text = new_text; 279 | } 280 | }); 281 | 282 | return msg.text; 283 | } 284 | } 285 | 286 | bot.on("message", async (msg) => { 287 | try { 288 | let from_username = msg.from.username 289 | ? msg.from.username.toLowerCase() 290 | : ""; 291 | let from_id = msg.from.id; 292 | if ( 293 | (!usernames_to_ignore.includes(from_username) && 294 | !user_ids_to_ignore.includes(from_id)) || 295 | !isGroup(msg.chat) 296 | ) { 297 | msg.text = replaceTextLinks(msg); 298 | 299 | msg.text = msg.text || msg.caption; 300 | msg.captionSavedAsText = msg.text == msg.caption; 301 | 302 | if (check_for_redirects) { 303 | URLRegex.lastIndex = 0; 304 | var longURLReplacements = []; 305 | while ((match = URLRegex.exec(msg.text)) !== null) { 306 | shortURLRegex.lastIndex = 0; 307 | rawUrlRegex.lastIndex = 0; 308 | if ( 309 | shortURLRegex.exec(match[0]) === null && 310 | rawUrlRegex.exec(match[0]) === null 311 | ) { 312 | log(`Found non-Amazon URL ${match[0]}`); 313 | let longURL = await getLongUrl(match[0]); 314 | longURLReplacements.push(longURL); 315 | } 316 | } 317 | 318 | longURLReplacements.forEach((element) => { 319 | if (element.fullURL !== null) { 320 | msg.text = msg.text.replace(element.shortURL, element.fullURL); 321 | } 322 | }); 323 | } 324 | 325 | shortURLRegex.lastIndex = 0; 326 | var replacements = []; 327 | var match; 328 | if (raw_links) { 329 | rawUrlRegex.lastIndex = 0; 330 | 331 | while ((match = rawUrlRegex.exec(msg.text)) !== null) { 332 | const fullURL = match[0]; 333 | 334 | replacements.push({ asin: null, fullURL: fullURL }); 335 | } 336 | } else { 337 | fullURLRegex.lastIndex = 0; 338 | 339 | while ((match = fullURLRegex.exec(msg.text)) !== null) { 340 | const asin = match[8]; 341 | const fullURL = match[0]; 342 | replacements.push({ asin: asin, fullURL: fullURL }); 343 | } 344 | } 345 | 346 | while ((match = shortURLRegex.exec(msg.text)) !== null) { 347 | const shortURL = match[0]; 348 | fullURLRegex.lastIndex = 0; // Otherwise sometimes getASINFromFullUrl won't succeed 349 | const url = await getLongUrl(shortURL); 350 | 351 | if (url != null) { 352 | if (raw_links) { 353 | replacements.push({ 354 | asin: null, 355 | expanded_url: url.fullURL, 356 | fullURL: shortURL, 357 | }); 358 | } else { 359 | replacements.push({ 360 | asin: getASINFromFullUrl(url.fullURL), 361 | fullURL: shortURL, 362 | }); 363 | } 364 | } 365 | } 366 | 367 | if (replacements.length > 0) { 368 | const text = await buildMessage( 369 | msg.chat, 370 | msg.text, 371 | replacements, 372 | msg.from 373 | ); 374 | const deleted = deleteAndSend(msg, text); 375 | 376 | if (replacements.length > 1) { 377 | replacements.forEach((element) => { 378 | log( 379 | "Long URL " + 380 | element.fullURL + 381 | " -> ASIN " + 382 | element.asin + 383 | " from " + 384 | buildMention(msg.from) + 385 | (deleted ? " (original message deleted)" : "") 386 | ); 387 | }); 388 | } else { 389 | log( 390 | "Long URL " + 391 | replacements[0].fullURL + 392 | " -> ASIN " + 393 | replacements[0].asin + 394 | " from " + 395 | buildMention(msg.from) + 396 | (deleted ? " (original message deleted)" : "") 397 | ); 398 | } 399 | } 400 | } else { 401 | log( 402 | `Ignored message from ${buildMention( 403 | msg.from 404 | )} because it is included in the IGNORE_USERS env variable` 405 | ); 406 | } 407 | } catch (e) { 408 | log( 409 | "ERROR, please file a bug report at https://github.com/LucaTNT/telegram-bot-amazon" 410 | ); 411 | console.log(e); 412 | } 413 | }); 414 | --------------------------------------------------------------------------------