commit
7ededc255e
@ -0,0 +1,112 @@
|
|||||||
|
const sqlite3 = require('sqlite3');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Promise function wrappers for sqlite3
|
||||||
|
* @type {Database}
|
||||||
|
*/
|
||||||
|
exports.Database = class {
|
||||||
|
constructor(path) {
|
||||||
|
this.path = path;
|
||||||
|
this.database = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Promise wrapper for sqlite3/Database constructor
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
init() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.database = new sqlite3.Database(this.path, (err) => {
|
||||||
|
if (err)
|
||||||
|
reject(err);
|
||||||
|
else
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Promise wrapper for sqlite3/Database run
|
||||||
|
* @param SQL
|
||||||
|
* @param values
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
run(SQL, values) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (values !== null && values instanceof Array)
|
||||||
|
this.database.run(SQL, values, (err) => {
|
||||||
|
if (err)
|
||||||
|
reject(err);
|
||||||
|
else
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
else
|
||||||
|
this.database.run(SQL, (err) => {
|
||||||
|
if (err)
|
||||||
|
reject(err);
|
||||||
|
else
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Promise wrapper for sqlite3/Database get
|
||||||
|
* @param SQL
|
||||||
|
* @param values
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
get(SQL, values) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (values !== null && values instanceof Array)
|
||||||
|
this.database.get(SQL, values, (err, row) => {
|
||||||
|
if (err)
|
||||||
|
reject(err);
|
||||||
|
else
|
||||||
|
resolve(row);
|
||||||
|
});
|
||||||
|
else
|
||||||
|
this.database.get(SQL, (err, row) => {
|
||||||
|
if (err)
|
||||||
|
reject(err);
|
||||||
|
else
|
||||||
|
resolve(row);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Promise wrapper for sqlite3/Database all
|
||||||
|
* @param SQL
|
||||||
|
* @param values
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
all(SQL, values) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (values !== null && values instanceof Array)
|
||||||
|
this.database.all(SQL, values, (err, rows) => {
|
||||||
|
if (err)
|
||||||
|
reject(err);
|
||||||
|
else
|
||||||
|
resolve(rows);
|
||||||
|
});
|
||||||
|
else
|
||||||
|
this.database.all(SQL, (err, rows) => {
|
||||||
|
if (err)
|
||||||
|
reject(err);
|
||||||
|
else
|
||||||
|
resolve(rows);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for sqlite3/Database close
|
||||||
|
*/
|
||||||
|
close() {
|
||||||
|
this.database.close();
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,477 @@
|
|||||||
|
const express = require('express'),
|
||||||
|
graphqlHTTP = require('express-graphql'),
|
||||||
|
{buildSchema} = require('graphql'),
|
||||||
|
compression = require('compression'),
|
||||||
|
md5 = require('js-md5'),
|
||||||
|
sha512 = require('js-sha512'),
|
||||||
|
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');
|
||||||
|
|
||||||
|
let logger = require('winston');
|
||||||
|
|
||||||
|
exports.setLogger = function (newLogger) {
|
||||||
|
logger = newLogger;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.WebServer = class {
|
||||||
|
constructor(port) {
|
||||||
|
this.app = express();
|
||||||
|
this.server = null;
|
||||||
|
this.port = port;
|
||||||
|
this.schema = buildSchema(fs.readFileSync('./web/graphql/schema.graphql', 'utf-8'));
|
||||||
|
this.root = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
configureExpress() {
|
||||||
|
this.app.set('view engine', 'pug');
|
||||||
|
this.app.set('trust proxy', 1);
|
||||||
|
this.app.set('views', './web/http/');
|
||||||
|
|
||||||
|
if (this.app.get('env') === 'devlopment')
|
||||||
|
this.app.use(require('cors')());
|
||||||
|
|
||||||
|
this.app.use(require('cors')());
|
||||||
|
this.app.use(session({
|
||||||
|
store: new SQLiteStore({dir: './data', db: 'sessions.db'}),
|
||||||
|
secret: config.webservice.sessionSecret,
|
||||||
|
resave: false,
|
||||||
|
saveUninitialized: true,
|
||||||
|
cookie: {secure: 'auto'},
|
||||||
|
genid: () => generateUUID('Session')
|
||||||
|
}));
|
||||||
|
this.app.use(bodyParser.json());
|
||||||
|
this.app.use(bodyParser.urlencoded({extended: true}));
|
||||||
|
|
||||||
|
this.app.use(compression({
|
||||||
|
filter: (req, res) => {
|
||||||
|
if (req.headers['x-no-compression'])
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
return compression.filter(req, res);
|
||||||
|
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
this.app.use(compileSass({
|
||||||
|
root: './web/http/'
|
||||||
|
}));
|
||||||
|
this.app.post('/', async (req, res) => {
|
||||||
|
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]);
|
||||||
|
if (!user) {
|
||||||
|
logger.debug(`User ${req.body.username} failed to authenticate`);
|
||||||
|
res.render('login', {msg: 'Login failed!'});
|
||||||
|
} else {
|
||||||
|
req.session.user = user;
|
||||||
|
res.render('index');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.app.use('/scripts', express.static('./web/http/scripts'));
|
||||||
|
this.app.use((req, res, next) => {
|
||||||
|
if (req.session.user)
|
||||||
|
next();
|
||||||
|
else
|
||||||
|
res.render('login');
|
||||||
|
});
|
||||||
|
this.app.get('/', (req, res) => {
|
||||||
|
res.render('index');
|
||||||
|
});
|
||||||
|
this.app.use('/graphql', graphqlHTTP({
|
||||||
|
schema: this.schema,
|
||||||
|
rootValue: this.root,
|
||||||
|
graphiql: config.webservice.graphiql || false
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starting the api webserver
|
||||||
|
*/
|
||||||
|
start() {
|
||||||
|
this.configureExpress();
|
||||||
|
if (config.webservice.https && config.webservice.https.enabled) {
|
||||||
|
let sslKey = null;
|
||||||
|
let sslCert = null;
|
||||||
|
|
||||||
|
if (config.webservice.https.keyFile)
|
||||||
|
sslKey = fs.readFileSync(config.webservice.https.keyFile, 'utf-8');
|
||||||
|
if (config.webservice.https.certFile)
|
||||||
|
sslCert = fs.readFileSync(config.webservice.https.certFile, 'utf-8');
|
||||||
|
if (sslKey && sslCert) {
|
||||||
|
logger.verbose('Creating https server.');
|
||||||
|
this.server = require('https').createServer({key: sslKey, cert: sslCert}, this.app);
|
||||||
|
} else {
|
||||||
|
logger.warn('Key or certificate file not found. Fallback to http server.');
|
||||||
|
this.server = require('http').createServer(this.app);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.server = require('http').createServer(this.app);
|
||||||
|
}
|
||||||
|
this.server.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
|
||||||
|
* @param password
|
||||||
|
* @param pwIsHash Is the password already a hash string?
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
async createUser(username, password, scope, pwIsHash) {
|
||||||
|
if (!pwIsHash) password = sha512(password);
|
||||||
|
let token = generateUUID(['TOKEN', username]);
|
||||||
|
await this.maindb.run('INSERT INTO users (username, password, token, scope) VALUES (?, ?, ?, ?)',
|
||||||
|
[username, password, token, scope]);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setting all objects that web can query
|
||||||
|
* @param objects
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
)`);
|
||||||
|
this.root = {
|
||||||
|
client: {
|
||||||
|
guilds: async (args) => {
|
||||||
|
let dcGuilds = objects.client.guilds.values();
|
||||||
|
if (args.id)
|
||||||
|
return [(await Promise.all(Array.from(dcGuilds)
|
||||||
|
.map(async (x) => new Guild(x, await objects.getGuildHandler(x)))))
|
||||||
|
.find(x => (x.id === args.id))];
|
||||||
|
else
|
||||||
|
try {
|
||||||
|
return await Promise.all(Array.from(dcGuilds)
|
||||||
|
.slice(args.offset, args.offset + args.first)
|
||||||
|
.map(async (x) => new Guild(x, await 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 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* generating an unique id
|
||||||
|
* @param input
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
function generateUUID(input) {
|
||||||
|
return generateID([input, (new Date()).getMilliseconds()]) + Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for graphql attribute access to the lib/music/DJ
|
||||||
|
*/
|
||||||
|
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 paused() {
|
||||||
|
return this.dj.disp? this.dj.disp.paused : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for graphql access to the discord.js Guild and lib/guilding/GuildHandler
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
async querySaved() {
|
||||||
|
if (this.guildHandler.db) {
|
||||||
|
let saved = [];
|
||||||
|
let rows = await this.guildHandler.db.all('SELECT * FROM playlists');
|
||||||
|
for (let row of rows)
|
||||||
|
saved.push({
|
||||||
|
id: generateID(['Media', row.url]),
|
||||||
|
name: row.name,
|
||||||
|
url: row.url,
|
||||||
|
thumbnail: utils.YouTube.getVideoThumbnailUrlFromUrl(row.url)
|
||||||
|
});
|
||||||
|
return saved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async saved(args) {
|
||||||
|
let result = await this.querySaved();
|
||||||
|
if (args.id)
|
||||||
|
return [result.find(x => (x.id === args.id))];
|
||||||
|
else if (args.name)
|
||||||
|
return [result.find(x => (x.name === args.name))];
|
||||||
|
else
|
||||||
|
return result.slice(args.offset, args.offset + args.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for graphql access to the discord.js Role
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for graphql access to the discord.js GuildMember
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for graphql access to the discord.js User
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for graphql access to log entries
|
||||||
|
*/
|
||||||
|
class LogEntry {
|
||||||
|
constructor(entry) {
|
||||||
|
this.id = generateID(['LogEntry', entry.level, entry.timestamp]);
|
||||||
|
this.message = entry.message;
|
||||||
|
this.timestamp = entry.timestamp;
|
||||||
|
this.level = entry.level;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
type Presence {
|
||||||
|
game: String
|
||||||
|
status: String
|
||||||
|
}
|
||||||
|
type User {
|
||||||
|
id: ID!
|
||||||
|
discordId: String
|
||||||
|
name: String!
|
||||||
|
avatar: String
|
||||||
|
bot: Boolean
|
||||||
|
tag: String!
|
||||||
|
presence: Presence
|
||||||
|
}
|
||||||
|
type Role {
|
||||||
|
id: ID!
|
||||||
|
discordId: String
|
||||||
|
name: String
|
||||||
|
color: String
|
||||||
|
members(first: Int = 10, offset: Int = 0, id: String): [GuildMember]
|
||||||
|
}
|
||||||
|
type GuildMember {
|
||||||
|
id: ID!
|
||||||
|
discordId: String
|
||||||
|
user: User
|
||||||
|
nickname: String
|
||||||
|
roles(first: Int = 10, offset: Int = 0, id: String): [Role]
|
||||||
|
highestRole: Role
|
||||||
|
}
|
||||||
|
type DJ {
|
||||||
|
queue(first: Int = 10, offset: Int = 0, id: String): [MediaEntry]
|
||||||
|
queueCount: Int!
|
||||||
|
songStartTime: String
|
||||||
|
playing: Boolean!
|
||||||
|
volume: Float
|
||||||
|
repeat: Boolean
|
||||||
|
currentSong: MediaEntry
|
||||||
|
quality: String
|
||||||
|
voiceChannel: String
|
||||||
|
connected: Boolean!
|
||||||
|
paused: Boolean!
|
||||||
|
}
|
||||||
|
type Guild {
|
||||||
|
id: ID!
|
||||||
|
discordId: String
|
||||||
|
name: String
|
||||||
|
owner: GuildMember
|
||||||
|
dj: DJ
|
||||||
|
members(first: Int = 10, offset: Int = 0, id: String): [GuildMember]
|
||||||
|
memberCount: Int!
|
||||||
|
roles(first: Int = 10, offset: Int = 0, id: String): [Role]
|
||||||
|
icon: String
|
||||||
|
ready: Boolean
|
||||||
|
saved(first: Int = 10, offset: Int = 0, id: String, name: String): [MediaEntry!]
|
||||||
|
savedCount: Int!
|
||||||
|
}
|
||||||
|
type Client {
|
||||||
|
guilds(first: Int = 10, offset: Int = 0, id: String): [Guild]
|
||||||
|
guildCount: Int
|
||||||
|
voiceConnectionCount: Int
|
||||||
|
user: User
|
||||||
|
ping: Float
|
||||||
|
status: Int
|
||||||
|
uptime: Int
|
||||||
|
}
|
||||||
|
type MediaEntry {
|
||||||
|
id: ID!
|
||||||
|
url: String!
|
||||||
|
name: String!
|
||||||
|
thumbnail: 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, level: String): [LogEntry]
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
doctype html
|
||||||
|
head
|
||||||
|
meta(charset='UTF-8')
|
||||||
|
title Dashboard
|
||||||
|
script(src='https://code.jquery.com/jquery-3.3.1.min.js')
|
||||||
|
script(type='text/javascript' src='https://momentjs.com/downloads/moment.min.js')
|
||||||
|
link(type='text/css' rel='stylesheet' href='sass/style.sass')
|
||||||
|
script(type='text/javascript' src='scripts/query.js')
|
||||||
|
#content
|
||||||
|
#column-left.column
|
||||||
|
h2.cell Logs
|
||||||
|
#log-container.listContainer
|
||||||
|
#column-middle.column
|
||||||
|
#avatar-container
|
||||||
|
#status-indicator
|
||||||
|
img#user-avatar.cell(src='' alt='Avatar')
|
||||||
|
h3#user-tag.cell
|
||||||
|
h4#user-game.cell
|
||||||
|
.space
|
||||||
|
h2.cell Status
|
||||||
|
.cell
|
||||||
|
span.label.text-right Ping:
|
||||||
|
span#client-ping.text-left
|
||||||
|
.cell
|
||||||
|
span.label.text-right Uptime:
|
||||||
|
span#client-uptime.text-left
|
||||||
|
.cell
|
||||||
|
span.label.text-right Socket Status:
|
||||||
|
span#client-status.text-left
|
||||||
|
.cell
|
||||||
|
span.label.text-right Guild Count:
|
||||||
|
span#client-guildCount.text-left
|
||||||
|
.cell
|
||||||
|
span.label.text-right Active Voice Connections:
|
||||||
|
span#client-vcCount.text-left
|
||||||
|
#column-right.column
|
||||||
|
select#guild-select.cell
|
||||||
|
option(value='select-default') -Select a guild-
|
||||||
|
#guildinfo(style='display: none')
|
||||||
|
.listContainer
|
||||||
|
#guild-icon-container.cell
|
||||||
|
img#guild-icon(src='' alt='Icon')
|
||||||
|
#guild-nameAndIcon.listContainer
|
||||||
|
h2#guild-name.cell
|
||||||
|
p.cell by
|
||||||
|
h3#guild-owner.cell
|
||||||
|
.space
|
||||||
|
h3.cell Stats
|
||||||
|
.cell
|
||||||
|
span.label.text-right Member Count:
|
||||||
|
span#guild-memberCount.text-left
|
||||||
|
.space
|
||||||
|
h3.cell DJ
|
||||||
|
.cell
|
||||||
|
span.label.text-right State:
|
||||||
|
span#guild-djStatus.text-left
|
||||||
|
.cell
|
||||||
|
span.label.text-right Repeat:
|
||||||
|
span#dj-repeat.text-left
|
||||||
|
.cell
|
||||||
|
span.label.text-right Voice Channel:
|
||||||
|
span#dj-voiceChannel.text-left
|
||||||
|
#dj-songinfo.listContainer(style='display: none')
|
||||||
|
a#songinfo-container
|
||||||
|
span#dj-songname
|
||||||
|
img#dj-songImg(src='' alt='')
|
||||||
|
#dj-songProgress(style='display:none')
|
||||||
|
span#dj-songCurrentTS
|
||||||
|
#dj-queue-container
|
||||||
|
span.cell.label(id='Queue Song count')
|
||||||
|
span#dj-queueCount
|
||||||
|
| Songs in Queue
|
||||||
|
span.cell
|
||||||
|
| Next
|
||||||
|
span#dj-queueDisplayCount 0
|
||||||
|
| Songs:
|
||||||
|
#dj-songQueue
|
||||||
|
script.
|
||||||
|
startUpdating();
|
@ -0,0 +1,14 @@
|
|||||||
|
doctype html
|
||||||
|
html
|
||||||
|
head
|
||||||
|
link(type='text/css' rel='stylesheet' href='sass/style.sass')
|
||||||
|
script(type='text/javascript' src='scripts/lib/sha512.min.js')
|
||||||
|
script(src='https://code.jquery.com/jquery-3.3.1.min.js')
|
||||||
|
script(type='text/javascript' src='scripts/login.js')
|
||||||
|
body
|
||||||
|
.listContainer
|
||||||
|
h1(class='cell') Login
|
||||||
|
h3(class='cell') #{message}
|
||||||
|
input(class='cell' id='username' name='username' type='text' required placeholder='Username' onkeypress=handleSubmit)
|
||||||
|
input(class='cell' id='password' name='password' type='password' required placeholder='Password' onkeypress=handleSubmit)
|
||||||
|
button(class='cell' type='submit' onclick='login()') Log in
|
@ -0,0 +1,280 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css?family=Ubuntu')
|
||||||
|
@import vars
|
||||||
|
|
||||||
|
body
|
||||||
|
font-family: $fNormal
|
||||||
|
color: $cPrimary
|
||||||
|
background-color: $cBackground
|
||||||
|
overflow: hidden
|
||||||
|
max-height: 100%
|
||||||
|
max-width: 100%
|
||||||
|
::-webkit-scrollbar
|
||||||
|
width: 12px
|
||||||
|
height: 12px
|
||||||
|
::-webkit-scrollbar-thumb
|
||||||
|
background: darken($cBackground, 5)
|
||||||
|
border-radius: 10px
|
||||||
|
::-webkit-scrollbar-track
|
||||||
|
background: lighten($cBackground, 5)
|
||||||
|
border-radius: 10px
|
||||||
|
|
||||||
|
input
|
||||||
|
color: $cPrimary
|
||||||
|
background: $cBackground
|
||||||
|
border: 2px solid $cPrimary
|
||||||
|
border-radius: 12px
|
||||||
|
padding: 5px
|
||||||
|
margin: auto
|
||||||
|
|
||||||
|
input:focus
|
||||||
|
background: $cBackgroundVariant
|
||||||
|
|
||||||
|
input::placeholder
|
||||||
|
color: darken($cPrimary, 20)
|
||||||
|
|
||||||
|
input.cell
|
||||||
|
margin: 10px auto
|
||||||
|
|
||||||
|
button
|
||||||
|
background: $cBackgroundVariant
|
||||||
|
border: none
|
||||||
|
border-radius: 12px
|
||||||
|
color: $cPrimary
|
||||||
|
padding: 10px
|
||||||
|
|
||||||
|
.column
|
||||||
|
display: table-column
|
||||||
|
padding: 20px
|
||||||
|
align-content: center
|
||||||
|
margin: 0 auto
|
||||||
|
text-align: center
|
||||||
|
max-height: 100vh
|
||||||
|
height: 100vh
|
||||||
|
|
||||||
|
.cell
|
||||||
|
display: list-item
|
||||||
|
list-style: none
|
||||||
|
align-content: center
|
||||||
|
text-align: center
|
||||||
|
margin: auto
|
||||||
|
user-select: none
|
||||||
|
|
||||||
|
.space
|
||||||
|
height: 20px
|
||||||
|
|
||||||
|
h2.cell
|
||||||
|
padding: 5px
|
||||||
|
|
||||||
|
div.cell
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
width: 100%
|
||||||
|
position: relative
|
||||||
|
|
||||||
|
div.cell > *
|
||||||
|
display: table-cell
|
||||||
|
align-items: center
|
||||||
|
width: 100%
|
||||||
|
padding: 2px 5px
|
||||||
|
|
||||||
|
.text-left
|
||||||
|
text-align: left
|
||||||
|
|
||||||
|
.text-right
|
||||||
|
text-align: right
|
||||||
|
|
||||||
|
.label
|
||||||
|
font-weight: bold
|
||||||
|
|
||||||
|
.listContainer
|
||||||
|
display: grid
|
||||||
|
width: 100%
|
||||||
|
text-align: left
|
||||||
|
overflow: hidden
|
||||||
|
position: relative
|
||||||
|
max-height: 90vh
|
||||||
|
|
||||||
|
.listContainer:hover
|
||||||
|
overflow: auto
|
||||||
|
|
||||||
|
.logEntry
|
||||||
|
display: list-item
|
||||||
|
list-style: none
|
||||||
|
padding: 5px
|
||||||
|
border-radius: 10px
|
||||||
|
margin: 5px
|
||||||
|
color: $cOnSurfaceVariant
|
||||||
|
user-select: none
|
||||||
|
position: relative
|
||||||
|
font-size: 110%
|
||||||
|
|
||||||
|
.logEntry[level=debug]
|
||||||
|
background: $cDebug
|
||||||
|
|
||||||
|
.logEntry[level=verbose]
|
||||||
|
background: $cVerbose
|
||||||
|
|
||||||
|
.logEntry[level=info]
|
||||||
|
background: $cInfo
|
||||||
|
|
||||||
|
.logEntry[level=warn]
|
||||||
|
background: $cWarn
|
||||||
|
|
||||||
|
.logEntry[level=warning]
|
||||||
|
background: $cWarn
|
||||||
|
user-select: all
|
||||||
|
|
||||||
|
.logEntry[level=error]
|
||||||
|
background: $cError
|
||||||
|
user-select: all
|
||||||
|
|
||||||
|
.logEntry .infodiv
|
||||||
|
display: flex
|
||||||
|
list-style: none
|
||||||
|
font-size: 75%
|
||||||
|
width: 100%
|
||||||
|
|
||||||
|
.logEntry .infodiv span
|
||||||
|
padding: 0 2px
|
||||||
|
margin: auto
|
||||||
|
width: 50%
|
||||||
|
display: table-cell
|
||||||
|
|
||||||
|
.songEntry
|
||||||
|
display: flex
|
||||||
|
background: lighten($cBackgroundVariant, 5)
|
||||||
|
padding: 2px
|
||||||
|
margin: 5px
|
||||||
|
border-radius: 5px
|
||||||
|
text-decoration: none
|
||||||
|
color: $cPrimary
|
||||||
|
> *
|
||||||
|
display: table-column
|
||||||
|
margin: auto
|
||||||
|
img
|
||||||
|
max-height: 30px
|
||||||
|
max-width: 20%
|
||||||
|
height: auto
|
||||||
|
width: auto
|
||||||
|
border-radius: 2px
|
||||||
|
a
|
||||||
|
width: 80%
|
||||||
|
text-decoration: none
|
||||||
|
color: $cPrimary
|
||||||
|
|
||||||
|
#content
|
||||||
|
display: flex
|
||||||
|
height: 100%
|
||||||
|
width: 100%
|
||||||
|
background-color: $cBackground
|
||||||
|
|
||||||
|
#column-left, #column-middle, #column-right
|
||||||
|
width: 33%
|
||||||
|
|
||||||
|
#column-middle
|
||||||
|
background: $cBackgroundVariant
|
||||||
|
border-radius: 20px
|
||||||
|
height: 100%
|
||||||
|
|
||||||
|
#column-right
|
||||||
|
padding: 0 20px 20px
|
||||||
|
display: grid
|
||||||
|
align-content: start
|
||||||
|
|
||||||
|
#user-avatar
|
||||||
|
max-width: 300px
|
||||||
|
width: 100%
|
||||||
|
height: auto
|
||||||
|
border-radius: 25%
|
||||||
|
|
||||||
|
#avatar-container
|
||||||
|
max-width: 300px
|
||||||
|
width: 100%
|
||||||
|
margin: auto
|
||||||
|
position: relative
|
||||||
|
|
||||||
|
#status-indicator
|
||||||
|
height: 20%
|
||||||
|
width: 20%
|
||||||
|
position: absolute
|
||||||
|
left: 0
|
||||||
|
top: 0
|
||||||
|
border-radius: 25%
|
||||||
|
display: block
|
||||||
|
z-index: 200
|
||||||
|
|
||||||
|
#status-indicator[status=online]
|
||||||
|
background-color: $cOnline
|
||||||
|
|
||||||
|
#status-indicator[status=idle]
|
||||||
|
background-color: $cIdle
|
||||||
|
|
||||||
|
#status-indicator[status=dnd]
|
||||||
|
background-color: $cDnd
|
||||||
|
|
||||||
|
#status-indicator[status=offline]
|
||||||
|
background-color: $cOffline
|
||||||
|
|
||||||
|
#guild-select
|
||||||
|
background: $cBackgroundVariant
|
||||||
|
color: $cPrimary
|
||||||
|
font-size: 150%
|
||||||
|
font-family: $fNormal
|
||||||
|
padding: 10px
|
||||||
|
width: 100%
|
||||||
|
margin: auto
|
||||||
|
border: none
|
||||||
|
height: 52px
|
||||||
|
border-radius: 12px
|
||||||
|
-webkit-appearance: none
|
||||||
|
|
||||||
|
#guild-icon-container
|
||||||
|
padding: 10px 0 0 0
|
||||||
|
display: flex
|
||||||
|
|
||||||
|
#guild-icon
|
||||||
|
max-width: 100px
|
||||||
|
width: 50%
|
||||||
|
height: auto
|
||||||
|
border-radius: 25%
|
||||||
|
|
||||||
|
#guild-nameAndIcon
|
||||||
|
width: 50%
|
||||||
|
|
||||||
|
#dj-songinfo
|
||||||
|
background-color: $cBackgroundVariant
|
||||||
|
border-radius: 20px
|
||||||
|
overflow-x: hidden
|
||||||
|
|
||||||
|
#songinfo-container
|
||||||
|
display: list-item
|
||||||
|
text-decoration: none
|
||||||
|
color: $cPrimary
|
||||||
|
padding: 10px
|
||||||
|
width: calc(100% - 20px)
|
||||||
|
|
||||||
|
#dj-queue-container
|
||||||
|
display: grid
|
||||||
|
padding: 0 5px 5px
|
||||||
|
|
||||||
|
#dj-songname
|
||||||
|
font-weight: bold
|
||||||
|
font-size: 120%
|
||||||
|
|
||||||
|
#dj-songImg
|
||||||
|
align-self: center
|
||||||
|
width: 80%
|
||||||
|
height: auto
|
||||||
|
margin: 0 10%
|
||||||
|
border-radius: 5%
|
||||||
|
|
||||||
|
#guildinfo
|
||||||
|
max-height: 90vh
|
||||||
|
overflow-y: hidden
|
||||||
|
|
||||||
|
#guildinfo:hover
|
||||||
|
overflow-y: auto
|
||||||
|
|
||||||
|
#dj-songQueue
|
||||||
|
display: grid
|
||||||
|
max-height: 100%
|
@ -0,0 +1,31 @@
|
|||||||
|
$cPrimary: #fff
|
||||||
|
$cPrimaryVariant: #4c10a5
|
||||||
|
$cSecondary: #c889f5
|
||||||
|
$cSecondaryVariant: #740bce
|
||||||
|
$cBackground: #77f
|
||||||
|
$cBackgroundVariant: #55b
|
||||||
|
$cSurface: #fff
|
||||||
|
$cSurfaceVariant: #000
|
||||||
|
$cError: #f59289
|
||||||
|
$cErrorVariant: #b20a00
|
||||||
|
$cOnPrimary: #fff
|
||||||
|
$cOnSecondary: #000
|
||||||
|
$cOnSurface: #000
|
||||||
|
$cOnSurfaceShadow: lighten($cOnSurface, 30%)
|
||||||
|
$cOnSurfaceVariant: #fff
|
||||||
|
$cOnBackground: #000
|
||||||
|
$cOnBackgroundShadow: lighten($cOnBackground, 30%)
|
||||||
|
$cOnBackgroundVariant: #fff
|
||||||
|
$cOnError: #000
|
||||||
|
$cOnErrorVariant: #fff
|
||||||
|
$cOnline: #0f0
|
||||||
|
$cIdle: #ff0
|
||||||
|
$cDnd: #f00
|
||||||
|
$cOffline: #888
|
||||||
|
$cDebug: #00f
|
||||||
|
$cVerbose: #088
|
||||||
|
$cInfo: #890
|
||||||
|
$cWarn: #a60
|
||||||
|
$cError: #a00
|
||||||
|
|
||||||
|
$fNormal: Ubuntu, sans-serif
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,26 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
function login() {
|
||||||
|
let username = document.querySelector('#username').value;
|
||||||
|
let password = sha512(document.querySelector('#password').value);
|
||||||
|
$.post({
|
||||||
|
url: "/",
|
||||||
|
data: JSON.stringify({
|
||||||
|
username: username,
|
||||||
|
password: password
|
||||||
|
}),
|
||||||
|
contentType: "application/json"
|
||||||
|
}).done((res) => {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSubmit(e) {
|
||||||
|
if (!e)
|
||||||
|
e = window.event;
|
||||||
|
if (e.which === 13) {
|
||||||
|
login();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('keydown', handleSubmit, false);
|
@ -0,0 +1,308 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
let latestLogs = [];
|
||||||
|
|
||||||
|
let status = {
|
||||||
|
0: 'ready',
|
||||||
|
1: 'connecting',
|
||||||
|
2: 'reconnecting',
|
||||||
|
3: 'idle',
|
||||||
|
4: 'nearly',
|
||||||
|
5: 'disconnected'
|
||||||
|
};
|
||||||
|
|
||||||
|
function getSplitDuration (duration) {
|
||||||
|
let dur = duration;
|
||||||
|
let retObj = {};
|
||||||
|
retObj.milliseconds = dur % 1000;
|
||||||
|
dur = Math.floor(dur / 1000);
|
||||||
|
retObj.seconds = dur % 60;
|
||||||
|
dur = Math.floor(dur / 60);
|
||||||
|
retObj.minutes = dur % 60;
|
||||||
|
dur = Math.floor(dur / 60);
|
||||||
|
retObj.hours = dur % 24;
|
||||||
|
dur = Math.floor(dur / 24);
|
||||||
|
retObj.days = dur;
|
||||||
|
return retObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
function postQuery(query) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
$.post({
|
||||||
|
url: "/graphql",
|
||||||
|
data: JSON.stringify({
|
||||||
|
query: query
|
||||||
|
}),
|
||||||
|
contentType: "application/json"
|
||||||
|
}).done((res) => resolve(res));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function queryStatic() {
|
||||||
|
let query = `{
|
||||||
|
client {
|
||||||
|
user {
|
||||||
|
tag
|
||||||
|
avatar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
postQuery(query).then((res) => {
|
||||||
|
let d = res.data;
|
||||||
|
document.querySelector('#user-avatar').setAttribute('src', d.client.user.avatar);
|
||||||
|
document.querySelector('#user-tag').innerText = d.client.user.tag;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function queryGuilds() {
|
||||||
|
let query = `{
|
||||||
|
client {
|
||||||
|
guilds {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
dj {
|
||||||
|
playing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
postQuery(query).then((res) => {
|
||||||
|
for (let guild of res.data.client.guilds)
|
||||||
|
if ($(`option[value=${guild.id}]`).length === 0) {
|
||||||
|
let option = document.createElement('option');
|
||||||
|
option.setAttribute('value', guild.id);
|
||||||
|
if (guild.dj)
|
||||||
|
option.innerText = guild.dj.playing? guild.name + ' 🎶' : guild.name;
|
||||||
|
let guildSelect = document.querySelector('#guild-select');
|
||||||
|
guildSelect.appendChild(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function queryGuild(guildId) {
|
||||||
|
let query = `{
|
||||||
|
client {
|
||||||
|
guilds(id: "${guildId}") {
|
||||||
|
name
|
||||||
|
icon
|
||||||
|
memberCount
|
||||||
|
owner {
|
||||||
|
id
|
||||||
|
user {
|
||||||
|
tag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config
|
||||||
|
}`;
|
||||||
|
postQuery(query).then((res) => {
|
||||||
|
let guild = res.data.client.guilds[0];
|
||||||
|
document.querySelector('#guild-icon').setAttribute('src', guild.icon);
|
||||||
|
document.querySelector('#guild-name').innerText = guild.name;
|
||||||
|
document.querySelector('#guild-owner').innerText = guild.owner.user.tag;
|
||||||
|
document.querySelector('#guild-owner').setAttribute('owner-id', guild.owner.id);
|
||||||
|
document.querySelector('#guild-memberCount').innerText = guild.memberCount;
|
||||||
|
queryGuildStatus(guildId);
|
||||||
|
let serverinfo = $('#guildinfo');
|
||||||
|
if (serverinfo.is(':hidden'))
|
||||||
|
serverinfo.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param guildId
|
||||||
|
*/
|
||||||
|
function queryGuildStatus(guildId) {
|
||||||
|
let query = `{
|
||||||
|
client {
|
||||||
|
guilds(id: "${guildId}") {
|
||||||
|
dj {
|
||||||
|
playing
|
||||||
|
connected
|
||||||
|
repeat
|
||||||
|
voiceChannel
|
||||||
|
songStartTime
|
||||||
|
paused
|
||||||
|
currentSong {
|
||||||
|
name
|
||||||
|
url
|
||||||
|
thumbnail
|
||||||
|
}
|
||||||
|
queueCount
|
||||||
|
queue(first: 5) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
url
|
||||||
|
thumbnail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config
|
||||||
|
}`;
|
||||||
|
postQuery(query).then((res) => {
|
||||||
|
let guild = res.data.client.guilds[0];
|
||||||
|
document.querySelector('#dj-repeat').innerText = guild.dj.repeat? 'on': 'off';
|
||||||
|
document.querySelector('#guild-djStatus').innerText = guild.dj.connected? 'connected' : 'disconnected';
|
||||||
|
if (guild.dj.connected) {
|
||||||
|
let songinfoContainer = $('#dj-songinfo');
|
||||||
|
songinfoContainer.show();
|
||||||
|
document.querySelector('#guild-djStatus').innerText = guild.dj.playing? 'playing' : 'connected';
|
||||||
|
document.querySelector('#dj-voiceChannel').innerText = guild.dj.voiceChannel;
|
||||||
|
|
||||||
|
|
||||||
|
if (guild.dj.playing) {
|
||||||
|
if (songinfoContainer.is(':hidden'))
|
||||||
|
songinfoContainer.show();
|
||||||
|
document.querySelector('#guild-djStatus').innerText = guild.dj.paused? 'paused' : 'playing';
|
||||||
|
document.querySelector('#songinfo-container').setAttribute('href', guild.dj.currentSong.url);
|
||||||
|
document.querySelector('#dj-songname').innerText = guild.dj.currentSong.name;
|
||||||
|
document.querySelector('#dj-songImg').setAttribute('src', guild.dj.currentSong.thumbnail.replace('maxresdefault', 'mqdefault'));
|
||||||
|
let songSd = getSplitDuration(Date.now() - guild.dj.songStartTime);
|
||||||
|
document.querySelector('#dj-songCurrentTS').innerText = `${songSd.minutes}:${songSd.seconds.toString().padStart(2, '0')}`;
|
||||||
|
document.querySelector('#dj-songCurrentTS').setAttribute('start-ts', guild.dj.songStartTime);
|
||||||
|
document.querySelector('#dj-queueCount').innerText = guild.dj.queueCount;
|
||||||
|
let songContainer = document.querySelector('#dj-songQueue');
|
||||||
|
$('.songEntry').remove();
|
||||||
|
for (let song of guild.dj.queue) {
|
||||||
|
let songEntry = document.createElement('a');
|
||||||
|
songEntry.setAttribute('href', song.url);
|
||||||
|
songEntry.setAttribute('class', 'songEntry');
|
||||||
|
songEntry.setAttribute('song-id', song.id);
|
||||||
|
let imageEntry = document.createElement('img');
|
||||||
|
imageEntry.setAttribute('src', song.thumbnail.replace('maxresdefault', 'mqdefault'));
|
||||||
|
songEntry.appendChild(imageEntry);
|
||||||
|
let nameEntry = document.createElement('a');
|
||||||
|
nameEntry.innerText = song.name;
|
||||||
|
songEntry.appendChild(nameEntry);
|
||||||
|
songContainer.appendChild(songEntry);
|
||||||
|
}
|
||||||
|
document.querySelector('#dj-queueDisplayCount').innerText = document.querySelectorAll('.songEntry').length;
|
||||||
|
} else {
|
||||||
|
if (songinfoContainer.is(':not(:hidden)'))
|
||||||
|
songinfoContainer.hide();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$('#dj-songinfo').hide();
|
||||||
|
document.querySelector('#dj-voiceChannel').innerText = 'None';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function queryStatus() {
|
||||||
|
let query = `{
|
||||||
|
client {
|
||||||
|
ping
|
||||||
|
status
|
||||||
|
uptime
|
||||||
|
guildCount
|
||||||
|
voiceConnectionCount
|
||||||
|
user {
|
||||||
|
presence {
|
||||||
|
game
|
||||||
|
status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
postQuery(query).then((res) => {
|
||||||
|
let d = res.data;
|
||||||
|
document.querySelector('#client-ping').innerText = Math.round(d.client.ping * 10)/10 + ' ms';
|
||||||
|
document.querySelector('#client-status').innerText = status[d.client.status];
|
||||||
|
|
||||||
|
let sd = getSplitDuration(d.client.uptime);
|
||||||
|
document.querySelector('#client-uptime')
|
||||||
|
.innerText = `${sd.days}d ${sd.hours}h ${sd.minutes}min ${sd.seconds}s`;
|
||||||
|
|
||||||
|
document.querySelector('#client-guildCount').innerText = d.client.guildCount;
|
||||||
|
document.querySelector('#client-vcCount').innerText = d.client.voiceConnectionCount;
|
||||||
|
if (d.client.status !== 0)
|
||||||
|
document.querySelector('#status-indicator').setAttribute('status', 'offline');
|
||||||
|
else
|
||||||
|
document.querySelector('#status-indicator').setAttribute('status', d.client.user.presence.status);
|
||||||
|
|
||||||
|
document.querySelector('#user-game').innerText = d.client.user.presence.game;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
let sd = getSplitDuration(d.client.uptime + 1000);
|
||||||
|
document.querySelector('#client-uptime')
|
||||||
|
.innerText = `${sd.days}d ${sd.hours}h ${sd.minutes}min ${sd.seconds}s`;
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function queryLogs(count) {
|
||||||
|
count = count || 5;
|
||||||
|
let query = `{
|
||||||
|
logs(last: ${count}, level: "verbose"){
|
||||||
|
id
|
||||||
|
level
|
||||||
|
message
|
||||||
|
timestamp
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
postQuery(query).then((res) => {
|
||||||
|
let d = res.data;
|
||||||
|
for (let logEntry of d.logs)
|
||||||
|
if (!latestLogs.find((x) => x.id === logEntry.id)) {
|
||||||
|
let entryElem = document.createElement('div');
|
||||||
|
entryElem.setAttribute('class', 'logEntry text-left');
|
||||||
|
entryElem.setAttribute('log-id', logEntry.id);
|
||||||
|
entryElem.setAttribute('level', logEntry.level);
|
||||||
|
let infoDiv = document.createElement('div');
|
||||||
|
infoDiv.setAttribute('class', 'infodiv');
|
||||||
|
let lvlSpan = document.createElement('span');
|
||||||
|
lvlSpan.innerText = logEntry.level;
|
||||||
|
lvlSpan.setAttribute('class', 'text-left');
|
||||||
|
infoDiv.appendChild(lvlSpan);
|
||||||
|
let tsSpan = document.createElement('span');
|
||||||
|
tsSpan.setAttribute('timestamp', logEntry.timestamp);
|
||||||
|
tsSpan.innerText = moment(logEntry.timestamp, 'YY-MM-DD-HH-mm-ss').format('MMM Do HH:mm:ss');
|
||||||
|
tsSpan.setAttribute('class', 'text-right');
|
||||||
|
infoDiv.appendChild(tsSpan);
|
||||||
|
entryElem.appendChild(infoDiv);
|
||||||
|
let msgSpan = document.createElement('span');
|
||||||
|
msgSpan.innerText = logEntry.message;
|
||||||
|
msgSpan.setAttribute('class', 'message');
|
||||||
|
entryElem.appendChild(msgSpan);
|
||||||
|
let logContainer = document.querySelector('#log-container');
|
||||||
|
logContainer.insertBefore(entryElem, logContainer.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
latestLogs = d.logs;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function startUpdating() {
|
||||||
|
queryStatic();
|
||||||
|
setInterval(queryStatic, 3600000);
|
||||||
|
queryStatus();
|
||||||
|
setInterval(queryStatus, 2000);
|
||||||
|
queryLogs(50);
|
||||||
|
setInterval(queryLogs, 5000);
|
||||||
|
queryGuilds();
|
||||||
|
setInterval(queryGuilds, 60000);
|
||||||
|
setInterval(() => {
|
||||||
|
let gid = $('#guild-select')[0].value;
|
||||||
|
if (gid && gid !== 'select-default')
|
||||||
|
queryGuildStatus(gid);
|
||||||
|
}, 5000);
|
||||||
|
setInterval(() => {
|
||||||
|
let gid = $('#guild-select')[0].value;
|
||||||
|
if (gid && gid !== 'select-default')
|
||||||
|
queryGuild(gid);
|
||||||
|
}, 600000);
|
||||||
|
$('#guild-select').on('change', (ev) => {
|
||||||
|
let fch = document.querySelector('#guild-select').firstElementChild;
|
||||||
|
if (fch.getAttribute('value') === 'select-default')
|
||||||
|
fch.remove();
|
||||||
|
let guildId = ev.target.value;
|
||||||
|
queryGuild(guildId);
|
||||||
|
});
|
||||||
|
setInterval(() => {
|
||||||
|
let songSd = getSplitDuration(Date.now() - $('#dj-songCurrentTS').attr('start-ts'));
|
||||||
|
document.querySelector('#dj-songCurrentTS').innerText = `${songSd.minutes}:${songSd.seconds.toString().padStart(2, '0')}`;
|
||||||
|
}, 500);
|
||||||
|
}
|
Loading…
Reference in New Issue