update db structure to let init work correctly

This commit is contained in:
Ean Milligan 2025-04-26 13:22:45 -04:00
parent ef08cd779a
commit 76e007e2e4
44 changed files with 2543 additions and 2280 deletions

View File

@ -2,10 +2,11 @@
// DATA WILL BE LOST IF DB ALREADY EXISTS, RUN AT OWN RISK // DATA WILL BE LOST IF DB ALREADY EXISTS, RUN AT OWN RISK
import config from '../config.ts'; import config from '../config.ts';
import { dbClient } from '../src/db.ts'; import dbClient from '../src/db/client.ts';
console.log('Attempting to create DB'); console.log('Attempting to create DB');
await dbClient.execute(`CREATE SCHEMA IF NOT EXISTS ${config.db.name};`); await dbClient.execute(`CREATE SCHEMA IF NOT EXISTS ${config.db.name};`);
console.log('test');
await dbClient.execute(`USE ${config.db.name}`); await dbClient.execute(`USE ${config.db.name}`);
console.log('DB created'); console.log('DB created');

View File

@ -1,28 +1,47 @@
// This file will populate the tables with default values // This file will populate the tables with default values
import config from '../config.ts'; import config from '../config.ts';
import { dbClient } from '../src/db.ts'; import dbClient from '../src/db/client.ts';
console.log('Attempting to populate DB Admin API key'); console.log('Attempting to populate DB Admin API key');
await dbClient.execute('INSERT INTO all_keys(userid,apiKey) values(?,?)', [config.api.admin, config.api.adminKey]).catch((e) => { await dbClient.execute('INSERT INTO all_keys(userid,apiKey) values(?,?)', [config.api.admin, config.api.adminKey]).catch((e) => {
console.log('Failed to insert into database', e); console.log('Failed to insert into database', e);
}); });
console.log('Inesrtion done'); console.log('Inesrtion done');
console.log('Attempting to insert default commands into command_cnt'); console.log('Attempting to insert default commands into command_cnt');
const commands = ['ping', 'rip', 'rollhelp', 'help', 'info', 'version', 'report', 'stats', 'roll', 'emojis', 'api', 'privacy', 'mention', 'audit', 'heatmap', 'rollDecorators', 'opt-out', 'opt-in']; const commands = [
'ping',
'rip',
'rollhelp',
'help',
'info',
'version',
'report',
'stats',
'roll',
'emojis',
'api',
'privacy',
'mention',
'audit',
'heatmap',
'rollDecorators',
'opt-out',
'opt-in',
];
for (const command of commands) { for (const command of commands) {
await dbClient.execute('INSERT INTO command_cnt(command) values(?)', [command]).catch((e) => { await dbClient.execute('INSERT INTO command_cnt(command) values(?)', [command]).catch((e) => {
console.log(`Failed to insert ${command} into database`, e); console.log(`Failed to insert ${command} into database`, e);
}); });
} }
console.log('Insertion done'); console.log('Insertion done');
console.log('Attempting to insert default hours into roll_time_heatmap'); console.log('Attempting to insert default hours into roll_time_heatmap');
for (let i = 0; i <= 23; i++) { for (let i = 0; i <= 23; i++) {
await dbClient.execute('INSERT INTO roll_time_heatmap(hour) values(?)', [i]).catch((e) => { await dbClient.execute('INSERT INTO roll_time_heatmap(hour) values(?)', [i]).catch((e) => {
console.log(`Failed to insert hour ${i} into database`, e); console.log(`Failed to insert hour ${i} into database`, e);
}); });
} }
console.log('Insertion done'); console.log('Insertion done');

572
mod.ts
View File

