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
feature/api-rewrite
Trivernis 5 years ago
parent 1e70c5e9ea
commit 1b08edd278

@ -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

@ -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=<DiscordBotToken>] [--ytapi=<GoogleApiKey>] [--owner=<DiscordTag>] [--prefix=<Char>] [--game=<String>] [-i=<Boolen>]`
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:

@ -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

@ -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.

@ -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() {

@ -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<>}
*/

@ -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<Column>}
* @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<Column>}
* @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<String>} - 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<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) {
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<String>}
*/
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
});

@ -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",

Loading…
Cancel
Save