├── .eslintrc.json ├── .gitattributes ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── config.js ├── image-1.png ├── image-2.png ├── image.png ├── package.json ├── src ├── index.js └── utils │ └── listToMatrix.js └── start.bat /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "env": { 4 | "node": true, 5 | "es6": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": 2021, 9 | "sourceType": "module" 10 | }, 11 | "rules": { 12 | "arrow-spacing": ["warn", { "before": true, "after": true }], 13 | "brace-style": ["error", "stroustrup", { "allowSingleLine": true }], 14 | "comma-dangle": ["error", "always-multiline"], 15 | "comma-spacing": "error", 16 | "comma-style": "error", 17 | "curly": ["error", "multi-line", "consistent"], 18 | "dot-location": ["error", "property"], 19 | "handle-callback-err": "off", 20 | "indent": ["error", "tab"], 21 | "keyword-spacing": "error", 22 | "max-nested-callbacks": ["error", { "max": 4 }], 23 | "max-statements-per-line": ["error", { "max": 2 }], 24 | "no-console": "off", 25 | "no-const-assign": "error", 26 | "no-empty-function": "error", 27 | "no-floating-decimal": "error", 28 | "no-inline-comments": "error", 29 | "no-lonely-if": "error", 30 | "no-multi-spaces": "error", 31 | "no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }], 32 | "no-shadow": ["error", { "allow": ["err", "resolve", "reject"] }], 33 | "no-trailing-spaces": ["error"], 34 | "no-unreachable": "error", 35 | "no-useless-escape": ["error"], 36 | "no-var": "error", 37 | "object-curly-spacing": ["error", "always"], 38 | "prefer-const": "error", 39 | "quotes": ["error", "double"], 40 | "semi": ["error", "always"], 41 | "space-before-blocks": "error", 42 | "space-before-function-paren": ["error", { 43 | "anonymous": "never", 44 | "named": "never", 45 | "asyncArrow": "always" 46 | }], 47 | "space-in-parens": "error", 48 | "space-infix-ops": "error", 49 | "space-unary-ops": "error", 50 | "spaced-comment": "error", 51 | "yoda": "error" 52 | } 53 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.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 | # Serverless directories 108 | .serverless/ 109 | 110 | # FuseBox cache 111 | .fusebox/ 112 | 113 | # DynamoDB Local files 114 | .dynamodb/ 115 | 116 | # TernJS port file 117 | .tern-port 118 | 119 | # Stores VSCode versions used for testing VSCode extensions 120 | .vscode-test 121 | 122 | # yarn v2 123 | .yarn/cache 124 | .yarn/unplugged 125 | .yarn/build-state.yml 126 | .yarn/install-state.gz 127 | .pnp.* 128 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.detectIndentation": false, 3 | "editor.insertSpaces": false, 4 | "editor.tabSize": 2, 5 | "javascript.updateImportsOnFileMove.enabled": "prompt", 6 | "editor.codeActionsOnSave": { 7 | "source.fixAll": true, 8 | "source.organizeImports": false 9 | } 10 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Asad 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Discord Image Logger 2 | 3 | Hi there 4 | 5 | This is a Discord bot designed to log deleted images and send those to a particular channel. 6 | 7 | ## Features 8 | - Configurable logging channels 9 | - Compatibility with multiple guilds 10 | - No database necessary 11 | - Logs all attached images to a particular channel when the message is deleted 12 | - Doesn't crash on uncaught exceptions 13 | 14 | ## Intents 15 | Required Priviliged Intents: Server Members (`GUILD_MEMBERS`) **and** Message Content (`MESSAGE_CONTENT`) 16 | 17 | ## Bugs 18 | 19 | Encounter any bugs? Open an issue! 20 | 21 | ## Disclaimer 22 | 23 | I do not accept any responsibility if this code is used for any malicious or unlawful purposes. 24 | ## Setup 25 | ### Installing NodeJS 26 | First step is to install NodeJS. You can download the latest version from [here](https://nodejs.org/en/download/). Download the LTS version for your operating system. 27 | 28 | ![Alt text](image.png) 29 | 30 | ### Installing the bot 31 | Download the repository. Once downloaded, head to the config.js file. Follow the instructions there to add your configuration data. Once done, open a terminal in the directory and run `npm install`. This will install all the dependencies. 32 | 33 | ### Adding the bot to your server 34 | Head to the [Discord Developer Portal](https://discord.com/developers/applications) and create a new application. Once done, head to the 'Bot' tab and click 'Add Bot'. 35 | Before we get to anything else, we need to configure Intents. This can be done by replicating the settings shown below: 36 | 37 | ![Alt text](image-2.png) 38 | 39 | > These are found on the 'Bot' section of your application. 40 | 41 | Once done, make a new file in the main folder and name it `.env`. Copy your bot's token. Inside the .env file, write "token=your_discord_token_goes_here", replacing your_discord_token_goes_here with your bot's token which you had copied moments earlier. 42 | 43 | Bot invite link: https://discord.com/api/oauth2/authorize?client_id=YOUR_CLIENT_ID&scope=bot&permissions=8 44 | You can use the above lik to add your bot to your server. Replace YOUR_CLIENT_ID with your bot's client ID. You can find this in the 'General Information' tab of your application in the Discord Developer Portal. 45 | 46 | ![Alt text](image-1.png) 47 | 48 | Once done, run `node index.js` to start the bot. You can do this by either clicking and running 'start.bat', or by holding shift+right clicking in the folder and then clicking 'Open PowerShell window here' and then typing `node index.js` to start the process. If you have not installed your dependencies, then the bot will not start. You can install your dependencies by running the command `npm install` in the terminal. 49 | 50 | 51 | ## Support 52 | If anybody needs help with this, feel free to open an issue on GitHub and I will get back to you as soon as I can. 53 | 54 | ## Author 55 | 56 | **Discord-Image-Logger** © [Asad Humayun](https://github.com/asadhumayun). 57 | 58 | Authored and maintained by Asad. 59 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | const Data = { 2 | logChannels: { 3 | /** 4 | * Object that contains configuration for the logging channels and their corresponding guilds. 5 | * To bind a particular guild to a specific channel, enter the guild id in "" followed by a :, 6 | * then enter the ID of the channel where deleted images should be sent. 7 | * Both IDs must be in quotes, otherwise an error will be thrown. 8 | * @example "8934298076025698": "84572569875460987" 9 | */ 10 | "a_guild_id": "a_channel_id", 11 | }, 12 | }; 13 | 14 | export { Data }; -------------------------------------------------------------------------------- /image-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsadHumayun/Discord-Image-Logger/ee61b9c4c61293b53147cdf5b0ca45e234c5d6f2/image-1.png -------------------------------------------------------------------------------- /image-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsadHumayun/Discord-Image-Logger/ee61b9c4c61293b53147cdf5b0ca45e234c5d6f2/image-2.png -------------------------------------------------------------------------------- /image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsadHumayun/Discord-Image-Logger/ee61b9c4c61293b53147cdf5b0ca45e234c5d6f2/image.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "discord-image-logger", 3 | "version": "1.0.0", 4 | "main": "src/index.js", 5 | "type": "module", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "devDependencies": { 10 | "eslint": "^8.16.0" 11 | }, 12 | "dependencies": { 13 | "discord.js": "13.7.0", 14 | "dotenv": "^16.0.1" 15 | }, 16 | "keywords": [], 17 | "author": { 18 | "name": "Asad" 19 | }, 20 | "license": "ISC", 21 | "description": "A simple Discord bot that is designed to log deleted images, configurable to use for a multitude of guilds." 22 | } 23 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { Client, Intents, MessageEmbed } from "discord.js"; 2 | import { config } from "dotenv"; 3 | 4 | import { Data } from "../config.js"; 5 | import { listToMatrix } from "./utils/listToMatrix.js"; 6 | 7 | const __warns = []; 8 | 9 | const client = new Client({ 10 | allowedMentions: { parse: [], repliedUser: false }, 11 | intents: new Intents().add([ 12 | Intents.FLAGS.GUILDS, 13 | Intents.FLAGS.GUILD_MESSAGES, 14 | Intents.FLAGS.GUILD_MEMBERS, 15 | ]), 16 | }); 17 | 18 | // Prevent crashes upon 'error' 19 | client.on("error", console.error); 20 | client.on("warn", console.warn); 21 | process.on("unhandledRejection", console.error); 22 | 23 | client.on("ready", () => { 24 | console.log(`Successfully logged in as ${client.user.tag} (${client.user.id})`); 25 | 26 | // Set a status 27 | client.user.presence.set({ 28 | activities: [{ 29 | name: "Logging images for you!", 30 | }], 31 | status: "dnd", 32 | }); 33 | }); 34 | 35 | client.on("messageDelete", async message => { 36 | const images = [...message.attachments.values()] 37 | .filter((attachment) => attachment.contentType.startsWith("image")) 38 | .map((attachment, index) => { 39 | attachment.index = index; 40 | return attachment; 41 | }); 42 | 43 | let embeds = []; 44 | for (const image of images) { 45 | embeds.push( 46 | new MessageEmbed() 47 | .setColor("#da0000") 48 | .setAuthor({ name: `${message.author.tag} (${message.author.id})`, iconURL: message.author.displayAvatarURL({ dynamic: true }) }) 49 | .setDescription(`Image ${image.index + 1} of ${images.length} sent in ${message.channel}.`) 50 | .setImage(image.url) 51 | .setFooter({ text: `Message ID: ${message.id}` }) 52 | .setTimestamp(), 53 | ); 54 | } 55 | 56 | embeds = listToMatrix(embeds, 10); 57 | 58 | for (const messageEmbeds of embeds) { 59 | const channel = Data.logChannels[message.guild.id]; 60 | 61 | if (!channel) { 62 | if (!__warns.includes(message.guild.id)) { 63 | console.warn( 64 | `Received messageDelete event in guild "${message.guild.name}", but no log channel was found. 65 | Add a property named '${message.guild.id}' in logChannels in config.js with the corresponding ID of the channel where you would like image delete logs to be sent. 66 | This warning will not appear for the "${message.guild.name}" guild once again for the duration of this programme.`, 67 | ); 68 | } 69 | __warns.push(message.guild.id); 70 | break; 71 | } 72 | else { 73 | client.channels.cache.get(channel).send({ embeds: messageEmbeds }); 74 | } 75 | } 76 | }); 77 | 78 | config(); 79 | client.login(process.env.token); -------------------------------------------------------------------------------- /src/utils/listToMatrix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @source https://stackoverflow.com/questions/4492385/convert-simple-array-into-two-dimensional-array-matrix 3 | */ 4 | export function listToMatrix(list, elementsPerSubArray) { 5 | const matrix = []; 6 | let i, k; 7 | for (i = 0, k = -1; i < list.length; i++) { 8 | if (i % elementsPerSubArray === 0) { 9 | k++; 10 | matrix[k] = []; 11 | } 12 | matrix[k].push(list[i]); 13 | } 14 | return matrix; 15 | } -------------------------------------------------------------------------------- /start.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | node . 3 | echo "Pressing any key will terminate the programme." 4 | pause --------------------------------------------------------------------------------