From 056bfc384534c243293129651c674b92b8f5e289 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sun, 3 Mar 2019 18:04:58 +0100 Subject: [PATCH 01/54] Updated package.json version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5fc1570..56a023f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discordbot", - "version": "0.10.1", + "version": "0.11.0", "scripts": { "start": "node bot.js", "test": "mocha --exit", From 2a2386e5451a03d61c2e4ad0d6981c8870c7fd42 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sun, 3 Mar 2019 18:22:45 +0100 Subject: [PATCH 02/54] Added name changes to changelog --- CHANGELOG.md | 10 +++++++--- lib/commands/MiscCommands/index.js | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63a5a6c..d58a7ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,11 @@ All notable changes to the discord bot will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.11.0] - 2019-03-03 +## [Unreleased] +### Changed +- name of MiscCommands module from `TemplateCommandModule` to `MiscoCommandModule` + +## [0.11.0-beta] - 2019-03-03 ### Changed - template Files to name `template.yaml` - loading template file form CommandModule property `templateFile` to loading the `template.yaml` file from the `_templateDir` property (still supporting loading form templateFile) @@ -19,12 +23,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed - `ExtendedRichEmbed.addNonemptyField` because the overide of `.addField` does the same -## [0.10.1] - 2019-03-03 +## [0.10.1]-beta - 2019-03-03 ### Changed - Bugfix on RichEmbed not returning itself on addField and setDescription because of method overide - AniList CommandModule bug fix on `~alCharacter` not returning voice actor names -## [0.10.0] - 2019-03-03 +## [0.10.0-beta] - 2019-03-03 ### Added - AniList api commands powered by [AniList.co](https://www.anilist.co) - MessageHandler - handles all incoming messages, parses the syntax, executes the syntax and handles rate limits diff --git a/lib/commands/MiscCommands/index.js b/lib/commands/MiscCommands/index.js index 8cfc852..b7e724b 100644 --- a/lib/commands/MiscCommands/index.js +++ b/lib/commands/MiscCommands/index.js @@ -15,7 +15,7 @@ function delay(seconds) { }); } -class TemplateCommandModule extends cmdLib.CommandModule { +class MiscCommandModule extends cmdLib.CommandModule { constructor() { super(cmdLib.CommandScopes.Global); @@ -78,5 +78,5 @@ class TemplateCommandModule extends cmdLib.CommandModule { Object.assign(exports, { - module: TemplateCommandModule + module: MiscCommandModule }); From 540ab231dbefd2a500c00f01368704888790ee99 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Mon, 4 Mar 2019 21:19:03 +0100 Subject: [PATCH 03/54] Changes to structure and fixes - added folders for every lib - moved lib files to folders and renamed them to `index.js` - moved commands outside of lib - moved graphql schema to web - removed lib graphql folder - added graphql folder to AniListApi folder - fixed bug on ~skip and ~stop - fixed bug on ExtendedRichEmbed - fixed bug on RichCharacterInfo --- CHANGELOG.md | 7 ++++ bot.js | 16 ++++---- {lib/commands => commands}/.template/index.js | 2 +- .../.template/template.yaml | 0 .../AnilistApiCommands/index.js | 6 +-- .../AnilistApiCommands/template.yaml | 0 .../InfoCommands/index.js | 4 +- .../InfoCommands/template.yaml | 0 .../MiscCommands/index.js | 2 +- .../MiscCommands/template.yaml | 0 .../MusicCommands/index.js | 10 ++--- .../MusicCommands/template.yaml | 0 .../ServerUtilityCommands/index.js | 2 +- .../ServerUtilityCommands/template.yaml | 0 .../UtilityCommands/index.js | 2 +- .../UtilityCommands/template.yaml | 0 lib/{CommandLib.js => CommandLib/index.js} | 40 ++++++++++--------- lib/{MessageLib.js => MessageLib/index.js} | 6 +-- lib/{MusicLib.js => MusicLib/index.js} | 6 +-- lib/StateLib/index.js | 26 ++++++++++++ lib/{WebLib.js => WebLib/index.js} | 8 ++-- .../graphql}/AnimeQuery.gql | 0 .../graphql}/CharacterQuery.gql | 0 .../graphql}/Fragments.yaml | 0 .../graphql}/MangaQuery.gql | 0 .../graphql}/StaffQuery.gql | 0 .../{AnilistApi.js => AniListApi/index.js} | 0 lib/guilding.js | 4 +- lib/{utils.js => utils/index.js} | 0 lib/{ => utils}/logging.js | 0 lib/{ => utils}/sqliteAsync.js | 0 test/test.js | 4 +- {lib => web}/api/graphql/schema.gql | 0 33 files changed, 91 insertions(+), 54 deletions(-) rename {lib/commands => commands}/.template/index.js (95%) rename {lib/commands => commands}/.template/template.yaml (100%) rename {lib/commands => commands}/AnilistApiCommands/index.js (98%) rename {lib/commands => commands}/AnilistApiCommands/template.yaml (100%) rename {lib/commands => commands}/InfoCommands/index.js (98%) rename {lib/commands => commands}/InfoCommands/template.yaml (100%) rename {lib/commands => commands}/MiscCommands/index.js (98%) rename {lib/commands => commands}/MiscCommands/template.yaml (100%) rename {lib/commands => commands}/MusicCommands/index.js (97%) rename {lib/commands => commands}/MusicCommands/template.yaml (100%) rename {lib/commands => commands}/ServerUtilityCommands/index.js (99%) rename {lib/commands => commands}/ServerUtilityCommands/template.yaml (100%) rename {lib/commands => commands}/UtilityCommands/index.js (98%) rename {lib/commands => commands}/UtilityCommands/template.yaml (100%) rename lib/{CommandLib.js => CommandLib/index.js} (88%) rename lib/{MessageLib.js => MessageLib/index.js} (98%) rename lib/{MusicLib.js => MusicLib/index.js} (98%) create mode 100644 lib/StateLib/index.js rename lib/{WebLib.js => WebLib/index.js} (98%) rename lib/api/{graphql/AnilistApi => AniListApi/graphql}/AnimeQuery.gql (100%) rename lib/api/{graphql/AnilistApi => AniListApi/graphql}/CharacterQuery.gql (100%) rename lib/api/{graphql/AnilistApi => AniListApi/graphql}/Fragments.yaml (100%) rename lib/api/{graphql/AnilistApi => AniListApi/graphql}/MangaQuery.gql (100%) rename lib/api/{graphql/AnilistApi => AniListApi/graphql}/StaffQuery.gql (100%) rename lib/api/{AnilistApi.js => AniListApi/index.js} (100%) rename lib/{utils.js => utils/index.js} (100%) rename lib/{ => utils}/logging.js (100%) rename lib/{ => utils}/sqliteAsync.js (100%) rename {lib => web}/api/graphql/schema.gql (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index d58a7ec..c387313 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Fixed +- bug where the bot counts itself when calculating needed votes to skip/stop music +- bug on the `ExtendedRichEmbed` where `addField` and `setDescription` throws an error when the value is null or undefined +- bug on `AnilistApiCommands` where the `RichCharacterInfo` uses a nonexistent function of the `ExtendedRichEmbed` + ### Changed - name of MiscCommands module from `TemplateCommandModule` to `MiscoCommandModule` +- moved everything in `lib` to subfolders with the same name as the files and renamed the files to `index.js` +- moved commands outside of `lib` ## [0.11.0-beta] - 2019-03-03 ### Changed diff --git a/bot.js b/bot.js index 155ec78..2c3a2f9 100644 --- a/bot.js +++ b/bot.js @@ -1,12 +1,12 @@ const Discord = require("discord.js"), fs = require('fs-extra'), - logging = require('./lib/logging'), + logging = require('./lib/utils/logging'), msgLib = require('./lib/MessageLib'), guilding = require('./lib/guilding'), utils = require('./lib/utils'), config = require('./config.json'), args = require('args-parser')(process.argv), - sqliteAsync = require('./lib/sqliteAsync'), + sqliteAsync = require('./lib/utils/sqliteAsync'), authToken = args.token || config.api.botToken, prefix = args.prefix || config.prefix || '~', gamepresence = args.game || config.presence; @@ -69,24 +69,24 @@ class Bot { if (config.webinterface && config.webinterface.enabled) await this.initializeWebserver(); this.logger.verbose('Registering commands'); - await this.messageHandler.registerCommandModule(require('./lib/commands/AnilistApiCommands').module, {}); - await this.messageHandler.registerCommandModule(require('./lib/commands/UtilityCommands').module, { + await this.messageHandler.registerCommandModule(require('./commands/AnilistApiCommands').module, {}); + await this.messageHandler.registerCommandModule(require('./commands/UtilityCommands').module, { bot: this, config: config }); - await this.messageHandler.registerCommandModule(require('./lib/commands/InfoCommands').module, { + await this.messageHandler.registerCommandModule(require('./commands/InfoCommands').module, { client: this.client, messageHandler: this.messageHandler }); - await this.messageHandler.registerCommandModule(require('./lib/commands/MusicCommands').module, { + await this.messageHandler.registerCommandModule(require('./commands/MusicCommands').module, { getGuildHandler: async (g) => await this.getGuildHandler(g) }); - await this.messageHandler.registerCommandModule(require('./lib/commands/ServerUtilityCommands').module, { + await this.messageHandler.registerCommandModule(require('./commands/ServerUtilityCommands').module, { getGuildHandler: async (g) => await this.getGuildHandler(g), messageHandler: this.messageHandler, config: config }); - await this.messageHandler.registerCommandModule(require('./lib/commands/MiscCommands').module, {}); + await this.messageHandler.registerCommandModule(require('./commands/MiscCommands').module, {}); this.registerEvents(); } diff --git a/lib/commands/.template/index.js b/commands/.template/index.js similarity index 95% rename from lib/commands/.template/index.js rename to commands/.template/index.js index 399ac59..caf55f0 100644 --- a/lib/commands/.template/index.js +++ b/commands/.template/index.js @@ -1,5 +1,5 @@ /* template index.js. Doesn't implement actual commands */ -const cmdLib = require('../../CommandLib'); // required for command objects +const cmdLib = require('../../lib/CommandLib'); // required for command objects /** * A description what the command module includes and why. Doesn't need to list commands but explains diff --git a/lib/commands/.template/template.yaml b/commands/.template/template.yaml similarity index 100% rename from lib/commands/.template/template.yaml rename to commands/.template/template.yaml diff --git a/lib/commands/AnilistApiCommands/index.js b/commands/AnilistApiCommands/index.js similarity index 98% rename from lib/commands/AnilistApiCommands/index.js rename to commands/AnilistApiCommands/index.js index 1e8e4fd..af30baa 100644 --- a/lib/commands/AnilistApiCommands/index.js +++ b/commands/AnilistApiCommands/index.js @@ -1,5 +1,5 @@ -const cmdLib = require('../../CommandLib'), - anilistApi = require('../../api/AnilistApi'); +const cmdLib = require('../../lib/CommandLib'), + anilistApi = require('../../lib/api/AniListApi'); /** * The AniList commands are all commands that interact with the anilist api. @@ -144,7 +144,7 @@ class RichCharacterInfo extends cmdLib.ExtendedRichEmbed { .replace(/~!.*?!~/g, '') .replace(/\n\n\n/g, '')); if (characterInfo.media && characterInfo.media.edges) - this.addNonemptyField( + this.addField( 'Media Appeareance', characterInfo.media.edges.map(x => { let media = x.node; diff --git a/lib/commands/AnilistApiCommands/template.yaml b/commands/AnilistApiCommands/template.yaml similarity index 100% rename from lib/commands/AnilistApiCommands/template.yaml rename to commands/AnilistApiCommands/template.yaml diff --git a/lib/commands/InfoCommands/index.js b/commands/InfoCommands/index.js similarity index 98% rename from lib/commands/InfoCommands/index.js rename to commands/InfoCommands/index.js index fbff255..ac68869 100644 --- a/lib/commands/InfoCommands/index.js +++ b/commands/InfoCommands/index.js @@ -1,6 +1,6 @@ -const cmdLib = require('../../CommandLib'), +const cmdLib = require('../../lib/CommandLib'), fsx = require('fs-extra'), - utils = require('../../utils'); + utils = require('../../lib/utils'); /** * Info commands provide information about the bot. These informations are diff --git a/lib/commands/InfoCommands/template.yaml b/commands/InfoCommands/template.yaml similarity index 100% rename from lib/commands/InfoCommands/template.yaml rename to commands/InfoCommands/template.yaml diff --git a/lib/commands/MiscCommands/index.js b/commands/MiscCommands/index.js similarity index 98% rename from lib/commands/MiscCommands/index.js rename to commands/MiscCommands/index.js index b7e724b..f9cc408 100644 --- a/lib/commands/MiscCommands/index.js +++ b/commands/MiscCommands/index.js @@ -1,5 +1,5 @@ /* template index.js. Doesn't implement actual commands */ -const cmdLib = require('../../CommandLib'); +const cmdLib = require('../../lib/CommandLib'); /** * Several commands that are that special that they can't be included in any other module. diff --git a/lib/commands/MiscCommands/template.yaml b/commands/MiscCommands/template.yaml similarity index 100% rename from lib/commands/MiscCommands/template.yaml rename to commands/MiscCommands/template.yaml diff --git a/lib/commands/MusicCommands/index.js b/commands/MusicCommands/index.js similarity index 97% rename from lib/commands/MusicCommands/index.js rename to commands/MusicCommands/index.js index c701326..469749d 100644 --- a/lib/commands/MusicCommands/index.js +++ b/commands/MusicCommands/index.js @@ -1,6 +1,6 @@ -const cmdLib = require('../../CommandLib'), - utils = require('../../utils'), - config = require('../../../config'); +const cmdLib = require('../../lib/CommandLib'), + utils = require('../../lib/utils'), + config = require('../../config'); function checkPermission(msg, rolePerm) { if (!rolePerm || ['all', 'any', 'everyone'].includes(rolePerm)) @@ -117,7 +117,7 @@ class MusicCommandModule extends cmdLib.CommandModule { let vc = gh.musicPlayer.voiceChannel || m.member.voiceChannel; if (gh.musicPlayer.connected && vc) { let votes = gh.updateCommandVote(stop.name, m.author.tag); - let neededVotes = Math.ceil(vc.members.size/2); + let neededVotes = Math.ceil((vc.members.size - 1) / 2); if (neededVotes <= votes.count || checkPermission(m, 'dj')) { this._logger.debug(`Vote passed. ${votes.count} out of ${neededVotes} for stop or permission granted`); @@ -167,7 +167,7 @@ class MusicCommandModule extends cmdLib.CommandModule { let vc = gh.musicPlayer.voiceChannel || m.member.voiceChannel; if (gh.musicPlayer.playing && vc) { let votes = gh.updateCommandVote(skip.name, m.author.tag); - let neededVotes = Math.ceil(vc.members.size/2); + let neededVotes = Math.ceil((vc.members.size - 1) / 2); if (neededVotes <= votes.count || checkPermission(m, 'dj')) { this._logger.debug(`Vote passed. ${votes.count} out of ${neededVotes} for skip or permission granted`); diff --git a/lib/commands/MusicCommands/template.yaml b/commands/MusicCommands/template.yaml similarity index 100% rename from lib/commands/MusicCommands/template.yaml rename to commands/MusicCommands/template.yaml diff --git a/lib/commands/ServerUtilityCommands/index.js b/commands/ServerUtilityCommands/index.js similarity index 99% rename from lib/commands/ServerUtilityCommands/index.js rename to commands/ServerUtilityCommands/index.js index b9c98ca..a560cc3 100644 --- a/lib/commands/ServerUtilityCommands/index.js +++ b/commands/ServerUtilityCommands/index.js @@ -1,4 +1,4 @@ -const cmdLib = require('../../CommandLib'); +const cmdLib = require('../../lib/CommandLib'); /** * This command module includes utility commands for the server. diff --git a/lib/commands/ServerUtilityCommands/template.yaml b/commands/ServerUtilityCommands/template.yaml similarity index 100% rename from lib/commands/ServerUtilityCommands/template.yaml rename to commands/ServerUtilityCommands/template.yaml diff --git a/lib/commands/UtilityCommands/index.js b/commands/UtilityCommands/index.js similarity index 98% rename from lib/commands/UtilityCommands/index.js rename to commands/UtilityCommands/index.js index 5071ad6..be9e9ff 100644 --- a/lib/commands/UtilityCommands/index.js +++ b/commands/UtilityCommands/index.js @@ -1,4 +1,4 @@ -const cmdLib = require('../../CommandLib'); +const cmdLib = require('../../lib/CommandLib'); /** * Utility commands are all commands that allow the user to control the behaviour of the diff --git a/lib/commands/UtilityCommands/template.yaml b/commands/UtilityCommands/template.yaml similarity index 100% rename from lib/commands/UtilityCommands/template.yaml rename to commands/UtilityCommands/template.yaml diff --git a/lib/CommandLib.js b/lib/CommandLib/index.js similarity index 88% rename from lib/CommandLib.js rename to lib/CommandLib/index.js index a14b1a3..c7dcd0b 100644 --- a/lib/CommandLib.js +++ b/lib/CommandLib/index.js @@ -1,9 +1,9 @@ const Discord = require('discord.js'), yaml = require('js-yaml'), fsx = require('fs-extra'), - logging = require('./logging'), - config = require('../config.json'), - utils = require('./utils'); + logging = require('../utils/logging'), + config = require('../../config.json'), + utils = require('../utils'); const scopes = { 'Global': 0, @@ -239,13 +239,16 @@ class ExtendedRichEmbed extends Discord.RichEmbed { * @param value */ setDescription(value) { - let croppedValue = value; - if (value.substring) - croppedValue = value.substring(0, 1024); - if (croppedValue.length < value.length) - croppedValue = croppedValue.replace(/\n.*$/g, ''); - if (croppedValue && croppedValue.replace(/\n/g, '').length > 0) - super.setDescription(croppedValue); + if (value) { + let croppedValue = value; + if (value.substring) + croppedValue = value.substring(0, 1024); + if (croppedValue.length < value.length && croppedValue.replace) + croppedValue = croppedValue.replace(/\n.*$/g, ''); + if (croppedValue && croppedValue.replace + && croppedValue.replace(/\n/g, '').length > 0) + super.setDescription(croppedValue); + } return this; } @@ -255,15 +258,16 @@ class ExtendedRichEmbed extends Discord.RichEmbed { * @param value */ addField(name, value) { - let croppedValue = value; - if (value.substring) - croppedValue = value.substring(0, 1024); - if (croppedValue.length < value.length) + if (name && value) { + let croppedValue = value; + if (value.substring) + croppedValue = value.substring(0, 1024); + if (croppedValue && croppedValue.length < value.length && croppedValue.replace) croppedValue = croppedValue.replace(/\n.*$/g, ''); - if (name && croppedValue - && croppedValue.replace(/\n/g, '').length > 0 && name.replace(/\n/g, '').length > 0) - super.addField(name, croppedValue); - + if (croppedValue && croppedValue.replace + && croppedValue.replace(/\n/g, '').length > 0 && name.replace(/\n/g, '').length > 0) + super.addField(name, croppedValue); + } return this; } } diff --git a/lib/MessageLib.js b/lib/MessageLib/index.js similarity index 98% rename from lib/MessageLib.js rename to lib/MessageLib/index.js index 3642b43..dd477e3 100644 --- a/lib/MessageLib.js +++ b/lib/MessageLib/index.js @@ -1,7 +1,7 @@ -const cmdLib = require('./CommandLib'), - config = require('../config.json'), +const cmdLib = require('../CommandLib'), + config = require('../../config.json'), Discord = require('discord.js'), - logging = require('./logging'), + logging = require('../utils/logging'), promiseWaterfall = require('promise-waterfall'); /* eslint no-useless-escape: 0 */ diff --git a/lib/MusicLib.js b/lib/MusicLib/index.js similarity index 98% rename from lib/MusicLib.js rename to lib/MusicLib/index.js index d6558da..4090f58 100644 --- a/lib/MusicLib.js +++ b/lib/MusicLib/index.js @@ -1,9 +1,9 @@ const ytdl = require("ytdl-core"), ypi = require('youtube-playlist-info'), yttl = require('get-youtube-title'), - config = require('../config.json'), - utils = require('./utils.js'), - logging = require('./logging'), + config = require('../../config.json'), + utils = require('../utils/index.js'), + logging = require('../utils/logging'), ytapiKey = config.api.youTubeApiKey; /** diff --git a/lib/StateLib/index.js b/lib/StateLib/index.js new file mode 100644 index 0000000..b16339c --- /dev/null +++ b/lib/StateLib/index.js @@ -0,0 +1,26 @@ + +class EventRouter { + + constructor() { + + } + + /** + * Dispatches + * @param event + */ + dispatchEvent(event) { + + } + + /** + * Registeres discord client events to the EventRouter + * @param client + */ + registerClientEvents(client) { + + } + +} + +class EventGroup diff --git a/lib/WebLib.js b/lib/WebLib/index.js similarity index 98% rename from lib/WebLib.js rename to lib/WebLib/index.js index 6bbcc96..c774637 100644 --- a/lib/WebLib.js +++ b/lib/WebLib/index.js @@ -4,21 +4,21 @@ const express = require('express'), compression = require('compression'), md5 = require('js-md5'), sha512 = require('js-sha512'), - logging = require('./logging'), + logging = require('../utils/logging'), fs = require('fs'), session = require('express-session'), SQLiteStore = require('connect-sqlite3')(session), bodyParser = require('body-parser'), compileSass = require('express-compile-sass'), - config = require('../config.json'), - utils = require('../lib/utils'); + config = require('../../config.json'), + utils = require('../utils'); exports.WebServer = class { constructor(port) { this.app = express(); this.server = null; this.port = port; - this.schema = buildSchema(fs.readFileSync('./lib/api/graphql/schema.gql', 'utf-8')); + this.schema = buildSchema(fs.readFileSync('./web/api/graphql/schema.gql', 'utf-8')); this.root = {}; this._logger = new logging.Logger(this); } diff --git a/lib/api/graphql/AnilistApi/AnimeQuery.gql b/lib/api/AniListApi/graphql/AnimeQuery.gql similarity index 100% rename from lib/api/graphql/AnilistApi/AnimeQuery.gql rename to lib/api/AniListApi/graphql/AnimeQuery.gql diff --git a/lib/api/graphql/AnilistApi/CharacterQuery.gql b/lib/api/AniListApi/graphql/CharacterQuery.gql similarity index 100% rename from lib/api/graphql/AnilistApi/CharacterQuery.gql rename to lib/api/AniListApi/graphql/CharacterQuery.gql diff --git a/lib/api/graphql/AnilistApi/Fragments.yaml b/lib/api/AniListApi/graphql/Fragments.yaml similarity index 100% rename from lib/api/graphql/AnilistApi/Fragments.yaml rename to lib/api/AniListApi/graphql/Fragments.yaml diff --git a/lib/api/graphql/AnilistApi/MangaQuery.gql b/lib/api/AniListApi/graphql/MangaQuery.gql similarity index 100% rename from lib/api/graphql/AnilistApi/MangaQuery.gql rename to lib/api/AniListApi/graphql/MangaQuery.gql diff --git a/lib/api/graphql/AnilistApi/StaffQuery.gql b/lib/api/AniListApi/graphql/StaffQuery.gql similarity index 100% rename from lib/api/graphql/AnilistApi/StaffQuery.gql rename to lib/api/AniListApi/graphql/StaffQuery.gql diff --git a/lib/api/AnilistApi.js b/lib/api/AniListApi/index.js similarity index 100% rename from lib/api/AnilistApi.js rename to lib/api/AniListApi/index.js diff --git a/lib/guilding.js b/lib/guilding.js index 63d03b3..1e3a399 100644 --- a/lib/guilding.js +++ b/lib/guilding.js @@ -1,8 +1,8 @@ const music = require('./MusicLib'), utils = require('./utils'), config = require('../config.json'), - sqliteAsync = require('./sqliteAsync'), - logging = require('./logging'), + sqliteAsync = require('./utils/sqliteAsync'), + logging = require('./utils/logging'), fs = require('fs-extra'), dataDir = config.dataPath || './data'; diff --git a/lib/utils.js b/lib/utils/index.js similarity index 100% rename from lib/utils.js rename to lib/utils/index.js diff --git a/lib/logging.js b/lib/utils/logging.js similarity index 100% rename from lib/logging.js rename to lib/utils/logging.js diff --git a/lib/sqliteAsync.js b/lib/utils/sqliteAsync.js similarity index 100% rename from lib/sqliteAsync.js rename to lib/utils/sqliteAsync.js diff --git a/test/test.js b/test/test.js index 5eb5b33..2ac6160 100644 --- a/test/test.js +++ b/test/test.js @@ -13,7 +13,7 @@ mockobjects.mockLogger = { }; describe('lib/utils', function() { - const utils = require('../lib/utils.js'); + const utils = require('../lib/utils/index.js'); describe('#getSplitDuration', function() { it('returns an object from milliseconds', function() { @@ -28,7 +28,7 @@ describe('lib/utils', function() { it('returns the correct extension for a filename', function(done) { assert(utils.getExtension('test.txt') === '.txt'); assert(utils.getExtension('test.tar.gz') === '.gz'); - assert(utils.getExtension('../lib/utils.js') === '.js'); + assert(utils.getExtension('../lib/index.js') === '.js'); assert(utils.getExtension('.gitignore') === '.gitignore'); done(); }); diff --git a/lib/api/graphql/schema.gql b/web/api/graphql/schema.gql similarity index 100% rename from lib/api/graphql/schema.gql rename to web/api/graphql/schema.gql From 08d220b4c1e12ce5e3156ae9f7cbf7e0640ff76b Mon Sep 17 00:00:00 2001 From: Trivernis Date: Mon, 4 Mar 2019 21:19:25 +0100 Subject: [PATCH 04/54] Updated AniListApi graphql path --- lib/api/AniListApi/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/AniListApi/index.js b/lib/api/AniListApi/index.js index e18efc4..0c46c90 100644 --- a/lib/api/AniListApi/index.js +++ b/lib/api/AniListApi/index.js @@ -1,7 +1,7 @@ const fetch = require('node-fetch'), fsx = require('fs-extra'), yaml = require('js-yaml'), - queryPath = './lib/api/graphql/AnilistApi', + queryPath = './lib/api/AnilistApi/graphql', alApiEndpoint = 'https://graphql.anilist.co'; async function getFragments() { From 016a3d042d00d71f07952dde8ecacff1a6218566 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Thu, 7 Mar 2019 21:47:11 +0100 Subject: [PATCH 05/54] Added lib/state - added with `Event`, `EventGroup` and `EventRouter` for optimized event handling - added special subclasses of EventGroup for Client Events --- CHANGELOG.md | 7 +- bot.js | 4 +- commands/.template/index.js | 2 +- commands/AnilistApiCommands/index.js | 2 +- commands/InfoCommands/index.js | 2 +- commands/MiscCommands/index.js | 2 +- commands/MusicCommands/index.js | 4 +- commands/ServerUtilityCommands/index.js | 2 +- commands/UtilityCommands/index.js | 2 +- lib/StateLib/index.js | 26 ---- lib/{CommandLib => command}/index.js | 0 lib/guilding.js | 2 +- lib/{MessageLib => message}/index.js | 2 +- lib/{MusicLib => music}/index.js | 0 lib/state/EventGroups.js | 163 ++++++++++++++++++++++++ lib/state/index.js | 99 ++++++++++++++ lib/utils/index.js | 2 +- lib/{WebLib => web}/index.js | 0 test/test.js | 10 +- 19 files changed, 286 insertions(+), 45 deletions(-) delete mode 100644 lib/StateLib/index.js rename lib/{CommandLib => command}/index.js (100%) rename lib/{MessageLib => message}/index.js (99%) rename lib/{MusicLib => music}/index.js (100%) create mode 100644 lib/state/EventGroups.js create mode 100644 lib/state/index.js rename lib/{WebLib => web}/index.js (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index c387313..d0a0d49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,12 +9,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - bug where the bot counts itself when calculating needed votes to skip/stop music - bug on the `ExtendedRichEmbed` where `addField` and `setDescription` throws an error when the value is null or undefined - bug on `AnilistApiCommands` where the `RichCharacterInfo` uses a nonexistent function of the `ExtendedRichEmbed` +- Typo in changelog ### Changed -- name of MiscCommands module from `TemplateCommandModule` to `MiscoCommandModule` +- name of MiscCommands module from `TemplateCommandModule` to `MiscCommandModule` - moved everything in `lib` to subfolders with the same name as the files and renamed the files to `index.js` +- renamed libfolders to lowercase and removed the lib suffix - moved commands outside of `lib` +### Added +- state lib with `EventRouter` and `EventGroup` and `Event` classes + ## [0.11.0-beta] - 2019-03-03 ### Changed - template Files to name `template.yaml` diff --git a/bot.js b/bot.js index 2c3a2f9..e45269c 100644 --- a/bot.js +++ b/bot.js @@ -1,7 +1,7 @@ const Discord = require("discord.js"), fs = require('fs-extra'), logging = require('./lib/utils/logging'), - msgLib = require('./lib/MessageLib'), + msgLib = require('./lib/message'), guilding = require('./lib/guilding'), utils = require('./lib/utils'), config = require('./config.json'), @@ -128,7 +128,7 @@ class Bot { */ async initializeWebserver() { this.logger.verbose('Importing weblib'); - weblib = require('./lib/WebLib'); + weblib = require('./lib/web'); this.logger.verbose('Creating WebServer'); this.webServer = new weblib.WebServer(config.webinterface.port || 8080); this.logger.debug('Setting Reference Objects to webserver'); diff --git a/commands/.template/index.js b/commands/.template/index.js index caf55f0..966beba 100644 --- a/commands/.template/index.js +++ b/commands/.template/index.js @@ -1,5 +1,5 @@ /* template index.js. Doesn't implement actual commands */ -const cmdLib = require('../../lib/CommandLib'); // required for command objects +const cmdLib = require('../../lib/command'); // required for command objects /** * A description what the command module includes and why. Doesn't need to list commands but explains diff --git a/commands/AnilistApiCommands/index.js b/commands/AnilistApiCommands/index.js index af30baa..9354d4c 100644 --- a/commands/AnilistApiCommands/index.js +++ b/commands/AnilistApiCommands/index.js @@ -1,4 +1,4 @@ -const cmdLib = require('../../lib/CommandLib'), +const cmdLib = require('../../lib/command'), anilistApi = require('../../lib/api/AniListApi'); /** diff --git a/commands/InfoCommands/index.js b/commands/InfoCommands/index.js index ac68869..e81d97a 100644 --- a/commands/InfoCommands/index.js +++ b/commands/InfoCommands/index.js @@ -1,4 +1,4 @@ -const cmdLib = require('../../lib/CommandLib'), +const cmdLib = require('../../lib/command'), fsx = require('fs-extra'), utils = require('../../lib/utils'); diff --git a/commands/MiscCommands/index.js b/commands/MiscCommands/index.js index f9cc408..be12770 100644 --- a/commands/MiscCommands/index.js +++ b/commands/MiscCommands/index.js @@ -1,5 +1,5 @@ /* template index.js. Doesn't implement actual commands */ -const cmdLib = require('../../lib/CommandLib'); +const cmdLib = require('../../lib/command'); /** * Several commands that are that special that they can't be included in any other module. diff --git a/commands/MusicCommands/index.js b/commands/MusicCommands/index.js index 469749d..c805577 100644 --- a/commands/MusicCommands/index.js +++ b/commands/MusicCommands/index.js @@ -1,4 +1,4 @@ -const cmdLib = require('../../lib/CommandLib'), +const cmdLib = require('../../lib/command'), utils = require('../../lib/utils'), config = require('../../config'); @@ -313,5 +313,5 @@ class MusicCommandModule extends cmdLib.CommandModule { } Object.assign(exports, { - 'module': MusicCommandModule + module: MusicCommandModule }); diff --git a/commands/ServerUtilityCommands/index.js b/commands/ServerUtilityCommands/index.js index a560cc3..78b5620 100644 --- a/commands/ServerUtilityCommands/index.js +++ b/commands/ServerUtilityCommands/index.js @@ -1,4 +1,4 @@ -const cmdLib = require('../../lib/CommandLib'); +const cmdLib = require('../../lib/command'); /** * This command module includes utility commands for the server. diff --git a/commands/UtilityCommands/index.js b/commands/UtilityCommands/index.js index be9e9ff..6e624c3 100644 --- a/commands/UtilityCommands/index.js +++ b/commands/UtilityCommands/index.js @@ -1,4 +1,4 @@ -const cmdLib = require('../../lib/CommandLib'); +const cmdLib = require('../../lib/command'); /** * Utility commands are all commands that allow the user to control the behaviour of the diff --git a/lib/StateLib/index.js b/lib/StateLib/index.js deleted file mode 100644 index b16339c..0000000 --- a/lib/StateLib/index.js +++ /dev/null @@ -1,26 +0,0 @@ - -class EventRouter { - - constructor() { - - } - - /** - * Dispatches - * @param event - */ - dispatchEvent(event) { - - } - - /** - * Registeres discord client events to the EventRouter - * @param client - */ - registerClientEvents(client) { - - } - -} - -class EventGroup diff --git a/lib/CommandLib/index.js b/lib/command/index.js similarity index 100% rename from lib/CommandLib/index.js rename to lib/command/index.js diff --git a/lib/guilding.js b/lib/guilding.js index 1e3a399..0199ec4 100644 --- a/lib/guilding.js +++ b/lib/guilding.js @@ -1,4 +1,4 @@ -const music = require('./MusicLib'), +const music = require('./music'), utils = require('./utils'), config = require('../config.json'), sqliteAsync = require('./utils/sqliteAsync'), diff --git a/lib/MessageLib/index.js b/lib/message/index.js similarity index 99% rename from lib/MessageLib/index.js rename to lib/message/index.js index dd477e3..e95bf1f 100644 --- a/lib/MessageLib/index.js +++ b/lib/message/index.js @@ -1,4 +1,4 @@ -const cmdLib = require('../CommandLib'), +const cmdLib = require('../command'), config = require('../../config.json'), Discord = require('discord.js'), logging = require('../utils/logging'), diff --git a/lib/MusicLib/index.js b/lib/music/index.js similarity index 100% rename from lib/MusicLib/index.js rename to lib/music/index.js diff --git a/lib/state/EventGroups.js b/lib/state/EventGroups.js new file mode 100644 index 0000000..ca22fe2 --- /dev/null +++ b/lib/state/EventGroups.js @@ -0,0 +1,163 @@ +let stateLib = require("index.js"); + +class DiscordGuildEvents extends EventGroup { + + constructor(client) { + super(); + this._registerClientEvents(client); + } + + /** + * Registeres the client events to the EventGroup + * @param client {Discord.Client} + * @private + */ + _registerClientEvents(client) { + this.registerEvent(new stateLib.Event('clientUserGuildSettingsUpdate')) + .registerEvent(new stateLib.Event('clientUserSettingsUpdate')) + .registerEvent(new stateLib.Event('emojiCreate')) + .registerEvent(new stateLib.Event('emojiDelete')) + .registerEvent(new stateLib.Event('emojiUpdate')) + .registerEvent(new stateLib.Event('guildBanAdd')) + .registerEvent(new stateLib.Event('guildBanRemove')) + .registerEvent(new stateLib.Event('guildCreate')) + .registerEvent(new stateLib.Event('guildDelete')) + .registerEvent(new stateLib.Event('guildMemberAdd')) + .registerEvent(new stateLib.Event('guildMemberAvailable')) + .registerEvent(new stateLib.Event('guildMemberRemove')) + .registerEvent(new stateLib.Event('guildMemberChunk')) + .registerEvent(new stateLib.Event('guildMemberSpeaking')) + .registerEvent(new stateLib.Event('guildMemberUpdate')) + .registerEvent(new stateLib.Event('guildUnavailable')) + .registerEvent(new stateLib.Event('guildUpdate')) + .registerEvent(new stateLib.Event('presenceUpdate')) + .registerEvent(new stateLib.Event('roleCreate')) + .registerEvent(new stateLib.Event('roleDelete')) + .registerEvent(new stateLib.Event('roleUpdate')) + .registerEvent(new stateLib.Event('userNoteUpdate')) + .registerEvent(new stateLib.Event('userUpdate')) + .registerEvent(new stateLib.Event('voiceStateUpdate')); + + client.on('clientUserGuildSettingsUpdate', (...o) => this.events.clientUserGuildSettingsUpdate.fire(o)); + client.on('clientUserSettingsUpdate', (...o) => this.events.clientUserSettingsUpdate.fire(o)); + client.on('emojiCreate', (...o) => this.events.emojiCreate.fire(o)); + client.on('emojiDelete', (...o) => this.events.emojiDelete.fire(o)); + client.on('emojiUpdate', (...o) => this.events.emojiUpdate.fire(o)); + client.on('guildBanAdd', (...o) => this.events.guildBanAdd.fire(o)); + client.on('guildBanRemove', (...o) => this.events.guildBanRemove.fire(o)); + client.on('guildCreate', (...o) => this.events.guildCreate.fire(o)); + client.on('guildDelete', (...o) => this.events.guildDelete.fire(o)); + client.on('guildMemberAdd', (...o) => this.events.guildMemberAdd.fire(o)); + client.on('guildMemberAvailable', (...o) => this.events.guildMemberAvailable.fire(o)); + client.on('guildMemberRemove', (...o) => this.events.guildMemberRemove.fire(o)); + client.on('guildMemberChunk', (...o) => this.events.guildMemberChunk.fire(o)); + client.on('guildMemberSpeaking', (...o) => this.events.guildMemberSpeaking.fire(o)); + client.on('guildMemberUpdate', (...o) => this.events.guildMemberUpdate.fire(o)); + client.on('guildUnavailable', (...o) => this.events.guildUnavailable.fire(o)); + client.on('guildUpdate', (...o) => this.events.guildUpdate.fire(o)); + client.on('presenceUpdate', (...o) => this.events.presenceUpdate.fire(o)); + client.on('roleCreate', (...o) => this.events.roleCreate.fire(o)); + client.on('roleDelete', (...o) => this.events.roleDelete.fire(o)); + client.on('roleUpdate', (...o) => this.events.roleUpdate.fire(o)); + client.on('userNoteUpdate', (...o) => this.events.userNoteUpdate.fire(o)); + client.on('userUpdate', (...o) => this.events.userUpdate.fire(o)); + client.on('voiceStateUpdate', (...o) => this.events.voiceStateUpdate.fire(o)); + + + } +} + +class DiscordMessageEvents extends stateLib.EventGroup { + + constructor(client) { + super(); + this._registerMessageEvents(client); + } + + /** + * Registeres all client message events + * @param client {Discord.Client} + * @private + */ + _registerMessageEvents(client) { + this.registerEvent(new stateLib.Event('messageDelete')) + .registerEvent(new stateLib.Event('messageDeleteBulk')) + .registerEvent(new stateLib.Event('messageReactionAdd')) + .registerEvent(new stateLib.Event('messageReactionRemove')) + .registerEvent(new stateLib.Event('messageReactionRemoveAll')) + .registerEvent(new stateLib.Event('messageUpdate')) + .registerEvent(new stateLib.Event('message')); + + client.on('messageDelete', (...o) => this.events.messageDelete.fire(o)); + client.on('messageDeleteBulk', (...o) => this.events.messageDeleteBulk.fire(o)); + client.on('messageReactionAdd', (...o) => this.events.messageReactionAdd.fire(o)); + client.on('messageReactionRemove', (...o) => this.events.messageReactionRemove.fire(o)); + client.on('messageReactionRemoveAll', (...o) => this.events.messageReactionRemoveAll.fire(o)); + client.on('messageUpdate', (...o) => this.events.messageUpdate.fire(o)); + client.on('message', (...o) => this.events.message.fire(o)); + } +} + +class DiscordChannelEvents extends stateLib.EventGroup { + + constructor(client) { + super(); + this._registerChannelEvents(client); + } + + /** + * Registers all events for discord channels. + * @param client {Discord.Client} + * @private + */ + _registerChannelEvents(client) { + this.registerEvent(new stateLib.Event('channelCreate')) + .registerEvent(new stateLib.Event('channelDelete')) + .registerEvent(new stateLib.Event('channelPinsUpdate')) + .registerEvent(new stateLib.Event('channelUpdate')) + .registerEvent(new stateLib.Event('typingStart')) + .registerEvent(new stateLib.Event('typingStop')); + + client.on('channelCreate', (...o) => this.events.channelCreate.fire(o)); + client.on('channelDelete', (...o) => this.events.channelDelete.fire(o)); + client.on('channelPinsUpdate', (...o) => this.events.channelPinsUpdate.fire(o)); + client.on('channelUpdate', (...o) => this.events.channelUpdate.fire(o)); + client.on('typingStart', (...o) => this.events.typingStart.fire(o)); + client.on('typingStop', (...o) => this.events.typingStop.fire(o)); + } + +} + +class DiscordClientEvents extends stateLib.EventGroup { + + constructor(client) { + super(); + this._registerClientEvents(client); + } + + /** + * Registers Discord client events + * @param client {Discord.Client} + * @private + */ + _registerClientEvents(client) { + this.registerEvent(new stateLib.Event('debug')) + .registerEvent(new stateLib.Event('warn')) + .registerEvent(new stateLib.Event('error')) + .registerEvent(new stateLib.Event('ready')) + .registerEvent(new stateLib.Event('resume')) + .registerEvent(new stateLib.Event('disconnect')) + .registerEvent(new stateLib.Event('reconnecting')) + .registerEvent(new stateLib.Event('rateLimit')); + + client.on('debug', (...o) => this.events.debug.fire(o)); + client.on('warn', (...o) => this.events.warn.fire(o)); + client.on('error', (...o) => this.events.error.fire(o)); + client.on('ready', (...o) => this.events.ready.fire(o)); + client.on('resume', (...o) => this.events.resume.fire(o)); + client.on('disconnect', (...o) => this.events.disconnect.fire(o)); + client.on('reconnecting', (...o) => this.events.reconnecting.fire(o)); + client.on('rateLimit', (...o) => this.events.rateLimit.fire(o)); + client.on('presenceUpdate', (...o) => this.events.presenceUpdate.fire(o)); + } +} diff --git a/lib/state/index.js b/lib/state/index.js new file mode 100644 index 0000000..3226cc3 --- /dev/null +++ b/lib/state/index.js @@ -0,0 +1,99 @@ +const logging = require('../utils/logging'); + +class EventRouter { + + constructor() { + this._logger = new logging.Logger(this); + this.eventGroups = {}; + } + + /** + * Fires an event of an event group with event data. + * @param eventGroup {String} + * @param eventName {String} + * @param eventData {Object} + */ + fireEvent(eventGroup, eventName, eventData) { + if (this.eventGroups[eventGroup] instanceof EventGroup) + this.eventGroups[eventGroup].fireEvent(eventName, eventData); + return this; + } + + /** + * Adds an EventRoute to the EventRouter + * @param group {EventGroup} + */ + registerEventGroup(group) { + this.eventGroups[group.name] = name; + } + +} + +class EventGroup { + + /** + * Creates a new EventGroup with the given name. + * @param [name] {String} + */ + constructor(name) { + this._logger = new logging.Logger(this); + this.name = name || this.constructor.name; + this.events = {}; + } + + fireEvent(eventName, eventData) { + if (this.events[eventName] instanceof Event) + this.events[eventName].fire(eventData); + return this; + } + + /** + * Registeres an Event to the EventGroup + * @param event {Event} + */ + registerEvent(event) { + this.events[event.name] = event; + } +} + +class Event { + + /** + * Creates a new Event with the given name. + * @param name + */ + constructor(name) { + this._logger = new logging.Logger(this); + this.name = name; + this.handlers = []; + } + + /** + * Adds an event handler to the Event + * @param handler {Function} + */ + addHandler(handler) { + this.handlers.push(handler); + return this; + } + + /** + * Fires the event with the given data. + * @param data {Object} + */ + fire(data) { + for (let handler in this.handlers) + try { + handler(data); + } catch (err) { + this._logger.verbose(err.message); + this._logger.silly(err.stack); + } + } +} + +Object.assign(exports, { + EventRouter: EventRouter, + EventGroup: EventGroup, + Event: Event +}); diff --git a/lib/utils/index.js b/lib/utils/index.js index 620689c..a2354c3 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -47,7 +47,7 @@ function objectDeepFind (object, attributePath) { /** * Shuffles an array with Fisher-Yates Shuffle * @param array - * @returns {Array}# + * @returns {Array} */ exports.shuffleArray = function(array) { let currentIndex = array.length, temporaryValue, randomIndex; diff --git a/lib/WebLib/index.js b/lib/web/index.js similarity index 100% rename from lib/WebLib/index.js rename to lib/web/index.js diff --git a/test/test.js b/test/test.js index 2ac6160..032c1b9 100644 --- a/test/test.js +++ b/test/test.js @@ -179,7 +179,7 @@ describe('lib/utils', function() { describe('lib/music', function() { - const music = rewire('../lib/MusicLib'); + const music = rewire('../lib/music'); const Readable = require('stream').Readable; music.__set__("logger", mockobjects.mockLogger); @@ -329,8 +329,8 @@ describe('lib/music', function() { }); }); -describe('lib/CommandLib', function() { - let cmdLib = require('../lib/CommandLib'); +describe('lib/command', function() { + let cmdLib = require('../lib/command'); describe('Answer', function() { @@ -373,8 +373,8 @@ describe('lib/CommandLib', function() { }); }); -describe('lib/MessageLib', function() { - let msgLib = require('../lib/MessageLib'); +describe('lib/message', function() { + let msgLib = require('../lib/message'); describe('MessageHandler', function() { it ('parses a command syntax', function() { From 1e70c5e9eabde2bd30aebeed3c572fb19a2853b9 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Thu, 7 Mar 2019 21:48:10 +0100 Subject: [PATCH 06/54] Updated changelog to latest commit --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0a0d49..43e4648 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - state lib with `EventRouter` and `EventGroup` and `Event` classes +- Subclasses of EventRouter for client events groupes `Client`, `Channel`, `Message` and `Guild` ## [0.11.0-beta] - 2019-03-03 ### Changed From 1b08edd2783c273128e5f42bedf1e490bcd2ec79 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sat, 9 Mar 2019 21:01:42 +0100 Subject: [PATCH 07/54] Bug Fixes, Utility Classes - fixed bugs in `AniListApi` - fixed typo in the music commands template - added logging of uncaught promise rejections - added generic SQL Statement classes - updated README --- CHANGELOG.md | 4 + README.md | 8 + bot.js | 7 + commands/MusicCommands/template.yaml | 4 +- lib/api/AniListApi/index.js | 2 +- lib/music/index.js | 10 + lib/utils/genericSql.js | 349 +++++++++++++++++++++++++++ package.json | 6 +- 8 files changed, 384 insertions(+), 6 deletions(-) create mode 100644 lib/utils/genericSql.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 43e4648..b22563a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - bug where the bot counts itself when calculating needed votes to skip/stop music - bug on the `ExtendedRichEmbed` where `addField` and `setDescription` throws an error when the value is null or undefined - bug on `AnilistApiCommands` where the `RichCharacterInfo` uses a nonexistent function of the `ExtendedRichEmbed` +- bug on`AnilistApi` where the `.gql` files couldn't be found. - Typo in changelog ### Changed @@ -16,10 +17,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - moved everything in `lib` to subfolders with the same name as the files and renamed the files to `index.js` - renamed libfolders to lowercase and removed the lib suffix - moved commands outside of `lib` +- switched from opusscript to node-opus for voice ### Added - state lib with `EventRouter` and `EventGroup` and `Event` classes - Subclasses of EventRouter for client events groupes `Client`, `Channel`, `Message` and `Guild` +- Utility classes for generic SQL Statements +- logging of unrejected promises ## [0.11.0-beta] - 2019-03-03 ### Changed diff --git a/README.md b/README.md index 42ab00f..7be1884 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,14 @@ discordbot [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blu A bot that does the discord thing. +Installation +--- + +You can easily install everything with npm `npm i`. If you run into an error see the [discord.js installation guide](https://github.com/discordjs/discord.js#installation) or open an issue. If you run into an error with `ffmpeg-binaries` try using nodejs `v10.15.0` + +Running +--- + `node bot.node [--token=] [--ytapi=] [--owner=] [--prefix=] [--game=] [-i=]` The arguments are optional because the token and youtube-api-key that the bot needs to run can also be defined in the config.json in the bot's directory: diff --git a/bot.js b/bot.js index e45269c..d2135b8 100644 --- a/bot.js +++ b/bot.js @@ -248,10 +248,17 @@ class Bot { // Executing the main function if (typeof require !== 'undefined' && require.main === module) { let logger = new logging.Logger('MAIN-init'); + process.on('unhandledRejection', err => { + // Will print "unhandledRejection err is not defined" + logger.warn(err.message); + logger.debug(err.stack); + }); + logger.info("Starting up... "); logger.debug('Calling constructor...'); let discordBot = new Bot(); logger.debug('Initializing services...'); + discordBot.initServices().then(() => { logger.debug('Starting Bot...'); discordBot.start().catch((err) => { //eslint-disable-line promise/no-nesting diff --git a/commands/MusicCommands/template.yaml b/commands/MusicCommands/template.yaml index 74d6b83..8f89469 100644 --- a/commands/MusicCommands/template.yaml +++ b/commands/MusicCommands/template.yaml @@ -17,7 +17,7 @@ play: url_invalid: > The URL you provided is not a valid YouTube video or Playlist URL. no_url: > - You need to provide an URL to a YouTube viceo or Playlist. + You need to provide an URL to a YouTube video or Playlist. no_voicechannel: > You need to join a VoiceChannel to request media playback. @@ -37,7 +37,7 @@ play_next: url_invalid: > The URL you provided is not a valid YouTube video or Playlist URL. no_url: > - You need to provide an URL to a YouTube viceo or Playlist. + You need to provide an URL to a YouTube video or Playlist. no_voicechannel: > You need to join a VoiceChannel to request media playback. diff --git a/lib/api/AniListApi/index.js b/lib/api/AniListApi/index.js index 0c46c90..9cf8d67 100644 --- a/lib/api/AniListApi/index.js +++ b/lib/api/AniListApi/index.js @@ -1,7 +1,7 @@ const fetch = require('node-fetch'), fsx = require('fs-extra'), yaml = require('js-yaml'), - queryPath = './lib/api/AnilistApi/graphql', + queryPath = __dirname + '/graphql', alApiEndpoint = 'https://graphql.anilist.co'; async function getFragments() { diff --git a/lib/music/index.js b/lib/music/index.js index 4090f58..b877e36 100644 --- a/lib/music/index.js +++ b/lib/music/index.js @@ -160,11 +160,20 @@ class MusicPlayer { } else if (!this.playing || !this.disp) { this._logger.debug(`Playing ${url}`); this.current = ({'url': url, 'title': await this.getVideoName(url)}); + if (this.repeat) + this.queue.push(this.current); this.disp = this.conn.playStream(ytdl(url, {filter: 'audioonly', quality: this.quality, liveBuffer: config.music.livePuffer || 20000}), {volume: this.volume}); + this.disp.on('debug', (dbgmsg) => this._logger.silly(dbgmsg)); + + this.disp.on('error', (err) => { + this._logger.error(err.message); + this._logger.debug(err.stack); + }); + this.disp.on('end', (reason) => { // end event triggers the next song to play when the reason is not stop if (reason !== 'stop') { this.playing = false; @@ -192,6 +201,7 @@ class MusicPlayer { /** * Gets the name of the YouTube Video at url + * TODO: ytdl.getInfo * @param url {String} * @returns {Promise<>} */ diff --git a/lib/utils/genericSql.js b/lib/utils/genericSql.js new file mode 100644 index 0000000..c92ac4d --- /dev/null +++ b/lib/utils/genericSql.js @@ -0,0 +1,349 @@ +/** + * Returns types based on the database. + */ +class GenericTypes { + /** + * Constructor. + * @param database {String} + */ + constructor(database) { + this.database = database; + } + + get null() { + switch(this.database) { + case 'postgresql': + case 'sqlite': + default: + return 'NULL'; + } + } + + get integer() { + switch(this.database) { + case 'postgresql': + case 'sqlite': + default: + return 'INTEGER'; + } + } + + get real() { + switch(this.database) { + case 'sqlite': + return 'REAL'; + case 'postgresql': + default: + return 'FLOAT'; + } + } + + get text() { + switch (this.database) { + case 'postgresql': + case 'sqlite': + default: + return 'TEXT'; + } + } + + get varchar() { + switch (this.database) { + case 'postgresql': + case 'sqlite': + default: + return 'VARCHAR'; + } + } + + get date() { + switch (this.database) { + case 'postgresql': + case 'sqlite': + default: + return 'DATE'; + } + } + + get datetime() { + switch (this.database) { + case 'postgresql': + return 'TIMESTAMP'; + case 'sqlite': + default: + return 'DATETIME'; + } + } + + get serial() { + switch (this.database) { + case 'sqlite': + return 'INTEGER AUTOINCREMENT NOT NULL'; + case 'postgresql': + default: + return 'SERIAL'; + } + } + + /** + * Returns the VARCHAR type with the specified length. + * @param length {Number} + */ + getVarchar(length) { + return `${this.varchar}(${length})`; + } +} + +/** + * Returns sql statements based on the database. + */ +class GenericSql { + /** + * Constructor. + * @param database {String} + */ + constructor(database) { + this.database = database; + this.types = new GenericTypes(database); + this.constraints = { + primaryKey: 'PRIMARY KEY', + notNull: 'NOT NULL', + unique: 'UNIQUE', + like: 'LIKE', + exists: 'EXISTS', + and: 'AND', + or: 'OR', + in: 'IN', + any: 'ANY', + all: 'ALL' + }; + } + + /** + * A sum selector - calculates the sum of all values of the column + * @param colname {String} - the name of the column where the sum is selected. + * @returns {string} + */ + sum(colname) { + switch (this.database) { + case 'postgresql': + case 'sqlite': + return `SUM(${colname})`; + } + } + + /** + * A avg selector - selects the average + * @param colname {String} - the name of the column where the avg value is selected. + * @returns {string} + */ + avg(colname) { + switch (this.database) { + case 'postgresql': + case 'sqlite': + return `AVG(${colname})`; + } + } + + /** + * A min selector - selects the minimum + * @param colname {String} - the name of the column where the min value is selected. + * @returns {string} + */ + min(colname) { + switch (this.database) { + case 'postgresql': + case 'sqlite': + return `MIN(${colname})`; + } + } + + /** + * A max selector - selects the maximum + * @param colname {String} - the name of the column where the max value is selected. + * @returns {string} + */ + max(colname) { + switch (this.database) { + case 'postgresql': + case 'sqlite': + return `MAX(${colname})`; + } + } + + /** + * A count selector - counts the results + * @param colname {String} - the name of the column to be counted. + * @returns {string} + */ + count(colname) { + switch (this.database) { + case 'postgresql': + case 'sqlite': + return `COUNT(${colname})`; + } + } + + /** + * A default constraint + * @param expression {String} - the expression to generate the default value. + * @returns {string} + */ + default(expression) { + switch (this.database) { + case 'postgresql': + case 'sqlite': + return `DEFAULT ${expression}`; + } + } + + /** + * A where statement + * @param row {String} - the row + * @param operator {String} - the comparison operator + * @param comparator {String} the value or row to compare to + */ + and(row, operator, comparator) { + switch (this.database) { + case 'postgresql': + case 'sqlite': + return `AND ${row} ${operator} ${comparator}`; + } + } + + /** + * A or statement + * @param row {String} - the row + * @param operator {String} - the comparison operator + * @param comparator {String} the value or row to compare to + */ + or(row, operator, comparator) { + switch (this.database) { + case 'postgresql': + case 'sqlite': + return `OR ${row} ${operator} ${comparator}`; + } + } + + /** + * A where statement + * @param row {String} - the row + * @param operator {String} - the comparison operator + * @param comparator {String} the value or row to compare to + */ + where(row, operator, comparator) { + switch (this.database) { + case 'postgresql': + case 'sqlite': + return `WHERE ${row} ${operator} ${comparator}`; + } + } + + /** + * Create Table statement + * @param table {String} + * @param rows {Array} + * @returns {string} + */ + createTable(table, rows) { + switch (this.database) { + case 'postgresql': + case 'sqlite': + return `CREATE TABLE ${table} (${rows.map(x => x.sql).join(',')})`; + } + } + + /** + * Create Table if it doesn't exist statement + * @param table {String} + * @param columns {Array} + * @returns {string} + */ + createTableIfNotExists(table, columns) { + switch (this.database) { + case 'postgresql': + case 'sqlite': + return `CREATE TABLE IF NOT EXISTS ${table} (${columns.map(x => x.sql).join(',')})`; + } + } + + /** + * Insert into the table. + * @param table {String} - the table name + * @param colValueObj {Object} - an object with keys as columnnames and values as columnvalues + * @returns {string} + */ + insert(table, colValueObj) { + let rownames = Object.keys(colValueObj); + let values = Object.values(colValueObj); + switch (this.database) { + case 'postgresql': + case 'sqlite': + return `INSERT INTO ${table} (${rownames.join(',')}) values (${values.join(',')})`; + } + } + + /** + * Updates the table with the rowValueObject. + * @param table {String} - the table name + * @param colValueObj {Object} - an object with keys as columnnames and values as columnvalues + * @param conditions {Array} - conditions for the update row selection (WHERE ... [OR ...][AND ...] + * @returns {string} + */ + update(table, colValueObj, conditions) { + switch (this.database) { + case 'postgresql': + case 'sqlite': + return `UPDATE ${table} SET ${Object.entries(colValueObj).map(x => `${x[0]} = ${x[1]}`).join(',')} ${conditions.join(' ')}`; + } + } + + /** + * Selects from a table + * @param table {String} - the tablename + * @param distinct {String|boolean} - should distinct values be selected? If yes provide distinct keyword. + * @param colnames {Array} - the rows to select + * @param conditions {Array} - conditions for the row selection (WHERE ... [OR ...][AND ...] + * @param operations {Array} - operations on the selected rows + * @returns {String} + */ + select(table, distinct, colnames, conditions, operations) { + switch (this.database) { + case 'postgresql': + case 'sqlite': + return `SELECT${distinct? ' ' + distinct : ''} ${colnames.join(' ')} FROM ${table} ${conditions.join(' ')} ${operations.join(' ')}`; + } + } +} + +class Column { + /** + * Create a column for usage in the generic sql statements + * @param name {String} + * @param [type] {String} + * @param [constraints] {Array} + */ + constructor(name, type, constraints) { + this.name = name; + this.type = type; + this.constraints = constraints || []; + } + + /** + * Sets the datatype of the row. + * @param constraint {String} + */ + addConstraint(constraint) { + this.constraints.push(constraint); + } + + get sql() { + return `${this.name} ${this.type} ${this.constraints.join(',')}`; + } +} + +Object.assign(exports, { + GenericSql: GenericSql, + GenericTypes: GenericSql, + Column: Column +}); diff --git a/package.json b/package.json index 56a023f..4d7f224 100644 --- a/package.json +++ b/package.json @@ -23,17 +23,17 @@ "graphql": "14.1.1", "js-md5": "0.7.3", "js-sha512": "0.8.0", + "js-yaml": "latest", "node-fetch": "^2.3.0", "node-sass": "4.11.0", - "opusscript": "0.0.6", + "node-opus": "0.3.1", "promise-waterfall": "0.1.0", "pug": "2.0.3", "sqlite3": "4.0.6", "winston": "3.2.1", "winston-daily-rotate-file": "3.8.0", "youtube-playlist-info": "1.1.2", - "ytdl-core": "0.29.1", - "js-yaml": "latest" + "ytdl-core": "0.29.1" }, "devDependencies": { "assert": "1.4.1", From ec5ed87c49b1edfa0a50f431cf4f7399846f4f8e Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sun, 10 Mar 2019 18:54:12 +0100 Subject: [PATCH 08/54] Generic Database Connection - you can now select one type of database either sqlite or postgres in the config.json (see README) - fixed bugs in generic SQL Statements - added generic database class - changed all sql scripts to generic type - added settings table to guild to manage settings of the guild - added extended database class for the guild with predefined sql statements - added music command volume to change the volume (role dj) - added music command quality to change the musics quality (role owner) --- CHANGELOG.md | 5 + README.md | 8 ++ bot.js | 44 +++--- commands/MusicCommands/index.js | 74 +++++++--- commands/MusicCommands/template.yaml | 25 ++++ commands/ServerUtilityCommands/index.js | 23 +-- commands/UtilityCommands/index.js | 3 +- lib/database/index.js | 177 ++++++++++++++++++++++++ lib/guilding.js | 102 -------------- lib/guilds/index.js | 166 ++++++++++++++++++++++ lib/music/index.js | 37 ++--- lib/utils/genericSql.js | 67 +++++++-- lib/web/index.js | 24 ++-- package.json | 7 +- 14 files changed, 567 insertions(+), 195 deletions(-) create mode 100644 lib/database/index.js delete mode 100644 lib/guilding.js create mode 100644 lib/guilds/index.js diff --git a/CHANGELOG.md b/CHANGELOG.md index b22563a..c90f22a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,12 +18,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - renamed libfolders to lowercase and removed the lib suffix - moved commands outside of `lib` - switched from opusscript to node-opus for voice +- all hard coded sql statements to generic sql generation ### Added - state lib with `EventRouter` and `EventGroup` and `Event` classes - Subclasses of EventRouter for client events groupes `Client`, `Channel`, `Message` and `Guild` - Utility classes for generic SQL Statements - logging of unrejected promises +- database class for database abstraction (lib/database) +- config entry for `database` with supported values `postgresql` or `sqlite` +- config entry for `databaseConnection` for postgresql (`user`, `host`, `password`, `database`, `port`) +- table `settings` to each guild to store guild specific settings ## [0.11.0-beta] - 2019-03-03 ### Changed diff --git a/README.md b/README.md index 7be1884..f15e00c 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,14 @@ The arguments are optional because the token and youtube-api-key that the bot ne "commandSettings": { "maxSequenceParallel": 5, // the maximum number of commands executed in parallel "maxSequenceSerial": 10 // the maximum number of commands executed in serial + }, + "database": "postgres or sqlite", // choose one + "databaseConnection": { + "user": "USERNAME", + "host": "HOSTNAME OR IP", + "password": "DATABASE USERPASSWORD", + "database": "BOT DATABASE NAME", // the database needs to exist + "port": 5432 // the port of the database server } } ``` diff --git a/bot.js b/bot.js index d2135b8..88d2927 100644 --- a/bot.js +++ b/bot.js @@ -2,11 +2,11 @@ const Discord = require("discord.js"), fs = require('fs-extra'), logging = require('./lib/utils/logging'), msgLib = require('./lib/message'), - guilding = require('./lib/guilding'), + guilding = require('./lib/guilds'), utils = require('./lib/utils'), config = require('./config.json'), args = require('args-parser')(process.argv), - sqliteAsync = require('./lib/utils/sqliteAsync'), + dblib = require('./lib/database'), authToken = args.token || config.api.botToken, prefix = args.prefix || config.prefix || '~', gamepresence = args.game || config.presence; @@ -112,13 +112,14 @@ class Bot { this.logger.debug('Checking for ./data/ existence'); await fs.ensureDir('./data'); this.logger.verbose('Connecting to main database'); - this.maindb = new sqliteAsync.Database('./data/main.db'); - await this.maindb.init(); - - await this.maindb.run(`${utils.sql.tableExistCreate} presences ( - ${utils.sql.pkIdSerial}, - text VARCHAR(255) UNIQUE NOT NULL - )`); + this.maindb = new dblib.Database('main'); + await this.maindb.initDatabase(); + let sql = this.maindb.sql; + await this.maindb.run(sql.createTableIfNotExists('presences', [ + sql.templates.idcolumn, + new dblib.Column('text', sql.types.getVarchar(255), + [sql.constraints.unique, sql.constraints.notNull]) + ])); this.logger.debug('Loading Presences...'); await this.loadPresences(); } @@ -145,33 +146,37 @@ class Bot { /** * If a data/presences.txt exists, it is read and each line is put into the presences array. - * Each line is also stored in the main.db database. After the file is completely read, it get's deleted. + * Each line is also stored in the dbot-main.db database. After the file is completely read, it get's deleted. * Then the data is read from the database and if the presence doesn't exist in the presences array, it get's * pushed in there. If the presences.txt file does not exist, the data is just read from the database. In the end * a rotator is created that rotates the presence every configured duration. */ async loadPresences() { + let sql = this.maindb.sql; if (await fs.pathExists('./data/presences.txt')) { let lineReader = require('readline').createInterface({ input: require('fs').createReadStream('./data/presences.txt') }); - lineReader.on('line', (line) => { - this.maindb.run('INSERT INTO presences (text) VALUES (?)', [line], (err) => { - if (err) - this.logger.warn(err.message); - - }); - this.presences.push(line); + this.maindb.begin(); + lineReader.on('line', async (line) => { + try { + await this.maindb.query(sql.insert('presences', {text: sql.parameter(1)}), [line]); + this.presences.push(line); + } catch (err) { + this.logger.warn(err.message); + this.logger.debug(err.stack); + } }); + await this.maindb.commit(); this.rotator = this.client.setInterval(() => this.rotatePresence(), config.presence_duration || 360000); await fs.unlink('./data/presences.txt'); - let rows = await this.maindb.all('SELECT text FROM presences'); + let rows = await this.maindb.all(sql.select('presences', false, ['text'])); for (let row of rows) if (!(row[0] in this.presences)) this.presences.push(row.text); } else { - let rows = await this.maindb.all('SELECT text FROM presences'); + let rows = await this.maindb.all(sql.select('presences', false, ['text'])); for (let row of rows) this.presences.push(row.text); this.rotator = this.client.setInterval(() => this.rotatePresence(), @@ -238,6 +243,7 @@ class Bot { if (!this.guildHandlers[guild.id]) { let newGuildHandler = new guilding.GuildHandler(guild); await newGuildHandler.initDatabase(); + await newGuildHandler.applySettings(); this.guildHandlers[guild.id] = newGuildHandler; } return this.guildHandlers[guild.id]; diff --git a/commands/MusicCommands/index.js b/commands/MusicCommands/index.js index c805577..7283379 100644 --- a/commands/MusicCommands/index.js +++ b/commands/MusicCommands/index.js @@ -42,9 +42,9 @@ class MusicCommandModule extends cmdLib.CommandModule { async _connectAndPlay(gh, vc, url, next) { if (!gh.musicPlayer.connected) { await gh.musicPlayer.connect(vc); - await gh.musicPlayer.playYouTube(url, next); + return await gh.musicPlayer.playYouTube(url, next); } else { - await gh.musicPlayer.playYouTube(url, next); + return await gh.musicPlayer.playYouTube(url, next); } } @@ -68,17 +68,24 @@ class MusicCommandModule extends cmdLib.CommandModule { return t.response.no_url; if (!utils.YouTube.isValidEntityUrl(url)) { url = s; - let row = await gh.db.get('SELECT url FROM playlists WHERE name = ?', [url]); + let row = await gh.db.get(gh.db.sql.select('playlists', false, ['url'], + gh.db.sql.where('name', '=', gh.db.sql.parameter(1))), [url]); if (!row) { this._logger.debug('Got invalid url for play command.'); return t.response.url_invalid; } else { - await this._connectAndPlay(gh, vc, row.url, n); - return t.response.success; + let songcount = await this._connectAndPlay(gh, vc, row.url, n); + if (songcount) + return `Added ${songcount} songs to the queue.`; + else + return t.response.success; } } else { - await this._connectAndPlay(gh, vc, url, n); - return t.response.success; + let songcount = await this._connectAndPlay(gh, vc, url, n); + if (songcount) + return `Added ${songcount} songs to the queue.`; + else + return t.response.success; } } @@ -249,14 +256,15 @@ class MusicCommandModule extends cmdLib.CommandModule { new cmdLib.Answer(async (m, k, s) => { let gh = await this._getGuildHandler(m.guild); let saveName = s.replace(k.url + ' ', ''); - let row = await gh.db - .get('SELECT COUNT(*) count FROM playlists WHERE name = ?', [saveName]); - if (!row || row.count === 0) - await gh.db.run('INSERT INTO playlists (name, url) VALUES (?, ?)', - [saveName, k.url]); + let row = await gh.db.get(gh.db.sql.select('playlists', false, + [gh.db.sql.count('*')], gh.db.sql.where('name', '=', gh.db.sql.parameter(1))), [saveName]); + if (!row || Number(row.count) === 0) + await gh.db.run(gh.db.sql.insert('playlists', + {name: gh.db.sql.parameter(1), url: gh.db.sql.parameter(2)}), [saveName, k.url]); else - await gh.db.run('UPDATE playlists SET url = ? WHERE name = ?', - [k.url, saveName]); + await gh.db.run(gh.db.sql.update('playlists', + {url: gh.db.sql.parameter(1)}, + gh.db.sql.where('name', '=', gh.db.sql.parameter(2))), [k.url, saveName]); return `Saved song/playlist as ${saveName}`; }) ); @@ -268,7 +276,7 @@ class MusicCommandModule extends cmdLib.CommandModule { if (!s) { return this.template.delete_media.response.no_name; } else { - await gh.db.run('DELETE FROM playlists WHERE name = ?', [s]); + await gh.db.run(gh.db.sql.delete('playlists', gh.db.sql.where('name', '=', gh.db.sql.parameter(1))), [s]); return `Deleted ${s} from saved media`; } }) @@ -279,7 +287,7 @@ class MusicCommandModule extends cmdLib.CommandModule { new cmdLib.Answer(async (m) => { let gh = await this._getGuildHandler(m.guild); let response = ''; - let rows = await gh.db.all('SELECT name, url FROM playlists'); + let rows = await gh.db.all(gh.db.sql.select('playlists', false, ['name', 'url'])); for (let row of rows) response += `[${row.name}](${row.url})\n`; @@ -292,6 +300,36 @@ class MusicCommandModule extends cmdLib.CommandModule { }) ); + let volume = new cmdLib.Command( + this.template.volume, + new cmdLib.Answer(async (m, k) => { + let volume = Number(k.volume); + if (volume && volume <= 100 && volume >= 0) { + let gh = await this._getGuildHandler(m.guild); + gh.musicPlayer.setVolume(Math.round(volume)/100); + await gh.db.setSetting('musicPlayerVolume', Math.round(volume)/100); + return `Set music volume to **${volume}**`; + } else { + return this.template.volume.response.invalid; + } + }) + ); + + let quality = new cmdLib.Command( + this.template.quality, + new cmdLib.Answer(async (m, k) => { + let allowed = ['highest', 'lowest', 'highestaudio', 'lowestaudio']; + if (allowed.includes(k.quality)) { + let gh = await this._getGuildHandler(m.guild); + gh.musicPlayer.quality = k.quality; + await gh.db.setSetting('musicPlayerQuality', k.quality); + return `Set music quality to **${k.quality}**`; + } else { + return this.template.quality.response.invalid; + } + }) + ); + // register commands commandHandler .registerCommand(play) @@ -308,7 +346,9 @@ class MusicCommandModule extends cmdLib.CommandModule { .registerCommand(toggleRepeat) .registerCommand(saveMedia) .registerCommand(deleteMedia) - .registerCommand(savedMedia); + .registerCommand(savedMedia) + .registerCommand(volume) + .registerCommand(quality); } } diff --git a/commands/MusicCommands/template.yaml b/commands/MusicCommands/template.yaml index 8f89469..4728d0c 100644 --- a/commands/MusicCommands/template.yaml +++ b/commands/MusicCommands/template.yaml @@ -168,3 +168,28 @@ saved_media: response: no_saved: > There are no saved YouTube URLs :( + +volume: + <<: *METADATA + name: volume + permission: dj + args: + - volume + description: > + Sets the volume of the Music Player. + response: + invalid: > + The value you entered is an invalid volume. + +quality: + <<: *METADATA + name: quality + permission: owner + args: + - quality + description: > + Sets the quality of the music of the Music Player. + The setting will be applied on the next song. + response: + invalid: > + You entered an invalid quality value. diff --git a/commands/ServerUtilityCommands/index.js b/commands/ServerUtilityCommands/index.js index 78b5620..c3ef5fe 100644 --- a/commands/ServerUtilityCommands/index.js +++ b/commands/ServerUtilityCommands/index.js @@ -66,14 +66,15 @@ class ServerUtilityCommandModule extends cmdLib.CommandModule { } else if (sequence.find(x => x.length > maxSqSer)) { return this.template.save_cmd.response.sequence_too_many_serial; } else { - let row = await gh.db - .get('SELECT COUNT(*) count FROM commands WHERE name = ?', [k.name]); - if (!row || row.count === 0) - await gh.db - .run('INSERT INTO commands (name, command) VALUES (?, ?)', [k.name, JSON.stringify(sequence)]); + let sql = gh.db.sql; + let row = await gh.db.get(sql.select('commands', false, [sql.count('*')], + sql.where('name', '=', sql.parameter(1))), [k.name]); + if (!row || Number(row.count) === 0) + await gh.db.run(sql.insert('commands', {name: sql.parameter(1), command: sql.parameter(2)}), + [k.name, JSON.stringify(sequence)]); else - await await gh.db - .run('UPDATE commands SET command = ? WHERE name = ?', [JSON.stringify(sequence), k.name]); + await gh.db.run(sql.update('commands', {command: sql.parameter(1)}, sql.where('name', '=', sql.parameter(2))), + [JSON.stringify(sequence), k.name]); } }) ); @@ -82,7 +83,7 @@ class ServerUtilityCommandModule extends cmdLib.CommandModule { this.template.delete_cmd, new cmdLib.Answer(async (m, k) => { let gh = await this._getGuildHandler(m.guild); - await gh.db.run('DELETE FROM commands WHERE name = ?', [k.name]); + await gh.db.run(gh.db.sql.delete('commands', gh.db.sql.where('name', '=', gh.db.sql.parameter(1)), ), [k.name]); return `Deleted command ${k.name}`; }) ); @@ -93,7 +94,7 @@ class ServerUtilityCommandModule extends cmdLib.CommandModule { let gh = await this._getGuildHandler(m.guild); let response = new cmdLib.ExtendedRichEmbed('Saved Commands') .setFooter(`Execute a saved entry with ${this._config.prefix}execute [Entryname]`); - let rows = await gh.db.all('SELECT name, command FROM commands'); + let rows = await gh.db.all(gh.db.sql.select('commands', ['name', 'command'])); if (rows.length === 0) return this.template.saved_cmd.response.no_commands; else @@ -107,8 +108,8 @@ class ServerUtilityCommandModule extends cmdLib.CommandModule { this.template.execute, new cmdLib.Answer(async (m, k) => { let gh = await this._getGuildHandler(m.guild); - let row = await gh.db - .get('SELECT command FROM commands WHERE name = ?', [k.name]); + let row = await gh.db.get(gh.db.sql.select('commands',false, ['command'], + gh.db.sql.where('name', '=', gh.db.sql.parameter(1))), [k.name]); if (row) await this._messageHandler .executeCommandSequence(JSON.parse(row.command), m); diff --git a/commands/UtilityCommands/index.js b/commands/UtilityCommands/index.js index 6e624c3..4e9ed9d 100644 --- a/commands/UtilityCommands/index.js +++ b/commands/UtilityCommands/index.js @@ -24,12 +24,13 @@ class UtilityCommandModule extends cmdLib.CommandModule { async register(commandHandler) { await this._loadTemplate(); + let sql = this._bot.maindb.sql; let addPresence = new cmdLib.Command( this.template.add_presence, new cmdLib.Answer(async (m, k, s) => { this._bot.presences.push(s); - await this._bot.maindb.run('INSERT INTO presences (text) VALUES (?)', [s]); + await this._bot.maindb.run(sql.insert('presences', {text: sql.parameter(1)}), [s]); return `Added Presence \`${s}\``; }) ); diff --git a/lib/database/index.js b/lib/database/index.js new file mode 100644 index 0000000..f3e30c2 --- /dev/null +++ b/lib/database/index.js @@ -0,0 +1,177 @@ +const genericSql = require('../utils/genericSql'), + logging = require('../utils/logging'), + config = require('../../config.json'); + +class Database { + /** + * Creates a new database. + * @param name {String} - the name of the database. + */ + constructor(name) { + this.name = name; + this._logger = new logging.Logger(`Database@${name}`); + this._dbType = config.database? config.database : 'sqlite'; + if (this._dbType === 'sqlite') + this.database = new (require('../utils/sqliteAsync')).Database(`./data/${this.name}.db`); + else if (this._dbType === 'postgresql') + this.database = new (require('pg')).Pool({ + user: config.databaseConnection.user, + host: config.databaseConnection.host, + database: config.databaseConnection.database, + password: config.databaseConnection.password, + port: config.databaseConnection.port + }); + this.sql = new genericSql.GenericSql(this._dbType); + } + + /** + * Initializes the database. + * @returns {Promise} + */ + async initDatabase() { + if (this._dbType === 'sqlite') { + await this.database.init(); + } else if (this._dbType === 'postgresql') { + await this.database.connect(); + await this.begin(); + await this.database.query(`CREATE SCHEMA IF NOT EXISTS ${this.name.replace(/\W/g, '')}`); + await this.database.query(`SET search_path TO ${this.name.replace(/\W/g, '')}`); + await this.commit(); + } + this._logger.verbose(`Connected to ${this._dbType} database ${this.name}`); + } + + /** + * Run a sql statement with seperate values and no return. + * Autocommit. + * @param sql {String} + * @param [values] {Array} + * @returns {Promise<*>} + */ + async run(sql, values) { + this._logger.debug(`Running SQL "${sql}" with values ${values}`); + if (this._dbType === 'sqlite') + await this.database.run(sql, values); + else if (this._dbType === 'postgresql') + try { + await this.begin(); + await this.database.query(sql, values); + await this.commit(); + } catch (err) { + this._logger.error(err.message); + this._logger.verbose(err.stack); + await this.rollback(); + } + } + + /** + * Begin. Part of Postgresqls BEGIN / COMMIT / ROLLBACK + * @returns {Promise} + */ + async begin() { + if (this._dbType === 'postgresql') { + await this.database.query('BEGIN'); + await this.database.query(`SET search_path TO ${this.name.replace(/\W/g, '')};`); + } + } + + /** + * Add a query to the current changes. No autocommit (except on sqlite). + * @param sql + * @param values + * @returns {Promise} + */ + async query(sql, values) { + if (this._dbType === 'sqlite') { + await this.run(sql, values); + } else if (this._dbType === 'postgresql') { + await this.database.query(sql, values); + this._logger.debug(`Running SQL "${sql}" with values ${values}`); + } + } + + /** + * Commit. Part of Postgresqls BEGIN / COMMIT / ROLLBACK. + * Writes data to the database, ROLLBACK on error. (has no effect on sqlite) + * @returns {Promise} + */ + async commit() { + if (this._dbType === 'postgresql') + try { + await this.database.query('COMMIT'); + } catch (err) { + await this.database.query('ROLLBACK'); + this._logger.error(err.message); + this._logger.verbose(err.stack); + } + } + + /** + * Rollback. Part of Postgresqls BEGIN / COMMIT / ROLLBACK. + * Reverts changes done in the current commit. (has no effect on sqlite) + * @returns {Promise} + */ + async rollback() { + if (this._dbType === 'postgresql') + this.database.query('ROLLBACK'); + } + + + /** + * Run a sql statement with seperate values and first result row as return. + * @param sql {String} - the sql statement with escaped values ($1, $2... for postgres, ? for sqlite) + * @param [values] {Array} + * @returns {Promise} + */ + async get(sql, values) { + this._logger.debug(`Running SQL "${sql}" with values ${values}`); + let result = null; + if (this._dbType === 'sqlite') { + result = await this.database.get(sql, values); + } else if (this._dbType === 'postgresql') { + await this.database.query(`SET search_path TO ${this.name.replace(/\W/g, '')};`); + result = (await this.database.query({ + text: sql, + values: values + })).rows; + } + if (result instanceof Array && result.length > 0) + return result[0]; + else + return result; + } + + /** + * Run a sql statement with seperate values and all result rows as return. + * @param sql {String} - the sql statement with escaped values ($1, $2... for postgres, ? for sqlite) + * @param [values] {Array} - the seperate values + * @returns {Promise} + */ + async all(sql, values) { + this._logger.debug(`Running SQL "${sql}" with values ${values}`); + if (this._dbType === 'sqlite') { + return await this.database.all(sql, values); + } else if (this._dbType === 'postgresql') { + await this.database.query(`SET search_path TO ${this.name.replace(/\W/g, '')};`); + return (await this.database.query({ + text: sql, + values: values + })).rows; + } + } + + /** + * Closes the connection to the database. + */ + close() { + if (this._dbType === 'sqlite') + this.database.close(); + else if (this._dbType === 'postgresql') + this.database.release(); + } +} + +Object.assign(exports, { + Column: genericSql.Column, + Database: Database +}); diff --git a/lib/guilding.js b/lib/guilding.js deleted file mode 100644 index 0199ec4..0000000 --- a/lib/guilding.js +++ /dev/null @@ -1,102 +0,0 @@ -const music = require('./music'), - utils = require('./utils'), - config = require('../config.json'), - sqliteAsync = require('./utils/sqliteAsync'), - logging = require('./utils/logging'), - fs = require('fs-extra'), - dataDir = config.dataPath || './data'; - -/** - * The Guild Handler handles guild settings and data. - * @type {GuildHandler} - */ -class GuildHandler { - - constructor(guild) { - this.guild = guild; - this._logger = new logging.Logger(`${this.constructor.name}@${this.guild}`); - this.musicPlayer = new music.MusicPlayer(null); - this._logger.silly('Initialized Guild Handler'); - this._votes = {}; - } - - /** - * Initializes the database - * @returns {Promise} - */ - async initDatabase() { - this._logger.silly('Initializing Database'); - await fs.ensureDir(dataDir + '/gdb'); - this.db = new sqliteAsync.Database(`${dataDir}/gdb/${this.guild}.db`); - await this.db.init(); - this._logger.debug(`Connected to the database for ${this.guild}`); - this._logger.debug('Creating Databases'); - await this._createTables(); - } - - /** - * Destroys the guild handler - */ - destroy() { - this._logger.debug('Ending musicPlayer'); - this.musicPlayer.stop(); - this._logger.debug('Ending Database'); - this.db.close(); - } - - /** - * Creates all tables needed in the Database. - * These are at the moment: - * messages - logs all messages send on the server - * playlists - save playlists to play them later - */ - async _createTables() { - await this.db.run(`${utils.sql.tableExistCreate} messages ( - ${utils.sql.pkIdSerial}, - creation_timestamp DATETIME NOT NULL, - author VARCHAR(128) NOT NULL, - author_name VARCHAR(128), - content TEXT NOT NULL - )`); - this._logger.silly('Created Table messages'); - await this.db.run(`${utils.sql.tableExistCreate} playlists ( - ${utils.sql.pkIdSerial}, - name VARCHAR(32) UNIQUE NOT NULL, - url VARCHAR(255) NOT NULL - )`); - this._logger.silly('Created Table playlists'); - await this.db.run(`${utils.sql.tableExistCreate} commands ( - ${utils.sql.pkIdSerial}, - name VARCHAR(32) UNIQUE NOT NULL, - command VARCHAR(255) NOT NULL - )`); - this._logger.silly('Created Table commands'); - } - - /** - * Sets the vote counter for a command up and adds the user. - * @param command {String} - * @param user {String} - */ - updateCommandVote(command, user) { - if (!this._votes[command]) - this._votes[command] = {count: 0, users: []}; - if (!this._votes[command].users.includes(user)) { - this._votes[command].count++; - this._votes[command].users.push(user); - } - return this._votes[command]; - } - - /** - * Resets the vote counter and voted users for a command. - * @param command {String} - */ - resetCommandVote(command) { - this._votes[command] = {count: 0, users: []}; - } -} - -Object.assign(exports, { - GuildHandler: GuildHandler -}); diff --git a/lib/guilds/index.js b/lib/guilds/index.js new file mode 100644 index 0000000..baf3f24 --- /dev/null +++ b/lib/guilds/index.js @@ -0,0 +1,166 @@ +const music = require('../music'), + utils = require('../utils'), + config = require('../../config.json'), + dblib = require('../database'), + logging = require('../utils/logging'), + fs = require('fs-extra'), + dataDir = config.dataPath || './data'; + +/** + * GuildDatabase class has abstraction for some sql statements. + */ +class GuildDatabase extends dblib.Database { + + /** + * Constructor. + * @param name + */ + constructor(name) { + super(name); + } + + /** + * Creates all tables needed in the guilds Database. + */ + async createTables() { + let sql = this.sql; + await this.run(sql.createTableIfNotExists('playlists', [ + sql.templates.idcolumn, + new dblib.Column('name', sql.types.getVarchar(32), [sql.constraints.unique, sql.constraints.notNull]), + new dblib.Column('url', sql.types.getVarchar(255), [sql.constraints.notNull]) + ])); + this._logger.silly('Created Table playlists.'); + await this.run(sql.createTableIfNotExists('commands', [ + sql.templates.idcolumn, + new dblib.Column('name', sql.types.getVarchar(32), [sql.constraints.unique, sql.constraints.notNull]), + new dblib.Column('command', sql.types.getVarchar(255), [sql.constraints.notNull]) + ])); + this._logger.silly('Created Table commands.'); + await this.run(sql.createTableIfNotExists('settings', [ + sql.templates.idcolumn, + new dblib.Column('key', sql.types.getVarchar(32), [sql.constraints.unique, sql.constraints.notNull]), + new dblib.Column('value', sql.types.getVarchar(32), []) + ])); + this._logger.silly('Created Table settings.'); + } + + /** + * Get the value of a setting + * @param name + * @returns {Promise<*>} + */ + async getSetting(name) { + let result = await this.get(this.sql.select('settings', false, 'value', + this.sql.where('key', '=', this.sql.parameter(1))), [name]); + if (result) + return result.value; + else + return null; + } + + /** + * Get all settings as object. + * @returns {Promise} + */ + async getSettings() { + let rows = await this.all(this.sql.select('settings', false, ['key', 'value'], [], [])); + let retObj = {}; + if (rows) + for (let row of rows) + retObj[row.key] = row.value; + return retObj; + } + + /** + * Insert or update a setting parameter in the settings database. + * @param name + * @param value + * @returns {Promise} + */ + async setSetting(name, value) { + let row = await this.get(this.sql.select('settings', false, [this.sql.count('*')], + this.sql.where('key', '=', this.sql.parameter(1))), [name]); + if (!row || Number(row.count) === 0) + await this.run(this.sql.insert('settings', {key: this.sql.parameter(1), value: this.sql.parameter(2)}), + [name, value]); + else + await this.run(this.sql.update('settings', {value: this.sql.parameter(1)}, + this.sql.where('key', '=', this.sql.parameter(2))), [value, name]); + } +} + +/** + * The Guild Handler handles guild settings and data. + * @type {GuildHandler} + */ +class GuildHandler { + + constructor(guild) { + this.guild = guild; + this._logger = new logging.Logger(`${this.constructor.name}@${this.guild}`); + this.musicPlayer = new music.MusicPlayer(null); + this._logger.silly('Initialized Guild Handler'); + this._votes = {}; + this.settings = {}; + } + + /** + * Initializes the database + * @returns {Promise} + */ + async initDatabase() { + this._logger.silly('Initializing Database'); + this.db = new GuildDatabase(`guild_${this.guild.name.replace(/\s/g, '_').replace(/\W/g, '')}`); + await this.db.initDatabase(); + this._logger.debug(`Connected to the database for ${this.guild}`); + this._logger.debug('Creating Databases'); + await this.db.createTables(); + } + + /** + * Applies all relevant guild settings. + * @returns {Promise} + */ + async applySettings() { + this.settings = await this.db.getSettings(); + this.musicPlayer.setVolume(Number(this.settings.musicPlayerVolume) || 0.5); + this.musicPlayer.quality = this.settings.musicPlayerQuality || 'lowest'; + } + + /** + * Destroys the guild handler + */ + destroy() { + this._logger.debug('Ending musicPlayer'); + this.musicPlayer.stop(); + this._logger.debug('Ending Database'); + this.db.close(); + } + + /** + * Sets the vote counter for a command up and adds the user. + * @param command {String} + * @param user {String} + */ + updateCommandVote(command, user) { + if (!this._votes[command]) + this._votes[command] = {count: 0, users: []}; + if (!this._votes[command].users.includes(user)) { + this._votes[command].count++; + this._votes[command].users.push(user); + } + return this._votes[command]; + } + + /** + * Resets the vote counter and voted users for a command. + * @param command {String} + */ + resetCommandVote(command) { + this._votes[command] = {count: 0, users: []}; + } +} + +Object.assign(exports, { + GuildHandler: GuildHandler +}); diff --git a/lib/music/index.js b/lib/music/index.js index b877e36..114b9af 100644 --- a/lib/music/index.js +++ b/lib/music/index.js @@ -1,6 +1,5 @@ const ytdl = require("ytdl-core"), ypi = require('youtube-playlist-info'), - yttl = require('get-youtube-title'), config = require('../../config.json'), utils = require('../utils/index.js'), logging = require('../utils/logging'), @@ -20,10 +19,11 @@ class MusicPlayer { this.repeat = false; this.volume = 0.5; this.voiceChannel = voiceChannel; - this.quality = 'lowest'; this.exitTimeout = null; this._logger = new logging.Logger(this); this._logger.silly('Initialized Music Player'); + config.music? this.quality = config.music.quality || 'lowest' : this.quality = 'lowest'; + config.music? this.liveBuffer = config.music.liveBuffer || 10000 : 10000; } /** @@ -151,12 +151,13 @@ class MusicPlayer { this.queue.push({'url': vurl, 'title': await this.getVideoName(vurl)}); //eslint-disable-line no-await-in-loop } catch (err) { if (err.message !== 'Not found') { - this._logger.warn(err.message); - this._logger.debug(err.stack); + this._logger.verbose(err.message); + this._logger.silly(err.stack); } } } this._logger.debug(`Added ${playlistItems.length} songs to the queue`); + return playlistItems.length; } else if (!this.playing || !this.disp) { this._logger.debug(`Playing ${url}`); this.current = ({'url': url, 'title': await this.getVideoName(url)}); @@ -164,11 +165,9 @@ class MusicPlayer { this.queue.push(this.current); this.disp = this.conn.playStream(ytdl(url, - {filter: 'audioonly', quality: this.quality, liveBuffer: config.music.livePuffer || 20000}), + {filter: 'audioonly', quality: this.quality, liveBuffer: this.liveBuffer}), {volume: this.volume}); - this.disp.on('debug', (dbgmsg) => this._logger.silly(dbgmsg)); - this.disp.on('error', (err) => { this._logger.error(err.message); this._logger.debug(err.stack); @@ -201,21 +200,12 @@ class MusicPlayer { /** * Gets the name of the YouTube Video at url - * TODO: ytdl.getInfo * @param url {String} - * @returns {Promise<>} + * @returns {String} */ - getVideoName(url) { - return new Promise((resolve, reject) => { - yttl(utils.YouTube.getVideoIdFromUrl(url), (err, title) => { - if (err) { - this._logger.debug(JSON.stringify(err)); - reject(err); - } else { - resolve(title); - } - }); - }); + async getVideoName(url) { + let info = await ytdl.getBasicInfo(url); + return info.title; } /** @@ -224,12 +214,9 @@ class MusicPlayer { */ setVolume(percentage) { this._logger.verbose(`Setting volume to ${percentage}`); - if (this.disp !== null) { - this.volume = percentage; + this.volume = percentage; + if (this.disp !== null) this.disp.setVolume(percentage); - } else { - this._logger.warn("No dispatcher found."); - } } /** diff --git a/lib/utils/genericSql.js b/lib/utils/genericSql.js index c92ac4d..7d5025f 100644 --- a/lib/utils/genericSql.js +++ b/lib/utils/genericSql.js @@ -85,6 +85,16 @@ class GenericTypes { } } + get serialPK() { + switch (this.database) { + case 'sqlite': + return 'INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL'; + case 'postgresql': + default: + return 'SERIAL PRIMARY KEY UNIQUE'; + } + } + /** * Returns the VARCHAR type with the specified length. * @param length {Number} @@ -117,6 +127,22 @@ class GenericSql { any: 'ANY', all: 'ALL' }; + this.templates = { + idcolumn: new Column('id', this.types.serialPK, []) + }; + } + + /** + * Returns a value placeholder for the specified number. + * @param number {Number} - the variables position. + */ + parameter(number) { + switch (this.database) { + case 'postgresql': + return `$${number}`; + case 'sqlite': + return '?'; + } } /** @@ -180,7 +206,7 @@ class GenericSql { switch (this.database) { case 'postgresql': case 'sqlite': - return `COUNT(${colname})`; + return `COUNT(${colname}) count`; } } @@ -287,10 +313,12 @@ class GenericSql { * Updates the table with the rowValueObject. * @param table {String} - the table name * @param colValueObj {Object} - an object with keys as columnnames and values as columnvalues - * @param conditions {Array} - conditions for the update row selection (WHERE ... [OR ...][AND ...] + * @param conditions {Array|String} - conditions for the update row selection (WHERE ... [OR ...][AND ...] * @returns {string} */ update(table, colValueObj, conditions) { + if (!(conditions instanceof Array)) + conditions = [conditions]; switch (this.database) { case 'postgresql': case 'sqlite': @@ -302,16 +330,37 @@ class GenericSql { * Selects from a table * @param table {String} - the tablename * @param distinct {String|boolean} - should distinct values be selected? If yes provide distinct keyword. - * @param colnames {Array} - the rows to select - * @param conditions {Array} - conditions for the row selection (WHERE ... [OR ...][AND ...] - * @param operations {Array} - operations on the selected rows + * @param colnames {Array|String} - the rows to select + * @param conditions {Array|String} - conditions for the row selection (WHERE ... [OR ...][AND ...] + * @param operations {Array|String} - operations on the selected rows * @returns {String} */ select(table, distinct, colnames, conditions, operations) { + if (!(colnames instanceof Array)) + colnames = [colnames]; + if (!(conditions instanceof Array)) + conditions = [conditions]; + if (!(operations instanceof Array)) + operations = [operations]; + switch (this.database) { + case 'postgresql': + case 'sqlite': + return `SELECT${distinct? ' ' + distinct : ''} ${colnames.join(', ')} FROM ${table} ${conditions.join(' ')} ${operations.join(' ')}`; + } + } + + /** + * Deletes from a table + * @param table {String} - the table name + * @param conditions {Array|String} - conditions for the row selection (WHERE ... [OR ...][AND ...] + */ + delete(table, conditions) { + if (!(conditions instanceof Array)) + conditions = [conditions]; switch (this.database) { case 'postgresql': case 'sqlite': - return `SELECT${distinct? ' ' + distinct : ''} ${colnames.join(' ')} FROM ${table} ${conditions.join(' ')} ${operations.join(' ')}`; + return `DELETE FROM ${table} ${conditions.join(' ')}`; } } } @@ -327,6 +376,8 @@ class Column { this.name = name; this.type = type; this.constraints = constraints || []; + if (!(constraints instanceof Array)) + this.constraints = [constraints]; } /** @@ -338,12 +389,12 @@ class Column { } get sql() { - return `${this.name} ${this.type} ${this.constraints.join(',')}`; + return `${this.name} ${this.type} ${this.constraints.join(' ')}`; } } Object.assign(exports, { GenericSql: GenericSql, - GenericTypes: GenericSql, + GenericTypes: GenericTypes, Column: Column }); diff --git a/lib/web/index.js b/lib/web/index.js index c774637..ed3b9ca 100644 --- a/lib/web/index.js +++ b/lib/web/index.js @@ -8,6 +8,7 @@ const express = require('express'), fs = require('fs'), session = require('express-session'), SQLiteStore = require('connect-sqlite3')(session), + dblib = require('../../lib/database'), bodyParser = require('body-parser'), compileSass = require('express-compile-sass'), config = require('../../config.json'), @@ -62,7 +63,11 @@ exports.WebServer = class { if (!req.body.username || !req.body.password) { res.render('login', {msg: 'Please enter username and password.'}); } else { - let user = await this.maindb.get('SELECT * FROM users WHERE username = ? AND password = ?', [req.body.username, req.body.password]); + let sql = this.maindb.sql; + let user = await this.maindb.get(sql.select('users', false, '*', [ + sql.where('username', '=', sql.parameter(1)), + sql.and('password', '=', sql.parameter(2)) + ]), [req.body.username, req.body.password]); if (!user) { this._logger.debug(`User ${req.body.username} failed to authenticate`); res.render('login', {msg: 'Login failed!'}); @@ -150,13 +155,14 @@ exports.WebServer = class { */ async setReferenceObjects(objects) { this.maindb = objects.maindb; - await this.maindb.run(`${utils.sql.tableExistCreate} users ( - ${utils.sql.pkIdSerial}, - username VARCHAR(32) UNIQUE NOT NULL, - password VARCHAR(255) NOT NULL, - token VARCHAR(255) UNIQUE NOT NULL, - scope INTEGER NOT NULL DEFAULT 0 - )`); + let sql = this.maindb.sql; + await this.maindb.run(sql.createTableIfNotExists('users', [ + sql.templates.idcolumn, + new dblib.Column('username', sql.types.getVarchar(32), [sql.constraints.unique, sql.constraints.notNull]), + new dblib.Column('password', sql.types.getVarchar(255), [sql.constraints.notNull]), + new dblib.Column('token', sql.types.getVarchar(255), [sql.constraints.unique, sql.constraints.notNull]), + new dblib.Column('scope', sql.types.integer, [sql.constraints.notNull, sql.default(0)]) + ])); this.root = { client: { guilds: async (args) => { @@ -360,7 +366,7 @@ class Guild { async querySaved() { if (this.guildHandler.db) { let saved = []; - let rows = await this.guildHandler.db.all('SELECT * FROM playlists'); + let rows = await this.guildHandler.db.all(this.guildHandler.db.sql.select('playlists', false, '*')); for (let row of rows) saved.push({ id: generateID(['Media', row.url]), diff --git a/package.json b/package.json index 4d7f224..1d12fc5 100644 --- a/package.json +++ b/package.json @@ -19,14 +19,14 @@ "express-session": "1.15.6", "ffmpeg-binaries": "4.0.0", "fs-extra": "7.0.1", - "get-youtube-title": "1.0.0", "graphql": "14.1.1", "js-md5": "0.7.3", "js-sha512": "0.8.0", "js-yaml": "latest", "node-fetch": "^2.3.0", - "node-sass": "4.11.0", "node-opus": "0.3.1", + "node-sass": "4.11.0", + "pg": "^7.8.2", "promise-waterfall": "0.1.0", "pug": "2.0.3", "sqlite3": "4.0.6", @@ -44,7 +44,8 @@ "sinon": "7.2.6", "eslint-plugin-graphql": "3.0.3", "eslint": "5.15.0", - "eslint-plugin-promise": "4.0.1" + "eslint-plugin-promise": "4.0.1", + "opusscript": "0.0.6" }, "eslintConfig": { "parserOptions": { From 1ea11d073aac6c1e4132e4cae4616732ea9710dc Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sun, 10 Mar 2019 20:08:51 +0100 Subject: [PATCH 09/54] Added logging of messages - added new main database table `messages` to log all messages for statistic reasons --- bot.js | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/bot.js b/bot.js index 88d2927..947af8f 100644 --- a/bot.js +++ b/bot.js @@ -120,6 +120,14 @@ class Bot { new dblib.Column('text', sql.types.getVarchar(255), [sql.constraints.unique, sql.constraints.notNull]) ])); + await this.maindb.run(sql.createTableIfNotExists('messages', [ + sql.templates.idcolumn, + new dblib.Column('server', sql.types.getVarchar(255)), + new dblib.Column('channel', sql.types.getVarchar(255)), + new dblib.Column('username', sql.types.getVarchar(255), [sql.constraints.notNull]), + new dblib.Column('message', sql.types.text), + new dblib.Column('timestamp', sql.types.datetime, [sql.constraints.notNull, sql.default('NOW()')]) + ])); this.logger.debug('Loading Presences...'); await this.loadPresences(); } @@ -232,6 +240,35 @@ class Bot { gh.musicPlayer.checkListeners(); } }); + + this.client.on('message', async (msg) => { + try { + let sql = this.maindb.sql; + let server = null; + let channel = null; + let user = msg.author.tag; + let message = msg.content; + if (msg.guild) { + server = msg.guild.name; + channel = msg.channel.name; + await this.maindb.run(sql.insert('messages', { + server: sql.parameter(1), + channel: sql.parameter(2), + username: sql.parameter(3), + message: sql.parameter(4) + }), [server, channel, user, message]); + } else { + await this.maindb.run(sql.insert('messages', { + channel: sql.parameter(1), + username: sql.parameter(2), + message: sql.parameter(3) + }), ['PRIVATE', user, message]); + } + } catch (err) { + this.logger.warn(err.message); + this.logger.debug(err.stack); + } + }); } /** From 3da2d098c4ca2f439e0db1f301b5bfdfc9778cec Mon Sep 17 00:00:00 2001 From: Trivernis Date: Mon, 11 Mar 2019 18:10:15 +0100 Subject: [PATCH 10/54] Reverted changes to music lib - reverted to youtube title information --- CHANGELOG.md | 1 + lib/music/index.js | 17 +++++++++++++---- package.json | 3 ++- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c90f22a..35e966e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - config entry for `database` with supported values `postgresql` or `sqlite` - config entry for `databaseConnection` for postgresql (`user`, `host`, `password`, `database`, `port`) - table `settings` to each guild to store guild specific settings +- table `messages` to main database where messages are stored for statistical analysis and bug handling ## [0.11.0-beta] - 2019-03-03 ### Changed diff --git a/lib/music/index.js b/lib/music/index.js index 114b9af..5c7cde5 100644 --- a/lib/music/index.js +++ b/lib/music/index.js @@ -1,5 +1,6 @@ const ytdl = require("ytdl-core"), ypi = require('youtube-playlist-info'), + yttl = require('get-youtube-title'), config = require('../../config.json'), utils = require('../utils/index.js'), logging = require('../utils/logging'), @@ -201,11 +202,19 @@ class MusicPlayer { /** * Gets the name of the YouTube Video at url * @param url {String} - * @returns {String} + * @returns {Promise} */ - async getVideoName(url) { - let info = await ytdl.getBasicInfo(url); - return info.title; + getVideoName(url) { + return new Promise((resolve, reject) => { + yttl(utils.YouTube.getVideoIdFromUrl(url), (err, title) => { + if (err) { + this._logger.debug(JSON.stringify(err)); + reject(err); + } else { + resolve(title); + } + }); + }); } /** diff --git a/package.json b/package.json index 1d12fc5..885897b 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ "winston": "3.2.1", "winston-daily-rotate-file": "3.8.0", "youtube-playlist-info": "1.1.2", - "ytdl-core": "0.29.1" + "ytdl-core": "0.29.1", + "get-youtube-title": "latest" }, "devDependencies": { "assert": "1.4.1", From 50ccb7c2295ddae6a2295bfc2822a223d0a43c01 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 18 Mar 2019 14:45:19 +0000 Subject: [PATCH 11/54] Update dependency compression to v1.7.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4c402d9..3a8ede2 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "dependencies": { "args-parser": "1.1.0", "body-parser": "1.18.3", - "compression": "1.7.3", + "compression": "1.7.4", "connect-sqlite3": "0.9.11", "cors": "2.8.5", "discord.js": "11.4.2", From 663833c352d031a9f4703956fd5d1d08b9e6d668 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 18 Mar 2019 16:26:53 +0000 Subject: [PATCH 12/54] Update dependency eslint to v5.15.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4c402d9..11d3449 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "rewire": "4.0.1", "sinon": "7.2.6", "eslint-plugin-graphql": "3.0.3", - "eslint": "5.15.0", + "eslint": "5.15.3", "eslint-plugin-promise": "4.0.1" }, "eslintConfig": { From ed1cb1a812bf929fef96e907b61d5d3bc5521790 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Wed, 20 Mar 2019 19:39:58 +0100 Subject: [PATCH 13/54] Major changes to command api - changed return type of command parsing to response - changed Response to extend the EventEmitter - changed MusicPlayer to extend the EvenEmitter - added continuos update to now playing if the message is the latest in the channel - started redesigning the graphql api --- CHANGELOG.md | 6 +- commands/MusicCommands/index.js | 22 ++ lib/command/index.js | 64 +++-- lib/guilds/index.js | 33 +++ lib/message/index.js | 57 ++++- lib/music/index.js | 17 +- lib/state/EventGroups.js | 163 ------------ lib/state/index.js | 99 ------- lib/utils/extended-events.js | 76 ++++++ lib/utils/genericSql.js | 13 + lib/utils/index.js | 15 +- lib/web/graphql-schema.js | 145 +++++++++++ lib/web/graphql-types.js | 440 ++++++++++++++++++++++++++++++++ lib/web/schema.graphqls | 429 +++++++++++++++++++++++++++++++ 14 files changed, 1287 insertions(+), 292 deletions(-) delete mode 100644 lib/state/EventGroups.js delete mode 100644 lib/state/index.js create mode 100644 lib/utils/extended-events.js create mode 100644 lib/web/graphql-schema.js create mode 100644 lib/web/graphql-types.js create mode 100644 lib/web/schema.graphqls diff --git a/CHANGELOG.md b/CHANGELOG.md index 35e966e..beecd2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,10 +19,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - moved commands outside of `lib` - switched from opusscript to node-opus for voice - all hard coded sql statements to generic sql generation +- MusicPlayer to extend the default EventEmitter +- MessageHandler to accept instances of Response and redirect events to it ### Added -- state lib with `EventRouter` and `EventGroup` and `Event` classes -- Subclasses of EventRouter for client events groupes `Client`, `Channel`, `Message` and `Guild` - Utility classes for generic SQL Statements - logging of unrejected promises - database class for database abstraction (lib/database) @@ -30,6 +30,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - config entry for `databaseConnection` for postgresql (`user`, `host`, `password`, `database`, `port`) - table `settings` to each guild to store guild specific settings - table `messages` to main database where messages are stored for statistical analysis and bug handling +- ExtendedEventEmitter class in lib/utils/extended-events.js +- Response object that allows the registration of events for messages ## [0.11.0-beta] - 2019-03-03 ### Changed diff --git a/commands/MusicCommands/index.js b/commands/MusicCommands/index.js index 7283379..1244a07 100644 --- a/commands/MusicCommands/index.js +++ b/commands/MusicCommands/index.js @@ -228,6 +228,28 @@ class MusicCommandModule extends cmdLib.CommandModule { .setColor(0x00aaff); else return this.template.media_current.response.not_playing; + }, async (response) => { + let message = response.message; + let gh = await this._getGuildHandler(message.guild); + + if (message.editable && gh.musicPlayer) { + let next = (song) => { + message.edit('', new cmdLib.ExtendedRichEmbed('Now playing:') + .setDescription(`[${song.title}](${song.url})`) + .setImage(utils.YouTube.getVideoThumbnailUrlFromUrl(song.url)) + .setColor(0x00aaff)); + if (message.id !== message.channel.lastMessageID) { + gh.musicPlayer.off('next', next); + message.delete(); + } + }; + gh.musicPlayer.on('next', next); + gh.musicPlayer.on('stop', () => { + gh.musicPlayer.off('next', next); + message.delete(); + }); + response.on('delete', () => gh.musicPlayer.off('next', next)); + } }) ); diff --git a/lib/command/index.js b/lib/command/index.js index c7dcd0b..45fd34f 100644 --- a/lib/command/index.js +++ b/lib/command/index.js @@ -3,22 +3,42 @@ const Discord = require('discord.js'), fsx = require('fs-extra'), logging = require('../utils/logging'), config = require('../../config.json'), + xevents = require('../utils/extended-events'), utils = require('../utils'); -const scopes = { - 'Global': 0, - 'User': 1, - 'Guild': 2 -}; +const scopes = new utils.Enum([ + 'Global', + 'User', + 'Guild' +]); + +/** + * The answer message object that is used for easyer access to events. + */ +class Response extends xevents.ExtendedEventEmitter { + + /** + * Constructor. + * @param content + */ + constructor(content) { + super(); + this.content = content; + this.message = null; + } +} class Answer { /** * Creates an new Answer object with _func as answer logic. - * @param func + * @param func {function} - the function to evaluate the answer + * @param [onSent] {function} - executed when the response was sent */ - constructor(func) { + constructor(func, onSent) { this._func = func; + this.listeners = onSent? {sent: onSent} : {}; + this.lastResponse = null; } /** @@ -27,14 +47,28 @@ class Answer { * @param message * @param kwargs * @param argsString - * @returns {Promise<*>} + * @returns {Promise} */ async evaluate(message, kwargs, argsString) { let result = this._func(message, kwargs, argsString); if (result instanceof Promise) - return await utils.resolveNestedPromise(result); + return this._getResponseInstance(await utils.resolveNestedPromise(result)); else - return result; + return this._getResponseInstance(result); + } + + /** + * Returns a response instance with listeners attached if defined. + * @param responseContent + * @returns {Response} + * @private + */ + _getResponseInstance(responseContent) { + this.lastResponse = new Response(responseContent); + + if (this.listeners) + this.lastResponse.addListeners(this.listeners); + return this.lastResponse; } } @@ -66,7 +100,7 @@ class Command { * @param message {Discord.Message} * @param kwargs {JSON} * @param argsString {String} The raw argument string. - * @returns {String} + * @returns {Response} */ async answer(message, kwargs, argsString) { return await this.answObj.evaluate(message, kwargs, argsString); @@ -104,7 +138,7 @@ class CommandHandler { * Handles the command and responds to the message. * @param commandMessage {String} * @param message {Discord.Message} - * @returns {Boolean | String | Promise} + * @returns {Response | Promise} */ handleCommand(commandMessage, message) { this._logger.debug(`Handling command ${commandMessage}`); @@ -129,14 +163,14 @@ class CommandHandler { return command.answer(message, kwargs, argsString); } else if (command) { this._logger.silly(`Permission ${command.permission} denied for command ${commandName}`); - return "You don't have permission for this command"; + return new Response("You don't have permission for this command"); } else { this._logger.silly(`Command ${commandName} not found.`); - return false; + return null; } } else { this._logger.silly(`No prefix found in command ${commandName}`); - return false; + return null; } } diff --git a/lib/guilds/index.js b/lib/guilds/index.js index baf3f24..9b55a78 100644 --- a/lib/guilds/index.js +++ b/lib/guilds/index.js @@ -44,6 +44,39 @@ class GuildDatabase extends dblib.Database { this._logger.silly('Created Table settings.'); } + /** + * Returns the value of the column where the key has the value keyvalue + * @param table {String} - the table name + * @param column {String} - the name of the column + * @param keyname {String} - the name of the key + * @param keyvalue {*} - the value of the key + * @returns {Promise<*>} + */ + async getSingleValue(table, column, keyname, keyvalue) { + let result = await this.get(this.sql.select(table, false, column, + this.sql.where(this.sql.parameter(1), '=', this.sql.parameter(2))), + [keyname, keyvalue]); + if (result) + return result[column]; + else + return null; + } + + /** + * Returns either the whole table or a limited version + * @param tablename + * @param limit + * @returns {Promise} + */ + async getTableContent(tablename, limit) { + if (limit) + return await this.all(this.sql.select(tablename, false, ['*'], [], [ + this.sql.limit(limit) + ])); + else + return await this.all(this.sql.select(tablename, false, ['*'], [], [])); + } + /** * Get the value of a setting * @param name diff --git a/lib/message/index.js b/lib/message/index.js index e95bf1f..ea61aba 100644 --- a/lib/message/index.js +++ b/lib/message/index.js @@ -23,6 +23,7 @@ class MessageHandler { this.guildCmdHandler = new cmdLib.CommandHandler(config.prefix, cmdLib.CommandScopes.Guild); this.userRates = {}; + this.registeredResponses = {}; this._registerEvents(); } @@ -68,7 +69,8 @@ class MessageHandler { * @private */ _registerEvents() { - this.logger.debug('Registering message event...'); + this.logger.debug('Registering message events...'); + this.discordClient.on('message', async (msg) => { this.logger.verbose(`<${msg.guild? msg.channel.name+'@'+msg.guild.name : 'PRIVATE'}> ${msg.author.tag}: ${msg.content}`); if (msg.author !== this.discordClient.user @@ -81,6 +83,32 @@ class MessageHandler { this.logger.debug('Executed command sequence'); } }); + + this.discordClient.on('messageReactionAdd', (messageReaction, user) => { + let responseInstance = this.registeredResponses[messageReaction.message]; + if (responseInstance) + responseInstance.emit('reactionAdd', messageReaction, user); + }); + + this.discordClient.on('messageReactionRemove', (messageReaction, user) => { + let responseInstance = this.registeredResponses[messageReaction.message]; + if (responseInstance) + responseInstance.emit('reactionRemove', messageReaction, user); + }); + + this.discordClient.on('messageReactionRemoveAll', (message) => { + let responseInstance = this.registeredResponses[message]; + if (responseInstance) + responseInstance.emit('reactionRemoveAll', message); + }); + + this.discordClient.on('messageDelete', (message) => { + let responseInstance = this.registeredResponses[message]; + if (responseInstance) { + responseInstance.on('delete', message); + delete this.registeredResponses[message]; + } + }); } /** @@ -124,9 +152,9 @@ class MessageHandler { this.logger.silly(`globalResult: ${globalResult}, scopeResult: ${scopeResult}`); if (scopeResult) - this._answerMessage(message, scopeResult); + await this._answerMessage(message, scopeResult); else if (globalResult) - this._answerMessage(message, globalResult); + await this._answerMessage(message, globalResult); } catch (err) { this.logger.verbose(err.message); this.logger.silly(err.stack); @@ -149,16 +177,25 @@ class MessageHandler { /** * Answers * @param message {Discord.Message} - * @param answer {String | Discord.RichEmbed} + * @param response {Response} * @private */ - _answerMessage(message, answer) { - this.logger.debug(`Sending answer ${answer}`); - if (answer) - if (answer instanceof Discord.RichEmbed) - message.channel.send('', answer); + async _answerMessage(message, response) { + this.logger.debug(`Sending answer ${response.content}`); + if (response && response.content) { + let responseMessage = null; + + if (response.content instanceof Discord.RichEmbed) + responseMessage = await message.channel.send('', response.content); else - message.channel.send(answer); + responseMessage = await message.channel.send(response.content); + + if (response.hasListeners) + this.registeredResponses[responseMessage] = response; + + response.message = responseMessage; + response.emit('sent', response); + } } /** diff --git a/lib/music/index.js b/lib/music/index.js index 5c7cde5..b7eff4b 100644 --- a/lib/music/index.js +++ b/lib/music/index.js @@ -2,7 +2,8 @@ const ytdl = require("ytdl-core"), ypi = require('youtube-playlist-info'), yttl = require('get-youtube-title'), config = require('../../config.json'), - utils = require('../utils/index.js'), + utils = require('../utils'), + xevents = require('../utils/extended-events'), logging = require('../utils/logging'), ytapiKey = config.api.youTubeApiKey; @@ -10,8 +11,13 @@ const ytdl = require("ytdl-core"), * The Music Player class is used to handle music playing tasks on Discord Servers (Guilds). * @type {MusicPlayer} */ -class MusicPlayer { +class MusicPlayer extends xevents.ExtendedEventEmitter{ + /** + * Constructor + * @param [voiceChannel] {Discord.VoiceChannel} + */ constructor(voiceChannel) { + super(); this.conn = null; this.disp = null; this.queue = []; @@ -46,6 +52,7 @@ class MusicPlayer { let connection = await this.voiceChannel.join(); this._logger.info(`Connected to Voicechannel ${this.voiceChannel.name}`); this.conn = connection; + this.emit('connected'); } /** @@ -56,6 +63,7 @@ class MusicPlayer { this.repeat = value; if (this.current) this.queue.push(this.current); + this.emit('listenOnRepeat', this.repeat); } /** @@ -182,6 +190,7 @@ class MusicPlayer { this.current = this.queue.shift(); if (this.repeat) // listen on repeat this.queue.push(this.current); + this.emit('next', this.current); this.playYouTube(this.current.url).catch((err) => this._logger.warn(err.message)); } else { this.stop(); @@ -280,6 +289,7 @@ class MusicPlayer { } catch (error) { this._logger.verbose(JSON.stringify(error)); } + this.emit('stop'); } /** @@ -303,6 +313,7 @@ class MusicPlayer { this.stop(); } } + this.emit('skip', this.current); } /** @@ -318,6 +329,7 @@ class MusicPlayer { */ shuffle() { this.queue = utils.shuffleArray(this.queue); + this.emit('shuffle'); } /** @@ -325,6 +337,7 @@ class MusicPlayer { */ clear() { this.queue = []; + this.emit('clear'); } } diff --git a/lib/state/EventGroups.js b/lib/state/EventGroups.js deleted file mode 100644 index ca22fe2..0000000 --- a/lib/state/EventGroups.js +++ /dev/null @@ -1,163 +0,0 @@ -let stateLib = require("index.js"); - -class DiscordGuildEvents extends EventGroup { - - constructor(client) { - super(); - this._registerClientEvents(client); - } - - /** - * Registeres the client events to the EventGroup - * @param client {Discord.Client} - * @private - */ - _registerClientEvents(client) { - this.registerEvent(new stateLib.Event('clientUserGuildSettingsUpdate')) - .registerEvent(new stateLib.Event('clientUserSettingsUpdate')) - .registerEvent(new stateLib.Event('emojiCreate')) - .registerEvent(new stateLib.Event('emojiDelete')) - .registerEvent(new stateLib.Event('emojiUpdate')) - .registerEvent(new stateLib.Event('guildBanAdd')) - .registerEvent(new stateLib.Event('guildBanRemove')) - .registerEvent(new stateLib.Event('guildCreate')) - .registerEvent(new stateLib.Event('guildDelete')) - .registerEvent(new stateLib.Event('guildMemberAdd')) - .registerEvent(new stateLib.Event('guildMemberAvailable')) - .registerEvent(new stateLib.Event('guildMemberRemove')) - .registerEvent(new stateLib.Event('guildMemberChunk')) - .registerEvent(new stateLib.Event('guildMemberSpeaking')) - .registerEvent(new stateLib.Event('guildMemberUpdate')) - .registerEvent(new stateLib.Event('guildUnavailable')) - .registerEvent(new stateLib.Event('guildUpdate')) - .registerEvent(new stateLib.Event('presenceUpdate')) - .registerEvent(new stateLib.Event('roleCreate')) - .registerEvent(new stateLib.Event('roleDelete')) - .registerEvent(new stateLib.Event('roleUpdate')) - .registerEvent(new stateLib.Event('userNoteUpdate')) - .registerEvent(new stateLib.Event('userUpdate')) - .registerEvent(new stateLib.Event('voiceStateUpdate')); - - client.on('clientUserGuildSettingsUpdate', (...o) => this.events.clientUserGuildSettingsUpdate.fire(o)); - client.on('clientUserSettingsUpdate', (...o) => this.events.clientUserSettingsUpdate.fire(o)); - client.on('emojiCreate', (...o) => this.events.emojiCreate.fire(o)); - client.on('emojiDelete', (...o) => this.events.emojiDelete.fire(o)); - client.on('emojiUpdate', (...o) => this.events.emojiUpdate.fire(o)); - client.on('guildBanAdd', (...o) => this.events.guildBanAdd.fire(o)); - client.on('guildBanRemove', (...o) => this.events.guildBanRemove.fire(o)); - client.on('guildCreate', (...o) => this.events.guildCreate.fire(o)); - client.on('guildDelete', (...o) => this.events.guildDelete.fire(o)); - client.on('guildMemberAdd', (...o) => this.events.guildMemberAdd.fire(o)); - client.on('guildMemberAvailable', (...o) => this.events.guildMemberAvailable.fire(o)); - client.on('guildMemberRemove', (...o) => this.events.guildMemberRemove.fire(o)); - client.on('guildMemberChunk', (...o) => this.events.guildMemberChunk.fire(o)); - client.on('guildMemberSpeaking', (...o) => this.events.guildMemberSpeaking.fire(o)); - client.on('guildMemberUpdate', (...o) => this.events.guildMemberUpdate.fire(o)); - client.on('guildUnavailable', (...o) => this.events.guildUnavailable.fire(o)); - client.on('guildUpdate', (...o) => this.events.guildUpdate.fire(o)); - client.on('presenceUpdate', (...o) => this.events.presenceUpdate.fire(o)); - client.on('roleCreate', (...o) => this.events.roleCreate.fire(o)); - client.on('roleDelete', (...o) => this.events.roleDelete.fire(o)); - client.on('roleUpdate', (...o) => this.events.roleUpdate.fire(o)); - client.on('userNoteUpdate', (...o) => this.events.userNoteUpdate.fire(o)); - client.on('userUpdate', (...o) => this.events.userUpdate.fire(o)); - client.on('voiceStateUpdate', (...o) => this.events.voiceStateUpdate.fire(o)); - - - } -} - -class DiscordMessageEvents extends stateLib.EventGroup { - - constructor(client) { - super(); - this._registerMessageEvents(client); - } - - /** - * Registeres all client message events - * @param client {Discord.Client} - * @private - */ - _registerMessageEvents(client) { - this.registerEvent(new stateLib.Event('messageDelete')) - .registerEvent(new stateLib.Event('messageDeleteBulk')) - .registerEvent(new stateLib.Event('messageReactionAdd')) - .registerEvent(new stateLib.Event('messageReactionRemove')) - .registerEvent(new stateLib.Event('messageReactionRemoveAll')) - .registerEvent(new stateLib.Event('messageUpdate')) - .registerEvent(new stateLib.Event('message')); - - client.on('messageDelete', (...o) => this.events.messageDelete.fire(o)); - client.on('messageDeleteBulk', (...o) => this.events.messageDeleteBulk.fire(o)); - client.on('messageReactionAdd', (...o) => this.events.messageReactionAdd.fire(o)); - client.on('messageReactionRemove', (...o) => this.events.messageReactionRemove.fire(o)); - client.on('messageReactionRemoveAll', (...o) => this.events.messageReactionRemoveAll.fire(o)); - client.on('messageUpdate', (...o) => this.events.messageUpdate.fire(o)); - client.on('message', (...o) => this.events.message.fire(o)); - } -} - -class DiscordChannelEvents extends stateLib.EventGroup { - - constructor(client) { - super(); - this._registerChannelEvents(client); - } - - /** - * Registers all events for discord channels. - * @param client {Discord.Client} - * @private - */ - _registerChannelEvents(client) { - this.registerEvent(new stateLib.Event('channelCreate')) - .registerEvent(new stateLib.Event('channelDelete')) - .registerEvent(new stateLib.Event('channelPinsUpdate')) - .registerEvent(new stateLib.Event('channelUpdate')) - .registerEvent(new stateLib.Event('typingStart')) - .registerEvent(new stateLib.Event('typingStop')); - - client.on('channelCreate', (...o) => this.events.channelCreate.fire(o)); - client.on('channelDelete', (...o) => this.events.channelDelete.fire(o)); - client.on('channelPinsUpdate', (...o) => this.events.channelPinsUpdate.fire(o)); - client.on('channelUpdate', (...o) => this.events.channelUpdate.fire(o)); - client.on('typingStart', (...o) => this.events.typingStart.fire(o)); - client.on('typingStop', (...o) => this.events.typingStop.fire(o)); - } - -} - -class DiscordClientEvents extends stateLib.EventGroup { - - constructor(client) { - super(); - this._registerClientEvents(client); - } - - /** - * Registers Discord client events - * @param client {Discord.Client} - * @private - */ - _registerClientEvents(client) { - this.registerEvent(new stateLib.Event('debug')) - .registerEvent(new stateLib.Event('warn')) - .registerEvent(new stateLib.Event('error')) - .registerEvent(new stateLib.Event('ready')) - .registerEvent(new stateLib.Event('resume')) - .registerEvent(new stateLib.Event('disconnect')) - .registerEvent(new stateLib.Event('reconnecting')) - .registerEvent(new stateLib.Event('rateLimit')); - - client.on('debug', (...o) => this.events.debug.fire(o)); - client.on('warn', (...o) => this.events.warn.fire(o)); - client.on('error', (...o) => this.events.error.fire(o)); - client.on('ready', (...o) => this.events.ready.fire(o)); - client.on('resume', (...o) => this.events.resume.fire(o)); - client.on('disconnect', (...o) => this.events.disconnect.fire(o)); - client.on('reconnecting', (...o) => this.events.reconnecting.fire(o)); - client.on('rateLimit', (...o) => this.events.rateLimit.fire(o)); - client.on('presenceUpdate', (...o) => this.events.presenceUpdate.fire(o)); - } -} diff --git a/lib/state/index.js b/lib/state/index.js deleted file mode 100644 index 3226cc3..0000000 --- a/lib/state/index.js +++ /dev/null @@ -1,99 +0,0 @@ -const logging = require('../utils/logging'); - -class EventRouter { - - constructor() { - this._logger = new logging.Logger(this); - this.eventGroups = {}; - } - - /** - * Fires an event of an event group with event data. - * @param eventGroup {String} - * @param eventName {String} - * @param eventData {Object} - */ - fireEvent(eventGroup, eventName, eventData) { - if (this.eventGroups[eventGroup] instanceof EventGroup) - this.eventGroups[eventGroup].fireEvent(eventName, eventData); - return this; - } - - /** - * Adds an EventRoute to the EventRouter - * @param group {EventGroup} - */ - registerEventGroup(group) { - this.eventGroups[group.name] = name; - } - -} - -class EventGroup { - - /** - * Creates a new EventGroup with the given name. - * @param [name] {String} - */ - constructor(name) { - this._logger = new logging.Logger(this); - this.name = name || this.constructor.name; - this.events = {}; - } - - fireEvent(eventName, eventData) { - if (this.events[eventName] instanceof Event) - this.events[eventName].fire(eventData); - return this; - } - - /** - * Registeres an Event to the EventGroup - * @param event {Event} - */ - registerEvent(event) { - this.events[event.name] = event; - } -} - -class Event { - - /** - * Creates a new Event with the given name. - * @param name - */ - constructor(name) { - this._logger = new logging.Logger(this); - this.name = name; - this.handlers = []; - } - - /** - * Adds an event handler to the Event - * @param handler {Function} - */ - addHandler(handler) { - this.handlers.push(handler); - return this; - } - - /** - * Fires the event with the given data. - * @param data {Object} - */ - fire(data) { - for (let handler in this.handlers) - try { - handler(data); - } catch (err) { - this._logger.verbose(err.message); - this._logger.silly(err.stack); - } - } -} - -Object.assign(exports, { - EventRouter: EventRouter, - EventGroup: EventGroup, - Event: Event -}); diff --git a/lib/utils/extended-events.js b/lib/utils/extended-events.js new file mode 100644 index 0000000..fc15186 --- /dev/null +++ b/lib/utils/extended-events.js @@ -0,0 +1,76 @@ +const logging = require('../utils/logging'), + EventEmitter = require('events'); + +/** + * Extends the event emitter with some useful features. + */ +class ExtendedEventEmitter extends EventEmitter { + + /** + * Constructor. + * @param [name] {String} + */ + constructor(name) { + super(); + this._logger = new logging.Logger(`${name}-${this.constructor.name}`); + this._registerDefault(); + } + + /** + * Registeres the error event to the logger so it won't crash the bot. + * @private + */ + _registerDefault() { + this.on('error', (err) => { + this._logger.error(err.message); + this._logger.debug(err.stack); + }); + } + + /** + * Adds an object of events with listeners to the bot. + * @param eventListenerObject + * @returns {ExtendedEventEmitter} + */ + addListeners(eventListenerObject) { + for (let [event, listener] of Object.entries(eventListenerObject)) + this.on(event, listener); + return this; + } + + /** + * Returns all registered events. + * @returns {*|Array|string[]} + */ + get events() { + return this.eventNames(); + } + + /** + * Wrapper around getMaxListeners function + * @returns {*|number} + */ + get maxListeners() { + return this.getMaxListeners(); + } + + /** + * Wrapper around setMaxListeners function. + * @param n + * @returns {this | this | Cluster | *} + */ + set maxListeners(n) { + return this.setMaxListeners(n); + } + + /** + * Returns if the emitter has additional listeners apart from the error listener. + */ + get hasListeners() { + return this.events.count > 1; + } +} + +Object.assign(exports, { + ExtendedEventEmitter: ExtendedEventEmitter, +}); diff --git a/lib/utils/genericSql.js b/lib/utils/genericSql.js index 7d5025f..607fe1f 100644 --- a/lib/utils/genericSql.js +++ b/lib/utils/genericSql.js @@ -265,6 +265,19 @@ class GenericSql { } } + /** + * A limit statement. + * @param count {Number} - the number of rows to return + * @returns {string} + */ + limit(count) { + switch (this.database) { + case 'postgresql': + case 'sqlite': + return `LIMIT ${count}`; + } + } + /** * Create Table statement * @param table {String} diff --git a/lib/utils/index.js b/lib/utils/index.js index a2354c3..14e2fb5 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -129,6 +129,18 @@ async function resolveNestedPromise (promise) { /* Classes */ +class Enum { + /** + * Constructor. + * @param symbols {Array} + */ + constructor(symbols) { + for (let symbol in symbols) + this[symbol] = symbols; + Object.freeze(this); + } +} + class YouTube { /** * returns if an url is a valid youtube url (without checking for an entity id) @@ -279,5 +291,6 @@ Object.assign(exports, { getExtension: getFileExtension, getFileExtension: getFileExtension, objectDeepFind: objectDeepFind, - Cleanup: Cleanup + Cleanup: Cleanup, + Enum: Enum }); diff --git a/lib/web/graphql-schema.js b/lib/web/graphql-schema.js new file mode 100644 index 0000000..95e484e --- /dev/null +++ b/lib/web/graphql-schema.js @@ -0,0 +1,145 @@ +const graphql = require('graphql'), + fs = require('fs'); + +class BotGraphql { + constructor(bot) { + this.schema = graphql.buildSchema(fs.readFileSync('.lib/web/schema.graphqls', 'utf-8')); + this.root = { + Query: new Query(bot) + }; + } +} + +/** + * Easyer managing of page info + */ +class PageInfo { + /** + * constructor. + * @param total {Number} - the total number of entries + * @param perPage {Number} - the number of entries per page + * @param currentPage {Number} - the current page's index + * @param lastPage {Number} - the index of the last page + * @param hasNext {Boolean} - is there a next page? + */ + constructor(total, perPage, currentPage, lastPage, hasNext) { + this.total = total; + this.perPage = perPage; + this.currentPage = currentPage; + this.lastPage = lastPage; + this.hasNext = hasNext; + } +} + +/** + * Generic edge + */ +class Edge { + /** + * contructor. + * @param node {Object} - the node belonging to the edge + * @param edgeProps {Object} - additional properties of the edge + */ + constructor(node, edgeProps) { + this.node = node; + Object.assign(this, edgeProps); + } +} + +/** + * Generic connection + */ +class Connection { + /** + * constructor. + * @param edges {Array} - the edges of the connection + * @param pageInfo {PageInfo} - page info for the connection + */ + constructor(edges, pageInfo) { + this.edges = edges; + this.nodes = this.edges.map(x => x.node); + this.pageInfo = pageInfo; + } +} + +/** + * Manages pagination + */ +class Paginator { + /** + * constructor. + * @param edges {Array} - the edges for the pages + * @param perPage {Number} - the number of entries per page + */ + constructor(edges, perPage) { + this._entries = edges; + this.perPage = perPage; + } + + /** + * Get the specific page + * @param page {Number} - the page's number + * @param [perPage] {Number} - the number of entries per page + * @returns {Connection} + */ + getPage(page, perPage) { + perPage = perPage || this.perPage; + let startIndex = (page - 1) * perPage; + let endIndex = startIndex + perPage; + let lastPage = Math.ceil(this._entries.length / perPage); + return new Connection( + this._entries.slice(startIndex, endIndex) || [], + new PageInfo(this._entries.length, perPage, page, lastPage, page !== lastPage) + ); + } + + /** + * Updates the entries of the Paginator. + * @param entries + */ + updateEntries(entries) { + this._entries = entries; + } +} + +class MusicPlayer { + constructor(guildHandler) { + + } +} + +class Guild { + constructor(guild) { + + } +} + +class Client { + constructor(bot) { + this._bot = bot; + this._client = bot.client; + this.guildPaginator = new Paginator() + } + + _getGuildEdges() { + let guildHandlerPaginator = Array.from(this._client.guilds.values()).map(x => new Edge( + + )); + return Array.from(this._client.guilds.values()).map(x => new Edge( + new Guild(x), + { + musicPlayer: new MusicPlayer(this._bot.getGuildHandler(x)), + new Connection( + bot.getGuildHandler(x).savedMedia + ) + } + )); + } +} + +class Query { + constructor(bot) { + this._bot = bot; + this.client = new Client(bot); + } +} diff --git a/lib/web/graphql-types.js b/lib/web/graphql-types.js new file mode 100644 index 0000000..8545849 --- /dev/null +++ b/lib/web/graphql-types.js @@ -0,0 +1,440 @@ +const graphql = require('graphql'); + +let pageInfoType = new graphql.GraphQLObjectType({ + name: 'PageInfo', + fields: { + total: { + type: graphql.assertNonNullType(graphql.GraphQLInt), + description: 'total number of pages' + }, + perPage: { + type: graphql.assertNonNullType(graphql.GraphQLInt), + description: 'number of entries per page' + }, + currentPage: { + type: graphql.assertNonNullType(graphql.GraphQLInt), + description: 'current page' + }, + lastPage: { + type: graphql.assertNonNullType(graphql.GraphQLInt), + description: 'last page' + }, + hasNextPage: { + type: graphql.assertNonNullType(graphql.GraphQLBoolean), + description: 'does the connection have a next page' + } + } +}); + +let mediaEntryType = new graphql.GraphQLObjectType({ + + name: 'MediaEntry', + fields: { + id: { + type: graphql.GraphQLID, + description: 'id of the media entry' + }, + url: { + type: graphql.GraphQLString, + description: 'url to the YouTube video' + }, + name: { + type: graphql.GraphQLString, + description: 'title of the YouTube video' + }, + thumbnail: { + type: graphql.GraphQLString, + description: 'thumbnail of the YouTube video' + } + } +}); + +let mediaEntryEdgeType = new graphql.GraphQLObjectType({ + name: 'MediaEntryEdge', + fields: { + id: { + type: graphql.GraphQLID, + description: 'the connection id' + }, + position: { + type: graphql.GraphQLInt, + description: 'position in the queue' + }, + node: { + type: mediaEntryType, + description: 'the media entry node' + } + } +}); + +let mediaEntryConnectionType = new graphql.GraphQLObjectType({ + name: 'MediaEntryConnection', + fields: { + edges: { + type: graphql.GraphQLList(mediaEntryEdgeType) + }, + nodes: { + type: graphql.GraphQLList(mediaEntryType) + }, + pageInfo: { + type: graphql.assertNonNullType(pageInfoType), + description: 'pagination information' + } + } +}); + +let musicPlayerType = new graphql.GraphQLObjectType({ + + name: 'MusicPlayer', + fields: { + queue: { + type: new graphql.GraphQLList(mediaEntryConnectionType), + description: 'media entries in the music queue' + }, + queueCount: { + type: graphql.GraphQLInt, + description: 'number of media entries in the queue' + }, + songStartTime: { + type: graphql.GraphQLString + }, + playing: { + type: graphql.GraphQLBoolean + }, + volume: { + type: graphql.GraphQLFloat + }, + repeat: { + type: graphql.GraphQLBoolean + }, + currentSong: { + type: mediaEntryType + }, + quality: { + type: graphql.GraphQLString + }, + voiceChannel: { + type: graphql.GraphQLString + }, + connected: { + type: graphql.GraphQLBoolean + }, + paused: { + type: graphql.GraphQLBoolean + } + } +}); + +let presenceType = new graphql.GraphQLObjectType({ + + name: 'Presence', + fields: { + game: { + type: graphql.GraphQLString + }, + status: { + type: graphql.GraphQLString + } + } +}); + +let userType = new graphql.GraphQLObjectType({ + + name: 'User', + fields: { + id: { + type: graphql.GraphQLID + }, + discordId: { + type: graphql.GraphQLID + }, + name: { + type: graphql.GraphQLString + }, + avatar: { + type: graphql.GraphQLString + }, + bot: { + type: graphql.GraphQLBoolean + }, + tag: { + type: graphql.GraphQLString + }, + presence: { + type: presenceType + } + } +}); + +let guildMemberType = new graphql.GraphQLObjectType({ + name: 'GuildMember', + fields: { + id: { + type: graphql.assertNonNullType(graphql.GraphQLID), + description: 'id of the guild member' + }, + discordId: { + type: graphql.GraphQLID + }, + user: { + type: userType, + description: 'the user instance of the guild member' + }, + nickname: { + type: graphql.GraphQLString, + description: 'the nickname of the guild member' + }, + roles: { + type: graphql.GraphQLList(roleType), + description: 'the roles of the guild member' + }, + highestRole: { + type: roleType, + description: 'the highest role of the guild member' + } + } +}); + +let userRoleEdgeType = new graphql.GraphQLObjectType({ + name: 'userRoleEdge', + fields: { + id: { + type: graphql.GraphQLID, + description: 'the connection id' + }, + node: { + type: guildMemberType, + description: 'guild member edge of the role' + }, + isHighest: { + type: graphql.GraphQLBoolean, + description: 'is the role the highest of the guild member' + } + } +}); + +let userRoleConnectionType = new graphql.GraphQLObjectType({ + name: 'UserRoleConnection', + fields: { + edges: { + type: graphql.GraphQLList(userRoleEdgeType) + }, + nodes: { + type: graphql.GraphQLList(userType) + }, + pageInfoType: { + type: graphql.assertNonNullType(pageInfoType), + description: 'pagination information' + } + } +}); + +let roleType = new graphql.GraphQLObjectType({ + + name: 'Role', + fields: { + id: { + type: graphql.GraphQLID + }, + discordId: { + type: graphql.GraphQLID + }, + name: { + type: graphql.GraphQLString + }, + color: { + type: graphql.GraphQLString + }, + members: { + type: userRoleConnectionType + } + } +}); + +let userGuildConnectionType = new graphql.GraphQLObjectType({ + name: 'UserGuildConnection', + fields: { + edges: { + type: graphql.GraphQLList(guildMemberType) + }, + nodes: { + type: graphql.GraphQLList(userType) + }, + pageInfoType: { + type: graphql.assertNonNullType(pageInfoType), + description: 'pagination information' + } + } +}); + +let guildType = new graphql.GraphQLObjectType({ + + name: 'Guild', + fields: { + id: { + type: graphql.GraphQLID + }, + discordId: { + type: graphql.GraphQLID + }, + name: { + type: graphql.GraphQLString + }, + owner: { + type: guildMemberType + }, + members: { + type: userGuildConnectionType + }, + memberCount: { + type: graphql.GraphQLInt + }, + roles: { + type: graphql.GraphQLList(roleType), + description: 'the roles of the guild' + }, + icon: { + type: graphql.GraphQLString + } + } +}); + +let guildEdgeType = new graphql.GraphQLObjectType({ + name: 'GuildEdge', + fields: { + id: { + type: graphql.GraphQLID, + description: 'id of the connection' + }, + node: { + type: guildType + }, + musicPlayer: { + type: musicPlayerType, + description: 'guilds music player' + }, + savedMedia: { + type: mediaEntryConnectionType, + description: 'saved media entries' + } + } +}); + +let guildConnectionType = new graphql.GraphQLObjectType({ + name: 'GuildConnection', + edges: { + type: guildEdgeType + }, + nodes: { + type: graphql.GraphQLList(guildType) + }, + pageInfo: { + type: pageInfoType, + description: 'pagination information' + } +}); + +let clientType = new graphql.GraphQLObjectType({ + name: 'Client', + fields: { + guilds: { + type: [guildType] + }, + guildCount: { + type: graphql.GraphQLInt + }, + voiceConnectionCount: { + type: graphql.GraphQLInt + }, + user: { + type: userType + }, + ping: { + type: graphql.GraphQLFloat + }, + status: { + type: graphql.GraphQLInt + }, + uptime: { + type: graphql.GraphQLInt + } + } +}); + +let logLevelEnum = new graphql.GraphQLEnumType({ + name: 'LogLevel', + description: 'log levels of log entries', + values: { + SILLY: { + value: 'silly' + }, + DEBUG: { + value: 'debug' + }, + VERBOSE: { + value: 'verbose' + }, + INFO: { + value: 'info' + }, + WARN: { + value: 'warn' + }, + ERROR: { + value: 'error' + } + } +}); + +let logEntryType = new graphql.GraphQLObjectType({ + name: 'LogEntry', + fields: { + id: { + type: graphql.assertNonNullType(graphql.GraphQLID), + description: 'id of the log entry' + }, + message: { + type: graphql.GraphQLString, + description: 'log entry content' + }, + level: { + type: logLevelEnum, + description: 'log level of the log entry' + }, + timestamp: { + type: graphql.GraphQLString, + description: 'timestamp of the log entry' + }, + module: { + type: graphql.GraphQLString, + description: 'module that logged the entry' + } + } +}); + +const queryType = new graphql.GraphQLObjectType({ + + name: 'Query', + fields: { + client: { + type: clientType, + description: 'client instance of the bot' + }, + presences: { + type: graphql.assertNonNullType(graphql.GraphQLList(presenceType)), + description: 'presences of the bot' + }, + prefix: { + type: graphql.GraphQLString, + description: 'prefix of the bot' + }, + logs: { + type: graphql.GraphQLList(logEntryType), + description: 'log entries of the bot' + } + } +}); + +Object.assign(exports, { + queryType: queryType +}); diff --git a/lib/web/schema.graphqls b/lib/web/schema.graphqls new file mode 100644 index 0000000..437dc31 --- /dev/null +++ b/lib/web/schema.graphqls @@ -0,0 +1,429 @@ +enum LogLevel { + SILLY + DEBUG + VERBOSE + INFO + WARN + ERROR +} + +type PageInfo { + + # the total number of entries on all pages + total: Int + + # the number of entries per page + perPage: Int + + # the current page + currentPage: Int + + # the last page + lastPage: Int + + # If there is a next page + hasNextPage: Boolean +} + +type MediaEntry { + + # the id of the media entry + id: ID! + + # the url to the YouTube video + url: String! + + # the title of the YouTube video + name: String! + + # the url of the YouTube video's thumbnail + thumbnail: String! +} + +type MediaEntryEdge { + + # the id of the edge + id: ID! + + node: MediaEntry + + # the position of the entry in the queue + position: Int +} + +type MediaEntryConnection { + + edges: [MediaEntryEdge] + + nodes: [MediaEntry] + + # the pagination information + pageInfo: PageInfo +} + +type MusicPlayer { + + # the content of the music players queue + # + # Arguments + # id: get the media entry by id + # page: get the page by number + # perPage: the number of entries per page + queue( + id: ID, + page: Int, + perPage: Int + ): MediaEntryConnection + + # the current position in the song + songPosition: Int + + # if the music player is currently playing + playing: Boolean! + + # the volume of the music player + volume: Float + + # if the music player plays on repeat + repeat: Boolean + + # the currently playing song + currentSong: MediaEntry + + # the quality of the music that is played (YouTube quality) + quality: String + + # the name of the voice channel the music player is playing in + voiceChannel: String + + # if the music player is connected to a voice channel + connected: Boolean! + + # if the music player is paused + paused: Boolean! +} + +type User { + + # the id of the user + id: ID! + + # the discord id of the user + discordId: String + + # the name of the user + name: String! + + # the url of the users avatar + avatar: String + + # if the user is a bot + bot: Boolean + + # the discord tag of the user + tag: String! + + # the current presence of the user + presence: Presence +} + +type Role { + + # the id of the role + id: ID! + + # the discord id of the role + discordId: String + + # the name of the role + name: String + + # the color of the role + color: String +} + +type GuildMemberRoleEdge { + + # the id of the edge + id: ID! + + node: GuildMember + + # if the role is the highest of the guild member + isHighest: Boolean +} + +type GuildMemberRoleConnection { + + edges: [GuildMemberRoleEdge] + + nodes: [GuildMember] + + # the pagination information + pageInfo: PageInfo +} + +type GuildRoleEdge { + + # the id of the edge + id: ID! + + node: Role + + # the members in the role + # + # Arguments + # id: get the member by id + # page: get the page by number + # perPage: the number of entries per page + members( + id: ID, + page: Int, + perPage: Int + ): GuildMemberRoleConnection +} + +type GuildRoleConnection{ + + edges: [GuildRoleEdge] + + nodes: [Role] + + # the pagination information + pageInfo: PageInfo +} + +type GuildMember { + + # the id of the guild member + id: ID! + + # the discord id of the guild member + discordId: String + + # the user associated with the guild member + user: User + + # the nickname of the guild member + nickname: String + + # the roles of the guild member + roles( + first: Int = 10, + offset: Int = 0, + id: String + ): [Role] + + # the highest role of the guild member + highestRole: Role +} + +type GuildMemberEdge { + + # the id of the edge + id: ID! + + node: GuildMember + + # if the guild member is the server owner + isOwner: Boolean +} + +type GuildMemberConnection{ + + edges: [GuildMemberEdge] + + nodes: [GuildMember] + + # the pagination information + pageInfo: PageInfo +} + +type Guild { + + # the id of the guild + id: ID! + + # the discord id of the guild + discordId: ID + + # the guild's name + name: String + + # the owner of the guild + owner: GuildMember + + # the members in the guild + # + # Arguments + # id: get the member by id + # page: get the page by number + # perPage: the number of entries per page + members( + id: ID, + page: Int, + perPage: Int + ): GuildMemberConnection + + # the roles of the guild + # + # Arguments + # id: get the role by id + # page: get the page by number + # perPage: the number of entries per page + roles( + id: ID, + page: Int, + perPage: Int + ): GuildRoleConnection + + # the url of the guild icon + icon: String +} + +type GuildEdge { + + # the id of the edge + id: ID! + + node: Guild + + # the music player associated with the guild + musicPlayer: MusicPlayer + + # the saved media of the guild + savedMedia: mediaEntryConnection +} + +type GuildConnection { + + edges: [GuildEdge] + + nodes: [Guild] + + # the pagination information + pageInfo: PageInfo +} + +type Client { + + # the guilds the client has joined + # + # Arguments + # id: get the guild by id + # page: get the page by number + # perPage: the number of entries per page + guilds ( + id: ID, + page: Int, + perPage: Int + ): GuildConnection + + # the number of voice connections + voiceConnectionCount: Int + + # the bot user + user: User + + # the current average ping + ping: Float + + # the websocket status + status: Int + + # the total uptime + uptime: Int +} + +type LogEntry { + + # the id of the log entry + id: ID! + + # the message of the log entry + message: String + + # the level of the log entry + level: Level + + # the timestamp of the log entry + timestamp: String + + # the module that created the log entry + module: String +} + +type Query { + + # The bots client + client: Client + + # the presences in the presence rotation + presences: [String]! + + # the prefix for commands + prefix: String + + # The log entries generated in the current runtime. + # + # Arguments + # first: the number of entries to get + # offset: the offset of the entries + # id: get the log entry by id + # last: oposite of first - the latest entries + # level: filter by loglevel + logs( + first: Int, + offset: Int = 0, + id: String, + last: Int = 10, + level: LogLevel + ): [LogEntry] +} + +type Mutation { + + # adds media to the queue + # + # Arguments + # guildId: the id of the guild + # url: the url to the media YouTube video + addMediaToQueue( + guildId: ID!, + url: String! + ): MusicPlayer + + # removes media from the queue + # + # Arguments + # guildId: the id of the guild + # entryId: the id of the media entry to remove + removeMediaFromQueue( + guildId: ID!, + entryId: ID! + ): MusicPlayer + + # skips to the next song + # + # Arguments + # guildId: the id of the guild + skipSong(guildId: ID!): MusicPlayer + + # toggles between pause and play + # + # Arguments + # guildId: the id of the guild + togglePause(guildId: ID!): MusicPlayer + + # toggles repeat + # + # Arguments + # guildId: the id of the guild + toggleRepeat(guildId: ID!): MusicPlayer + + # stops the music + # + # Arguments + # guildId: the id of the guild + stopMusic(guildId: ID!): MusicPlayer +} From f524643d4accb417ae981ec4d16b84597865bdee Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 26 Mar 2019 19:24:03 +0000 Subject: [PATCH 14/54] Update dependency graphql to v14.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4c402d9..f111a19 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "ffmpeg-binaries": "4.0.0", "fs-extra": "7.0.1", "get-youtube-title": "1.0.0", - "graphql": "14.1.1", + "graphql": "14.2.0", "js-md5": "0.7.3", "js-sha512": "0.8.0", "node-sass": "4.11.0", From 2071f3065183a3e01cc626aea194a1e08022cd0d Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Wed, 27 Mar 2019 11:52:50 +0000 Subject: [PATCH 15/54] Update dependency sinon to v7.3.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4c402d9..896273b 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "mocha": "6.0.2", "nyc": "13.3.0", "rewire": "4.0.1", - "sinon": "7.2.6", + "sinon": "7.3.1", "eslint-plugin-graphql": "3.0.3", "eslint": "5.15.0", "eslint-plugin-promise": "4.0.1" From f803aed91d7d740e50572c7a2fa7d579978a82d4 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 31 Mar 2019 12:21:31 +0000 Subject: [PATCH 16/54] Update dependency graphql to v14.2.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f0a309c..72a5ab7 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "ffmpeg-binaries": "4.0.0", "fs-extra": "7.0.1", "get-youtube-title": "1.0.0", - "graphql": "14.2.0", + "graphql": "14.2.1", "js-md5": "0.7.3", "js-sha512": "0.8.0", "node-sass": "4.11.0", From e85c3347b588c9c6386f94d827ec2d672d23435c Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 1 Apr 2019 18:57:16 +0000 Subject: [PATCH 17/54] Update dependency eslint-plugin-promise to v4.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f0a309c..5c645ec 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "sinon": "7.3.1", "eslint-plugin-graphql": "3.0.3", "eslint": "5.15.3", - "eslint-plugin-promise": "4.0.1" + "eslint-plugin-promise": "4.1.1" }, "eslintConfig": { "parserOptions": { From b8a0d124fe0f57e3b37d25f1f78be93a2dec511d Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sun, 7 Apr 2019 17:07:26 +0200 Subject: [PATCH 18/54] Removed additions to api --- lib/web/graphql-schema.js | 145 ------------- lib/web/graphql-types.js | 440 -------------------------------------- lib/web/schema.graphqls | 429 ------------------------------------- 3 files changed, 1014 deletions(-) delete mode 100644 lib/web/graphql-schema.js delete mode 100644 lib/web/graphql-types.js delete mode 100644 lib/web/schema.graphqls diff --git a/lib/web/graphql-schema.js b/lib/web/graphql-schema.js deleted file mode 100644 index 95e484e..0000000 --- a/lib/web/graphql-schema.js +++ /dev/null @@ -1,145 +0,0 @@ -const graphql = require('graphql'), - fs = require('fs'); - -class BotGraphql { - constructor(bot) { - this.schema = graphql.buildSchema(fs.readFileSync('.lib/web/schema.graphqls', 'utf-8')); - this.root = { - Query: new Query(bot) - }; - } -} - -/** - * Easyer managing of page info - */ -class PageInfo { - /** - * constructor. - * @param total {Number} - the total number of entries - * @param perPage {Number} - the number of entries per page - * @param currentPage {Number} - the current page's index - * @param lastPage {Number} - the index of the last page - * @param hasNext {Boolean} - is there a next page? - */ - constructor(total, perPage, currentPage, lastPage, hasNext) { - this.total = total; - this.perPage = perPage; - this.currentPage = currentPage; - this.lastPage = lastPage; - this.hasNext = hasNext; - } -} - -/** - * Generic edge - */ -class Edge { - /** - * contructor. - * @param node {Object} - the node belonging to the edge - * @param edgeProps {Object} - additional properties of the edge - */ - constructor(node, edgeProps) { - this.node = node; - Object.assign(this, edgeProps); - } -} - -/** - * Generic connection - */ -class Connection { - /** - * constructor. - * @param edges {Array} - the edges of the connection - * @param pageInfo {PageInfo} - page info for the connection - */ - constructor(edges, pageInfo) { - this.edges = edges; - this.nodes = this.edges.map(x => x.node); - this.pageInfo = pageInfo; - } -} - -/** - * Manages pagination - */ -class Paginator { - /** - * constructor. - * @param edges {Array} - the edges for the pages - * @param perPage {Number} - the number of entries per page - */ - constructor(edges, perPage) { - this._entries = edges; - this.perPage = perPage; - } - - /** - * Get the specific page - * @param page {Number} - the page's number - * @param [perPage] {Number} - the number of entries per page - * @returns {Connection} - */ - getPage(page, perPage) { - perPage = perPage || this.perPage; - let startIndex = (page - 1) * perPage; - let endIndex = startIndex + perPage; - let lastPage = Math.ceil(this._entries.length / perPage); - return new Connection( - this._entries.slice(startIndex, endIndex) || [], - new PageInfo(this._entries.length, perPage, page, lastPage, page !== lastPage) - ); - } - - /** - * Updates the entries of the Paginator. - * @param entries - */ - updateEntries(entries) { - this._entries = entries; - } -} - -class MusicPlayer { - constructor(guildHandler) { - - } -} - -class Guild { - constructor(guild) { - - } -} - -class Client { - constructor(bot) { - this._bot = bot; - this._client = bot.client; - this.guildPaginator = new Paginator() - } - - _getGuildEdges() { - let guildHandlerPaginator = Array.from(this._client.guilds.values()).map(x => new Edge( - - )); - return Array.from(this._client.guilds.values()).map(x => new Edge( - new Guild(x), - { - musicPlayer: new MusicPlayer(this._bot.getGuildHandler(x)), - new Connection( - bot.getGuildHandler(x).savedMedia - ) - } - )); - } -} - -class Query { - constructor(bot) { - this._bot = bot; - this.client = new Client(bot); - } -} diff --git a/lib/web/graphql-types.js b/lib/web/graphql-types.js deleted file mode 100644 index 8545849..0000000 --- a/lib/web/graphql-types.js +++ /dev/null @@ -1,440 +0,0 @@ -const graphql = require('graphql'); - -let pageInfoType = new graphql.GraphQLObjectType({ - name: 'PageInfo', - fields: { - total: { - type: graphql.assertNonNullType(graphql.GraphQLInt), - description: 'total number of pages' - }, - perPage: { - type: graphql.assertNonNullType(graphql.GraphQLInt), - description: 'number of entries per page' - }, - currentPage: { - type: graphql.assertNonNullType(graphql.GraphQLInt), - description: 'current page' - }, - lastPage: { - type: graphql.assertNonNullType(graphql.GraphQLInt), - description: 'last page' - }, - hasNextPage: { - type: graphql.assertNonNullType(graphql.GraphQLBoolean), - description: 'does the connection have a next page' - } - } -}); - -let mediaEntryType = new graphql.GraphQLObjectType({ - - name: 'MediaEntry', - fields: { - id: { - type: graphql.GraphQLID, - description: 'id of the media entry' - }, - url: { - type: graphql.GraphQLString, - description: 'url to the YouTube video' - }, - name: { - type: graphql.GraphQLString, - description: 'title of the YouTube video' - }, - thumbnail: { - type: graphql.GraphQLString, - description: 'thumbnail of the YouTube video' - } - } -}); - -let mediaEntryEdgeType = new graphql.GraphQLObjectType({ - name: 'MediaEntryEdge', - fields: { - id: { - type: graphql.GraphQLID, - description: 'the connection id' - }, - position: { - type: graphql.GraphQLInt, - description: 'position in the queue' - }, - node: { - type: mediaEntryType, - description: 'the media entry node' - } - } -}); - -let mediaEntryConnectionType = new graphql.GraphQLObjectType({ - name: 'MediaEntryConnection', - fields: { - edges: { - type: graphql.GraphQLList(mediaEntryEdgeType) - }, - nodes: { - type: graphql.GraphQLList(mediaEntryType) - }, - pageInfo: { - type: graphql.assertNonNullType(pageInfoType), - description: 'pagination information' - } - } -}); - -let musicPlayerType = new graphql.GraphQLObjectType({ - - name: 'MusicPlayer', - fields: { - queue: { - type: new graphql.GraphQLList(mediaEntryConnectionType), - description: 'media entries in the music queue' - }, - queueCount: { - type: graphql.GraphQLInt, - description: 'number of media entries in the queue' - }, - songStartTime: { - type: graphql.GraphQLString - }, - playing: { - type: graphql.GraphQLBoolean - }, - volume: { - type: graphql.GraphQLFloat - }, - repeat: { - type: graphql.GraphQLBoolean - }, - currentSong: { - type: mediaEntryType - }, - quality: { - type: graphql.GraphQLString - }, - voiceChannel: { - type: graphql.GraphQLString - }, - connected: { - type: graphql.GraphQLBoolean - }, - paused: { - type: graphql.GraphQLBoolean - } - } -}); - -let presenceType = new graphql.GraphQLObjectType({ - - name: 'Presence', - fields: { - game: { - type: graphql.GraphQLString - }, - status: { - type: graphql.GraphQLString - } - } -}); - -let userType = new graphql.GraphQLObjectType({ - - name: 'User', - fields: { - id: { - type: graphql.GraphQLID - }, - discordId: { - type: graphql.GraphQLID - }, - name: { - type: graphql.GraphQLString - }, - avatar: { - type: graphql.GraphQLString - }, - bot: { - type: graphql.GraphQLBoolean - }, - tag: { - type: graphql.GraphQLString - }, - presence: { - type: presenceType - } - } -}); - -let guildMemberType = new graphql.GraphQLObjectType({ - name: 'GuildMember', - fields: { - id: { - type: graphql.assertNonNullType(graphql.GraphQLID), - description: 'id of the guild member' - }, - discordId: { - type: graphql.GraphQLID - }, - user: { - type: userType, - description: 'the user instance of the guild member' - }, - nickname: { - type: graphql.GraphQLString, - description: 'the nickname of the guild member' - }, - roles: { - type: graphql.GraphQLList(roleType), - description: 'the roles of the guild member' - }, - highestRole: { - type: roleType, - description: 'the highest role of the guild member' - } - } -}); - -let userRoleEdgeType = new graphql.GraphQLObjectType({ - name: 'userRoleEdge', - fields: { - id: { - type: graphql.GraphQLID, - description: 'the connection id' - }, - node: { - type: guildMemberType, - description: 'guild member edge of the role' - }, - isHighest: { - type: graphql.GraphQLBoolean, - description: 'is the role the highest of the guild member' - } - } -}); - -let userRoleConnectionType = new graphql.GraphQLObjectType({ - name: 'UserRoleConnection', - fields: { - edges: { - type: graphql.GraphQLList(userRoleEdgeType) - }, - nodes: { - type: graphql.GraphQLList(userType) - }, - pageInfoType: { - type: graphql.assertNonNullType(pageInfoType), - description: 'pagination information' - } - } -}); - -let roleType = new graphql.GraphQLObjectType({ - - name: 'Role', - fields: { - id: { - type: graphql.GraphQLID - }, - discordId: { - type: graphql.GraphQLID - }, - name: { - type: graphql.GraphQLString - }, - color: { - type: graphql.GraphQLString - }, - members: { - type: userRoleConnectionType - } - } -}); - -let userGuildConnectionType = new graphql.GraphQLObjectType({ - name: 'UserGuildConnection', - fields: { - edges: { - type: graphql.GraphQLList(guildMemberType) - }, - nodes: { - type: graphql.GraphQLList(userType) - }, - pageInfoType: { - type: graphql.assertNonNullType(pageInfoType), - description: 'pagination information' - } - } -}); - -let guildType = new graphql.GraphQLObjectType({ - - name: 'Guild', - fields: { - id: { - type: graphql.GraphQLID - }, - discordId: { - type: graphql.GraphQLID - }, - name: { - type: graphql.GraphQLString - }, - owner: { - type: guildMemberType - }, - members: { - type: userGuildConnectionType - }, - memberCount: { - type: graphql.GraphQLInt - }, - roles: { - type: graphql.GraphQLList(roleType), - description: 'the roles of the guild' - }, - icon: { - type: graphql.GraphQLString - } - } -}); - -let guildEdgeType = new graphql.GraphQLObjectType({ - name: 'GuildEdge', - fields: { - id: { - type: graphql.GraphQLID, - description: 'id of the connection' - }, - node: { - type: guildType - }, - musicPlayer: { - type: musicPlayerType, - description: 'guilds music player' - }, - savedMedia: { - type: mediaEntryConnectionType, - description: 'saved media entries' - } - } -}); - -let guildConnectionType = new graphql.GraphQLObjectType({ - name: 'GuildConnection', - edges: { - type: guildEdgeType - }, - nodes: { - type: graphql.GraphQLList(guildType) - }, - pageInfo: { - type: pageInfoType, - description: 'pagination information' - } -}); - -let clientType = new graphql.GraphQLObjectType({ - name: 'Client', - fields: { - guilds: { - type: [guildType] - }, - guildCount: { - type: graphql.GraphQLInt - }, - voiceConnectionCount: { - type: graphql.GraphQLInt - }, - user: { - type: userType - }, - ping: { - type: graphql.GraphQLFloat - }, - status: { - type: graphql.GraphQLInt - }, - uptime: { - type: graphql.GraphQLInt - } - } -}); - -let logLevelEnum = new graphql.GraphQLEnumType({ - name: 'LogLevel', - description: 'log levels of log entries', - values: { - SILLY: { - value: 'silly' - }, - DEBUG: { - value: 'debug' - }, - VERBOSE: { - value: 'verbose' - }, - INFO: { - value: 'info' - }, - WARN: { - value: 'warn' - }, - ERROR: { - value: 'error' - } - } -}); - -let logEntryType = new graphql.GraphQLObjectType({ - name: 'LogEntry', - fields: { - id: { - type: graphql.assertNonNullType(graphql.GraphQLID), - description: 'id of the log entry' - }, - message: { - type: graphql.GraphQLString, - description: 'log entry content' - }, - level: { - type: logLevelEnum, - description: 'log level of the log entry' - }, - timestamp: { - type: graphql.GraphQLString, - description: 'timestamp of the log entry' - }, - module: { - type: graphql.GraphQLString, - description: 'module that logged the entry' - } - } -}); - -const queryType = new graphql.GraphQLObjectType({ - - name: 'Query', - fields: { - client: { - type: clientType, - description: 'client instance of the bot' - }, - presences: { - type: graphql.assertNonNullType(graphql.GraphQLList(presenceType)), - description: 'presences of the bot' - }, - prefix: { - type: graphql.GraphQLString, - description: 'prefix of the bot' - }, - logs: { - type: graphql.GraphQLList(logEntryType), - description: 'log entries of the bot' - } - } -}); - -Object.assign(exports, { - queryType: queryType -}); diff --git a/lib/web/schema.graphqls b/lib/web/schema.graphqls deleted file mode 100644 index 437dc31..0000000 --- a/lib/web/schema.graphqls +++ /dev/null @@ -1,429 +0,0 @@ -enum LogLevel { - SILLY - DEBUG - VERBOSE - INFO - WARN - ERROR -} - -type PageInfo { - - # the total number of entries on all pages - total: Int - - # the number of entries per page - perPage: Int - - # the current page - currentPage: Int - - # the last page - lastPage: Int - - # If there is a next page - hasNextPage: Boolean -} - -type MediaEntry { - - # the id of the media entry - id: ID! - - # the url to the YouTube video - url: String! - - # the title of the YouTube video - name: String! - - # the url of the YouTube video's thumbnail - thumbnail: String! -} - -type MediaEntryEdge { - - # the id of the edge - id: ID! - - node: MediaEntry - - # the position of the entry in the queue - position: Int -} - -type MediaEntryConnection { - - edges: [MediaEntryEdge] - - nodes: [MediaEntry] - - # the pagination information - pageInfo: PageInfo -} - -type MusicPlayer { - - # the content of the music players queue - # - # Arguments - # id: get the media entry by id - # page: get the page by number - # perPage: the number of entries per page - queue( - id: ID, - page: Int, - perPage: Int - ): MediaEntryConnection - - # the current position in the song - songPosition: Int - - # if the music player is currently playing - playing: Boolean! - - # the volume of the music player - volume: Float - - # if the music player plays on repeat - repeat: Boolean - - # the currently playing song - currentSong: MediaEntry - - # the quality of the music that is played (YouTube quality) - quality: String - - # the name of the voice channel the music player is playing in - voiceChannel: String - - # if the music player is connected to a voice channel - connected: Boolean! - - # if the music player is paused - paused: Boolean! -} - -type User { - - # the id of the user - id: ID! - - # the discord id of the user - discordId: String - - # the name of the user - name: String! - - # the url of the users avatar - avatar: String - - # if the user is a bot - bot: Boolean - - # the discord tag of the user - tag: String! - - # the current presence of the user - presence: Presence -} - -type Role { - - # the id of the role - id: ID! - - # the discord id of the role - discordId: String - - # the name of the role - name: String - - # the color of the role - color: String -} - -type GuildMemberRoleEdge { - - # the id of the edge - id: ID! - - node: GuildMember - - # if the role is the highest of the guild member - isHighest: Boolean -} - -type GuildMemberRoleConnection { - - edges: [GuildMemberRoleEdge] - - nodes: [GuildMember] - - # the pagination information - pageInfo: PageInfo -} - -type GuildRoleEdge { - - # the id of the edge - id: ID! - - node: Role - - # the members in the role - # - # Arguments - # id: get the member by id - # page: get the page by number - # perPage: the number of entries per page - members( - id: ID, - page: Int, - perPage: Int - ): GuildMemberRoleConnection -} - -type GuildRoleConnection{ - - edges: [GuildRoleEdge] - - nodes: [Role] - - # the pagination information - pageInfo: PageInfo -} - -type GuildMember { - - # the id of the guild member - id: ID! - - # the discord id of the guild member - discordId: String - - # the user associated with the guild member - user: User - - # the nickname of the guild member - nickname: String - - # the roles of the guild member - roles( - first: Int = 10, - offset: Int = 0, - id: String - ): [Role] - - # the highest role of the guild member - highestRole: Role -} - -type GuildMemberEdge { - - # the id of the edge - id: ID! - - node: GuildMember - - # if the guild member is the server owner - isOwner: Boolean -} - -type GuildMemberConnection{ - - edges: [GuildMemberEdge] - - nodes: [GuildMember] - - # the pagination information - pageInfo: PageInfo -} - -type Guild { - - # the id of the guild - id: ID! - - # the discord id of the guild - discordId: ID - - # the guild's name - name: String - - # the owner of the guild - owner: GuildMember - - # the members in the guild - # - # Arguments - # id: get the member by id - # page: get the page by number - # perPage: the number of entries per page - members( - id: ID, - page: Int, - perPage: Int - ): GuildMemberConnection - - # the roles of the guild - # - # Arguments - # id: get the role by id - # page: get the page by number - # perPage: the number of entries per page - roles( - id: ID, - page: Int, - perPage: Int - ): GuildRoleConnection - - # the url of the guild icon - icon: String -} - -type GuildEdge { - - # the id of the edge - id: ID! - - node: Guild - - # the music player associated with the guild - musicPlayer: MusicPlayer - - # the saved media of the guild - savedMedia: mediaEntryConnection -} - -type GuildConnection { - - edges: [GuildEdge] - - nodes: [Guild] - - # the pagination information - pageInfo: PageInfo -} - -type Client { - - # the guilds the client has joined - # - # Arguments - # id: get the guild by id - # page: get the page by number - # perPage: the number of entries per page - guilds ( - id: ID, - page: Int, - perPage: Int - ): GuildConnection - - # the number of voice connections - voiceConnectionCount: Int - - # the bot user - user: User - - # the current average ping - ping: Float - - # the websocket status - status: Int - - # the total uptime - uptime: Int -} - -type LogEntry { - - # the id of the log entry - id: ID! - - # the message of the log entry - message: String - - # the level of the log entry - level: Level - - # the timestamp of the log entry - timestamp: String - - # the module that created the log entry - module: String -} - -type Query { - - # The bots client - client: Client - - # the presences in the presence rotation - presences: [String]! - - # the prefix for commands - prefix: String - - # The log entries generated in the current runtime. - # - # Arguments - # first: the number of entries to get - # offset: the offset of the entries - # id: get the log entry by id - # last: oposite of first - the latest entries - # level: filter by loglevel - logs( - first: Int, - offset: Int = 0, - id: String, - last: Int = 10, - level: LogLevel - ): [LogEntry] -} - -type Mutation { - - # adds media to the queue - # - # Arguments - # guildId: the id of the guild - # url: the url to the media YouTube video - addMediaToQueue( - guildId: ID!, - url: String! - ): MusicPlayer - - # removes media from the queue - # - # Arguments - # guildId: the id of the guild - # entryId: the id of the media entry to remove - removeMediaFromQueue( - guildId: ID!, - entryId: ID! - ): MusicPlayer - - # skips to the next song - # - # Arguments - # guildId: the id of the guild - skipSong(guildId: ID!): MusicPlayer - - # toggles between pause and play - # - # Arguments - # guildId: the id of the guild - togglePause(guildId: ID!): MusicPlayer - - # toggles repeat - # - # Arguments - # guildId: the id of the guild - toggleRepeat(guildId: ID!): MusicPlayer - - # stops the music - # - # Arguments - # guildId: the id of the guild - stopMusic(guildId: ID!): MusicPlayer -} From d65616eadc6dd24e8cb989c3f331eb005d70caca Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sun, 7 Apr 2019 19:11:48 +0200 Subject: [PATCH 19/54] Fixed and switched to ytdl-core-discord - removed volume commands - removed volume functions and properties from MusicPlayer - fixed missing space on 'Next n Songs' on dashboard - removed ytdl-core from package.json --- CHANGELOG.md | 5 +++ commands/MusicCommands/index.js | 4 +- lib/guilds/index.js | 1 - lib/music/index.js | 79 +++++++++++++++------------------ package.json | 4 +- web/http/index.pug | 2 +- web/http/sass/vars.sass | 6 +-- 7 files changed, 49 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index beecd2b..64200a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - all hard coded sql statements to generic sql generation - MusicPlayer to extend the default EventEmitter - MessageHandler to accept instances of Response and redirect events to it +- switched to `ytdl-core-discord` for youtube audio playback ### Added - Utility classes for generic SQL Statements @@ -33,6 +34,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - ExtendedEventEmitter class in lib/utils/extended-events.js - Response object that allows the registration of events for messages +### Removed +- `~volume` command because volume can't be controlled anymore +- volume functions and properties from the MusicPlayer + ## [0.11.0-beta] - 2019-03-03 ### Changed - template Files to name `template.yaml` diff --git a/commands/MusicCommands/index.js b/commands/MusicCommands/index.js index 1244a07..daee567 100644 --- a/commands/MusicCommands/index.js +++ b/commands/MusicCommands/index.js @@ -322,6 +322,7 @@ class MusicCommandModule extends cmdLib.CommandModule { }) ); + /* TODO: Delete completely on release let volume = new cmdLib.Command( this.template.volume, new cmdLib.Answer(async (m, k) => { @@ -335,7 +336,7 @@ class MusicCommandModule extends cmdLib.CommandModule { return this.template.volume.response.invalid; } }) - ); + );*/ let quality = new cmdLib.Command( this.template.quality, @@ -369,7 +370,6 @@ class MusicCommandModule extends cmdLib.CommandModule { .registerCommand(saveMedia) .registerCommand(deleteMedia) .registerCommand(savedMedia) - .registerCommand(volume) .registerCommand(quality); } } diff --git a/lib/guilds/index.js b/lib/guilds/index.js index 9b55a78..866846b 100644 --- a/lib/guilds/index.js +++ b/lib/guilds/index.js @@ -156,7 +156,6 @@ class GuildHandler { */ async applySettings() { this.settings = await this.db.getSettings(); - this.musicPlayer.setVolume(Number(this.settings.musicPlayerVolume) || 0.5); this.musicPlayer.quality = this.settings.musicPlayerQuality || 'lowest'; } diff --git a/lib/music/index.js b/lib/music/index.js index b7eff4b..573ba72 100644 --- a/lib/music/index.js +++ b/lib/music/index.js @@ -1,4 +1,4 @@ -const ytdl = require("ytdl-core"), +const ytdl = require("ytdl-core-discord"), ypi = require('youtube-playlist-info'), yttl = require('get-youtube-title'), config = require('../../config.json'), @@ -11,7 +11,7 @@ const ytdl = require("ytdl-core"), * The Music Player class is used to handle music playing tasks on Discord Servers (Guilds). * @type {MusicPlayer} */ -class MusicPlayer extends xevents.ExtendedEventEmitter{ +class MusicPlayer extends xevents.ExtendedEventEmitter { /** * Constructor * @param [voiceChannel] {Discord.VoiceChannel} @@ -24,13 +24,12 @@ class MusicPlayer extends xevents.ExtendedEventEmitter{ this.playing = false; this.current = null; this.repeat = false; - this.volume = 0.5; this.voiceChannel = voiceChannel; this.exitTimeout = null; this._logger = new logging.Logger(this); this._logger.silly('Initialized Music Player'); - config.music? this.quality = config.music.quality || 'lowest' : this.quality = 'lowest'; - config.music? this.liveBuffer = config.music.liveBuffer || 10000 : 10000; + config.music ? this.quality = config.music.quality || 'lowest' : this.quality = 'lowest'; + config.music ? this.liveBuffer = config.music.liveBuffer || 10000 : 10000; } /** @@ -132,7 +131,7 @@ class MusicPlayer extends xevents.ExtendedEventEmitter{ * If the url is a playlist, the videos of the playlist are fetched and put * in the queue. For each song the title is saved in the queue too. * @param url {String} - * @param playnext {Boolean} + * @param [playnext] {Boolean} */ async playYouTube(url, playnext) { let plist = utils.YouTube.getPlaylistIdFromUrl(url); @@ -143,7 +142,7 @@ class MusicPlayer extends xevents.ExtendedEventEmitter{ let firstSongTitle = null; try { firstSongTitle = await this.getVideoName(firstSong); - } catch(err) { + } catch (err) { if (err.message !== 'Not found') { this._logger.warn(err.message); this._logger.debug(err.stack); @@ -172,32 +171,37 @@ class MusicPlayer extends xevents.ExtendedEventEmitter{ this.current = ({'url': url, 'title': await this.getVideoName(url)}); if (this.repeat) this.queue.push(this.current); + let toggleNext = () => { + if (this.queue.length > 0) { + this.current = this.queue.shift(); + this.emit('next', this.current); + this.playYouTube(this.current.url).catch((err) => this._logger.warn(err.message)); + } else { + this.stop(); + } + }; + try { - this.disp = this.conn.playStream(ytdl(url, - {filter: 'audioonly', quality: this.quality, liveBuffer: this.liveBuffer}), - {volume: this.volume}); - - this.disp.on('error', (err) => { - this._logger.error(err.message); - this._logger.debug(err.stack); - }); + this.disp = this.conn.playOpusStream(await ytdl(url, + {filter: 'audioonly', quality: this.quality, liveBuffer: this.liveBuffer})); + this.disp.on('error', (err) => { + this._logger.error(err.message); + this._logger.debug(err.stack); + }); - this.disp.on('end', (reason) => { // end event triggers the next song to play when the reason is not stop - if (reason !== 'stop') { - this.playing = false; - this.current = null; - if (this.queue.length > 0) { - this.current = this.queue.shift(); - if (this.repeat) // listen on repeat - this.queue.push(this.current); - this.emit('next', this.current); - this.playYouTube(this.current.url).catch((err) => this._logger.warn(err.message)); - } else { - this.stop(); + this.disp.on('end', (reason) => { // end event triggers the next song to play when the reason is not stop + if (reason !== 'stop') { + this.playing = false; + this.current = null; + toggleNext(); } - } - }); - this.playing = true; + }); + this.playing = true; + } catch (err) { + this._logger.verbose(err.message); + this._logger.silly(err.stack); + toggleNext(); + } } else { this._logger.debug(`Added ${url} to the queue`); if (playnext) @@ -226,17 +230,6 @@ class MusicPlayer extends xevents.ExtendedEventEmitter{ }); } - /** - * Sets the volume of the dispatcher to the given value - * @param percentage {Number} - */ - setVolume(percentage) { - this._logger.verbose(`Setting volume to ${percentage}`); - this.volume = percentage; - if (this.disp !== null) - this.disp.setVolume(percentage); - } - /** * Pauses if a dispatcher exists */ @@ -244,7 +237,7 @@ class MusicPlayer extends xevents.ExtendedEventEmitter{ this._logger.verbose("Pausing music..."); if (this.disp !== null) this.disp.pause(); - else + else this._logger.warn("No dispatcher found"); } @@ -256,7 +249,7 @@ class MusicPlayer extends xevents.ExtendedEventEmitter{ this._logger.verbose("Resuming music..."); if (this.disp !== null) this.disp.resume(); - else + else this._logger.warn("No dispatcher found"); } diff --git a/package.json b/package.json index 885897b..c46f614 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "express-session": "1.15.6", "ffmpeg-binaries": "4.0.0", "fs-extra": "7.0.1", + "get-youtube-title": "^1.0.0", "graphql": "14.1.1", "js-md5": "0.7.3", "js-sha512": "0.8.0", @@ -33,8 +34,7 @@ "winston": "3.2.1", "winston-daily-rotate-file": "3.8.0", "youtube-playlist-info": "1.1.2", - "ytdl-core": "0.29.1", - "get-youtube-title": "latest" + "ytdl-core-discord": "^1.0.3" }, "devDependencies": { "assert": "1.4.1", diff --git a/web/http/index.pug b/web/http/index.pug index 95650ea..71dc031 100644 --- a/web/http/index.pug +++ b/web/http/index.pug @@ -71,7 +71,7 @@ head span#mp-queueCount | Songs in Queue span.cell - | Next + | Next span#mp-queueDisplayCount 0 | Songs: #mp-songQueue diff --git a/web/http/sass/vars.sass b/web/http/sass/vars.sass index c367af8..ee76512 100644 --- a/web/http/sass/vars.sass +++ b/web/http/sass/vars.sass @@ -2,8 +2,8 @@ $cPrimary: #fff $cPrimaryVariant: #4c10a5 $cSecondary: #c889f5 $cSecondaryVariant: #740bce -$cBackground: #77f -$cBackgroundVariant: #55b +$cBackground: #1f1f2f +$cBackgroundVariant: #3f3f55 $cSurface: #fff $cSurfaceVariant: #000 $cError: #f59289 @@ -28,4 +28,4 @@ $cInfo: #890 $cWarn: #a60 $cError: #a00 -$fNormal: Ubuntu, sans-serif \ No newline at end of file +$fNormal: Ubuntu, sans-serif From 86f3ef8c134a4cc807ab7daba67c922ed45234b9 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sun, 7 Apr 2019 19:38:53 +0200 Subject: [PATCH 20/54] Fixes to music functions and commands --- CHANGELOG.md | 2 ++ commands/MusicCommands/index.js | 2 +- lib/music/index.js | 5 +++++ web/http/index.pug | 2 +- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64200a6..3679780 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - bug on `AnilistApiCommands` where the `RichCharacterInfo` uses a nonexistent function of the `ExtendedRichEmbed` - bug on`AnilistApi` where the `.gql` files couldn't be found. - Typo in changelog +- bug on `~np` message that causes the player to crash ### Changed - name of MiscCommands module from `TemplateCommandModule` to `MiscCommandModule` @@ -33,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - table `messages` to main database where messages are stored for statistical analysis and bug handling - ExtendedEventEmitter class in lib/utils/extended-events.js - Response object that allows the registration of events for messages +- Handling of error event for every VoiceConnection ### Removed - `~volume` command because volume can't be controlled anymore diff --git a/commands/MusicCommands/index.js b/commands/MusicCommands/index.js index daee567..a348806 100644 --- a/commands/MusicCommands/index.js +++ b/commands/MusicCommands/index.js @@ -239,7 +239,7 @@ class MusicCommandModule extends cmdLib.CommandModule { .setImage(utils.YouTube.getVideoThumbnailUrlFromUrl(song.url)) .setColor(0x00aaff)); if (message.id !== message.channel.lastMessageID) { - gh.musicPlayer.off('next', next); + gh.musicPlayer.removeListener('next', next); message.delete(); } }; diff --git a/lib/music/index.js b/lib/music/index.js index 573ba72..c01db3a 100644 --- a/lib/music/index.js +++ b/lib/music/index.js @@ -49,6 +49,11 @@ class MusicPlayer extends xevents.ExtendedEventEmitter { this.voiceChannel = voiceChannel; this._logger.verbose(`Connecting to voiceChannel ${this.voiceChannel.name}`); let connection = await this.voiceChannel.join(); + + connection.on('error', (err) => { + this._logger.error(err.message); + this._logger.debug(err.stack); + }); this._logger.info(`Connected to Voicechannel ${this.voiceChannel.name}`); this.conn = connection; this.emit('connected'); diff --git a/web/http/index.pug b/web/http/index.pug index 71dc031..95650ea 100644 --- a/web/http/index.pug +++ b/web/http/index.pug @@ -71,7 +71,7 @@ head span#mp-queueCount | Songs in Queue span.cell - | Next + | Next span#mp-queueDisplayCount 0 | Songs: #mp-songQueue From 3155ffc18881e65694ae0f5d617dab52df569f7b Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sun, 7 Apr 2019 19:44:50 +0200 Subject: [PATCH 21/54] Fix to MusicPlayer - fix to playing boolean --- lib/music/index.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/music/index.js b/lib/music/index.js index c01db3a..4cdde4a 100644 --- a/lib/music/index.js +++ b/lib/music/index.js @@ -183,23 +183,20 @@ class MusicPlayer extends xevents.ExtendedEventEmitter { this.playYouTube(this.current.url).catch((err) => this._logger.warn(err.message)); } else { this.stop(); + this.current = null; + this.playing = false; } }; try { - this.disp = this.conn.playOpusStream(await ytdl(url, {filter: 'audioonly', quality: this.quality, liveBuffer: this.liveBuffer})); this.disp.on('error', (err) => { this._logger.error(err.message); this._logger.debug(err.stack); }); - this.disp.on('end', (reason) => { // end event triggers the next song to play when the reason is not stop - if (reason !== 'stop') { - this.playing = false; - this.current = null; + if (reason !== 'stop') toggleNext(); - } }); this.playing = true; } catch (err) { From adf1da7ac3654b344852002346218da338723547 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 8 Apr 2019 18:35:55 +0000 Subject: [PATCH 22/54] Update dependency mocha to v6.1.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f0a309c..f83c38f 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "devDependencies": { "assert": "1.4.1", "chai": "4.2.0", - "mocha": "6.0.2", + "mocha": "6.1.2", "nyc": "13.3.0", "rewire": "4.0.1", "sinon": "7.3.1", From 441a891e231e9c570d0b236cc5472530aacfa605 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Wed, 10 Apr 2019 10:09:54 +0000 Subject: [PATCH 23/54] Update dependency eslint to v5.16.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 749155a..854a17b 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "rewire": "4.0.1", "sinon": "7.3.1", "eslint-plugin-graphql": "3.0.3", - "eslint": "5.15.3", + "eslint": "5.16.0", "eslint-plugin-promise": "4.1.1" }, "eslintConfig": { From 1c95afbdcc0ea85104f63126eefec9cb6b507dd0 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 14 Apr 2019 07:11:35 +0000 Subject: [PATCH 24/54] Update dependency express-graphql to v0.8.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 854a17b..ef72132 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "discord.js": "11.4.2", "express": "4.16.4", "express-compile-sass": "4.0.0", - "express-graphql": "0.7.1", + "express-graphql": "0.8.0", "express-session": "1.15.6", "ffmpeg-binaries": "4.0.0", "fs-extra": "7.0.1", From 752b5bbb90c132526d5746e9e2ca0430be0e9fb2 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 16 Apr 2019 10:14:13 +0000 Subject: [PATCH 25/54] Update dependency nyc to v14 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 854a17b..541a2e4 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "assert": "1.4.1", "chai": "4.2.0", "mocha": "6.1.2", - "nyc": "13.3.0", + "nyc": "14.0.0", "rewire": "4.0.1", "sinon": "7.3.1", "eslint-plugin-graphql": "3.0.3", From 38f73379c178de71918d9c55f40faba178670066 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Wed, 17 Apr 2019 16:17:51 +0000 Subject: [PATCH 26/54] Update dependency sinon to v7.3.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 854a17b..f5bbd3c 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "mocha": "6.1.2", "nyc": "13.3.0", "rewire": "4.0.1", - "sinon": "7.3.1", + "sinon": "7.3.2", "eslint-plugin-graphql": "3.0.3", "eslint": "5.16.0", "eslint-plugin-promise": "4.1.1" From 83c849ac48e13d11623b0f241eef269141c7a703 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 23 Apr 2019 08:28:30 +0000 Subject: [PATCH 27/54] Update dependency mocha to v6.1.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 986ba4e..6917c4b 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "devDependencies": { "assert": "1.4.1", "chai": "4.2.0", - "mocha": "6.1.2", + "mocha": "6.1.4", "nyc": "14.0.0", "rewire": "4.0.1", "sinon": "7.3.2", From f0dc692b991bae6d6b710ae6761b80c9b3e8a307 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 23 Apr 2019 08:30:17 +0000 Subject: [PATCH 28/54] Update dependency express-session to v1.16.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6917c4b..8fb4d8f 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "express": "4.16.4", "express-compile-sass": "4.0.0", "express-graphql": "0.8.0", - "express-session": "1.15.6", + "express-session": "1.16.1", "ffmpeg-binaries": "4.0.0", "fs-extra": "7.0.1", "get-youtube-title": "1.0.0", From 99ad843b997ae25a483bee95049d4dc50ef7bb2e Mon Sep 17 00:00:00 2001 From: Trivernis <19694973+Trivernis@users.noreply.github.com> Date: Tue, 23 Apr 2019 10:35:41 +0200 Subject: [PATCH 29/54] Fixed formatting error in package.json - fixed missing comma --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 6931e11..ded77cd 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "js-md5": "0.7.3", "js-sha512": "0.8.0", "js-yaml": "latest", - "node-fetch": "^2.3.0", + "node-fetch": "2.3.0", "node-opus": "0.3.1", "node-sass": "4.11.0", "pg": "^7.8.2", @@ -45,7 +45,7 @@ "sinon": "7.3.2", "eslint-plugin-graphql": "3.0.3", "eslint": "5.16.0", - "eslint-plugin-promise": "4.1.1" + "eslint-plugin-promise": "4.1.1", "opusscript": "0.0.6" }, "eslintConfig": { From 63ad6293e2abc880bd2549a89bf05c7df8a0dfb5 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Tue, 8 Oct 2019 14:06:12 +0200 Subject: [PATCH 30/54] Update renovate.json --- renovate.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/renovate.json b/renovate.json index 61aa129..2560c8b 100644 --- a/renovate.json +++ b/renovate.json @@ -4,5 +4,6 @@ ], "baseBranches": [ "dependency-updates" - ] + ], + "automerge": true } From f735928460bcff4f399aff456a09b60b4e041a16 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 8 Oct 2019 12:19:03 +0000 Subject: [PATCH 31/54] Update dependency mocha to v6.2.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ded77cd..52ef3f3 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "devDependencies": { "assert": "1.4.1", "chai": "4.2.0", - "mocha": "6.1.4", + "mocha": "6.2.1", "nyc": "14.0.0", "rewire": "4.0.1", "sinon": "7.3.2", From 7ec9e32d71b8638f7a2ae77162f4a77acf700bca Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 8 Oct 2019 13:31:56 +0000 Subject: [PATCH 32/54] Update dependency opusscript to v0.0.7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ded77cd..b4f63f7 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "eslint-plugin-graphql": "3.0.3", "eslint": "5.16.0", "eslint-plugin-promise": "4.1.1", - "opusscript": "0.0.6" + "opusscript": "0.0.7" }, "eslintConfig": { "parserOptions": { From 412f64e4acf26e8d76d3f4b417e24cfd9820f3cd Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 8 Oct 2019 14:52:55 +0000 Subject: [PATCH 33/54] Update dependency sinon to v7.5.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b4f63f7..d030018 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "mocha": "6.1.4", "nyc": "14.0.0", "rewire": "4.0.1", - "sinon": "7.3.2", + "sinon": "7.5.0", "eslint-plugin-graphql": "3.0.3", "eslint": "5.16.0", "eslint-plugin-promise": "4.1.1", From 58b8977e2e5974e92e0f94c8fea48efb0eb0bbfa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2019 20:49:01 +0100 Subject: [PATCH 34/54] Update dependency nyc to v14.1.1 (#76) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f421b7f..ac97931 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "assert": "1.4.1", "chai": "4.2.0", "mocha": "6.2.1", - "nyc": "14.0.0", + "nyc": "14.1.1", "rewire": "4.0.1", "sinon": "7.5.0", "eslint-plugin-graphql": "3.0.3", From 9b793d3a66f4cb080def3ce6275bb903abaff479 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2019 20:50:59 +0100 Subject: [PATCH 35/54] Update dependency winston-daily-rotate-file to v4 (#95) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ac97931..b96ca4e 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "pug": "2.0.3", "sqlite3": "4.0.6", "winston": "3.2.1", - "winston-daily-rotate-file": "3.8.0", + "winston-daily-rotate-file": "4.2.1", "youtube-playlist-info": "1.1.2", "ytdl-core-discord": "^1.0.3" }, From 35107dafbce6eef50cd99f716da02d023882dc63 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Tue, 8 Oct 2019 22:05:00 +0200 Subject: [PATCH 36/54] Database and music fix - swiched back to ytdl-core for youtube playback - fixed music skip being stuck sometimes - removed database release call on pooled client --- CHANGELOG.md | 2 +- commands/MusicCommands/index.js | 4 ++-- lib/database/index.js | 4 +--- lib/guilds/index.js | 7 ++----- lib/music/index.js | 22 ++++++++++++++++++---- package.json | 12 +++++++----- 6 files changed, 31 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3679780..85216b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - bug on`AnilistApi` where the `.gql` files couldn't be found. - Typo in changelog - bug on `~np` message that causes the player to crash +- database handler using release on pooled client ### Changed - name of MiscCommands module from `TemplateCommandModule` to `MiscCommandModule` @@ -22,7 +23,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - all hard coded sql statements to generic sql generation - MusicPlayer to extend the default EventEmitter - MessageHandler to accept instances of Response and redirect events to it -- switched to `ytdl-core-discord` for youtube audio playback ### Added - Utility classes for generic SQL Statements diff --git a/commands/MusicCommands/index.js b/commands/MusicCommands/index.js index a348806..fa6ec77 100644 --- a/commands/MusicCommands/index.js +++ b/commands/MusicCommands/index.js @@ -322,7 +322,6 @@ class MusicCommandModule extends cmdLib.CommandModule { }) ); - /* TODO: Delete completely on release let volume = new cmdLib.Command( this.template.volume, new cmdLib.Answer(async (m, k) => { @@ -336,7 +335,7 @@ class MusicCommandModule extends cmdLib.CommandModule { return this.template.volume.response.invalid; } }) - );*/ + ); let quality = new cmdLib.Command( this.template.quality, @@ -370,6 +369,7 @@ class MusicCommandModule extends cmdLib.CommandModule { .registerCommand(saveMedia) .registerCommand(deleteMedia) .registerCommand(savedMedia) + .registerCommand(volume) .registerCommand(quality); } } diff --git a/lib/database/index.js b/lib/database/index.js index f3e30c2..fc2f088 100644 --- a/lib/database/index.js +++ b/lib/database/index.js @@ -145,7 +145,7 @@ class Database { * Run a sql statement with seperate values and all result rows as return. * @param sql {String} - the sql statement with escaped values ($1, $2... for postgres, ? for sqlite) * @param [values] {Array} - the seperate values - * @returns {Promise} + * @returns {Promise} */ async all(sql, values) { this._logger.debug(`Running SQL "${sql}" with values ${values}`); @@ -166,8 +166,6 @@ class Database { close() { if (this._dbType === 'sqlite') this.database.close(); - else if (this._dbType === 'postgresql') - this.database.release(); } } diff --git a/lib/guilds/index.js b/lib/guilds/index.js index 866846b..cbc771f 100644 --- a/lib/guilds/index.js +++ b/lib/guilds/index.js @@ -1,10 +1,6 @@ const music = require('../music'), - utils = require('../utils'), - config = require('../../config.json'), dblib = require('../database'), - logging = require('../utils/logging'), - fs = require('fs-extra'), - dataDir = config.dataPath || './data'; + logging = require('../utils/logging'); /** * GuildDatabase class has abstraction for some sql statements. @@ -156,6 +152,7 @@ class GuildHandler { */ async applySettings() { this.settings = await this.db.getSettings(); + this.musicPlayer.setVolume(Number(this.settings.musicPlayerVolume) || 0.5); this.musicPlayer.quality = this.settings.musicPlayerQuality || 'lowest'; } diff --git a/lib/music/index.js b/lib/music/index.js index 4cdde4a..7482313 100644 --- a/lib/music/index.js +++ b/lib/music/index.js @@ -1,4 +1,4 @@ -const ytdl = require("ytdl-core-discord"), +const ytdl = require("ytdl-core"), ypi = require('youtube-playlist-info'), yttl = require('get-youtube-title'), config = require('../../config.json'), @@ -24,6 +24,7 @@ class MusicPlayer extends xevents.ExtendedEventEmitter { this.playing = false; this.current = null; this.repeat = false; + this.volume = 0.5; this.voiceChannel = voiceChannel; this.exitTimeout = null; this._logger = new logging.Logger(this); @@ -188,8 +189,8 @@ class MusicPlayer extends xevents.ExtendedEventEmitter { } }; try { - this.disp = this.conn.playOpusStream(await ytdl(url, - {filter: 'audioonly', quality: this.quality, liveBuffer: this.liveBuffer})); + this.disp = this.conn.playStream(await ytdl(url, + {filter: 'audioonly', quality: this.quality, liveBuffer: this.liveBuffer}, {volume: this.volume})); this.disp.on('error', (err) => { this._logger.error(err.message); this._logger.debug(err.stack); @@ -232,6 +233,17 @@ class MusicPlayer extends xevents.ExtendedEventEmitter { }); } + /** + * Sets the volume of the dispatcher to the given value + * @param percentage {Number} + */ + setVolume(percentage) { + this._logger.verbose(`Setting volume to ${percentage}`); + this.volume = percentage; + if (this.disp !== null) + this.disp.setVolume(percentage); + } + /** * Pauses if a dispatcher exists */ @@ -295,7 +307,9 @@ class MusicPlayer extends xevents.ExtendedEventEmitter { skip() { this._logger.debug("Skipping song"); if (this.disp !== null) { - this.disp.end(); + let disp = this.disp; + this.disp = null; + disp.end(); } else { this.playing = false; if (this.queue.length > 0) { diff --git a/package.json b/package.json index ded77cd..10e70e7 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "compression": "1.7.4", "connect-sqlite3": "0.9.11", "cors": "2.8.5", - "discord.js": "11.4.2", + "discord.js": "^11.5.1", "express": "4.16.4", "express-compile-sass": "4.0.0", "express-graphql": "0.8.0", @@ -23,17 +23,19 @@ "graphql": "14.2.1", "js-md5": "0.7.3", "js-sha512": "0.8.0", - "js-yaml": "latest", - "node-fetch": "2.3.0", - "node-opus": "0.3.1", + "js-yaml": "^3.13.1", + "node-fetch": "^2.6.0", + "node-gyp": "^6.0.0", + "node-opus": "^0.3.2", "node-sass": "4.11.0", - "pg": "^7.8.2", + "pg": "^7.12.1", "promise-waterfall": "0.1.0", "pug": "2.0.3", "sqlite3": "4.0.6", "winston": "3.2.1", "winston-daily-rotate-file": "3.8.0", "youtube-playlist-info": "1.1.2", + "ytdl-core": "^1.0.0", "ytdl-core-discord": "^1.0.3" }, "devDependencies": { From 2e993c368818e6026ec633461ce6196524ec6382 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Tue, 8 Oct 2019 22:17:47 +0200 Subject: [PATCH 37/54] Music fix... again - fixed not playing the next song --- bot.js | 29 ----------------------------- lib/music/index.js | 1 + 2 files changed, 1 insertion(+), 29 deletions(-) diff --git a/bot.js b/bot.js index 947af8f..bd63aa0 100644 --- a/bot.js +++ b/bot.js @@ -240,35 +240,6 @@ class Bot { gh.musicPlayer.checkListeners(); } }); - - this.client.on('message', async (msg) => { - try { - let sql = this.maindb.sql; - let server = null; - let channel = null; - let user = msg.author.tag; - let message = msg.content; - if (msg.guild) { - server = msg.guild.name; - channel = msg.channel.name; - await this.maindb.run(sql.insert('messages', { - server: sql.parameter(1), - channel: sql.parameter(2), - username: sql.parameter(3), - message: sql.parameter(4) - }), [server, channel, user, message]); - } else { - await this.maindb.run(sql.insert('messages', { - channel: sql.parameter(1), - username: sql.parameter(2), - message: sql.parameter(3) - }), ['PRIVATE', user, message]); - } - } catch (err) { - this.logger.warn(err.message); - this.logger.debug(err.stack); - } - }); } /** diff --git a/lib/music/index.js b/lib/music/index.js index 7482313..9375876 100644 --- a/lib/music/index.js +++ b/lib/music/index.js @@ -179,6 +179,7 @@ class MusicPlayer extends xevents.ExtendedEventEmitter { this.queue.push(this.current); let toggleNext = () => { if (this.queue.length > 0) { + this.disp = null; this.current = this.queue.shift(); this.emit('next', this.current); this.playYouTube(this.current.url).catch((err) => this._logger.warn(err.message)); From 92b52a785d90154cad1ded8620647f93a7559a7b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2019 13:37:24 +0100 Subject: [PATCH 38/54] Update dependency body-parser to v1.19.0 (#72) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b96ca4e..f2f2a9d 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "dependencies": { "args-parser": "1.1.0", - "body-parser": "1.18.3", + "body-parser": "1.19.0", "compression": "1.7.4", "connect-sqlite3": "0.9.11", "cors": "2.8.5", From 9536c26e58d86c67af6db45359b1100fe11bfd8c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2019 13:37:55 +0100 Subject: [PATCH 39/54] Update dependency discord.js to v11.5.1 (#78) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f2f2a9d..48572c6 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "compression": "1.7.4", "connect-sqlite3": "0.9.11", "cors": "2.8.5", - "discord.js": "11.4.2", + "discord.js": "11.5.1", "express": "4.16.4", "express-compile-sass": "4.0.0", "express-graphql": "0.8.0", From 4fe9a3e0617a78129b5464f746be928073024a57 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2019 13:42:26 +0100 Subject: [PATCH 40/54] Update dependency express-session to v1.16.2 (#86) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 48572c6..f85e560 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "express": "4.16.4", "express-compile-sass": "4.0.0", "express-graphql": "0.8.0", - "express-session": "1.16.1", + "express-session": "1.16.2", "ffmpeg-binaries": "4.0.0", "fs-extra": "7.0.1", "get-youtube-title": "1.0.0", From a97fd541bf569bad8b9e1e12575fbbef73565d45 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2019 13:42:51 +0100 Subject: [PATCH 41/54] Update dependency graphql to v14.5.8 (#80) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f85e560..64e4d62 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "ffmpeg-binaries": "4.0.0", "fs-extra": "7.0.1", "get-youtube-title": "1.0.0", - "graphql": "14.2.1", + "graphql": "14.5.8", "js-md5": "0.7.3", "js-sha512": "0.8.0", "js-yaml": "latest", From a59cae0fd456e47e1e732f5db2c857b6cb1c349d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2019 13:43:33 +0100 Subject: [PATCH 42/54] Update dependency node-fetch to v2.6.0 (#73) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 64e4d62..f77034d 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "js-md5": "0.7.3", "js-sha512": "0.8.0", "js-yaml": "latest", - "node-fetch": "2.3.0", + "node-fetch": "2.6.0", "node-opus": "0.3.1", "node-sass": "4.11.0", "pg": "^7.8.2", From 2898373367e949aef3c236b7cae67b14e51e6a6c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2019 13:44:18 +0100 Subject: [PATCH 43/54] Update dependency node-sass to v4.12.0 (#74) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f77034d..35ce612 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "js-yaml": "latest", "node-fetch": "2.6.0", "node-opus": "0.3.1", - "node-sass": "4.11.0", + "node-sass": "4.12.0", "pg": "^7.8.2", "promise-waterfall": "0.1.0", "pug": "2.0.3", From a2df1d8fd58be96fcfd88954a6122a2ebd3c111e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2019 13:44:44 +0100 Subject: [PATCH 44/54] Update dependency pug to v2.0.4 (#87) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 35ce612..6234021 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "node-sass": "4.12.0", "pg": "^7.8.2", "promise-waterfall": "0.1.0", - "pug": "2.0.3", + "pug": "2.0.4", "sqlite3": "4.0.6", "winston": "3.2.1", "winston-daily-rotate-file": "4.2.1", From beb99016aea43e784f80bac24b65212bc8ef0ea0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2019 13:45:03 +0100 Subject: [PATCH 45/54] Update dependency assert to v2 (#82) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6234021..f56028f 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "ytdl-core-discord": "^1.0.3" }, "devDependencies": { - "assert": "1.4.1", + "assert": "2.0.0", "chai": "4.2.0", "mocha": "6.2.1", "nyc": "14.1.1", From 0edfd56489f0817366624be479c90d79eb8d45ed Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2019 13:45:11 +0100 Subject: [PATCH 46/54] Update dependency eslint to v6 (#89) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f56028f..700612f 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "rewire": "4.0.1", "sinon": "7.5.0", "eslint-plugin-graphql": "3.0.3", - "eslint": "5.16.0", + "eslint": "6.5.1", "eslint-plugin-promise": "4.1.1", "opusscript": "0.0.7" }, From 909b70e70c970ccb730acaeef319ef79d1df6089 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2019 13:45:24 +0100 Subject: [PATCH 47/54] Update dependency fs-extra to v8 (#83) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 700612f..af39db1 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "express-graphql": "0.8.0", "express-session": "1.16.2", "ffmpeg-binaries": "4.0.0", - "fs-extra": "7.0.1", + "fs-extra": "8.1.0", "get-youtube-title": "1.0.0", "graphql": "14.5.8", "js-md5": "0.7.3", From c5e44b3edff726a1f2fb3f117a3fff40027f0368 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2019 13:46:01 +0100 Subject: [PATCH 48/54] Update dependency sqlite3 to v4.1.0 (#81) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index af39db1..0352d12 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "pg": "^7.8.2", "promise-waterfall": "0.1.0", "pug": "2.0.4", - "sqlite3": "4.0.6", + "sqlite3": "4.1.0", "winston": "3.2.1", "winston-daily-rotate-file": "4.2.1", "youtube-playlist-info": "1.1.2", From 5829b403b1fb5055c8dc8447bc4e6cb5e0804394 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2019 13:46:24 +0100 Subject: [PATCH 49/54] Update dependency express to v4.17.1 (#79) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0352d12..90c4bc3 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "connect-sqlite3": "0.9.11", "cors": "2.8.5", "discord.js": "11.5.1", - "express": "4.16.4", + "express": "4.17.1", "express-compile-sass": "4.0.0", "express-graphql": "0.8.0", "express-session": "1.16.2", From 4adb7eebf3edbf05c20bee80fdeb4d9e405bcafe Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2019 13:46:49 +0100 Subject: [PATCH 50/54] Update dependency eslint-plugin-promise to v4.2.1 (#88) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 90c4bc3..22c7f9d 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "sinon": "7.5.0", "eslint-plugin-graphql": "3.0.3", "eslint": "6.5.1", - "eslint-plugin-promise": "4.1.1", + "eslint-plugin-promise": "4.2.1", "opusscript": "0.0.7" }, "eslintConfig": { From ebb372a65298f15762724c61a30d0f2af7825dea Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2019 13:47:01 +0100 Subject: [PATCH 51/54] Update dependency eslint-plugin-graphql to v3.1.0 (#91) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 22c7f9d..baad2ac 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "nyc": "14.1.1", "rewire": "4.0.1", "sinon": "7.5.0", - "eslint-plugin-graphql": "3.0.3", + "eslint-plugin-graphql": "3.1.0", "eslint": "6.5.1", "eslint-plugin-promise": "4.2.1", "opusscript": "0.0.7" From 75b496a98d873df193aec35d5c75f01e3cad20c5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2019 13:47:09 +0100 Subject: [PATCH 52/54] Update dependency node-opus to v0.3.2 (#85) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index baad2ac..d62d47d 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "js-sha512": "0.8.0", "js-yaml": "latest", "node-fetch": "2.6.0", - "node-opus": "0.3.1", + "node-opus": "0.3.2", "node-sass": "4.12.0", "pg": "^7.8.2", "promise-waterfall": "0.1.0", From a978049fb197908164a50c10948601bb322e1c8f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2019 13:47:27 +0100 Subject: [PATCH 53/54] Update dependency express-graphql to v0.9.0 (#90) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d62d47d..d67216d 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "discord.js": "11.5.1", "express": "4.17.1", "express-compile-sass": "4.0.0", - "express-graphql": "0.8.0", + "express-graphql": "0.9.0", "express-session": "1.16.2", "ffmpeg-binaries": "4.0.0", "fs-extra": "8.1.0", From 045dff609628d9c8032029bfcd845ebafc3f83f5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2019 13:47:57 +0100 Subject: [PATCH 54/54] Pin dependencies (#71) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d67216d..75af553 100644 --- a/package.json +++ b/package.json @@ -27,14 +27,14 @@ "node-fetch": "2.6.0", "node-opus": "0.3.2", "node-sass": "4.12.0", - "pg": "^7.8.2", + "pg": "7.12.1", "promise-waterfall": "0.1.0", "pug": "2.0.4", "sqlite3": "4.1.0", "winston": "3.2.1", "winston-daily-rotate-file": "4.2.1", "youtube-playlist-info": "1.1.2", - "ytdl-core-discord": "^1.0.3" + "ytdl-core-discord": "1.0.3" }, "devDependencies": { "assert": "2.0.0",