Compare commits

..

No commits in common. "master" and "V1.0.4" have entirely different histories.

27 changed files with 296 additions and 587 deletions

5
.github/CODEOWNERS vendored
View File

@ -1,5 +0,0 @@
* @burn-e99
config.example.ts @davidopluslau
README.md @davidopluslau
src/buttons/event-creation/activities.ts @davidopluslau

View File

@ -1,42 +0,0 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will install Deno then run `deno lint` and `deno test`.
# For more information see: https://github.com/denoland/setup-deno
name: Deno
on:
push:
branches: ["master"]
pull_request:
branches: ["master"]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Setup repo
uses: actions/checkout@v4
- name: Setup Deno
# uses: denoland/setup-deno@v1
uses: denoland/setup-deno@61fe2df320078202e33d7d5ad347e7dcfa0e8f31 # v1.1.2
with:
deno-version: v1.x
- name: Verify formatting
run: deno fmt --check
- name: Run linter
run: deno lint
# Add this back in when tests are added
# - name: Run tests
# run: deno test -A

View File

@ -1,10 +0,0 @@
# CONTRIBUTING TO GROUP UP
## Things to check before committing a change
- Formatting and linting
- Run `deno fmt` to set all formatting correct
- Run `deno lint` to check for any issues that need fixed
- Are you making a change that will be updating the version number?
- Update the version number in `README.md` and `config.example.ts`, and update the date in `README.md`
- Create a tag on your commit marking this version (name it Vx.x.x)
## Things to check after committing a change
- Check in on Sonar to see if your commit caused new issues to appear. If it did, please fix them

View File

@ -3,7 +3,7 @@
### Public Bot Information
Publicly available versions of `Group Up#1305` (Discord ID: `847256159123013722`) (herein referred to as _The Bot_ or _Bot_) do not automatically track or collect user information via Discord.
Upon inviting _The Bot_ to a user's guild, _The Bot_ sends the guild name, Discord Guild ID, and current count of guild members to `@burn_e99` (herein referred to as _The Developer_) via a private Discord Guild. The guild name, Discord Guild ID, and current count of guild members are only used to roughly gage how popular _The Bot_ is and to determine if _The Bot_'s hosting solution needs to be improved. These pieces of information will never be sold or shared with anyone.
Upon inviting _The Bot_ to a user's guild, _The Bot_ sends the guild name, Discord Guild ID, and current count of guild members to Burn_E99#1062 (herein referred to as _The Developer_) via a private Discord Guild. The guild name, Discord Guild ID, and current count of guild members are only used to roughly gage how popular _The Bot_ is and to determine if _The Bot_'s hosting solution needs to be improved. These pieces of information will never be sold or shared with anyone.
_The Bot_ reads every message that it is allowed to, meaning if _The Bot_ is allowed to see a channel in a guild, it reads every new message sent in said channel. This is for the automated cleanup of designated Event Channels.
@ -11,7 +11,7 @@ _The Bot_ does not read any user messages sent in the past, but does read its ow
* Messages that do not begin with _The Bot_'s command prefix are not saved or stored anywhere. Messages that do not begin with _The Bot_'s command prefix that are sent outside of a designated Event Channel are ignored and not processed.
* Slash Commands sent to _The Bot_ do not automatically log user data, and most commands to not log any data. The commands that log data are the report command (in Discord, this command is known as `/report [message]`), the Event Channel setup command (known as `/setup`), and the Create New Event command (known as `/create-event` or the `Create New Event` button).
* The report command stores the Discord Guild ID, Discord User ID, and the text placed within the message that is directly after the command (herein referred to as _The Report Text_). This command is entirely optional, meaning users never need to run this command under normal usage of _The Bot_. This command is only intended to be used to report roll commands that did not output what was expected. This command will accept any value for _The Report Text_, thus it is up to the user to remove any sensitive information before sending the command. _The Report Text_, Discord Guild ID, and Discord User ID are stored in a private Discord Guild in a channel that only _The Developer_ can see. _The Report Text_ is solely used to improve _The Bot_, either by providing a feature suggestions or alerting _The Developer_ to bugs that need patched. The Discord Guild ID and Discord User ID are only used to determine which reports need to be deleted as detailed in the _Deleting Your Data_ section below.
* The report command only stores the text placed within the message that is directly after the command (herein referred to as _The Report Text_). This command is entirely optional, meaning users never need to run this command under normal usage of _The Bot_. This command is only intended to be used to report roll commands that did not output what was expected. This command will accept any value for _The Report Text_, thus it is up to the user to remove any sensitive information before sending the command. _The Report Text_ is stored in a private Discord Guild in a channel that only _The Developer_ can see. _The Report Text_ is solely used to improve _The Bot_, either by providing a feature suggestions or alerting _The Developer_ to bugs that need patched.
* The Event Channel setup command only stores Discord IDs. The setup command will always store the Discord Channel ID and Discord Guild ID from where it was run.
* If the Event Channel setup command was run with the `with-manager-role` option, the submitted Discord Role ID and Discord Channel ID for the desired Manager Role and Log Channel will also be stored.
* The Create New Event command stores the following data for every event that is created:
@ -38,4 +38,4 @@ Due to the nature of open source code, _Rehosts_ may not use the same codebase t
If you wish to remove all data that _The Bot_ has on your Guild, simply remove _The Bot_ from your Guild. Upon removal, _The Bot_ deletes all data on Event Channel, all data on Events created in the Guild, and all Custom Activities created in the Guild.
## User Data Deletion
If you would like to ensure that all of your submitted reports are removed from _The Bot_'s private development server, please contact _The Developer_ via Discord (by sending a direct message to `@burn_e99`) or via email (<ean@milligan.dev>) with a message along the lines of `"Please remove all of my submitted reports from your development server."`. Submitted reports are deleted from the server as they are processed, which happens roughly once a week, but this can be accelerated if requested.
If you would like to ensure that all of your submitted reports are removed from _The Bot_'s private development server, please contact _The Developer_ via Discord (by sending a direct message to `Burn_E99#1062`) or via email (<ean@milligan.dev>) with a message along the lines of `"Please remove all of my submitted reports from your development server."`. Submitted reports are deleted from the server as they are processed, which happens roughly once a week, but this can be accelerated if requested.

View File

