You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
discordbot.js/lib/webapi.js

441 lines
14 KiB
JavaScript

const express = require('express'),
graphqlHTTP = require('express-graphql'),
{buildSchema} = require('graphql'),
compression = require('compression'),
md5 = require('js-md5'),
cors = require('cors'),
fs = require('fs'),
compileSass = require('express-compile-sass'),
config = require('../config.json'),
utils = require('../lib/utils');
let logger = require('winston');
exports.setLogger = function (newLogger) {
logger = newLogger;
};
exports.WebServer = class {
constructor(port, schema, root, referenceObjects) {
this.app = express();
this.server = null;
this.port = port;
this.schema = buildSchema(fs.readFileSync('./web/graphql/schema.graphql', 'utf-8'));
this.root = {};
if (referenceObjects)
this.setReferenceObjects(referenceObjects);
}
/**
* Starting the api webserver
*/
start() {
this.app.use(cors());
if (config.webservice.useBearers) {
this.app.use('/graphql', (req, res, next) => this.authenticateUser(req, res, next));
}
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,
graphiql: config.webservice.graphiql || false
}));
this.app.use(compileSass({
root: './web/http/'
}));
this.app.use('/', express.static('./web/http/'));
this.server = this.app.listen(this.port);
}
/**
* Stopping the webserver
* @returns {Promise<any>}
*/
stop() {
return new Promise((resolve) => {
if (this.server)
this.server.close(resolve);
else
resolve();
})
}
/**
* Generates a token for a given username
* @param username
* @param scope
* @returns {Promise<any>}
*/
generateToken(username, scope) {
return new Promise((resolve, reject) => {
let token = generateID(['TOKEN', username, (new Date()).getMilliseconds()]);
this.maindb.run('INSERT INTO users (username, token, scope) VALUES (?, ?, ?)',
[username, token, scope], (err) => {
if (err) {
logger.warn(err.message);
reject(err);
} else {
resolve(token);
}
})
});
}
authenticateUser(req, res, next) {
if (req.headers.authorization
&& req.headers.authorization.split(' ')[0] === 'Bearer') {
let bearer = req.headers.authorization.split(' ')[1];
this.maindb.get('SELECT * FROM users WHERE token = ?', [bearer], (err, user) => {
if (err) {
logger.warn(err.message);
logger.debug('Unauthorized access');
res.status(401);
res.end('Unauthorized Access');
} else {
if (!user) {
res.status(401);
res.end('Unauthorized Access');
} else {
req.user = user;
next();
}
}
});
} else {
logger.debug('Unauthorized access');
res.status(401);
res.end('Unauthorized Access');
}
}
/**
* Setting all objects that web can query
* @param objects
*/
setReferenceObjects(objects) {
this.maindb = objects.maindb;
this.maindb.run(`${utils.sql.tableExistCreate} users (
${utils.sql.pkIdSerial},
username VARCHAR(32) UNIQUE NOT NULL,
token VARCHAR(255) UNIQUE NOT NULL,
scope INTEGER NOT NULL DEFAULT 0
)`, (err) => {
if (err) {
logger.error(err.message);
}
});
this.root = {
client: {
guilds: (args) => {
let dcGuilds = objects.client.guilds.values();
if (args.id) {
return [Array.from(dcGuilds)
.map((x) => new Guild(x, objects.getGuildHandler(x)))
.find(x => (x.id === args.id))];
} else {
try {
return Array.from(dcGuilds)
.slice(args.offset, args.offset + args.first)
.map((x) => new Guild(x, objects.getGuildHandler(x)));
} catch (err) {
logger.error(err.stack);
return null;
}
}
},
guildCount: () => {
return Array.from(objects.client.guilds.values()).length;
},
user: () => {
return new User(objects.client.user);
},
ping: () => {
return objects.client.ping;
},
status: () => {
return objects.client.status;
},
uptime: () => {
return objects.client.uptime;
},
voiceConnectionCount: () => {
let dcGuilds = Array.from(objects.client.guilds.values());
return dcGuilds.filter((x) => {
let gh = objects.guildHandlers[x.id];
if (gh) {
if (gh.dj)
return gh.dj.playing;
else
return false;
} else {
return false;
}
}).length;;
}
},
prefix: objects.prefix,
presences: objects.presences,
config: () => {
let newConfig = JSON.parse(JSON.stringify(config));
delete newConfig.api;
return JSON.stringify(newConfig, null, ' ')
},
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.level) {
logEntries = logEntries
.filter(x => (utils.logLevels[x.level] >= utils.logLevels[args.level]));
}
if (args.id) {
logEntries = [logEntries.find(x => (x.id === args.id))];
}
if (args.first) {
logEntries = logEntries.slice(args.offset, args.offset + args.first);
} else {
logEntries = logEntries.slice(logEntries.length - args.last);
}
resolve(logEntries);
})
})
}
}
}
};
/**
* generating an unique id
* @param valArr
* @returns {*}
*/
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 DJ {
constructor(musicDj) {
this.dj = musicDj;
this.quality = musicDj.quality;
}
queue(args) {
let queue = this.dj.queue.map((x) => {
return {
id: generateID(['Media', x.url]),
name: x.title,
url: x.url,
thumbnail: utils.YouTube.getVideoThumbnailUrlFromUrl(x.url)
}
});
if (args.id) {
return [queue.find(x => (x.id === args.id))];
} else {
return queue.slice(args.offset, args.offset + args.first);
}
}
get playing() {
return this.dj.playing;
}
get connected() {
return this.dj.connected;
}
get queueCount() {
return this.dj.queue.length;
}
get songStartTime() {
return this.dj.disp.player.streamingData.startTime;
}
get volume() {
return this.dj.volume;
}
get repeat() {
return this.dj.repeat;
}
get currentSong() {
let x = this.dj.current;
return {
id: generateID(['Media', x.url]),
name: x.title,
url: x.url,
thumbnail: utils.YouTube.getVideoThumbnailUrlFromUrl(x.url)
}
}
get voiceChannel() {
return this.dj.voiceChannel.name;
}
}
class Guild {
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.prMembers = Array.from(discordGuild.members.values())
.map((x) => new GuildMember(x));
this.prRoles = Array.from(discordGuild.roles.values())
.map((x) => new Role(x));
guildHandler = guildHandler || {};
this.ready = guildHandler.ready;
this.prSaved = null;
this.guildHandler = guildHandler;
this.dj = this.guildHandler.dj ? new DJ(this.guildHandler.dj) : null;
}
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(['Media', row.url]),
name: row.name,
url: row.url,
thumbnail: utils.YouTube.getVideoThumbnailUrlFromUrl(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 = generateID(['Role', discordRole.id]);
this.discordId = discordRole.id;
this.name = discordRole.name;
this.color = discordRole.hexColor;
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 = generateID(['GuildMember', discordGuildMember.id]);
this.discordId = discordGuildMember.id;
this.user = new User(discordGuildMember.user);
this.nickname = discordGuildMember.nickname;
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 = 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;
this.presence = {
game: discordUser.presence.game? discordUser.presence.game.name : null,
status: discordUser.presence.status
}
}
}
class LogEntry {
constructor(entry) {
this.id = generateID(['LogEntry', entry.level, entry.timestamp]);
this.message = entry.message;
this.timestamp = entry.timestamp;
this.level = entry.level;
}
}