├── Settings └── Settings.json ├── bot.js ├── package.json ├── Events ├── messageStats.js ├── commandHandler.js └── voiceStats.js ├── README.md ├── LICENSE ├── index.js ├── Commands ├── Top.js ├── Reset.js └── Me.js └── Helpers └── Database.js /Settings/Settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Token": "", 3 | "Prefix": "!", 4 | "Private_Server": true 5 | } 6 | -------------------------------------------------------------------------------- /bot.js: -------------------------------------------------------------------------------- 1 | const client = global.client; 2 | 3 | client.on("ready", () => { 4 | console.log("Bot is ready!"); 5 | }); 6 | 7 | client.login(global.Settings.Token); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "discord-stat-bot", 3 | "version": "1.0.0", 4 | "description": "This bot is a simple Stat bot. The data it records are user activities.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "bot", 11 | "stat", 12 | "discord", 13 | "serendia", 14 | "squad", 15 | "Alosha" 16 | ], 17 | "author": "Alosha", 18 | "license": "MIT", 19 | "dependencies": { 20 | "discord.js": "^12.2.0", 21 | "moment": "^2.27.0", 22 | "moment-duration-format": "^2.3.2" 23 | } 24 | } -------------------------------------------------------------------------------- /Events/messageStats.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js"); 2 | const Database = require("../Helpers/Database"); 3 | const vt = new Database("Database", "Message"); 4 | 5 | /** 6 | * @param {Discord.Message} message 7 | */ 8 | exports.execute = async (message) => { 9 | if(message.author.bot || message.content.startsWith(global.Settings.Prefix)) return; 10 | 11 | vt.add(`stats.${message.guild.id}.${message.author.id}.channels.${message.channel.id}`, 1); 12 | vt.set(`stats.${message.guild.id}.${message.author.id}.activity`, Date.now()); 13 | }; 14 | 15 | exports.conf = { 16 | event: "message" 17 | }; 18 | -------------------------------------------------------------------------------- /Events/commandHandler.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js"); 2 | 3 | /** 4 | * @param {Discord.Message} message 5 | */ 6 | exports.execute = async (message) => { 7 | if(message.author.bot || !message.content.startsWith(global.Settings.Prefix)) return; 8 | 9 | let args = message.content.split(" "); 10 | let commandName = args[0].substring(global.Settings.Prefix.length); 11 | args = args.splice(1); 12 | let command = global.Commands.get(commandName); 13 | if(!command || !command.conf.enabled || (command.conf.guildOnly && message.channel.type != "text")) return; 14 | if(command) 15 | command.run(message.client, message, args); 16 | }; 17 | 18 | exports.conf = { 19 | event: "message" 20 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # discord-stats-bot 2 | A bot that lets you to view activity of users with ranking. 3 | 4 | ## Installation 5 | * Download the project. 6 | * Extract the files to a folder. 7 | * Copy the path of that directory. 8 | * Run cmd, type `cd path/you/copied` and then `npm i`. 9 | 10 | > After the installation, go into the `Settings` folder and then open the `Settings.json` file. 11 | 12 | ```json 13 | { 14 | "Token": "", 15 | "Prefix": "!", 16 | "Private_Server": true 17 | } 18 | ``` 19 | 20 | Fill the `Token` part. 21 | 22 | # 🎉 Ta da! Your bot is ready to go! 🎉 23 | 24 | > Footnote: Bot's database is based on a `JSON` file. If your machine/computer/server doesn't have read/create/wrtie permissions, then the database will be invalid. 25 | 26 | 27 | # discord.gg/serendia 28 | 29 | ### Features that's included in bot does depend on the version. We'll add new features and statistics in future. 30 | 31 | # Discord: Alosha#3779 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Serendia Squad 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 | -------------------------------------------------------------------------------- /Events/voiceStats.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js"); 2 | const Database = require("../Helpers/Database"); 3 | const vt = new Database("Database", "Voice"); 4 | 5 | const Activites = new Map(); 6 | 7 | /** 8 | * @param {Discord.VoiceState} oldState 9 | * @param {Discord.VoiceState} newState 10 | */ 11 | exports.execute = async (oldState, newState) => { 12 | if((oldState.member && oldState.member.user.bot) || (newState.member && newState.member.user.bot)) return; 13 | if(!oldState.channelID && newState.channelID) { // This user has join the channel. 14 | Activites.set(oldState.id, Date.now()); 15 | } 16 | let data; 17 | if(!Activites.has(oldState.id)){ 18 | data = Date.now(); 19 | Activites.set(oldState.id, data); // check current data for the existence of 20 | } 21 | else 22 | data = Activites.get(oldState.id); 23 | let duration = Date.now() - data; 24 | if(oldState.channelID && !newState.channelID) { // This user has left the channel. 25 | Activites.delete(oldState.id); 26 | vt.add(`stats.${oldState.guild.id}.${oldState.id}.channels.${oldState.channelID}`, duration); 27 | vt.set(`stats.${oldState.guild.id}.${oldState.id}.activity`, Date.now()); 28 | } 29 | else if(oldState.channelID && newState.channelID){ // This user has changes the channel. 30 | Activites.set(oldState.id, Date.now()); 31 | vt.add(`stats.${oldState.guild.id}.${oldState.id}.channels.${oldState.channelID}`, duration); 32 | vt.set(`stats.${oldState.guild.id}.${oldState.id}.activity`, Date.now()); 33 | } 34 | }; 35 | 36 | exports.conf = { 37 | event: "voiceStateUpdate" 38 | }; 39 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js"); 2 | const fs = require("fs"); 3 | const Settings = global.Settings = require("./Settings/Settings.json"); 4 | 5 | console.log("Launching bot..."); 6 | let _client = new Discord.Client(); 7 | if (Settings.Private_Server === true) { 8 | _client = new Discord.Client({ 9 | fetchAllMembers: true 10 | }); 11 | } 12 | const client = global.client = _client; 13 | 14 | const Commands = global.Commands = new Map(); 15 | console.log("--------------------------------"); 16 | console.log("Loading commands..."); 17 | fs.readdirSync("./Commands", { encoding: "utf-8" }).filter(file => file.endsWith(".js")).forEach(file => { 18 | let prop = require(`./Commands/${file}`); 19 | if (prop.conf.commands == undefined || prop.run == undefined) return console.error(`[COMMAND] ${file} is not load.`); 20 | if (prop.conf.commands && prop.conf.commands.length > 0) { 21 | prop.conf.commands.forEach(aliase => Commands.set(aliase, prop)); 22 | } 23 | if (prop.onLoad != undefined && typeof (prop.onLoad) == "function") prop.onLoad(client); 24 | console.log(`[COMMAND] A total of ${prop.conf.commands.length} supporters have been installed for ${file}.`); 25 | }); 26 | console.log("--------------------------------"); 27 | console.log("Loading events..."); 28 | fs.readdirSync("./Events", { encoding: "utf-8" }).filter(file => file.endsWith(".js")).forEach(file => { 29 | let prop = require(`./Events/${file}`); 30 | client.on(prop.conf.event, prop.execute); 31 | console.log(`[EVENT] ${file} is loaded.`); 32 | }); 33 | 34 | console.log("--------------------------------"); 35 | console.log("| Preparation has been completed. Starting the bot now |"); 36 | 37 | require("./bot.js"); -------------------------------------------------------------------------------- /Commands/Top.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js"); 2 | const Database = require("../Helpers/Database"); 3 | const vt = new Database("Database", "Voice"); 4 | const mdb = new Database("Database", "Message"); 5 | const moment = require("moment"); 6 | require("moment-duration-format"); 7 | // exports.onLoad = (client) => {}; 8 | /** 9 | * 10 | * @param {Discord.Client} client 11 | * @param {Discord.Message} message 12 | * @param {Array} args 13 | */ 14 | exports.run = async (client, message, args) => { 15 | const voiceData = vt.get(`stats.${message.guild.id}`) || undefined; 16 | const messageData = mdb.get(`stats.${message.guild.id}`) || undefined; 17 | 18 | let messageList = "No results."; 19 | if(messageData){ 20 | messageList = Object.keys(messageData || {}).map(md => { 21 | return { 22 | Id: md, 23 | Total: Object.values(messageData[md].channels || {}).reduce((a, b) => a + b, 0) 24 | }; 25 | }).sort((a, b) => b.Total - a.Total).splice(0, 10).map((user, index) => `\`${index + 1}.\` <@${user.Id}> \`${user.Total} message\``).join("\n"); 26 | } 27 | 28 | let voiceList = "No results."; 29 | if(voiceData){ 30 | voiceList = Object.keys(voiceData || {}).map(md => { 31 | return { 32 | Id: md, 33 | Total: Object.values(voiceData[md].channels || {}).reduce((a, b) => a + b, 0) 34 | }; 35 | }).sort((a, b) => b.Total - a.Total).splice(0, 10).map((user, index) => `\`${index + 1}.\` <@${user.Id}> \`${moment.duration(user.Total).format("H [hours,] m [minutes]")}\``).join("\n"); 36 | } 37 | 38 | let embed = new Discord.MessageEmbed(); 39 | embed.setColor(message.member.displayHexColor) 40 | .setFooter(`${message.author.tag} | Powered by Serendia Squad`) 41 | .setThumbnail(message.author.avatarURL({dynamic: true})) 42 | .setDescription(`You can see your total activity here and you can find information about the activity and the list on this server below.`) 43 | .addField("Voice | Ranking", `** **\n${voiceList}`) 44 | .addField("Message | Ranking", `** **\n${messageList}`); 45 | message.channel.send(embed); 46 | }; 47 | 48 | exports.conf = { 49 | commands: ["top", "siralama", "sıralama", "ranks", "ranking"], 50 | enabled: true, 51 | guildOnly: true 52 | }; 53 | 54 | exports.help = { 55 | name: 'Reset server statistics', 56 | description: 'Resets server statics.', 57 | usage: '[p]rstats [all/voice/messages]', 58 | category: 'Guild' 59 | }; 60 | -------------------------------------------------------------------------------- /Helpers/Database.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | 3 | class Database { 4 | constructor(location = "Database", name = "All"){ 5 | if(location == "Database" && !fs.existsSync(`${__dirname}/Database`)) 6 | { 7 | fs.mkdirSync(`${__dirname}/Database`, {recursive: true}); 8 | } 9 | else if(!fs.existsSync(`${location}`)) 10 | {fs.mkdirSync(`${__dirname}/${location}`, {recursive: true});} 11 | let filePath = `${__dirname}/${location}/${name}.json`; 12 | if(!fs.existsSync(filePath)) 13 | fs.closeSync(fs.openSync(filePath, 'w')); 14 | this.FilePath = filePath; 15 | this.Location = location; 16 | } 17 | add(path, value){ 18 | let data = this.get(path); 19 | if(typeof data == "number") data += Number(value); 20 | else data = Number(value); 21 | return this.set(path, data); 22 | } 23 | get(path){ 24 | let data = this.read(), result = undefined; 25 | if(!data) data = {}; 26 | result = _get(path, data); 27 | return result ? result : undefined; 28 | } 29 | set(path, value){ 30 | let data = this.read(); 31 | if(!data) data = {}; 32 | data = _set(path, value, data); 33 | fs.truncateSync(this.FilePath); 34 | fs.writeFileSync(this.FilePath, JSON.stringify(data), {encoding: "utf-8"}); 35 | return data; 36 | } 37 | read(){ 38 | let data = fs.readFileSync(this.FilePath, {encoding: "utf-8"}); 39 | if(!data || (data && data == null)) return {}; 40 | let obj = JSON.parse(data); 41 | return obj; 42 | } 43 | } 44 | 45 | function _set(path, value, obj = undefined){ 46 | if(obj == undefined) return undefined; 47 | let locations = path.split("."), output = {}; 48 | output = obj; 49 | let ref = output; 50 | for (let index = 0; index < locations.length - 1; index++) { 51 | if(!ref[locations[index]]) 52 | ref = ref[locations[index]] = {}; 53 | else 54 | ref = ref[locations[index]]; 55 | } 56 | ref[locations[locations.length - 1]] = value; 57 | return output; 58 | } 59 | 60 | function _get(path, obj = {}){ 61 | let locations = path.split("."), ref = obj; 62 | for (let index = 0; index < locations.length - 1; index++) { 63 | ref = ref[locations[index]] ? ref[locations[index]] : undefined; 64 | if(!ref) return undefined; 65 | } 66 | let output = ref[locations[locations.length - 1]]; 67 | return output; 68 | } 69 | 70 | module.exports = Database; 71 | -------------------------------------------------------------------------------- /Commands/Reset.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js"); 2 | const Database = require("../Helpers/Database"); 3 | const vt = new Database("Database", "Voice"); 4 | const mdb = new Database("Database", "Message"); 5 | // exports.onLoad = (client) => {}; 6 | /** 7 | * 8 | * @param {Discord.Client} client 9 | * @param {Discord.Message} message 10 | * @param {Array} args 11 | */ 12 | exports.run = async (client, message, args) => { 13 | if(!message.member.permissions.has("ADMINISTRATOR") && !message.member.permissions.has("MANAGE_GUILD")) return message.reply("you are not permission to do this."); 14 | let deleteMessages = []; 15 | 16 | let msg = await message.reply("what data do you want to reset? `(all, voice and messages)` type one of these."); 17 | deleteMessages.push(msg); 18 | 19 | let reply = await message.channel.awaitMessages((m) => m.author.id == message.author.id, { 20 | time: 15000, 21 | max: 1 22 | }).then(messages => messages.first()).catch(err => undefined); 23 | if(!reply){ 24 | message.reply("15 seconds have passed and you were unable to answer properly.") 25 | return delete_Messages(deleteMessages); 26 | } 27 | deleteMessages.push(reply); 28 | 29 | if(!["all", "voice", "messages"].some(type => reply.content.toLowerCase() == type)) return delete_Messages(deleteMessages); 30 | 31 | switch (reply.content) { 32 | case "all": 33 | vt.set(`stats.${message.guild.id}`, {}); 34 | mdb.set(`stats.${message.guild.id}`, {}); 35 | break; 36 | case "voice": 37 | vt.set(`stats.${message.guild.id}`, {}); 38 | break; 39 | case "messages": 40 | mdb.set(`stats.${message.guild.id}`, {}); 41 | break; 42 | default: 43 | vt.set(`stats.${message.guild.id}`, {}); 44 | mdb.set(`stats.${message.guild.id}`, {}); 45 | break; 46 | } 47 | delete_Messages(deleteMessages); 48 | message.reply(`\`${reply.content}\` info was successfully deleted.`).then(m => m.delete({timeout: 5000})); 49 | }; 50 | 51 | exports.conf = { 52 | commands: ["rstats", "resetstat", "resetstats"], 53 | enabled: true, 54 | guildOnly: true 55 | }; 56 | 57 | exports.help = { 58 | name: 'me', 59 | description: 'Sunucudaki aktifliğiniz hakkında bilgi verir.', 60 | usage: 'me', 61 | kategori: 'kullanıcı' 62 | }; 63 | 64 | function delete_Messages(messages) { 65 | messages.forEach(message => { 66 | if(message.deletable && !message.deleted) message.delete().catch(); 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /Commands/Me.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js"); 2 | const Database = require("../Helpers/Database"); 3 | const vt = new Database("Database", "Voice"); 4 | const mdb = new Database("Database", "Message"); 5 | const moment = require("moment"); 6 | require("moment-duration-format"); 7 | // exports.onLoad = (client) => {}; 8 | /** 9 | * 10 | * @param {Discord.Client} client 11 | * @param {Discord.Message} message 12 | * @param {Array} args 13 | */ 14 | exports.run = async (client, message, args) => { 15 | 16 | let voiceData = vt.get(`stats.${message.guild.id}.${message.author.id}`) || {voice: 0, channels: {}}; 17 | let messageData = mdb.get(`stats.${message.guild.id}.${message.author.id}`) || {messages: 0, channels: {}}; 18 | 19 | let voiceList = Object.keys(voiceData.channels).map(vd => { 20 | return { 21 | Id: vd, 22 | Total: voiceData.channels[vd] 23 | }; 24 | }).sort((a, b) => b.Total - a.Total); 25 | 26 | let messageList = Object.keys(messageData.channels).map(md => { 27 | return { 28 | Id: md, 29 | Total: messageData.channels[md] 30 | }; 31 | }).sort((a, b) => b.Total - a.Total); 32 | 33 | voiceList = voiceList.length > 10 ? voiceList.splice(0, 10) : voiceList; 34 | voiceList = voiceList.map((vd, index)=> `\`${index + 1}.\` ${client.channels.cache.has(vd.Id) ? client.channels.cache.get(vd.Id).toString() : "#deleted-channel"}: \`${moment.duration(vd.Total).format("H [hours,] m [minutes]")}\``).join("\n"); 35 | messageList = messageList.length > 10 ? messageList.splice(0, 10) : messageList; 36 | messageList = messageList.map((md, index)=> `\`${index + 1}.\` ${client.channels.cache.has(md.Id) ? client.channels.cache.get(md.Id).toString() : "#deleted-channel"}: \`${md.Total} message\``).join("\n"); 37 | let embed = new Discord.MessageEmbed(); 38 | embed.setColor(message.member.displayHexColor) 39 | .setFooter(`${message.author.tag} | Powered by Serendia Squad`) 40 | .setThumbnail(message.author.avatarURL({dynamic: true})) 41 | .addField("User Information",` 42 | 43 | \`ID:\` ${message.author.id} 44 | \`Roles:\` ${message.member.roles.cache.size >= 5 ? "Roles are too much..." : message.member.roles.cache.map(role => role.toString())} 45 | \`Nickname:\` ${message.member.displayName} 46 | `) 47 | .addField("Voice Activity", ` 48 | Last Activity: ${new Date(voiceData.activity).toLocaleDateString()} 49 | 50 | ** **${voiceList} 51 | `) 52 | .addField("Message Activity", ` 53 | Last Activity: ${new Date(messageData.activity).toLocaleDateString()} 54 | 55 | ** **${messageList} 56 | `); 57 | 58 | message.channel.send(embed); 59 | }; 60 | 61 | exports.conf = { 62 | commands: ["ben", "istatistik", "i", "me"], 63 | enabled: true, 64 | guildOnly: true 65 | }; 66 | 67 | exports.help = { 68 | name: 'Me', 69 | description: 'Provides information about your statistics on the server.', 70 | usage: '[p]me', 71 | kategori: 'User' 72 | }; 73 | --------------------------------------------------------------------------------