├── .env.example ├── .github └── dependabot.yml ├── .gitignore ├── .replit ├── Procfile ├── README.md ├── index.js ├── package.json └── src ├── commands └── status │ └── status.js ├── index.js └── util └── watching.js /.env.example: -------------------------------------------------------------------------------- 1 | BOT_TOKEN="BOT_TOKEN GOES HERE" 2 | PREFIX="?" 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "11:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .github/ 2 | node_modules/ 3 | package-lock.json -------------------------------------------------------------------------------- /.replit: -------------------------------------------------------------------------------- 1 | language = "nodejs" 2 | run = "node index.js" 3 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | worker: node index.js 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to the Status Bot repository 2 | 3 | 4 | 5 | 6 | 7 | 8 | # This repo and any bots using it will not work after April 30th 2022, following the announcement made by Discord staff 9 | # [READ THIS](https://github.com/discord/discord-api-docs/discussions/4510) 10 | ## Anything using the v6/v7 API endpoints will either need to be upgraded to v9 or v10, if not it will not work after the date above! 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | - Alrighty with the welcome out of the way, lets get started. 20 | 21 | # Discord Notice 22 | 23 | In order for this to work you WILL need to enable these 24 | ![img](https://i.imgur.com/GWXtIOW.png) 25 | 26 | On the Discord Developers page for your bot. 27 | 28 | 29 | # Getting started 30 | 31 | 32 | > ⚠ You'll need nodejs v12+ to run this code, due to discord.js-v12 33 | 34 | 35 | 36 | - 1. Download the bot or use `git clone https://github.com/elara-bots/Status-Bot` 37 | - 2. Once you got that done open either `git bash` or `command prompt` in the folder and do `npm install` and wait until it's finished. 38 | - 3. Go to `index.js` and put in the required information. 39 | ```js 40 | owners: [] // Your owner ID, example: owners: ["123456789"] 41 | prefix: process.env.PREFIX || "?", // Your bot's prefix, example: prefix: "?" // default prefix is "?" 42 | token: process.env.BOT_TOKEN // Put the bot's token in the .env file, instructions down below, you get this from the Discord Developers page, if you're unsure where to get it do a quick google to figure it out. 43 | watch: [] // This is an array-object for which users to watch by the bot and announce when they go offline/online 44 | // Example: [add("USERID", "WEBHOOK_URL", "GUILD_ID", {role: "ROLE_ID", enabled: true})] // note: the role is optional.. 45 | // Why use webhooks? 46 | // Webhooks allow you to mention the role without needing it mentionable in the server settings. 🙂 47 | presence: { // optional 48 | status: "", // Types: online, idle, dnd, invisible 49 | name: "", // Whatever you want the bot to say it's playing. 50 | type: "", // Types: PLAYING, LISTENING, STREAMING, WATCHING 51 | url: "" // only needed for the streaming playing status. 52 | } 53 | ``` 54 | - 4. Remove the `.example` from the `.env.example` file, fill out the `BOT_TOKEN` field and the `PREFIX` 55 | 56 | 57 | # Extra Information 58 | This repository uses: [great-commando](https://github.com/elara-bots/great-commando/tree/v12) [v12 branch].. if you have any issues with the `great-commando` package open an issue in that repository, include the branch you're using. 59 | 60 | 61 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | 3 | const Bot = require("./src/index"), 4 | { add } = require("./src/util/watching"); 5 | 6 | 7 | new Bot({ 8 | owners: [], // YOUR USER ID IN HERE 9 | prefix: process.env.PREFIX || "?", // DEFAULT IS ? 10 | token: process.env.BOT_TOKEN, // YOUR BOT TOKEN (DO NOT SHARE WITH ANYONE) 11 | // embed: {} // on: {}, off: {} // refer to the readme 12 | watch: [ 13 | // The user/bot's ID, The full webhook URL, the guildID that you want the bot to watch for, the roleID and if you want it enabled. 14 | // add(`USERID`, `WEBHOOK_URL`, `GUILD_ID`, {role: "ROLE_ID", enabled: true}), 15 | ], 16 | presence: { 17 | status: "online", 18 | name: "Discord Bots", 19 | type: "WATCHING" 20 | } 21 | }).start(); 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "status-bot", 3 | "version": "3.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/elara-bots/Status-Bot.git" 12 | }, 13 | "keywords": [ 14 | "discordbot", 15 | "discord", 16 | "discord-alert", 17 | "discord-alerts" 18 | ], 19 | "author": "SUPERCHIEFYT (Elara-Discord-Bots, Elara-Services)", 20 | "license": "ISC", 21 | "bugs": { 22 | "url": "https://github.com/elara-bots/Status-Bot/issues" 23 | }, 24 | "homepage": "https://github.com/elara-bots/Status-Bot#readme", 25 | "dependencies": { 26 | "discord-hook": "^1.1.4", 27 | "discord.js": "12.5.3", 28 | "dotenv": "^14.1.0", 29 | "great-commando": "github:elara-bots/great-commando#v12" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/commands/status/status.js: -------------------------------------------------------------------------------- 1 | const { Command } = require('great-commando'), 2 | { MessageEmbed } = require('discord.js'); 3 | 4 | module.exports = class Status extends Command { 5 | constructor(client){ 6 | super(client, { 7 | name: "status", 8 | memberName: "status", 9 | group: "status", 10 | description: `Shows the status of the people in the 'watch' array`, 11 | examples: [`${client.commandPrefix}status`] 12 | }) 13 | }; 14 | async run(message){ 15 | let [ online, idle, dnd, offline ] = [ [], [], [], [] ]; 16 | 17 | for (const user of this.client.watch){ 18 | let object = this.client.users.cache.get(user.userID); 19 | switch(object.presence.status){ 20 | case "online": online.push(object.username); break; 21 | case "idle": idle.push(object.username); break; 22 | case "dnd": dnd.push(object.username); break; 23 | case "offline": case "invisible": offline.push(object.username); break; 24 | } 25 | }; 26 | let e = new MessageEmbed() 27 | .setTitle(`Status`) 28 | .setColor(message.member.displayColor) 29 | .setTimestamp() 30 | 31 | if(offline.length) e.addField(`**Offline**`, offline.join('\n')) 32 | if(online.length) e.addField(`Online`, online.join('\n')) 33 | if(idle.length) e.addField(`Idle`, idle.join('\n')); 34 | if(dnd.length) e.addField(`Dnd`, dnd.join('\n')) 35 | return message.channel.send(e).catch(() => null) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // DO NOT CHANGE A DAMN THING IN THIS FILE UNLESS YOU KNOW WHAT YOU'RE DOING.. 2 | 3 | // I ain't responsible for idiots who fuck it up, if you fuck it up don't complain.. I warned you to not mess with this file. 4 | 5 | const { CommandoClient } = require("great-commando"), 6 | Hook = require("discord-hook"), 7 | { join } = require("path"), 8 | getEmbed = (embed, user) => { 9 | function filterArgs(args){ 10 | if(typeof args !== "string") return args; 11 | return args 12 | .replace(/%user%/gi, user.user.username) 13 | .replace(/%id%/gi, user.user.id) 14 | .replace(/%tag%/gi, user.user.tag) 15 | .replace(/%status%/gi, user.status) 16 | .replace(/%icon%/gi, user.user.displayAvatarURL({ dynamic: true })) 17 | .replace(/%mention%/gi, user.toString()) 18 | .replace(/%timestamp%/gi, new Date()); 19 | }; 20 | return { 21 | title: filterArgs(embed.title || ""), 22 | description: filterArgs(embed.description || ""), 23 | thumbnail: filterArgs(embed.thumbnail || ""), 24 | timestamp: filterArgs(embed.timestamp || ""), 25 | color: embed.color, 26 | footer: { 27 | text: filterArgs(embed.footer.text || ""), 28 | icon_url: filterArgs(embed.footer.icon_url || "") 29 | }, 30 | author: { 31 | name: filterArgs(embed.author.name || ""), 32 | icon_url: filterArgs(embed.author.icon_url || ""), 33 | url: filterArgs(embed.author.url || "") 34 | } 35 | } 36 | }, 37 | log = (c) => console.log(`[${new Date().toISOString()}] - ${c}`), 38 | embed = (e, color = 0xFF000, on = false) => { 39 | let em = { 40 | title: e.title ? e.title : "", 41 | description: e.description ? e.description : `%mention% (\`%id%\`)`, 42 | color: e.color ? e.color : color, 43 | timestamp: "%timestamp%", 44 | thumbnail: e.thumbnail ? e.thumbnail : "%icon%", 45 | footer: { 46 | text: e.footer ? e.footer.text ? e.footer.text : "" : "", 47 | icon_url: e.footer ? e.footer.icon_url ? e.footer.icon_url : "" : "" 48 | }, 49 | author: { 50 | name: e.author ? e.author.name ? e.author.name : `%tag% ${on ? `has come back online!` : `went offline!`}` : `%tag% ${on ? `has come back online!` : `went offline!`}`, 51 | icon_url: e.author ? e.author.icon_url ? e.author.icon_url : "%icon%" : "%icon%", 52 | url: e.author ? e.author.url : "" 53 | } 54 | }; 55 | return em; 56 | }, 57 | defEmbed = { 58 | title: "", 59 | description: "", 60 | thumbnail: "", 61 | color: "", 62 | footer: { 63 | text: "", 64 | icon_url: "" 65 | }, 66 | author: { 67 | name: "", 68 | icon_url: "", 69 | url: "" 70 | }, 71 | timestamp: "%timestamp%" 72 | } 73 | 74 | 75 | module.exports = class StatusService extends CommandoClient { 76 | //----- Typings ---- 77 | /** 78 | * 79 | * @typedef {Object} AuthorOptions 80 | * @property {string} [name] 81 | * @property {string} [icon_url] 82 | * @property {string} [url] 83 | */ 84 | /** 85 | * 86 | * @typedef {Object} FooterOptions 87 | * @property {string} [text] 88 | * @property {string} [icon_url] 89 | */ 90 | /** 91 | * 92 | * @typedef {Object} EmbedObject 93 | * @property {string} [title] 94 | * @property {string} [description] 95 | * @property {string} [thumbnail] 96 | * @property {AuthorOptions} [author] 97 | * @property {FooterOptions} [footer] 98 | * @property {string} [color] 99 | */ 100 | /** 101 | * @typedef {Object} EmbedOptions 102 | * @property {EmbedObject} [on] - The online embed format 103 | * @property {EmbedObject} [off] - The offline embed format 104 | */ 105 | 106 | /** 107 | * 108 | * @typedef {Object} Presence 109 | * @property {string} status - Types: online, idle, dnd, invisible 110 | * @property {string} name - What the bot says it's playing, default "Discord Bots" 111 | * @property {string} type - Types: PLAYING, STREAMING, WATCHING, LISTENING 112 | * @property {string} url - Optional, for the streaming playing status. 113 | */ 114 | /** 115 | * @typedef {Object} Options 116 | * @property {string[]} [owners] - The list of the bot developers/owners 117 | * @property {string[]} [watch] - The objects that contains the userIDs to watch. 118 | * @property {string} [prefix] - The prefix for the bot, default is ? 119 | * @property {EmbedOptions} [embed] - The embed format for the online and offline embed messages messages 120 | * @property {string} [token] - The token for the bot to connect to discord (DO NOT SHARE) 121 | * @property {Presence} [presence] - The presence/playing status for the bot 122 | * @property {string} [invite] - The support server invite. 123 | * @property {boolean} [console] - If the presence updates should be posted in the console 124 | /** 125 | * @param {Options} info - The setup 126 | */ 127 | //---- End of Typings ----- 128 | 129 | 130 | constructor(info){ 131 | super({ 132 | commandPrefix: info.prefix || "?", 133 | invite: info.invite || "https://my.elara.services/support", 134 | owner: info.owners || [], 135 | shards: "auto", 136 | fetchAllMembers: true, 137 | disableMentions: "all", 138 | ws: { 139 | intents: [ 140 | "GUILDS", 141 | "GUILD_MEMBERS", 142 | "GUILD_MESSAGES", 143 | "GUILD_PRESENCES", 144 | "GUILD_MESSAGE_REACTIONS" 145 | ] 146 | } 147 | }); 148 | this.watch = info.watch || []; 149 | this.console = Boolean(info.console || false); 150 | this.embed = { 151 | on: embed(info.embed ? info.embed.on : defEmbed, 0xFF000, true), 152 | off: embed(info.embed ? info.embed.off : defEmbed, 0xFF0000, false) 153 | } 154 | this.cache = []; // this is for ignoring bots/users for a short amount of time and resets after the bot restarts. 155 | this.on("ready", () => { 156 | log(`${this.user.tag} is now ready in ${this.guilds.cache.size} servers`); 157 | if(!Array.isArray(this.watch)){ 158 | log(`Error: "watch" isn't an array!`); 159 | return process.exit(1); 160 | }; 161 | if(!this.watch.length){ 162 | log(`You have 0 people in the watch array`); 163 | return process.exit(1); 164 | }; 165 | for (const w of this.watch){ 166 | let user = this.users.cache.get(w.userID); 167 | if(!user){ 168 | log(`Unable to find ${w.userID} in my cache.`); 169 | }; 170 | let g = this.guilds.cache.get(w.guildID); 171 | if(!g){ 172 | log(`Error: Server (${w.guildID}) I ain't in, so how am I supposed to track ${w.userID}'s activity?`); 173 | return process.exit(1); 174 | } 175 | }; 176 | if(info.presence){ 177 | this.user.setPresence({ 178 | status: info.presence.status || "online", 179 | activity: { 180 | name: info.presence.name || "Users", 181 | type: info.presence.type || "WATCHING", 182 | url: info.presence.url || "https://twitch.tv/Discord" 183 | } 184 | }) 185 | }else{ 186 | this.user.setPresence({ 187 | status: "dnd", 188 | activity: { 189 | name: `Discord Bots`, 190 | type: 'WATCHING', 191 | url: "https://twitch.tv/Discord" 192 | } 193 | }) 194 | } 195 | }); 196 | this.on("commandError", (command, error) => log(`Command Error: ${command.name}\n${error.stack}`)) 197 | this.on('shardReady', (id) => log(`Shard: ${id} is now ready!`)); 198 | this.on('shardReconnecting', (id) => log(`Shard: ${id} is trying to reconnect`)); 199 | this.on('shardDisconnected', (id) => log(`Shard: ${id} has been disconnected!`)); 200 | this.on("shardResumed", (events, id) => log(`Shard: ${id} has resumed with [${events}] events!`)); 201 | this.on('shardError', (error, id) => log(`Shard: ${id} Error: ${error.stack}`)); 202 | this.on("presenceUpdate", async (o, n) => { 203 | if(!o || !n || !o.status || !n.status || o.status === n.status) return null; 204 | let find = this.watch.find(c => c.userID === n.user.id); 205 | if(!find || n.guild.id !== find.guildID || !find.webhook) return null; 206 | if(this.cache.includes(n.user.id)) return null; 207 | if(n.status === "offline"){ 208 | let em = getEmbed(this.embed.off, n); 209 | if(!em) return null; 210 | let { author, title, description, timestamp, color, footer, thumbnail } = em; 211 | let h = new Hook(find.webhook) 212 | .embed({ 213 | author, title, description, timestamp, color, footer, 214 | thumbnail: thumbnail ? { url: thumbnail } : undefined 215 | }) 216 | .name(this.user.username) 217 | .avatar(this.user.displayAvatarURL({ format: "png", dynamic: true })) 218 | if(find.alert.enabled && find.alert.role) h.mention(`<@&${find.alert.role}>`); 219 | if(this.console) log(`${n.user.tag} (${n.user.id}) has gone offline`); 220 | h.send().catch(e => log(`[SEND_WEBHOOK_ERROR]: `, e)); 221 | }else 222 | if(["online", "idle", "dnd"].includes(n.status) && o.status === "offline"){ 223 | let em = getEmbed(this.embed.on, n); 224 | if(!em) return null; 225 | let { author, title, description, timestamp, color, footer, thumbnail } = em; 226 | let h = new Hook(find.webhook) 227 | .embed({ 228 | author, title, description, timestamp, color, footer, 229 | thumbnail: thumbnail ? { url: thumbnail } : undefined 230 | }) 231 | .name(this.user.username) 232 | .avatar(this.user.displayAvatarURL({ format: "png", dynamic: true })) 233 | h.send().catch(e => log(`[SEND_WEBHOOK_ERROR]: `, e)); 234 | if(this.console) log(`${n.user.tag} (${n.user.id}) has come back online!`); 235 | } 236 | }); 237 | 238 | this.start = () => { 239 | this.login(info.token).then(() => { 240 | log(`Connected to the Discord API`); 241 | }).catch((err) => { 242 | log(`Discord API login issue\n`, err.stack); 243 | return process.exit(1); 244 | }) 245 | }; 246 | this.registry 247 | .registerDefaultGroups() 248 | .registerDefaultTypes() 249 | .registerDefaultCommands({help: true, prefix: true, eval: true, ping: true, commandState: true, unknownCommand: false}) 250 | .registerGroup('status', "Status Commands", true) 251 | .registerCommandsIn(join(__dirname, 'commands')) 252 | }; 253 | }; 254 | -------------------------------------------------------------------------------- /src/util/watching.js: -------------------------------------------------------------------------------- 1 | // DO NOT CHANGE A DAMN THING IN THIS FILE UNLESS YOU KNOW WHAT YOU'RE DOING.. 2 | // I ain't responsible for idiots who fuck it up, if you fuck it up don't complain.. I warned you to not mess with this file. 3 | 4 | 5 | const list = []; 6 | module.exports = { 7 | add: (userID, webhook, guildID, alert = { role: "", enabled: false }) => { 8 | let obj = { userID, webhook, guildID, alert }; 9 | list.push(obj); 10 | return obj 11 | }, 12 | list 13 | }; 14 | --------------------------------------------------------------------------------