@ -7,25 +7,26 @@
import config from './config.ts'; import config from './config.ts';
import { DEBUG, DEVMODE, LOCALMODE } from './flags.ts'; import { DEBUG, DEVMODE, LOCALMODE } from './flags.ts';
import { import {
// Discordeno deps // Discordeno deps
botId, botId,
cache, cache,
DiscordActivityTypes, DiscordActivityTypes,
DiscordenoGuild, DiscordenoGuild,
DiscordenoMessage, DiscordenoMessage,
editBotNickname, editBotNickname,
editBotStatus, editBotStatus,
initLog, initLog,
Intents, Intents,
// Log4Deno deps // Log4Deno deps
log, log,
LT, LT,
// Discordeno deps // Discordeno deps
sendMessage, sendMessage,
startBot, startBot,
} from './deps.ts'; } from './deps.ts';
import api from './src/api.ts'; import api from './src/api.ts';
import { dbClient, ignoreList } from './src/db.ts'; import dbClient from './src/db/client.ts';
import { ignoreList } from './src/db/common.ts';
import commands from './src/commands/_index.ts'; import commands from './src/commands/_index.ts';
import intervals from './src/intervals.ts'; import intervals from './src/intervals.ts';
import { successColor, warnColor } from './src/commandUtils.ts'; import { successColor, warnColor } from './src/commandUtils.ts';
@ -36,283 +37,302 @@ initLog('logs', DEBUG);
// Start up the Discord Bot // Start up the Discord Bot
startBot({ startBot({
token: LOCALMODE ? config.localtoken : config.token, token: LOCALMODE ? config.localtoken : config.token,
intents: [Intents.GuildMessages, Intents.DirectMessages, Intents.Guilds], intents: [Intents.GuildMessages, Intents.DirectMessages, Intents.Guilds],
eventHandlers: { eventHandlers: {
ready: () => { ready: () => {
log(LT.INFO, `${config.name} Logged in!`); log(LT.INFO, `${config.name} Logged in!`);
editBotStatus({ editBotStatus({
activities: [{ activities: [
name: 'Booting up . . .', {
type: DiscordActivityTypes.Game, name: 'Booting up . . .',
createdAt: new Date().getTime(), type: DiscordActivityTypes.Game,
}], createdAt: new Date().getTime(),
status: 'online', },
}); ],
status: 'online',
});
// Interval to rotate the status text every 30 seconds to show off more commands // Interval to rotate the status text every 30 seconds to show off more commands
setInterval(async () => { setInterval(async () => {
log(LT.LOG, 'Changing bot status'); log(LT.LOG, 'Changing bot status');
try { try {
// Wrapped in try-catch due to hard crash possible // Wrapped in try-catch due to hard crash possible
editBotStatus({ editBotStatus({
activities: [{ activities: [
name: await intervals.getRandomStatus(), {
type: DiscordActivityTypes.Game, name: await intervals.getRandomStatus(),
createdAt: new Date().getTime(), type: DiscordActivityTypes.Game,
}], createdAt: new Date().getTime(),
status: 'online', },
}); ],
} catch (e) { status: 'online',
log(LT.ERROR, `Failed to update status: ${JSON.stringify(e)}`); });
} } catch (e) {
}, 30000); log(LT.ERROR, `Failed to update status: ${JSON.stringify(e)}`);
}
}, 30000);
// Interval to update bot list stats every 24 hours // Interval to update bot list stats every 24 hours
LOCALMODE ? log(LT.INFO, 'updateListStatistics not running') : setInterval(() => { LOCALMODE
log(LT.LOG, 'Updating all bot lists statistics'); ? log(LT.INFO, 'updateListStatistics not running')
intervals.updateListStatistics(botId, cache.guilds.size + cache.dispatchedGuildIds.size); : setInterval(() => {
}, 86400000); log(LT.LOG, 'Updating all bot lists statistics');
intervals.updateListStatistics(botId, cache.guilds.size + cache.dispatchedGuildIds.size);
}, 86400000);
// Interval to update hourlyRates every hour // Interval to update hourlyRates every hour
setInterval(() => { setInterval(() => {
log(LT.LOG, 'Updating all command hourlyRates'); log(LT.LOG, 'Updating all command hourlyRates');
intervals.updateHourlyRates(); intervals.updateHourlyRates();
}, 3600000); }, 3600000);
// Interval to update heatmap.png every hour // Interval to update heatmap.png every hour
setInterval(() => { setInterval(() => {
log(LT.LOG, 'Updating heatmap.png'); log(LT.LOG, 'Updating heatmap.png');
intervals.updateHeatmapPng(); intervals.updateHeatmapPng();
}, 3600000); }, 3600000);
// setTimeout added to make sure the startup message does not error out // setTimeout added to make sure the startup message does not error out
setTimeout(() => { setTimeout(() => {
LOCALMODE && editBotNickname(config.devServer, `LOCAL - ${config.name}`); LOCALMODE && editBotNickname(config.devServer, `LOCAL - ${config.name}`);
LOCALMODE ? log(LT.INFO, 'updateListStatistics not running') : intervals.updateListStatistics(botId, cache.guilds.size + cache.dispatchedGuildIds.size); LOCALMODE ? log(LT.INFO, 'updateListStatistics not running') : intervals.updateListStatistics(botId, cache.guilds.size + cache.dispatchedGuildIds.size);
intervals.updateHourlyRates(); intervals.updateHourlyRates();
intervals.updateHeatmapPng(); intervals.updateHeatmapPng();
editBotStatus({ editBotStatus({
activities: [{ activities: [
name: 'Booting Complete', {
type: DiscordActivityTypes.Game, name: 'Booting Complete',
createdAt: new Date().getTime(), type: DiscordActivityTypes.Game,
}], createdAt: new Date().getTime(),
status: 'online', },
}); ],
sendMessage(config.logChannel, { status: 'online',
embeds: [{ });
title: `${config.name} is now Online`, sendMessage(config.logChannel, {
color: successColor, embeds: [
fields: [ {
{ title: `${config.name} is now Online`,
name: 'Version:', color: successColor,
value: `${config.version}`, fields: [
inline: true, {
}, name: 'Version:',
], value: `${config.version}`,
}], inline: true,
}).catch((e: Error) => utils.commonLoggers.messageSendError('mod.ts:88', 'Startup', e)); },
}, 1000); ],
}, },
guildCreate: (guild: DiscordenoGuild) => { ],
log(LT.LOG, `Handling joining guild ${JSON.stringify(guild)}`); }).catch((e: Error) => utils.commonLoggers.messageSendError('mod.ts:88', 'Startup', e));
sendMessage(config.logChannel, { }, 1000);
embeds: [{ },
title: 'New Guild Joined!', guildCreate: (guild: DiscordenoGuild) => {
color: successColor, log(LT.LOG, `Handling joining guild ${JSON.stringify(guild)}`);
fields: [ sendMessage(config.logChannel, {
{ embeds: [
name: 'Name:', {
value: `${guild.name}`, title: 'New Guild Joined!',
inline: true, color: successColor,
}, fields: [
{ {
name: 'Id:', name: 'Name:',
value: `${guild.id}`, value: `${guild.name}`,
inline: true, inline: true,
}, },
{ {
name: 'Member Count:', name: 'Id:',
value: `${guild.memberCount}`, value: `${guild.id}`,
inline: true, inline: true,
}, },
], {
}], name: 'Member Count:',
}).catch((e: Error) => utils.commonLoggers.messageSendError('mod.ts:95', 'Join Guild', e)); value: `${guild.memberCount}`,
}, inline: true,
guildDelete: (guild: DiscordenoGuild) => { },
log(LT.LOG, `Handling leaving guild ${JSON.stringify(guild)}`); ],
sendMessage(config.logChannel, { },
embeds: [{ ],
title: 'Removed from Guild', }).catch((e: Error) => utils.commonLoggers.messageSendError('mod.ts:95', 'Join Guild', e));
color: warnColor, },
fields: [ guildDelete: (guild: DiscordenoGuild) => {
{ log(LT.LOG, `Handling leaving guild ${JSON.stringify(guild)}`);
name: 'Name:', sendMessage(config.logChannel, {
value: `${guild.name}`, embeds: [
inline: true, {
}, title: 'Removed from Guild',
{ color: warnColor,
name: 'Id:', fields: [
value: `${guild.id}`, {
inline: true, name: 'Name:',
}, value: `${guild.name}`,
{ inline: true,
name: 'Member Count:', },
value: `${guild.memberCount}`, {
inline: true, name: 'Id:',
}, value: `${guild.id}`,
], inline: true,
}], },
}).catch((e: Error) => utils.commonLoggers.messageSendError('mod.ts:99', 'Leave Guild', e)); {
dbClient.execute('DELETE FROM allowed_guilds WHERE guildid = ? AND banned = 0', [guild.id]).catch((e) => utils.commonLoggers.dbError('mod.ts:100', 'delete from', e)); name: 'Member Count:',
}, value: `${guild.memberCount}`,
debug: DEVMODE ? (dmsg) => log(LT.LOG, `Debug Message | ${JSON.stringify(dmsg)}`) : undefined, inline: true,
messageCreate: (message: DiscordenoMessage) => { },
// Ignore all other bots ],
if (message.isBot) return; },
],
}).catch((e: Error) => utils.commonLoggers.messageSendError('mod.ts:99', 'Leave Guild', e));
dbClient
.execute('DELETE FROM allowed_guilds WHERE guildid = ? AND banned = 0', [guild.id])
.catch((e) => utils.commonLoggers.dbError('mod.ts:100', 'delete from', e));
},
debug: DEVMODE ? (dmsg) => log(LT.LOG, `Debug Message | ${JSON.stringify(dmsg)}`) : undefined,
messageCreate: (message: DiscordenoMessage) => {
// Ignore all other bots
if (message.isBot) return;
// Ignore users who requested to be ignored // Ignore users who requested to be ignored
if (ignoreList.includes(message.authorId) && (!message.content.startsWith(`${config.prefix}opt-in`) || message.guildId !== 0n)) return; if (ignoreList.includes(message.authorId) && (!message.content.startsWith(`${config.prefix}opt-in`) || message.guildId !== 0n)) return;
// Ignore all messages that are not commands // Ignore all messages that are not commands
if (message.content.indexOf(config.prefix) !== 0) { if (message.content.indexOf(config.prefix) !== 0) {
// Handle @bot messages // Handle @bot messages
if (message.mentionedUserIds[0] === botId && (message.content.trim().startsWith(`<@${botId}>`) || message.content.trim().startsWith(`<@!${botId}>`))) { if (message.mentionedUserIds[0] === botId && (message.content.trim().startsWith(`<@${botId}>`) || message.content.trim().startsWith(`<@!${botId}>`))) {
commands.handleMentions(message); commands.handleMentions(message);
} }
// return as we are done handling this command // return as we are done handling this command
return; return;
} }
log(LT.LOG, `Handling ${config.prefix}command message: ${JSON.stringify(message)}`); log(LT.LOG, `Handling ${config.prefix}command message: ${JSON.stringify(message)}`);
// Split into standard command + args format // Split into standard command + args format
const args = message.content.slice(config.prefix.length).trim().split(/[ \n]+/g); const args = message.content
const command = args.shift()?.toLowerCase(); .slice(config.prefix.length)
.trim()
.split(/[ \n]+/g);
const command = args.shift()?.toLowerCase();
// All commands below here // All commands below here
switch (command) { switch (command) {
case 'opt-out': case 'opt-out':
case 'ignore-me': case 'ignore-me':
// [[opt-out or [[ignore-me // [[opt-out or [[ignore-me
// Tells the bot to add you to the ignore list. // Tells the bot to add you to the ignore list.
commands.optOut(message); commands.optOut(message);
break; break;
case 'opt-in': case 'opt-in':
// [[opt-in // [[opt-in
// Tells the bot to remove you from the ignore list. // Tells the bot to remove you from the ignore list.
commands.optIn(message); commands.optIn(message);
break; break;
case 'ping': case 'ping':
// [[ping // [[ping
// Its a ping test, what else do you want. // Its a ping test, what else do you want.
commands.ping(message); commands.ping(message);
break; break;
case 'rip': case 'rip':
case 'memory': case 'memory':
// [[rip [[memory // [[rip [[memory
// Displays a short message I wanted to include // Displays a short message I wanted to include
commands.rip(message); commands.rip(message);
break; break;
case 'rollhelp': case 'rollhelp':
case 'rh': case 'rh':
case 'hr': case 'hr':
case '??': case '??':
// [[rollhelp or [[rh or [[hr or [[?? // [[rollhelp or [[rh or [[hr or [[??
// Help command specifically for the roll command // Help command specifically for the roll command
commands.rollHelp(message); commands.rollHelp(message);
break; break;
case 'rolldecorators': case 'rolldecorators':
case 'rd': case 'rd':
case 'dr': case 'dr':
case '???': case '???':
// [[rollDecorators or [[rd or [[dr or [[??? // [[rollDecorators or [[rd or [[dr or [[???
// Help command specifically for the roll command decorators // Help command specifically for the roll command decorators
commands.rollDecorators(message); commands.rollDecorators(message);
break; break;
case 'help': case 'help':
case 'h': case 'h':
case '?': case '?':
// [[help or [[h or [[? // [[help or [[h or [[?
// Help command, prints from help file // Help command, prints from help file
commands.help(message); commands.help(message);
break; break;
case 'info': case 'info':
case 'i': case 'i':
// [[info or [[i // [[info or [[i
// Info command, prints short desc on bot and some links // Info command, prints short desc on bot and some links
commands.info(message); commands.info(message);
break; break;
case 'privacy': case 'privacy':
// [[privacy // [[privacy
// Privacy command, prints short desc on bot's privacy policy // Privacy command, prints short desc on bot's privacy policy
commands.privacy(message); commands.privacy(message);
break; break;
case 'version': case 'version':
case 'v': case 'v':
// [[version or [[v // [[version or [[v
// Returns version of the bot // Returns version of the bot
commands.version(message); commands.version(message);
break; break;
case 'report': case 'report':
case 'r': case 'r':
// [[report or [[r (command that failed) // [[report or [[r (command that failed)
// Manually report a failed roll // Manually report a failed roll
commands.report(message, args); commands.report(message, args);
break; break;
case 'stats': case 'stats':
case 's': case 's':
// [[stats or [[s // [[stats or [[s
// Displays stats on the bot // Displays stats on the bot
commands.stats(message); commands.stats(message);
break; break;
case 'api': case 'api':
// [[api arg // [[api arg
// API sub commands // API sub commands
commands.api(message, args); commands.api(message, args);
break; break;
case 'audit': case 'audit':
// [[audit arg // [[audit arg
// Audit sub commands // Audit sub commands
commands.audit(message, args); commands.audit(message, args);
break; break;
case 'heatmap': case 'heatmap':
case 'hm': case 'hm':
// [[heatmap or [[hm // [[heatmap or [[hm
// Audit sub commands // Audit sub commands
commands.heatmap(message); commands.heatmap(message);
break; break;
default: default:
// Non-standard commands // Non-standard commands
if (command?.startsWith('xdy')) { if (command?.startsWith('xdy')) {
// [[xdydz (aka someone copy pasted the template as a roll) // [[xdydz (aka someone copy pasted the template as a roll)
// Help command specifically for the roll command // Help command specifically for the roll command
commands.rollHelp(message); commands.rollHelp(message);
} else if (command && (`${command}${args.join('')}`).indexOf(config.postfix) > -1) { } else if (command && `${command}${args.join('')}`.indexOf(config.postfix) > -1) {
// [[roll]] // [[roll]]
// Dice rolling commence! // Dice rolling commence!
commands.roll(message, args, command); commands.roll(message, args, command);
} else if (command) { } else if (command) {
// [[emoji or [[emojialias // [[emoji or [[emojialias
// Check if the unhandled command is an emoji request // Check if the unhandled command is an emoji request
commands.emoji(message, command); commands.emoji(message, command);
} }
break; break;
} }
}, },
}, },
}); });
// Start up the command prompt for debug usage // Start up the command prompt for debug usage
if (DEBUG) { if (DEBUG) {
utils.cmdPrompt(config.logChannel, config.name); utils.cmdPrompt(config.logChannel, config.name);
} }
// Start up the API for rolling from third party apps (like excel macros) // Start up the API for rolling from third party apps (like excel macros)
if (config.api.enable) { if (config.api.enable) {
api.start(); api.start();
} }

View File

@ -6,217 +6,219 @@
import config from '../config.ts'; import config from '../config.ts';
import { import {
// Log4Deno deps // Log4Deno deps
log, log,
LT, LT,
} from '../deps.ts'; } from '../deps.ts';
import { dbClient } from './db.ts'; import dbClient from './db/client.ts';
import endpoints from './endpoints/_index.ts'; import endpoints from './endpoints/_index.ts';
import stdResp from './endpoints/stdResponses.ts'; import stdResp from './endpoints/stdResponses.ts';
// start() returns nothing // start() returns nothing
// start initializes and runs the entire API for the bot // start initializes and runs the entire API for the bot
const start = async (): Promise<void> => { const start = async (): Promise<void> => {
const server = Deno.listen({ port: config.api.port }); const server = Deno.listen({ port: config.api.port });
log(LT.INFO, `HTTP api running at: http://localhost:${config.api.port}/`); log(LT.INFO, `HTTP api running at: http://localhost:${config.api.port}/`);
// rateLimitTime holds all users with the last time they started a rate limit timer // rateLimitTime holds all users with the last time they started a rate limit timer
const rateLimitTime = new Map<string, number>(); const rateLimitTime = new Map<string, number>();
// rateLimitCnt holds the number of times the user has called the api in the current rate limit timer // rateLimitCnt holds the number of times the user has called the api in the current rate limit timer
const rateLimitCnt = new Map<string, number>(); const rateLimitCnt = new Map<string, number>();
// Catching every request made to the server // Catching every request made to the server
for await (const conn of server) { for await (const conn of server) {
(async () => { (async () => {
const httpConn = Deno.serveHttp(conn); const httpConn = Deno.serveHttp(conn);
for await (const requestEvent of httpConn) { for await (const requestEvent of httpConn) {
const request = requestEvent.request; const request = requestEvent.request;
log(LT.LOG, `Handling request: ${JSON.stringify(request.headers)} | ${JSON.stringify(request.method)} | ${JSON.stringify(request.url)}`); log(LT.LOG, `Handling request: ${JSON.stringify(request.headers)} | ${JSON.stringify(request.method)} | ${JSON.stringify(request.url)}`);
// Check if user is authenticated to be using this API // Check if user is authenticated to be using this API
let authenticated = false; let authenticated = false;
let rateLimited = false; let rateLimited = false;
let updateRateLimitTime = false; let updateRateLimitTime = false;
let apiUserid = 0n; let apiUserid = 0n;
let apiUseridStr = ''; let apiUseridStr = '';
let apiUserEmail = ''; let apiUserEmail = '';
let apiUserDelCode = ''; let apiUserDelCode = '';
// Check the requests API key // Check the requests API key
if (request.headers.has('X-Api-Key')) { if (request.headers.has('X-Api-Key')) {
// Get the userid and flags for the specific key // Get the userid and flags for the specific key
const dbApiQuery = await dbClient.query('SELECT userid, email, deleteCode FROM all_keys WHERE apiKey = ? AND active = 1 AND banned = 0', [request.headers.get('X-Api-Key')]); const dbApiQuery = await dbClient.query('SELECT userid, email, deleteCode FROM all_keys WHERE apiKey = ? AND active = 1 AND banned = 0', [
request.headers.get('X-Api-Key'),
]);
// If only one user returned, is not banned, and is currently active, mark as authenticated // If only one user returned, is not banned, and is currently active, mark as authenticated
if (dbApiQuery.length === 1) { if (dbApiQuery.length === 1) {
apiUserid = BigInt(dbApiQuery[0].userid); apiUserid = BigInt(dbApiQuery[0].userid);
apiUserEmail = dbApiQuery[0].email; apiUserEmail = dbApiQuery[0].email;
apiUserDelCode = dbApiQuery[0].deleteCode; apiUserDelCode = dbApiQuery[0].deleteCode;
authenticated = true; authenticated = true;
// Rate limiting inits // Rate limiting inits
apiUseridStr = apiUserid.toString(); apiUseridStr = apiUserid.toString();
const apiTimeNow = new Date().getTime(); const apiTimeNow = new Date().getTime();
// Check if user has sent a request recently // Check if user has sent a request recently
if (rateLimitTime.has(apiUseridStr) && (((rateLimitTime.get(apiUseridStr) || 0) + config.api.rateLimitTime) > apiTimeNow)) { if (rateLimitTime.has(apiUseridStr) && (rateLimitTime.get(apiUseridStr) || 0) + config.api.rateLimitTime > apiTimeNow) {
// Get current count // Get current count
const currentCnt = rateLimitCnt.get(apiUseridStr) || 0; const currentCnt = rateLimitCnt.get(apiUseridStr) || 0;
if (currentCnt < config.api.rateLimitCnt) { if (currentCnt < config.api.rateLimitCnt) {
// Limit not yet exceeded, update count // Limit not yet exceeded, update count
rateLimitCnt.set(apiUseridStr, currentCnt + 1); rateLimitCnt.set(apiUseridStr, currentCnt + 1);
} else { } else {
// Limit exceeded, prevent API use // Limit exceeded, prevent API use
rateLimited = true; rateLimited = true;
} }
} else { } else {
// Update the maps // Update the maps
updateRateLimitTime = true; updateRateLimitTime = true;
rateLimitCnt.set(apiUseridStr, 1); rateLimitCnt.set(apiUseridStr, 1);
} }
} }
} }
if (!rateLimited) { if (!rateLimited) {
// Get path and query as a string // Get path and query as a string
const [urlPath, tempQ] = request.url.split('?'); const [urlPath, tempQ] = request.url.split('?');
const path = urlPath.split('api')[1]; const path = urlPath.split('api')[1];
// Turn the query into a map (if it exists) // Turn the query into a map (if it exists)
const query = new Map<string, string>(); const query = new Map<string, string>();
if (tempQ !== undefined) { if (tempQ !== undefined) {
tempQ.split('&').forEach((e: string) => { tempQ.split('&').forEach((e: string) => {
log(LT.LOG, `Parsing request query ${request} ${e}`); log(LT.LOG, `Parsing request query ${request} ${e}`);
const [option, params] = e.split('='); const [option, params] = e.split('=');
query.set(option.toLowerCase(), params); query.set(option.toLowerCase(), params);
}); });
} }
if (path) { if (path) {
if (authenticated) { if (authenticated) {
// Handle the authenticated request // Handle the authenticated request
switch (request.method) { switch (request.method) {
case 'GET': case 'GET':
switch (path.toLowerCase()) { switch (path.toLowerCase()) {
case '/key': case '/key':
case '/key/': case '/key/':
endpoints.get.apiKeyAdmin(requestEvent, query, apiUserid); endpoints.get.apiKeyAdmin(requestEvent, query, apiUserid);
break; break;
case '/channel': case '/channel':
case '/channel/': case '/channel/':
endpoints.get.apiChannel(requestEvent, query, apiUserid); endpoints.get.apiChannel(requestEvent, query, apiUserid);
break; break;
case '/roll': case '/roll':
case '/roll/': case '/roll/':
endpoints.get.apiRoll(requestEvent, query, apiUserid); endpoints.get.apiRoll(requestEvent, query, apiUserid);
break; break;
default: default:
// Alert API user that they messed up // Alert API user that they messed up
requestEvent.respondWith(stdResp.NotFound('Auth Get')); requestEvent.respondWith(stdResp.NotFound('Auth Get'));
break; break;
} }
break; break;
case 'POST': case 'POST':
switch (path.toLowerCase()) { switch (path.toLowerCase()) {
case '/channel/add': case '/channel/add':
case '/channel/add/': case '/channel/add/':
endpoints.post.apiChannelAdd(requestEvent, query, apiUserid); endpoints.post.apiChannelAdd(requestEvent, query, apiUserid);
break; break;
default: default:
// Alert API user that they messed up // Alert API user that they messed up
requestEvent.respondWith(stdResp.NotFound('Auth Post')); requestEvent.respondWith(stdResp.NotFound('Auth Post'));
break; break;
} }
break; break;
case 'PUT': case 'PUT':
switch (path.toLowerCase()) { switch (path.toLowerCase()) {
case '/key/ban': case '/key/ban':
case '/key/ban/': case '/key/ban/':
case '/key/unban': case '/key/unban':
case '/key/unban/': case '/key/unban/':
case '/key/activate': case '/key/activate':
case '/key/activate/': case '/key/activate/':
case '/key/deactivate': case '/key/deactivate':
case '/key/deactivate/': case '/key/deactivate/':
endpoints.put.apiKeyManage(requestEvent, query, apiUserid, path); endpoints.put.apiKeyManage(requestEvent, query, apiUserid, path);
break; break;
case '/channel/ban': case '/channel/ban':
case '/channel/ban/': case '/channel/ban/':
case '/channel/unban': case '/channel/unban':
case '/channel/unban/': case '/channel/unban/':
endpoints.put.apiChannelManageBan(requestEvent, query, apiUserid, path); endpoints.put.apiChannelManageBan(requestEvent, query, apiUserid, path);
break; break;
case '/channel/activate': case '/channel/activate':
case '/channel/activate/': case '/channel/activate/':
case '/channel/deactivate': case '/channel/deactivate':
case '/channel/deactivate/': case '/channel/deactivate/':
endpoints.put.apiChannelManageActive(requestEvent, query, apiUserid, path); endpoints.put.apiChannelManageActive(requestEvent, query, apiUserid, path);
break; break;
default: default:
// Alert API user that they messed up // Alert API user that they messed up
requestEvent.respondWith(stdResp.NotFound('Auth Put')); requestEvent.respondWith(stdResp.NotFound('Auth Put'));
break; break;
} }
break; break;
case 'DELETE': case 'DELETE':
switch (path.toLowerCase()) { switch (path.toLowerCase()) {
case '/key/delete': case '/key/delete':
case '/key/delete/': case '/key/delete/':
endpoints.delete.apiKeyDelete(requestEvent, query, apiUserid, apiUserEmail, apiUserDelCode); endpoints.delete.apiKeyDelete(requestEvent, query, apiUserid, apiUserEmail, apiUserDelCode);
break; break;
default: default:
// Alert API user that they messed up // Alert API user that they messed up
requestEvent.respondWith(stdResp.NotFound('Auth Del')); requestEvent.respondWith(stdResp.NotFound('Auth Del'));
break; break;
} }
break; break;
default: default:
// Alert API user that they messed up // Alert API user that they messed up
requestEvent.respondWith(stdResp.MethodNotAllowed('Auth')); requestEvent.respondWith(stdResp.MethodNotAllowed('Auth'));
break; break;
} }
// Update rate limit details // Update rate limit details
if (updateRateLimitTime) { if (updateRateLimitTime) {
const apiTimeNow = new Date().getTime(); const apiTimeNow = new Date().getTime();
rateLimitTime.set(apiUseridStr, apiTimeNow); rateLimitTime.set(apiUseridStr, apiTimeNow);
} }
} else if (!authenticated) { } else if (!authenticated) {
// Handle the unathenticated request // Handle the unathenticated request
switch (request.method) { switch (request.method) {
case 'GET': case 'GET':
switch (path.toLowerCase()) { switch (path.toLowerCase()) {
case '/key': case '/key':
case '/key/': case '/key/':
endpoints.get.apiKey(requestEvent, query); endpoints.get.apiKey(requestEvent, query);
break; break;
case '/heatmap.png': case '/heatmap.png':
endpoints.get.heatmapPng(requestEvent); endpoints.get.heatmapPng(requestEvent);
break; break;
default: default:
// Alert API user that they messed up // Alert API user that they messed up
requestEvent.respondWith(stdResp.NotFound('NoAuth Get')); requestEvent.respondWith(stdResp.NotFound('NoAuth Get'));
break; break;
} }
break; break;
default: default:
// Alert API user that they messed up // Alert API user that they messed up
requestEvent.respondWith(stdResp.MethodNotAllowed('NoAuth')); requestEvent.respondWith(stdResp.MethodNotAllowed('NoAuth'));
break; break;
} }
} }
} else { } else {
requestEvent.respondWith(stdResp.Forbidden('What are you trying to do?')); requestEvent.respondWith(stdResp.Forbidden('What are you trying to do?'));
} }
} else if (authenticated && rateLimited) { } else if (authenticated && rateLimited) {
// Alert API user that they are doing this too often // Alert API user that they are doing this too often
requestEvent.respondWith(stdResp.TooManyRequests('Slow down, servers are expensive and this bot is free to use.')); requestEvent.respondWith(stdResp.TooManyRequests('Slow down, servers are expensive and this bot is free to use.'));
} else { } else {
// Alert API user that they shouldn't be doing this // Alert API user that they shouldn't be doing this
requestEvent.respondWith(stdResp.Forbidden('Why are you here?')); requestEvent.respondWith(stdResp.Forbidden('Why are you here?'));
} }
} }
})(); })();
} }
}; };
export default { start }; export default { start };

View File

@ -1,74 +1,84 @@
import { dbClient, queries } from '../db.ts'; import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import { import {
// Discordeno deps // Discordeno deps
DiscordenoMessage, DiscordenoMessage,
hasGuildPermissions, hasGuildPermissions,
} from '../../deps.ts'; } from '../../deps.ts';
import apiCommands from './apiCmd/_index.ts'; import apiCommands from './apiCmd/_index.ts';
import { failColor } from '../commandUtils.ts'; import { failColor } from '../commandUtils.ts';
import utils from '../utils.ts'; import utils from '../utils.ts';
export const api = async (message: DiscordenoMessage, args: string[]) => { export const api = async (message: DiscordenoMessage, args: string[]) => {
// Light telemetry to see how many times a command is being run // Light telemetry to see how many times a command is being run
dbClient.execute(queries.callIncCnt('api')).catch((e) => utils.commonLoggers.dbError('apiCmd.ts:16', 'call sproc INC_CNT on', e)); dbClient.execute(queries.callIncCnt('api')).catch((e) => utils.commonLoggers.dbError('apiCmd.ts:16', 'call sproc INC_CNT on', e));
// Local apiArg in lowercase // Local apiArg in lowercase
const apiArg = (args[0] || 'help').toLowerCase(); const apiArg = (args[0] || 'help').toLowerCase();
// Alert users who DM the bot that this command is for guilds only // Alert users who DM the bot that this command is for guilds only
if (message.guildId === 0n) { if (message.guildId === 0n) {
message.send({ message
embeds: [{ .send({
color: failColor, embeds: [
title: 'API commands are only available in guilds.', {
}], color: failColor,
}).catch((e: Error) => utils.commonLoggers.messageSendError('apiCmd.ts:30', message, e)); title: 'API commands are only available in guilds.',
return; },
} ],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('apiCmd.ts:30', message, e));
return;
}
// Makes sure the user is authenticated to run the API command // Makes sure the user is authenticated to run the API command
if (await hasGuildPermissions(message.authorId, message.guildId, ['ADMINISTRATOR'])) { if (await hasGuildPermissions(message.authorId, message.guildId, ['ADMINISTRATOR'])) {
switch (apiArg) { switch (apiArg) {
case 'help': case 'help':
case 'h': case 'h':
// [[api help // [[api help
// Shows API help details // Shows API help details
apiCommands.help(message); apiCommands.help(message);
break; break;
case 'allow': case 'allow':
case 'block': case 'block':
case 'enable': case 'enable':
case 'disable': case 'disable':
// [[api allow/block // [[api allow/block
// Lets a guild admin allow or ban API rolls from happening in said guild // Lets a guild admin allow or ban API rolls from happening in said guild
apiCommands.allowBlock(message, apiArg); apiCommands.allowBlock(message, apiArg);
break; break;
case 'delete': case 'delete':
// [[api delete // [[api delete
// Lets a guild admin delete their server from the database // Lets a guild admin delete their server from the database
apiCommands.deleteGuild(message); apiCommands.deleteGuild(message);
break; break;
case 'status': case 'status':
// [[api status // [[api status
// Lets a guild admin check the status of API rolling in said guild // Lets a guild admin check the status of API rolling in said guild
apiCommands.status(message); apiCommands.status(message);
break; break;
case 'show-warn': case 'show-warn':
case 'hide-warn': case 'hide-warn':
// [[api show-warn/hide-warn // [[api show-warn/hide-warn
// Lets a guild admin decide if the API warning should be shown on messages from the API // Lets a guild admin decide if the API warning should be shown on messages from the API
apiCommands.showHideWarn(message, apiArg); apiCommands.showHideWarn(message, apiArg);
break; break;
default: default:
break; break;
} }
} else { } else {
message.send({ message
embeds: [{ .send({
color: failColor, embeds: [
title: 'API commands are powerful and can only be used by guild Owners and Admins.', {
description: 'For information on how to use the API, please check the GitHub README for more information [here](https://github.com/Burn-E99/TheArtificer).', color: failColor,
}], title: 'API commands are powerful and can only be used by guild Owners and Admins.',
}).catch((e: Error) => utils.commonLoggers.messageSendError('apiCmd.ts:77', message, e)); description:
} 'For information on how to use the API, please check the GitHub README for more information [here](https://github.com/Burn-E99/TheArtificer).',
},
],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('apiCmd.ts:77', message, e));
}
}; };

View File

@ -1,42 +1,52 @@
import { dbClient } from '../../db.ts'; import dbClient from '../../db/client.ts';
import { import {
// Discordeno deps // Discordeno deps
DiscordenoMessage, DiscordenoMessage,
} from '../../../deps.ts'; } from '../../../deps.ts';
import { generateApiFailed, generateApiSuccess } from '../../commandUtils.ts'; import { generateApiFailed, generateApiSuccess } from '../../commandUtils.ts';
import utils from '../../utils.ts'; import utils from '../../utils.ts';
export const allowBlock = async (message: DiscordenoMessage, apiArg: string) => { export const allowBlock = async (message: DiscordenoMessage, apiArg: string) => {
let errorOutInitial = false; let errorOutInitial = false;
const guildQuery = await dbClient.query(`SELECT guildid, channelid FROM allowed_guilds WHERE guildid = ? AND channelid = ?`, [message.guildId, message.channelId]).catch((e0) => { const guildQuery = await dbClient
utils.commonLoggers.dbError('allowBlock.ts:15', 'query', e0); .query(`SELECT guildid, channelid FROM allowed_guilds WHERE guildid = ? AND channelid = ?`, [message.guildId, message.channelId])
message.send(generateApiFailed(apiArg)).catch((e: Error) => utils.commonLoggers.messageSendError('allowBlock.ts:16', message, e)); .catch((e0) => {
errorOutInitial = true; utils.commonLoggers.dbError('allowBlock.ts:15', 'query', e0);
}); message.send(generateApiFailed(apiArg)).catch((e: Error) => utils.commonLoggers.messageSendError('allowBlock.ts:16', message, e));
if (errorOutInitial) return; errorOutInitial = true;
});
if (errorOutInitial) return;
let errorOut = false; let errorOut = false;
if (guildQuery.length === 0) { if (guildQuery.length === 0) {
// Since guild is not in our DB, add it in // Since guild is not in our DB, add it in
await dbClient.execute(`INSERT INTO allowed_guilds(guildid,channelid,active) values(?,?,?)`, [message.guildId, message.channelId, (apiArg === 'allow' || apiArg === 'enable') ? 1 : 0]).catch( await dbClient
(e0) => { .execute(`INSERT INTO allowed_guilds(guildid,channelid,active) values(?,?,?)`, [
utils.commonLoggers.dbError('allowBlock:26', 'insert into', e0); message.guildId,
message.send(generateApiFailed(apiArg)).catch((e: Error) => utils.commonLoggers.messageSendError('allowBlock.ts:27', message, e)); message.channelId,
errorOut = true; apiArg === 'allow' || apiArg === 'enable' ? 1 : 0,
}, ])
); .catch((e0) => {
} else { utils.commonLoggers.dbError('allowBlock:26', 'insert into', e0);
// Since guild is in our DB, update it message.send(generateApiFailed(apiArg)).catch((e: Error) => utils.commonLoggers.messageSendError('allowBlock.ts:27', message, e));
await dbClient.execute(`UPDATE allowed_guilds SET active = ? WHERE guildid = ? AND channelid = ?`, [(apiArg === 'allow' || apiArg === 'enable') ? 1 : 0, message.guildId, message.channelId]).catch( errorOut = true;
(e0) => { });
utils.commonLoggers.dbError('allowBlock.ts:35', 'update', e0); } else {
message.send(generateApiFailed(apiArg)).catch((e: Error) => utils.commonLoggers.messageSendError('allowBlock.ts:36', message, e)); // Since guild is in our DB, update it
errorOut = true; await dbClient
}, .execute(`UPDATE allowed_guilds SET active = ? WHERE guildid = ? AND channelid = ?`, [
); apiArg === 'allow' || apiArg === 'enable' ? 1 : 0,
} message.guildId,
if (errorOut) return; message.channelId,
])
.catch((e0) => {
utils.commonLoggers.dbError('allowBlock.ts:35', 'update', e0);
message.send(generateApiFailed(apiArg)).catch((e: Error) => utils.commonLoggers.messageSendError('allowBlock.ts:36', message, e));
errorOut = true;
});
}
if (errorOut) return;
// We won't get here if there's any errors, so we know it has bee successful, so report as such // We won't get here if there's any errors, so we know it has bee successful, so report as such
message.send(generateApiSuccess(`${apiArg}ed`)).catch((e: Error) => utils.commonLoggers.messageSendError('allowBlock.ts:44', message, e)); message.send(generateApiSuccess(`${apiArg}ed`)).catch((e: Error) => utils.commonLoggers.messageSendError('allowBlock.ts:44', message, e));
}; };

View File

@ -1,31 +1,39 @@
import { dbClient } from '../../db.ts'; import dbClient from '../../db/client.ts';
import { import {
// Discordeno deps // Discordeno deps
DiscordenoMessage, DiscordenoMessage,
} from '../../../deps.ts'; } from '../../../deps.ts';
import { failColor, successColor } from '../../commandUtils.ts'; import { failColor, successColor } from '../../commandUtils.ts';
import utils from '../../utils.ts'; import utils from '../../utils.ts';
export const deleteGuild = async (message: DiscordenoMessage) => { export const deleteGuild = async (message: DiscordenoMessage) => {
let errorOut = false; let errorOut = false;
await dbClient.execute(`DELETE FROM allowed_guilds WHERE guildid = ? AND channelid = ?`, [message.guildId, message.channelId]).catch((e0) => { await dbClient.execute(`DELETE FROM allowed_guilds WHERE guildid = ? AND channelid = ?`, [message.guildId, message.channelId]).catch((e0) => {
utils.commonLoggers.dbError('deleteGuild.ts:15', 'query', e0); utils.commonLoggers.dbError('deleteGuild.ts:15', 'query', e0);
message.send({ message
embeds: [{ .send({
color: failColor, embeds: [
title: 'Failed to delete this guild from the database.', {
description: 'If this issue persists, please report this to the developers.', color: failColor,
}], title: 'Failed to delete this guild from the database.',
}).catch((e: Error) => utils.commonLoggers.messageSendError('deleteGuild.ts:22', message, e)); description: 'If this issue persists, please report this to the developers.',
errorOut = true; },
}); ],
if (errorOut) return; })
.catch((e: Error) => utils.commonLoggers.messageSendError('deleteGuild.ts:22', message, e));
errorOut = true;
});
if (errorOut) return;
// We won't get here if there's any errors, so we know it has bee successful, so report as such // We won't get here if there's any errors, so we know it has bee successful, so report as such
message.send({ message
embeds: [{ .send({
color: successColor, embeds: [
title: 'This guild\'s API setting has been removed from The Artifier\'s Database.', {
}], color: successColor,
}).catch((e: Error) => utils.commonLoggers.messageSendError('deleteGuild.ts:33', message, e)); title: "This guild's API setting has been removed from The Artifier's Database.",
},
],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('deleteGuild.ts:33', message, e));
}; };

View File

@ -1,38 +1,48 @@
import { dbClient } from '../../db.ts'; import dbClient from '../../db/client.ts';
import { import {
// Discordeno deps // Discordeno deps
DiscordenoMessage, DiscordenoMessage,
} from '../../../deps.ts'; } from '../../../deps.ts';
import { generateApiFailed, generateApiSuccess } from '../../commandUtils.ts'; import { generateApiFailed, generateApiSuccess } from '../../commandUtils.ts';
import utils from '../../utils.ts'; import utils from '../../utils.ts';
export const showHideWarn = async (message: DiscordenoMessage, apiArg: string) => { export const showHideWarn = async (message: DiscordenoMessage, apiArg: string) => {
let errorOutInitial = false; let errorOutInitial = false;
const guildQuery = await dbClient.query(`SELECT guildid, channelid FROM allowed_guilds WHERE guildid = ? AND channelid = ?`, [message.guildId, message.channelId]).catch((e0) => { const guildQuery = await dbClient
utils.commonLoggers.dbError('showHideWarn.ts:15', 'query', e0); .query(`SELECT guildid, channelid FROM allowed_guilds WHERE guildid = ? AND channelid = ?`, [message.guildId, message.channelId])
message.send(generateApiFailed(`${apiArg} on`)).catch((e: Error) => utils.commonLoggers.messageSendError('showHideWarn.ts:16', message, e)); .catch((e0) => {
errorOutInitial = true; utils.commonLoggers.dbError('showHideWarn.ts:15', 'query', e0);
}); message.send(generateApiFailed(`${apiArg} on`)).catch((e: Error) => utils.commonLoggers.messageSendError('showHideWarn.ts:16', message, e));
if (errorOutInitial) return; errorOutInitial = true;
});
if (errorOutInitial) return;
let errorOut = false; let errorOut = false;
if (guildQuery.length === 0) { if (guildQuery.length === 0) {
// Since guild is not in our DB, add it in // Since guild is not in our DB, add it in
await dbClient.execute(`INSERT INTO allowed_guilds(guildid,channelid,hidewarn) values(?,?,?)`, [message.guildId, message.channelId, (apiArg === 'hide-warn') ? 1 : 0]).catch((e0) => { await dbClient
utils.commonLoggers.dbError('showHideWarn.ts:25', 'insert inot', e0); .execute(`INSERT INTO allowed_guilds(guildid,channelid,hidewarn) values(?,?,?)`, [message.guildId, message.channelId, apiArg === 'hide-warn' ? 1 : 0])
message.send(generateApiFailed(`${apiArg} on`)).catch((e: Error) => utils.commonLoggers.messageSendError('showHideWarn.ts:26', message, e)); .catch((e0) => {
errorOut = true; utils.commonLoggers.dbError('showHideWarn.ts:25', 'insert inot', e0);
}); message.send(generateApiFailed(`${apiArg} on`)).catch((e: Error) => utils.commonLoggers.messageSendError('showHideWarn.ts:26', message, e));
} else { errorOut = true;
// Since guild is in our DB, update it });
await dbClient.execute(`UPDATE allowed_guilds SET hidewarn = ? WHERE guildid = ? AND channelid = ?`, [(apiArg === 'hide-warn') ? 1 : 0, message.guildId, message.channelId]).catch((e0) => { } else {
utils.commonLoggers.dbError('showHideWarn.ts:32', 'update', e0); // Since guild is in our DB, update it
message.send(generateApiFailed(`${apiArg} on`)).catch((e: Error) => utils.commonLoggers.messageSendError('showHideWarn.ts:33', message, e)); await dbClient
errorOut = true; .execute(`UPDATE allowed_guilds SET hidewarn = ? WHERE guildid = ? AND channelid = ?`, [
}); apiArg === 'hide-warn' ? 1 : 0,
} message.guildId,
if (errorOut) return; message.channelId,
])
.catch((e0) => {
utils.commonLoggers.dbError('showHideWarn.ts:32', 'update', e0);
message.send(generateApiFailed(`${apiArg} on`)).catch((e: Error) => utils.commonLoggers.messageSendError('showHideWarn.ts:33', message, e));
errorOut = true;
});
}
if (errorOut) return;
// We won't get here if there's any errors, so we know it has bee successful, so report as such // We won't get here if there's any errors, so we know it has bee successful, so report as such
message.send(generateApiSuccess(apiArg)).catch((e: Error) => utils.commonLoggers.messageSendError('showHideWarn.ts:40', message, e)); message.send(generateApiSuccess(apiArg)).catch((e: Error) => utils.commonLoggers.messageSendError('showHideWarn.ts:40', message, e));
}; };

View File

@ -1,37 +1,43 @@
import { dbClient } from '../../db.ts'; import dbClient from '../../db/client.ts';
import { import {
// Discordeno deps // Discordeno deps
DiscordenoMessage, DiscordenoMessage,
} from '../../../deps.ts'; } from '../../../deps.ts';
import { failColor, generateApiStatus } from '../../commandUtils.ts'; import { failColor, generateApiStatus } from '../../commandUtils.ts';
import utils from '../../utils.ts'; import utils from '../../utils.ts';
export const status = async (message: DiscordenoMessage) => { export const status = async (message: DiscordenoMessage) => {
// Get status of guild from the db // Get status of guild from the db
let errorOut = false; let errorOut = false;
const guildQuery = await dbClient.query(`SELECT active, banned FROM allowed_guilds WHERE guildid = ? AND channelid = ?`, [message.guildId, message.channelId]).catch((e0) => { const guildQuery = await dbClient
utils.commonLoggers.dbError('status.ts:16', 'query', e0); .query(`SELECT active, banned FROM allowed_guilds WHERE guildid = ? AND channelid = ?`, [message.guildId, message.channelId])
message.send({ .catch((e0) => {
embeds: [{ utils.commonLoggers.dbError('status.ts:16', 'query', e0);
color: failColor, message
title: 'Failed to check API rolls status for this guild.', .send({
description: 'If this issue persists, please report this to the developers.', embeds: [
}], {
}).catch((e: Error) => utils.commonLoggers.messageSendError('status.ts:23', message, e)); color: failColor,
errorOut = true; title: 'Failed to check API rolls status for this guild.',
}); description: 'If this issue persists, please report this to the developers.',
if (errorOut) return; },
],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('status.ts:23', message, e));
errorOut = true;
});
if (errorOut) return;
// Check if we got an item back or not // Check if we got an item back or not
if (guildQuery.length > 0) { if (guildQuery.length > 0) {
// Check if guild is banned from using API and return appropriate message // Check if guild is banned from using API and return appropriate message
if (guildQuery[0].banned) { if (guildQuery[0].banned) {
message.send(generateApiStatus(true, false)).catch((e: Error) => utils.commonLoggers.messageSendError('status.ts:32', message, e)); message.send(generateApiStatus(true, false)).catch((e: Error) => utils.commonLoggers.messageSendError('status.ts:32', message, e));
} else { } else {
message.send(generateApiStatus(false, guildQuery[0].active)).catch((e: Error) => utils.commonLoggers.messageSendError('status.ts:34', message, e)); message.send(generateApiStatus(false, guildQuery[0].active)).catch((e: Error) => utils.commonLoggers.messageSendError('status.ts:34', message, e));
} }
} else { } else {
// Guild is not in DB, therefore they are blocked // Guild is not in DB, therefore they are blocked
message.send(generateApiStatus(false, false)).catch((e: Error) => utils.commonLoggers.messageSendError('status.ts:38', message, e)); message.send(generateApiStatus(false, false)).catch((e: Error) => utils.commonLoggers.messageSendError('status.ts:38', message, e));
} }
}; };

View File

@ -1,48 +1,53 @@
import config from '../../config.ts'; import config from '../../config.ts';
import { dbClient, queries } from '../db.ts'; import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import { import {
// Discordeno deps // Discordeno deps
DiscordenoMessage, DiscordenoMessage,
} from '../../deps.ts'; } from '../../deps.ts';
import auditCommands from './auditCmd/_index.ts'; import auditCommands from './auditCmd/_index.ts';
import { failColor } from '../commandUtils.ts'; import { failColor } from '../commandUtils.ts';
import utils from '../utils.ts'; import utils from '../utils.ts';
export const audit = async (message: DiscordenoMessage, args: string[]) => { export const audit = async (message: DiscordenoMessage, args: string[]) => {
// Light telemetry to see how many times a command is being run // Light telemetry to see how many times a command is being run
dbClient.execute(queries.callIncCnt('audit')).catch((e) => utils.commonLoggers.dbError('audit.ts:16', 'call sproc INC_CNT on', e)); dbClient.execute(queries.callIncCnt('audit')).catch((e) => utils.commonLoggers.dbError('audit.ts:16', 'call sproc INC_CNT on', e));
// Local apiArg in lowercase // Local apiArg in lowercase
const auditArg = (args[0] || 'help').toLowerCase(); const auditArg = (args[0] || 'help').toLowerCase();
// Makes sure the user is authenticated to run the API command // Makes sure the user is authenticated to run the API command
if (message.authorId === config.api.admin) { if (message.authorId === config.api.admin) {
switch (auditArg) { switch (auditArg) {
case 'help': case 'help':
case 'h': case 'h':
// [[audit help or [[audit h // [[audit help or [[audit h
// Shows API help details // Shows API help details
auditCommands.auditHelp(message); auditCommands.auditHelp(message);
break; break;
case 'db': case 'db':
// [[audit db // [[audit db
// Shows current DB table sizes // Shows current DB table sizes
auditCommands.auditDB(message); auditCommands.auditDB(message);
break; break;
case 'guilds': case 'guilds':
// [[audit guilds // [[audit guilds
// Shows breakdown of guilds and detials on them // Shows breakdown of guilds and detials on them
auditCommands.auditGuilds(message); auditCommands.auditGuilds(message);
break; break;
default: default:
break; break;
} }
} else { } else {
message.send({ message
embeds: [{ .send({
color: failColor, embeds: [
title: `Audit commands are powerful and can only be used by ${config.name}'s owner.`, {
}], color: failColor,
}).catch((e: Error) => utils.commonLoggers.messageSendError('audit.ts:51', message, e)); title: `Audit commands are powerful and can only be used by ${config.name}'s owner.`,
} },
],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('audit.ts:51', message, e));
}
}; };

View File

@ -1,42 +1,44 @@
import { dbClient } from '../../db.ts'; import dbClient from '../../db/client.ts';
import { import {
// Discordeno deps // Discordeno deps
DiscordenoMessage, DiscordenoMessage,
EmbedField, EmbedField,
} from '../../../deps.ts'; } from '../../../deps.ts';
import { infoColor2 } from '../../commandUtils.ts'; import { infoColor2 } from '../../commandUtils.ts';
import { compilingStats } from '../../commonEmbeds.ts'; import { compilingStats } from '../../commonEmbeds.ts';
import utils from '../../utils.ts'; import utils from '../../utils.ts';
export const auditDB = async (message: DiscordenoMessage) => { export const auditDB = async (message: DiscordenoMessage) => {
try { try {
const m = await message.send(compilingStats); const m = await message.send(compilingStats);
// Get DB statistics // Get DB statistics
const auditQuery = await dbClient.query(`SELECT * FROM db_size;`).catch((e) => utils.commonLoggers.dbError('auditDB.ts:19', 'query', e)); const auditQuery = await dbClient.query(`SELECT * FROM db_size;`).catch((e) => utils.commonLoggers.dbError('auditDB.ts:19', 'query', e));
// Turn all tables into embed fields, currently only properly will handle 25 tables, but we'll fix that when artificer gets 26 tables // Turn all tables into embed fields, currently only properly will handle 25 tables, but we'll fix that when artificer gets 26 tables
const embedFields: Array<EmbedField> = []; const embedFields: Array<EmbedField> = [];
auditQuery.forEach((row: any) => { auditQuery.forEach((row: any) => {
embedFields.push({ embedFields.push({
name: `${row.table}`, name: `${row.table}`,
value: `**Size:** ${row.size} MB value: `**Size:** ${row.size} MB
**Rows:** ${row.rows}`, **Rows:** ${row.rows}`,
inline: true, inline: true,
}); });
}); });
// Send the results // Send the results
m.edit({ m.edit({
embeds: [{ embeds: [
color: infoColor2, {
title: 'Database Audit', color: infoColor2,
description: 'Lists all tables with their current size and row count.', title: 'Database Audit',
timestamp: new Date().toISOString(), description: 'Lists all tables with their current size and row count.',
fields: embedFields, timestamp: new Date().toISOString(),
}], fields: embedFields,
}).catch((e: Error) => utils.commonLoggers.messageEditError('auditDB.ts:43', message, e)); },
} catch (e) { ],
utils.commonLoggers.messageSendError('auditDB.ts:45', message, e); }).catch((e: Error) => utils.commonLoggers.messageEditError('auditDB.ts:43', message, e));
} } catch (e) {
utils.commonLoggers.messageSendError('auditDB.ts:45', message, e);
}
}; };

View File

@ -1,11 +1,12 @@
import config from '../../config.ts'; import config from '../../config.ts';
import { dbClient, queries } from '../db.ts'; import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import { import {
// Discordeno deps // Discordeno deps
DiscordenoMessage, DiscordenoMessage,
// Log4Deno deps // Log4Deno deps
log, log,
LT, LT,
} from '../../deps.ts'; } from '../../deps.ts';
import { EmojiConf } from '../mod.d.ts'; import { EmojiConf } from '../mod.d.ts';
import utils from '../utils.ts'; import utils from '../utils.ts';
@ -13,28 +14,30 @@ import utils from '../utils.ts';
const allEmojiAliases: string[] = []; const allEmojiAliases: string[] = [];
config.emojis.forEach((emji: EmojiConf) => { config.emojis.forEach((emji: EmojiConf) => {
allEmojiAliases.push(...emji.aliases); allEmojiAliases.push(...emji.aliases);
}); });
export const emoji = (message: DiscordenoMessage, command: string) => { export const emoji = (message: DiscordenoMessage, command: string) => {
// shortcut // shortcut
if (allEmojiAliases.indexOf(command)) { if (allEmojiAliases.indexOf(command)) {
// Start looping thru the possible emojis // Start looping thru the possible emojis
config.emojis.some((emji: EmojiConf) => { config.emojis.some((emji: EmojiConf) => {
log(LT.LOG, `Checking if command was emoji ${JSON.stringify(emji)}`); log(LT.LOG, `Checking if command was emoji ${JSON.stringify(emji)}`);
// If a match gets found // If a match gets found
if (emji.aliases.indexOf(command || '') > -1) { if (emji.aliases.indexOf(command || '') > -1) {
// Light telemetry to see how many times a command is being run // Light telemetry to see how many times a command is being run
dbClient.execute(queries.callIncCnt('emojis')).catch((e) => utils.commonLoggers.dbError('emojis.ts:28', 'call sproc INC_CNT on', e)); dbClient.execute(queries.callIncCnt('emojis')).catch((e) => utils.commonLoggers.dbError('emojis.ts:28', 'call sproc INC_CNT on', e));
// Send the needed emoji // Send the needed emoji
message.send(`<${emji.animated ? 'a' : ''}:${emji.name}:${emji.id}>`).catch((e: Error) => utils.commonLoggers.messageSendError('emoji.ts:33', message, e)); message
// And attempt to delete if needed .send(`<${emji.animated ? 'a' : ''}:${emji.name}:${emji.id}>`)
if (emji.deleteSender) { .catch((e: Error) => utils.commonLoggers.messageSendError('emoji.ts:33', message, e));
message.delete().catch((e: Error) => utils.commonLoggers.messageDeleteError('emoji.ts:36', message, e)); // And attempt to delete if needed
} if (emji.deleteSender) {
return true; message.delete().catch((e: Error) => utils.commonLoggers.messageDeleteError('emoji.ts:36', message, e));
} }
}); return true;
} }
});
}
}; };

View File

@ -1,31 +1,38 @@
import config from '../../config.ts'; import config from '../../config.ts';
import { dbClient, queries } from '../db.ts'; import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import { import {
// Discordeno deps // Discordeno deps
DiscordenoMessage, DiscordenoMessage,
// Log4Deno deps // Log4Deno deps
log, log,
LT, LT,
} from '../../deps.ts'; } from '../../deps.ts';
import { infoColor1 } from '../commandUtils.ts'; import { infoColor1 } from '../commandUtils.ts';
import utils from '../utils.ts'; import utils from '../utils.ts';
export const handleMentions = (message: DiscordenoMessage) => { export const handleMentions = (message: DiscordenoMessage) => {
log(LT.LOG, `Handling @mention message: ${JSON.stringify(message)}`); log(LT.LOG, `Handling @mention message: ${JSON.stringify(message)}`);
// Light telemetry to see how many times a command is being run // Light telemetry to see how many times a command is being run
dbClient.execute(queries.callIncCnt('mention')).catch((e) => utils.commonLoggers.dbError('handleMentions.ts:17', 'call sproc INC_CNT on', e)); dbClient.execute(queries.callIncCnt('mention')).catch((e) => utils.commonLoggers.dbError('handleMentions.ts:17', 'call sproc INC_CNT on', e));
message.send({ message
embeds: [{ .send({
color: infoColor1, embeds: [
title: `Hello! I am ${config.name}!`, {
fields: [{ color: infoColor1,
name: 'I am a bot that specializes in rolling dice and doing basic algebra.', title: `Hello! I am ${config.name}!`,
value: `To learn about my available commands, please run \`${config.prefix}help\`. fields: [
{
name: 'I am a bot that specializes in rolling dice and doing basic algebra.',
value: `To learn about my available commands, please run \`${config.prefix}help\`.
Want me to ignore you? Simply run \`${config.prefix}opt-out\` and ${config.name} will no longer read your messages or respond to you.`, Want me to ignore you? Simply run \`${config.prefix}opt-out\` and ${config.name} will no longer read your messages or respond to you.`,
}], },
}], ],
}).catch((e: Error) => utils.commonLoggers.messageSendError('handleMentions.ts:30', message, e)); },
],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('handleMentions.ts:30', message, e));
}; };

View File

@ -1,7 +1,8 @@
import { dbClient, queries } from '../db.ts'; import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import { import {
// Discordeno deps // Discordeno deps
DiscordenoMessage, DiscordenoMessage,
} from '../../deps.ts'; } from '../../deps.ts';
import config from '../../config.ts'; import config from '../../config.ts';
import { failColor, infoColor2 } from '../commandUtils.ts'; import { failColor, infoColor2 } from '../commandUtils.ts';
@ -9,33 +10,41 @@ import utils from '../utils.ts';
import intervals from '../intervals.ts'; import intervals from '../intervals.ts';
export const heatmap = async (message: DiscordenoMessage) => { export const heatmap = async (message: DiscordenoMessage) => {
// Light telemetry to see how many times a command is being run // Light telemetry to see how many times a command is being run
dbClient.execute(queries.callIncCnt('heatmap')).catch((e) => utils.commonLoggers.dbError('heatmap.ts:14', 'call sproc INC_CNT on', e)); dbClient.execute(queries.callIncCnt('heatmap')).catch((e) => utils.commonLoggers.dbError('heatmap.ts:14', 'call sproc INC_CNT on', e));
if (config.api.enable) { if (config.api.enable) {
message.send({ message
embeds: [{ .send({
title: 'Roll Heatmap', embeds: [
description: `Over time, this image will show a nice pattern of when rolls are requested the most. {
title: 'Roll Heatmap',
description: `Over time, this image will show a nice pattern of when rolls are requested the most.
Least Rolls: ${intervals.getMinRollCnt()} Least Rolls: ${intervals.getMinRollCnt()}
Most Rolls: ${intervals.getMaxRollCnt()}`, Most Rolls: ${intervals.getMaxRollCnt()}`,
footer: { footer: {
text: 'Data is shown in US Eastern Time. | This heatmap uses data starting 6/26/2022.', text: 'Data is shown in US Eastern Time. | This heatmap uses data starting 6/26/2022.',
}, },
color: infoColor2, color: infoColor2,
image: { image: {
url: `${config.api.publicDomain}api/heatmap.png`, url: `${config.api.publicDomain}api/heatmap.png`,
}, },
}], },
}).catch((e) => utils.commonLoggers.messageSendError('heatmap.ts:21', message, e)); ],
} else { })
message.send({ .catch((e) => utils.commonLoggers.messageSendError('heatmap.ts:21', message, e));
embeds: [{ } else {
title: 'Roll Heatmap Disabled', message
description: 'This command requires the bot\'s API to be enabled. If you are the host of this bot, check your `config.ts` file to enable it.', .send({
color: failColor, embeds: [
}], {
}).catch((e) => utils.commonLoggers.messageSendError('heatmap.ts:21', message, e)); title: 'Roll Heatmap Disabled',
} description: "This command requires the bot's API to be enabled. If you are the host of this bot, check your `config.ts` file to enable it.",
color: failColor,
},
],
})
.catch((e) => utils.commonLoggers.messageSendError('heatmap.ts:21', message, e));
}
}; };

View File

@ -1,98 +1,102 @@
import config from '../../config.ts'; import config from '../../config.ts';
import { dbClient, queries } from '../db.ts'; import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import { import {
// Discordeno deps // Discordeno deps
DiscordenoMessage, DiscordenoMessage,
} from '../../deps.ts'; } from '../../deps.ts';
import { infoColor2 } from '../commandUtils.ts'; import { infoColor2 } from '../commandUtils.ts';
import utils from '../utils.ts'; import utils from '../utils.ts';
export const help = (message: DiscordenoMessage) => { export const help = (message: DiscordenoMessage) => {
// Light telemetry to see how many times a command is being run // Light telemetry to see how many times a command is being run
dbClient.execute(queries.callIncCnt('help')).catch((e) => utils.commonLoggers.dbError('htlp.ts:15', 'call sproc INC_CNT on', e)); dbClient.execute(queries.callIncCnt('help')).catch((e) => utils.commonLoggers.dbError('htlp.ts:15', 'call sproc INC_CNT on', e));
message.send({ message
embeds: [{ .send({
color: infoColor2, embeds: [
title: 'The Artificer\'s Available Commands:', {
fields: [ color: infoColor2,
{ title: "The Artificer's Available Commands:",
name: `\`${config.prefix}?\``, fields: [
value: 'This command', {
inline: true, name: `\`${config.prefix}?\``,
}, value: 'This command',
{ inline: true,
name: `\`${config.prefix}rollhelp\` or \`${config.prefix}??\``, },
value: `Details on how to use the roll command, listed as \`${config.prefix}xdy...${config.postfix}\` below`, {
inline: true, name: `\`${config.prefix}rollhelp\` or \`${config.prefix}??\``,
}, value: `Details on how to use the roll command, listed as \`${config.prefix}xdy...${config.postfix}\` below`,
{ inline: true,
name: `\`${config.prefix}rollDecorators\` or \`${config.prefix}???\``, },
value: `Details on how to use decorators on the roll command`, {
inline: true, name: `\`${config.prefix}rollDecorators\` or \`${config.prefix}???\``,
}, value: `Details on how to use decorators on the roll command`,
{ inline: true,
name: `\`${config.prefix}api [subcommand]\``, },
value: `Administrative tools for the bots's API, run \`${config.prefix}api help\` for more details`, {
inline: true, name: `\`${config.prefix}api [subcommand]\``,
}, value: `Administrative tools for the bots's API, run \`${config.prefix}api help\` for more details`,
{ inline: true,
name: `\`${config.prefix}ping\``, },
value: 'Pings the bot to check connectivity', {
inline: true, name: `\`${config.prefix}ping\``,
}, value: 'Pings the bot to check connectivity',
{ inline: true,
name: `\`${config.prefix}info\``, },
value: 'Prints some information and links relating to the bot', {
inline: true, name: `\`${config.prefix}info\``,
}, value: 'Prints some information and links relating to the bot',
{ inline: true,
name: `\`${config.prefix}privacy\``, },
value: 'Prints some information about the Privacy Policy', {
inline: true, name: `\`${config.prefix}privacy\``,
}, value: 'Prints some information about the Privacy Policy',
{ inline: true,
name: `\`${config.prefix}version\``, },
value: 'Prints the bots version', {
inline: true, name: `\`${config.prefix}version\``,
}, value: 'Prints the bots version',
{ inline: true,
name: `\`${config.prefix}popcat\``, },
value: 'Popcat', {
inline: true, name: `\`${config.prefix}popcat\``,
}, value: 'Popcat',
{ inline: true,
name: `\`${config.prefix}report [text]\``, },
value: 'Report a command that failed to run', {
inline: true, name: `\`${config.prefix}report [text]\``,
}, value: 'Report a command that failed to run',
{ inline: true,
name: `\`${config.prefix}stats\``, },
value: 'Statistics on the bot', {
inline: true, name: `\`${config.prefix}stats\``,
}, value: 'Statistics on the bot',
{ inline: true,
name: `\`${config.prefix}heatmap\``, },
value: 'Heatmap of when the roll command is run the most', {
inline: true, name: `\`${config.prefix}heatmap\``,
}, value: 'Heatmap of when the roll command is run the most',
{ inline: true,
name: `\`${config.prefix}opt-out\` or \`${config.prefix}ignore-me\``, },
value: 'Adds you to an ignore list so the bot will never respond to you', {
inline: true, name: `\`${config.prefix}opt-out\` or \`${config.prefix}ignore-me\``,
}, value: 'Adds you to an ignore list so the bot will never respond to you',
{ inline: true,
name: `\`${config.prefix}opt-in\` **Available via DM ONLY**`, },
value: 'Removes you from the ignore list', {
inline: true, name: `\`${config.prefix}opt-in\` **Available via DM ONLY**`,
}, value: 'Removes you from the ignore list',
{ inline: true,
name: `\`${config.prefix}xdydzracsq!${config.postfix}\` ...`, },
value: {
`Rolls all configs requested, you may repeat the command multiple times in the same message (just ensure you close each roll with \`${config.postfix}\`), run \`${config.prefix}??\` for more details`, name: `\`${config.prefix}xdydzracsq!${config.postfix}\` ...`,
inline: true, value: `Rolls all configs requested, you may repeat the command multiple times in the same message (just ensure you close each roll with \`${config.postfix}\`), run \`${config.prefix}??\` for more details`,
}, inline: true,
], },
}], ],
}).catch((e: Error) => utils.commonLoggers.messageSendError('help.ts:82', message, e)); },
],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('help.ts:82', message, e));
}; };

View File

@ -1,24 +1,29 @@
import config from '../../config.ts'; import config from '../../config.ts';
import { dbClient, queries } from '../db.ts'; import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import { import {
// Discordeno deps // Discordeno deps
DiscordenoMessage, DiscordenoMessage,
} from '../../deps.ts'; } from '../../deps.ts';
import { infoColor2 } from '../commandUtils.ts'; import { infoColor2 } from '../commandUtils.ts';
import utils from '../utils.ts'; import utils from '../utils.ts';
export const info = (message: DiscordenoMessage) => { export const info = (message: DiscordenoMessage) => {
// Light telemetry to see how many times a command is being run // Light telemetry to see how many times a command is being run
dbClient.execute(queries.callIncCnt('info')).catch((e) => utils.commonLoggers.dbError('info.ts:12', 'call sproc INC_CNT on', e)); dbClient.execute(queries.callIncCnt('info')).catch((e) => utils.commonLoggers.dbError('info.ts:12', 'call sproc INC_CNT on', e));
message.send({ message
embeds: [{ .send({
color: infoColor2, embeds: [
title: `${config.name}, a Discord bot that specializing in rolling dice and calculating math`, {
description: `${config.name} is developed by Ean AKA Burn_E99. color: infoColor2,
title: `${config.name}, a Discord bot that specializing in rolling dice and calculating math`,
description: `${config.name} is developed by Ean AKA Burn_E99.
Additional information can be found on my website [here](https://discord.burne99.com/TheArtificer/). Additional information can be found on my website [here](https://discord.burne99.com/TheArtificer/).
Want to check out my source code? Check it out [here](https://github.com/Burn-E99/TheArtificer). Want to check out my source code? Check it out [here](https://github.com/Burn-E99/TheArtificer).
Need help with this bot? Join my support server [here](https://discord.gg/peHASXMZYv).`, Need help with this bot? Join my support server [here](https://discord.gg/peHASXMZYv).`,
}], },
}).catch((e: Error) => utils.commonLoggers.messageSendError('info.ts:23', message, e)); ],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('info.ts:23', message, e));
}; };

View File

@ -1,39 +1,48 @@
import config from '../../config.ts'; import config from '../../config.ts';
import { dbClient, ignoreList, queries } from '../db.ts'; import dbClient from '../db/client.ts';
import { queries, ignoreList } from '../db/common.ts';
import { import {
// Discordeno deps // Discordeno deps
DiscordenoMessage, DiscordenoMessage,
} from '../../deps.ts'; } from '../../deps.ts';
import { failColor, successColor } from '../commandUtils.ts'; import { failColor, successColor } from '../commandUtils.ts';
import utils from '../utils.ts'; import utils from '../utils.ts';
export const optIn = async (message: DiscordenoMessage) => { export const optIn = async (message: DiscordenoMessage) => {
// Light telemetry to see how many times a command is being run // Light telemetry to see how many times a command is being run
dbClient.execute(queries.callIncCnt('opt-out')).catch((e) => utils.commonLoggers.dbError('optIn.ts:11', 'call sproc INC_CNT on', e)); dbClient.execute(queries.callIncCnt('opt-out')).catch((e) => utils.commonLoggers.dbError('optIn.ts:11', 'call sproc INC_CNT on', e));
const idIdx = ignoreList.indexOf(message.authorId); const idIdx = ignoreList.indexOf(message.authorId);
if (idIdx !== -1) { if (idIdx !== -1) {
try { try {
ignoreList.splice(idIdx, 1); ignoreList.splice(idIdx, 1);
await dbClient.execute('DELETE FROM ignore_list WHERE userid = ?', [message.authorId]); await dbClient.execute('DELETE FROM ignore_list WHERE userid = ?', [message.authorId]);
message.reply({ message
embeds: [{ .reply({
color: successColor, embeds: [
title: `${config.name} will now respond to you again.`, {
description: `If you want ${config.name} to ignore to you again, please run the following command: color: successColor,
title: `${config.name} will now respond to you again.`,
description: `If you want ${config.name} to ignore to you again, please run the following command:
\`${config.prefix}opt-out\``, \`${config.prefix}opt-out\``,
}], },
}).catch((e: Error) => utils.commonLoggers.messageSendError('optIn.ts:27', message, e)); ],
} catch (err) { })
message.reply({ .catch((e: Error) => utils.commonLoggers.messageSendError('optIn.ts:27', message, e));
embeds: [{ } catch (err) {
color: failColor, message
title: 'Opt-In failed', .reply({
description: 'Please try the command again. If the issue persists, please join the support server, linked in my About Me section.', embeds: [
}], {
}).catch((e: Error) => utils.commonLoggers.messageSendError('optIn.ts:27', message, e)); color: failColor,
} title: 'Opt-In failed',
} description: 'Please try the command again. If the issue persists, please join the support server, linked in my About Me section.',
},
],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('optIn.ts:27', message, e));
}
}
}; };

View File

@ -1,36 +1,45 @@
import config from '../../config.ts'; import config from '../../config.ts';
import { dbClient, ignoreList, queries } from '../db.ts'; import dbClient from '../db/client.ts';
import { queries, ignoreList } from '../db/common.ts';
import { import {
// Discordeno deps // Discordeno deps
DiscordenoMessage, DiscordenoMessage,
} from '../../deps.ts'; } from '../../deps.ts';
import { failColor, successColor } from '../commandUtils.ts'; import { failColor, successColor } from '../commandUtils.ts';
import utils from '../utils.ts'; import utils from '../utils.ts';
export const optOut = async (message: DiscordenoMessage) => { export const optOut = async (message: DiscordenoMessage) => {
// Light telemetry to see how many times a command is being run // Light telemetry to see how many times a command is being run
dbClient.execute(queries.callIncCnt('opt-out')).catch((e) => utils.commonLoggers.dbError('optOut.ts:11', 'call sproc INC_CNT on', e)); dbClient.execute(queries.callIncCnt('opt-out')).catch((e) => utils.commonLoggers.dbError('optOut.ts:11', 'call sproc INC_CNT on', e));
try { try {
ignoreList.push(message.authorId); ignoreList.push(message.authorId);
await dbClient.execute('INSERT INTO ignore_list(userid) values(?)', [message.authorId]); await dbClient.execute('INSERT INTO ignore_list(userid) values(?)', [message.authorId]);
message.reply({ message
embeds: [{ .reply({
color: successColor, embeds: [
title: `${config.name} will no longer respond to you.`, {
description: `If you want ${config.name} to respond to you again, please DM ${config.name} the following command: color: successColor,
title: `${config.name} will no longer respond to you.`,
description: `If you want ${config.name} to respond to you again, please DM ${config.name} the following command:
\`${config.prefix}opt-in\``, \`${config.prefix}opt-in\``,
}], },
}).catch((e: Error) => utils.commonLoggers.messageSendError('optOut.ts:25', message, e)); ],
} catch (err) { })
message.reply({ .catch((e: Error) => utils.commonLoggers.messageSendError('optOut.ts:25', message, e));
embeds: [{ } catch (err) {
color: failColor, message
title: 'Opt-Out failed', .reply({
description: `Please try the command again. If the issue persists, please report this using the \`${config.prefix}report opt-out failed\` command.`, embeds: [
}], {
}).catch((e: Error) => utils.commonLoggers.messageSendError('optOut.ts:33', message, e)); color: failColor,
} title: 'Opt-Out failed',
description: `Please try the command again. If the issue persists, please report this using the \`${config.prefix}report opt-out failed\` command.`,
},
],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('optOut.ts:33', message, e));
}
}; };

View File

@ -1,20 +1,21 @@
import { dbClient, queries } from '../db.ts'; import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import { import {
// Discordeno deps // Discordeno deps
DiscordenoMessage, DiscordenoMessage,
} from '../../deps.ts'; } from '../../deps.ts';
import { generatePing } from '../commandUtils.ts'; import { generatePing } from '../commandUtils.ts';
import utils from '../utils.ts'; import utils from '../utils.ts';
export const ping = async (message: DiscordenoMessage) => { export const ping = async (message: DiscordenoMessage) => {
// Light telemetry to see how many times a command is being run // Light telemetry to see how many times a command is being run
dbClient.execute(queries.callIncCnt('ping')).catch((e) => utils.commonLoggers.dbError('ping.ts:14', 'call sproc INC_CNT on', e)); dbClient.execute(queries.callIncCnt('ping')).catch((e) => utils.commonLoggers.dbError('ping.ts:14', 'call sproc INC_CNT on', e));
// Calculates ping between sending a message and editing it, giving a nice round-trip latency. // Calculates ping between sending a message and editing it, giving a nice round-trip latency.
try { try {
const m = await message.send(generatePing(-1)); const m = await message.send(generatePing(-1));
m.edit(generatePing(m.timestamp - message.timestamp)); m.edit(generatePing(m.timestamp - message.timestamp));
} catch (e) { } catch (e) {
utils.commonLoggers.messageSendError('ping.ts:23', message, e); utils.commonLoggers.messageSendError('ping.ts:23', message, e);
} }
}; };

View File

@ -1,31 +1,37 @@
import config from '../../config.ts'; import config from '../../config.ts';
import { dbClient, queries } from '../db.ts'; import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import { import {
// Discordeno deps // Discordeno deps
DiscordenoMessage, DiscordenoMessage,
} from '../../deps.ts'; } from '../../deps.ts';
import { infoColor1 } from '../commandUtils.ts'; import { infoColor1 } from '../commandUtils.ts';
import utils from '../utils.ts'; import utils from '../utils.ts';
export const privacy = (message: DiscordenoMessage) => { export const privacy = (message: DiscordenoMessage) => {
// Light telemetry to see how many times a command is being run // Light telemetry to see how many times a command is being run
dbClient.execute(queries.callIncCnt('privacy')).catch((e) => utils.commonLoggers.dbError('privacy.ts:15', 'call sproc INC_CNT on', e)); dbClient.execute(queries.callIncCnt('privacy')).catch((e) => utils.commonLoggers.dbError('privacy.ts:15', 'call sproc INC_CNT on', e));
message.send({ message
embeds: [{ .send({
color: infoColor1, embeds: [
title: 'Privacy Policy', {
fields: [{ color: infoColor1,
name: 'The Artificer does not track or collect user information via Discord.', title: 'Privacy Policy',
value: fields: [
`The only user submitted information that is stored is submitted via the \`${config.prefix}report\` command. This information is only stored for a short period of time in a location that only the Developer of The Artificer can see. {
name: 'The Artificer does not track or collect user information via Discord.',
value: `The only user submitted information that is stored is submitted via the \`${config.prefix}report\` command. This information is only stored for a short period of time in a location that only the Developer of The Artificer can see.
For more details, please check out the Privacy Policy on the GitHub [here](https://github.com/Burn-E99/TheArtificer/blob/master/PRIVACY.md). For more details, please check out the Privacy Policy on the GitHub [here](https://github.com/Burn-E99/TheArtificer/blob/master/PRIVACY.md).
Terms of Service can also be found on GitHub [here](https://github.com/Burn-E99/TheArtificer/blob/master/TERMS.md). Terms of Service can also be found on GitHub [here](https://github.com/Burn-E99/TheArtificer/blob/master/TERMS.md).
Want me to ignore you? Simply run \`${config.prefix}opt-out\` and ${config.name} will no longer read your messages or respond to you.`, Want me to ignore you? Simply run \`${config.prefix}opt-out\` and ${config.name} will no longer read your messages or respond to you.`,
}], },
}], ],
}).catch((e: Error) => utils.commonLoggers.messageSendError('privacy.ts:33', message, e)); },
],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('privacy.ts:33', message, e));
}; };

View File

@ -1,34 +1,43 @@
import config from '../../config.ts'; import config from '../../config.ts';
import { dbClient, queries } from '../db.ts'; import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import { import {
// Discordeno deps // Discordeno deps
DiscordenoMessage, DiscordenoMessage,
// Discordeno deps // Discordeno deps
sendMessage, sendMessage,
} from '../../deps.ts'; } from '../../deps.ts';
import { failColor, generateReport, successColor } from '../commandUtils.ts'; import { failColor, generateReport, successColor } from '../commandUtils.ts';
import utils from '../utils.ts'; import utils from '../utils.ts';
export const report = (message: DiscordenoMessage, args: string[]) => { export const report = (message: DiscordenoMessage, args: string[]) => {
// Light telemetry to see how many times a command is being run // Light telemetry to see how many times a command is being run
dbClient.execute(queries.callIncCnt('report')).catch((e) => utils.commonLoggers.dbError('report.ts:17', 'call sproc INC_CNT on', e)); dbClient.execute(queries.callIncCnt('report')).catch((e) => utils.commonLoggers.dbError('report.ts:17', 'call sproc INC_CNT on', e));
if (args.join(' ')) { if (args.join(' ')) {
sendMessage(config.reportChannel, generateReport(args.join(' '))).catch((e: Error) => utils.commonLoggers.messageSendError('report.ts:22', message, e)); sendMessage(config.reportChannel, generateReport(args.join(' '))).catch((e: Error) => utils.commonLoggers.messageSendError('report.ts:22', message, e));
message.send({ message
embeds: [{ .send({
color: successColor, embeds: [
title: 'Failed command has been reported to my developer.', {
description: `For more in depth support, and information about planned maintenance, please join the support server [here](https://discord.gg/peHASXMZYv).`, color: successColor,
}], title: 'Failed command has been reported to my developer.',
}).catch((e: Error) => utils.commonLoggers.messageSendError('report.ts:29', message, e)); description: `For more in depth support, and information about planned maintenance, please join the support server [here](https://discord.gg/peHASXMZYv).`,
} else { },
message.send({ ],
embeds: [{ })
color: failColor, .catch((e: Error) => utils.commonLoggers.messageSendError('report.ts:29', message, e));
title: 'Please provide a short description of what failed', } else {
description: 'Providing a short description helps my developer quickly diagnose what went wrong.', message
}], .send({
}).catch((e: Error) => utils.commonLoggers.messageSendError('report.ts:37', message, e)); embeds: [
} {
color: failColor,
title: 'Please provide a short description of what failed',
description: 'Providing a short description helps my developer quickly diagnose what went wrong.',
},
],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('report.ts:37', message, e));
}
}; };

View File

@ -1,22 +1,27 @@
import { dbClient, queries } from '../db.ts'; import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import { import {
// Discordeno deps // Discordeno deps
DiscordenoMessage, DiscordenoMessage,
} from '../../deps.ts'; } from '../../deps.ts';
import { infoColor2 } from '../commandUtils.ts'; import { infoColor2 } from '../commandUtils.ts';
import utils from '../utils.ts'; import utils from '../utils.ts';
export const rip = (message: DiscordenoMessage) => { export const rip = (message: DiscordenoMessage) => {
// Light telemetry to see how many times a command is being run // Light telemetry to see how many times a command is being run
dbClient.execute(queries.callIncCnt('rip')).catch((e) => utils.commonLoggers.dbError('rip.ts:14', 'call sproc INC_CNT on', e)); dbClient.execute(queries.callIncCnt('rip')).catch((e) => utils.commonLoggers.dbError('rip.ts:14', 'call sproc INC_CNT on', e));
message.send({ message
embeds: [{ .send({
color: infoColor2, embeds: [
title: 'The Artificer was built in memory of my Grandmother, Babka', {
description: `With much love, Ean color: infoColor2,
title: 'The Artificer was built in memory of my Grandmother, Babka',
description: `With much love, Ean
December 21, 2020`, December 21, 2020`,
}], },
}).catch((e: Error) => utils.commonLoggers.messageSendError('rip.ts:26', message, e)); ],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('rip.ts:26', message, e));
}; };

View File

@ -1,12 +1,13 @@
import config from '../../config.ts'; import config from '../../config.ts';
import { DEVMODE } from '../../flags.ts'; import { DEVMODE } from '../../flags.ts';
import { dbClient, queries } from '../db.ts'; import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import { import {
// Discordeno deps // Discordeno deps
DiscordenoMessage, DiscordenoMessage,
// Log4Deno deps // Log4Deno deps
log, log,
LT, LT,
} from '../../deps.ts'; } from '../../deps.ts';
import { rollingEmbed, warnColor } from '../commandUtils.ts'; import { rollingEmbed, warnColor } from '../commandUtils.ts';
import rollFuncs from './roll/_index.ts'; import rollFuncs from './roll/_index.ts';
@ -15,49 +16,51 @@ import { QueuedRoll } from '../mod.d.ts';
import utils from '../utils.ts'; import utils from '../utils.ts';
export const roll = async (message: DiscordenoMessage, args: string[], command: string) => { export const roll = async (message: DiscordenoMessage, args: string[], command: string) => {
// Light telemetry to see how many times a command is being run // Light telemetry to see how many times a command is being run
const currDateTime = new Date(); const currDateTime = new Date();
dbClient.execute(queries.callIncCnt('roll')).catch((e) => utils.commonLoggers.dbError('roll.ts:20', 'call sproc INC_CNT on', e)); dbClient.execute(queries.callIncCnt('roll')).catch((e) => utils.commonLoggers.dbError('roll.ts:20', 'call sproc INC_CNT on', e));
dbClient.execute(queries.callIncHeatmap(currDateTime)).catch((e) => utils.commonLoggers.dbError('roll.ts:21', 'update', e)); dbClient.execute(queries.callIncHeatmap(currDateTime)).catch((e) => utils.commonLoggers.dbError('roll.ts:21', 'update', e));
// If DEVMODE is on, only allow this command to be used in the devServer // If DEVMODE is on, only allow this command to be used in the devServer
if (DEVMODE && message.guildId !== config.devServer) { if (DEVMODE && message.guildId !== config.devServer) {
message.send({ message
embeds: [{ .send({
color: warnColor, embeds: [
title: 'Command is in development, please try again later.', {
}], color: warnColor,
}).catch((e: Error) => utils.commonLoggers.messageSendError('roll.ts:30', message, e)); title: 'Command is in development, please try again later.',
return; },
} ],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('roll.ts:30', message, e));
return;
}
// Rest of this command is in a try-catch to protect all sends/edits from erroring out // Rest of this command is in a try-catch to protect all sends/edits from erroring out
try { try {
const originalCommand = `${config.prefix}${command} ${args.join(' ')}`; const originalCommand = `${config.prefix}${command} ${args.join(' ')}`;
const m = await message.reply(rollingEmbed); const m = await message.reply(rollingEmbed);
// Get modifiers from command // Get modifiers from command
const modifiers = rollFuncs.getModifiers(m, args, command, originalCommand); const modifiers = rollFuncs.getModifiers(m, args, command, originalCommand);
// Return early if the modifiers were invalid // Return early if the modifiers were invalid
if (!modifiers.valid) { if (!modifiers.valid) {
return; return;
} }
// Rejoin all of the args and send it into the solver, if solver returns a falsy item, an error object will be substituded in // Rejoin all of the args and send it into the solver, if solver returns a falsy item, an error object will be substituded in
const rollCmd = message.content.substring(2); const rollCmd = message.content.substring(2);
queueRoll( queueRoll(<QueuedRoll>{
<QueuedRoll> { apiRoll: false,
apiRoll: false, dd: { m, message },
dd: { m, message }, rollCmd,
rollCmd, modifiers,
modifiers, originalCommand,
originalCommand, });
}, } catch (e) {
); log(LT.ERROR, `Undandled Error: ${JSON.stringify(e)}`);
} catch (e) { }
log(LT.ERROR, `Undandled Error: ${JSON.stringify(e)}`);
}
}; };

View File

@ -1,117 +1,130 @@
import config from '../../../config.ts'; import config from '../../../config.ts';
import { DEVMODE } from '../../../flags.ts'; import { DEVMODE } from '../../../flags.ts';
import { dbClient, queries } from '../../db.ts'; import dbClient from '../../db/client.ts';
import { queries } from '../../db/common.ts';
import { import {
// Discordeno deps // Discordeno deps
DiscordenoMessage, DiscordenoMessage,
// Log4Deno deps // Log4Deno deps
log, log,
LT, LT,
} from '../../../deps.ts'; } from '../../../deps.ts';
import { generateRollError } from '../../commandUtils.ts'; import { generateRollError } from '../../commandUtils.ts';
import { RollModifiers } from '../../mod.d.ts'; import { RollModifiers } from '../../mod.d.ts';
import utils from '../../utils.ts'; import utils from '../../utils.ts';
export const getModifiers = (m: DiscordenoMessage, args: string[], command: string, originalCommand: string): RollModifiers => { export const getModifiers = (m: DiscordenoMessage, args: string[], command: string, originalCommand: string): RollModifiers => {
const errorType = 'Modifiers invalid:'; const errorType = 'Modifiers invalid:';
const modifiers: RollModifiers = { const modifiers: RollModifiers = {
noDetails: false, noDetails: false,
superNoDetails: false, superNoDetails: false,
spoiler: '', spoiler: '',
maxRoll: false, maxRoll: false,
nominalRoll: false, nominalRoll: false,
gmRoll: false, gmRoll: false,
gms: [], gms: [],
order: '', order: '',
valid: false, valid: false,
count: false, count: false,
apiWarn: '', apiWarn: '',
}; };
// Check if any of the args are command flags and pull those out into the modifiers object // Check if any of the args are command flags and pull those out into the modifiers object
for (let i = 0; i < args.length; i++) { for (let i = 0; i < args.length; i++) {
log(LT.LOG, `Checking ${command}${args.join(' ')} for command modifiers ${i}`); log(LT.LOG, `Checking ${command}${args.join(' ')} for command modifiers ${i}`);
let defaultCase = false; let defaultCase = false;
switch (args[i].toLowerCase()) { switch (args[i].toLowerCase()) {
case '-c': case '-c':
modifiers.count = true; modifiers.count = true;
break; break;
case '-nd': case '-nd':
modifiers.noDetails = true; modifiers.noDetails = true;
break; break;
case '-snd': case '-snd':
modifiers.superNoDetails = true; modifiers.superNoDetails = true;
break; break;
case '-s': case '-s':
modifiers.spoiler = '||'; modifiers.spoiler = '||';
break; break;
case '-m': case '-m':
modifiers.maxRoll = true; modifiers.maxRoll = true;
break; break;
case '-n': case '-n':
modifiers.nominalRoll = true; modifiers.nominalRoll = true;
break; break;
case '-gm': case '-gm':
modifiers.gmRoll = true; modifiers.gmRoll = true;
// -gm is a little more complex, as we must get all of the GMs that need to be DMd // -gm is a little more complex, as we must get all of the GMs that need to be DMd
while (((i + 1) < args.length) && args[i + 1].startsWith('<@')) { while (i + 1 < args.length && args[i + 1].startsWith('<@')) {
log(LT.LOG, `Finding all GMs, checking args ${JSON.stringify(args)}`); log(LT.LOG, `Finding all GMs, checking args ${JSON.stringify(args)}`);
// Keep looping thru the rest of the args until one does not start with the discord mention code // Keep looping thru the rest of the args until one does not start with the discord mention code
modifiers.gms.push(args[i + 1].replace(/!/g, '')); modifiers.gms.push(args[i + 1].replace(/!/g, ''));
args.splice(i + 1, 1); args.splice(i + 1, 1);
} }
if (modifiers.gms.length < 1) { if (modifiers.gms.length < 1) {
// If -gm is on and none were found, throw an error // If -gm is on and none were found, throw an error
m.edit(generateRollError(errorType, 'Must specifiy at least one GM by @mentioning them')).catch((e) => utils.commonLoggers.messageEditError('getModifiers.ts:66', m, e)); m.edit(generateRollError(errorType, 'Must specifiy at least one GM by @mentioning them')).catch((e) =>
utils.commonLoggers.messageEditError('getModifiers.ts:66', m, e)
);
if (DEVMODE && config.logRolls) { if (DEVMODE && config.logRolls) {
// If enabled, log rolls so we can verify the bots math // If enabled, log rolls so we can verify the bots math
dbClient.execute(queries.insertRollLogCmd(0, 1), [originalCommand, 'NoGMsFound', m.id]).catch((e) => utils.commonLoggers.dbError('getModifiers.ts:72', 'insert into', e)); dbClient
} .execute(queries.insertRollLogCmd(0, 1), [originalCommand, 'NoGMsFound', m.id])
return modifiers; .catch((e) => utils.commonLoggers.dbError('getModifiers.ts:72', 'insert into', e));
} }
break; return modifiers;
case '-o': }
// Shift the -o out of the array so the next item is the direction break;
args.splice(i, 1); case '-o':
// Shift the -o out of the array so the next item is the direction
args.splice(i, 1);
if (!args[i] || args[i].toLowerCase()[0] !== 'd' && args[i].toLowerCase()[0] !== 'a') { if (!args[i] || (args[i].toLowerCase()[0] !== 'd' && args[i].toLowerCase()[0] !== 'a')) {
// If -o is on and asc or desc was not specified, error out // If -o is on and asc or desc was not specified, error out
m.edit(generateRollError(errorType, 'Must specifiy `a` or `d` to order the rolls ascending or descending')).catch((e) => utils.commonLoggers.messageEditError('getModifiers.ts:81', m, e)); m.edit(generateRollError(errorType, 'Must specifiy `a` or `d` to order the rolls ascending or descending')).catch((e) =>
utils.commonLoggers.messageEditError('getModifiers.ts:81', m, e)
);
if (DEVMODE && config.logRolls) { if (DEVMODE && config.logRolls) {
// If enabled, log rolls so we can verify the bots math // If enabled, log rolls so we can verify the bots math
dbClient.execute(queries.insertRollLogCmd(0, 1), [originalCommand, 'NoOrderFound', m.id]).catch((e) => utils.commonLoggers.dbError('getModifiers.ts:89', 'insert into', e)); dbClient
} .execute(queries.insertRollLogCmd(0, 1), [originalCommand, 'NoOrderFound', m.id])
return modifiers; .catch((e) => utils.commonLoggers.dbError('getModifiers.ts:89', 'insert into', e));
} }
return modifiers;
}
modifiers.order = args[i].toLowerCase()[0]; modifiers.order = args[i].toLowerCase()[0];
break; break;
default: default:
// Default case should not mess with the array // Default case should not mess with the array
defaultCase = true; defaultCase = true;
break; break;
} }
if (!defaultCase) { if (!defaultCase) {
args.splice(i, 1); args.splice(i, 1);
i--; i--;
} }
} }
// maxRoll and nominalRoll cannot both be on, throw an error // maxRoll and nominalRoll cannot both be on, throw an error
if (modifiers.maxRoll && modifiers.nominalRoll) { if (modifiers.maxRoll && modifiers.nominalRoll) {
m.edit(generateRollError(errorType, 'Cannot maximise and nominise the roll at the same time')).catch((e) => utils.commonLoggers.messageEditError('getModifiers.ts:106', m, e)); m.edit(generateRollError(errorType, 'Cannot maximise and nominise the roll at the same time')).catch((e) =>
utils.commonLoggers.messageEditError('getModifiers.ts:106', m, e)
);
if (DEVMODE && config.logRolls) { if (DEVMODE && config.logRolls) {
// If enabled, log rolls so we can verify the bots math // If enabled, log rolls so we can verify the bots math
dbClient.execute(queries.insertRollLogCmd(0, 1), [originalCommand, 'MaxAndNominal', m.id]).catch((e) => utils.commonLoggers.dbError('getModifiers.ts:120', 'insert into', e)); dbClient
} .execute(queries.insertRollLogCmd(0, 1), [originalCommand, 'MaxAndNominal', m.id])
return modifiers; .catch((e) => utils.commonLoggers.dbError('getModifiers.ts:120', 'insert into', e));
} }
return modifiers;
}
modifiers.valid = true; modifiers.valid = true;
return modifiers; return modifiers;
}; };

View File

@ -1,71 +1,75 @@
import config from '../../config.ts'; import config from '../../config.ts';
import { dbClient, queries } from '../db.ts'; import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import { import {
// Discordeno deps // Discordeno deps
DiscordenoMessage, DiscordenoMessage,
} from '../../deps.ts'; } from '../../deps.ts';
import { infoColor2 } from '../commandUtils.ts'; import { infoColor2 } from '../commandUtils.ts';
import utils from '../utils.ts'; import utils from '../utils.ts';
export const rollDecorators = (message: DiscordenoMessage) => { export const rollDecorators = (message: DiscordenoMessage) => {
// Light telemetry to see how many times a command is being run // Light telemetry to see how many times a command is being run
dbClient.execute(queries.callIncCnt('rollDecorators')).catch((e) => utils.commonLoggers.dbError('rollHelp.ts:15', 'call sproc INC_CNT on', e)); dbClient.execute(queries.callIncCnt('rollDecorators')).catch((e) => utils.commonLoggers.dbError('rollHelp.ts:15', 'call sproc INC_CNT on', e));
message.send({ message
embeds: [ .send({
{ embeds: [
color: infoColor2, {
title: 'Roll Command Decorators:', color: infoColor2,
description: `This command also has some useful decorators that can used. These decorators simply need to be placed after all rolls in the message. title: 'Roll Command Decorators:',
description: `This command also has some useful decorators that can used. These decorators simply need to be placed after all rolls in the message.
Examples: \`${config.prefix}d20${config.postfix} -nd\`, \`${config.prefix}d20${config.postfix} -nd -s\`, \`${config.prefix}d20${config.postfix} ${config.prefix}d20${config.postfix} ${config.prefix}d20${config.postfix} -o a\``, Examples: \`${config.prefix}d20${config.postfix} -nd\`, \`${config.prefix}d20${config.postfix} -nd -s\`, \`${config.prefix}d20${config.postfix} ${config.prefix}d20${config.postfix} ${config.prefix}d20${config.postfix} -o a\``,
fields: [ fields: [
{ {
name: '`-nd` - No Details', name: '`-nd` - No Details',
value: 'Suppresses all details of the requested roll', value: 'Suppresses all details of the requested roll',
inline: true, inline: true,
}, },
{ {
name: '`-snd` - Super No Details', name: '`-snd` - Super No Details',
value: 'Suppresses all details of the requested roll and hides no details message', value: 'Suppresses all details of the requested roll and hides no details message',
inline: true, inline: true,
}, },
{ {
name: '`-s` - Spoiler', name: '`-s` - Spoiler',
value: 'Spoilers all details of the requested roll', value: 'Spoilers all details of the requested roll',
inline: true, inline: true,
}, },
{ {
name: '`-m` - Maximize Roll', name: '`-m` - Maximize Roll',
value: 'Rolls the theoretical maximum roll, cannot be used with -n', value: 'Rolls the theoretical maximum roll, cannot be used with -n',
inline: true, inline: true,
}, },
{ {
name: '`-n` - Nominal Roll', name: '`-n` - Nominal Roll',
value: 'Rolls the theoretical nominal roll, cannot be used with -m', value: 'Rolls the theoretical nominal roll, cannot be used with -m',
inline: true, inline: true,
}, },
{ {
name: '`-gm @user1 @user2 @usern` - GM Roll', name: '`-gm @user1 @user2 @usern` - GM Roll',
value: 'Rolls the requested roll in GM mode, suppressing all publicly shown results and details and sending the results directly to the specified GMs', value:
inline: true, 'Rolls the requested roll in GM mode, suppressing all publicly shown results and details and sending the results directly to the specified GMs',
}, inline: true,
{ },
name: '`-c` - Count Rolls', {
value: 'Shows the Count Embed, containing the count of successful rolls, failed rolls, rerolls, drops, and explosions', name: '`-c` - Count Rolls',
inline: true, value: 'Shows the Count Embed, containing the count of successful rolls, failed rolls, rerolls, drops, and explosions',
}, inline: true,
{ },
name: '`-o [direction]` - Order Roll', {
value: `Rolls the requested roll and orders the results in the requested direction name: '`-o [direction]` - Order Roll',
value: `Rolls the requested roll and orders the results in the requested direction
Available directions: Available directions:
\`a\` - Ascending (least to greatest) \`a\` - Ascending (least to greatest)
\`d\` - Descending (greatest to least)`, \`d\` - Descending (greatest to least)`,
inline: true, inline: true,
}, },
], ],
}, },
], ],
}).catch((e: Error) => utils.commonLoggers.messageSendError('rollHelp.ts:247', message, e)); })
.catch((e: Error) => utils.commonLoggers.messageSendError('rollHelp.ts:247', message, e));
}; };

View File

@ -1,262 +1,275 @@
import config from '../../config.ts'; import config from '../../config.ts';
import { dbClient, queries } from '../db.ts'; import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import { import {
// Discordeno deps // Discordeno deps
DiscordenoMessage, DiscordenoMessage,
} from '../../deps.ts'; } from '../../deps.ts';
import { infoColor1, infoColor2, successColor } from '../commandUtils.ts'; import { infoColor1, infoColor2, successColor } from '../commandUtils.ts';
import utils from '../utils.ts'; import utils from '../utils.ts';
export const rollHelp = (message: DiscordenoMessage) => { export const rollHelp = (message: DiscordenoMessage) => {
// Light telemetry to see how many times a command is being run // Light telemetry to see how many times a command is being run
dbClient.execute(queries.callIncCnt('rollhelp')).catch((e) => utils.commonLoggers.dbError('rollHelp.ts:15', 'call sproc INC_CNT on', e)); dbClient.execute(queries.callIncCnt('rollhelp')).catch((e) => utils.commonLoggers.dbError('rollHelp.ts:15', 'call sproc INC_CNT on', e));
message.send({ message
embeds: [ .send({
{ embeds: [
color: infoColor1, {
title: 'The Artificer\'s Roll Command Details:', color: infoColor1,
description: `You can chain as many of these options as you want, as long as the option does not disallow it. title: "The Artificer's Roll Command Details:",
description: `You can chain as many of these options as you want, as long as the option does not disallow it.
This command also can fully solve math equations with parenthesis. This command also can fully solve math equations with parenthesis.
The Artificer supports most of the [Roll20 formatting](https://artificer.eanm.dev/roll20). More details and examples can be found [here](https://artificer.eanm.dev/roll20). The Artificer supports most of the [Roll20 formatting](https://artificer.eanm.dev/roll20). More details and examples can be found [here](https://artificer.eanm.dev/roll20).
Run \`[[???\` or \`[[rollDecorators\` for details on the roll decorators.`, Run \`[[???\` or \`[[rollDecorators\` for details on the roll decorators.`,
}, },
{ {
color: infoColor2, color: infoColor2,
title: 'Roll20 Dice Options:', title: 'Roll20 Dice Options:',
fields: [ fields: [
{ {
name: `\`${config.prefix}xdydzracsq!${config.postfix}\` ...`, name: `\`${config.prefix}xdydzracsq!${config.postfix}\` ...`,
value: `Rolls all configs requested, you may repeat the command multiple times in the same message (just ensure you close each roll with \`${config.postfix}\`)`, value: `Rolls all configs requested, you may repeat the command multiple times in the same message (just ensure you close each roll with \`${config.postfix}\`)`,
}, },
{ {
name: '`x` [Optional]', name: '`x` [Optional]',
value: `Number of dice to roll, if omitted, 1 is used value: `Number of dice to roll, if omitted, 1 is used
Additionally, replace \`x\` with \`F\` to roll Fate dice`, Additionally, replace \`x\` with \`F\` to roll Fate dice`,
inline: true, inline: true,
}, },
{ {
name: '`dy` [Required]', name: '`dy` [Required]',
value: 'Size of dice to roll, `d20` = 20 sided die', value: 'Size of dice to roll, `d20` = 20 sided die',
inline: true, inline: true,
}, },
{ {
name: '`dz` or `dlz` [Optional]', name: '`dz` or `dlz` [Optional]',
value: 'Drops the lowest `z` dice, cannot be used with `kz`', value: 'Drops the lowest `z` dice, cannot be used with `kz`',
inline: true, inline: true,
}, },
{ {
name: '`kz` or `khz` [Optional]', name: '`kz` or `khz` [Optional]',
value: 'Keeps the highest `z` dice, cannot be used with `dz`', value: 'Keeps the highest `z` dice, cannot be used with `dz`',
inline: true, inline: true,
}, },
{ {
name: '`dhz` [Optional]', name: '`dhz` [Optional]',
value: 'Drops the highest `z` dice, cannot be used with `kz`', value: 'Drops the highest `z` dice, cannot be used with `kz`',
inline: true, inline: true,
}, },
{ {
name: '`klz` [Optional]', name: '`klz` [Optional]',
value: 'Keeps the lowest `z` dice, cannot be used with `dz`', value: 'Keeps the lowest `z` dice, cannot be used with `dz`',
inline: true, inline: true,
}, },
{ {
name: '`ra` or `r=q` [Optional]', name: '`ra` or `r=q` [Optional]',
value: 'Rerolls any rolls that match `a`, `r3` will reroll every die that land on 3, throwing out old rolls, cannot be used with `ro`', value: 'Rerolls any rolls that match `a`, `r3` will reroll every die that land on 3, throwing out old rolls, cannot be used with `ro`',
inline: true, inline: true,
}, },
{ {
name: '`r<q` [Optional]', name: '`r<q` [Optional]',
value: 'Rerolls any rolls that are less than or equal to `a`, `r3` will reroll every die that land on 3, 2, or 1, throwing out old rolls, cannot be used with `ro`', value:
inline: true, 'Rerolls any rolls that are less than or equal to `a`, `r3` will reroll every die that land on 3, 2, or 1, throwing out old rolls, cannot be used with `ro`',
}, inline: true,
{ },
name: '`r>q` [Optional]', {
value: 'Rerolls any rolls that are greater than or equal to `a`, `r3` will reroll every die that land on 3 or greater, throwing out old rolls, cannot be used with `ro`', name: '`r>q` [Optional]',
inline: true, value:
}, 'Rerolls any rolls that are greater than or equal to `a`, `r3` will reroll every die that land on 3 or greater, throwing out old rolls, cannot be used with `ro`',
{ inline: true,
name: '`roa` or `ro=q` [Optional]', },
value: 'Rerolls any rolls that match `a`, `ro3` will reroll each die that lands on 3 ONLY ONE TIME, throwing out old rolls, cannot be used with `r`', {
inline: true, name: '`roa` or `ro=q` [Optional]',
}, value:
{ 'Rerolls any rolls that match `a`, `ro3` will reroll each die that lands on 3 ONLY ONE TIME, throwing out old rolls, cannot be used with `r`',
name: '`ro<q` [Optional]', inline: true,
value: 'Rerolls any rolls that are less than or equal to `a`, `ro3` will reroll each die that lands on 3, 2, or 1 ONLY ONE TIME, throwing out old rolls, cannot be used with `r`', },
inline: true, {
}, name: '`ro<q` [Optional]',
{ value:
name: '`ro>q` [Optional]', 'Rerolls any rolls that are less than or equal to `a`, `ro3` will reroll each die that lands on 3, 2, or 1 ONLY ONE TIME, throwing out old rolls, cannot be used with `r`',
value: 'Rerolls any rolls that are greater than or equal to `a`, `ro3` will reroll each die that lands on 3 or greater ONLY ONE TIME, throwing out old rolls, cannot be used with `r`', inline: true,
inline: true, },
}, {
{ name: '`ro>q` [Optional]',
name: '`csq` or `cs=q` [Optional]', value:
value: 'Changes crit score to `q`', 'Rerolls any rolls that are greater than or equal to `a`, `ro3` will reroll each die that lands on 3 or greater ONLY ONE TIME, throwing out old rolls, cannot be used with `r`',
inline: true, inline: true,
}, },
{ {
name: '`cs<q` [Optional]', name: '`csq` or `cs=q` [Optional]',
value: 'Changes crit score to be less than or equal to `q`', value: 'Changes crit score to `q`',
inline: true, inline: true,
}, },
{ {
name: '`cs>q` [Optional]', name: '`cs<q` [Optional]',
value: 'Changes crit score to be greater than or equal to `q`', value: 'Changes crit score to be less than or equal to `q`',
inline: true, inline: true,
}, },
{ {
name: '`cfq` or `cf=q` [Optional]', name: '`cs>q` [Optional]',
value: 'Changes crit fail to `q`', value: 'Changes crit score to be greater than or equal to `q`',
inline: true, inline: true,
}, },
{ {
name: '`cf<q` [Optional]', name: '`cfq` or `cf=q` [Optional]',
value: 'Changes crit fail to be less than or equal to `q`', value: 'Changes crit fail to `q`',
inline: true, inline: true,
}, },
{ {
name: '`cf>q` [Optional]', name: '`cf<q` [Optional]',
value: 'Changes crit fail to be greater than or equal to `q`', value: 'Changes crit fail to be less than or equal to `q`',
inline: true, inline: true,
}, },
{ {
name: '`!` [Optional]', name: '`cf>q` [Optional]',
value: 'Exploding, rolls another `dy` for every crit success', value: 'Changes crit fail to be greater than or equal to `q`',
inline: true, inline: true,
}, },
{ {
name: '`!o` [Optional]', name: '`!` [Optional]',
value: 'Exploding Once, rolls one `dy` for each original crit success', value: 'Exploding, rolls another `dy` for every crit success',
inline: true, inline: true,
}, },
{ {
name: '`!p` [Optional]', name: '`!o` [Optional]',
value: 'Penetrating Explosion, rolls one `dy` for each crit success, but subtracts one from each resulting explosion', value: 'Exploding Once, rolls one `dy` for each original crit success',
inline: true, inline: true,
}, },
{ {
name: '`!!` [Optional]', name: '`!p` [Optional]',
value: 'Compounding Explosion, rolls one `dy` for each crit success, but adds the resulting explosion to the die that caused this explosion', value: 'Penetrating Explosion, rolls one `dy` for each crit success, but subtracts one from each resulting explosion',
inline: true, inline: true,
}, },
{ {
name: '`!=u` [Optional]', name: '`!!` [Optional]',
value: 'Explode on `u`, rolls another `dy` for every die that lands on `u`', value: 'Compounding Explosion, rolls one `dy` for each crit success, but adds the resulting explosion to the die that caused this explosion',
inline: true, inline: true,
}, },
{ {
name: '`!>u` [Optional]', name: '`!=u` [Optional]',
value: 'Explode on `u` and greater, rolls another `dy` for every die that lands on `u` or greater', value: 'Explode on `u`, rolls another `dy` for every die that lands on `u`',
inline: true, inline: true,
}, },
], {
}, name: '`!>u` [Optional]',
{ value: 'Explode on `u` and greater, rolls another `dy` for every die that lands on `u` or greater',
color: infoColor2, inline: true,
fields: [ },
{ ],
name: '`!<u` [Optional]', },
value: 'Explode on `u` and under, rolls another `dy` for every die that lands on `u` or less', {
inline: true, color: infoColor2,
}, fields: [
{ {
name: '`!o=u` [Optional]', name: '`!<u` [Optional]',
value: 'Explodes Once on `u`, rolls another `dy` for each original die that landed on `u`', value: 'Explode on `u` and under, rolls another `dy` for every die that lands on `u` or less',
inline: true, inline: true,
}, },
{ {
name: '`!o>u` [Optional]', name: '`!o=u` [Optional]',
value: 'Explode Once on `u` and greater, rolls another `dy` for each original die that landed on `u` or greater', value: 'Explodes Once on `u`, rolls another `dy` for each original die that landed on `u`',
inline: true, inline: true,
}, },
{ {
name: '`!o<u` [Optional]', name: '`!o>u` [Optional]',
value: 'Explode Once on `u` and under, rolls another `dy` for each original die that landed on `u` or less', value: 'Explode Once on `u` and greater, rolls another `dy` for each original die that landed on `u` or greater',
inline: true, inline: true,
}, },
{ {
name: '`!p=u` [Optional]', name: '`!o<u` [Optional]',
value: 'Penetrating Explosion on `u`, rolls one `dy` for each die that lands on `u`, but subtracts one from each resulting explosion', value: 'Explode Once on `u` and under, rolls another `dy` for each original die that landed on `u` or less',
inline: true, inline: true,
}, },
{ {
name: '`!p>u` [Optional]', name: '`!p=u` [Optional]',
value: 'Penetrating Explosion on `u` and greater, rolls one `dy` for each die that lands on `u` or greater, but subtracts one from each resulting explosion', value: 'Penetrating Explosion on `u`, rolls one `dy` for each die that lands on `u`, but subtracts one from each resulting explosion',
inline: true, inline: true,
}, },
{ {
name: '`!p<u` [Optional]', name: '`!p>u` [Optional]',
value: 'Penetrating Explosion on `u` and under, rolls one `dy` for each die that lands on `u` or under, but subtracts one from each resulting explosion', value:
inline: true, 'Penetrating Explosion on `u` and greater, rolls one `dy` for each die that lands on `u` or greater, but subtracts one from each resulting explosion',
}, inline: true,
{ },
name: '`!!=u` [Optional]', {
value: 'Compounding Explosion on `u`, rolls one `dy` for each die that lands on `u`, but adds the resulting explosion to the die that caused this explosion', name: '`!p<u` [Optional]',
inline: true, value:
}, 'Penetrating Explosion on `u` and under, rolls one `dy` for each die that lands on `u` or under, but subtracts one from each resulting explosion',
{ inline: true,
name: '`!!>u` [Optional]', },
value: 'Compounding Explosion on `u` and greater, rolls one `dy` for each die that lands on `u` or greater, but adds the resulting explosion to the die that caused this explosion', {
inline: true, name: '`!!=u` [Optional]',
}, value:
{ 'Compounding Explosion on `u`, rolls one `dy` for each die that lands on `u`, but adds the resulting explosion to the die that caused this explosion',
name: '`!!<u` [Optional]', inline: true,
value: 'Compounding Explosion on `u` and under, rolls one `dy` for each die that lands on `u` or under, but adds the resulting explosion to the die that caused this explosion', },
inline: true, {
}, name: '`!!>u` [Optional]',
], value:
}, 'Compounding Explosion on `u` and greater, rolls one `dy` for each die that lands on `u` or greater, but adds the resulting explosion to the die that caused this explosion',
{ inline: true,
color: infoColor1, },
title: 'Custom Dice Options', {
fields: [ name: '`!!<u` [Optional]',
{ value:
name: 'CWOD Rolling', 'Compounding Explosion on `u` and under, rolls one `dy` for each die that lands on `u` or under, but adds the resulting explosion to the die that caused this explosion',
value: `\`${config.prefix}xcwody${config.postfix}\` inline: true,
},
],
},
{
color: infoColor1,
title: 'Custom Dice Options',
fields: [
{
name: 'CWOD Rolling',
value: `\`${config.prefix}xcwody${config.postfix}\`
\`x\` - Number of CWOD dice to roll \`x\` - Number of CWOD dice to roll
\`y\` - Difficulty to roll at`, \`y\` - Difficulty to roll at`,
inline: true, inline: true,
}, },
{ {
name: 'OVA Rolling', name: 'OVA Rolling',
value: `\`${config.prefix}xovady${config.postfix}\` value: `\`${config.prefix}xovady${config.postfix}\`
\`x\` - Number of OVA dice to roll \`x\` - Number of OVA dice to roll
\`y\` - Size of the die to roll (defaults to 6 if omitted)`, \`y\` - Size of the die to roll (defaults to 6 if omitted)`,
inline: true, inline: true,
}, },
], ],
}, },
{ {
color: successColor, color: successColor,
title: 'Results Formatting:', title: 'Results Formatting:',
description: 'The results have some formatting applied on them to provide details on what happened during this roll.', description: 'The results have some formatting applied on them to provide details on what happened during this roll.',
fields: [ fields: [
{ {
name: 'Bold', name: 'Bold',
value: 'Critical successes will be **bolded**.', value: 'Critical successes will be **bolded**.',
inline: true, inline: true,
}, },
{ {
name: 'Underline', name: 'Underline',
value: 'Critical fails will be __underlined__.', value: 'Critical fails will be __underlined__.',
inline: true, inline: true,
}, },
{ {
name: 'Strikethrough', name: 'Strikethrough',
value: 'Rolls that were dropped or rerolled ~~crossed out~~.', value: 'Rolls that were dropped or rerolled ~~crossed out~~.',
inline: true, inline: true,
}, },
{ {
name: 'Exclamation mark (`!`)', name: 'Exclamation mark (`!`)',
value: 'Rolls that were caused by an explosion have an exclamation mark (`!`) after them.', value: 'Rolls that were caused by an explosion have an exclamation mark (`!`) after them.',
inline: true, inline: true,
}, },
], ],
}, },
], ],
}).catch((e: Error) => utils.commonLoggers.messageSendError('rollHelp.ts:247', message, e)); })
.catch((e: Error) => utils.commonLoggers.messageSendError('rollHelp.ts:247', message, e));
}; };

View File

@ -1,36 +1,49 @@
import { dbClient, queries } from '../db.ts'; import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import { import {
// Discordeno deps // Discordeno deps
cache, cache,
cacheHandlers, cacheHandlers,
DiscordenoMessage, DiscordenoMessage,
} from '../../deps.ts'; } from '../../deps.ts';
import { generateStats } from '../commandUtils.ts'; import { generateStats } from '../commandUtils.ts';
import { compilingStats } from '../commonEmbeds.ts'; import { compilingStats } from '../commonEmbeds.ts';
import utils from '../utils.ts'; import utils from '../utils.ts';
export const stats = async (message: DiscordenoMessage) => { export const stats = async (message: DiscordenoMessage) => {
// Light telemetry to see how many times a command is being run // Light telemetry to see how many times a command is being run
dbClient.execute(queries.callIncCnt('stats')).catch((e) => utils.commonLoggers.dbError('stats.ts:14', 'call sproc INC_CNT on', e)); dbClient.execute(queries.callIncCnt('stats')).catch((e) => utils.commonLoggers.dbError('stats.ts:14', 'call sproc INC_CNT on', e));
try { try {
const m = await message.send(compilingStats); const m = await message.send(compilingStats);
// Calculate how many times commands have been run // Calculate how many times commands have been run
const rollQuery = await dbClient.query(`SELECT count, hourlyRate FROM command_cnt WHERE command = "roll";`).catch((e) => utils.commonLoggers.dbError('stats.ts:23', 'query', e)); const rollQuery = await dbClient
const totalQuery = await dbClient.query(`SELECT SUM(count) as count, SUM(hourlyRate) as hourlyRate FROM command_cnt;`).catch((e) => utils.commonLoggers.dbError('stats.ts:24', 'query', e)); .query(`SELECT count, hourlyRate FROM command_cnt WHERE command = "roll";`)
const rolls = BigInt(rollQuery[0].count); .catch((e) => utils.commonLoggers.dbError('stats.ts:23', 'query', e));
const rollRate = parseFloat(rollQuery[0].hourlyRate); const totalQuery = await dbClient
const total = BigInt(totalQuery[0].count); .query(`SELECT SUM(count) as count, SUM(hourlyRate) as hourlyRate FROM command_cnt;`)
const totalRate = parseFloat(totalQuery[0].hourlyRate); .catch((e) => utils.commonLoggers.dbError('stats.ts:24', 'query', e));
const rolls = BigInt(rollQuery[0].count);
const rollRate = parseFloat(rollQuery[0].hourlyRate);
const total = BigInt(totalQuery[0].count);
const totalRate = parseFloat(totalQuery[0].hourlyRate);
const cachedGuilds = await cacheHandlers.size('guilds'); const cachedGuilds = await cacheHandlers.size('guilds');
const cachedChannels = await cacheHandlers.size('channels'); const cachedChannels = await cacheHandlers.size('channels');
const cachedMembers = await cacheHandlers.size('members'); const cachedMembers = await cacheHandlers.size('members');
m.edit(generateStats(cachedGuilds + cache.dispatchedGuildIds.size, cachedChannels + cache.dispatchedChannelIds.size, cachedMembers, rolls, total - rolls, rollRate, totalRate - rollRate)).catch(( m.edit(
e: Error, generateStats(
) => utils.commonLoggers.messageEditError('stats.ts:38', m, e)); cachedGuilds + cache.dispatchedGuildIds.size,
} catch (e) { cachedChannels + cache.dispatchedChannelIds.size,
utils.commonLoggers.messageSendError('stats.ts:41', message, e); cachedMembers,
} rolls,
total - rolls,
rollRate,
totalRate - rollRate
)
).catch((e: Error) => utils.commonLoggers.messageEditError('stats.ts:38', m, e));
} catch (e) {
utils.commonLoggers.messageSendError('stats.ts:41', message, e);
}
}; };

View File

@ -1,20 +1,25 @@
import config from '../../config.ts'; import config from '../../config.ts';
import { dbClient, queries } from '../db.ts'; import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import { import {
// Discordeno deps // Discordeno deps
DiscordenoMessage, DiscordenoMessage,
} from '../../deps.ts'; } from '../../deps.ts';
import { infoColor1 } from '../commandUtils.ts'; import { infoColor1 } from '../commandUtils.ts';
import utils from '../utils.ts'; import utils from '../utils.ts';
export const version = (message: DiscordenoMessage) => { export const version = (message: DiscordenoMessage) => {
// Light telemetry to see how many times a command is being run // Light telemetry to see how many times a command is being run
dbClient.execute(queries.callIncCnt('version')).catch((e) => utils.commonLoggers.dbError('version.ts:15', 'call sproc INC_CNT on', e)); dbClient.execute(queries.callIncCnt('version')).catch((e) => utils.commonLoggers.dbError('version.ts:15', 'call sproc INC_CNT on', e));
message.send({ message
embeds: [{ .send({
color: infoColor1, embeds: [
title: `My current version is ${config.version}`, {
}], color: infoColor1,
}).catch((e: Error) => utils.commonLoggers.messageSendError('version.ts:24', message, e)); title: `My current version is ${config.version}`,
},
],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('version.ts:24', message, e));
}; };

View File

@ -1,30 +0,0 @@
import config from '../config.ts';
import { Client } from '../deps.ts';
import { LOCALMODE } from '../flags.ts';
type UserIdObj = {
userid: bigint;
};
export const dbClient = await new Client().connect({
hostname: LOCALMODE ? config.db.localhost : config.db.host,
port: config.db.port,
db: config.db.name,
username: config.db.username,
password: config.db.password,
});
// List of userIds who have requested that the bot ignore them
export const ignoreList: Array<bigint> = [];
const dbIgnoreList = await dbClient.query('SELECT * FROM ignore_list');
dbIgnoreList.forEach((userIdObj: UserIdObj) => {
ignoreList.push(userIdObj.userid);
});
export const weekDays = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
export const queries = {
insertRollLogCmd: (api: number, error: number) => `INSERT INTO roll_log(input,result,resultid,api,error) values(?,?,?,${api},${error})`,
callIncCnt: (cmdName: string) => `CALL INC_CNT("${cmdName}");`,
callIncHeatmap: (dateObj: Date) => `CALL INC_HEATMAP("${weekDays[dateObj.getDay()]}", ${dateObj.getHours()});`,
};

14
src/db/client.ts Normal file
View File

@ -0,0 +1,14 @@
import config from '../../config.ts';
import { Client } from '../../deps.ts';
import { LOCALMODE } from '../../flags.ts';
const dbClient = await new Client().connect({
hostname: LOCALMODE ? config.db.localhost : config.db.host,
port: config.db.port,
db: config.db.name,
username: config.db.username,
password: config.db.password,
debug: true,
});
export default dbClient;

20
src/db/common.ts Normal file
View File

@ -0,0 +1,20 @@
import dbClient from './client.ts';
type UserIdObj = {
userid: bigint;
};
// List of userIds who have requested that the bot ignore them
export const ignoreList: Array<bigint> = [];
const dbIgnoreList = await dbClient.query('SELECT * FROM ignore_list');
dbIgnoreList.forEach((userIdObj: UserIdObj) => {
ignoreList.push(userIdObj.userid);
});
export const weekDays = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
export const queries = {
insertRollLogCmd: (api: number, error: number) => `INSERT INTO roll_log(input,result,resultid,api,error) values(?,?,?,${api},${error})`,
callIncCnt: (cmdName: string) => `CALL INC_CNT("${cmdName}");`,
callIncHeatmap: (dateObj: Date) => `CALL INC_HEATMAP("${weekDays[dateObj.getDay()]}", ${dateObj.getHours()});`,
};

View File

@ -1,83 +1,89 @@
import config from '../../../config.ts'; import config from '../../../config.ts';
import { dbClient } from '../../db.ts'; import dbClient from '../../db/client.ts';
import { import {
// nanoid deps // nanoid deps
nanoid, nanoid,
// Discordeno deps // Discordeno deps
sendMessage, sendMessage,
} from '../../../deps.ts'; } from '../../../deps.ts';
import { generateApiDeleteEmail } from '../../commandUtils.ts'; import { generateApiDeleteEmail } from '../../commandUtils.ts';
import utils from '../../utils.ts'; import utils from '../../utils.ts';
import stdResp from '../stdResponses.ts'; import stdResp from '../stdResponses.ts';
export const apiKeyDelete = async (requestEvent: Deno.RequestEvent, query: Map<string, string>, apiUserid: BigInt, apiUserEmail: string, apiUserDelCode: string) => { export const apiKeyDelete = async (
if (query.has('user') && ((query.get('user') || '').length > 0) && query.has('email') && ((query.get('email') || '').length > 0)) { requestEvent: Deno.RequestEvent,
if (apiUserid === BigInt(query.get('user') || '0') && apiUserEmail === query.get('email')) { query: Map<string, string>,
if (query.has('code') && ((query.get('code') || '').length > 0)) { apiUserid: BigInt,
if ((query.get('code') || '') === apiUserDelCode) { apiUserEmail: string,
// User has recieved their delete code and we need to delete the account now apiUserDelCode: string
let erroredOut = false; ) => {
if (query.has('user') && (query.get('user') || '').length > 0 && query.has('email') && (query.get('email') || '').length > 0) {
if (apiUserid === BigInt(query.get('user') || '0') && apiUserEmail === query.get('email')) {
if (query.has('code') && (query.get('code') || '').length > 0) {
if ((query.get('code') || '') === apiUserDelCode) {
// User has recieved their delete code and we need to delete the account now
let erroredOut = false;
await dbClient.execute('DELETE FROM allowed_channels WHERE userid = ?', [apiUserid]).catch((e) => { await dbClient.execute('DELETE FROM allowed_channels WHERE userid = ?', [apiUserid]).catch((e) => {
utils.commonLoggers.dbError('apiKeyDelete.ts:25', 'insert into', e); utils.commonLoggers.dbError('apiKeyDelete.ts:25', 'insert into', e);
requestEvent.respondWith(stdResp.InternalServerError('Channel Clean Failed.')); requestEvent.respondWith(stdResp.InternalServerError('Channel Clean Failed.'));
erroredOut = true; erroredOut = true;
}); });
if (erroredOut) { if (erroredOut) {
return; return;
} }
await dbClient.execute('DELETE FROM all_keys WHERE userid = ?', [apiUserid]).catch((e) => { await dbClient.execute('DELETE FROM all_keys WHERE userid = ?', [apiUserid]).catch((e) => {
utils.commonLoggers.dbError('apiKeyDelete.ts:34', 'delete from', e); utils.commonLoggers.dbError('apiKeyDelete.ts:34', 'delete from', e);
requestEvent.respondWith(stdResp.InternalServerError('Delete Key Failed.')); requestEvent.respondWith(stdResp.InternalServerError('Delete Key Failed.'));
erroredOut = true; erroredOut = true;
}); });
if (erroredOut) { if (erroredOut) {
return; return;
} else { } else {
// Send OK as response to indicate key deletion was successful // Send OK as response to indicate key deletion was successful
requestEvent.respondWith(stdResp.OK('You have been removed from the DB, Goodbye.')); requestEvent.respondWith(stdResp.OK('You have been removed from the DB, Goodbye.'));
return; return;
} }
} else { } else {
// Alert API user that they shouldn't be doing this // Alert API user that they shouldn't be doing this
requestEvent.respondWith(stdResp.Forbidden('Invalid Delete Code.')); requestEvent.respondWith(stdResp.Forbidden('Invalid Delete Code.'));
} }
} else { } else {
// User does not have their delete code yet, so we need to generate one and email it to them // User does not have their delete code yet, so we need to generate one and email it to them
const deleteCode = await nanoid(10); const deleteCode = await nanoid(10);
let erroredOut = false; let erroredOut = false;
// Execute the DB modification // Execute the DB modification
await dbClient.execute('UPDATE all_keys SET deleteCode = ? WHERE userid = ?', [deleteCode, apiUserid]).catch((e) => { await dbClient.execute('UPDATE all_keys SET deleteCode = ? WHERE userid = ?', [deleteCode, apiUserid]).catch((e) => {
utils.commonLoggers.dbError('apiKeyDelete.ts:57', 'update', e); utils.commonLoggers.dbError('apiKeyDelete.ts:57', 'update', e);
requestEvent.respondWith(stdResp.InternalServerError('Delete Code Failed')); requestEvent.respondWith(stdResp.InternalServerError('Delete Code Failed'));
erroredOut = true; erroredOut = true;
}); });
if (erroredOut) { if (erroredOut) {
return; return;
} }
// "Send" the email // "Send" the email
await sendMessage(config.api.email, generateApiDeleteEmail(apiUserEmail, deleteCode)).catch(() => { await sendMessage(config.api.email, generateApiDeleteEmail(apiUserEmail, deleteCode)).catch(() => {
requestEvent.respondWith(stdResp.InternalServerError('Failed to send email.')); requestEvent.respondWith(stdResp.InternalServerError('Failed to send email.'));
erroredOut = true; erroredOut = true;
}); });
if (erroredOut) { if (erroredOut) {
return; return;
} else { } else {
// Send API key as response // Send API key as response
requestEvent.respondWith(stdResp.FailedDependency('Please look for an email containing a Delete Key and run this query again with said key.')); requestEvent.respondWith(stdResp.FailedDependency('Please look for an email containing a Delete Key and run this query again with said key.'));
return; return;
} }
} }
} else { } else {
// Alert API user that they shouldn't be doing this // Alert API user that they shouldn't be doing this
requestEvent.respondWith(stdResp.Forbidden('You can only delete your own key.')); requestEvent.respondWith(stdResp.Forbidden('You can only delete your own key.'));
} }
} else { } else {
// Alert API user that they messed up // Alert API user that they messed up
requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams)); requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams));
} }
}; };

View File

@ -1,35 +1,35 @@
import { dbClient } from '../../db.ts'; import dbClient from '../../db/client.ts';
import stdResp from '../stdResponses.ts'; import stdResp from '../stdResponses.ts';
import utils from '../../utils.ts'; import utils from '../../utils.ts';
export const apiChannel = async (requestEvent: Deno.RequestEvent, query: Map<string, string>, apiUserid: BigInt) => { export const apiChannel = async (requestEvent: Deno.RequestEvent, query: Map<string, string>, apiUserid: BigInt) => {
if (query.has('user') && ((query.get('user') || '').length > 0)) { if (query.has('user') && (query.get('user') || '').length > 0) {
if (apiUserid === BigInt(query.get('user') || '0')) { if (apiUserid === BigInt(query.get('user') || '0')) {
// Flag to see if there is an error inside the catch // Flag to see if there is an error inside the catch
let erroredOut = false; let erroredOut = false;
// Get all channels userid has authorized // Get all channels userid has authorized
const dbAllowedChannelQuery = await dbClient.query('SELECT * FROM allowed_channels WHERE userid = ?', [apiUserid]).catch((e) => { const dbAllowedChannelQuery = await dbClient.query('SELECT * FROM allowed_channels WHERE userid = ?', [apiUserid]).catch((e) => {
utils.commonLoggers.dbError('apiChannel.ts', 'query', e); utils.commonLoggers.dbError('apiChannel.ts', 'query', e);
requestEvent.respondWith(stdResp.InternalServerError('Failed to get channels.')); requestEvent.respondWith(stdResp.InternalServerError('Failed to get channels.'));
erroredOut = true; erroredOut = true;
}); });
if (erroredOut) { if (erroredOut) {
return; return;
} else { } else {
// Customized strinification to handle BigInts correctly // Customized strinification to handle BigInts correctly
const returnChannels = JSON.stringify(dbAllowedChannelQuery, (_key, value) => (typeof value === 'bigint' ? value.toString() : value)); const returnChannels = JSON.stringify(dbAllowedChannelQuery, (_key, value) => (typeof value === 'bigint' ? value.toString() : value));
// Send channel list as response // Send channel list as response
requestEvent.respondWith(stdResp.OK(returnChannels)); requestEvent.respondWith(stdResp.OK(returnChannels));
return; return;
} }
} else { } else {
// Alert API user that they shouldn't be doing this // Alert API user that they shouldn't be doing this
requestEvent.respondWith(stdResp.Forbidden('You can only view your own channels.')); requestEvent.respondWith(stdResp.Forbidden('You can only view your own channels.'));
} }
} else { } else {
// Alert API user that they messed up // Alert API user that they messed up
requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams)); requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams));
} }
}; };

View File

@ -1,52 +1,52 @@
import config from '../../../config.ts'; import config from '../../../config.ts';
import { dbClient } from '../../db.ts'; import dbClient from '../../db/client.ts';
import { import {
// nanoid deps // nanoid deps
nanoid, nanoid,
// Discordeno deps // Discordeno deps
sendMessage, sendMessage,
} from '../../../deps.ts'; } from '../../../deps.ts';
import { generateApiKeyEmail } from '../../commandUtils.ts'; import { generateApiKeyEmail } from '../../commandUtils.ts';
import utils from '../../utils.ts'; import utils from '../../utils.ts';
import stdResp from '../stdResponses.ts'; import stdResp from '../stdResponses.ts';
export const apiKey = async (requestEvent: Deno.RequestEvent, query: Map<string, string>) => { export const apiKey = async (requestEvent: Deno.RequestEvent, query: Map<string, string>) => {
if ((query.has('user') && ((query.get('user') || '').length > 0)) && (query.has('email') && ((query.get('email') || '').length > 0))) { if (query.has('user') && (query.get('user') || '').length > 0 && query.has('email') && (query.get('email') || '').length > 0) {
// Generate new secure key // Generate new secure key
const newKey = await nanoid(25); const newKey = await nanoid(25);
// Flag to see if there is an error inside the catch // Flag to see if there is an error inside the catch
let erroredOut = false; let erroredOut = false;
// Insert new key/user pair into the db // Insert new key/user pair into the db
await dbClient.execute('INSERT INTO all_keys(userid,apiKey,email) values(?,?,?)', [BigInt(query.get('user') || '0'), newKey, (query.get('email') || '').toLowerCase()]).catch( await dbClient
(e) => { .execute('INSERT INTO all_keys(userid,apiKey,email) values(?,?,?)', [BigInt(query.get('user') || '0'), newKey, (query.get('email') || '').toLowerCase()])
utils.commonLoggers.dbError('apiKey.ts:27', 'insert into', e); .catch((e) => {
requestEvent.respondWith(stdResp.InternalServerError('Failed to store key.')); utils.commonLoggers.dbError('apiKey.ts:27', 'insert into', e);
erroredOut = true; requestEvent.respondWith(stdResp.InternalServerError('Failed to store key.'));
}, erroredOut = true;
); });
// Exit this case now if catch errored // Exit this case now if catch errored
if (erroredOut) { if (erroredOut) {
return; return;
} }
// "Send" the email // "Send" the email
await sendMessage(config.api.email, generateApiKeyEmail(query.get('email') || 'no email', newKey)).catch(() => { await sendMessage(config.api.email, generateApiKeyEmail(query.get('email') || 'no email', newKey)).catch(() => {
requestEvent.respondWith(stdResp.InternalServerError('Failed to send email.')); requestEvent.respondWith(stdResp.InternalServerError('Failed to send email.'));
erroredOut = true; erroredOut = true;
}); });
if (erroredOut) { if (erroredOut) {
return; return;
} else { } else {
// Send basic OK to indicate key has been sent // Send basic OK to indicate key has been sent
requestEvent.respondWith(stdResp.OK('Email Sent.')); requestEvent.respondWith(stdResp.OK('Email Sent.'));
return; return;
} }
} else { } else {
// Alert API user that they messed up // Alert API user that they messed up
requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams)); requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams));
} }
}; };

View File

@ -1,42 +1,42 @@
import config from '../../../config.ts'; import config from '../../../config.ts';
import { dbClient } from '../../db.ts'; import dbClient from '../../db/client.ts';
import { import {
// nanoid deps // nanoid deps
nanoid, nanoid,
} from '../../../deps.ts'; } from '../../../deps.ts';
import stdResp from '../stdResponses.ts'; import stdResp from '../stdResponses.ts';
import utils from '../../utils.ts'; import utils from '../../utils.ts';
export const apiKeyAdmin = async (requestEvent: Deno.RequestEvent, query: Map<string, string>, apiUserid: BigInt) => { export const apiKeyAdmin = async (requestEvent: Deno.RequestEvent, query: Map<string, string>, apiUserid: BigInt) => {
if ((query.has('user') && ((query.get('user') || '').length > 0)) && (query.has('a') && ((query.get('a') || '').length > 0))) { if (query.has('user') && (query.get('user') || '').length > 0 && query.has('a') && (query.get('a') || '').length > 0) {
if (apiUserid === config.api.admin && apiUserid === BigInt(query.get('a') || '0')) { if (apiUserid === config.api.admin && apiUserid === BigInt(query.get('a') || '0')) {
// Generate new secure key // Generate new secure key
const newKey = await nanoid(25); const newKey = await nanoid(25);
// Flag to see if there is an error inside the catch // Flag to see if there is an error inside the catch
let erroredOut = false; let erroredOut = false;
// Insert new key/user pair into the db // Insert new key/user pair into the db
await dbClient.execute('INSERT INTO all_keys(userid,apiKey) values(?,?)', [apiUserid, newKey]).catch((e) => { await dbClient.execute('INSERT INTO all_keys(userid,apiKey) values(?,?)', [apiUserid, newKey]).catch((e) => {
utils.commonLoggers.dbError('apiKeyAdmin.ts:24', 'insert into', e); utils.commonLoggers.dbError('apiKeyAdmin.ts:24', 'insert into', e);
requestEvent.respondWith(stdResp.InternalServerError('Failed to store key.')); requestEvent.respondWith(stdResp.InternalServerError('Failed to store key.'));
erroredOut = true; erroredOut = true;
}); });
// Exit this case now if catch errored // Exit this case now if catch errored
if (erroredOut) { if (erroredOut) {
return; return;
} else { } else {
// Send API key as response // Send API key as response
requestEvent.respondWith(stdResp.OK(JSON.stringify({ 'key': newKey, 'userid': query.get('user') }))); requestEvent.respondWith(stdResp.OK(JSON.stringify({ key: newKey, userid: query.get('user') })));
return; return;
} }
} else { } else {
// Only allow the db admin to use this API // Only allow the db admin to use this API
requestEvent.respondWith(stdResp.Forbidden(stdResp.Strings.restricted)); requestEvent.respondWith(stdResp.Forbidden(stdResp.Strings.restricted));
} }
} else { } else {
// Alert API user that they messed up // Alert API user that they messed up
requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams)); requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams));
} }
}; };

View File

@ -1,11 +1,11 @@
import config from '../../../config.ts'; import config from '../../../config.ts';
import { dbClient, queries } from '../../db.ts'; import dbClient from '../../db/client.ts';
import { import {
// Discordeno deps // Discordeno deps
cache, cache,
// Log4Deno deps // Log4Deno deps
log, log,
LT, LT,
} from '../../../deps.ts'; } from '../../../deps.ts';
import { QueuedRoll, RollModifiers } from '../../mod.d.ts'; import { QueuedRoll, RollModifiers } from '../../mod.d.ts';
import utils from '../../utils.ts'; import utils from '../../utils.ts';
@ -15,107 +15,116 @@ import stdResp from '../stdResponses.ts';
const apiWarning = `The following roll was conducted using my built in API. If someone in this channel did not request this roll, please report API abuse here: <${config.api.supportURL}>`; const apiWarning = `The following roll was conducted using my built in API. If someone in this channel did not request this roll, please report API abuse here: <${config.api.supportURL}>`;
export const apiRoll = async (requestEvent: Deno.RequestEvent, query: Map<string, string>, apiUserid: BigInt) => { export const apiRoll = async (requestEvent: Deno.RequestEvent, query: Map<string, string>, apiUserid: BigInt) => {
// Make sure query contains all the needed parts // Make sure query contains all the needed parts
if ( if (
(query.has('rollstr') && ((query.get('rollstr') || '').length > 0)) && (query.has('channel') && ((query.get('channel') || '').length > 0)) && query.has('rollstr') &&
(query.has('user') && ((query.get('user') || '').length > 0)) (query.get('rollstr') || '').length > 0 &&
) { query.has('channel') &&
if (query.has('n') && query.has('m')) { (query.get('channel') || '').length > 0 &&
// Alert API user that they shouldn't be doing this query.has('user') &&
requestEvent.respondWith(stdResp.BadRequest('Cannot have both \'n\' and \'m\'.')); (query.get('user') || '').length > 0
return; ) {
} if (query.has('n') && query.has('m')) {
// Alert API user that they shouldn't be doing this
requestEvent.respondWith(stdResp.BadRequest("Cannot have both 'n' and 'm'."));
return;
}
// Check if user is authenticated to use this endpoint // Check if user is authenticated to use this endpoint
let authorized = false; let authorized = false;
let hideWarn = false; let hideWarn = false;
// Check if the db has the requested userid/channelid combo, and that the requested userid matches the userid linked with the api key // Check if the db has the requested userid/channelid combo, and that the requested userid matches the userid linked with the api key
const dbChannelQuery = await dbClient.query('SELECT active, banned FROM allowed_channels WHERE userid = ? AND channelid = ?', [apiUserid, BigInt(query.get('channel') || '0')]); const dbChannelQuery = await dbClient.query('SELECT active, banned FROM allowed_channels WHERE userid = ? AND channelid = ?', [
if (dbChannelQuery.length === 1 && (apiUserid === BigInt(query.get('user') || '0')) && dbChannelQuery[0].active && !dbChannelQuery[0].banned) { apiUserid,
// Get the guild from the channel and make sure user is in said guild BigInt(query.get('channel') || '0'),
const guild = cache.channels.get(BigInt(query.get('channel') || ''))?.guild; ]);
if (guild && guild.members.get(BigInt(query.get('user') || ''))?.id) { if (dbChannelQuery.length === 1 && apiUserid === BigInt(query.get('user') || '0') && dbChannelQuery[0].active && !dbChannelQuery[0].banned) {
const dbGuildQuery = await dbClient.query('SELECT active, banned, hidewarn FROM allowed_guilds WHERE guildid = ? AND channelid = ?', [ // Get the guild from the channel and make sure user is in said guild
guild.id, const guild = cache.channels.get(BigInt(query.get('channel') || ''))?.guild;
BigInt(query.get('channel') || '0'), if (guild && guild.members.get(BigInt(query.get('user') || ''))?.id) {
]); const dbGuildQuery = await dbClient.query('SELECT active, banned, hidewarn FROM allowed_guilds WHERE guildid = ? AND channelid = ?', [
guild.id,
BigInt(query.get('channel') || '0'),
]);
// Make sure guild allows API rolls // Make sure guild allows API rolls
if (dbGuildQuery.length === 1 && dbGuildQuery[0].active && !dbGuildQuery[0].banned) { if (dbGuildQuery.length === 1 && dbGuildQuery[0].active && !dbGuildQuery[0].banned) {
authorized = true; authorized = true;
hideWarn = dbGuildQuery[0].hidewarn; hideWarn = dbGuildQuery[0].hidewarn;
} }
} }
} }
if (authorized) { if (authorized) {
// Rest of this command is in a try-catch to protect all sends/edits from erroring out // Rest of this command is in a try-catch to protect all sends/edits from erroring out
try { try {
// Make sure rollCmd is not undefined // Make sure rollCmd is not undefined
let rollCmd = query.get('rollstr') || ''; let rollCmd = query.get('rollstr') || '';
const originalCommand = query.get('rollstr'); const originalCommand = query.get('rollstr');
if (rollCmd.length === 0) { if (rollCmd.length === 0) {
// Alert API user that they messed up // Alert API user that they messed up
requestEvent.respondWith(stdResp.BadRequest('rollCmd is required.')); requestEvent.respondWith(stdResp.BadRequest('rollCmd is required.'));
// Always log API rolls for abuse detection // Always log API rolls for abuse detection
dbClient.execute(queries.insertRollLogCmd(1, 1), [originalCommand, 'EmptyInput', null]).catch((e) => utils.commonLoggers.dbError('apiRoll.ts:65', 'insert', e)); dbClient
return; .execute(queries.insertRollLogCmd(1, 1), [originalCommand, 'EmptyInput', null])
} .catch((e) => utils.commonLoggers.dbError('apiRoll.ts:65', 'insert', e));
return;
}
if (query.has('o') && (query.get('o')?.toLowerCase() !== 'd' && query.get('o')?.toLowerCase() !== 'a')) { if (query.has('o') && query.get('o')?.toLowerCase() !== 'd' && query.get('o')?.toLowerCase() !== 'a') {
// Alert API user that they messed up // Alert API user that they messed up
requestEvent.respondWith(stdResp.BadRequest('Order must be set to \'a\' or \'d\'.')); requestEvent.respondWith(stdResp.BadRequest("Order must be set to 'a' or 'd'."));
// Always log API rolls for abuse detection // Always log API rolls for abuse detection
dbClient.execute(queries.insertRollLogCmd(1, 1), [originalCommand, 'BadOrder', null]).catch((e) => utils.commonLoggers.dbError('apiRoll.ts:66', 'insert', e)); dbClient
return; .execute(queries.insertRollLogCmd(1, 1), [originalCommand, 'BadOrder', null])
} .catch((e) => utils.commonLoggers.dbError('apiRoll.ts:66', 'insert', e));
return;
}
// Clip off the leading prefix. API calls must be formatted with a prefix at the start to match how commands are sent in Discord // Clip off the leading prefix. API calls must be formatted with a prefix at the start to match how commands are sent in Discord
rollCmd = rollCmd.substring(rollCmd.indexOf(config.prefix) + 2).replace(/%20/g, ' '); rollCmd = rollCmd.substring(rollCmd.indexOf(config.prefix) + 2).replace(/%20/g, ' ');
const modifiers: RollModifiers = { const modifiers: RollModifiers = {
noDetails: query.has('nd'), noDetails: query.has('nd'),
superNoDetails: query.has('snd'), superNoDetails: query.has('snd'),
spoiler: query.has('s') ? '||' : '', spoiler: query.has('s') ? '||' : '',
maxRoll: query.has('m'), maxRoll: query.has('m'),
nominalRoll: query.has('n'), nominalRoll: query.has('n'),
gmRoll: query.has('gms'), gmRoll: query.has('gms'),
gms: query.has('gms') ? (query.get('gms') || '').split(',') : [], gms: query.has('gms') ? (query.get('gms') || '').split(',') : [],
order: query.has('o') ? (query.get('o')?.toLowerCase() || '') : '', order: query.has('o') ? query.get('o')?.toLowerCase() || '' : '',
count: query.has('c'), count: query.has('c'),
valid: true, valid: true,
apiWarn: hideWarn ? '' : apiWarning, apiWarn: hideWarn ? '' : apiWarning,
}; };
// Parse the roll and get the return text // Parse the roll and get the return text
await queueRoll( await queueRoll(<QueuedRoll>{
<QueuedRoll> { apiRoll: true,
apiRoll: true, api: { requestEvent, channelId: BigInt(query.get('channel') || '0'), userId: BigInt(query.get('user') || '') },
api: { requestEvent, channelId: BigInt(query.get('channel') || '0'), userId: BigInt(query.get('user') || '') }, rollCmd,
rollCmd, modifiers,
modifiers, originalCommand,
originalCommand, });
}, } catch (err) {
); // Handle any errors we missed
} catch (err) { log(LT.ERROR, `Unhandled Error: ${JSON.stringify(err)}`);
// Handle any errors we missed requestEvent.respondWith(stdResp.InternalServerError('Something went wrong.'));
log(LT.ERROR, `Unhandled Error: ${JSON.stringify(err)}`); }
requestEvent.respondWith(stdResp.InternalServerError('Something went wrong.')); } else {
} // Alert API user that they messed up
} else { requestEvent.respondWith(
// Alert API user that they messed up stdResp.Forbidden(
requestEvent.respondWith( `Verify you are a member of the guild you are sending this roll to. If you are, the ${config.name} may not have that registered, please send a message in the guild so ${config.name} can register this. This registration is temporary, so if you see this error again, just poke your server again.`
stdResp.Forbidden( )
`Verify you are a member of the guild you are sending this roll to. If you are, the ${config.name} may not have that registered, please send a message in the guild so ${config.name} can register this. This registration is temporary, so if you see this error again, just poke your server again.`, );
), }
); } else {
} // Alert API user that they shouldn't be doing this
} else { requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams));
// Alert API user that they shouldn't be doing this }
requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams));
}
}; };

