2021-01-07 22:02:38 -08:00
/ * T h e A r t i f i c e r w a s b u i l t i n m e m o r y o f B a b k a
* With love , Ean
*
* December 21 , 2020
* /
V1.4.0 Completed
This update adds a handful of new features and readies the API for public use. Detailed changes below:
config.example.ts - Bumped version, added local testing options, added prefill data, changed default logging to false, moved long strings for help and rollhelp to longStrings.ts
db/initialize.ts (previously initDB.ts) - Relocated file for organization, added local options, added new command count and allowed guilds table, created stored procedure for counting commands
db/populateDefaults.ts - Fills in db with default values, adding admin's api key and populating the command count table
flags.ts - Centralized flags for dev/local modes
longStrings.ts - Moved all long string commands to here, contains help, rollhelp, apihelp, info, and privacy commands
mod.ts - Moved flags out into flags.ts, implemented local mode for development, implemented command counting for basic statistics, added info and privacy commands for user help, added more stats to the stats command, added api command, allowing users to allow or block api rolls from happening in their server, reformatted API code, using proper HTTP methods, makes sure api is allowed to roll into chosen guild, added delete endpoint to remove user's data from the database, added endpoint to allow the general public to generate their own api keys
PRIVACY.md - I got bored and wrote a privacy policy, detailing how little data is collected and how the user can have their data removed
README.md - Added new commands to README, updated API documentation, added delete endpoint, updated self hosting details
src/utils.ts - Bumped discordeno version
www/api - Built API website
www/home (previously located in www) - Moved for better organization, minor fixes, updated API details,
2021-02-12 20:26:33 -08:00
import { Message , MessageContent } from "https://deno.land/x/discordeno@10.3.0/mod.ts" ;
2021-01-07 05:34:14 -08:00
2021-01-07 11:00:46 -08:00
// split2k(longMessage) returns shortMessage[]
// split2k takes a long string in and cuts it into shorter strings to be sent in Discord
2021-01-07 05:34:14 -08:00
const split2k = ( chunk : string ) : string [ ] = > {
2021-01-07 11:00:46 -08:00
// Replace any malformed newline characters
2021-01-07 05:34:14 -08:00
chunk = chunk . replace ( /\\n/g , "\n" ) ;
const bites = [ ] ;
2021-01-07 11:00:46 -08:00
// While there is more characters than allowed to be sent in discord
2021-01-07 05:34:14 -08:00
while ( chunk . length > 2000 ) {
2021-01-07 11:00:46 -08:00
// Take 2001 chars to see if word magically ends on char 2000
2021-01-07 05:34:14 -08:00
let bite = chunk . substr ( 0 , 2001 ) ;
2021-01-07 11:00:46 -08:00
const lastI = bite . lastIndexOf ( " " ) ;
if ( lastI < 2000 ) {
// If there is a final word before the 2000 split point, split right after that word
bite = bite . substr ( 0 , lastI ) ;
2021-01-07 05:34:14 -08:00
} else {
2021-01-07 11:00:46 -08:00
// Else cut exactly 2000 characters
2021-01-07 05:34:14 -08:00
bite = bite . substr ( 0 , 2000 ) ;
}
2021-01-07 11:00:46 -08:00
// Push and remove the bite taken out of the chunk
2021-01-07 05:34:14 -08:00
bites . push ( bite ) ;
chunk = chunk . slice ( bite . length ) ;
}
// Push leftovers into bites
bites . push ( chunk ) ;
return bites ;
} ;
2021-01-07 11:00:46 -08:00
// ask(prompt) returns string
// ask prompts the user at command line for message
const ask = async ( question : string , stdin = Deno . stdin , stdout = Deno . stdout ) : Promise < string > = > {
2021-01-07 05:34:14 -08:00
const buf = new Uint8Array ( 1024 ) ;
// Write question to console
await stdout . write ( new TextEncoder ( ) . encode ( question ) ) ;
// Read console's input into answer
const n = < number > await stdin . read ( buf ) ;
const answer = new TextDecoder ( ) . decode ( buf . subarray ( 0 , n ) ) ;
return answer . trim ( ) ;
} ;
2021-01-07 11:00:46 -08:00
// cmdPrompt(logChannel, botName, sendMessage) returns nothing
// cmdPrompt creates an interactive CLI for the bot, commands can vary
2021-01-07 05:34:14 -08:00
const cmdPrompt = async ( logChannel : string , botName : string , sendMessage : ( c : string , m : string ) = > Promise < Message > ) : Promise < void > = > {
let done = false ;
while ( ! done ) {
2021-01-07 11:00:46 -08:00
// Get a command and its args
2021-01-07 05:34:14 -08:00
const fullCmd = await ask ( "cmd> " ) ;
2021-01-07 11:00:46 -08:00
// Split the args off of the command and prep the command
2021-01-07 05:34:14 -08:00
const args = fullCmd . split ( " " ) ;
const command = args . shift ( ) ? . toLowerCase ( ) ;
2021-01-07 11:00:46 -08:00
// All commands below here
// exit or e
// Fully closes the bot
2021-01-07 05:34:14 -08:00
if ( command === "exit" || command === "e" ) {
console . log ( ` ${ botName } Shutting down. \ n \ nGoodbye. ` ) ;
done = true ;
Deno . exit ( 0 ) ;
2021-01-07 11:00:46 -08:00
}
// stop
// Closes the CLI only, leaving the bot running truly headless
else if ( command === "stop" ) {
2021-01-07 05:34:14 -08:00
console . log ( ` Closing ${ botName } CLI. Bot will continue to run. \ n \ nGoodbye. ` ) ;
done = true ;
2021-01-07 11:00:46 -08:00
}
// m [channel] [message]
// Sends [message] to specified [channel]
else if ( command === "m" ) {
2021-01-07 05:34:14 -08:00
try {
const channelID = args . shift ( ) || "" ;
const message = args . join ( " " ) ;
2021-01-07 11:00:46 -08:00
// Utilize the split2k function to ensure a message over 2000 chars is not sent
2021-01-07 05:34:14 -08:00
const messages = split2k ( message ) ;
for ( let i = 0 ; i < messages . length ; i ++ ) {
sendMessage ( channelID , messages [ i ] ) . catch ( reason = > {
console . error ( reason ) ;
} ) ;
}
}
catch ( e ) {
console . error ( e ) ;
}
2021-01-07 11:00:46 -08:00
}
// ml [message]
// Sends a message to the specified log channel
else if ( command === "ml" ) {
2021-01-07 05:34:14 -08:00
const message = args . join ( " " ) ;
2021-01-07 11:00:46 -08:00
// Utilize the split2k function to ensure a message over 2000 chars is not sent
2021-01-07 05:34:14 -08:00
const messages = split2k ( message ) ;
for ( let i = 0 ; i < messages . length ; i ++ ) {
sendMessage ( logChannel , messages [ i ] ) . catch ( reason = > {
console . error ( reason ) ;
} ) ;
}
2021-01-07 11:00:46 -08:00
}
// help or h
// Shows a basic help menu
else if ( command === "help" || command === "h" ) {
2021-01-07 05:34:14 -08:00
console . log ( ` ${ botName } CLI Help: \ n \ nAvailable Commands: \ n exit - closes bot \ n stop - closes the CLI \ n m [ChannelID] [messgae] - sends message to specific ChannelID as the bot \ n ml [message] sends a message to the specified botlog \ n help - this message ` ) ;
2021-01-07 11:00:46 -08:00
}
// Unhandled commands die here
else {
2021-01-07 05:34:14 -08:00
console . log ( "undefined command" ) ;
}
}
} ;
2021-01-07 11:00:46 -08:00
// sendIndirectMessage(originalMessage, messageContent, sendMessage, sendDirectMessage) returns Message
// sendIndirectMessage determines if the message needs to be sent as a direct message or as a normal message
2021-01-26 23:35:46 -08:00
const sendIndirectMessage = async ( originalMessage : Message , messageContent : ( string | MessageContent ) , sendMessage : ( c : string , m : ( string | MessageContent ) ) = > Promise < Message > , sendDirectMessage : ( c : string , m : ( string | MessageContent ) ) = > Promise < Message > ) : Promise < Message > = > {
2021-01-07 11:00:46 -08:00
if ( originalMessage . guildID === "" ) {
// guildID was empty, meaning the original message was sent as a DM
return await sendDirectMessage ( originalMessage . author . id , messageContent ) ;
2021-01-07 05:34:14 -08:00
} else {
2021-01-07 11:00:46 -08:00
// guildID was not empty, meaning the original message was sent in a server
return await sendMessage ( originalMessage . channelID , messageContent ) ;
2021-01-07 05:34:14 -08:00
}
} ;
export default { split2k , cmdPrompt , sendIndirectMessage } ;