diff --git a/.gitignore b/.gitignore index 4c49bd7..a9e11d4 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ .env +node_modules +database.sqlite +package-lock.json \ No newline at end of file diff --git a/commands/addgame.js b/commands/addgame.js index f0cd187..dfe4c2e 100644 --- a/commands/addgame.js +++ b/commands/addgame.js @@ -1,45 +1,57 @@ -const fs = require ('fs'); +const fs = require('fs'); const fetch = require('node-fetch'); module.exports = { - name:'addgame', - aliases:['ag'], - usage:'', - description:'adds the game you want to see its runs to the gamelist', - async execute(message,args){ - if(message.guild&&!message.member.permissions.has("MANAGE_MESSAGES")) return message.reply('only staff can change game'); + name: 'addgame', + aliases: ['ag'], + usage: '', + description: 'adds the game you want to see its runs to the gamelist', + async execute(message, args, Guild, Game) { + if (message.guild && !message.member.permissions.has("MANAGE_MESSAGES")) return message.reply('only staff can change game'); const input = args.join('%20') const errormsg = "please input a valid name, abbreviation or id"; - const res = await fetch(`https://www.speedrun.com/api/v1/games/${input}`); + const res = await fetch(`https://www.speedrun.com/api/v1/games/${input}`); let json = await res.json(); - if(!json.data){ - + if (!json.data) { const ares = await fetch(`https://www.speedrun.com/api/v1/games?name=${input}`); - let ajson = await ares.json(); - if(!ajson.data) return message.reply(errormsg); - if(ajson.data[0]){ - json.data = ajson.data.find(x => x.names.international == args.join(" ")) - if(!json.data) return message.reply(errormsg); - } else { - json = ajson; + let ajson = await ares.json(); + if (!ajson.data) return message.reply(errormsg); + if (ajson.data[0]) { + json.data = ajson.data.find(x => x.names.international == args.join(" ")) + if (!json.data) return message.reply(errormsg); + } else { + json = ajson; - if(!json.data.id) return message.reply(errormsg); + if (!json.data.id) return message.reply(errormsg); + } } - } - const content = await fs.promises.readFile('./storage.json'); -const storageObject = JSON.parse(content); -let objtype = message.guild?message.guild.id:message.author.id; - -const obj = { id : json.data.id, name : json.data.names.international, url:json.data.assets['cover-large'].uri}; -const serverObject = storageObject[objtype]; - - if(serverObject && serverObject.find(x => x.id == json.data.id)) return message.reply('i can\'t add a game thats already in the list'); - - if (storageObject[objtype] instanceof Array) { - storageObject[objtype].push(obj); -} else { - storageObject[objtype] = [ obj ]; -} -await fs.promises.writeFile('./storage.json', JSON.stringify(storageObject)); + // const content = await fs.promises.readFile('./storage.json'); + // const storageObject = JSON.parse(content); + const [game,] = await Game.findOrCreate({ + where: { + id: json.data.id + }, + defaults: { + name: json.data.names.international, + url: json.data.assets['cover-large'].uri + } + }), + [guild,] = await Guild.findOrCreate({ + where: { + id: message.guild ? message.guild.id : message.author.id + }, + defaults: { + channel: message.guild && message.guild.channels.cache.find(x => x.name == "new-runs")?.id || null, + isUser: !message.guild + } + }), + isGameInGuild = await guild?.hasGame(game); + if (isGameInGuild) return message.reply('i can\'t add a game thats already in the list'); + await guild.addGame(game); + // if (storageObject[objtype] instanceof Array) { + // storageObject[objtype].push(obj); + // } else { + // storageObject[objtype] = [obj]; + // } message.channel.send(`the game ${json.data.names.international} has been successfully added to the runlist`); } -}; \ No newline at end of file +}; diff --git a/commands/addgames.js b/commands/addgames.js index 1ba667d..04aa4dd 100644 --- a/commands/addgames.js +++ b/commands/addgames.js @@ -5,24 +5,30 @@ module.exports = { aliases: ['ags'], usage: '', description: 'adds the games you want to see their runs to the gamelist', - async execute(message, args) { + async execute(message, args, Guild, Game) { if (message.guild && !message.member.permissions.has("MANAGE_MESSAGES")) return message.reply('only staff can change game'); let argz = args.join(' ').split('|'); - argz = argz.map(x => x.replace(' ', '%20')) - const content = await fs.promises.readFile('./storage.json'); - const storageObject = JSON.parse(content); - const objtype = message.guild ? message.guild.id : message.author.id + argz = argz.map(x => x.replace(' ', '%20')); + const [guild,] = await Guild.findOrCreate({ + where: { + id: message.guild ? message.guild.id : message.author.id + }, + defaults: { + channel: message.guild && message.guild.channels.cache.find(x => x.name == "new-runs")?.id || null, + isUser: !message.guild + } + }); + let games = []; for (const x of argz) { x = x.trim(); - const errormsg = `are you sure https://www.speedrun.com/${x} exists -||if it didn't work try deleting unnecessary spaces||`; + const errormsg = `are you sure https://www.speedrun.com/${x} exists\n||if it didn't work try deleting unnecessary spaces||`; const res = await fetch(`https://www.speedrun.com/api/v1/games/${x}`); let json = await res.json(); if (!json.data) { const ares = await fetch(`https://www.speedrun.com/api/v1/games?name=${x}`); let ajson = await ares.json(); if (!ajson.data) { - message.reply(eerrormsg); + message.reply(errormsg); continue; } if (ajson.data[0]) { @@ -39,23 +45,26 @@ module.exports = { } } } - const obj = { id: json.data.id, name: json.data.names.international, url: json.data.assets['cover-large'].uri }; - - if (storageObject[objtype] && - storageObject[objtype].find(y => y.id == json.data.id)) { + const [game,] = await Game.findOrCreate({ + where: { + id: json.data.id + }, + defaults: { + name: json.data.names.international, + url: json.data.assets['cover-large'].uri + } + }), + isGameInGuild = await guild?.hasGame(game); + if (isGameInGuild) { message.reply('i can\'t add a game thats already in the list'); continue; - } - - if (storageObject[objtype] instanceof Array) { - storageObject[objtype].push(obj); - } else { - storageObject[objtype] = [obj]; - } + } + games.push(game) message.channel.send(`the game ${json.data.names.international} has been successfully added to the runlist`); } - await fs.promises.writeFile('./storage.json', JSON.stringify(storageObject)); + await guild.addGames(games); + } }; diff --git a/commands/deletegame.js b/commands/deletegame.js index cea2236..98f9791 100644 --- a/commands/deletegame.js +++ b/commands/deletegame.js @@ -1,40 +1,44 @@ const fetch = require('node-fetch'); -const fs = require ('fs'); +const fs = require('fs'); module.exports = { - name:'deletegame', - aliases:['dg'], - usage:'', - description:'delete the game you don\'t want to see its runs from the gamelist', - async execute(message,args){ - if(message.guild&&!message.member.permissions.has("MANAGE_MESSAGES")) return message.reply('only staff can change game'); - const input = args.join("%20") - const res = await fetch(`https://www.speedrun.com/api/v1/games/${input}`); - let json = await res.json(); - if(!json.data){ - + name: 'deletegame', + aliases: ['dg'], + usage: '', + description: 'delete the game you don\'t want to see its runs from the gamelist', + async execute(message, args, Guild, Game) { + if (message.guild && !message.member.permissions.has("MANAGE_MESSAGES")) return message.reply('only staff can change game'); + const [guild,] = await Guild.findOrCreate({ + where: { + id: message.guild ? message.guild.id : message.author.id + }, + defaults: { + channel: message.guild && message.guild.channels.cache.find(x => x.name == "new-runs")?.id || null, + isUser: !message.guild + } + }); + const input = args.join("%20") + const res = await fetch(`https://www.speedrun.com/api/v1/games/${input}`); + let json = await res.json(); + if (!json.data) { const ares = await fetch(`https://www.speedrun.com/api/v1/games?name=${input}`); - let ajson = await ares.json(); - if(!ajson.data) return message.reply('please input a valid name, abbreviation or id'); - if(ajson.data[0]){ - json.data = ajson.data.find(x => x.names.international == args.join(" ")) - if(!json.data) return message.reply('please input a valid name, abbreviation or id'); - } else { - json = ajson; + let ajson = await ares.json(); + if (!ajson.data) return message.reply('please input a valid name, abbreviation or id'); + if (ajson.data[0]) { + json.data = ajson.data.find(x => x.names.international == args.join(" ")) + if (!json.data) return message.reply('please input a valid name, abbreviation or id'); + } else { + json = ajson; + } } - } -const objtype = message.guild?message.guild.id:message.author.id; -const content = await fs.promises.readFile('./storage.json'); -const storageObject = JSON.parse(content); -const list = storageObject[objtype] - - if(!list) return message.reply('i can\'t delete a game from a list thats empty'); - if(!list.find(x => x.id == json.data.id)) return message.reply('i can\'t delete a game thats not in the list') - storageObject[objtype] = list.filter(x => x.id != json.data.id) - -await fs.promises.writeFile('./storage.json', JSON.stringify(storageObject)); - - - + const game = await Game.findOne({ + where: { + id: json.data.id + } + }); + if(!game) return message.reply('i can\'t delete a game that never got added'); + const isGameInGuild = guild.hasGame(game); + if (!isGameInGuild) return message.reply('i can\'t delete a game thats not in the list'); + guild.removeGame(game) message.channel.send(`the game ${json.data.names.international} got successfully deleted`); } }; diff --git a/commands/deletegames.js b/commands/deletegames.js index 1b952a0..b5b0cb2 100644 --- a/commands/deletegames.js +++ b/commands/deletegames.js @@ -1,57 +1,66 @@ -const fs = require ('fs'); +const fs = require('fs'); const fetch = require('node-fetch'); module.exports = { - name:'deletegames', - aliases:['ags'], - usage:'', - description:'deletes the games you don\'t want to see their runs to the gamelist', - async execute(message,args){ - if(message.guild&&!message.member.permissions.has("MANAGE_MESSAGES")) return message.reply('only staff can change game'); - let argz = args.join(' ').split('|'); - argz = argz.map(x => x.replace(' ','%20')); - const content = await fs.promises.readFile('./storage.json'); -const storageObject = JSON.parse(content); -const objtype = message.guild?message.guild.id:message.author.id + name: 'deletegames', + aliases: ['ags'], + usage: '', + description: 'deletes the games you don\'t want to see their runs to the gamelist', + async execute(message, args, Guild, Game) { + if (message.guild && !message.member.permissions.has("MANAGE_MESSAGES")) return message.reply('only staff can change game'); + let argz = args.join(' ').split('|'), + games = []; + argz = argz.map(x => x.replace(' ', '%20')); + const [guild,] = await Guild.findOrCreate({ + where: { + id: message.guild ? message.guild.id : message.author.id + }, + defaults: { + channel: message.guild && message.guild.channels.cache.find(x => x.name == "new-runs")?.id || null, + isUser: !message.guild + } + }); - for(const x of argz){ - x = x.trim(); - const errormsg = `are you sure https://www.speedrun.com/${x} exists -||if it didn't work try deleting unnecessary spaces||`; + for (const x of argz) { + x = x.trim(); + const errormsg = `are you sure https://www.speedrun.com/${x} exists\n||if it didn't work try deleting unnecessary spaces||`; const res = await fetch(`https://www.speedrun.com/api/v1/games/${x}`); - let json = await res.json(); - if(!json.data){ - - const ares = await fetch(`https://www.speedrun.com/api/v1/games?name=${x}`); - let ajson = await ares.json(); - if(!ajson.data){ -message.reply(errormsg) -continue; -} - if(ajson.data[0]){ - json.data = ajson.data.find(y => y.names.international == x.replace('%20',' ')); - if(!json.data){ -message.reply(errormsg); -continue; -} - } else { - json = ajson; - } - } -const list = storageObject[objtype]; + let json = await res.json(); + if (!json.data) { - if(!list){ -message.reply('i can\'t delete a game from a list thats empty'); -continue; -} - if(!list.find(y => y.id == json.data.id)){ -message.reply('i can\'t delete a game thats not in the list'); -continue; -} - storageObject[objtype] = list.filter(y => y.id != json.data.id) - - message.channel.send(`the game ${json.data.names.international} got successfully deleted`); - } - await fs.promises.writeFile('./storage.json', JSON.stringify(storageObject)); + const ares = await fetch(`https://www.speedrun.com/api/v1/games?name=${x}`); + let ajson = await ares.json(); + if (!ajson.data) { + message.reply(errormsg) + continue; + } + if (ajson.data[0]) { + json.data = ajson.data.find(y => y.names.international == x.replace('%20', ' ')); + if (!json.data) { + message.reply(errormsg); + continue; + } + } else { + json = ajson; + } + } + const game = await Game.findOne({ + where: { + id: json.data.id + } + }); - } + if (!game) { + message.reply('i can\'t delete a game that never got added'); + continue; + } + const isGameInGuild = guild.hasGame(game); + if (!isGameInGuild) { + message.reply('i can\'t delete a game thats not in the list'); + continue; + } + games.push(game) + message.channel.send(`the game ${json.data.names.international} got successfully deleted`); + } + await guild.removeGames(games); + } }; diff --git a/commands/gamelist.js b/commands/gamelist.js index 58feff2..55055cd 100644 --- a/commands/gamelist.js +++ b/commands/gamelist.js @@ -1,40 +1,49 @@ -const fs = require ('fs'); -const fetch = require ('node-fetch'); +const fs = require('fs'); +const fetch = require('node-fetch'); module.exports = { - name:'gamelist', - description:'displays the list of games that its runs will be sent', - async execute(message){ - const objtype = message.guild?message.guild.id:message.author.id; - - function underify(array) { - if(`•${array.join('\n•')}`.length <= 2048)return [array]; - let total = [], secondarray = []; - while(`•${array.join('\n•')}`.length > 2048) secondarray.push(array.shift()); - - if('•'+secondarray.join('\n•').length <= 2048){ - total.push(secondarray); - } else { - total.push(...underify(secondarray)); - } - total.push(array); - return total; + name: 'gamelist', + description: 'displays the list of games that its runs will be sent', + async execute(message, args, Guild) { + const objtype = message.guild ? message.guild.id : message.author.id; - } - const content = await fs.promises.readFile('./storage.json'); -const storageObject = JSON.parse(content); -const list = storageObject[objtype]; - - - if(!list||!list.length) return message.reply('the gamelist is currently empty add games to it using .addgame'); - const sorted = list.map(x => x.name).sort(); - - let lists = underify(sorted); - - lists.forEach(x=>{ - message.channel.send({embed:{title:`${message.guild?message.guild.name:message.author.username}'s gamelist`, - color:'RANDOM', - description: '•'+x.join('\n•') - }}); + function underify(array) { + if (`•${array.join('\n•')}`.length <= 2048) return [array]; + let total = [], secondarray = []; + while (`•${array.join('\n•')}`.length > 2048) secondarray.push(array.shift()); + + if ('•' + secondarray.join('\n•').length <= 2048) { + total.push(secondarray); + } else { + total.push(...underify(secondarray)); + } + total.push(array); + return total; + + } + const [guild,] = await Guild.findOrCreate({ + where: { + id: message.guild ? message.guild.id : message.author.id + }, + defaults: { + channel: message.guild && message.guild.channels.cache.find(x => x.name == "new-runs")?.id || null, + isUser: !message.guild + } + }); + const list = await guild.getGames(); + + + if (!list.length) return message.reply('the gamelist is currently empty add games to it using .addgame'); + const sorted = list.map(x => x.name).sort(), + lists = underify(sorted); + + lists.forEach(x => { + message.channel.send({ + embed: { + title: `${message.guild ? message.guild.name : message.author.username}'s gamelist`, + color: 'RANDOM', + description: '•' + x.join('\n•') + } + }); }); } }; diff --git a/commands/help.js b/commands/help.js index 363bf30..bba99af 100644 --- a/commands/help.js +++ b/commands/help.js @@ -9,10 +9,10 @@ module.exports = { aliases: ['h'], - execute: async (message, args, client, prefix) => { + execute: async (message, args, _Guild, _Game, prefix) => { if (!args.length) { - const cmds = client.commands.map(x => `• ${x.name} ${x.usage?`\`${x.usage}\``:''}: ${x.description}`).join('\n\n') + const cmds = message.client.commands.map(x => `• ${x.name} ${x.usage?`\`${x.usage}\``:''}: ${x.description}`).join('\n\n') diff --git a/commands/setchannel.js b/commands/setchannel.js index 83b6f25..2cf1e23 100644 --- a/commands/setchannel.js +++ b/commands/setchannel.js @@ -1,20 +1,23 @@ const fs = require("fs"); module.exports ={ name:"setchannel", - async execute(message,args){ - if(!message.guild) return message.reply("there's no channels in dms idot"); + async execute(message, args, Guild){ + if(!message.guild) return message.reply("there's no channels in dms idiot"); if(!message.member.permissions.has("MANAGE_MESSAGES")) return message.reply('only staff can change game'); const channel = (message.mentions.channels.first() || message.guild.channels.cache.get(args[0]) || message.channel).id; - const {id} = message.guild; - const content = await fs.promises.readFile('./storage.json'); - const storageObject = JSON.parse(content); - const firstel = storageObject[id][0]; - if(firstel && firstel.channel) storageObject[id][0].channel = channel.id; - else storageObject[objtype].unshift({channel}); - await fs.promises.writeFile( - './storage.json', - JSON.stringify(storageObject) - ); - message.channel.send(`Successfully set channel to ${channel}`); + const [guild, created] = await Guild.findOrCreate({ + where: { + id: message.guild ? message.guild.id : message.author.id + }, + defaults: { + channel, + isUser: false + } + }); + if(!created) { + guild.channel = channel; + await guild.save(); + } + message.channel.send(`Successfully set channel to ${channel}`); } }; \ No newline at end of file diff --git a/duration.js b/duration.js index a53d5ed..7509bbb 100644 --- a/duration.js +++ b/duration.js @@ -1,10 +1,11 @@ module.exports = (time,time2) =>{ if(!time || !time2) return; - const duration = time - time2; - let hours = Math.floor(duration/360000); - - let minutes = Math.floor((duration % 360000)/60000); - let seconds = ((duration % 60000)/1000).toFixed(2); + + let duration = time - time2, + hours = Math.floor(duration/360000), + minutes = Math.floor((duration % 360000)/60000), + seconds = ((duration % 60000)/1000).toFixed(2); + minutes = minutes ? minutes + ' minutes and ' : ''; hours = hours ? hours + ' hours, ' : ''; diff --git a/events/guildDelete.js b/events/guildDelete.js index df2fa1b..8e11fd7 100644 --- a/events/guildDelete.js +++ b/events/guildDelete.js @@ -1,14 +1,11 @@ -const fs = require ('fs'); +const fs = require('fs'); module.exports = { - async run(guild){ - const content = await fs.promises.readFile('./storage.json'); -const storageObject = JSON.parse(content); - - - if(!storageObject[guild.id]) return; - -delete storageObject[guild.id]; - -await fs.promises.writeFile('./storage.json', JSON.stringify(storageObject)); + async run(guild, Guild) { + const leavingGuild = await Guild.findOne({ + where: { + id: guild.id + } + }); + if(leavingGuild) await leavingGuild.destroy(); } } \ No newline at end of file diff --git a/events/message.js b/events/message.js index 3e1f9b8..56558d9 100644 --- a/events/message.js +++ b/events/message.js @@ -1,49 +1,41 @@ -const Discord = require ('discord.js'); +const Discord = require('discord.js'); const escapeRegex = str => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const duration = require('../duration.js'); -module.exports ={ - name:'message', - run:(message,client,prefix) => { - - - client.spaceCommands.array().forEach(cmd => { - if(cmd.guildOnly && !message.guild) return; - cmd.execute(message); - }); - -const prefixRegex = new RegExp(`^(<@!?${client.user.id}>|${escapeRegex(prefix.toLowerCase())})\\s*`); - if (!prefixRegex.test(message.content.toLowerCase())) return; - - const [, matchedPrefix] = message.content.toLowerCase().match(prefixRegex); - const args = message.content.slice(matchedPrefix.length).trim().split(/ +/); -const command = args.shift().toLowerCase(); - - - const cmd = client.commands.get(command) || client.commands.find(x => x.aliases && x.aliases.includes(command)) - if (!cmd) return; - if (!client.cooldowns.has(cmd.name)) { - client.cooldowns.set(cmd.name, new Discord.Collection()); -} -const now = Date.now(); -const timestamps = client.cooldowns.get(cmd.name); -const cooldownAmount = (cmd.cooldown || 3) * 1000; - -if (timestamps.has(message.author.id)) { - const expirationTime = timestamps.get(message.author.id) + cooldownAmount; - - if (now < expirationTime) { - return message.reply(`please wait ${duration(expirationTime,now)} before reusing the \`${cmd.name}\` command.`).then(msg => msg.delete({timeout:5000})); +module.exports = { + name: 'message', + run: (message, Guild, Game, prefix, client) => { + const prefixRegex = new RegExp(`^(<@!?${client.user.id}>|${escapeRegex(prefix.toLowerCase())})\\s*`); + if (!prefixRegex.test(message.content.toLowerCase())) return; + + const [, matchedPrefix] = message.content.toLowerCase().match(prefixRegex); + const args = message.content.slice(matchedPrefix.length).trim().split(/ +/); + const command = args.shift().toLowerCase(); + + + const cmd = client.commands.get(command) || client.commands.find(x => x.aliases && x.aliases.includes(command)) + if (!cmd) return; + if (!client.cooldowns.has(cmd.name)) { + client.cooldowns.set(cmd.name, new Discord.Collection()); + } + const now = Date.now(); + const timestamps = client.cooldowns.get(cmd.name); + const cooldownAmount = (cmd.cooldown || 3) * 1000; + + if (timestamps.has(message.author.id)) { + const expirationTime = timestamps.get(message.author.id) + cooldownAmount; + + if (now < expirationTime) { + return message.reply(`please wait ${duration(expirationTime, now)} before reusing the \`${cmd.name}\` command.`).then(msg => msg.delete({ timeout: 5000 })); + } + } + cmd.execute(message, args, Guild, Game, prefix); + timestamps.set(message.author.id, now); + + setTimeout(() => timestamps.delete(message.author.id), cooldownAmount); } -} -if(client.commands.get(cmd.name).guildOnly && !message.guild) return; - cmd.execute(message, args, client, prefix, queue); - timestamps.set(message.author.id, now); - setTimeout(() => timestamps.delete(message.author.id), cooldownAmount); - } - - - - }; \ No newline at end of file + + +}; \ No newline at end of file diff --git a/events/ready.js b/events/ready.js index 5461238..9d5ffae 100644 --- a/events/ready.js +++ b/events/ready.js @@ -1,121 +1,132 @@ -const { Collection, MessageEmbed } = require('discord.js'); -const fs = require('fs'); -const fetch = require('node-fetch'); +const { channel } = require('diagnostics_channel'); +const { Collection, MessageEmbed } = require('discord.js'), + fs = require('fs'), + { Op } = require("sequelize"), + fetch = require('node-fetch'); module.exports = { -ㅤname: 'ready', -ㅤasync run(client, prefix) { -ㅤㅤconsole.log('bot ready') -ㅤㅤclient.user.setActivity('SpeedrunsLive', { type: 'COMPETING' }); -ㅤㅤlet er = new Collection(); -ㅤㅤsetInterval(async () => { -ㅤㅤㅤconst content = await fs.promises.readFile('./storage.json'); -ㅤㅤㅤconst storageObject = JSON.parse(content); -ㅤㅤㅤconst runs = await fetch('https://www.speedrun.com/api/v1/runs?status=verified&orderby=verify-date&direction=desc').catch(); -ㅤㅤㅤconst runsjson = await runs.json(); -ㅤㅤㅤconst runsdata = runsjson.data; -ㅤㅤㅤ//fetching newly verified runs -ㅤㅤㅤrunsdata.forEach(run => client.runs.set(run.id, run)) -ㅤㅤㅤ//adding runs to client.runs collection -ㅤㅤㅤclient.runs = client.runs.filter(x => runsdata.find(z => x.id == z.id)); -ㅤㅤㅤ// deleting old unnecessary runs from client.runs -ㅤㅤㅤconst newruns = client.runs.filter(x => !er.has(x.id)); -ㅤㅤㅤ//filter the runs that existing runs collection doesn't have -ㅤㅤㅤif (newruns.first()) { -ㅤㅤㅤㅤnewruns.forEach(async newrun => { -ㅤㅤㅤㅤㅤlet level = '', -ㅤㅤㅤㅤㅤㅤlvlid, top = 'N/A', -ㅤㅤㅤㅤㅤㅤgame, cover, index, cache, guildid, user = ''; -ㅤㅤㅤㅤㅤconst guildarr = Object.entries(storageObject).find(x => x[1].find(y => y.id == newrun.game)); -ㅤㅤㅤㅤㅤif (guildarr) { -ㅤㅤㅤㅤㅤㅤguildid = guildarr[0]; -ㅤㅤㅤㅤㅤㅤindex = guildarr[1].findIndex(x => x.id == newrun.game); -ㅤㅤㅤㅤㅤㅤcache = guildarr[1][index]; -ㅤㅤㅤㅤㅤㅤgame = cache.name; -ㅤㅤㅤㅤㅤㅤif (cache.url) -ㅤㅤㅤㅤㅤㅤㅤcover = cache.url; -ㅤㅤㅤㅤㅤㅤelse { -ㅤㅤㅤㅤㅤㅤㅤconst gameres = await fetch(`https://speedrun.com/api/v1/games/${newrun.game}`); -ㅤㅤㅤㅤㅤㅤㅤconst gamejson = await gameres.json(); -ㅤㅤㅤㅤㅤㅤㅤ//fetching game data -ㅤㅤㅤㅤㅤㅤㅤcover = gamejson.data.assets['cover-large'].uri; -ㅤㅤㅤㅤㅤㅤㅤif (cache) { -ㅤㅤㅤㅤㅤㅤㅤㅤstorageObject[guildid][index].url = cover; -ㅤㅤㅤㅤㅤㅤㅤㅤawait fs.promises.writeFile('./storage.json', JSON.stringify(storageObject)); -ㅤㅤㅤㅤㅤㅤㅤ} -ㅤㅤㅤㅤㅤㅤ} -ㅤㅤㅤㅤㅤ} -ㅤㅤㅤㅤㅤfor (let player of newrun.players) { -ㅤㅤㅤㅤㅤㅤconst userres = await fetch(`https://speedrun.com/api/v1/users/${player.id}`).catch(); -ㅤㅤㅤㅤㅤㅤconst userjson = await userres.json(); -ㅤㅤㅤㅤㅤㅤconst i = newrun.players.findIndex(x => x.id == player.id) -ㅤㅤㅤㅤㅤㅤuser += (user ? i == newruns.players.length - 1 ? ' and ' : ', ' : '') + userjson.data.names.international + name: 'ready', + once: true, + async run(Guild, Game, prefix, client) { + console.log('bot ready') + client.user.setActivity('SpeedrunsLive', { type: 'COMPETING' }); + let er = new Collection(); + setInterval(async () => { + const content = await fs.promises.readFile('./storage.json'); + const storageObject = JSON.parse(content); + const runs = await fetch('https://www.speedrun.com/api/v1/runs?status=verified&orderby=verify-date&direction=desc').catch(); + const runsjson = await runs.json(); + const runsdata = runsjson.data; + //fetching newly verified runs + runsdata.forEach(run => client.runs.set(run.id, run)) + //adding runs to client.runs collection + client.runs = client.runs.filter(x => runsdata.find(z => x.id == z.id)); + // deleting old unnecessary runs from client.runs + const newruns = client.runs.filter(x => !er.has(x.id)); + //filter the runs that existing runs collection doesn't have + if (newruns.first()) { + newruns.forEach(async newrun => { + let level = '', + lvlid, top = 'N/A', + game, cover, user = ''; + // const guildarr = Object.entries(storageObject).find(x => x[1].find(y => y.id == newrun.game)); + const gameObj = await Game.findOne({ + where: { + id: newrun.game + } + }) + if (!gameObj) return; + // guildid = guildarr[0]; + // index = guildarr[1].findIndex(x => x.id == newrun.game); + // cache = guildarr[1][index]; + game = gameObj.id; + if (gameObj.url) cover = gameObj.url; + else { + const gameres = await fetch(`https://speedrun.com/api/v1/games/${newrun.game}`); + const gamejson = await gameres.json(); + //fetching game data + cover = gameObj.url = gamejson.data.assets['cover-large'].uri; + await gameObj.save(); + } + for (let player of newrun.players) { + const userres = await fetch(`https://speedrun.com/api/v1/users/${player.id}`).catch(); + const userjson = await userres.json(); + const i = newrun.players.findIndex(x => x.id == player.id) + user += (user ? i == newruns.players.length - 1 ? ' and ' : ', ' : '') + userjson.data.names.international -ㅤㅤㅤㅤㅤ} -ㅤㅤㅤㅤㅤ// fetching user data + } + // fetching user data -ㅤㅤㅤㅤㅤconst categoryres = await fetch(`https://speedrun.com/api/v1/categories/${newrun.category}`).catch(); -ㅤㅤㅤㅤㅤconst categoryjson = await categoryres.json(); -ㅤㅤㅤㅤㅤconst category = categoryjson.data.name; -ㅤㅤㅤㅤㅤ// fetching category data -ㅤㅤㅤㅤㅤconst variablesres = await fetch(`https://speedrun.com/api/v1/categories/${newrun.category}/variables`).catch(); -ㅤㅤㅤㅤㅤconst variablesjson = await variablesres.json(); + const categoryres = await fetch(`https://speedrun.com/api/v1/categories/${newrun.category}`).catch(); + const categoryjson = await categoryres.json(); + const category = categoryjson.data.name; + // fetching category data + const variablesres = await fetch(`https://speedrun.com/api/v1/categories/${newrun.category}/variables`).catch(); + const variablesjson = await variablesres.json(); -ㅤㅤㅤㅤㅤconst runVariables = Object.entries(newrun.values); -ㅤㅤㅤㅤㅤlet subcategoryName = '', -ㅤㅤㅤㅤㅤㅤsubcategoryQuery = ''; -ㅤㅤㅤㅤㅤrunVariables.forEach(v => { -ㅤㅤㅤㅤㅤㅤconst foundVariable = variablesjson.data.find(c => c.id === v[0]); -ㅤㅤㅤㅤㅤㅤif (foundVariable['is-subcategory'] === true) { -ㅤㅤㅤㅤㅤㅤㅤsubcategoryName += !subcategoryName ? foundVariable.values.values[v[1]].label : ', ' + foundVariable.values.values[v[1]].label; -ㅤㅤㅤㅤㅤㅤㅤsubcategoryQuery += !subcategoryQuery ? '?var-' + v[0] + '=' + v[1] : '&var-' + v[0] + '=' + v[1]; + const runVariables = Object.entries(newrun.values); + let subcategoryName = '', + subcategoryQuery = ''; + runVariables.forEach(v => { + const foundVariable = variablesjson.data.find(c => c.id === v[0]); + if (foundVariable['is-subcategory'] === true) { + subcategoryName += !subcategoryName ? foundVariable.values.values[v[1]].label : ', ' + foundVariable.values.values[v[1]].label; + subcategoryQuery += !subcategoryQuery ? '?var-' + v[0] + '=' + v[1] : '&var-' + v[0] + '=' + v[1]; -ㅤㅤㅤㅤㅤㅤ} -ㅤㅤㅤㅤㅤ}); -ㅤㅤㅤㅤㅤ// fetching subcategory data if found -ㅤㅤㅤㅤㅤif (newrun.level) { -ㅤㅤㅤㅤㅤㅤconst lvlres = await fetch(`https://speedrun.com/api/v1/levels/${newrun.level}`); -ㅤㅤㅤㅤㅤㅤconst lvljson = await lvlres.json(); -ㅤㅤㅤㅤㅤㅤlevel = lvljson.data.name -ㅤㅤㅤㅤㅤㅤlvlid = lvljson.data.id -ㅤㅤㅤㅤㅤㅤ//fetching level data if found -ㅤㅤㅤㅤㅤ} -ㅤㅤㅤㅤㅤconst leaderres = await fetch(`https://speedrun.com/api/v1/leaderboards/${newrun.game}/${level?`level/${lvlid}`:'category'}/${categoryjson.data.id}${subcategoryQuery}`); -ㅤㅤㅤㅤㅤconst leaderjson = await leaderres.json(); -ㅤㅤㅤㅤㅤconst topobj = leaderjson.data.runs.find(rundata => rundata.run.id == newrun.id); -ㅤㅤㅤㅤㅤif (topobj) top = topobj.place; -ㅤㅤㅤㅤㅤ// fetching place in leaderboards -ㅤㅤㅤㅤㅤconst embed = new MessageEmbed() -ㅤㅤㅤㅤㅤㅤ.setTitle(`${game}:${level} ${category} ${subcategoryName}`) -ㅤㅤㅤㅤㅤㅤ.setColor('RANDOM') -ㅤㅤㅤㅤㅤㅤ.setDescription(`**${newrun.times.primary.replace('PT','').replace('H',' hours ').replace('M',' minutes ').replace('S',' seconds')} by ${user}**`) -ㅤㅤㅤㅤㅤㅤ.setURL(newrun.weblink) -ㅤㅤㅤㅤㅤㅤ.addField('Verified at:', '`' + newrun.status['verify-date'].replace('T', ' ').replace('Z', '') + '`', true) -ㅤㅤㅤㅤㅤㅤ.setThumbnail(cover) -ㅤㅤㅤㅤㅤㅤ.addField('Place in leaderboards', top, true); -ㅤㅤㅤㅤㅤ// constructing the run embed -ㅤㅤㅤㅤㅤclient.guilds.cache.forEach(g => { -ㅤㅤㅤㅤㅤㅤconst dbgame = storageObject[g.id]; -ㅤㅤㅤㅤㅤㅤif (dbgame && dbgame[0]) { -ㅤㅤㅤㅤㅤㅤㅤconst channel = g.channels.cache.find(c => dbgame[0].channel == c.id); -ㅤㅤㅤㅤㅤㅤㅤif (channel && dbgame.find(x => x.id == newrun.game)) channel.send(embed); -ㅤㅤㅤㅤㅤㅤㅤ// sending the embed -ㅤㅤㅤㅤㅤㅤ} -ㅤㅤㅤㅤㅤ}); + } + }); + // fetching subcategory data if found + if (newrun.level) { + const lvlres = await fetch(`https://speedrun.com/api/v1/levels/${newrun.level}`); + const lvljson = await lvlres.json(); + level = lvljson.data.name + lvlid = lvljson.data.id + // fetching level data if found + } + const leaderres = await fetch(`https://speedrun.com/api/v1/leaderboards/${newrun.game}/${level ? `level/${lvlid}` : 'category'}/${categoryjson.data.id}${subcategoryQuery}`); + const leaderjson = await leaderres.json(); + const topobj = leaderjson.data.runs.find(rundata => rundata.run.id == newrun.id); + if (topobj) top = topobj.place; + // fetching place in leaderboards + const embed = new MessageEmbed() + .setTitle(`${game}:${level} ${category} ${subcategoryName}`) + .setColor('RANDOM') + .setDescription(`**${newrun.times.primary.replace('PT', '').replace('H', ' hours ').replace('M', ' minutes ').replace('S', ' seconds')} by ${user}**`) + .setURL(newrun.weblink) + .addField('Verified at:', '`' + newrun.status['verify-date'].replace('T', ' ').replace('Z', '') + '`', true) + .setThumbnail(cover) + .addField('Place in leaderboards', top, true); + // constructing the run embed + const guilds = await Guild.findAll({ + where : { + channel: { + [Op.not]: null + } + } + }); + for (let guild of guilds) { + const isGameInGuild = await guild.hasGame(game); + if(!isGameInGuild) continue; + if(guild.isUser) client.users.cache.get(guild.id).send(embed); + else if(guild.channel) client.channels.cache.get(guild.channel).send(embed) -ㅤㅤㅤㅤㅤclient.users.cache.forEach(u => { -ㅤㅤㅤㅤㅤㅤconst dbgame = storageObject[u.id]; -ㅤㅤㅤㅤㅤㅤif (dbgame && dbgame[0] && dbgame.find(x => x.id == newrun.game)) u.send(embed); -ㅤㅤㅤㅤㅤㅤ// sending the embed -ㅤㅤㅤㅤㅤ}); + } + // client.guilds.cache.forEach(g => { + // const dbgame = storageObject[g.id]; + // if (dbgame && dbgame[0]) { + // const channel = g.channels.cache.find(c => dbgame[0].channel == c.id); + // if (channel && dbgame.find(x => x.id == newrun.game)) channel.send(embed); + // // sending the embed + // } + // }); -ㅤㅤㅤㅤ}) -ㅤㅤㅤ} -ㅤㅤㅤclient.runs.forEach(run => er.set(run.id, run)); -ㅤㅤㅤ//setting new runs to existing to not get detected as new next time + }) + } + client.runs.forEach(run => er.set(run.id, run)); + //setting new runs to existing to not get detected as new next time -ㅤㅤㅤer = er.filter(x => client.runs.has(x.id)); -ㅤㅤㅤ// deleting unnecessary old runs -ㅤㅤ}, 60000); -ㅤㅤ// using setInterval to repeat the process every minute -ㅤ} + er = er.filter(x => client.runs.has(x.id)); + // deleting unnecessary old runs + }, 60000); + // using setInterval to repeat the process every minute + } } \ No newline at end of file diff --git a/index.js b/index.js index fd5bf66..6804699 100644 --- a/index.js +++ b/index.js @@ -1,26 +1,58 @@ -require('dotenv').config +// initialise .env config. replit users don't need this +require('dotenv').config(); +// import required libraries const fs = require('fs'), - Discord = require('discord.js'), - path = require('path'), - {TOKEN, PREFIX} = process, - client = new Discord.Client({ + {Client, Collection} = require('discord.js'), + {TOKEN, PREFIX} = process.env, + Sequelize = require("sequelize"), + // let sequelise access the database + sequelize = new Sequelize({ + dialect: 'sqlite', + storage: 'database.sqlite' + }), + // define the client for our discord bot + client = new Client({ ws: { properties: { $browser: 'Discord Android' } } + }), + // define the schemas for our tables + Guild = sequelize.define("guild",{ + id: { + type: Sequelize.STRING, + primaryKey: true, + }, + channel: { + type: Sequelize.STRING, + allowNull: true + }, + isUser: Sequelize.BOOLEAN + }), + Game = sequelize.define("game",{ + id: { + type: Sequelize.STRING, + primaryKey: true, + }, + name: Sequelize.STRING, + url: { + type: Sequelize.STRING, + allowNull: true + } }); -client.cooldowns = new Discord.Collection(); -client.events = new Discord.Collection(); -client.commands = new Discord.Collection(); + // makes the tables if they don't exist, does nothing otherwise. + Guild.belongsToMany(Game, { through: "GuildGames" } ); + Game.belongsToMany(Guild, { through: "GuildGames" }); + sequelize.sync(); + ['cooldowns','events','commands','runs'].forEach(x => client[x] = new Collection()); fs.readdir('./events/', (err, files) => { // We use the method readdir to read what is in the events folder if (err) return console.error(err); // If there is an error during the process to read all contents of the ./events folder, throw an error in the console files.forEach(file => { - const module = require(`./events/${file}`), - { emitter } = module; + const module = require(`./events/${file}`); // Try catch block to throw an error if the code in try{} doesn't work try { - ((emitter instanceof String ? client[emitter] : emitter) || client)[module.once ? "once" : "on"](file.split('.')[0], (...args) => module.run(...args, client, PREFIX)); // Run the event using the above defined emitter (or client) + client[module.once ? "once" : "on"](file.split('.')[0], (...args) => module.run(...args, Guild, Game, PREFIX, client)); // Run the event using the client } catch (error) { console.error(error.stack); // If there is an error, console log the error stack message } @@ -33,6 +65,7 @@ for (const file of commandFiles) { const command = require(`./commands/${file}`); client.commands.set(command.name, command); } +// for replit users, uncomment this! /* const https = require('https'); require("express")().all('/', (req, res) => { @@ -53,4 +86,5 @@ for (const file of commandFiles) { },17e5); server.listen(3000, () => console.log("Server is Ready!")); */ +// connect to the bot using our token client.login(TOKEN); \ No newline at end of file diff --git a/package.json b/package.json index 7562a5a..0963bcd 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "RunGet", + "name": "runget", "version": "1.0.0", "description": "", "main": "index.js", @@ -7,14 +7,13 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], - "author": "", + "author": "NoobJsPerson", "license": "ISC", "dependencies": { "discord.js": "^12.5.1", "dotenv": "10.0.0", - "express": "^4.17.1", "node-fetch": "^2.6.1", "sequelize": "^6.21.3", - "sqlite": "^4.1.2" + "sqlite3": "^5.0.10" } }