View File

@ -1,19 +1,19 @@
import { import {
// httpd deps // httpd deps
Status, STATUS_CODE,
STATUS_TEXT, STATUS_TEXT,
} from '../../../deps.ts'; } from '../../../deps.ts';
export const heatmapPng = async (requestEvent: Deno.RequestEvent) => { export const heatmapPng = async (requestEvent: Deno.RequestEvent) => {
const file = Deno.readFileSync('./src/endpoints/gets/heatmap.png'); const file = Deno.readFileSync('./src/endpoints/gets/heatmap.png');
const imageHeaders = new Headers(); const imageHeaders = new Headers();
imageHeaders.append('Content-Type', 'image/png'); imageHeaders.append('Content-Type', 'image/png');
// Send basic OK to indicate key has been sent // Send basic OK to indicate key has been sent
requestEvent.respondWith( requestEvent.respondWith(
new Response(file, { new Response(file, {
status: Status.OK, status: STATUS_CODE.OK,
statusText: STATUS_TEXT[Status.OK], statusText: STATUS_TEXT[STATUS_CODE.OK],
headers: imageHeaders, headers: imageHeaders,
}), })
); );
}; };

View File

@ -1,34 +1,34 @@
import { dbClient } from '../../db.ts'; import dbClient from '../../db/client.ts';
import stdResp from '../stdResponses.ts'; import stdResp from '../stdResponses.ts';
import utils from '../../utils.ts'; import utils from '../../utils.ts';
export const apiChannelAdd = async (requestEvent: Deno.RequestEvent, query: Map<string, string>, apiUserid: BigInt) => { export const apiChannelAdd = async (requestEvent: Deno.RequestEvent, query: Map<string, string>, apiUserid: BigInt) => {
if ((query.has('user') && ((query.get('user') || '').length > 0)) && (query.has('channel') && ((query.get('channel') || '').length > 0))) { if (query.has('user') && (query.get('user') || '').length > 0 && query.has('channel') && (query.get('channel') || '').length > 0) {
if (apiUserid === BigInt(query.get('user') || '0')) { if (apiUserid === BigInt(query.get('user') || '0')) {
// Flag to see if there is an error inside the catch // Flag to see if there is an error inside the catch
let erroredOut = false; let erroredOut = false;
// Insert new user/channel pair into the db // Insert new user/channel pair into the db
await dbClient.execute('INSERT INTO allowed_channels(userid,channelid) values(?,?)', [apiUserid, BigInt(query.get('channel') || '0')]).catch((e) => { await dbClient.execute('INSERT INTO allowed_channels(userid,channelid) values(?,?)', [apiUserid, BigInt(query.get('channel') || '0')]).catch((e) => {
utils.commonLoggers.dbError('apiChannelAdd.ts:17', 'insert into', e); utils.commonLoggers.dbError('apiChannelAdd.ts:17', 'insert into', e);
requestEvent.respondWith(stdResp.InternalServerError('Failed to store channel.')); requestEvent.respondWith(stdResp.InternalServerError('Failed to store channel.'));
erroredOut = true; erroredOut = true;
}); });
// Exit this case now if catch errored // Exit this case now if catch errored
if (erroredOut) { if (erroredOut) {
return; return;
} else { } else {
// Send OK to indicate modification was successful // Send OK to indicate modification was successful
requestEvent.respondWith(stdResp.OK('Successfully added channel.')); requestEvent.respondWith(stdResp.OK('Successfully added channel.'));
return; return;
} }
} else { } else {
// Alert API user that they shouldn't be doing this // Alert API user that they shouldn't be doing this
requestEvent.respondWith(stdResp.Forbidden('You can only add channels to your key.')); requestEvent.respondWith(stdResp.Forbidden('You can only add channels to your key.'));
} }
} else { } else {
// Alert API user that they messed up // Alert API user that they messed up
requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams)); requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams));
} }
}; };

