diff --git a/README.md b/README.md index 7589dee..df465de 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,8 @@ The Artificer comes with a few supplemental commands to the main rolling command * If bot is given the permission `Manage Messages`, the bot will remove the message requesting the emote. * `[[stats` or `[[s` * Prints out how many users, channels, and servers the bot is currently serving. +* `[[heatmap` or `[[hm` + * Heatmap of when the roll command is run the most. * `[[report` or `[[r [command that failed]` * People aren't perfect, but this bot is trying to be. * If you encounter a command that errors out or returns something unexpected, please use this command to alert the developers of the problem. diff --git a/config.example.ts b/config.example.ts index b984208..d878025 100644 --- a/config.example.ts +++ b/config.example.ts @@ -12,6 +12,7 @@ export const config = { }, "api": { // Setting for the built-in API "enable": false, // Leave this off if you have no intention of using this/supporting it + "publicDomain": 'http://example.com/', // Public domain that the API is behind, should end with a / "port": 8080, // Port for the API to listen on "supportURL": "your_support_url_for_api_abuse", // Fill this in with the way you wish to be contacted when somebody needs to report API key abuse "rateLimitTime": 10000, // Time range for how often the API rate limits will be lifted (time in ms) diff --git a/src/api.ts b/src/api.ts index a187a81..bd0549e 100644 --- a/src/api.ts +++ b/src/api.ts @@ -91,114 +91,121 @@ const start = async (): Promise => { }); } - 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; - 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; + // 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 diff --git a/src/commands/heatmap.ts b/src/commands/heatmap.ts index 23f193d..c851e34 100644 --- a/src/commands/heatmap.ts +++ b/src/commands/heatmap.ts @@ -3,25 +3,34 @@ import { // Discordeno deps DiscordenoMessage, } from '../../deps.ts'; -import { } from '../commandUtils.ts'; -import { compilingStats } from '../commonEmbeds.ts'; +import config from '../../config.ts'; +import { LOCALMODE } from '../../flags.ts'; +import { failColor, infoColor2 } from '../commandUtils.ts'; import utils from '../utils.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)); - try { - const m = await message.send(compilingStats); + if (config.api.enable) { + const m = await message.send({ + embeds: [{ + title: 'Roll Heatmap', + color: infoColor2, + image: { + url: `${config.api.publicDomain}api/heatmap.png`, + }, + }], + }).catch((e) => utils.commonLoggers.messageSendError('heatmap.ts:21', message, e)); - // Calculate how many times commands have been run - const hmQuery = await dbClient.query(`SELECT * FROM roll_time_heatmap`).catch((e) => utils.commonLoggers.dbError('heatmap.ts:20', 'query', e)); - console.log(hmQuery); - - m.edit('').catch((e: Error) => - utils.commonLoggers.messageEditError('heatmap.ts:21', m, e) - ); - } catch (e) { - utils.commonLoggers.messageSendError('heatmap.ts:24', message, e); + console.log(m); + } 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)); } }; diff --git a/src/commands/help.ts b/src/commands/help.ts index e91eaa4..a6ed395 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -66,6 +66,11 @@ export const help = (message: DiscordenoMessage) => { 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}xdydzracsq!${config.postfix}\` ...`, value: diff --git a/src/commands/stats.ts b/src/commands/stats.ts index 1e26cd5..9aeb3af 100644 --- a/src/commands/stats.ts +++ b/src/commands/stats.ts @@ -27,9 +27,9 @@ export const stats = async (message: DiscordenoMessage) => { 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) - ); + 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); } diff --git a/src/endpoints/_index.ts b/src/endpoints/_index.ts index 8fc0bcf..c621ed7 100644 --- a/src/endpoints/_index.ts +++ b/src/endpoints/_index.ts @@ -3,6 +3,7 @@ import { apiKey } from './gets/apiKey.ts'; import { apiRoll } from './gets/apiRoll.ts'; import { apiKeyAdmin } from './gets/apiKeyAdmin.ts'; import { apiChannel } from './gets/apiChannel.ts'; +import { heatmapPng } from './gets/heatmapPng.ts'; import { apiChannelAdd } from './posts/apiChannelAdd.ts'; import { apiKeyManage } from './puts/apiKeyManage.ts'; import { apiChannelManageBan } from './puts/apiChannelManageBan.ts'; @@ -17,6 +18,7 @@ export default { apiRoll, apiKeyAdmin, apiChannel, + heatmapPng, }, post: { apiChannelAdd, diff --git a/src/endpoints/gets/heatmap-base.png b/src/endpoints/gets/heatmap-base.png new file mode 100644 index 0000000..68a5cc7 Binary files /dev/null and b/src/endpoints/gets/heatmap-base.png differ diff --git a/src/endpoints/gets/heatmapPng.ts b/src/endpoints/gets/heatmapPng.ts new file mode 100644 index 0000000..352d429 --- /dev/null +++ b/src/endpoints/gets/heatmapPng.ts @@ -0,0 +1,16 @@ +import { + // httpd deps + Status, + STATUS_TEXT, +} from '../../../deps.ts'; + +export const heatmapPng = async (requestEvent: Deno.RequestEvent) => { + const file = Deno.readFileSync('./src/endpoints/gets/heatmap.png'); + // Send basic OK to indicate key has been sent + requestEvent.respondWith( + new Response(file, { + status: Status.OK, + statusText: STATUS_TEXT.get(Status.OK), + }), + ); +}; diff --git a/src/intervals.ts b/src/intervals.ts index e0c9b7e..5426b5d 100644 --- a/src/intervals.ts +++ b/src/intervals.ts @@ -63,7 +63,7 @@ const updateListStatistics = (botID: bigint, serverCount: number): void => { // Keep one week of data const hoursToKeep = 7 * 24; -const previousHours: Array> = [] +const previousHours: Array> = []; // updateHourlyRates() returns nothing // Updates the hourlyRate for command usage const updateHourlyRates = async () => { @@ -73,7 +73,7 @@ const updateHourlyRates = async () => { if (previousHours.length > 1) { const oldestHour = previousHours[0]; - const computedDiff: Array = [] + const computedDiff: Array = []; for (let i = 0; i < newestHour.length; i++) { computedDiff.push({ command: newestHour[i].command, @@ -85,7 +85,9 @@ const updateHourlyRates = async () => { // 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)); + 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) + ); }); } @@ -93,7 +95,7 @@ const updateHourlyRates = async () => { previousHours.unshift(); } } catch (e) { - log(LT.ERROR, `Something went wrong in previousHours interval | Error: ${e.name} - ${e.message}`) + log(LT.ERROR, `Something went wrong in previousHours interval | Error: ${e.name} - ${e.message}`); } };