Compare commits

...

11 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
Ean Milligan (Bastion) 7b03f3140e V2.0.3 - Fix multiline input 2022-07-09 17:02:22 -04:00
Ean Milligan (Bastion) 74b0e287fb Add autodeletion to Privacy Policy 2022-07-09 14:56:18 -04:00
Ean Milligan (Bastion) f4b0e04cec V2.0.2 - Fix faulty array.splice in solver 2022-07-09 02:23:42 -04:00
21 changed files with 181 additions and 39 deletions

View File

@ -39,7 +39,7 @@ If you would like to remove all of your submitted data, this can easily be done
If you have been banned from using _The API_, your API Key, and registration information (Discord User ID, and Email Address) will not be deleted as this data is considered necessary.
If you would like your Discord Guild ID to be removed from _The Bot_'s database, a Guild Owner or Administrator needs to run `[[api delete`. This will remove your Discord Guild's ID from _The Bot_'s database, reverting it back to the default setting of blocking _The API_.
If you would like your Discord Guild ID to be removed from _The Bot_'s database, a Guild Owner or Administrator needs to run `[[api delete`. This will remove your Discord Guild's ID from _The Bot_'s database, reverting it back to the default setting of blocking _The API_. Additionally, _The Bot_ will automatically remove any data related to your Discord Guild when _The Bot_ is removed from your guild.
If your guild has been banned from using _The API_, the Discord Guild ID will not be deleted as this data is considered necessary.

View File

@ -1,4 +1,4 @@
# The Artificer - A Dice Rolling Discord Bot | V2.0.1 - 2022/07/08
# 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.1', // 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

@ -46,7 +46,7 @@ export const roll = async (message: DiscordenoMessage, args: string[], command:
}
// 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 = `${command} ${args.join(' ')}`;
const rollCmd = message.content.substring(2);
queueRoll(
<QueuedRoll> {

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

@ -178,22 +178,22 @@ export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll
// If maximiseRoll or nominalRoll are on, mark the output as such, else use default formatting
if (modifiers.maxRoll) {
line1 = ` requested the theoretical maximum of: \`${config.prefix}${fullCmd}\``;
line1 = ` requested the theoretical maximum of:\n\`${config.prefix}${fullCmd}\``;
line2 = 'Theoretical Maximum Results: ';
} else if (modifiers.nominalRoll) {
line1 = ` requested the theoretical nominal of: \`${config.prefix}${fullCmd}\``;
line1 = ` requested the theoretical nominal of:\n\`${config.prefix}${fullCmd}\``;
line2 = 'Theoretical Nominal Results: ';
} else if (modifiers.order === 'a') {
line1 = ` requested the following rolls to be ordered from least to greatest: \`${config.prefix}${fullCmd}\``;
line1 = ` requested the following rolls to be ordered from least to greatest:\n\`${config.prefix}${fullCmd}\``;
line2 = 'Results: ';
tempReturnData.sort(compareTotalRolls);
} else if (modifiers.order === 'd') {
line1 = ` requested the following rolls to be ordered from greatest to least: \`${config.prefix}${fullCmd}\``;
line1 = ` requested the following rolls to be ordered from greatest to least:\n\`${config.prefix}${fullCmd}\``;
line2 = 'Results: ';
tempReturnData.sort(compareTotalRolls);
tempReturnData.reverse();
} else {
line1 = ` rolled: \`${config.prefix}${fullCmd}\``;
line1 = ` rolled:\n\`${config.prefix}${fullCmd}\``;
line2 = 'Results: ';
}

View File

@ -61,8 +61,10 @@ export const fullSolver = (conf: (string | number | SolvedStep)[], wrapDetails:
throw new Error('UnbalancedParens');
}
// Replace the itemes between openParen and closeParen (including the parens) with its solved equilvalent by calling the solver on the items between openParen and closeParen (excluding the parens)
conf.splice(openParen, closeParen + 1, fullSolver(conf.slice(openParen + 1, closeParen), true));
// 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);
// 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