View File

@ -1,41 +1,44 @@
import { dbClient } from '../../db.ts'; import dbClient from '../../db/client.ts';
import stdResp from '../stdResponses.ts'; import stdResp from '../stdResponses.ts';
import utils from '../../utils.ts'; import utils from '../../utils.ts';
export const apiChannelManageActive = async (requestEvent: Deno.RequestEvent, query: Map<string, string>, apiUserid: BigInt, path: string) => { export const apiChannelManageActive = async (requestEvent: Deno.RequestEvent, query: Map<string, string>, apiUserid: BigInt, path: string) => {
if ((query.has('channel') && ((query.get('channel') || '').length > 0)) && (query.has('user') && ((query.get('user') || '').length > 0))) { if (query.has('channel') && (query.get('channel') || '').length > 0 && query.has('user') && (query.get('user') || '').length > 0) {
if (apiUserid === BigInt(query.get('user') || '0')) { if (apiUserid === BigInt(query.get('user') || '0')) {
// Flag to see if there is an error inside the catch // Flag to see if there is an error inside the catch
let value, erroredOut = false; let value,
erroredOut = false;
// Determine value to set // Determine value to set
if (path.toLowerCase().indexOf('de') > 0) { if (path.toLowerCase().indexOf('de') > 0) {
value = 0; value = 0;
} else { } else {
value = 1; value = 1;
} }
// Update the requested entry // Update the requested entry
await dbClient.execute('UPDATE allowed_channels SET active = ? WHERE userid = ? AND channelid = ?', [value, apiUserid, BigInt(query.get('channel') || '0')]).catch((e) => { await dbClient
utils.commonLoggers.dbError('apiChannelManageActive.ts:25', 'update', e); .execute('UPDATE allowed_channels SET active = ? WHERE userid = ? AND channelid = ?', [value, apiUserid, BigInt(query.get('channel') || '0')])
requestEvent.respondWith(stdResp.InternalServerError('Failed to update channel.')); .catch((e) => {
erroredOut = true; utils.commonLoggers.dbError('apiChannelManageActive.ts:25', 'update', e);
}); requestEvent.respondWith(stdResp.InternalServerError('Failed to update channel.'));
erroredOut = true;
});
// Exit this case now if catch errored // Exit this case now if catch errored
if (erroredOut) { if (erroredOut) {
return; return;
} else { } else {
// Send API key as response // Send API key as response
requestEvent.respondWith(stdResp.OK(`Successfully active to ${value}.`)); requestEvent.respondWith(stdResp.OK(`Successfully active to ${value}.`));
return; return;
} }
} else { } else {
// Alert API user that they shouldn't be doing this // Alert API user that they shouldn't be doing this
requestEvent.respondWith(stdResp.Forbidden('You can only manage your own channels.')); requestEvent.respondWith(stdResp.Forbidden('You can only manage your own channels.'));
} }
} else { } else {
// Alert API user that they messed up // Alert API user that they messed up
requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams)); requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams));
} }
}; };

