Compare commits

...

8 Commits

Author SHA1 Message Date
Ean Milligan 65fbd115fb Deno apparently got more picky about launching workers, so added more files to the read permission 2024-08-27 08:41:15 -04:00
Ean Milligan (Bastion) 783395e4a5 V2.1.3 - Fix server count reporting 2023-05-01 15:45:03 -04:00
Ean Milligan (Bastion) 23ca3a2665 Fix error that was causing crash when posting statistics
Additionally, added system logging to rc.service
2022-07-31 16:09:14 -04:00
Ean Milligan (Bastion) 766dff179e Update wording around OPT-IN command 2022-07-10 02:50:18 -04:00
Ean Milligan (Bastion) 7bb6018bdf Made [[opt-in available via DM only 2022-07-10 02:17:41 -04:00
Ean Milligan (Bastion) f43cf0fa31 Add mention of opt-out to the @mention and [[privacy commands 2022-07-10 02:03:00 -04:00
Ean Milligan (Bastion) 38146a3c8f deno fmt 2022-07-10 01:59:55 -04:00
Ean Milligan (Bastion) 539616679b Add opt-out/opt-in commands
Allows users to be globally ignored by the bot.
2022-07-10 01:58:37 -04:00
18 changed files with 171 additions and 31 deletions

View File

@ -1,4 +1,4 @@
# The Artificer - A Dice Rolling Discord Bot | V2.0.3 - 2022/07/09
# The Artificer - A Dice Rolling Discord Bot | V2.1.2 - 2022/07/31
[![SonarCloud](https://sonarcloud.io/images/project_badges/sonarcloud-orange.svg)](https://sonarcloud.io/summary/new_code?id=TheArtificer)
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=TheArtificer&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=TheArtificer) [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=TheArtificer&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=TheArtificer) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=TheArtificer&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=TheArtificer) [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=TheArtificer&metric=bugs)](https://sonarcloud.io/summary/new_code?id=TheArtificer) [![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=TheArtificer&metric=duplicated_lines_density)](https://sonarcloud.io/summary/new_code?id=TheArtificer) [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=TheArtificer&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=TheArtificer)
@ -59,6 +59,10 @@ The Artificer comes with a few supplemental commands to the main rolling command
* If you encounter a command that errors out or returns something unexpected, please use this command to alert the developers of the problem.
* Example:
* `[[report [[2+2]] returned 5 when I expected it to return 4` will send the entire message after `[[report` to the devs via Discord.
* `[[opt-out` or `[[ignore-me`
* Adds you to an ignore list so the bot will never respond to you
* `[[opt-in` **Available via DM ONLY**
* Removes you from the ignore list
* `[[xdydzracsq!]]`
* This is the command the bot was built specifically for.
* It looks a little complicated at first, but if you are familiar with the [Roll20 formatting](https://artificer.eanm.dev/roll20), this will no different.
@ -216,7 +220,7 @@ The Artificer is built on [Deno](https://deno.land/) `v1.22.0` using [Discordeno
You will also need to install and setup a MySQL database with a user for the bot to use to add/modify the database. This user must have the "DB Manager" admin rights and "REFERENCES" Global Privileges. Once the DB is installed and a user is setup, run the provided `db\initialize.ts` to create the schema and tables. After this, run `db\populateDefaults.ts` to insert some needed values into the tables.
Once everything is set up, starting the bot can simply be done with `deno run --allow-net .\mod.ts`.
Once everything is set up, starting the bot can simply be done with the command in `start.command`.
If you choose to run version `1.1.0` or newer, ensure you disable the API in `config.ts` or verify you have properly secured your instance of The Artificer. If you enable the API, you should manually generate a 25 char nanoid and place it in `config.api.adminKey` and copy your `userid` and place it in `config.api.admin` before running `db\populateDefaults.ts`.

View File

@ -10,11 +10,12 @@ pidfile="/var/dbots/TheArtificer/artificer.pid"
artificer_root="/var/dbots/TheArtificer"
artificer_write="./logs/,./src/endpoints/gets/heatmap.png"
artificer_read="./src/solver/,./src/endpoints/gets/heatmap-base.png,./src/endpoints/gets/heatmap.png"
artificer_read="./src/solver/,./src/endpoints/gets/heatmap-base.png,./src/endpoints/gets/heatmap.png,./config.ts,./deps.ts,./src/mod.d.ts"
artificer_log="/var/log/artificer.log"
artificer_chdir="${artificer_root}"
command="/usr/sbin/daemon"
command_args="-f -R 5 -P ${pidfile} /usr/local/bin/deno run --allow-write=${artificer_write} --allow-read=${artificer_read} --allow-net ${artificer_root}/mod.ts"
command_args="-f -R 5 -P ${pidfile} -o ${artificer_log} /usr/local/bin/deno run --allow-write=${artificer_write} --allow-read=${artificer_read} --allow-net ${artificer_root}/mod.ts"
load_rc_config artificer
run_rc_command "$1"

View File

@ -6,7 +6,7 @@ After=network.target
[Service]
Type=simple
PIDFile=/run/deno.pid
ExecStart=/root/.deno/bin/deno run --allow-write=./logs/,./src/endpoints/gets/heatmap.png --allow-read=./src/solver/,./src/endpoints/gets/heatmap-base.png --allow-net .\mod.ts
ExecStart=/root/.deno/bin/deno run --allow-write=./logs/,./src/endpoints/gets/heatmap.png --allow-read=./src/solver/,./src/endpoints/gets/heatmap-base.png,./config.ts,./deps.ts,./src/mod.d.ts --allow-net .\mod.ts
RestartSec=60
Restart=on-failure

View File

@ -1,6 +1,6 @@
export const config = {
'name': 'The Artificer', // Name of the bot
'version': '2.0.3', // Version of the bot
'version': '2.1.3', // Version of the bot
'token': 'the_bot_token', // Discord API Token for this bot
'localtoken': 'local_testing_token', // Discord API Token for a secondary OPTIONAL testing bot, THIS MUST BE DIFFERENT FROM "token"
'prefix': '[[', // Prefix for all commands

View File

@ -19,8 +19,20 @@ await dbClient.execute(`DROP PROCEDURE IF EXISTS INC_HEATMAP;`);
await dbClient.execute(`DROP TABLE IF EXISTS roll_time_heatmap;`);
await dbClient.execute(`DROP PROCEDURE IF EXISTS INC_CNT;`);
await dbClient.execute(`DROP TABLE IF EXISTS command_cnt;`);
await dbClient.execute(`DROP TABLE IF EXISTS ignore_list;`);
console.log('Tables dropped');
// Table to hold list of users who want to be ignored by the bot
console.log('Attempting to create table ignore_list');
await dbClient.execute(`
CREATE TABLE ignore_list (
userid bigint unsigned NOT NULL,
PRIMARY KEY (userid),
UNIQUE KEY ignore_list_userid_UNIQUE (userid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`);
console.log('Table created');
// Light telemetry on how many commands have been run
console.log('Attempting to create table command_cnt');
await dbClient.execute(`

View File

@ -10,7 +10,7 @@ await dbClient.execute('INSERT INTO all_keys(userid,apiKey) values(?,?)', [confi
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'];
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);

20
mod.ts
View File

@ -25,7 +25,7 @@ import {
startBot,
} from './deps.ts';
import api from './src/api.ts';
import { dbClient } from './src/db.ts';
import { dbClient, ignoreList } from './src/db.ts';
import commands from './src/commands/_index.ts';
import intervals from './src/intervals.ts';
import { successColor, warnColor } from './src/commandUtils.ts';
@ -71,7 +71,7 @@ startBot({
// 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);
intervals.updateListStatistics(botId, cache.guilds.size + cache.dispatchedGuildIds.size);
}, 86400000);
// Interval to update hourlyRates every hour
@ -89,7 +89,7 @@ startBot({
// 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);
LOCALMODE ? log(LT.INFO, 'updateListStatistics not running') : intervals.updateListStatistics(botId, cache.guilds.size + cache.dispatchedGuildIds.size);
intervals.updateHourlyRates();
intervals.updateHeatmapPng();
editBotStatus({
@ -173,6 +173,9 @@ startBot({
// 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 all messages that are not commands
if (message.content.indexOf(config.prefix) !== 0) {
// Handle @bot messages
@ -193,6 +196,17 @@ startBot({
// 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.

View File

@ -14,6 +14,8 @@ import { roll } from './roll.ts';
import { handleMentions } from './handleMentions.ts';
import { audit } from './audit.ts';
import { heatmap } from './heatmap.ts';
import { optOut } from './optOut.ts';
import { optIn } from './optIn.ts';
export default {
ping,
@ -32,4 +34,6 @@ export default {
handleMentions,
audit,
heatmap,
optOut,
optIn,
};

View File

@ -21,8 +21,10 @@ export const handleMentions = (message: DiscordenoMessage) => {
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\``,
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));

View File

@ -76,6 +76,16 @@ export const help = (message: DiscordenoMessage) => {
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:

View File

@ -1,3 +1,4 @@
import config from '../../config.ts';
import { dbClient, queries } from '../db.ts';
import {
// Discordeno deps
@ -8,16 +9,16 @@ 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:14', 'call sproc INC_CNT on', e));
dbClient.execute(queries.callIncCnt('info')).catch((e) => utils.commonLoggers.dbError('info.ts:12', 'call sproc INC_CNT on', e));
message.send({
embeds: [{
color: infoColor2,
title: 'The Artificer, a Discord bot that specializing in rolling dice and calculating math',
description: `The Artificer is developed by Ean AKA Burn_E99.
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:27', message, e));
}).catch((e: Error) => utils.commonLoggers.messageSendError('info.ts:23', message, e));
};

39
src/commands/optIn.ts Normal file
View File

@ -0,0 +1,39 @@
import config from '../../config.ts';
import { dbClient, ignoreList, queries } from '../db.ts';
import {
// 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));
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:
\`${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));
}
}
};

36
src/commands/optOut.ts Normal file
View File

@ -0,0 +1,36 @@
import config from '../../config.ts';
import { dbClient, ignoreList, queries } from '../db.ts';
import {
// 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));
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:
\`${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));
}
};

View File

@ -22,7 +22,9 @@ export const privacy = (message: DiscordenoMessage) => {
For more details, please check out the Privacy Policy on the GitHub [here](https://github.com/Burn-E99/TheArtificer/blob/master/PRIVACY.md).
Terms of Service can also be found on GitHub [here](https://github.com/Burn-E99/TheArtificer/blob/master/TERMS.md).`,
Terms of Service can also be found on GitHub [here](https://github.com/Burn-E99/TheArtificer/blob/master/TERMS.md).
Want me to ignore you? Simply run \`${config.prefix}opt-out\` and ${config.name} will no longer read your messages or respond to you.`,
}],
}],
}).catch((e: Error) => utils.commonLoggers.messageSendError('privacy.ts:33', message, e));

View File

@ -2,6 +2,10 @@ 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,
@ -10,6 +14,13 @@ export const dbClient = await new Client().connect({
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 = {

View File

@ -47,18 +47,22 @@ const getRandomStatus = async (): Promise<string> => {
// 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) => {
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)}`);
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}`)
}
});
};

View File

@ -64,7 +64,7 @@ export const fullSolver = (conf: (string | number | SolvedStep)[], wrapDetails:
// Call the solver on the items between openParen and closeParen (excluding the parens)
const parenSolve = fullSolver(conf.slice(openParen + 1, closeParen), true);
// Replace the itemes between openParen and closeParen (including the parens) with its solved equilvalent
conf.splice(openParen, (closeParen - openParen + 1), parenSolve);
conf.splice(openParen, closeParen - openParen + 1, parenSolve);
// Determing if we need to add in a multiplication sign to handle implicit multiplication (like "(4)2" = 8)
// insertedMult flags if there was a multiplication sign inserted before the parens

View File

@ -1 +1 @@
deno run --allow-write=./logs/,./src/endpoints/gets/heatmap.png --allow-read=./src/solver/,./src/endpoints/gets/heatmap-base.png --allow-net .\mod.ts
deno run --allow-write=./logs/,./src/endpoints/gets/heatmap.png --allow-read=./src/solver/,./src/endpoints/gets/heatmap-base.png,./config.ts,./deps.ts,./src/mod.d.ts --allow-net .\mod.ts