@ -1,4 +1,4 @@
# Group Up - An Event Scheduling Discord Bot | V2.0.1 - 2024/12/24
# Group Up - An Event Scheduling Discord Bot | V1.0.4 - 2024/05/04
[![SonarCloud](https://sonarcloud.io/images/project_badges/sonarcloud-orange.svg)](https://sonarcloud.io/summary/new_code?id=GroupUp)
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=GroupUp&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=GroupUp) [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=GroupUp&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=GroupUp) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=GroupUp&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=GroupUp) [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=GroupUp&metric=bugs)](https://sonarcloud.io/summary/new_code?id=GroupUp) [![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=GroupUp&metric=duplicated_lines_density)](https://sonarcloud.io/summary/new_code?id=GroupUp) [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=GroupUp&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=GroupUp)
@ -43,12 +43,7 @@ If you run into any errors or problems with the bot, or think you have a good id
---
## Self Hosting Group Up
Group Up is built on [Deno](https://deno.land/) using [Discordeno](https://discordeno.mod.land/) `v17.0.1`.
Group Up `V1.1.6` and lower requires Deno `V.33.1`.
Group Up `V2.0.0` and up requires Deno `V2.0.0`.
If you choose to run this yourself, you will need to rename `config.example.ts` to `config.ts` and edit some values. You will need to create a new [Discord Application](https://discord.com/developers/applications) and copy the newly generated token into the `"token"` field. If you want to utilize some of the bots dev features, you will need to fill in the keys `"logChannel"` and `"reportChannel"` with text channel IDs and `"devServer"` with a guild ID.
Group Up is built on [Deno](https://deno.land/) `v1.33.1` using [Discordeno](https://discordeno.mod.land/) `v17.0.1`. If you choose to run this yourself, you will need to rename `config.example.ts` to `config.ts` and edit some values. You will need to create a new [Discord Application](https://discord.com/developers/applications) and copy the newly generated token into the `"token"` field. If you want to utilize some of the bots dev features, you will need to fill in the keys `"logChannel"` and `"reportChannel"` with text channel IDs and `"devServer"` with a guild ID.
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.
@ -59,6 +54,6 @@ Once everything is set up, starting the bot can simply be done with `deno run --
## Privacy Policy and Terms of Service
Group Up has a Privacy Policy and Terms of Service to detail expectations of what user data is stored and how users should use Group Up. The following Privacy Policy and Terms of Service only apply to the officially hosted version of Group Up (`Group Up#1305`, Discord ID: `847256159123013722`).
Privacy Policy TL;DR: Group Up stores data relating to events, event channels, and text from the `/report` command. For more detailed information, please check out the full [PRIVACY POLICY](https://github.com/Burn-E99/GroupUp/blob/master/PRIVACY.md).
Privacy Policy TL;DR: Group Up stores data relating to events, event channels, and text from the `/report` command. For more detailed information, please check out the full [PRIVACY POLICY](https://github.com/Burn-E99/TheArtificer/blob/master/PRIVACY.md).
Terms of Service TL;DR: Don't abuse or attempt to hack/damage Group Up. If you do, you may be banned from use. For more detailed information, please check out the full [TERMS OF SERVICE](https://github.com/Burn-E99/GroupUp/blob/master/TERMS.md).
Terms of Service TL;DR: Don't abuse or attempt to hack/damage Group Up. If you do, you may be banned from use. For more detailed information, please check out the full [TERMS OF SERVICE](https://github.com/Burn-E99/TheArtificer/blob/master/TERMS.md).

View File

@ -1,49 +1,40 @@
export const config = {
// !! NOTICE !! All fields below are required unless they are explicitly noted as OPTIONAL. If a field is OPTIONAL, do not remove it from this file, just leave it at the default value
name: 'Group Up', // Name of the bot
version: '2.0.2', // 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 SHOULD BE DIFFERENT FROM "token"
prefix: '/', // Prefix for all commands, as this bot uses slash commands, this needs to be '/'
db: {
// Settings for the MySQL database, this is required to keep track of the currently active events.
host: '', // IP address for the db, usually localhost
localhost: '', // IP address for a secondary OPTIONAL local testing DB, usually also is localhost, but depends on your dev environment
port: 3306, // Port for the db
username: '', // Username for the account that will access your DB, this account will need "DB Manager" admin rights and "REFERENCES" Global Privileges
password: '', // Password for the account, user account may need to be authenticated with the "Standard" Authentication Type if this does not work out of the box
name: '', // Name of the database Schema to use for the bot
'name': 'Group Up', // Name of the bot
'version': '1.0.4', // 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
'db': { // Settings for the MySQL database, this is required for use with the API, if you do not want to set this up, you will need to rip all code relating to the DB out of the bot
'host': '', // IP address for the db, usually localhost
'localhost': '', // IP address for a secondary OPTIONAL local testing DB, usually also is localhost, but depends on your dev environment
'port': 3306, // Port for the db
'username': '', // Username for the account that will access your DB, this account will need "DB Manager" admin rights and "REFERENCES" Global Privileges
'password': '', // Password for the account, user account may need to be authenticated with the "Standard" Authentication Type if this does not work out of the box
'name': '', // Name of the database Schema to use for the bot
},
links: {
// Links to various sites
sourceCode: 'https://github.com/Burn-E99/GroupUp', // Link to the repository, OPTIONAL
supportServer: '', // Invite link to the Discord support server, OPTIONAL
addToCalendar: '', // Link to where the icsGenerator is hosted, OPTIONAL
creatorIcon: '', // Link to where the GroupUpSinglePerson.png (or similar image) is hosted
'links': { // Links to various sites
'sourceCode': 'https://github.com/Burn-E99/GroupUp', // Link to the repository
'supportServer': '', // Invite link to the Discord support server
'addToCalendar': '', // Link to where the icsGenerator is hosted
'creatorIcon': '', // Link to where the GroupUpSinglePerson.png (or similar image) is hosted
},
defaultDateFormat: 'MONTH/DAY/YEAR', // Default format that Group Up will suggest to the user. Must match one of the options in the 'DateTimeFormats' enum inside 'src/buttons/event-creation/dateTimeUtils.ts'
logChannel: 0n, // Discord channel ID where the bot should put startup messages and other error messages needed. This value is a bigint, so please ensure you have a `n` after the ID you get from Discord. OPTIONAL
reportChannel: 0n, // Discord channel ID where reports will be sent when using the built-in report command. This value is a bigint, so please ensure you have a `n` after the ID you get from Discord. OPTIONAL
devServer: 0n, // Discord guild ID where testing of indev features/commands will be handled, used in conjunction with the DEVMODE bool in mod.ts. This value is a bigint, so please ensure you have a `n` after the ID you get from Discord. OPTIONAL
owner: 0n, // Discord user ID of the bot admin. This value is a bigint, so please ensure you have a `n` after the ID you get from Discord.
botLists: [
// Array of objects containing all bot lists that stats should be posted to, OPTIONAL
{
// Bot List object, duplicate for each bot list
name: 'Bot List Name', // Name of bot list, not used
enabled: false, // Should statistics be posted to this list?
apiUrl: 'https://example.com/api/bots/?{bot_id}/stats', // API URL, use ?{bot_id} in place of the bot id so that it can be dynamically replaced
headers: [
// Array of headers that need to be added to the request
{
// Header Object, duplicate for every header needed
header: 'header_name', // Name of header needed, usually Authorization is needed
value: 'header_value', // Value for the header
'logChannel': 0n, // Discord channel ID where the bot should put startup messages and other error messages needed
'reportChannel': 0n, // Discord channel ID where reports will be sent when using the built-in report command
'devServer': 0n, // Discord guild ID where testing of indev features/commands will be handled, used in conjunction with the DEVMODE bool in mod.ts
'owner': 0n, // Discord user ID of the bot admin
'botLists': [ // Array of objects containing all bot lists that stats should be posted to
{ // Bot List object, duplicate for each bot list
'name': 'Bot List Name', // Name of bot list, not used
'enabled': false, // Should statistics be posted to this list?
'apiUrl': 'https://example.com/api/bots/?{bot_id}/stats', // API URL, use ?{bot_id} in place of the bot id so that it can be dynamically replaced
'headers': [ // Array of headers that need to be added to the request
{ // Header Object, duplicate for every header needed
'header': 'header_name', // Name of header needed, usually Authorization is needed
'value': 'header_value', // Value for the header
},
],
body: {
// Data payload to send to the bot list, will be turned into a string and any ?{} will be replaced with the required value, currently only has ?{server_count}
param_name: '?{param_value}', // Add more params as needed
'body': { // Data payload to send to the bot list, will be turned into a string and any ?{} will be replaced with the required value, currently only has ?{server_count}
'param_name': '?{param_value}', // Add more params as needed
},
},
],

View File

@ -1,19 +1,42 @@
{
"compilerOptions": {
"lib": ["deno.window"],
"allowJs": true,
"lib": [
"deno.window"
],
"strict": true
},
"lint": {
"include": ["src/", "db/", "mod.ts", "deps.ts", "config.ts", "config.example.ts"],
"exclude": [],
"files": {
"include": [
"src/",
"db/",
"mod.ts",
"deps.ts",
"config.ts",
"config.example.ts"
],
"exclude": []
},
"rules": {
"tags": ["recommended"],
"include": ["ban-untagged-todo"],
"tags": [
"recommended"
],
"include": [
"ban-untagged-todo"
],
"exclude": []
}
},
"fmt": {
"include": ["src/", "db/", "mod.ts", "deps.ts", "config.ts", "config.example.ts"],
"include": [
"src/",
"db/",
"mod.ts",
"deps.ts",
"config.ts",
"config.example.ts"
],
"exclude": [],
"useTabs": true,
"lineWidth": 200,

View File

@ -47,4 +47,4 @@ export type {
export { Client } from 'https://deno.land/x/mysql@v2.11.0/mod.ts';
export { initLog, log, LogTypes as LT } from 'https://raw.githubusercontent.com/Burn-E99/Log4Deno/V2.0.0/mod.ts';
export { initLog, log, LogTypes as LT } from 'https://raw.githubusercontent.com/Burn-E99/Log4Deno/V1.1.0/mod.ts';

View File

@ -1,5 +1,3 @@
import { log, LT } from '../../../deps.ts';
// Activity should either have maxMembers or options specified, NOT both
export type Activity = {
name: string;
@ -16,19 +14,7 @@ export const Activities: Array<Activity> = [
name: 'Raids',
options: [
{
name: 'The Desert Perpetual (Epic)',
maxMembers: 6,
},
{
name: 'The Desert Perpetual',
maxMembers: 6,
},
{
name: "Salvation's Edge",
maxMembers: 6,
},
{
name: "Crota's End",
name: 'Crota\'s End',
maxMembers: 6,
},
{
@ -36,7 +22,7 @@ export const Activities: Array<Activity> = [
maxMembers: 6,
},
{
name: "King's Fall",
name: 'King\'s Fall',
maxMembers: 6,
},
{
@ -65,19 +51,7 @@ export const Activities: Array<Activity> = [
name: 'Dungeons',
options: [
{
name: 'Equilibrium',
maxMembers: 3,
},
{
name: 'Sundered Doctrine',
maxMembers: 3,
},
{
name: "Vesper's Host",
maxMembers: 3,
},
{
name: "Warlord's Ruin",
name: 'Warlord\'s Ruin',
maxMembers: 3,
},
{
@ -180,52 +154,11 @@ export const Activities: Array<Activity> = [
name: '//node.ovrd.AVALON//',
maxMembers: 3,
},
{
name: 'Zero Hour',
maxMembers: 3,
},
{
name: 'The Whisper',
maxMembers: 3,
},
{
name: 'Presage',
maxMembers: 3,
},
],
},
{
name: 'Miscellaneous/Seasonal',
options: [
{
name: 'Excision',
maxMembers: 12,
},
{
name: 'Pantheon',
options: [
{
name: 'Atraks Sovereign (Week 1)',
maxMembers: 6,
},
{
name: 'Oryx Exalted (Week 2)',
maxMembers: 6,
},
{
name: 'Rhulk Indomitable (Week 3)',
maxMembers: 6,
},
{
name: 'Nezarec Sublime (Week 4)',
maxMembers: 6,
},
],
},
{
name: 'Onslaught',
maxMembers: 3,
},
{
name: 'Fishing',
maxMembers: 3,
@ -300,33 +233,3 @@ export const Activities: Array<Activity> = [
],
},
];
// Activities Verification, verifies fields are proper lengths and amount of activities will actually fit in Discord
const actVerification = (currentAct: Activity, currentDepth = 0) => {
if (currentDepth > 4) {
log(LT.ERROR, `'${currentAct.name}' is too deep (${currentDepth} > 4)!`);
}
if (currentAct.name.length > 100) {
log(LT.ERROR, `'${currentAct.name}' is too long (${currentAct.name.length} > 100)!`);
}
if (currentAct.options && currentAct.maxMembers) {
log(LT.ERROR, `'${currentAct.name}' has both maxMembers and options specified (ONLY ONE ALLOWED)!`);
}
if (!currentAct.options && !currentAct.maxMembers) {
log(LT.ERROR, `'${currentAct.name}' is missing both maxMembers and options specified (ONE IS NEEDED)!`);
}
if (currentAct.options) {
if (currentAct.options.length > 25) {
log(LT.ERROR, `'${currentAct.name}' has too many options (${currentAct.options.length} > 25)!`);
}
for (const act of currentAct.options) {
actVerification(act, currentDepth + 1);
}
}
};
// Use a fake root activity to allow testing to occur simply
actVerification({
name: 'root',
options: Activities,
});

View File

@ -1,10 +1,3 @@
import config from '../../../config.ts';
import { editEventDetailsBtnName } from './utils.ts';
enum DateTimeFormats {
MMDDYYYY = 'MONTH/DAY/YEAR',
DDMMYYYY = 'DAY/MONTH/YEAR',
}
const monthsLong: Array<string> = ['JANUARY', 'FEBRUARY', 'MARCH', 'APRIL', 'MAY', 'JUNE', 'JULY', 'AUGUST', 'SEPTEMBER', 'OCTOBER', 'NOVEMBER', 'DECEMBER'];
export const monthsShort: Array<string> = monthsLong.map((month) => month.slice(0, 3));
const tzMap: Map<string, string> = new Map([
@ -59,8 +52,7 @@ const tzMap: Map<string, string> = new Map([
['CHST', '+10:00'],
['SST', '-11:00'],
]);
const shorthandUSTZ: Array<string> = ['ET', 'CT', 'MT', 'PT', 'HT', 'AKT'];
const allUSTZ: Array<string> = ['EST', 'CST', 'MST', 'PST', 'HST', 'AKST', 'EDT', 'CDT', 'MDT', 'PDT', 'HDT', 'AKDT'];
const shorthandUSTZ: Array<string> = ['ET', 'CT', 'MT', 'PT'];
// Takes user input Time and makes it actually usable
const parseEventTime = (preParsedEventTime: string): [string, string, string] => {
@ -101,33 +93,14 @@ export const isDSTActive = (): boolean => {
return today.getTimezoneOffset() < Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset());
};
const editButtonMessage = `click \`${editEventDetailsBtnName}\` and change`;
const warningText = (incorrectTZ: string, correctTZ: string, newEvent: boolean) =>
`⚠️⚠️⚠️ WARNING! Did you mean to enter \`${incorrectTZ}\` for the time zone? ⚠️⚠️⚠️\nCurrently (as of the posting of this message), Daylight Savings Time is ${
isDSTActive() ? '' : 'not '
}active in most of the United States. If DST is ${isDSTActive() ? '' : 'not '}in effect for you (and will be when this event is scheduled to happen), ${
newEvent ? editButtonMessage : 'please dismiss this message and start over, using'
} the time zone ${newEvent ? 'to ' : ''}\`${correctTZ}\`, or shorten it to \`${correctTZ.slice(0, -2)}T\` to let ${config.name} automatically use the correct time zone.\n\n`;
const usTZDSTCheck = (timeZone: string, newEvent: boolean): string => {
if (allUSTZ.includes(timeZone)) {
if (isDSTActive() && timeZone.endsWith('ST')) {
return warningText(timeZone, `${timeZone.slice(0, -2)}DT`, newEvent);
} else if (!isDSTActive() && timeZone.endsWith('DT')) {
return warningText(timeZone, `${timeZone.slice(0, -2)}ST`, newEvent);
}
}
return '';
};
// Takes user input Time Zone and makes it actually usable
const parseEventTimeZone = (preParsedEventTimeZone: string): [string, string] => {
if (shorthandUSTZ.includes(preParsedEventTimeZone)) {
// Handle shorthand US timezones, adding S for standard time and D for Daylight Savings
if (isDSTActive()) {
preParsedEventTimeZone = `${preParsedEventTimeZone.slice(0, -1)}DT`;
preParsedEventTimeZone = `${preParsedEventTimeZone.slice(0, 1)}DT`;
} else {
preParsedEventTimeZone = `${preParsedEventTimeZone.slice(0, -1)}ST`;
preParsedEventTimeZone = `${preParsedEventTimeZone.slice(0, 1)}ST`;
}
}
if (tzMap.has(preParsedEventTimeZone)) {
@ -140,8 +113,8 @@ const parseEventTimeZone = (preParsedEventTimeZone: string): [string, string] =>
addPlusSign = true;
}
// Determine if we need to prepend UTC/GMT, handle adding the + into the string
if (!preParsedEventTimeZone.startsWith('UTC') || preParsedEventTimeZone.startsWith('GMT')) {
preParsedEventTimeZone = `UTC${addPlusSign ? '+' : ''}${preParsedEventTimeZone.startsWith('GMT') ? preParsedEventTimeZone.slice(3) : preParsedEventTimeZone}`;
if (!preParsedEventTimeZone.startsWith('UTC') && preParsedEventTimeZone.startsWith('GMT')) {
preParsedEventTimeZone = `UTC${addPlusSign && '+'}${preParsedEventTimeZone}`;
} else if (addPlusSign) {
preParsedEventTimeZone = `${preParsedEventTimeZone.slice(0, 3)}+${preParsedEventTimeZone.slice(3)}`;
}
@ -149,49 +122,10 @@ const parseEventTimeZone = (preParsedEventTimeZone: string): [string, string] =>
}
};
const determineDateTimeOrder = (parsedSlot1: string, parsedSlot2: string, parsedSlot3: string): [string, string, string] => {
// Default these to MMDDYYYY in case something goes wrong and does not properly override them
let parsedEventMonth = parsedSlot1;
let parsedEventDay = parsedSlot2;
let parsedEventYear = parsedSlot3;
const slot1AsInt = parseInt(parsedSlot1);
const slot2AsInt = parseInt(parsedSlot2);
if (!isNaN(slot1AsInt) && slot1AsInt > 999) {
// First parsing slot appears to be a year, assume user used ISO8601 format
parsedEventYear = parsedSlot1;
parsedEventMonth = parsedSlot2;
parsedEventDay = parsedSlot3;
} else if (!isNaN(slot1AsInt) && slot1AsInt > 12) {
// First parsing slot appears to be a day, assume user used DDMMYYYY format
parsedEventDay = parsedSlot1;
parsedEventMonth = parsedSlot2;
parsedEventYear = parsedSlot3;
} else if (!isNaN(slot2AsInt) && slot2AsInt > 12) {
// Second parsing slot appears to be a day, assume user used MMDDYYYY format
parsedEventMonth = parsedSlot1;
parsedEventDay = parsedSlot2;
parsedEventYear = parsedSlot3;
} else if (config.defaultDateFormat === DateTimeFormats.DDMMYYYY) {
// Year was not first, and cannot locate a day from the string, fall back to bot's default setting
parsedEventDay = parsedSlot1;
parsedEventMonth = parsedSlot2;
parsedEventYear = parsedSlot3;
} else if (config.defaultDateFormat === DateTimeFormats.MMDDYYYY) {
// Year was not first, and cannot locate a day from the string, fall back to bot's default setting
parsedEventMonth = parsedSlot1;
parsedEventDay = parsedSlot2;
parsedEventYear = parsedSlot3;
}
return [parsedEventMonth, parsedEventDay, parsedEventYear];
};
// Takes user input Date and makes it actually usable
const parseEventDate = (preParsedEventDate: string): [string, string, string] => {
const today = new Date();
const [parsedSlot1, parsedSlot2, parsedSlot3] = preParsedEventDate.split(/[\s,\\/-]+/g);
let [parsedEventMonth, parsedEventDay, parsedEventYear] = determineDateTimeOrder(parsedSlot1, parsedSlot2, parsedSlot3);
let [parsedEventMonth, parsedEventDay, parsedEventYear] = preParsedEventDate.split(/[\s,\\/-]+/g);
if (isNaN(parseInt(parsedEventDay))) {
// User only provided one word, we're assuming it was TOMORROW, and all others will be treated as today
@ -220,13 +154,12 @@ const parseEventDate = (preParsedEventDate: string): [string, string, string] =>
};
// Take full raw Date/Time input and convert it to a proper Date
export const getDateFromRawInput = (rawEventTime: string, rawEventTimeZone: string, rawEventDate: string, newEvent: boolean): [Date, string, boolean, boolean, string] => {
export const getDateFromRawInput = (rawEventTime: string, rawEventTimeZone: string, rawEventDate: string): [Date, string, boolean, boolean] => {
// Verify/Set Time
const [parsedEventTimeHours, parsedEventTimeMinutes, parsedEventTimePeriod] = parseEventTime(rawEventTime.replaceAll(':', '').toUpperCase());
// Verify/Set Time Zone
const [parsedEventTimeZone, userInputTimeZone] = parseEventTimeZone(rawEventTimeZone.replaceAll(' ', '').trim().toUpperCase());
const usTZWarning = usTZDSTCheck(userInputTimeZone, newEvent);
// Verify/Set Date
const [parsedEventYear, parsedEventMonth, parsedEventDay] = parseEventDate(rawEventDate.trim().toUpperCase());
@ -239,6 +172,5 @@ export const getDateFromRawInput = (rawEventTime: string, rawEventTimeZone: stri
} ${parsedEventDay}, ${parsedEventYear}`,
parsedDateTime.getTime() > new Date().getTime(),
!isNaN(parsedDateTime.getTime()),
usTZWarning,
];
};

View File

@ -67,7 +67,7 @@ const execute = async (bot: Bot, interaction: Interaction) => {
}
// Get Date Object from user input
const [eventDateTime, eventDateTimeStr, eventInFuture, dateTimeValid, usTZWarning] = getDateFromRawInput(rawEventTime, rawEventTimeZone, rawEventDate, true);
const [eventDateTime, eventDateTimeStr, eventInFuture, dateTimeValid] = getDateFromRawInput(rawEventTime, rawEventTimeZone, rawEventDate);
addTokenToMap(bot, interaction, interaction.guildId, interaction.channelId, interaction.member.id);
bot.helpers.sendInteractionResponse(
@ -89,7 +89,6 @@ const execute = async (bot: Bot, interaction: Interaction) => {
customIdIdxPath,
eventInFuture,
dateTimeValid,
usTZWarning,
),
).catch((e: Error) => utils.commonLoggers.interactionSendError('step2-finalize.ts', interaction, e));
} else {

View File

@ -2,7 +2,7 @@ import { ApplicationCommandFlags, Bot, Interaction, InteractionResponseTypes, Me
import { generateLFGButtons } from './utils.ts';
import { idSeparator, LfgEmbedIndexes } from '../eventUtils.ts';
import { deleteTokenEarly } from '../tokenCleanup.ts';
import { commonFixes, dmTestMessage, safelyDismissMsg, sendDirectMessage, somethingWentWrong, warnColor } from '../../commandUtils.ts';
import { dmTestMessage, safelyDismissMsg, sendDirectMessage, somethingWentWrong, warnColor } from '../../commandUtils.ts';
import { dbClient } from '../../db/client.ts';
import { queries } from '../../db/common.ts';
import utils from '../../utils.ts';
@ -52,7 +52,7 @@ const execute = async (bot: Bot, interaction: Interaction) => {
}],
}).catch((e: Error) => utils.commonLoggers.messageSendError('step3-createEvent.ts', 'createEvent', e));
if (!eventMessage) {
somethingWentWrong(bot, interaction, 'creatingEventSendMessageFinalizeEventStep', commonFixes.CANT_SEND_MESSAGE);
somethingWentWrong(bot, interaction, 'creatingEventSendMessageFinalizeEventStep');
return;
}

View File

@ -55,7 +55,7 @@ export const generateActionRow = (baseValue: string, activities: Array<Activity>
const createEventBtnName = 'Create Event';
const createWhitelistedBtnName = 'Create Whitelisted Event';
export const editEventDetailsBtnName = 'Edit Event Details';
const editEventDetailsBtnName = 'Edit Event Details';
export const invalidDateTimeStr = '`Invalid Date/Time`';
const finalizeButtons = (idxPath: string, eventInFuture: boolean): [ButtonComponent, ButtonComponent, ButtonComponent] | [ButtonComponent] => {
const editButton: ButtonComponent = {
@ -129,7 +129,6 @@ export const createLFGPost = (
idxPath: string,
eventInFuture: boolean,
dateTimeValid: boolean,
usTZWarning: string,
): InteractionResponse => {
const icsDetails = `${category}: ${activity.name}`;
const dateTimePastFutureStr = dateTimeValid ? 'in the past' : 'with an invalid date/time';
@ -139,7 +138,7 @@ export const createLFGPost = (
data: {
flags: ApplicationCommandFlags.Ephemeral,
content: eventInFuture
? `${usTZWarning}🛑🛑🛑 HEY! ONE MORE THING! 🛑🛑🛑\n\nPlease verify the information below, then click on the \`${createEventBtnName}\` or \`${createWhitelistedBtnName}\` button, or change the event \`Date/Time\` or \`Description\` with the \`${editEventDetailsBtnName}\` button below. \n\n${
? `Please verify the information below, then click on the \`${createEventBtnName}\` or \`${createWhitelistedBtnName}\` button, or change the event \`Date/Time\` or \`Description\` with the \`${editEventDetailsBtnName}\` button below. \n\n${
selfDestructMessage(new Date().getTime())
}`
: `You cannot create an event ${dateTimePastFutureStr}. Please change the event's \`Date/Time\` to be ${dateTimeValidStr} with the \`${editEventDetailsBtnName}\` button below.`,

View File

@ -1,4 +1,3 @@
import config from '../../config.ts';
import { ActionRow, MessageComponentTypes, TextStyles } from '../../deps.ts';
import { LFGMember } from '../types/commandTypes.ts';
import { isDSTActive } from './event-creation/dateTimeUtils.ts';
@ -27,12 +26,10 @@ export const alternateEventBtnStr = 'Join as Alternate';
export const noDescProvided = 'No description provided.';
// Member List generators
const escapeMemberNameForDisplay = (memberName: string): string => memberName.replaceAll('\\', '').replaceAll('_', '\\_');
export const generateMemberTitle = (memberList: Array<LFGMember>, maxMembers: number): string => `Members Joined: ${memberList.length}/${maxMembers}`;
export const generateMemberList = (memberList: Array<LFGMember>): string =>
memberList.length ? memberList.map((member) => `${escapeMemberNameForDisplay(member.name)} - <@${member.id}>`).join('\n') : noMembersStr;
export const generateMemberList = (memberList: Array<LFGMember>): string => memberList.length ? memberList.map((member) => `${member.name} - <@${member.id}>`).join('\n') : noMembersStr;
export const generateAlternateList = (alternateList: Array<LFGMember>): string =>
alternateList.length ? alternateList.map((member) => `${escapeMemberNameForDisplay(member.name)} - <@${member.id}>${member.joined ? ' *' : ''}`).join('\n') : noMembersStr;
alternateList.length ? alternateList.map((member) => `${member.name} - <@${member.id}>${member.joined ? ' *' : ''}`).join('\n') : noMembersStr;
// Fields for event creation and editing modals
export const eventTimeId = 'eventTime';
@ -87,7 +84,7 @@ export const dateTimeFields = (prefillTime = '', prefillTimeZone = '', prefillDa
type: MessageComponentTypes.InputText,
customId: eventDateId,
label: 'Start Date:',
placeholder: `Enter date as "${config.defaultDateFormat}" or "Month Day, Year"`,
placeholder: 'Enter date as "MONTH/DAY/YEAR" or "Month Day, Year"',
style: TextStyles.Short,
minLength: 1,
maxLength: 20,

View File

@ -33,7 +33,7 @@ const execute = async (bot: Bot, interaction: Interaction) => {
}
// Get Date Object from user input
const [eventDateTime, eventDateTimeStr, eventInFuture, dateTimeValid, usTZWarning] = getDateFromRawInput(newTime, newTimeZone, newDate, false);
const [eventDateTime, eventDateTimeStr, eventInFuture, dateTimeValid] = getDateFromRawInput(newTime, newTimeZone, newDate);
if (!eventInFuture || !dateTimeValid) {
bot.helpers.sendInteractionResponse(interaction.id, interaction.token, {
type: InteractionResponseTypes.ChannelMessageWithSource,
@ -70,7 +70,7 @@ const execute = async (bot: Bot, interaction: Interaction) => {
type: InteractionResponseTypes.ChannelMessageWithSource,
data: {
flags: ApplicationCommandFlags.Ephemeral,
content: applyEditMessage(new Date().getTime(), usTZWarning),
content: applyEditMessage(new Date().getTime()),
embeds: [eventMessage.embeds[0]],
components: applyEditButtons(interaction.data.customId.split(idSeparator)[1] || ''),
},

View File

@ -31,7 +31,7 @@ const execute = async (bot: Bot, interaction: Interaction) => {
type: InteractionResponseTypes.ChannelMessageWithSource,
data: {
flags: ApplicationCommandFlags.Ephemeral,
content: applyEditMessage(new Date().getTime(), ''),
content: applyEditMessage(new Date().getTime()),
embeds: [eventMessage.embeds[0]],
components: applyEditButtons(interaction.data.customId.split(idSeparator)[1] || ''),
},

View File

@ -143,7 +143,7 @@ const execute = async (bot: Bot, interaction: Interaction) => {
type: InteractionResponseTypes.ChannelMessageWithSource,
data: {
flags: ApplicationCommandFlags.Ephemeral,
content: applyEditMessage(new Date().getTime(), ''),
content: applyEditMessage(new Date().getTime()),
embeds: [eventMessage.embeds[0]],
components: applyEditButtons(interaction.data.customId.replaceAll(fillerChar, '').split(idSeparator)[1] || ''),
},

View File

@ -106,7 +106,7 @@ ${safelyDismissMsg}`,
timestamp: new Date().getTime(),
});
}).catch((e: Error) => {
somethingWentWrong(bot, interaction, 'failedToDMOwnerInRequestToJoinEventButton', `${config.name} could not message <@${ownerId}>. This likely means <@${ownerId}> has turned off DMs.`);
somethingWentWrong(bot, interaction, 'failedToDMOwnerInRequestToJoinEventButton');
utils.commonLoggers.messageSendError('joinEvent.ts@dmOwner', 'failed to DM owner for join request', e);
});
}

View File

@ -194,7 +194,7 @@ export const removeMemberFromEvent = async (
await sendDirectMessage(bot, memberToPromote.id, {
embeds: [{
color: successColor,
title: "Good news, you've been promoted!",
title: 'Good news, you\'ve been promoted!',
description: `A member left [the full event](${utils.idsToMessageUrl(urlIds)}) in \`${await getGuildName(
bot,
evtGuildId,
@ -353,8 +353,8 @@ export const joinRequestResponseButtons = (disabled: boolean): ActionRow[] => [{
}];
export const applyEditButtonName = 'Apply Edit';
export const applyEditMessage = (currentTime: number, usTZWarning: string) =>
`${usTZWarning}Please verify the updated event below, then click on the \`${applyEditButtonName}\` button. If this does not look right, please dismiss this message and start over.\n\n${
export const applyEditMessage = (currentTime: number) =>
`Please verify the updated event below, then click on the \`${applyEditButtonName}\` button. If this does not look right, please dismiss this message and start over.\n\n${
selfDestructMessage(currentTime)
}`;
export const applyEditButtons = (idxPath: string): ActionRow[] => [{

View File

@ -27,7 +27,7 @@ export const isLFGChannel = (guildId: bigint, channelId: bigint) => {
};
// Tell user to try again or report issue
export const somethingWentWrong = (bot: Bot, interaction: Interaction, errorCode: string, possibleFix = 'No fix provided.') =>
export const somethingWentWrong = (bot: Bot, interaction: Interaction, errorCode: string) =>
bot.helpers.sendInteractionResponse(interaction.id, interaction.token, {
type: InteractionResponseTypes.ChannelMessageWithSource,
data: {
@ -35,24 +35,15 @@ export const somethingWentWrong = (bot: Bot, interaction: Interaction, errorCode
embeds: [{
color: failColor,
title: 'Something went wrong...',
description:
`You should not be able to get here. If ${config.name} has seen this error before, the developer may have a possible fix for you to try. If one is provided, please attempt it before \`/${reportSlashName}\`ing it. If the issue continues, please \`/${reportSlashName}\` this issue to the developer with the error code below.`,
description: `You should not be able to get here. Please try again and if the issue continues, \`/${reportSlashName}\` this issue to the developers with the error code below.`,
fields: [{
name: 'Error Code:',
value: `\`${errorCode}\``,
}, {
name: 'Possible Fix:',
value: possibleFix,
}],
}],
},
}).catch((e: Error) => utils.commonLoggers.interactionSendError('commandUtils.ts@somethingWentWrong', interaction, e));
// Possible fixes for the user to try before reporting.
export const commonFixes = {
CANT_SEND_MESSAGE: `Please verify ${config.name} has permission to send messages in this channel.`,
};
// Smack the user for trying to modify an event that isn't theirs
export const stopThat = (bot: Bot, interaction: Interaction, stopWhat: string) =>
bot.helpers.sendInteractionResponse(interaction.id, interaction.token, {

View File

@ -1,5 +1,5 @@
import config from '../../config.ts';
import { ApplicationCommandOptionTypes, ApplicationCommandTypes, BotWithCache, DiscordEmbedField, Interaction, InteractionResponseTypes } from '../../deps.ts';
import { ApplicationCommandOptionTypes, ApplicationCommandTypes, Bot, DiscordEmbedField, Interaction, InteractionResponseTypes } from '../../deps.ts';
import { infoColor2, isLFGChannel, somethingWentWrong } from '../commandUtils.ts';
import { dbClient } from '../db/client.ts';
import { queries } from '../db/common.ts';
@ -7,20 +7,8 @@ import { CommandDetails } from '../types/commandTypes.ts';
import utils from '../utils.ts';
import { auditSlashName } from './slashCommandNames.ts';
type DupeAct = {
upperActTitle?: string;
upperActSubtitle?: string;
dupeCount: number;
};
type DBSizeTable = {
table: string;
size: number;
rows: number;
};
const auditDbName = 'database';
const auditCustomActivitiesName = 'custom-activities';
const auditCustomActivities = 'custom-activities';
const auditGuildName = 'guilds';
const details: CommandDetails = {
@ -35,7 +23,7 @@ const details: CommandDetails = {
description: `Developer Command: Checks ${config.name}'s DB size.`,
},
{
name: auditCustomActivitiesName,
name: auditCustomActivities,
type: ApplicationCommandOptionTypes.SubCommand,
description: 'Developer Command: Checks for duplicate custom activities.',
},
@ -47,18 +35,18 @@ const details: CommandDetails = {
],
};
const execute = async (bot: BotWithCache, interaction: Interaction) => {
const execute = async (bot: Bot, interaction: Interaction) => {
if (interaction.member && interaction.guildId && interaction.data?.options?.[0].options) {
dbClient.execute(queries.callIncCnt('cmd-audit')).catch((e) => utils.commonLoggers.dbError('audit.ts@inc', 'call sproc INC_CNT on', e));
const auditName = interaction.data.options[0].name;
switch (auditName) {
case auditDbName: {
// Get DB statistics
const auditQuery: Array<DBSizeTable> = await dbClient.query(`SELECT * FROM db_size;`).catch((e) => utils.commonLoggers.dbError('audit.ts@dbSize', 'query', e));
const auditQuery = await dbClient.query(`SELECT * FROM db_size;`).catch((e) => utils.commonLoggers.dbError('audit.ts@dbSize', 'query', e));
// Turn all tables into embed fields, currently only properly will handle 25 tables, but we'll fix that when group up gets 26 tables
const embedFields: Array<DiscordEmbedField> = [];
auditQuery.forEach((row) => {
auditQuery.forEach((row: any) => {
embedFields.push({
name: `${row.table}`,
value: `**Size:** ${row.size} MB
@ -66,129 +54,27 @@ const execute = async (bot: BotWithCache, interaction: Interaction) => {
inline: true,
});
});
bot.helpers
.sendInteractionResponse(interaction.id, interaction.token, {
type: InteractionResponseTypes.ChannelMessageWithSource,
data: {
flags: isLFGChannel(interaction.guildId || 0n, interaction.channelId || 0n),
embeds: [
bot.helpers.sendInteractionResponse(
interaction.id,
interaction.token,
{
color: infoColor2,
title: 'Database Audit',
description: 'Lists all tables with their current size and row count.',
timestamp: new Date().getTime(),
fields: embedFields.slice(0, 25),
},
],
},
})
.catch((e: Error) => utils.commonLoggers.interactionSendError('audit.ts@dbSize', interaction, e));
break;
}
case auditCustomActivitiesName: {
const dupActTitles: Array<DupeAct> = await dbClient.query(
`SELECT UPPER(activityTitle) as upperActTitle, COUNT(*) as dupeCount FROM custom_activities GROUP BY upperActTitle HAVING dupeCount > 1;`,
).catch((e) => utils.commonLoggers.dbError('audit.ts@customActTitle', 'query', e));
const dupActSubTitles: Array<DupeAct> = await dbClient.query(
`SELECT UPPER(activitySubtitle) as upperActSubtitle, COUNT(*) as dupeCount FROM custom_activities GROUP BY upperActSubtitle HAVING dupeCount > 1;`,
).catch((e) => utils.commonLoggers.dbError('audit.ts@customActSubTitle', 'query', e));
const dupActs: Array<DupeAct> = await dbClient
.query(
`SELECT UPPER(activityTitle) as upperActTitle, UPPER(activitySubtitle) as upperActSubtitle, COUNT(*) as dupeCount FROM custom_activities GROUP BY upperActTitle, upperActSubtitle HAVING dupeCount > 1;`,
)
.catch((e) => utils.commonLoggers.dbError('audit.ts@customAct', 'query', e));
bot.helpers
.sendInteractionResponse(interaction.id, interaction.token, {
type: InteractionResponseTypes.ChannelMessageWithSource,
data: {
flags: isLFGChannel(interaction.guildId || 0n, interaction.channelId || 0n),
content: 'Duplicate Custom Activity Titles, Subtitles, and Activities:',
embeds: [
{
color: infoColor2,
title: 'Duplicate Activity Titles:',
description: dupActTitles.map((dupAct) => `${dupAct.upperActTitle}: ${dupAct.dupeCount}`).join('\n'),
timestamp: new Date().getTime(),
},
{
color: infoColor2,
title: 'Duplicate Activity Subtitles:',
description: dupActSubTitles.map((dupAct) => `${dupAct.upperActSubtitle}: ${dupAct.dupeCount}`).join('\n'),
timestamp: new Date().getTime(),
},
{
color: infoColor2,
title: 'Duplicate Activities (Title/Subtitle):',
description: dupActs.map((dupAct) => `${dupAct.upperActTitle}/${dupAct.upperActSubtitle}: ${dupAct.dupeCount}`).join('\n'),
timestamp: new Date().getTime(),
},
],
},
})
.catch((e: Error) => utils.commonLoggers.interactionSendError('audit.ts@dbSize', interaction, e));
break;
}
case auditGuildName: {
let totalCount = 0;
let auditText = '';
bot.guilds.forEach((guild) => {
totalCount += guild.memberCount;
auditText += `Guild: ${guild.name} (${guild.id})
Owner: ${guild.ownerId}
Tot mem: ${guild.memberCount}
`;
});
const b = await new Blob([auditText as BlobPart], { 'type': 'text' });
const tooBig = await new Blob(['tooBig' as BlobPart], { 'type': 'text' });
bot.helpers
.sendInteractionResponse(interaction.id, interaction.token, {
type: InteractionResponseTypes.ChannelMessageWithSource,
data: {
flags: isLFGChannel(interaction.guildId || 0n, interaction.channelId || 0n),
embeds: [{
color: infoColor2,
title: 'Guilds Audit',
description: `Shows details of the guilds that ${config.name} serves.
Please see attached file for audit details on cached guilds and members.`,
fields: [
{
name: 'Total Guilds:',
value: `${bot.guilds.size}`,
inline: true,
},
{
name: 'Uncached Guilds:',
value: `${bot.dispatchedGuildIds.size}`,
inline: true,
},
{
name: 'Total Members\n(may be artificially higher if 1 user is in multiple guilds the bot is in):',
value: `${totalCount}`,
inline: true,
},
{
name: 'Average members per guild:',
value: `${(totalCount / bot.guilds.size).toFixed(2)}`,
inline: true,
},
],
title: 'Database Audit',
description: 'Lists all tables with their current size and row count.',
timestamp: new Date().getTime(),
fields: embedFields.slice(0, 25),
}],
file: {
'blob': b.size > 8388290 ? tooBig : b,
'name': 'auditDetails.txt',
},
},
})
.catch((e: Error) => utils.commonLoggers.interactionSendError('audit.ts@guilds', interaction, e));
).catch((e: Error) => utils.commonLoggers.interactionSendError('audit.ts@dbSize', interaction, e));
break;
}
case auditCustomActivities:
case auditGuildName:
default:
somethingWentWrong(bot, interaction, `auditNameNotHandled@${auditName}`);
break;

View File

@ -31,9 +31,6 @@ const execute = (bot: Bot, interaction: Interaction) => {
color: infoColor2,
title: 'USER REPORT:',
description: interaction.data.options[0].value as string,
footer: {
text: `${interaction.guildId}-${interaction.user.id}`,
},
}],
}).catch((e: Error) => utils.commonLoggers.interactionSendError('report.ts:28', interaction, e));
bot.helpers.sendInteractionResponse(

View File

@ -1,4 +1,4 @@
import { ApplicationCommandOption, ApplicationCommandTypes, Bot, BotWithCache, Interaction, PermissionStrings } from '../../deps.ts';
import { ApplicationCommandOption, ApplicationCommandTypes, PermissionStrings } from '../../deps.ts';
export type CommandDetails = {
name: string;
@ -11,12 +11,12 @@ export type CommandDetails = {
export type Command = {
details: CommandDetails;
execute: ((bot: Bot, interaction: Interaction) => void) | ((bot: BotWithCache, interaction: Interaction) => void);
execute: Function;
};
export type Button = {
customId: string;
execute: ((bot: Bot, interaction: Interaction) => void) | ((bot: BotWithCache, interaction: Interaction) => void);
execute: Function;
};
export type LfgChannelSetting = {

View File

@ -1,7 +1,6 @@
import { CreateMessage, Interaction, log, LT, Message } from '../deps.ts';
import { UrlIds } from './types/commandTypes.ts';
// deno-lint-ignore no-explicit-any
const jsonStringifyBig = (input: any) => {
return JSON.stringify(input, (_key, value) => typeof value === 'bigint' ? value.toString() + 'n' : value);
};

View File

@ -0,0 +1,54 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
<meta name="HandheldFriendly" content="true"/>
<meta name="author" content="Ean Milligan (ean@milligan.dev)">
<meta name="designer" content="Ean Milligan (ean@milligan.dev)">
<meta name="publisher" content="Ean Milligan (ean@milligan.dev)">
<title>Group Up Time Checker</title>
<meta name="description" content="The Group Up Discord Bot is a fancy event scheduler. This page converts the time of an event to the user's local time.">
<meta name="robots" content="index, follow">
<meta name="revisit-after" content="7 days">
<meta name="distribution" content="web">
<meta name="robots" content="noodp, noydir">
<meta name="distribution" content="web">
<meta name="web_author" content="Ean Milligan (ean@milligan.dev)">
<style>
body {
font-family: sans-serif;
font-size: 5rem;
height: 100vh;
padding: 0;
margin: 0;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
color: #dcddde;
background-color: #2f3136;
}
</style>
</head>
<body>
<div id="message">
I don't know why you are here, the URL should have a timestamp in it.
</div>
<script>
if (window.location.hash) {
var groupDate = new Date(parseInt(window.location.hash.substr(1)));
document.getElementById("message").innerText = "Your event is happening at:\n\n" + groupDate.toLocaleString();
}
</script>
</body>
</html>