View File

@ -1,45 +1,52 @@
import config from '../../../config.ts'; import config from '../../../config.ts';
import { dbClient } from '../../db.ts'; import dbClient from '../../db/client.ts';
import stdResp from '../stdResponses.ts'; import stdResp from '../stdResponses.ts';
import utils from '../../utils.ts'; import utils from '../../utils.ts';
export const apiChannelManageBan = async (requestEvent: Deno.RequestEvent, query: Map<string, string>, apiUserid: BigInt, path: string) => { export const apiChannelManageBan = async (requestEvent: Deno.RequestEvent, query: Map<string, string>, apiUserid: BigInt, path: string) => {
if ( if (
(query.has('a') && ((query.get('a') || '').length > 0)) && (query.has('channel') && ((query.get('channel') || '').length > 0)) && query.has('a') &&
(query.has('user') && ((query.get('user') || '').length > 0)) (query.get('a') || '').length > 0 &&
) { query.has('channel') &&
if (apiUserid === config.api.admin && apiUserid === BigInt(query.get('a') || '0')) { (query.get('channel') || '').length > 0 &&
// Flag to see if there is an error inside the catch query.has('user') &&
let value, erroredOut = false; (query.get('user') || '').length > 0
) {
if (apiUserid === config.api.admin && apiUserid === BigInt(query.get('a') || '0')) {
// Flag to see if there is an error inside the catch
let value,
erroredOut = false;
// Determine value to set // Determine value to set
if (path.toLowerCase().indexOf('un') > 0) { if (path.toLowerCase().indexOf('un') > 0) {
value = 0; value = 0;
} else { } else {
value = 1; value = 1;
} }
// Execute the DB modification // Execute the DB modification
await dbClient.execute('UPDATE allowed_channels SET banned = ? WHERE userid = ? AND channelid = ?', [value, apiUserid, BigInt(query.get('channel') || '0')]).catch((e) => { await dbClient
utils.commonLoggers.dbError('apiChannelManageBan.ts:28', 'update', e); .execute('UPDATE allowed_channels SET banned = ? WHERE userid = ? AND channelid = ?', [value, apiUserid, BigInt(query.get('channel') || '0')])
requestEvent.respondWith(stdResp.InternalServerError('Failed to update channel.')); .catch((e) => {
erroredOut = true; utils.commonLoggers.dbError('apiChannelManageBan.ts:28', 'update', e);
}); requestEvent.respondWith(stdResp.InternalServerError('Failed to update channel.'));
erroredOut = true;
});
// Exit this case now if catch errored // Exit this case now if catch errored
if (erroredOut) { if (erroredOut) {
return; return;
} else { } else {
// Send OK to indicate modification was successful // Send OK to indicate modification was successful
requestEvent.respondWith(stdResp.OK(`Successfully active to ${value}.`)); requestEvent.respondWith(stdResp.OK(`Successfully active to ${value}.`));
return; return;
} }
} else { } else {
// Alert API user that they shouldn't be doing this // Alert API user that they shouldn't be doing this
requestEvent.respondWith(stdResp.Forbidden(stdResp.Strings.restricted)); requestEvent.respondWith(stdResp.Forbidden(stdResp.Strings.restricted));
} }
} else { } else {
// Alert API user that they messed up // Alert API user that they messed up
requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams)); requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams));
} }
}; };

