From 72794a2f633dccf6b34d14aa1ca0d5f8e36c5f87 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Fri, 1 Feb 2019 21:18:41 +0100 Subject: [PATCH] Added Query entries - changed id to type ID! - changed id to md5 hash of base64 string of properties - added discord Id - added paging to arrays - implemented guildHandler into Guild query type - added saved to retrieve saved songs/playlists - added ready indicator boolean - added querying logs - added presences array - added config as String - added prefix --- bot.js | 41 +++++++--- graphql/schema.graphql | 43 ++++++++--- lib/logging.js | 31 ++++---- lib/webapi.js | 172 ++++++++++++++++++++++++++++++++++++++--- package.json | 2 + 5 files changed, 241 insertions(+), 48 deletions(-) diff --git a/bot.js b/bot.js index 6585572..c7be1c3 100644 --- a/bot.js +++ b/bot.js @@ -4,7 +4,6 @@ const Discord = require("discord.js"), cmd = require("./lib/cmd"), guilding = require('./lib/guilding'), utils = require('./lib/utils'), - webapi = require('./lib/webapi'), config = require('./config.json'), args = require('args-parser')(process.argv), sqlite3 = require('sqlite3'), @@ -12,6 +11,8 @@ const Discord = require("discord.js"), prefix = args.prefix || config.prefix || '~', gamepresence = args.game || config.presence; +let webapi = null; + class Bot { constructor() { this.client = new Discord.Client(); @@ -44,11 +45,11 @@ class Bot { } } guilding.setLogger(logger); - webapi.setLogger(logger); cmd.init(prefix); logger.verbose('Registering commands'); this.registerCommands(); logger.debug('Checking for ./data/ existence'); + utils.dirExistence('./data', () => { logger.verbose('Connecting to main database'); this.maindb = new sqlite3.Database('./data/main.db', (err) => { @@ -58,7 +59,7 @@ class Bot { this.maindb.run(`${utils.sql.tableExistCreate} presences ( ${utils.sql.pkIdSerial}, text VARCHAR(255) UNIQUE NOT NULL - )`, (err) => { + )`, (err) => { if (err) { logger.error(err.message); } else { @@ -72,15 +73,27 @@ class Bot { this.registerCallbacks(); if (config.webservice && config.webservice.enabled) { + logger.verbose('Importing webapi'); + webapi = require('./lib/webapi'); + webapi.setLogger(logger); logger.verbose('Creating WebServer'); this.webServer = new webapi.WebServer(config.webservice.port || 8080); logger.debug('Setting Reference Objects to webserver'); + this.webServer.setReferenceObjects({ - client: this.client + client: this.client, + presences: this.presences, + maind: this.maindb, + prefix: prefix, + getGuildHandler: (guild) => this.getGuildHandler(guild, prefix) }); } } + /** + * Starting the bot by connecting to the discord service and starting the webservice. + * @returns {Promise} + */ start() { return new Promise((resolve, reject) => { this.client.login(authToken).then(() => { @@ -89,7 +102,7 @@ class Bot { }).catch((err) => { reject(err); }); - if (this.webServer){ + if (this.webServer) { this.webServer.start(); logger.info(`WebServer runing on port ${this.webServer.port}`); } @@ -116,7 +129,8 @@ class Bot { }); this.presences.push(line); }); - this.rotator = this.client.setInterval(() => this.rotatePresence(), config.presence_duration || 360000); + this.rotator = this.client.setInterval(() => this.rotatePresence(), + config.presence_duration || 360000); fs.unlink('./data/presences.txt', (err) => { if (err) logger.warn(err.message); @@ -140,7 +154,8 @@ class Bot { this.presences.push(row.text); } } - this.rotator = this.client.setInterval(() => this.rotatePresence(), config.presence_duration || 360000); + this.rotator = this.client.setInterval(() => this.rotatePresence(), + config.presence_duration || 360000); }) } } @@ -219,8 +234,10 @@ class Bot { rotatePresence() { let pr = this.presences.shift(); this.presences.push(pr); - this.client.user.setPresence({game: {name: `${gamepresence} | ${pr}`, type: "PLAYING"}, status: 'online'}); - logger.debug(`Presence rotation to ${pr}`); + this.client.user.setPresence({ + game: {name: `${gamepresence} | ${pr}`, type: "PLAYING"}, + status: 'online' + }).then(() => logger.debug(`Presence rotation to ${pr}`)); } @@ -234,7 +251,11 @@ class Bot { this.client.on('ready', () => { logger.info(`logged in as ${this.client.user.tag}!`); - this.client.user.setPresence({game: {name: gamepresence, type: "PLAYING"}, status: 'online'}) + this.client.user.setPresence({ + game: { + name: gamepresence, type: "PLAYING" + }, status: 'online' + }) .catch((err) => { if (err) logger.warn(err.message); diff --git a/graphql/schema.graphql b/graphql/schema.graphql index 404e47d..24eca7d 100644 --- a/graphql/schema.graphql +++ b/graphql/schema.graphql @@ -1,39 +1,60 @@ type User { - id: String - name: String + id: ID! + discordId: String + name: String! avatar: String bot: Boolean - tag: String + tag: String! } type Role { - id: String + id: ID! + discordId: String name: String color: String - members: [GuildMember] + members(first: Int = 10, offset: Int = 0, id: String): [GuildMember] } type GuildMember { - id: String + id: ID! + discordId: String user: User nickname: String - roles: [Role] + roles(first: Int = 10, offset: Int = 0, id: String): [Role] highestRole: Role } type Guild { - id: String + id: ID! + discordId: String name: String owner: GuildMember - members: [GuildMember] - roles: [Role] + members(first: Int = 10, offset: Int = 0, id: String): [GuildMember] + roles(first: Int = 10, offset: Int = 0, id: String): [Role] memberCount: Int icon: String + ready: Boolean + saved(first: Int = 10, offset: Int = 0, id: String, name: String): [SavedEntry!] } type Client { - guilds(count: Int): [Guild] + guilds(first: Int = 10, offset: Int = 0, id: String): [Guild] user: User ping: Float status: Int uptime: Int } +type SavedEntry { + id: ID! + url: String! + name: String! +} +type LogEntry { + id: ID! + message: String + level: String + timestamp: String +} type Query { client: Client + presences: [String]! + config: String + prefix: String + logs(first: Int, offset: Int = 0, id: String, last: Int = 10): [LogEntry] } \ No newline at end of file diff --git a/lib/logging.js b/lib/logging.js index d73087c..3d8567d 100644 --- a/lib/logging.js +++ b/lib/logging.js @@ -11,37 +11,38 @@ const winston = require('winston'), loggingFullFormat = winston.format.combine( winston.format.splat(), winston.format.timestamp({ - format: 'MM-DD HH:mm:ss.SSS' // don't include the year because the filename already tells + format: 'YY-MM-DD HH:mm:ss.SSS' }), - fileLoggingFormat // the logging format for files that logs with a capitalized level + winston.format.json() ), logger = winston.createLogger({ level: winston.config.npm.levels, // logs with npm levels - format: loggingFullFormat, // the full format for files + format: loggingFullFormat, transports: [ new winston.transports.Console({ format: winston.format.combine( - winston.format.colorize(), // colorizes the console logging output + winston.format.colorize(), winston.format.splat(), winston.format.timestamp({ - format: 'YY-MM-DD HH:mm:ss.SSS' // logs with the year to the console + format: 'YY-MM-DD HH:mm:ss.SSS' }), - consoleLoggingFormat // logs with the custom console format + consoleLoggingFormat ), - level: args.loglevel || 'info' // logs to the console with the arg loglevel or info if it is not given + level: args.loglevel || 'info' }), new winston.transports.File({ - level: 'debug', // logs with debug level to the active file - filename: './.log/latest.log', // the filename of the current file, + level: 'debug', + filename: './.log/latest.log', options: {flags: 'w'} // overwrites the file on restart }), new DailyRotateFile({ - level: 'verbose', // log verbose in the rotating logvile - filename: './.log/%DATE%.log', // the pattern of the filename - datePattern: 'YYYY-MM-DD', // the pattern of %DATE% - zippedArchive: true, // indicates that old logfiles should get zipped - maxSize: '32m', // the maximum filesize - maxFiles: '30d' // the maximum files to keep + level: 'verbose', + filename: './.log/%DATE%.log', + datePattern: 'YYYY-MM-DD', + zippedArchive: true, + maxSize: '32m', + maxFiles: '30d', + json: true }) ] }); diff --git a/lib/webapi.js b/lib/webapi.js index 32f578a..1e16e23 100644 --- a/lib/webapi.js +++ b/lib/webapi.js @@ -1,6 +1,8 @@ const express = require('express'), graphqlHTTP = require('express-graphql'), {buildSchema} = require('graphql'), + compression = require('compression'), + md5 = require('js-md5'), config = require('../config.json'), fs = require('fs'); @@ -18,7 +20,19 @@ exports.WebServer = class { this.root = {}; } + /** + * Starting the api webserver + */ start() { + this.app.use(compression({ + filter: (req, res) => { + if (req.headers['x-no-compression']) { + return false + } else { + return compression.filter(req, res); + } + } + })); this.app.use('/graphql', graphqlHTTP({ schema: this.schema, rootValue: this.root, @@ -30,9 +44,17 @@ exports.WebServer = class { setReferenceObjects(objects) { this.root = { client: { - guilds: ({count}) => { + guilds: (args) => { let dcGuilds = objects.client.guilds.values(); - return Array.from(dcGuilds).map((x) => new Guild(x)).slice(0, count); + if (args.id) { + return [Array.from(dcGuilds) + .map((x) => new Guild(x, objects.getGuildHandler(x))) + .find(x => (x.id === args.id))]; + } else { + return Array.from(dcGuilds) + .map((x) => new Guild(x, objects.getGuildHandler(x))) + .slice(args.offset, args.offset + args.first); + } }, user: () => { return new User(objects.client.user); @@ -45,54 +67,180 @@ exports.WebServer = class { }, uptime: () => { return objects.client.uptime; - } + }, + }, + prefix: objects.prefix, + presences: objects.presences, + config: JSON.stringify(config), + logs: (args) => { + return new Promise((resolve) => { + let logEntries = []; + let lineReader = require('readline').createInterface({ + input: require('fs').createReadStream('./.log/latest.log') + }); + lineReader.on('line', (line) => { + logEntries.push(new LogEntry(JSON.parse(line))); + }); + lineReader.on('close', () => { + if (args.id) { + resolve([logEntries.find(x => (x.id === args.id))]); + } else if (args.first) { + resolve(logEntries.slice(args.offset, args.offset + args.first)); + } else { + resolve(logEntries.slice(logEntries.length - args.last)); + } + }) + }) } } } }; +function generateID(valArr) { + let b64 = Buffer.from(valArr.map(x => { + if (x) + return x.toString(); + else + return 'null'; + }).join('_')).toString('base64'); + return md5(b64); +} + class Guild { - constructor(discordGuild) { - this.id = discordGuild.id; + constructor(discordGuild, guildHandler) { + this.id = generateID(['Guild', discordGuild.id]); + this.discordId = discordGuild.id; this.name = discordGuild.name; this.owner = new GuildMember(discordGuild.owner); this.memberCount = discordGuild.memberCount; this.icon = discordGuild.iconURL; - this.members = Array.from(discordGuild.members.values()) + this.prMembers = Array.from(discordGuild.members.values()) .map((x) => new GuildMember(x)); - this.roles = Array.from(discordGuild.roles.values()) + this.prRoles = Array.from(discordGuild.roles.values()) .map((x) => new Role(x)); + guildHandler = guildHandler || {}; + this.ready = guildHandler.ready; + this.prSaved = null; + this.guildHandler = guildHandler; + } + + querySaved() { + return new Promise((resolve) => { + if (this.guildHandler.db) { + let saved = []; + this.guildHandler.db.all('SELECT * FROM playlists', (err, rows) => { + if (err) { + logger.error(err.message); + resolve(null) + } else { + for (let row of rows) { + saved.push({ + id: generateID(['Guild', 'ROW', row.id, row.name]), + name: row.name, + url: row.url + }); + } + resolve(saved); + } + }) + } else { + resolve(null); + } + }); + } + + saved(args) { + return new Promise((resolve) => { + this.querySaved().then((result) => { + if (result) { + if (args.id) { + resolve([result.find(x => (x.id === args.id))]); + } else if (args.name) { + resolve([result.find(x => (x.name === args.name))]); + } else { + resolve(result.slice(args.offset, args.offset + args.first)); + } + } else { + resolve(null); + } + }) + + }) + } + + roles(args) { + if (args.id) { + return [this.prRoles.find(x => (x.id === args.id))]; + } else { + return this.prRoles.slice(args.offset, args.offset + args.first); + } + } + + members(args) { + if (args.id) { + return [this.prMembers.find(x => (x.id === args.id))]; + } else { + return this.prMembers.slice(args.offset, args.offset + args.first); + } } } class Role { constructor(discordRole) { - this.id = discordRole.id; + this.id = generateID(['Role', discordRole.id]); + this.discordId = discordRole.id; this.name = discordRole.name; this.color = discordRole.hexColor; - this.members = Array.from(discordRole.members.values) + this.prMembers = Array.from(discordRole.members.values) .map((x) => new GuildMember(x)); } + + members(args) { + if (args.id) { + return [this.prMembers.find(x => (x.id === args.id))]; + } else { + return this.prMembers.slice(args.offset, args.offset + args.first); + } + } } class GuildMember { constructor(discordGuildMember) { - this.id = discordGuildMember.id; + this.id = generateID(['GuildMember', discordGuildMember.id]); + this.discordId = discordGuildMember.id; this.user = new User(discordGuildMember.user); this.nickname = discordGuildMember.nickname; - this.roles = Array.from(discordGuildMember.roles.values()) + this.prRoles = Array.from(discordGuildMember.roles.values()) .map((x) => new Role(x)); this.highestRole = new Role(discordGuildMember.highestRole); } + + roles(args) { + if (args.id) { + return [this.prRoles.find(x => (x.id === args.id))]; + } else { + return this.prRoles.slice(args.offset, args.offset + args.first); + } + } } class User { constructor(discordUser) { - this.id = discordUser.id; + this.id = generateID(['User', discordUser.id]); + this.discordId = discordUser.id; this.name = discordUser.username; this.avatar = discordUser.avatarURL; this.bot = discordUser.bot; this.tag = discordUser.tag; this.tag = discordUser.tag; } +} + +class LogEntry { + constructor(entry) { + this.id = generateID(['LogEntry', entry.level, entry.timestamp]); + this.message = entry.message; + this.timestamp = entry.timestamp; + this.level = entry.level; + } } \ No newline at end of file diff --git a/package.json b/package.json index 0ae3f96..1b156a1 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ }, "dependencies": { "args-parser": "1.1.0", + "compression": "^1.7.3", "discord.js": "11.4.2", "eslint-plugin-graphql": "^3.0.1", "express": "^4.16.4", @@ -15,6 +16,7 @@ "ffmpeg-binaries": "4.0.0", "get-youtube-title": "1.0.0", "graphql": "^14.1.1", + "js-md5": "^0.7.3", "opusscript": "0.0.6", "sqlite3": "4.0.6", "winston": "3.2.1",