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
import config from '../config.ts';
import { dbClient } from '../src/db.ts';
import dbClient from '../src/db/client.ts';
console.log('Attempting to create DB');
await dbClient.execute(`CREATE SCHEMA IF NOT EXISTS ${config.db.name};`);
console.log('test');
await dbClient.execute(`USE ${config.db.name}`);
console.log('DB created');

View File

@ -1,28 +1,47 @@
// This file will populate the tables with default values
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');
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('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) {
await dbClient.execute('INSERT INTO command_cnt(command) values(?)', [command]).catch((e) => {
console.log(`Failed to insert ${command} into database`, e);
});
await dbClient.execute('INSERT INTO command_cnt(command) values(?)', [command]).catch((e) => {
console.log(`Failed to insert ${command} into database`, e);
});
}
console.log('Insertion done');
console.log('Attempting to insert default hours into roll_time_heatmap');
for (let i = 0; i <= 23; i++) {
await dbClient.execute('INSERT INTO roll_time_heatmap(hour) values(?)', [i]).catch((e) => {
console.log(`Failed to insert hour ${i} into database`, 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('Insertion done');

572
mod.ts
View File

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

View File

@ -6,217 +6,219 @@
import config from '../config.ts';
import {
// Log4Deno deps
log,
LT,
// Log4Deno deps
log,
LT,
} from '../deps.ts';
import { dbClient } from './db.ts';
import dbClient from './db/client.ts';
import endpoints from './endpoints/_index.ts';
import stdResp from './endpoints/stdResponses.ts';
// start() returns nothing
// start initializes and runs the entire API for the bot
const start = async (): Promise<void> => {
const server = Deno.listen({ port: config.api.port });
log(LT.INFO, `HTTP api running at: http://localhost:${config.api.port}/`);
const server = Deno.listen({ port: 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
const rateLimitTime = new Map<string, number>();
// rateLimitCnt holds the number of times the user has called the api in the current rate limit timer
const rateLimitCnt = new Map<string, number>();
// rateLimitTime holds all users with the last time they started a rate limit timer
const rateLimitTime = new Map<string, number>();
// rateLimitCnt holds the number of times the user has called the api in the current rate limit timer
const rateLimitCnt = new Map<string, number>();
// Catching every request made to the server
for await (const conn of server) {
(async () => {
const httpConn = Deno.serveHttp(conn);
for await (const requestEvent of httpConn) {
const request = requestEvent.request;
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
let authenticated = false;
let rateLimited = false;
let updateRateLimitTime = false;
let apiUserid = 0n;
let apiUseridStr = '';
let apiUserEmail = '';
let apiUserDelCode = '';
// Catching every request made to the server
for await (const conn of server) {
(async () => {
const httpConn = Deno.serveHttp(conn);
for await (const requestEvent of httpConn) {
const request = requestEvent.request;
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
let authenticated = false;
let rateLimited = false;
let updateRateLimitTime = false;
let apiUserid = 0n;
let apiUseridStr = '';
let apiUserEmail = '';
let apiUserDelCode = '';
// Check the requests API key
if (request.headers.has('X-Api-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')]);
// Check the requests API key
if (request.headers.has('X-Api-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'),
]);
// If only one user returned, is not banned, and is currently active, mark as authenticated
if (dbApiQuery.length === 1) {
apiUserid = BigInt(dbApiQuery[0].userid);
apiUserEmail = dbApiQuery[0].email;
apiUserDelCode = dbApiQuery[0].deleteCode;
authenticated = true;
// If only one user returned, is not banned, and is currently active, mark as authenticated
if (dbApiQuery.length === 1) {
apiUserid = BigInt(dbApiQuery[0].userid);
apiUserEmail = dbApiQuery[0].email;
apiUserDelCode = dbApiQuery[0].deleteCode;
authenticated = true;
// Rate limiting inits
apiUseridStr = apiUserid.toString();
const apiTimeNow = new Date().getTime();
// Rate limiting inits
apiUseridStr = apiUserid.toString();
const apiTimeNow = new Date().getTime();
// Check if user has sent a request recently
if (rateLimitTime.has(apiUseridStr) && (((rateLimitTime.get(apiUseridStr) || 0) + config.api.rateLimitTime) > apiTimeNow)) {
// Get current count
const currentCnt = rateLimitCnt.get(apiUseridStr) || 0;
if (currentCnt < config.api.rateLimitCnt) {
// Limit not yet exceeded, update count
rateLimitCnt.set(apiUseridStr, currentCnt + 1);
} else {
// Limit exceeded, prevent API use
rateLimited = true;
}
} else {
// Update the maps
updateRateLimitTime = true;
rateLimitCnt.set(apiUseridStr, 1);
}
}
}
// Check if user has sent a request recently
if (rateLimitTime.has(apiUseridStr) && (rateLimitTime.get(apiUseridStr) || 0) + config.api.rateLimitTime > apiTimeNow) {
// Get current count
const currentCnt = rateLimitCnt.get(apiUseridStr) || 0;
if (currentCnt < config.api.rateLimitCnt) {
// Limit not yet exceeded, update count
rateLimitCnt.set(apiUseridStr, currentCnt + 1);
} else {
// Limit exceeded, prevent API use
rateLimited = true;
}
} else {
// Update the maps
updateRateLimitTime = true;
rateLimitCnt.set(apiUseridStr, 1);
}
}
}
if (!rateLimited) {
// Get path and query as a string
const [urlPath, tempQ] = request.url.split('?');
const path = urlPath.split('api')[1];
if (!rateLimited) {
// Get path and query as a string
const [urlPath, tempQ] = request.url.split('?');
const path = urlPath.split('api')[1];
// Turn the query into a map (if it exists)
const query = new Map<string, string>();
if (tempQ !== undefined) {
tempQ.split('&').forEach((e: string) => {
log(LT.LOG, `Parsing request query ${request} ${e}`);
const [option, params] = e.split('=');
query.set(option.toLowerCase(), params);
});
}
// Turn the query into a map (if it exists)
const query = new Map<string, string>();
if (tempQ !== undefined) {
tempQ.split('&').forEach((e: string) => {
log(LT.LOG, `Parsing request query ${request} ${e}`);
const [option, params] = e.split('=');
query.set(option.toLowerCase(), params);
});
}
if (path) {
if (authenticated) {
// Handle the authenticated request
switch (request.method) {
case 'GET':
switch (path.toLowerCase()) {
case '/key':
case '/key/':
endpoints.get.apiKeyAdmin(requestEvent, query, apiUserid);
break;
case '/channel':
case '/channel/':
endpoints.get.apiChannel(requestEvent, query, apiUserid);
break;
case '/roll':
case '/roll/':
endpoints.get.apiRoll(requestEvent, query, apiUserid);
break;
default:
// Alert API user that they messed up
requestEvent.respondWith(stdResp.NotFound('Auth Get'));
break;
}
break;
case 'POST':
switch (path.toLowerCase()) {
case '/channel/add':
case '/channel/add/':
endpoints.post.apiChannelAdd(requestEvent, query, apiUserid);
break;
default:
// Alert API user that they messed up
requestEvent.respondWith(stdResp.NotFound('Auth Post'));
break;
}
break;
case 'PUT':
switch (path.toLowerCase()) {
case '/key/ban':
case '/key/ban/':
case '/key/unban':
case '/key/unban/':
case '/key/activate':
case '/key/activate/':
case '/key/deactivate':
case '/key/deactivate/':
endpoints.put.apiKeyManage(requestEvent, query, apiUserid, path);
break;
case '/channel/ban':
case '/channel/ban/':
case '/channel/unban':
case '/channel/unban/':
endpoints.put.apiChannelManageBan(requestEvent, query, apiUserid, path);
break;
case '/channel/activate':
case '/channel/activate/':
case '/channel/deactivate':
case '/channel/deactivate/':
endpoints.put.apiChannelManageActive(requestEvent, query, apiUserid, path);
break;
default:
// Alert API user that they messed up
requestEvent.respondWith(stdResp.NotFound('Auth Put'));
break;
}
break;
case 'DELETE':
switch (path.toLowerCase()) {
case '/key/delete':
case '/key/delete/':
endpoints.delete.apiKeyDelete(requestEvent, query, apiUserid, apiUserEmail, apiUserDelCode);
break;
default:
// Alert API user that they messed up
requestEvent.respondWith(stdResp.NotFound('Auth Del'));
break;
}
break;
default:
// Alert API user that they messed up
requestEvent.respondWith(stdResp.MethodNotAllowed('Auth'));
break;
}
if (path) {
if (authenticated) {
// Handle the authenticated request
switch (request.method) {
case 'GET':
switch (path.toLowerCase()) {
case '/key':
case '/key/':
endpoints.get.apiKeyAdmin(requestEvent, query, apiUserid);
break;
case '/channel':
case '/channel/':
endpoints.get.apiChannel(requestEvent, query, apiUserid);
break;
case '/roll':
case '/roll/':
endpoints.get.apiRoll(requestEvent, query, apiUserid);
break;
default:
// Alert API user that they messed up
requestEvent.respondWith(stdResp.NotFound('Auth Get'));
break;
}
break;
case 'POST':
switch (path.toLowerCase()) {
case '/channel/add':
case '/channel/add/':
endpoints.post.apiChannelAdd(requestEvent, query, apiUserid);
break;
default:
// Alert API user that they messed up
requestEvent.respondWith(stdResp.NotFound('Auth Post'));
break;
}
break;
case 'PUT':
switch (path.toLowerCase()) {
case '/key/ban':
case '/key/ban/':
case '/key/unban':
case '/key/unban/':
case '/key/activate':
case '/key/activate/':
case '/key/deactivate':
case '/key/deactivate/':
endpoints.put.apiKeyManage(requestEvent, query, apiUserid, path);
break;
case '/channel/ban':
case '/channel/ban/':
case '/channel/unban':
case '/channel/unban/':
endpoints.put.apiChannelManageBan(requestEvent, query, apiUserid, path);
break;
case '/channel/activate':
case '/channel/activate/':
case '/channel/deactivate':
case '/channel/deactivate/':
endpoints.put.apiChannelManageActive(requestEvent, query, apiUserid, path);
break;
default:
// Alert API user that they messed up
requestEvent.respondWith(stdResp.NotFound('Auth Put'));
break;
}
break;
case 'DELETE':
switch (path.toLowerCase()) {
case '/key/delete':
case '/key/delete/':
endpoints.delete.apiKeyDelete(requestEvent, query, apiUserid, apiUserEmail, apiUserDelCode);
break;
default:
// Alert API user that they messed up
requestEvent.respondWith(stdResp.NotFound('Auth Del'));
break;
}
break;
default:
// Alert API user that they messed up
requestEvent.respondWith(stdResp.MethodNotAllowed('Auth'));
break;
}
// Update rate limit details
if (updateRateLimitTime) {
const apiTimeNow = new Date().getTime();
rateLimitTime.set(apiUseridStr, apiTimeNow);
}
} else if (!authenticated) {
// Handle the unathenticated request
switch (request.method) {
case 'GET':
switch (path.toLowerCase()) {
case '/key':
case '/key/':
endpoints.get.apiKey(requestEvent, query);
break;
case '/heatmap.png':
endpoints.get.heatmapPng(requestEvent);
break;
default:
// Alert API user that they messed up
requestEvent.respondWith(stdResp.NotFound('NoAuth Get'));
break;
}
break;
default:
// Alert API user that they messed up
requestEvent.respondWith(stdResp.MethodNotAllowed('NoAuth'));
break;
}
}
} else {
requestEvent.respondWith(stdResp.Forbidden('What are you trying to do?'));
}
} else if (authenticated && rateLimited) {
// 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.'));
} else {
// Alert API user that they shouldn't be doing this
requestEvent.respondWith(stdResp.Forbidden('Why are you here?'));
}
}
})();
}
// Update rate limit details
if (updateRateLimitTime) {
const apiTimeNow = new Date().getTime();
rateLimitTime.set(apiUseridStr, apiTimeNow);
}
} else if (!authenticated) {
// Handle the unathenticated request
switch (request.method) {
case 'GET':
switch (path.toLowerCase()) {
case '/key':
case '/key/':
endpoints.get.apiKey(requestEvent, query);
break;
case '/heatmap.png':
endpoints.get.heatmapPng(requestEvent);
break;
default:
// Alert API user that they messed up
requestEvent.respondWith(stdResp.NotFound('NoAuth Get'));
break;
}
break;
default:
// Alert API user that they messed up
requestEvent.respondWith(stdResp.MethodNotAllowed('NoAuth'));
break;
}
}
} else {
requestEvent.respondWith(stdResp.Forbidden('What are you trying to do?'));
}
} else if (authenticated && rateLimited) {
// 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.'));
} else {
// Alert API user that they shouldn't be doing this
requestEvent.respondWith(stdResp.Forbidden('Why are you here?'));
}
}
})();
}
};
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 {
// Discordeno deps
DiscordenoMessage,
hasGuildPermissions,
// Discordeno deps
DiscordenoMessage,
hasGuildPermissions,
} from '../../deps.ts';
import apiCommands from './apiCmd/_index.ts';
import { failColor } from '../commandUtils.ts';
import utils from '../utils.ts';
export const api = async (message: DiscordenoMessage, args: string[]) => {
// 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));
// 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));
// Local apiArg in lowercase
const apiArg = (args[0] || 'help').toLowerCase();
// Local apiArg in lowercase
const apiArg = (args[0] || 'help').toLowerCase();
// Alert users who DM the bot that this command is for guilds only
if (message.guildId === 0n) {
message.send({
embeds: [{
color: failColor,
title: 'API commands are only available in guilds.',
}],
}).catch((e: Error) => utils.commonLoggers.messageSendError('apiCmd.ts:30', message, e));
return;
}
// Alert users who DM the bot that this command is for guilds only
if (message.guildId === 0n) {
message
.send({
embeds: [
{
color: failColor,
title: 'API commands are only available in guilds.',
},
],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('apiCmd.ts:30', message, e));
return;
}
// Makes sure the user is authenticated to run the API command
if (await hasGuildPermissions(message.authorId, message.guildId, ['ADMINISTRATOR'])) {
switch (apiArg) {
case 'help':
case 'h':
// [[api help
// Shows API help details
apiCommands.help(message);
break;
case 'allow':
case 'block':
case 'enable':
case 'disable':
// [[api allow/block
// Lets a guild admin allow or ban API rolls from happening in said guild
apiCommands.allowBlock(message, apiArg);
break;
case 'delete':
// [[api delete
// Lets a guild admin delete their server from the database
apiCommands.deleteGuild(message);
break;
case 'status':
// [[api status
// Lets a guild admin check the status of API rolling in said guild
apiCommands.status(message);
break;
case 'show-warn':
case 'hide-warn':
// [[api show-warn/hide-warn
// Lets a guild admin decide if the API warning should be shown on messages from the API
apiCommands.showHideWarn(message, apiArg);
break;
default:
break;
}
} else {
message.send({
embeds: [{
color: failColor,
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).',
}],
}).catch((e: Error) => utils.commonLoggers.messageSendError('apiCmd.ts:77', message, e));
}
// Makes sure the user is authenticated to run the API command
if (await hasGuildPermissions(message.authorId, message.guildId, ['ADMINISTRATOR'])) {
switch (apiArg) {
case 'help':
case 'h':
// [[api help
// Shows API help details
apiCommands.help(message);
break;
case 'allow':
case 'block':
case 'enable':
case 'disable':
// [[api allow/block
// Lets a guild admin allow or ban API rolls from happening in said guild
apiCommands.allowBlock(message, apiArg);
break;
case 'delete':
// [[api delete
// Lets a guild admin delete their server from the database
apiCommands.deleteGuild(message);
break;
case 'status':
// [[api status
// Lets a guild admin check the status of API rolling in said guild
apiCommands.status(message);
break;
case 'show-warn':
case 'hide-warn':
// [[api show-warn/hide-warn
// Lets a guild admin decide if the API warning should be shown on messages from the API
apiCommands.showHideWarn(message, apiArg);
break;
default:
break;
}
} else {
message
.send({
embeds: [
{
color: failColor,
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).',
},
],
})
.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 {
// Discordeno deps
DiscordenoMessage,
// Discordeno deps
DiscordenoMessage,
} from '../../../deps.ts';
import { generateApiFailed, generateApiSuccess } from '../../commandUtils.ts';
import utils from '../../utils.ts';
export const allowBlock = async (message: DiscordenoMessage, apiArg: string) => {
let errorOutInitial = false;
const guildQuery = await dbClient.query(`SELECT guildid, channelid FROM allowed_guilds WHERE guildid = ? AND channelid = ?`, [message.guildId, message.channelId]).catch((e0) => {
utils.commonLoggers.dbError('allowBlock.ts:15', 'query', e0);
message.send(generateApiFailed(apiArg)).catch((e: Error) => utils.commonLoggers.messageSendError('allowBlock.ts:16', message, e));
errorOutInitial = true;
});
if (errorOutInitial) return;
let errorOutInitial = false;
const guildQuery = await dbClient
.query(`SELECT guildid, channelid FROM allowed_guilds WHERE guildid = ? AND channelid = ?`, [message.guildId, message.channelId])
.catch((e0) => {
utils.commonLoggers.dbError('allowBlock.ts:15', 'query', e0);
message.send(generateApiFailed(apiArg)).catch((e: Error) => utils.commonLoggers.messageSendError('allowBlock.ts:16', message, e));
errorOutInitial = true;
});
if (errorOutInitial) return;
let errorOut = false;
if (guildQuery.length === 0) {
// 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(
(e0) => {
utils.commonLoggers.dbError('allowBlock:26', 'insert into', e0);
message.send(generateApiFailed(apiArg)).catch((e: Error) => utils.commonLoggers.messageSendError('allowBlock.ts:27', message, e));
errorOut = true;
},
);
} else {
// Since guild is in our DB, update it
await dbClient.execute(`UPDATE allowed_guilds SET active = ? WHERE guildid = ? AND channelid = ?`, [(apiArg === 'allow' || apiArg === 'enable') ? 1 : 0, message.guildId, 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;
let errorOut = false;
if (guildQuery.length === 0) {
// 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((e0) => {
utils.commonLoggers.dbError('allowBlock:26', 'insert into', e0);
message.send(generateApiFailed(apiArg)).catch((e: Error) => utils.commonLoggers.messageSendError('allowBlock.ts:27', message, e));
errorOut = true;
});
} else {
// Since guild is in our DB, update it
await dbClient
.execute(`UPDATE allowed_guilds SET active = ? WHERE guildid = ? AND channelid = ?`, [
apiArg === 'allow' || apiArg === 'enable' ? 1 : 0,
message.guildId,
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
message.send(generateApiSuccess(`${apiArg}ed`)).catch((e: Error) => utils.commonLoggers.messageSendError('allowBlock.ts:44', message, e));
// 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));
};

View File

@ -1,31 +1,39 @@
import { dbClient } from '../../db.ts';
import dbClient from '../../db/client.ts';
import {
// Discordeno deps
DiscordenoMessage,
// Discordeno deps
DiscordenoMessage,
} from '../../../deps.ts';
import { failColor, successColor } from '../../commandUtils.ts';
import utils from '../../utils.ts';
export const deleteGuild = async (message: DiscordenoMessage) => {
let errorOut = false;
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);
message.send({
embeds: [{
color: failColor,
title: 'Failed to delete this guild from the database.',
description: 'If this issue persists, please report this to the developers.',
}],
}).catch((e: Error) => utils.commonLoggers.messageSendError('deleteGuild.ts:22', message, e));
errorOut = true;
});
if (errorOut) return;
let errorOut = false;
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);
message
.send({
embeds: [
{
color: failColor,
title: 'Failed to delete this guild from the database.',
description: 'If this issue persists, please report this to the developers.',
},
],
})
.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
message.send({
embeds: [{
color: successColor,
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));
// We won't get here if there's any errors, so we know it has bee successful, so report as such
message
.send({
embeds: [
{
color: successColor,
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 {
// Discordeno deps
DiscordenoMessage,
// Discordeno deps
DiscordenoMessage,
} from '../../../deps.ts';
import { generateApiFailed, generateApiSuccess } from '../../commandUtils.ts';
import utils from '../../utils.ts';
export const showHideWarn = async (message: DiscordenoMessage, apiArg: string) => {
let errorOutInitial = false;
const guildQuery = await dbClient.query(`SELECT guildid, channelid FROM allowed_guilds WHERE guildid = ? AND channelid = ?`, [message.guildId, message.channelId]).catch((e0) => {
utils.commonLoggers.dbError('showHideWarn.ts:15', 'query', e0);
message.send(generateApiFailed(`${apiArg} on`)).catch((e: Error) => utils.commonLoggers.messageSendError('showHideWarn.ts:16', message, e));
errorOutInitial = true;
});
if (errorOutInitial) return;
let errorOutInitial = false;
const guildQuery = await dbClient
.query(`SELECT guildid, channelid FROM allowed_guilds WHERE guildid = ? AND channelid = ?`, [message.guildId, message.channelId])
.catch((e0) => {
utils.commonLoggers.dbError('showHideWarn.ts:15', 'query', e0);
message.send(generateApiFailed(`${apiArg} on`)).catch((e: Error) => utils.commonLoggers.messageSendError('showHideWarn.ts:16', message, e));
errorOutInitial = true;
});
if (errorOutInitial) return;
let errorOut = false;
if (guildQuery.length === 0) {
// 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) => {
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));
errorOut = true;
});
} else {
// 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) => {
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;
let errorOut = false;
if (guildQuery.length === 0) {
// 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) => {
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));
errorOut = true;
});
} else {
// 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) => {
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
message.send(generateApiSuccess(apiArg)).catch((e: Error) => utils.commonLoggers.messageSendError('showHideWarn.ts:40', message, e));
// 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));
};

View File

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

View File

@ -1,48 +1,53 @@
import config from '../../config.ts';
import { dbClient, queries } from '../db.ts';
import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import {
// Discordeno deps
DiscordenoMessage,
// Discordeno deps
DiscordenoMessage,
} from '../../deps.ts';
import auditCommands from './auditCmd/_index.ts';
import { failColor } from '../commandUtils.ts';
import utils from '../utils.ts';
export const audit = async (message: DiscordenoMessage, args: string[]) => {
// 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));
// 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));
// Local apiArg in lowercase
const auditArg = (args[0] || 'help').toLowerCase();
// Local apiArg in lowercase
const auditArg = (args[0] || 'help').toLowerCase();
// Makes sure the user is authenticated to run the API command
if (message.authorId === config.api.admin) {
switch (auditArg) {
case 'help':
case 'h':
// [[audit help or [[audit h
// Shows API help details
auditCommands.auditHelp(message);
break;
case 'db':
// [[audit db
// Shows current DB table sizes
auditCommands.auditDB(message);
break;
case 'guilds':
// [[audit guilds
// Shows breakdown of guilds and detials on them
auditCommands.auditGuilds(message);
break;
default:
break;
}
} else {
message.send({
embeds: [{
color: failColor,
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));
}
// Makes sure the user is authenticated to run the API command
if (message.authorId === config.api.admin) {
switch (auditArg) {
case 'help':
case 'h':
// [[audit help or [[audit h
// Shows API help details
auditCommands.auditHelp(message);
break;
case 'db':
// [[audit db
// Shows current DB table sizes
auditCommands.auditDB(message);
break;
case 'guilds':
// [[audit guilds
// Shows breakdown of guilds and detials on them
auditCommands.auditGuilds(message);
break;
default:
break;
}
} else {
message
.send({
embeds: [
{
color: failColor,
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 {
// Discordeno deps
DiscordenoMessage,
EmbedField,
// Discordeno deps
DiscordenoMessage,
EmbedField,
} from '../../../deps.ts';
import { infoColor2 } from '../../commandUtils.ts';
import { compilingStats } from '../../commonEmbeds.ts';
import utils from '../../utils.ts';
export const auditDB = async (message: DiscordenoMessage) => {
try {
const m = await message.send(compilingStats);
try {
const m = await message.send(compilingStats);
// Get DB statistics
const auditQuery = await dbClient.query(`SELECT * FROM db_size;`).catch((e) => utils.commonLoggers.dbError('auditDB.ts:19', 'query', e));
// Get DB statistics
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
const embedFields: Array<EmbedField> = [];
auditQuery.forEach((row: any) => {
embedFields.push({
name: `${row.table}`,
value: `**Size:** ${row.size} MB
// 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> = [];
auditQuery.forEach((row: any) => {
embedFields.push({
name: `${row.table}`,
value: `**Size:** ${row.size} MB
**Rows:** ${row.rows}`,
inline: true,
});
});
inline: true,
});
});
// Send the results
m.edit({
embeds: [{
color: infoColor2,
title: 'Database Audit',
description: 'Lists all tables with their current size and row count.',
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);
}
// Send the results
m.edit({
embeds: [
{
color: infoColor2,
title: 'Database Audit',
description: 'Lists all tables with their current size and row count.',
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);
}
};

View File

@ -1,11 +1,12 @@
import config from '../../config.ts';
import { dbClient, queries } from '../db.ts';
import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import {
// Discordeno deps
DiscordenoMessage,
// Log4Deno deps
log,
LT,
// Discordeno deps
DiscordenoMessage,
// Log4Deno deps
log,
LT,
} from '../../deps.ts';
import { EmojiConf } from '../mod.d.ts';
import utils from '../utils.ts';
@ -13,28 +14,30 @@ import utils from '../utils.ts';
const allEmojiAliases: string[] = [];
config.emojis.forEach((emji: EmojiConf) => {
allEmojiAliases.push(...emji.aliases);
allEmojiAliases.push(...emji.aliases);
});
export const emoji = (message: DiscordenoMessage, command: string) => {
// shortcut
if (allEmojiAliases.indexOf(command)) {
// Start looping thru the possible emojis
config.emojis.some((emji: EmojiConf) => {
log(LT.LOG, `Checking if command was emoji ${JSON.stringify(emji)}`);
// If a match gets found
if (emji.aliases.indexOf(command || '') > -1) {
// 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));
// shortcut
if (allEmojiAliases.indexOf(command)) {
// Start looping thru the possible emojis
config.emojis.some((emji: EmojiConf) => {
log(LT.LOG, `Checking if command was emoji ${JSON.stringify(emji)}`);
// If a match gets found
if (emji.aliases.indexOf(command || '') > -1) {
// 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));
// Send the needed emoji
message.send(`<${emji.animated ? 'a' : ''}:${emji.name}:${emji.id}>`).catch((e: Error) => utils.commonLoggers.messageSendError('emoji.ts:33', message, e));
// And attempt to delete if needed
if (emji.deleteSender) {
message.delete().catch((e: Error) => utils.commonLoggers.messageDeleteError('emoji.ts:36', message, e));
}
return true;
}
});
}
// Send the needed emoji
message
.send(`<${emji.animated ? 'a' : ''}:${emji.name}:${emji.id}>`)
.catch((e: Error) => utils.commonLoggers.messageSendError('emoji.ts:33', message, e));
// And attempt to delete if needed
if (emji.deleteSender) {
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 { dbClient, queries } from '../db.ts';
import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import {
// Discordeno deps
DiscordenoMessage,
// Log4Deno deps
log,
LT,
// Discordeno deps
DiscordenoMessage,
// Log4Deno deps
log,
LT,
} from '../../deps.ts';
import { infoColor1 } from '../commandUtils.ts';
import utils from '../utils.ts';
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
dbClient.execute(queries.callIncCnt('mention')).catch((e) => utils.commonLoggers.dbError('handleMentions.ts:17', 'call sproc INC_CNT on', e));
// 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));
message.send({
embeds: [{
color: infoColor1,
title: `Hello! I am ${config.name}!`,
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\`.
message
.send({
embeds: [
{
color: infoColor1,
title: `Hello! I am ${config.name}!`,
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.`,
}],
}],
}).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 {
// Discordeno deps
DiscordenoMessage,
// Discordeno deps
DiscordenoMessage,
} from '../../deps.ts';
import config from '../../config.ts';
import { failColor, infoColor2 } from '../commandUtils.ts';
@ -9,33 +10,41 @@ import utils from '../utils.ts';
import intervals from '../intervals.ts';
export const heatmap = async (message: DiscordenoMessage) => {
// 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));
// 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));
if (config.api.enable) {
message.send({
embeds: [{
title: 'Roll Heatmap',
description: `Over time, this image will show a nice pattern of when rolls are requested the most.
if (config.api.enable) {
message
.send({
embeds: [
{
title: 'Roll Heatmap',
description: `Over time, this image will show a nice pattern of when rolls are requested the most.
Least Rolls: ${intervals.getMinRollCnt()}
Most Rolls: ${intervals.getMaxRollCnt()}`,
footer: {
text: 'Data is shown in US Eastern Time. | This heatmap uses data starting 6/26/2022.',
},
color: infoColor2,
image: {
url: `${config.api.publicDomain}api/heatmap.png`,
},
}],
}).catch((e) => utils.commonLoggers.messageSendError('heatmap.ts:21', message, e));
} else {
message.send({
embeds: [{
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));
}
footer: {
text: 'Data is shown in US Eastern Time. | This heatmap uses data starting 6/26/2022.',
},
color: infoColor2,
image: {
url: `${config.api.publicDomain}api/heatmap.png`,
},
},
],
})
.catch((e) => utils.commonLoggers.messageSendError('heatmap.ts:21', message, e));
} else {
message
.send({
embeds: [
{
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 { dbClient, queries } from '../db.ts';
import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import {
// Discordeno deps
DiscordenoMessage,
// Discordeno deps
DiscordenoMessage,
} from '../../deps.ts';
import { infoColor2 } from '../commandUtils.ts';
import utils from '../utils.ts';
export const help = (message: DiscordenoMessage) => {
// 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));
// 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));
message.send({
embeds: [{
color: infoColor2,
title: 'The Artificer\'s Available Commands:',
fields: [
{
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}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}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}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}popcat\``,
value: 'Popcat',
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}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-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`,
inline: true,
},
],
}],
}).catch((e: Error) => utils.commonLoggers.messageSendError('help.ts:82', message, e));
message
.send({
embeds: [
{
color: infoColor2,
title: "The Artificer's Available Commands:",
fields: [
{
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}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}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}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}popcat\``,
value: 'Popcat',
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}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-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`,
inline: true,
},
],
},
],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('help.ts:82', message, e));
};

View File

@ -1,24 +1,29 @@
import config from '../../config.ts';
import { dbClient, queries } from '../db.ts';
import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import {
// Discordeno deps
DiscordenoMessage,
// Discordeno deps
DiscordenoMessage,
} from '../../deps.ts';
import { infoColor2 } from '../commandUtils.ts';
import utils from '../utils.ts';
export const info = (message: DiscordenoMessage) => {
// 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));
// 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));
message.send({
embeds: [{
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.
message
.send({
embeds: [
{
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/).
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).`,
}],
}).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 { dbClient, ignoreList, queries } from '../db.ts';
import dbClient from '../db/client.ts';
import { queries, ignoreList } from '../db/common.ts';
import {
// Discordeno deps
DiscordenoMessage,
// Discordeno deps
DiscordenoMessage,
} from '../../deps.ts';
import { failColor, successColor } from '../commandUtils.ts';
import utils from '../utils.ts';
export const optIn = async (message: DiscordenoMessage) => {
// 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));
// 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));
const idIdx = ignoreList.indexOf(message.authorId);
if (idIdx !== -1) {
try {
ignoreList.splice(idIdx, 1);
await dbClient.execute('DELETE FROM ignore_list WHERE userid = ?', [message.authorId]);
const idIdx = ignoreList.indexOf(message.authorId);
if (idIdx !== -1) {
try {
ignoreList.splice(idIdx, 1);
await dbClient.execute('DELETE FROM ignore_list WHERE userid = ?', [message.authorId]);
message.reply({
embeds: [{
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:
message
.reply({
embeds: [
{
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\``,
}],
}).catch((e: Error) => utils.commonLoggers.messageSendError('optIn.ts:27', message, e));
} catch (err) {
message.reply({
embeds: [{
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));
}
}
},
],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('optIn.ts:27', message, e));
} catch (err) {
message
.reply({
embeds: [
{
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 { dbClient, ignoreList, queries } from '../db.ts';
import dbClient from '../db/client.ts';
import { queries, ignoreList } from '../db/common.ts';
import {
// Discordeno deps
DiscordenoMessage,
// Discordeno deps
DiscordenoMessage,
} from '../../deps.ts';
import { failColor, successColor } from '../commandUtils.ts';
import utils from '../utils.ts';
export const optOut = async (message: DiscordenoMessage) => {
// 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));
// 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));
try {
ignoreList.push(message.authorId);
await dbClient.execute('INSERT INTO ignore_list(userid) values(?)', [message.authorId]);
try {
ignoreList.push(message.authorId);
await dbClient.execute('INSERT INTO ignore_list(userid) values(?)', [message.authorId]);
message.reply({
embeds: [{
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:
message
.reply({
embeds: [
{
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\``,
}],
}).catch((e: Error) => utils.commonLoggers.messageSendError('optOut.ts:25', message, e));
} catch (err) {
message.reply({
embeds: [{
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));
}
},
],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('optOut.ts:25', message, e));
} catch (err) {
message
.reply({
embeds: [
{
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 {
// Discordeno deps
DiscordenoMessage,
// Discordeno deps
DiscordenoMessage,
} from '../../deps.ts';
import { generatePing } from '../commandUtils.ts';
import utils from '../utils.ts';
export const ping = async (message: DiscordenoMessage) => {
// 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));
// 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));
// Calculates ping between sending a message and editing it, giving a nice round-trip latency.
try {
const m = await message.send(generatePing(-1));
m.edit(generatePing(m.timestamp - message.timestamp));
} catch (e) {
utils.commonLoggers.messageSendError('ping.ts:23', message, e);
}
// Calculates ping between sending a message and editing it, giving a nice round-trip latency.
try {
const m = await message.send(generatePing(-1));
m.edit(generatePing(m.timestamp - message.timestamp));
} catch (e) {
utils.commonLoggers.messageSendError('ping.ts:23', message, e);
}
};

View File

@ -1,31 +1,37 @@
import config from '../../config.ts';
import { dbClient, queries } from '../db.ts';
import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import {
// Discordeno deps
DiscordenoMessage,
// Discordeno deps
DiscordenoMessage,
} from '../../deps.ts';
import { infoColor1 } from '../commandUtils.ts';
import utils from '../utils.ts';
export const privacy = (message: DiscordenoMessage) => {
// 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));
// 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));
message.send({
embeds: [{
color: infoColor1,
title: 'Privacy Policy',
fields: [{
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.
message
.send({
embeds: [
{
color: infoColor1,
title: 'Privacy Policy',
fields: [
{
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).
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.`,
}],
}],
}).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 { dbClient, queries } from '../db.ts';
import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import {
// Discordeno deps
DiscordenoMessage,
// Discordeno deps
sendMessage,
// Discordeno deps
DiscordenoMessage,
// Discordeno deps
sendMessage,
} from '../../deps.ts';
import { failColor, generateReport, successColor } from '../commandUtils.ts';
import utils from '../utils.ts';
export const report = (message: DiscordenoMessage, args: string[]) => {
// 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));
// 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));
if (args.join(' ')) {
sendMessage(config.reportChannel, generateReport(args.join(' '))).catch((e: Error) => utils.commonLoggers.messageSendError('report.ts:22', message, e));
message.send({
embeds: [{
color: successColor,
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).`,
}],
}).catch((e: Error) => utils.commonLoggers.messageSendError('report.ts:29', message, e));
} else {
message.send({
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));
}
if (args.join(' ')) {
sendMessage(config.reportChannel, generateReport(args.join(' '))).catch((e: Error) => utils.commonLoggers.messageSendError('report.ts:22', message, e));
message
.send({
embeds: [
{
color: successColor,
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).`,
},
],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('report.ts:29', message, e));
} else {
message
.send({
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 {
// Discordeno deps
DiscordenoMessage,
// Discordeno deps
DiscordenoMessage,
} from '../../deps.ts';
import { infoColor2 } from '../commandUtils.ts';
import utils from '../utils.ts';
export const rip = (message: DiscordenoMessage) => {
// 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));
// 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));
message.send({
embeds: [{
color: infoColor2,
title: 'The Artificer was built in memory of my Grandmother, Babka',
description: `With much love, Ean
message
.send({
embeds: [
{
color: infoColor2,
title: 'The Artificer was built in memory of my Grandmother, Babka',
description: `With much love, Ean
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 { DEVMODE } from '../../flags.ts';
import { dbClient, queries } from '../db.ts';
import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import {
// Discordeno deps
DiscordenoMessage,
// Log4Deno deps
log,
LT,
// Discordeno deps
DiscordenoMessage,
// Log4Deno deps
log,
LT,
} from '../../deps.ts';
import { rollingEmbed, warnColor } from '../commandUtils.ts';
import rollFuncs from './roll/_index.ts';
@ -15,49 +16,51 @@ import { QueuedRoll } from '../mod.d.ts';
import utils from '../utils.ts';
export const roll = async (message: DiscordenoMessage, args: string[], command: string) => {
// Light telemetry to see how many times a command is being run
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.callIncHeatmap(currDateTime)).catch((e) => utils.commonLoggers.dbError('roll.ts:21', 'update', e));
// Light telemetry to see how many times a command is being run
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.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 && message.guildId !== config.devServer) {
message.send({
embeds: [{
color: warnColor,
title: 'Command is in development, please try again later.',
}],
}).catch((e: Error) => utils.commonLoggers.messageSendError('roll.ts:30', message, e));
return;
}
// If DEVMODE is on, only allow this command to be used in the devServer
if (DEVMODE && message.guildId !== config.devServer) {
message
.send({
embeds: [
{
color: warnColor,
title: 'Command is in development, please try again later.',
},
],
})
.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
try {
const originalCommand = `${config.prefix}${command} ${args.join(' ')}`;
// Rest of this command is in a try-catch to protect all sends/edits from erroring out
try {
const originalCommand = `${config.prefix}${command} ${args.join(' ')}`;
const m = await message.reply(rollingEmbed);
const m = await message.reply(rollingEmbed);
// Get modifiers from command
const modifiers = rollFuncs.getModifiers(m, args, command, originalCommand);
// Get modifiers from command
const modifiers = rollFuncs.getModifiers(m, args, command, originalCommand);
// Return early if the modifiers were invalid
if (!modifiers.valid) {
return;
}
// Return early if the modifiers were invalid
if (!modifiers.valid) {
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
const rollCmd = message.content.substring(2);
// 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);
queueRoll(
<QueuedRoll> {
apiRoll: false,
dd: { m, message },
rollCmd,
modifiers,
originalCommand,
},
);
} catch (e) {
log(LT.ERROR, `Undandled Error: ${JSON.stringify(e)}`);
}
queueRoll(<QueuedRoll>{
apiRoll: false,
dd: { m, message },
rollCmd,
modifiers,
originalCommand,
});
} catch (e) {
log(LT.ERROR, `Undandled Error: ${JSON.stringify(e)}`);
}
};

View File

@ -1,117 +1,130 @@
import config from '../../../config.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 {
// Discordeno deps
DiscordenoMessage,
// Log4Deno deps
log,
LT,
// Discordeno deps
DiscordenoMessage,
// Log4Deno deps
log,
LT,
} from '../../../deps.ts';
import { generateRollError } from '../../commandUtils.ts';
import { RollModifiers } from '../../mod.d.ts';
import utils from '../../utils.ts';
export const getModifiers = (m: DiscordenoMessage, args: string[], command: string, originalCommand: string): RollModifiers => {
const errorType = 'Modifiers invalid:';
const modifiers: RollModifiers = {
noDetails: false,
superNoDetails: false,
spoiler: '',
maxRoll: false,
nominalRoll: false,
gmRoll: false,
gms: [],
order: '',
valid: false,
count: false,
apiWarn: '',
};
const errorType = 'Modifiers invalid:';
const modifiers: RollModifiers = {
noDetails: false,
superNoDetails: false,
spoiler: '',
maxRoll: false,
nominalRoll: false,
gmRoll: false,
gms: [],
order: '',
valid: false,
count: false,
apiWarn: '',
};
// 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++) {
log(LT.LOG, `Checking ${command}${args.join(' ')} for command modifiers ${i}`);
let defaultCase = false;
switch (args[i].toLowerCase()) {
case '-c':
modifiers.count = true;
break;
case '-nd':
modifiers.noDetails = true;
break;
case '-snd':
modifiers.superNoDetails = true;
break;
case '-s':
modifiers.spoiler = '||';
break;
case '-m':
modifiers.maxRoll = true;
break;
case '-n':
modifiers.nominalRoll = true;
break;
case '-gm':
modifiers.gmRoll = true;
// 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++) {
log(LT.LOG, `Checking ${command}${args.join(' ')} for command modifiers ${i}`);
let defaultCase = false;
switch (args[i].toLowerCase()) {
case '-c':
modifiers.count = true;
break;
case '-nd':
modifiers.noDetails = true;
break;
case '-snd':
modifiers.superNoDetails = true;
break;
case '-s':
modifiers.spoiler = '||';
break;
case '-m':
modifiers.maxRoll = true;
break;
case '-n':
modifiers.nominalRoll = true;
break;
case '-gm':
modifiers.gmRoll = true;
// -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('<@')) {
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
modifiers.gms.push(args[i + 1].replace(/!/g, ''));
args.splice(i + 1, 1);
}
if (modifiers.gms.length < 1) {
// 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));
// -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('<@')) {
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
modifiers.gms.push(args[i + 1].replace(/!/g, ''));
args.splice(i + 1, 1);
}
if (modifiers.gms.length < 1) {
// 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)
);
if (DEVMODE && config.logRolls) {
// 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));
}
return modifiers;
}
break;
case '-o':
// Shift the -o out of the array so the next item is the direction
args.splice(i, 1);
if (DEVMODE && config.logRolls) {
// 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));
}
return modifiers;
}
break;
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 -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));
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
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 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));
}
return modifiers;
}
if (DEVMODE && config.logRolls) {
// 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));
}
return modifiers;
}
modifiers.order = args[i].toLowerCase()[0];
break;
default:
// Default case should not mess with the array
defaultCase = true;
break;
}
modifiers.order = args[i].toLowerCase()[0];
break;
default:
// Default case should not mess with the array
defaultCase = true;
break;
}
if (!defaultCase) {
args.splice(i, 1);
i--;
}
}
if (!defaultCase) {
args.splice(i, 1);
i--;
}
}
// maxRoll and nominalRoll cannot both be on, throw an error
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));
// maxRoll and nominalRoll cannot both be on, throw an error
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)
);
if (DEVMODE && config.logRolls) {
// 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));
}
return modifiers;
}
if (DEVMODE && config.logRolls) {
// 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));
}
return modifiers;
}
modifiers.valid = true;
return modifiers;
modifiers.valid = true;
return modifiers;
};

View File

@ -1,71 +1,75 @@
import config from '../../config.ts';
import { dbClient, queries } from '../db.ts';
import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import {
// Discordeno deps
DiscordenoMessage,
// Discordeno deps
DiscordenoMessage,
} from '../../deps.ts';
import { infoColor2 } from '../commandUtils.ts';
import utils from '../utils.ts';
export const rollDecorators = (message: DiscordenoMessage) => {
// 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));
// 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));
message.send({
embeds: [
{
color: infoColor2,
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.
message
.send({
embeds: [
{
color: infoColor2,
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\``,
fields: [
{
name: '`-nd` - No Details',
value: 'Suppresses all details of the requested roll',
inline: true,
},
{
name: '`-snd` - Super No Details',
value: 'Suppresses all details of the requested roll and hides no details message',
inline: true,
},
{
name: '`-s` - Spoiler',
value: 'Spoilers all details of the requested roll',
inline: true,
},
{
name: '`-m` - Maximize Roll',
value: 'Rolls the theoretical maximum roll, cannot be used with -n',
inline: true,
},
{
name: '`-n` - Nominal Roll',
value: 'Rolls the theoretical nominal roll, cannot be used with -m',
inline: true,
},
{
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',
inline: true,
},
{
name: '`-c` - Count Rolls',
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
fields: [
{
name: '`-nd` - No Details',
value: 'Suppresses all details of the requested roll',
inline: true,
},
{
name: '`-snd` - Super No Details',
value: 'Suppresses all details of the requested roll and hides no details message',
inline: true,
},
{
name: '`-s` - Spoiler',
value: 'Spoilers all details of the requested roll',
inline: true,
},
{
name: '`-m` - Maximize Roll',
value: 'Rolls the theoretical maximum roll, cannot be used with -n',
inline: true,
},
{
name: '`-n` - Nominal Roll',
value: 'Rolls the theoretical nominal roll, cannot be used with -m',
inline: true,
},
{
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',
inline: true,
},
{
name: '`-c` - Count Rolls',
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
Available directions:
\`a\` - Ascending (least to greatest)
\`d\` - Descending (greatest to least)`,
inline: true,
},
],
},
],
}).catch((e: Error) => utils.commonLoggers.messageSendError('rollHelp.ts:247', message, e));
inline: true,
},
],
},
],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('rollHelp.ts:247', message, e));
};

View File

@ -1,262 +1,275 @@
import config from '../../config.ts';
import { dbClient, queries } from '../db.ts';
import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import {
// Discordeno deps
DiscordenoMessage,
// Discordeno deps
DiscordenoMessage,
} from '../../deps.ts';
import { infoColor1, infoColor2, successColor } from '../commandUtils.ts';
import utils from '../utils.ts';
export const rollHelp = (message: DiscordenoMessage) => {
// 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));
// 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));
message.send({
embeds: [
{
color: infoColor1,
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.
message
.send({
embeds: [
{
color: infoColor1,
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.
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.`,
},
{
color: infoColor2,
title: 'Roll20 Dice Options:',
fields: [
{
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}\`)`,
},
{
name: '`x` [Optional]',
value: `Number of dice to roll, if omitted, 1 is used
},
{
color: infoColor2,
title: 'Roll20 Dice Options:',
fields: [
{
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}\`)`,
},
{
name: '`x` [Optional]',
value: `Number of dice to roll, if omitted, 1 is used
Additionally, replace \`x\` with \`F\` to roll Fate dice`,
inline: true,
},
{
name: '`dy` [Required]',
value: 'Size of dice to roll, `d20` = 20 sided die',
inline: true,
},
{
name: '`dz` or `dlz` [Optional]',
value: 'Drops the lowest `z` dice, cannot be used with `kz`',
inline: true,
},
{
name: '`kz` or `khz` [Optional]',
value: 'Keeps the highest `z` dice, cannot be used with `dz`',
inline: true,
},
{
name: '`dhz` [Optional]',
value: 'Drops the highest `z` dice, cannot be used with `kz`',
inline: true,
},
{
name: '`klz` [Optional]',
value: 'Keeps the lowest `z` dice, cannot be used with `dz`',
inline: true,
},
{
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`',
inline: true,
},
{
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`',
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`',
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: '`ro<q` [Optional]',
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: '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,
},
{
name: '`csq` or `cs=q` [Optional]',
value: 'Changes crit score to `q`',
inline: true,
},
{
name: '`cs<q` [Optional]',
value: 'Changes crit score to be less than or equal to `q`',
inline: true,
},
{
name: '`cs>q` [Optional]',
value: 'Changes crit score to be greater than or equal to `q`',
inline: true,
},
{
name: '`cfq` or `cf=q` [Optional]',
value: 'Changes crit fail to `q`',
inline: true,
},
{
name: '`cf<q` [Optional]',
value: 'Changes crit fail to be less than or equal to `q`',
inline: true,
},
{
name: '`cf>q` [Optional]',
value: 'Changes crit fail to be greater than or equal to `q`',
inline: true,
},
{
name: '`!` [Optional]',
value: 'Exploding, rolls another `dy` for every crit success',
inline: true,
},
{
name: '`!o` [Optional]',
value: 'Exploding Once, rolls one `dy` for each original crit success',
inline: true,
},
{
name: '`!p` [Optional]',
value: 'Penetrating Explosion, rolls one `dy` for each crit success, but subtracts one from each resulting explosion',
inline: true,
},
{
name: '`!!` [Optional]',
value: 'Compounding Explosion, rolls one `dy` for each crit success, but adds the resulting explosion to the die that caused this explosion',
inline: true,
},
{
name: '`!=u` [Optional]',
value: 'Explode on `u`, rolls another `dy` for every die that lands on `u`',
inline: true,
},
{
name: '`!>u` [Optional]',
value: 'Explode on `u` and greater, rolls another `dy` for every die that lands on `u` or greater',
inline: true,
},
],
},
{
color: infoColor2,
fields: [
{
name: '`!<u` [Optional]',
value: 'Explode on `u` and under, rolls another `dy` for every die that lands on `u` or less',
inline: true,
},
{
name: '`!o=u` [Optional]',
value: 'Explodes Once on `u`, rolls another `dy` for each original die that landed on `u`',
inline: true,
},
{
name: '`!o>u` [Optional]',
value: 'Explode Once on `u` and greater, rolls another `dy` for each original die that landed on `u` or greater',
inline: true,
},
{
name: '`!o<u` [Optional]',
value: 'Explode Once on `u` and under, rolls another `dy` for each original die that landed on `u` or less',
inline: true,
},
{
name: '`!p=u` [Optional]',
value: 'Penetrating Explosion on `u`, rolls one `dy` for each die that lands on `u`, but subtracts one from each resulting explosion',
inline: true,
},
{
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',
inline: true,
},
{
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',
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',
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` 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,
},
],
},
{
color: infoColor1,
title: 'Custom Dice Options',
fields: [
{
name: 'CWOD Rolling',
value: `\`${config.prefix}xcwody${config.postfix}\`
inline: true,
},
{
name: '`dy` [Required]',
value: 'Size of dice to roll, `d20` = 20 sided die',
inline: true,
},
{
name: '`dz` or `dlz` [Optional]',
value: 'Drops the lowest `z` dice, cannot be used with `kz`',
inline: true,
},
{
name: '`kz` or `khz` [Optional]',
value: 'Keeps the highest `z` dice, cannot be used with `dz`',
inline: true,
},
{
name: '`dhz` [Optional]',
value: 'Drops the highest `z` dice, cannot be used with `kz`',
inline: true,
},
{
name: '`klz` [Optional]',
value: 'Keeps the lowest `z` dice, cannot be used with `dz`',
inline: true,
},
{
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`',
inline: true,
},
{
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`',
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`',
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: '`ro<q` [Optional]',
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:
'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,
},
{
name: '`csq` or `cs=q` [Optional]',
value: 'Changes crit score to `q`',
inline: true,
},
{
name: '`cs<q` [Optional]',
value: 'Changes crit score to be less than or equal to `q`',
inline: true,
},
{
name: '`cs>q` [Optional]',
value: 'Changes crit score to be greater than or equal to `q`',
inline: true,
},
{
name: '`cfq` or `cf=q` [Optional]',
value: 'Changes crit fail to `q`',
inline: true,
},
{
name: '`cf<q` [Optional]',
value: 'Changes crit fail to be less than or equal to `q`',
inline: true,
},
{
name: '`cf>q` [Optional]',
value: 'Changes crit fail to be greater than or equal to `q`',
inline: true,
},
{
name: '`!` [Optional]',
value: 'Exploding, rolls another `dy` for every crit success',
inline: true,
},
{
name: '`!o` [Optional]',
value: 'Exploding Once, rolls one `dy` for each original crit success',
inline: true,
},
{
name: '`!p` [Optional]',
value: 'Penetrating Explosion, rolls one `dy` for each crit success, but subtracts one from each resulting explosion',
inline: true,
},
{
name: '`!!` [Optional]',
value: 'Compounding Explosion, rolls one `dy` for each crit success, but adds the resulting explosion to the die that caused this explosion',
inline: true,
},
{
name: '`!=u` [Optional]',
value: 'Explode on `u`, rolls another `dy` for every die that lands on `u`',
inline: true,
},
{
name: '`!>u` [Optional]',
value: 'Explode on `u` and greater, rolls another `dy` for every die that lands on `u` or greater',
inline: true,
},
],
},
{
color: infoColor2,
fields: [
{
name: '`!<u` [Optional]',
value: 'Explode on `u` and under, rolls another `dy` for every die that lands on `u` or less',
inline: true,
},
{
name: '`!o=u` [Optional]',
value: 'Explodes Once on `u`, rolls another `dy` for each original die that landed on `u`',
inline: true,
},
{
name: '`!o>u` [Optional]',
value: 'Explode Once on `u` and greater, rolls another `dy` for each original die that landed on `u` or greater',
inline: true,
},
{
name: '`!o<u` [Optional]',
value: 'Explode Once on `u` and under, rolls another `dy` for each original die that landed on `u` or less',
inline: true,
},
{
name: '`!p=u` [Optional]',
value: 'Penetrating Explosion on `u`, rolls one `dy` for each die that lands on `u`, but subtracts one from each resulting explosion',
inline: true,
},
{
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',
inline: true,
},
{
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',
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',
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` 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,
},
],
},
{
color: infoColor1,
title: 'Custom Dice Options',
fields: [
{
name: 'CWOD Rolling',
value: `\`${config.prefix}xcwody${config.postfix}\`
\`x\` - Number of CWOD dice to roll
\`y\` - Difficulty to roll at`,
inline: true,
},
{
name: 'OVA Rolling',
value: `\`${config.prefix}xovady${config.postfix}\`
inline: true,
},
{
name: 'OVA Rolling',
value: `\`${config.prefix}xovady${config.postfix}\`
\`x\` - Number of OVA dice to roll
\`y\` - Size of the die to roll (defaults to 6 if omitted)`,
inline: true,
},
],
},
{
color: successColor,
title: 'Results Formatting:',
description: 'The results have some formatting applied on them to provide details on what happened during this roll.',
fields: [
{
name: 'Bold',
value: 'Critical successes will be **bolded**.',
inline: true,
},
{
name: 'Underline',
value: 'Critical fails will be __underlined__.',
inline: true,
},
{
name: 'Strikethrough',
value: 'Rolls that were dropped or rerolled ~~crossed out~~.',
inline: true,
},
{
name: 'Exclamation mark (`!`)',
value: 'Rolls that were caused by an explosion have an exclamation mark (`!`) after them.',
inline: true,
},
],
},
],
}).catch((e: Error) => utils.commonLoggers.messageSendError('rollHelp.ts:247', message, e));
inline: true,
},
],
},
{
color: successColor,
title: 'Results Formatting:',
description: 'The results have some formatting applied on them to provide details on what happened during this roll.',
fields: [
{
name: 'Bold',
value: 'Critical successes will be **bolded**.',
inline: true,
},
{
name: 'Underline',
value: 'Critical fails will be __underlined__.',
inline: true,
},
{
name: 'Strikethrough',
value: 'Rolls that were dropped or rerolled ~~crossed out~~.',
inline: true,
},
{
name: 'Exclamation mark (`!`)',
value: 'Rolls that were caused by an explosion have an exclamation mark (`!`) after them.',
inline: true,
},
],
},
],
})
.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 {
// Discordeno deps
cache,
cacheHandlers,
DiscordenoMessage,
// Discordeno deps
cache,
cacheHandlers,
DiscordenoMessage,
} from '../../deps.ts';
import { generateStats } from '../commandUtils.ts';
import { compilingStats } from '../commonEmbeds.ts';
import utils from '../utils.ts';
export const stats = async (message: DiscordenoMessage) => {
// 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));
// 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));
try {
const m = await message.send(compilingStats);
try {
const m = await message.send(compilingStats);
// 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 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));
const rolls = BigInt(rollQuery[0].count);
const rollRate = parseFloat(rollQuery[0].hourlyRate);
const total = BigInt(totalQuery[0].count);
const totalRate = parseFloat(totalQuery[0].hourlyRate);
// 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 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));
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 cachedChannels = await cacheHandlers.size('channels');
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((
e: Error,
) => utils.commonLoggers.messageEditError('stats.ts:38', m, e));
} catch (e) {
utils.commonLoggers.messageSendError('stats.ts:41', message, e);
}
const cachedGuilds = await cacheHandlers.size('guilds');
const cachedChannels = await cacheHandlers.size('channels');
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((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 { dbClient, queries } from '../db.ts';
import dbClient from '../db/client.ts';
import { queries } from '../db/common.ts';
import {
// Discordeno deps
DiscordenoMessage,
// Discordeno deps
DiscordenoMessage,
} from '../../deps.ts';
import { infoColor1 } from '../commandUtils.ts';
import utils from '../utils.ts';
export const version = (message: DiscordenoMessage) => {
// 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));
// 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));
message.send({
embeds: [{
color: infoColor1,
title: `My current version is ${config.version}`,
}],
}).catch((e: Error) => utils.commonLoggers.messageSendError('version.ts:24', message, e));
message
.send({
embeds: [
{
color: infoColor1,
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 { dbClient } from '../../db.ts';
import dbClient from '../../db/client.ts';
import {
// nanoid deps
nanoid,
// Discordeno deps
sendMessage,
// nanoid deps
nanoid,
// Discordeno deps
sendMessage,
} from '../../../deps.ts';
import { generateApiDeleteEmail } from '../../commandUtils.ts';
import utils from '../../utils.ts';
import stdResp from '../stdResponses.ts';
export const apiKeyDelete = async (requestEvent: Deno.RequestEvent, query: Map<string, string>, apiUserid: BigInt, apiUserEmail: string, apiUserDelCode: string) => {
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;
export const apiKeyDelete = async (
requestEvent: Deno.RequestEvent,
query: Map<string, string>,
apiUserid: BigInt,
apiUserEmail: string,
apiUserDelCode: string
) => {
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) => {
utils.commonLoggers.dbError('apiKeyDelete.ts:25', 'insert into', e);
requestEvent.respondWith(stdResp.InternalServerError('Channel Clean Failed.'));
erroredOut = true;
});
if (erroredOut) {
return;
}
await dbClient.execute('DELETE FROM allowed_channels WHERE userid = ?', [apiUserid]).catch((e) => {
utils.commonLoggers.dbError('apiKeyDelete.ts:25', 'insert into', e);
requestEvent.respondWith(stdResp.InternalServerError('Channel Clean Failed.'));
erroredOut = true;
});
if (erroredOut) {
return;
}
await dbClient.execute('DELETE FROM all_keys WHERE userid = ?', [apiUserid]).catch((e) => {
utils.commonLoggers.dbError('apiKeyDelete.ts:34', 'delete from', e);
requestEvent.respondWith(stdResp.InternalServerError('Delete Key Failed.'));
erroredOut = true;
});
if (erroredOut) {
return;
} else {
// Send OK as response to indicate key deletion was successful
requestEvent.respondWith(stdResp.OK('You have been removed from the DB, Goodbye.'));
return;
}
} else {
// Alert API user that they shouldn't be doing this
requestEvent.respondWith(stdResp.Forbidden('Invalid Delete Code.'));
}
} else {
// User does not have their delete code yet, so we need to generate one and email it to them
const deleteCode = await nanoid(10);
await dbClient.execute('DELETE FROM all_keys WHERE userid = ?', [apiUserid]).catch((e) => {
utils.commonLoggers.dbError('apiKeyDelete.ts:34', 'delete from', e);
requestEvent.respondWith(stdResp.InternalServerError('Delete Key Failed.'));
erroredOut = true;
});
if (erroredOut) {
return;
} else {
// Send OK as response to indicate key deletion was successful
requestEvent.respondWith(stdResp.OK('You have been removed from the DB, Goodbye.'));
return;
}
} else {
// Alert API user that they shouldn't be doing this
requestEvent.respondWith(stdResp.Forbidden('Invalid Delete Code.'));
}
} else {
// User does not have their delete code yet, so we need to generate one and email it to them
const deleteCode = await nanoid(10);
let erroredOut = false;
let erroredOut = false;
// Execute the DB modification
await dbClient.execute('UPDATE all_keys SET deleteCode = ? WHERE userid = ?', [deleteCode, apiUserid]).catch((e) => {
utils.commonLoggers.dbError('apiKeyDelete.ts:57', 'update', e);
requestEvent.respondWith(stdResp.InternalServerError('Delete Code Failed'));
erroredOut = true;
});
if (erroredOut) {
return;
}
// Execute the DB modification
await dbClient.execute('UPDATE all_keys SET deleteCode = ? WHERE userid = ?', [deleteCode, apiUserid]).catch((e) => {
utils.commonLoggers.dbError('apiKeyDelete.ts:57', 'update', e);
requestEvent.respondWith(stdResp.InternalServerError('Delete Code Failed'));
erroredOut = true;
});
if (erroredOut) {
return;
}
// "Send" the email
await sendMessage(config.api.email, generateApiDeleteEmail(apiUserEmail, deleteCode)).catch(() => {
requestEvent.respondWith(stdResp.InternalServerError('Failed to send email.'));
erroredOut = true;
});
if (erroredOut) {
return;
} else {
// 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.'));
return;
}
}
} else {
// Alert API user that they shouldn't be doing this
requestEvent.respondWith(stdResp.Forbidden('You can only delete your own key.'));
}
} else {
// Alert API user that they messed up
requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams));
}
// "Send" the email
await sendMessage(config.api.email, generateApiDeleteEmail(apiUserEmail, deleteCode)).catch(() => {
requestEvent.respondWith(stdResp.InternalServerError('Failed to send email.'));
erroredOut = true;
});
if (erroredOut) {
return;
} else {
// 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.'));
return;
}
}
} else {
// Alert API user that they shouldn't be doing this
requestEvent.respondWith(stdResp.Forbidden('You can only delete your own key.'));
}
} else {
// Alert API user that they messed up
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 utils from '../../utils.ts';
export const apiChannel = async (requestEvent: Deno.RequestEvent, query: Map<string, string>, apiUserid: BigInt) => {
if (query.has('user') && ((query.get('user') || '').length > 0)) {
if (apiUserid === BigInt(query.get('user') || '0')) {
// Flag to see if there is an error inside the catch
let erroredOut = false;
if (query.has('user') && (query.get('user') || '').length > 0) {
if (apiUserid === BigInt(query.get('user') || '0')) {
// Flag to see if there is an error inside the catch
let erroredOut = false;
// Get all channels userid has authorized
const dbAllowedChannelQuery = await dbClient.query('SELECT * FROM allowed_channels WHERE userid = ?', [apiUserid]).catch((e) => {
utils.commonLoggers.dbError('apiChannel.ts', 'query', e);
requestEvent.respondWith(stdResp.InternalServerError('Failed to get channels.'));
erroredOut = true;
});
// Get all channels userid has authorized
const dbAllowedChannelQuery = await dbClient.query('SELECT * FROM allowed_channels WHERE userid = ?', [apiUserid]).catch((e) => {
utils.commonLoggers.dbError('apiChannel.ts', 'query', e);
requestEvent.respondWith(stdResp.InternalServerError('Failed to get channels.'));
erroredOut = true;
});
if (erroredOut) {
return;
} else {
// Customized strinification to handle BigInts correctly
const returnChannels = JSON.stringify(dbAllowedChannelQuery, (_key, value) => (typeof value === 'bigint' ? value.toString() : value));
// Send channel list as response
requestEvent.respondWith(stdResp.OK(returnChannels));
return;
}
} else {
// Alert API user that they shouldn't be doing this
requestEvent.respondWith(stdResp.Forbidden('You can only view your own channels.'));
}
} else {
// Alert API user that they messed up
requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams));
}
if (erroredOut) {
return;
} else {
// Customized strinification to handle BigInts correctly
const returnChannels = JSON.stringify(dbAllowedChannelQuery, (_key, value) => (typeof value === 'bigint' ? value.toString() : value));
// Send channel list as response
requestEvent.respondWith(stdResp.OK(returnChannels));
return;
}
} else {
// Alert API user that they shouldn't be doing this
requestEvent.respondWith(stdResp.Forbidden('You can only view your own channels.'));
}
} else {
// Alert API user that they messed up
requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams));
}
};

View File

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

View File

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

View File

@ -1,11 +1,11 @@
import config from '../../../config.ts';
import { dbClient, queries } from '../../db.ts';
import dbClient from '../../db/client.ts';
import {
// Discordeno deps
cache,
// Log4Deno deps
log,
LT,
// Discordeno deps
cache,
// Log4Deno deps
log,
LT,
} from '../../../deps.ts';
import { QueuedRoll, RollModifiers } from '../../mod.d.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}>`;
export const apiRoll = async (requestEvent: Deno.RequestEvent, query: Map<string, string>, apiUserid: BigInt) => {
// Make sure query contains all the needed parts
if (
(query.has('rollstr') && ((query.get('rollstr') || '').length > 0)) && (query.has('channel') && ((query.get('channel') || '').length > 0)) &&
(query.has('user') && ((query.get('user') || '').length > 0))
) {
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;
}
// Make sure query contains all the needed parts
if (
query.has('rollstr') &&
(query.get('rollstr') || '').length > 0 &&
query.has('channel') &&
(query.get('channel') || '').length > 0 &&
query.has('user') &&
(query.get('user') || '').length > 0
) {
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
let authorized = false;
let hideWarn = false;
// Check if user is authenticated to use this endpoint
let authorized = 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
const dbChannelQuery = await dbClient.query('SELECT active, banned FROM allowed_channels WHERE userid = ? AND channelid = ?', [apiUserid, BigInt(query.get('channel') || '0')]);
if (dbChannelQuery.length === 1 && (apiUserid === BigInt(query.get('user') || '0')) && dbChannelQuery[0].active && !dbChannelQuery[0].banned) {
// Get the guild from the channel and make sure user is in said guild
const guild = cache.channels.get(BigInt(query.get('channel') || ''))?.guild;
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'),
]);
// 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'),
]);
if (dbChannelQuery.length === 1 && apiUserid === BigInt(query.get('user') || '0') && dbChannelQuery[0].active && !dbChannelQuery[0].banned) {
// Get the guild from the channel and make sure user is in said guild
const guild = cache.channels.get(BigInt(query.get('channel') || ''))?.guild;
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
if (dbGuildQuery.length === 1 && dbGuildQuery[0].active && !dbGuildQuery[0].banned) {
authorized = true;
hideWarn = dbGuildQuery[0].hidewarn;
}
}
}
// Make sure guild allows API rolls
if (dbGuildQuery.length === 1 && dbGuildQuery[0].active && !dbGuildQuery[0].banned) {
authorized = true;
hideWarn = dbGuildQuery[0].hidewarn;
}
}
}
if (authorized) {
// Rest of this command is in a try-catch to protect all sends/edits from erroring out
try {
// Make sure rollCmd is not undefined
let rollCmd = query.get('rollstr') || '';
const originalCommand = query.get('rollstr');
if (authorized) {
// Rest of this command is in a try-catch to protect all sends/edits from erroring out
try {
// Make sure rollCmd is not undefined
let rollCmd = query.get('rollstr') || '';
const originalCommand = query.get('rollstr');
if (rollCmd.length === 0) {
// Alert API user that they messed up
requestEvent.respondWith(stdResp.BadRequest('rollCmd is required.'));
if (rollCmd.length === 0) {
// Alert API user that they messed up
requestEvent.respondWith(stdResp.BadRequest('rollCmd is required.'));
// 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));
return;
}
// 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));
return;
}
if (query.has('o') && (query.get('o')?.toLowerCase() !== 'd' && query.get('o')?.toLowerCase() !== 'a')) {
// Alert API user that they messed up
requestEvent.respondWith(stdResp.BadRequest('Order must be set to \'a\' or \'d\'.'));
if (query.has('o') && query.get('o')?.toLowerCase() !== 'd' && query.get('o')?.toLowerCase() !== 'a') {
// Alert API user that they messed up
requestEvent.respondWith(stdResp.BadRequest("Order must be set to 'a' or 'd'."));
// 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));
return;
}
// 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));
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
rollCmd = rollCmd.substring(rollCmd.indexOf(config.prefix) + 2).replace(/%20/g, ' ');
// 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, ' ');
const modifiers: RollModifiers = {
noDetails: query.has('nd'),
superNoDetails: query.has('snd'),
spoiler: query.has('s') ? '||' : '',
maxRoll: query.has('m'),
nominalRoll: query.has('n'),
gmRoll: query.has('gms'),
gms: query.has('gms') ? (query.get('gms') || '').split(',') : [],
order: query.has('o') ? (query.get('o')?.toLowerCase() || '') : '',
count: query.has('c'),
valid: true,
apiWarn: hideWarn ? '' : apiWarning,
};
const modifiers: RollModifiers = {
noDetails: query.has('nd'),
superNoDetails: query.has('snd'),
spoiler: query.has('s') ? '||' : '',
maxRoll: query.has('m'),
nominalRoll: query.has('n'),
gmRoll: query.has('gms'),
gms: query.has('gms') ? (query.get('gms') || '').split(',') : [],
order: query.has('o') ? query.get('o')?.toLowerCase() || '' : '',
count: query.has('c'),
valid: true,
apiWarn: hideWarn ? '' : apiWarning,
};
// Parse the roll and get the return text
await queueRoll(
<QueuedRoll> {
apiRoll: true,
api: { requestEvent, channelId: BigInt(query.get('channel') || '0'), userId: BigInt(query.get('user') || '') },
rollCmd,
modifiers,
originalCommand,
},
);
} catch (err) {
// Handle any errors we missed
log(LT.ERROR, `Unhandled Error: ${JSON.stringify(err)}`);
requestEvent.respondWith(stdResp.InternalServerError('Something went wrong.'));
}
} else {
// Alert API user that they messed up
requestEvent.respondWith(
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
requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams));
}
// Parse the roll and get the return text
await queueRoll(<QueuedRoll>{
apiRoll: true,
api: { requestEvent, channelId: BigInt(query.get('channel') || '0'), userId: BigInt(query.get('user') || '') },
rollCmd,
modifiers,
originalCommand,
});
} catch (err) {
// Handle any errors we missed
log(LT.ERROR, `Unhandled Error: ${JSON.stringify(err)}`);
requestEvent.respondWith(stdResp.InternalServerError('Something went wrong.'));
}
} else {
// Alert API user that they messed up
requestEvent.respondWith(
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
requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams));
}
};

View File

@ -1,19 +1,19 @@
import {
// httpd deps
Status,
STATUS_TEXT,
// httpd deps
STATUS_CODE,
STATUS_TEXT,
} from '../../../deps.ts';
export const heatmapPng = async (requestEvent: Deno.RequestEvent) => {
const file = Deno.readFileSync('./src/endpoints/gets/heatmap.png');
const imageHeaders = new Headers();
imageHeaders.append('Content-Type', 'image/png');
// Send basic OK to indicate key has been sent
requestEvent.respondWith(
new Response(file, {
status: Status.OK,
statusText: STATUS_TEXT[Status.OK],
headers: imageHeaders,
}),
);
const file = Deno.readFileSync('./src/endpoints/gets/heatmap.png');
const imageHeaders = new Headers();
imageHeaders.append('Content-Type', 'image/png');
// Send basic OK to indicate key has been sent
requestEvent.respondWith(
new Response(file, {
status: STATUS_CODE.OK,
statusText: STATUS_TEXT[STATUS_CODE.OK],
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 utils from '../../utils.ts';
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 (apiUserid === BigInt(query.get('user') || '0')) {
// Flag to see if there is an error inside the catch
let erroredOut = false;
if (query.has('user') && (query.get('user') || '').length > 0 && query.has('channel') && (query.get('channel') || '').length > 0) {
if (apiUserid === BigInt(query.get('user') || '0')) {
// Flag to see if there is an error inside the catch
let erroredOut = false;
// 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) => {
utils.commonLoggers.dbError('apiChannelAdd.ts:17', 'insert into', e);
requestEvent.respondWith(stdResp.InternalServerError('Failed to store channel.'));
erroredOut = true;
});
// 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) => {
utils.commonLoggers.dbError('apiChannelAdd.ts:17', 'insert into', e);
requestEvent.respondWith(stdResp.InternalServerError('Failed to store channel.'));
erroredOut = true;
});
// Exit this case now if catch errored
if (erroredOut) {
return;
} else {
// Send OK to indicate modification was successful
requestEvent.respondWith(stdResp.OK('Successfully added channel.'));
return;
}
} else {
// Alert API user that they shouldn't be doing this
requestEvent.respondWith(stdResp.Forbidden('You can only add channels to your key.'));
}
} else {
// Alert API user that they messed up
requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams));
}
// Exit this case now if catch errored
if (erroredOut) {
return;
} else {
// Send OK to indicate modification was successful
requestEvent.respondWith(stdResp.OK('Successfully added channel.'));
return;
}
} else {
// Alert API user that they shouldn't be doing this
requestEvent.respondWith(stdResp.Forbidden('You can only add channels to your key.'));
}
} else {
// Alert API user that they messed up
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 utils from '../../utils.ts';
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 (apiUserid === BigInt(query.get('user') || '0')) {
// Flag to see if there is an error inside the catch
let value, erroredOut = false;
if (query.has('channel') && (query.get('channel') || '').length > 0 && query.has('user') && (query.get('user') || '').length > 0) {
if (apiUserid === BigInt(query.get('user') || '0')) {
// Flag to see if there is an error inside the catch
let value,
erroredOut = false;
// Determine value to set
if (path.toLowerCase().indexOf('de') > 0) {
value = 0;
} else {
value = 1;
}
// Determine value to set
if (path.toLowerCase().indexOf('de') > 0) {
value = 0;
} else {
value = 1;
}
// 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) => {
utils.commonLoggers.dbError('apiChannelManageActive.ts:25', 'update', e);
requestEvent.respondWith(stdResp.InternalServerError('Failed to update channel.'));
erroredOut = true;
});
// 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) => {
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
if (erroredOut) {
return;
} else {
// Send API key as response
requestEvent.respondWith(stdResp.OK(`Successfully active to ${value}.`));
return;
}
} else {
// Alert API user that they shouldn't be doing this
requestEvent.respondWith(stdResp.Forbidden('You can only manage your own channels.'));
}
} else {
// Alert API user that they messed up
requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams));
}
// Exit this case now if catch errored
if (erroredOut) {
return;
} else {
// Send API key as response
requestEvent.respondWith(stdResp.OK(`Successfully active to ${value}.`));
return;
}
} else {
// Alert API user that they shouldn't be doing this
requestEvent.respondWith(stdResp.Forbidden('You can only manage your own channels.'));
}
} else {
// Alert API user that they messed up
requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams));
}
};

View File

@ -1,45 +1,52 @@
import config from '../../../config.ts';
import { dbClient } from '../../db.ts';
import dbClient from '../../db/client.ts';
import stdResp from '../stdResponses.ts';
import utils from '../../utils.ts';
export const apiChannelManageBan = async (requestEvent: Deno.RequestEvent, query: Map<string, string>, apiUserid: BigInt, path: string) => {
if (
(query.has('a') && ((query.get('a') || '').length > 0)) && (query.has('channel') && ((query.get('channel') || '').length > 0)) &&
(query.has('user') && ((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;
if (
query.has('a') &&
(query.get('a') || '').length > 0 &&
query.has('channel') &&
(query.get('channel') || '').length > 0 &&
query.has('user') &&
(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
if (path.toLowerCase().indexOf('un') > 0) {
value = 0;
} else {
value = 1;
}
// Determine value to set
if (path.toLowerCase().indexOf('un') > 0) {
value = 0;
} else {
value = 1;
}
// 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) => {
utils.commonLoggers.dbError('apiChannelManageBan.ts:28', 'update', e);
requestEvent.respondWith(stdResp.InternalServerError('Failed to update channel.'));
erroredOut = true;
});
// 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) => {
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
if (erroredOut) {
return;
} else {
// Send OK to indicate modification was successful
requestEvent.respondWith(stdResp.OK(`Successfully active to ${value}.`));
return;
}
} else {
// Alert API user that they shouldn't be doing this
requestEvent.respondWith(stdResp.Forbidden(stdResp.Strings.restricted));
}
} else {
// Alert API user that they messed up
requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams));
}
// Exit this case now if catch errored
if (erroredOut) {
return;
} else {
// Send OK to indicate modification was successful
requestEvent.respondWith(stdResp.OK(`Successfully active to ${value}.`));
return;
}
} else {
// Alert API user that they shouldn't be doing this
requestEvent.respondWith(stdResp.Forbidden(stdResp.Strings.restricted));
}
} else {
// Alert API user that they messed up
requestEvent.respondWith(stdResp.BadRequest(stdResp.Strings.missingParams));
}
};

View File

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

View File

@ -1,23 +1,25 @@
import {
// httpd deps
Status,
STATUS_TEXT,
// httpd deps
StatusCode,
STATUS_CODE,
STATUS_TEXT,
} 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 {
BadRequest: (customText: string) => genericResponse(customText, Status.BadRequest),
FailedDependency: (customText: string) => genericResponse(customText, Status.FailedDependency),
InternalServerError: (customText: string) => genericResponse(customText, Status.InternalServerError),
Forbidden: (customText: string) => genericResponse(customText, Status.Forbidden),
MethodNotAllowed: (customText: string) => genericResponse(customText, Status.MethodNotAllowed),
NotFound: (customText: string) => genericResponse(customText, Status.NotFound),
OK: (customText: string) => genericResponse(customText, Status.OK),
RequestTimeout: (customText: string) => genericResponse(customText, Status.RequestTimeout),
TooManyRequests: (customText: string) => genericResponse(customText, Status.TooManyRequests),
Strings: {
missingParams: 'Missing Parameters.',
restricted: 'This API is restricted.',
},
BadRequest: (customText: string) => genericResponse(customText, STATUS_CODE.BadRequest),
FailedDependency: (customText: string) => genericResponse(customText, STATUS_CODE.FailedDependency),
InternalServerError: (customText: string) => genericResponse(customText, STATUS_CODE.InternalServerError),
Forbidden: (customText: string) => genericResponse(customText, STATUS_CODE.Forbidden),
MethodNotAllowed: (customText: string) => genericResponse(customText, STATUS_CODE.MethodNotAllowed),
NotFound: (customText: string) => genericResponse(customText, STATUS_CODE.NotFound),
OK: (customText: string) => genericResponse(customText, STATUS_CODE.OK),
RequestTimeout: (customText: string) => genericResponse(customText, STATUS_CODE.RequestTimeout),
TooManyRequests: (customText: string) => genericResponse(customText, STATUS_CODE.TooManyRequests),
Strings: {
missingParams: 'Missing Parameters.',
restricted: 'This API is restricted.',
},
};

View File

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

View File

@ -1,15 +1,16 @@
import config from '../../config.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 {
// Discordeno deps
DiscordenoMessage,
// Log4Deno deps
log,
LT,
// Discordeno deps
sendDirectMessage,
sendMessage,
// Discordeno deps
DiscordenoMessage,
// Log4Deno deps
log,
LT,
// Discordeno deps
sendDirectMessage,
sendMessage,
} from '../../deps.ts';
import { SolvedRoll } from '../solver/solver.d.ts';
import { QueuedRoll, RollModifiers } from '../mod.d.ts';
@ -22,189 +23,208 @@ const rollQueue: Array<QueuedRoll> = [];
// Handle setting up and calling the rollWorker
const handleRollWorker = async (rq: QueuedRoll) => {
currentWorkers++;
currentWorkers++;
// gmModifiers used to create gmEmbed (basically just turn off the gmRoll)
const gmModifiers = JSON.parse(JSON.stringify(rq.modifiers));
gmModifiers.gmRoll = false;
// gmModifiers used to create gmEmbed (basically just turn off the gmRoll)
const gmModifiers = JSON.parse(JSON.stringify(rq.modifiers));
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 () => {
rollWorker.terminate();
currentWorkers--;
if (rq.apiRoll) {
rq.api.requestEvent.respondWith(stdResp.RequestTimeout('Roll took too long to process, try breaking roll down into simpler parts'));
} else {
rq.dd.m.edit({
embeds: [
(await generateRollEmbed(
rq.dd.message.authorId,
<SolvedRoll> {
error: true,
errorCode: 'TooComplex',
errorMsg: 'Error: Roll took too long to process, try breaking roll down into simpler parts',
},
<RollModifiers> {},
)).embed,
],
}).catch((e) => utils.commonLoggers.messageEditError('rollQueue.ts:51', rq.dd.m, e));
}
}, config.limits.workerTimeout);
const workerTimeout = setTimeout(async () => {
rollWorker.terminate();
currentWorkers--;
if (rq.apiRoll) {
rq.api.requestEvent.respondWith(stdResp.RequestTimeout('Roll took too long to process, try breaking roll down into simpler parts'));
} else {
rq.dd.m
.edit({
embeds: [
(
await generateRollEmbed(
rq.dd.message.authorId,
<SolvedRoll>{
error: true,
errorCode: 'TooComplex',
errorMsg: 'Error: Roll took too long to process, try breaking roll down into simpler parts',
},
<RollModifiers>{}
)
).embed,
],
})
.catch((e) => utils.commonLoggers.messageEditError('rollQueue.ts:51', rq.dd.m, e));
}
}, config.limits.workerTimeout);
rollWorker.addEventListener('message', async (workerMessage) => {
if (workerMessage.data === 'ready') {
rollWorker.postMessage({
rollCmd: rq.rollCmd,
modifiers: rq.modifiers,
});
return;
}
let apiErroredOut = false;
try {
currentWorkers--;
clearTimeout(workerTimeout);
const returnmsg = workerMessage.data;
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 countEmbed = generateCountDetailsEmbed(returnmsg.counts);
rollWorker.addEventListener('message', async (workerMessage) => {
if (workerMessage.data === 'ready') {
rollWorker.postMessage({
rollCmd: rq.rollCmd,
modifiers: rq.modifiers,
});
return;
}
let apiErroredOut = false;
try {
currentWorkers--;
clearTimeout(workerTimeout);
const returnmsg = workerMessage.data;
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 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 (returnmsg.error) {
if (rq.apiRoll) {
rq.api.requestEvent.respondWith(stdResp.InternalServerError(returnmsg.errorMsg));
} else {
rq.dd.m.edit({ embeds: [pubEmbedDetails.embed] });
}
// If there was an error, report it to the user in hopes that they can determine what they did wrong
if (returnmsg.error) {
if (rq.apiRoll) {
rq.api.requestEvent.respondWith(stdResp.InternalServerError(returnmsg.errorMsg));
} else {
rq.dd.m.edit({ embeds: [pubEmbedDetails.embed] });
}
if (rq.apiRoll || DEVMODE && config.logRolls) {
// 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) =>
utils.commonLoggers.dbError('rollQueue.ts:82', 'insert into', e)
);
}
} else {
let n: DiscordenoMessage | void;
// Determine if we are to send a GM roll or a normal roll
if (rq.modifiers.gmRoll) {
if (rq.apiRoll) {
n = await sendMessage(rq.api.channelId, {
content: rq.modifiers.apiWarn,
embeds: [pubEmbedDetails.embed],
}).catch(() => {
apiErroredOut = true;
rq.api.requestEvent.respondWith(stdResp.InternalServerError('Message failed to send - location 0.'));
});
} else {
// Send the public embed to correct channel
rq.dd.m.edit({ embeds: [pubEmbedDetails.embed] });
}
if (rq.apiRoll || (DEVMODE && config.logRolls)) {
// 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) => utils.commonLoggers.dbError('rollQueue.ts:82', 'insert into', e));
}
} else {
let n: DiscordenoMessage | void;
// Determine if we are to send a GM roll or a normal roll
if (rq.modifiers.gmRoll) {
if (rq.apiRoll) {
n = await sendMessage(rq.api.channelId, {
content: rq.modifiers.apiWarn,
embeds: [pubEmbedDetails.embed],
}).catch(() => {
apiErroredOut = true;
rq.api.requestEvent.respondWith(stdResp.InternalServerError('Message failed to send - location 0.'));
});
} else {
// Send the public embed to correct channel
rq.dd.m.edit({ embeds: [pubEmbedDetails.embed] });
}
if (!apiErroredOut) {
// 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) => {
log(LT.LOG, `Messaging GM ${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)), {
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
if (gmEmbedDetails.hasAttachment) {
await sendDirectMessage(BigInt(gm.substring(2, gm.length - 1)), {
file: gmEmbedDetails.attachment,
}).catch(() => {
if (n && rq.apiRoll) {
n.reply(generateDMFailed(gm));
} else {
rq.dd.message.reply(generateDMFailed(gm));
}
});
}
}).catch(() => {
if (rq.apiRoll && n) {
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) {
n = await sendMessage(rq.api.channelId, {
content: rq.modifiers.apiWarn,
embeds: rq.modifiers.count ? [pubEmbedDetails.embed, countEmbed] : [pubEmbedDetails.embed],
}).catch(() => {
apiErroredOut = true;
rq.api.requestEvent.respondWith(stdResp.InternalServerError('Message failed to send - location 1.'));
});
} else {
n = await rq.dd.m.edit({
embeds: rq.modifiers.count ? [pubEmbedDetails.embed, countEmbed] : [pubEmbedDetails.embed],
});
}
if (!apiErroredOut) {
// 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) => {
log(LT.LOG, `Messaging GM ${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)), {
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
if (gmEmbedDetails.hasAttachment) {
await sendDirectMessage(BigInt(gm.substring(2, gm.length - 1)), {
file: gmEmbedDetails.attachment,
}).catch(() => {
if (n && rq.apiRoll) {
n.reply(generateDMFailed(gm));
} else {
rq.dd.message.reply(generateDMFailed(gm));
}
});
}
})
.catch(() => {
if (rq.apiRoll && n) {
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) {
n = await sendMessage(rq.api.channelId, {
content: rq.modifiers.apiWarn,
embeds: rq.modifiers.count ? [pubEmbedDetails.embed, countEmbed] : [pubEmbedDetails.embed],
}).catch(() => {
apiErroredOut = true;
rq.api.requestEvent.respondWith(stdResp.InternalServerError('Message failed to send - location 1.'));
});
} else {
n = await rq.dd.m.edit({
embeds: rq.modifiers.count ? [pubEmbedDetails.embed, countEmbed] : [pubEmbedDetails.embed],
});
}
if (pubEmbedDetails.hasAttachment && n) {
// Attachment requires you to send a new message
n.reply({
file: pubEmbedDetails.attachment,
});
}
}
if (pubEmbedDetails.hasAttachment && n) {
// Attachment requires you to send a new message
n.reply({
file: pubEmbedDetails.attachment,
});
}
}
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));
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));
rq.api.requestEvent.respondWith(stdResp.OK(JSON.stringify(
rq.modifiers.count
? {
counts: countEmbed,
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)));
}
}
});
rq.api.requestEvent.respondWith(
stdResp.OK(
JSON.stringify(
rq.modifiers.count
? {
counts: countEmbed,
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)));
}
}
});
};
// Runs the roll or queues it depending on how many workers are currently running
export const queueRoll = async (rq: QueuedRoll) => {
if (rq.apiRoll) {
handleRollWorker(rq);
} else if (!rollQueue.length && currentWorkers < config.limits.maxWorkers) {
handleRollWorker(rq);
} else {
rq.dd.m.edit({
embeds: [{
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.
if (rq.apiRoll) {
handleRollWorker(rq);
} else if (!rollQueue.length && currentWorkers < config.limits.maxWorkers) {
handleRollWorker(rq);
} else {
rq.dd.m
.edit({
embeds: [
{
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.`,
}],
}).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
setInterval(async () => {
log(LT.LOG, `Checking rollQueue for items, rollQueue length: ${rollQueue.length}, currentWorkers: ${currentWorkers}, config.limits.maxWorkers: ${config.limits.maxWorkers}`);
if (rollQueue.length && currentWorkers < config.limits.maxWorkers) {
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);
}
}
log(
LT.LOG,
`Checking rollQueue for items, rollQueue length: ${rollQueue.length}, currentWorkers: ${currentWorkers}, config.limits.maxWorkers: ${config.limits.maxWorkers}`
);
if (rollQueue.length && currentWorkers < config.limits.maxWorkers) {
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);