View File

@ -1,51 +1,51 @@
import config from '../../../config.ts'; import config from '../../../config.ts';
import { dbClient } from '../../db.ts'; import dbClient from '../../db/client.ts';
import stdResp from '../stdResponses.ts'; import stdResp from '../stdResponses.ts';
import utils from '../../utils.ts'; import utils from '../../utils.ts';
export const apiKeyManage = async (requestEvent: Deno.RequestEvent, query: Map<string, string>, apiUserid: BigInt, path: string) => { export const apiKeyManage = async (requestEvent: Deno.RequestEvent, query: Map<string, string>, apiUserid: BigInt, path: string) => {
if ((query.has('a') && ((query.get('a') || '').length > 0)) && (query.has('user') && ((query.get('user') || '').length > 0))) { if (query.has('a') && (query.get('a') || '').length > 0 && query.has('user') && (query.get('user') || '').length > 0) {
if (apiUserid === config.api.admin && apiUserid === BigInt(query.get('a') || '0')) { if (apiUserid === config.api.admin && apiUserid === BigInt(query.get('a') || '0')) {
// Flag to see if there is an error inside the catch // Flag to see if there is an error inside the catch
let key: string, let key: string,
value: number, value: number,
erroredOut = false; erroredOut = false;
// Determine key to edit // Determine key to edit
if (path.toLowerCase().indexOf('ban') > 0) { if (path.toLowerCase().indexOf('ban') > 0) {
key = 'banned'; key = 'banned';
} else { } else {
key = 'active'; key = 'active';
} }
// Determine value to set // Determine value to set
if (path.toLowerCase().indexOf('de') > 0 || path.toLowerCase().indexOf('un') > 0) { if (path.toLowerCase().indexOf('de') > 0 || path.toLowerCase().indexOf('un') > 0) {
value = 0; value = 0;
} else { } else {
value = 1; value = 1;
} }
// Execute the DB modification // Execute the DB modification
await dbClient.execute('UPDATE all_keys SET ?? = ? WHERE userid = ?', [key, value, apiUserid]).catch((e) => { await dbClient.execute('UPDATE all_keys SET ?? = ? WHERE userid = ?', [key, value, apiUserid]).catch((e) => {
utils.commonLoggers.dbError('apiKeyManage.ts', 'update', e); utils.commonLoggers.dbError('apiKeyManage.ts', 'update', e);
requestEvent.respondWith(stdResp.InternalServerError(`Failed to ${key} to ${value}.`)); requestEvent.respondWith(stdResp.InternalServerError(`Failed to ${key} to ${value}.`));
erroredOut = true; erroredOut = true;
}); });
// Exit this case now if catch errored // Exit this case now if catch errored
if (erroredOut) { if (erroredOut) {
return; return;
} else { } else {
// Send OK as response to indicate modification was successful // Send OK as response to indicate modification was successful
requestEvent.respondWith(stdResp.OK(`Successfully ${key} to ${value}.`)); requestEvent.respondWith(stdResp.OK(`Successfully ${key} to ${value}.`));
return; return;
} }
} else { } else {
// Alert API user that they shouldn't be doing this // Alert API user that they shouldn't be doing this
requestEvent.respondWith(stdResp.Forbidden('You can only manage your own key.')); requestEvent.respondWith(stdResp.Forbidden('You can only manage your own key.'));
} }
} else { } else {
// Alert API user that they messed up // Alert API user that they messed up
requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams)); requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams));
} }
}; };

View File

@ -1,23 +1,25 @@
import { import {
// httpd deps // httpd deps
Status, StatusCode,
STATUS_TEXT, STATUS_CODE,
STATUS_TEXT,
} from '../../deps.ts'; } from '../../deps.ts';
const genericResponse = (customText: string, status: Status) => new Response(customText || STATUS_TEXT[status], { status: status, statusText: STATUS_TEXT[status] }); const genericResponse = (customText: string, status: StatusCode) =>
new Response(customText || STATUS_TEXT[status], { status: status, statusText: STATUS_TEXT[status] });
export default { export default {
BadRequest: (customText: string) => genericResponse(customText, Status.BadRequest), BadRequest: (customText: string) => genericResponse(customText, STATUS_CODE.BadRequest),
FailedDependency: (customText: string) => genericResponse(customText, Status.FailedDependency), FailedDependency: (customText: string) => genericResponse(customText, STATUS_CODE.FailedDependency),
InternalServerError: (customText: string) => genericResponse(customText, Status.InternalServerError), InternalServerError: (customText: string) => genericResponse(customText, STATUS_CODE.InternalServerError),
Forbidden: (customText: string) => genericResponse(customText, Status.Forbidden), Forbidden: (customText: string) => genericResponse(customText, STATUS_CODE.Forbidden),
MethodNotAllowed: (customText: string) => genericResponse(customText, Status.MethodNotAllowed), MethodNotAllowed: (customText: string) => genericResponse(customText, STATUS_CODE.MethodNotAllowed),
NotFound: (customText: string) => genericResponse(customText, Status.NotFound), NotFound: (customText: string) => genericResponse(customText, STATUS_CODE.NotFound),
OK: (customText: string) => genericResponse(customText, Status.OK), OK: (customText: string) => genericResponse(customText, STATUS_CODE.OK),
RequestTimeout: (customText: string) => genericResponse(customText, Status.RequestTimeout), RequestTimeout: (customText: string) => genericResponse(customText, STATUS_CODE.RequestTimeout),
TooManyRequests: (customText: string) => genericResponse(customText, Status.TooManyRequests), TooManyRequests: (customText: string) => genericResponse(customText, STATUS_CODE.TooManyRequests),
Strings: { Strings: {
missingParams: 'Missing Parameters.', missingParams: 'Missing Parameters.',
restricted: 'This API is restricted.', restricted: 'This API is restricted.',
}, },
}; };

View File

@ -5,66 +5,67 @@
*/ */
import { import {
// Discordeno deps // Discordeno deps
cache, cache,
cacheHandlers, cacheHandlers,
// imagescript dep // imagescript dep
is, is,
// Log4Deno deps // Log4Deno deps
log, log,
LT, LT,
} from '../deps.ts'; } from '../deps.ts';
import { PastCommandCount } from './mod.d.ts'; import { PastCommandCount } from './mod.d.ts';
import { dbClient, weekDays } from './db.ts'; import dbClient from './db/client.ts';
import { weekDays } from './db/common.ts';
import utils from './utils.ts'; import utils from './utils.ts';
import config from '../config.ts'; import config from '../config.ts';
// getRandomStatus() returns status as string // getRandomStatus() returns status as string
// Gets a new random status for the bot // Gets a new random status for the bot
const getRandomStatus = async (): Promise<string> => { const getRandomStatus = async (): Promise<string> => {
let status = ''; let status = '';
switch (Math.floor((Math.random() * 4) + 1)) { switch (Math.floor(Math.random() * 4 + 1)) {
case 1: case 1:
status = `${config.prefix}help for commands`; status = `${config.prefix}help for commands`;
break; break;
case 2: case 2:
status = `Running V${config.version}`; status = `Running V${config.version}`;
break; break;
case 3: case 3:
status = `${config.prefix}info to learn more`; status = `${config.prefix}info to learn more`;
break; break;
default: { default: {
const cachedCount = await cacheHandlers.size('guilds'); const cachedCount = await cacheHandlers.size('guilds');
status = `Rolling dice for ${cachedCount + cache.dispatchedGuildIds.size} servers`; status = `Rolling dice for ${cachedCount + cache.dispatchedGuildIds.size} servers`;
break; break;
} }
} }
return status; return status;
}; };
// updateListStatistics(bot ID, current guild count) returns nothing, posts to botlists // updateListStatistics(bot ID, current guild count) returns nothing, posts to botlists
// Sends the current server count to all bot list sites we are listed on // Sends the current server count to all bot list sites we are listed on
const updateListStatistics = (botID: bigint, serverCount: number): void => { const updateListStatistics = (botID: bigint, serverCount: number): void => {
config.botLists.forEach(async (e) => { config.botLists.forEach(async (e) => {
try { try {
log(LT.LOG, `Updating statistics for ${JSON.stringify(e)}`); log(LT.LOG, `Updating statistics for ${JSON.stringify(e)}`);
if (e.enabled) { if (e.enabled) {
const tempHeaders = new Headers(); const tempHeaders = new Headers();
tempHeaders.append(e.headers[0].header, e.headers[0].value); tempHeaders.append(e.headers[0].header, e.headers[0].value);
tempHeaders.append('Content-Type', 'application/json'); tempHeaders.append('Content-Type', 'application/json');
// ?{} is a template used in config, just need to replace it with the real value // ?{} is a template used in config, just need to replace it with the real value
const response = await fetch(e.apiUrl.replace('?{bot_id}', botID.toString()), { const response = await fetch(e.apiUrl.replace('?{bot_id}', botID.toString()), {
'method': 'POST', method: 'POST',
'headers': tempHeaders, headers: tempHeaders,
'body': JSON.stringify(e.body).replace('"?{server_count}"', serverCount.toString()), // ?{server_count} needs the "" removed from around it aswell to make sure its sent as a number body: JSON.stringify(e.body).replace('"?{server_count}"', serverCount.toString()), // ?{server_count} needs the "" removed from around it aswell to make sure its sent as a number
}); });
log(LT.INFO, `Posted server count to ${e.name}. Results: ${JSON.stringify(response)}`); log(LT.INFO, `Posted server count to ${e.name}. Results: ${JSON.stringify(response)}`);
} }
} catch (err) { } catch (err) {
log(LT.ERROR, `Failed to update statistics for ${e.name} | Error: ${err.name} - ${err.message}`) log(LT.ERROR, `Failed to update statistics for ${e.name} | Error: ${err.name} - ${err.message}`);
} }
}); });
}; };
// Keep one week of data // Keep one week of data
@ -73,141 +74,141 @@ const previousHours: Array<Array<PastCommandCount>> = [];
// updateHourlyRates() returns nothing, updates DB directly // updateHourlyRates() returns nothing, updates DB directly
// Updates the hourlyRate for command usage // Updates the hourlyRate for command usage
const updateHourlyRates = async () => { const updateHourlyRates = async () => {
try { try {
const newestHour = await dbClient.query('SELECT command, count FROM command_cnt ORDER BY command;').catch((e) => utils.commonLoggers.dbError('intervals.ts:71', 'query', e)); const newestHour = await dbClient
previousHours.push(newestHour); .query('SELECT command, count FROM command_cnt ORDER BY command;')
if (previousHours.length > 1) { .catch((e) => utils.commonLoggers.dbError('intervals.ts:71', 'query', e));
const oldestHour = previousHours[0]; previousHours.push(newestHour);
if (previousHours.length > 1) {
const oldestHour = previousHours[0];
const computedDiff: Array<PastCommandCount> = []; const computedDiff: Array<PastCommandCount> = [];
for (let i = 0; i < newestHour.length; i++) { for (let i = 0; i < newestHour.length; i++) {
computedDiff.push({ computedDiff.push({
command: newestHour[i].command, command: newestHour[i].command,
count: (newestHour[i].count - oldestHour[i].count), count: newestHour[i].count - oldestHour[i].count,
}); });
log(LT.LOG, `Updating hourlyRate | Computing diffs: ${JSON.stringify(computedDiff)}`); log(LT.LOG, `Updating hourlyRate | Computing diffs: ${JSON.stringify(computedDiff)}`);
} }
// Update DB // Update DB
computedDiff.forEach(async (cmd) => { computedDiff.forEach(async (cmd) => {
log(LT.LOG, `Updating hourlyRate | Storing to DB: ${JSON.stringify(cmd)}`); log(LT.LOG, `Updating hourlyRate | Storing to DB: ${JSON.stringify(cmd)}`);
await dbClient.execute(`UPDATE command_cnt SET hourlyRate = ? WHERE command = ?`, [cmd.count / previousHours.length, cmd.command]).catch((e) => await dbClient
utils.commonLoggers.dbError('intervals.ts:88', 'update', e) .execute(`UPDATE command_cnt SET hourlyRate = ? WHERE command = ?`, [cmd.count / previousHours.length, cmd.command])
); .catch((e) => utils.commonLoggers.dbError('intervals.ts:88', 'update', e));
}); });
} }
if (previousHours.length > hoursToKeep) { if (previousHours.length > hoursToKeep) {
previousHours.unshift(); previousHours.unshift();
} }
} catch (e) { } catch (e) {
log(LT.ERROR, `Something went wrong in previousHours interval | Error: ${e.name} - ${e.message}`); log(LT.ERROR, `Something went wrong in previousHours interval | Error: ${e.name} - ${e.message}`);
} }
}; };
// getPercentOfRange(min, max, val) returns number // getPercentOfRange(min, max, val) returns number
// Gets a percent value of where val lies in the min-max range // Gets a percent value of where val lies in the min-max range
const getPercentOfRange = (minVal: number, maxVal: number, val: number): number => { const getPercentOfRange = (minVal: number, maxVal: number, val: number): number => {
const localMax = maxVal - minVal; const localMax = maxVal - minVal;
const localVal = val - minVal; const localVal = val - minVal;
return localVal / localMax; return localVal / localMax;
}; };
// Pixel locations in heatmap-base.png, pixel locations are 0 based // Pixel locations in heatmap-base.png, pixel locations are 0 based
// dayPixels holds the left and right (AKA X Coord) pixel locations for each col (ex: [leftPX, rightPX]) // dayPixels holds the left and right (AKA X Coord) pixel locations for each col (ex: [leftPX, rightPX])
const dayPixels: Array<Array<number>> = [ const dayPixels: Array<Array<number>> = [
[72, 159], [72, 159],
[163, 260], [163, 260],
[264, 359], [264, 359],
[363, 497], [363, 497],
[501, 608], [501, 608],
[612, 686], [612, 686],
[690, 800], [690, 800],
]; ];
// hourPixels holds the top and bottom (AKA Y Coord) pixel locations for each row (ex: [topPX, botPX]) // hourPixels holds the top and bottom (AKA Y Coord) pixel locations for each row (ex: [topPX, botPX])
const hourPixels: Array<Array<number>> = [ const hourPixels: Array<Array<number>> = [
[29, 49], [29, 49],
[51, 72], [51, 72],
[74, 95], [74, 95],
[97, 118], [97, 118],
[120, 141], [120, 141],
[143, 164], [143, 164],
[166, 187], [166, 187],
[189, 209], [189, 209],
[211, 232], [211, 232],
[234, 254], [234, 254],
[256, 277], [256, 277],
[279, 299], [279, 299],
[301, 322], [301, 322],
[324, 345], [324, 345],
[347, 368], [347, 368],
[370, 391], [370, 391],
[393, 413], [393, 413],
[415, 436], [415, 436],
[438, 459], [438, 459],
[461, 482], [461, 482],
[484, 505], [484, 505],
[507, 528], [507, 528],
[530, 550], [530, 550],
[552, 572], [552, 572],
]; ];
// updateHeatmap() returns nothing, creates new heatmap.png // updateHeatmap() returns nothing, creates new heatmap.png
// Updates the heatmap image with latest data from the db // Updates the heatmap image with latest data from the db
let minRollCnt: number; let minRollCnt: number;
let maxRollCnt: number; let maxRollCnt: number;
const updateHeatmapPng = async () => { const updateHeatmapPng = async () => {
const baseHeatmap = Deno.readFileSync('./src/endpoints/gets/heatmap-base.png'); const baseHeatmap = Deno.readFileSync('./src/endpoints/gets/heatmap-base.png');
const heatmap = await is.decode(baseHeatmap); const heatmap = await is.decode(baseHeatmap);
if (!(heatmap instanceof is.Image)) { if (!(heatmap instanceof is.Image)) {
return; return;
} }
// Get latest data from DB // Get latest data from DB
const heatmapData = await dbClient.query('SELECT * FROM roll_time_heatmap ORDER BY hour;').catch((e) => utils.commonLoggers.dbError('intervals.ts:148', 'query', e)); const heatmapData = await dbClient
.query('SELECT * FROM roll_time_heatmap ORDER BY hour;')
.catch((e) => utils.commonLoggers.dbError('intervals.ts:148', 'query', e));
minRollCnt = Infinity; minRollCnt = Infinity;
maxRollCnt = 0; maxRollCnt = 0;
// determine min and max values // determine min and max values
for (const hour of heatmapData) { for (const hour of heatmapData) {
for (const day of weekDays) { for (const day of weekDays) {
const rollCnt = hour[day]; const rollCnt = hour[day];
log(LT.LOG, `updateHeatmapPng | finding min/max | min: ${minRollCnt} max: ${maxRollCnt} curr: ${rollCnt}`); log(LT.LOG, `updateHeatmapPng | finding min/max | min: ${minRollCnt} max: ${maxRollCnt} curr: ${rollCnt}`);
if (rollCnt > maxRollCnt) { if (rollCnt > maxRollCnt) {
maxRollCnt = rollCnt; maxRollCnt = rollCnt;
} }
if (rollCnt < minRollCnt) { if (rollCnt < minRollCnt) {
minRollCnt = rollCnt; minRollCnt = rollCnt;
} }
} }
} }
// Apply values to image // Apply values to image
for (let hour = 0; hour < heatmapData.length; hour++) { for (let hour = 0; hour < heatmapData.length; hour++) {
for (let day = 0; day < weekDays.length; day++) { for (let day = 0; day < weekDays.length; day++) {
log(LT.LOG, `updateHeatmapPng | putting ${weekDays[day]} ${hour}:00 into image`); log(LT.LOG, `updateHeatmapPng | putting ${weekDays[day]} ${hour}:00 into image`);
const percent = getPercentOfRange(minRollCnt, maxRollCnt, heatmapData[hour][weekDays[day]]); const percent = getPercentOfRange(minRollCnt, maxRollCnt, heatmapData[hour][weekDays[day]]);
heatmap.drawBox( heatmap.drawBox(
dayPixels[day][0] + 1, dayPixels[day][0] + 1,
hourPixels[hour][0] + 1, hourPixels[hour][0] + 1,
dayPixels[day][1] - dayPixels[day][0] + 1, dayPixels[day][1] - dayPixels[day][0] + 1,
hourPixels[hour][1] - hourPixels[hour][0] + 1, hourPixels[hour][1] - hourPixels[hour][0] + 1,
is.Image.rgbToColor( is.Image.rgbToColor(255 * (1 - percent), 255 * percent, 0)
255 * (1 - percent), );
255 * percent, }
0, }
),
);
}
}
Deno.writeFileSync('./src/endpoints/gets/heatmap.png', await heatmap.encode()); Deno.writeFileSync('./src/endpoints/gets/heatmap.png', await heatmap.encode());
}; };
export default { export default {
getRandomStatus, getRandomStatus,
updateListStatistics, updateListStatistics,
updateHourlyRates, updateHourlyRates,
updateHeatmapPng, updateHeatmapPng,
getMinRollCnt: () => minRollCnt, getMinRollCnt: () => minRollCnt,
getMaxRollCnt: () => maxRollCnt, getMaxRollCnt: () => maxRollCnt,
}; };

View File

@ -1,15 +1,16 @@
import config from '../../config.ts'; import config from '../../config.ts';
import { DEVMODE } from '../../flags.ts'; import { DEVMODE } from '../../flags.ts';
import { dbClient, queries } from '../db.ts'; import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import { import {
// Discordeno deps // Discordeno deps
DiscordenoMessage, DiscordenoMessage,
// Log4Deno deps // Log4Deno deps
log, log,
LT, LT,
// Discordeno deps // Discordeno deps
sendDirectMessage, sendDirectMessage,
sendMessage, sendMessage,
} from '../../deps.ts'; } from '../../deps.ts';
import { SolvedRoll } from '../solver/solver.d.ts'; import { SolvedRoll } from '../solver/solver.d.ts';
import { QueuedRoll, RollModifiers } from '../mod.d.ts'; import { QueuedRoll, RollModifiers } from '../mod.d.ts';
@ -22,189 +23,208 @@ const rollQueue: Array<QueuedRoll> = [];
// Handle setting up and calling the rollWorker // Handle setting up and calling the rollWorker
const handleRollWorker = async (rq: QueuedRoll) => { const handleRollWorker = async (rq: QueuedRoll) => {
currentWorkers++; currentWorkers++;
// gmModifiers used to create gmEmbed (basically just turn off the gmRoll) // gmModifiers used to create gmEmbed (basically just turn off the gmRoll)
const gmModifiers = JSON.parse(JSON.stringify(rq.modifiers)); const gmModifiers = JSON.parse(JSON.stringify(rq.modifiers));
gmModifiers.gmRoll = false; gmModifiers.gmRoll = false;
const rollWorker = new Worker(new URL('../solver/rollWorker.ts', import.meta.url).href, { type: 'module' }); const rollWorker = new Worker(new URL('../solver/rollWorker.ts', import.meta.url).href, { type: 'module' });
const workerTimeout = setTimeout(async () => { const workerTimeout = setTimeout(async () => {
rollWorker.terminate(); rollWorker.terminate();
currentWorkers--; currentWorkers--;
if (rq.apiRoll) { if (rq.apiRoll) {
rq.api.requestEvent.respondWith(stdResp.RequestTimeout('Roll took too long to process, try breaking roll down into simpler parts')); rq.api.requestEvent.respondWith(stdResp.RequestTimeout('Roll took too long to process, try breaking roll down into simpler parts'));
} else { } else {
rq.dd.m.edit({ rq.dd.m
embeds: [ .edit({
(await generateRollEmbed( embeds: [
rq.dd.message.authorId, (
<SolvedRoll> { await generateRollEmbed(
error: true, rq.dd.message.authorId,
errorCode: 'TooComplex', <SolvedRoll>{
errorMsg: 'Error: Roll took too long to process, try breaking roll down into simpler parts', error: true,
}, errorCode: 'TooComplex',
<RollModifiers> {}, errorMsg: 'Error: Roll took too long to process, try breaking roll down into simpler parts',
)).embed, },
], <RollModifiers>{}
}).catch((e) => utils.commonLoggers.messageEditError('rollQueue.ts:51', rq.dd.m, e)); )
} ).embed,
}, config.limits.workerTimeout); ],
})
.catch((e) => utils.commonLoggers.messageEditError('rollQueue.ts:51', rq.dd.m, e));
}
}, config.limits.workerTimeout);
rollWorker.addEventListener('message', async (workerMessage) => { rollWorker.addEventListener('message', async (workerMessage) => {
if (workerMessage.data === 'ready') { if (workerMessage.data === 'ready') {
rollWorker.postMessage({ rollWorker.postMessage({
rollCmd: rq.rollCmd, rollCmd: rq.rollCmd,
modifiers: rq.modifiers, modifiers: rq.modifiers,
}); });
return; return;
} }
let apiErroredOut = false; let apiErroredOut = false;
try { try {
currentWorkers--; currentWorkers--;
clearTimeout(workerTimeout); clearTimeout(workerTimeout);
const returnmsg = workerMessage.data; const returnmsg = workerMessage.data;
const pubEmbedDetails = await generateRollEmbed(rq.apiRoll ? rq.api.userId : rq.dd.message.authorId, returnmsg, rq.modifiers); const pubEmbedDetails = await generateRollEmbed(rq.apiRoll ? rq.api.userId : rq.dd.message.authorId, returnmsg, rq.modifiers);
const gmEmbedDetails = await generateRollEmbed(rq.apiRoll ? rq.api.userId : rq.dd.message.authorId, returnmsg, gmModifiers); const gmEmbedDetails = await generateRollEmbed(rq.apiRoll ? rq.api.userId : rq.dd.message.authorId, returnmsg, gmModifiers);
const countEmbed = generateCountDetailsEmbed(returnmsg.counts); const countEmbed = generateCountDetailsEmbed(returnmsg.counts);
// If there was an error, report it to the user in hopes that they can determine what they did wrong // If there was an error, report it to the user in hopes that they can determine what they did wrong
if (returnmsg.error) { if (returnmsg.error) {
if (rq.apiRoll) { if (rq.apiRoll) {
rq.api.requestEvent.respondWith(stdResp.InternalServerError(returnmsg.errorMsg)); rq.api.requestEvent.respondWith(stdResp.InternalServerError(returnmsg.errorMsg));
} else { } else {
rq.dd.m.edit({ embeds: [pubEmbedDetails.embed] }); rq.dd.m.edit({ embeds: [pubEmbedDetails.embed] });
} }
if (rq.apiRoll || DEVMODE && config.logRolls) { if (rq.apiRoll || (DEVMODE && config.logRolls)) {
// If enabled, log rolls so we can see what went wrong // If enabled, log rolls so we can see what went wrong
dbClient.execute(queries.insertRollLogCmd(rq.apiRoll ? 1 : 0, 1), [rq.originalCommand, returnmsg.errorCode, rq.apiRoll ? null : rq.dd.m.id]).catch((e) => dbClient
utils.commonLoggers.dbError('rollQueue.ts:82', 'insert into', e) .execute(queries.insertRollLogCmd(rq.apiRoll ? 1 : 0, 1), [rq.originalCommand, returnmsg.errorCode, rq.apiRoll ? null : rq.dd.m.id])
); .catch((e) => utils.commonLoggers.dbError('rollQueue.ts:82', 'insert into', e));
} }
} else { } else {
let n: DiscordenoMessage | void; let n: DiscordenoMessage | void;
// Determine if we are to send a GM roll or a normal roll // Determine if we are to send a GM roll or a normal roll
if (rq.modifiers.gmRoll) { if (rq.modifiers.gmRoll) {
if (rq.apiRoll) { if (rq.apiRoll) {
n = await sendMessage(rq.api.channelId, { n = await sendMessage(rq.api.channelId, {
content: rq.modifiers.apiWarn, content: rq.modifiers.apiWarn,
embeds: [pubEmbedDetails.embed], embeds: [pubEmbedDetails.embed],
}).catch(() => { }).catch(() => {
apiErroredOut = true; apiErroredOut = true;
rq.api.requestEvent.respondWith(stdResp.InternalServerError('Message failed to send - location 0.')); rq.api.requestEvent.respondWith(stdResp.InternalServerError('Message failed to send - location 0.'));
}); });
} else { } else {
// Send the public embed to correct channel // Send the public embed to correct channel
rq.dd.m.edit({ embeds: [pubEmbedDetails.embed] }); rq.dd.m.edit({ embeds: [pubEmbedDetails.embed] });
} }
if (!apiErroredOut) { if (!apiErroredOut) {
// And message the full details to each of the GMs, alerting roller of every GM that could not be messaged // And message the full details to each of the GMs, alerting roller of every GM that could not be messaged
rq.modifiers.gms.forEach(async (gm) => { rq.modifiers.gms.forEach(async (gm) => {
log(LT.LOG, `Messaging GM ${gm}`); log(LT.LOG, `Messaging GM ${gm}`);
// Attempt to DM the GM and send a warning if it could not DM a GM // Attempt to DM the GM and send a warning if it could not DM a GM
await sendDirectMessage(BigInt(gm.substring(2, gm.length - 1)), { await sendDirectMessage(BigInt(gm.substring(2, gm.length - 1)), {
embeds: rq.modifiers.count ? [gmEmbedDetails.embed, countEmbed] : [gmEmbedDetails.embed], embeds: rq.modifiers.count ? [gmEmbedDetails.embed, countEmbed] : [gmEmbedDetails.embed],
}).then(async () => { })
// Check if we need to attach a file and send it after the initial details sent .then(async () => {
if (gmEmbedDetails.hasAttachment) { // Check if we need to attach a file and send it after the initial details sent
await sendDirectMessage(BigInt(gm.substring(2, gm.length - 1)), { if (gmEmbedDetails.hasAttachment) {
file: gmEmbedDetails.attachment, await sendDirectMessage(BigInt(gm.substring(2, gm.length - 1)), {
}).catch(() => { file: gmEmbedDetails.attachment,
if (n && rq.apiRoll) { }).catch(() => {
n.reply(generateDMFailed(gm)); if (n && rq.apiRoll) {
} else { n.reply(generateDMFailed(gm));
rq.dd.message.reply(generateDMFailed(gm)); } else {
} rq.dd.message.reply(generateDMFailed(gm));
}); }
} });
}).catch(() => { }
if (rq.apiRoll && n) { })
n.reply(generateDMFailed(gm)); .catch(() => {
} else { if (rq.apiRoll && n) {
rq.dd.message.reply(generateDMFailed(gm)); n.reply(generateDMFailed(gm));
} } else {
}); rq.dd.message.reply(generateDMFailed(gm));
}); }
} });
} else { });
// Not a gm roll, so just send normal embed to correct channel }
if (rq.apiRoll) { } else {
n = await sendMessage(rq.api.channelId, { // Not a gm roll, so just send normal embed to correct channel
content: rq.modifiers.apiWarn, if (rq.apiRoll) {
embeds: rq.modifiers.count ? [pubEmbedDetails.embed, countEmbed] : [pubEmbedDetails.embed], n = await sendMessage(rq.api.channelId, {
}).catch(() => { content: rq.modifiers.apiWarn,
apiErroredOut = true; embeds: rq.modifiers.count ? [pubEmbedDetails.embed, countEmbed] : [pubEmbedDetails.embed],
rq.api.requestEvent.respondWith(stdResp.InternalServerError('Message failed to send - location 1.')); }).catch(() => {
}); apiErroredOut = true;
} else { rq.api.requestEvent.respondWith(stdResp.InternalServerError('Message failed to send - location 1.'));
n = await rq.dd.m.edit({ });
embeds: rq.modifiers.count ? [pubEmbedDetails.embed, countEmbed] : [pubEmbedDetails.embed], } else {
}); n = await rq.dd.m.edit({
} embeds: rq.modifiers.count ? [pubEmbedDetails.embed, countEmbed] : [pubEmbedDetails.embed],
});
}
if (pubEmbedDetails.hasAttachment && n) { if (pubEmbedDetails.hasAttachment && n) {
// Attachment requires you to send a new message // Attachment requires you to send a new message
n.reply({ n.reply({
file: pubEmbedDetails.attachment, file: pubEmbedDetails.attachment,
}); });
} }
} }
if (rq.apiRoll && !apiErroredOut) { if (rq.apiRoll && !apiErroredOut) {
dbClient.execute(queries.insertRollLogCmd(1, 0), [rq.originalCommand, returnmsg.errorCode, n ? n.id : null]).catch((e) => utils.commonLoggers.dbError('rollQueue.ts:155', 'insert into', e)); dbClient
.execute(queries.insertRollLogCmd(1, 0), [rq.originalCommand, returnmsg.errorCode, n ? n.id : null])
.catch((e) => utils.commonLoggers.dbError('rollQueue.ts:155', 'insert into', e));
rq.api.requestEvent.respondWith(stdResp.OK(JSON.stringify( rq.api.requestEvent.respondWith(
rq.modifiers.count stdResp.OK(
? { JSON.stringify(
counts: countEmbed, rq.modifiers.count
details: pubEmbedDetails, ? {
} counts: countEmbed,
: { details: pubEmbedDetails,
details: pubEmbedDetails, }
}, : {
))); details: pubEmbedDetails,
} }
} )
} catch (e) { )
log(LT.ERROR, `Unddandled Error: ${JSON.stringify(e)}`); );
if (rq.apiRoll && !apiErroredOut) { }
rq.api.requestEvent.respondWith(stdResp.InternalServerError(JSON.stringify(e))); }
} } catch (e) {
} log(LT.ERROR, `Unddandled Error: ${JSON.stringify(e)}`);
}); if (rq.apiRoll && !apiErroredOut) {
rq.api.requestEvent.respondWith(stdResp.InternalServerError(JSON.stringify(e)));
}
}
});
}; };
// Runs the roll or queues it depending on how many workers are currently running // Runs the roll or queues it depending on how many workers are currently running
export const queueRoll = async (rq: QueuedRoll) => { export const queueRoll = async (rq: QueuedRoll) => {
if (rq.apiRoll) { if (rq.apiRoll) {
handleRollWorker(rq); handleRollWorker(rq);
} else if (!rollQueue.length && currentWorkers < config.limits.maxWorkers) { } else if (!rollQueue.length && currentWorkers < config.limits.maxWorkers) {
handleRollWorker(rq); handleRollWorker(rq);
} else { } else {
rq.dd.m.edit({ rq.dd.m
embeds: [{ .edit({
color: infoColor2, embeds: [
title: `${config.name} currently has its hands full and has queued your roll.`, {
description: `There are currently ${currentWorkers + rollQueue.length} rolls ahead of this roll. color: infoColor2,
title: `${config.name} currently has its hands full and has queued your roll.`,
description: `There are currently ${currentWorkers + rollQueue.length} rolls ahead of this roll.
The results for this roll will replace this message when it is done.`, The results for this roll will replace this message when it is done.`,
}], },
}).catch((e: Error) => utils.commonLoggers.messageEditError('rollQueue.ts:197', rq.dd.m, e)); ],
rollQueue.push(rq); })
} .catch((e: Error) => utils.commonLoggers.messageEditError('rollQueue.ts:197', rq.dd.m, e));
rollQueue.push(rq);
}
}; };
// Checks the queue constantly to make sure the queue stays empty // Checks the queue constantly to make sure the queue stays empty
setInterval(async () => { setInterval(async () => {
log(LT.LOG, `Checking rollQueue for items, rollQueue length: ${rollQueue.length}, currentWorkers: ${currentWorkers}, config.limits.maxWorkers: ${config.limits.maxWorkers}`); log(
if (rollQueue.length && currentWorkers < config.limits.maxWorkers) { LT.LOG,
const temp = rollQueue.shift(); `Checking rollQueue for items, rollQueue length: ${rollQueue.length}, currentWorkers: ${currentWorkers}, config.limits.maxWorkers: ${config.limits.maxWorkers}`
if (temp) { );
temp.dd.m.edit(rollingEmbed).catch((e: Error) => utils.commonLoggers.messageEditError('rollQueue.ts:208', temp.dd.m, e)); if (rollQueue.length && currentWorkers < config.limits.maxWorkers) {
handleRollWorker(temp); const temp = rollQueue.shift();
} if (temp) {
} temp.dd.m.edit(rollingEmbed).catch((e: Error) => utils.commonLoggers.messageEditError('rollQueue.ts:208', temp.dd.m, e));
handleRollWorker(temp);
}
}
}, 1000); }, 1000);