2021-02-19 17:44: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
* /
import {
// Discordeno deps
CacheData , Message , MessageContent ,
// MySQL Driver deps
Client ,
// httpd deps
serve ,
Status , STATUS_TEXT ,
// nanoid deps
nanoid
} from "../deps.ts" ;
import solver from "./solver.ts" ;
2021-03-14 20:27:53 -07:00
import { LogTypes as LT } from "./utils.enums.ts" ;
import utils from "./utils.ts" ;
2021-02-19 17:44:38 -08:00
import config from "../config.ts" ;
// start(databaseClient, botCache, sendMessage, sendDirectMessage) returns nothing
// start initializes and runs the entire API for the bot
const start = async ( dbClient : Client , cache : CacheData , sendMessage : ( c : string , m : ( string | MessageContent ) ) = > Promise < Message > , sendDirectMessage : ( c : string , m : ( string | MessageContent ) ) = > Promise < Message > ) : Promise < void > = > {
const server = serve ( { hostname : "0.0.0.0" , port : config.api.port } ) ;
2021-03-14 20:27:53 -07:00
utils . log ( LT . LOG , ` HTTP api running at: http://localhost: ${ config . api . port } / ` ) ;
2021-02-19 17:44:38 -08:00
// rateLimitTime holds all users with the last time they started a rate limit timer
const rateLimitTime = new Map < string , number > ( ) ;
// rateLimitCnt holds the number of times the user has called the api in the current rate limit timer
const rateLimitCnt = new Map < string , number > ( ) ;
// Catching every request made to the server
for await ( const request of server ) {
// Check if user is authenticated to be using this API
let authenticated = false ;
let rateLimited = false ;
let updateRateLimitTime = false ;
let apiUserid = 0 n ;
let apiUseridStr = "" ;
let apiUserEmail = "" ;
let apiUserDelCode = "" ;
// Check the requests API key
if ( request . headers . has ( "X-Api-Key" ) ) {
// Get the userid and flags for the specific key
const dbApiQuery = await dbClient . query ( "SELECT userid, email, deleteCode FROM all_keys WHERE apiKey = ? AND active = 1 AND banned = 0" , [ request . headers . get ( "X-Api-Key" ) ] ) ;
// If only one user returned, is not banned, and is currently active, mark as authenticated
if ( dbApiQuery . length === 1 ) {
apiUserid = BigInt ( dbApiQuery [ 0 ] . userid ) ;
apiUserEmail = dbApiQuery [ 0 ] . email ;
apiUserDelCode = dbApiQuery [ 0 ] . deleteCode ;
authenticated = true ;
// Rate limiting inits
apiUseridStr = apiUserid . toString ( ) ;
const apiTimeNow = new Date ( ) . getTime ( ) ;
// Check if user has sent a request recently
if ( rateLimitTime . has ( apiUseridStr ) && ( ( ( rateLimitTime . get ( apiUseridStr ) || 0 ) + config . api . rateLimitTime ) > apiTimeNow ) ) {
// Get current count
const currentCnt = rateLimitCnt . get ( apiUseridStr ) || 0 ;
if ( currentCnt < config . api . rateLimitCnt ) {
// Limit not yet exceeded, update count
rateLimitCnt . set ( apiUseridStr , ( currentCnt + 1 ) ) ;
} else {
// Limit exceeded, prevent API use
rateLimited = true ;
}
} else {
// Update the maps
updateRateLimitTime = true ;
rateLimitCnt . set ( apiUseridStr , 1 ) ;
}
}
}
if ( authenticated && ! rateLimited ) {
// Get path and query as a string
const [ path , tempQ ] = request . url . split ( "?" ) ;
// Turn the query into a map (if it exists)
const query = new Map < string , string > ( ) ;
if ( tempQ !== undefined ) {
tempQ . split ( "&" ) . forEach ( e = > {
const [ option , params ] = e . split ( "=" ) ;
query . set ( option . toLowerCase ( ) , params ) ;
} ) ;
}
// Handle the request
switch ( request . method ) {
case "GET" :
switch ( path . toLowerCase ( ) ) {
case "/api/key" :
case "/api/key/" :
if ( ( query . has ( "user" ) && ( ( query . get ( "user" ) || "" ) . length > 0 ) ) && ( query . has ( "a" ) && ( ( query . get ( "a" ) || "" ) . length > 0 ) ) ) {
if ( apiUserid === config . api . admin && apiUserid === BigInt ( query . get ( "a" ) ) ) {
// Generate new secure key
const newKey = await nanoid ( 25 ) ;
// Flag to see if there is an error inside the catch
let erroredOut = false ;
// Insert new key/user pair into the db
2021-03-14 20:27:53 -07:00
await dbClient . execute ( "INSERT INTO all_keys(userid,apiKey) values(?,?)" , [ apiUserid , newKey ] ) . catch ( e = > {
utils . log ( LT . ERROR , ` Failed to insert into database: ${ JSON . stringify ( e ) } ` ) ;
2021-02-19 17:44:38 -08:00
request . respond ( { status : Status.InternalServerError , body : STATUS_TEXT.get ( Status . InternalServerError ) } ) ;
erroredOut = true ;
} ) ;
// Exit this case now if catch errored
if ( erroredOut ) {
break ;
} else {
// Send API key as response
request . respond ( { status : Status.OK , body : JSON.stringify ( { "key" : newKey , "userid" : query . get ( "user" ) } ) } ) ;
break ;
}
} else {
// Only allow the db admin to use this API
request . respond ( { status : Status.Forbidden , body : STATUS_TEXT.get ( Status . Forbidden ) } ) ;
}
} else {
// Alert API user that they messed up
request . respond ( { status : Status.BadRequest , body : STATUS_TEXT.get ( Status . BadRequest ) } ) ;
}
break ;
case "/api/channel" :
case "/api/channel/" :
if ( query . has ( "user" ) && ( ( query . get ( "user" ) || "" ) . length > 0 ) ) {
if ( apiUserid === BigInt ( query . get ( "user" ) ) ) {
// Flag to see if there is an error inside the catch
let erroredOut = false ;
// Get all channels userid has authorized
2021-03-14 20:27:53 -07:00
const dbAllowedChannelQuery = await dbClient . query ( "SELECT * FROM allowed_channels WHERE userid = ?" , [ apiUserid ] ) . catch ( e = > {
utils . log ( LT . ERROR , ` Failed to insert into database: ${ JSON . stringify ( e ) } ` ) ;
2021-02-19 17:44:38 -08:00
request . respond ( { status : Status.InternalServerError , body : STATUS_TEXT.get ( Status . InternalServerError ) } ) ;
erroredOut = true ;
} ) ;
if ( erroredOut ) {
break ;
} else {
// Customized strinification to handle BigInts correctly
const returnChannels = JSON . stringify ( dbAllowedChannelQuery , ( _key , value ) = > ( typeof value === 'bigint' ? value . toString ( ) : value ) ) ;
// Send API key as response
request . respond ( { status : Status.OK , body : returnChannels } ) ;
break ;
}
} else {
// Alert API user that they shouldn't be doing this
request . respond ( { status : Status.Forbidden , body : STATUS_TEXT.get ( Status . Forbidden ) } ) ;
}
} else {
// Alert API user that they messed up
request . respond ( { status : Status.BadRequest , body : STATUS_TEXT.get ( Status . BadRequest ) } ) ;
}
break ;
case "/api/roll" :
case "/api/roll/" :
// Make sure query contains all the needed parts
if ( ( query . has ( "rollstr" ) && ( ( query . get ( "rollstr" ) || "" ) . length > 0 ) ) && ( query . has ( "channel" ) && ( ( query . get ( "channel" ) || "" ) . length > 0 ) ) && ( query . has ( "user" ) && ( ( query . get ( "user" ) || "" ) . length > 0 ) ) ) {
if ( query . has ( "n" ) && query . has ( "m" ) ) {
// Alert API user that they shouldn't be doing this
request . respond ( { status : Status.BadRequest , body : STATUS_TEXT.get ( Status . BadRequest ) } ) ;
break ;
}
// Check if user is authenticated to use this endpoint
let authorized = false ;
// Check if the db has the requested userid/channelid combo, and that the requested userid matches the userid linked with the api key
const dbChannelQuery = await dbClient . query ( "SELECT active, banned FROM allowed_channels WHERE userid = ? AND channelid = ?" , [ apiUserid , BigInt ( query . get ( "channel" ) ) ] ) ;
if ( dbChannelQuery . length === 1 && ( apiUserid === BigInt ( query . get ( "user" ) ) ) && dbChannelQuery [ 0 ] . active && ! dbChannelQuery [ 0 ] . banned ) {
// Get the guild from the channel and make sure user is in said guild
const guild = cache . channels . get ( query . get ( "channel" ) || "" ) ? . guild ;
if ( guild && guild . members . get ( query . get ( "user" ) || "" ) ? . id ) {
const dbGuildQuery = await dbClient . query ( "SELECT active, banned FROM allowed_guilds WHERE guildid = ?" , [ BigInt ( guild . id ) ] ) ;
// Make sure guild allows API rolls
if ( dbGuildQuery . length === 1 && dbGuildQuery [ 0 ] . active && ! dbGuildQuery [ 0 ] . banned ) {
authorized = true ;
}
}
}
if ( authorized ) {
// Rest of this command is in a try-catch to protect all sends/edits from erroring out
try {
// Flag to tell if roll was completely successful
let errorOut = false ;
// Make sure rollCmd is not undefined
let rollCmd = query . get ( "rollstr" ) || "" ;
const originalCommand = query . get ( "rollstr" ) ;
if ( rollCmd . length === 0 ) {
// Alert API user that they messed up
request . respond ( { status : Status.BadRequest , body : STATUS_TEXT.get ( Status . BadRequest ) } ) ;
// Always log API rolls for abuse detection
2021-03-14 20:27:53 -07:00
dbClient . execute ( "INSERT INTO roll_log(input,result,resultid,api,error) values(?,?,?,1,1)" , [ originalCommand , "EmptyInput" , null ] ) . catch ( e = > {
utils . log ( LT . ERROR , ` Failed to insert into database: ${ JSON . stringify ( e ) } ` ) ;
2021-02-19 17:44:38 -08:00
} ) ;
break ;
}
if ( query . has ( "o" ) && ( query . get ( "o" ) ? . toLowerCase ( ) !== "d" && query . get ( "o" ) ? . toLowerCase ( ) !== "a" ) ) {
// Alert API user that they messed up
request . respond ( { status : Status.BadRequest , body : STATUS_TEXT.get ( Status . BadRequest ) } ) ;
// Always log API rolls for abuse detection
2021-03-14 20:27:53 -07:00
dbClient . execute ( "INSERT INTO roll_log(input,result,resultid,api,error) values(?,?,?,1,1)" , [ originalCommand , "BadOrder" , null ] ) . catch ( e = > {
utils . log ( LT . ERROR , ` Failed to insert into database: ${ JSON . stringify ( e ) } ` ) ;
2021-02-19 17:44:38 -08:00
} ) ;
break ;
}
// Clip off the leading prefix. API calls must be formatted with a prefix at the start to match how commands are sent in Discord
rollCmd = rollCmd . substr ( rollCmd . indexOf ( config . prefix ) + 2 ) . replace ( /%20/g , " " ) ;
// Parse the roll and get the return text
const returnmsg = solver . parseRoll ( rollCmd , config . prefix , config . postfix , query . has ( "m" ) , query . has ( "n" ) , query . has ( "o" ) ? ( query . get ( "o" ) ? . toLowerCase ( ) || "" ) : "" ) ;
// Alert users why this message just appeared and how they can report abues pf this feature
const apiPrefix = "The following roll was conducted using my built in API. If someone in this channel did not request this roll, please report API abuse here: <" + config . api . supportURL + ">\n\n" ;
let m , returnText = "" ;
// Handle sending the error message to whoever called the api
if ( returnmsg . error ) {
request . respond ( { status : Status.InternalServerError , body : returnmsg.errorMsg } ) ;
// Always log API rolls for abuse detection
2021-03-14 20:27:53 -07:00
dbClient . execute ( "INSERT INTO roll_log(input,result,resultid,api,error) values(?,?,?,1,1)" , [ originalCommand , returnmsg . errorCode , null ] ) . catch ( e = > {
utils . log ( LT . ERROR , ` Failed to insert into database: ${ JSON . stringify ( e ) } ` ) ;
2021-02-19 17:44:38 -08:00
} ) ;
break ;
} else {
returnText = apiPrefix + "<@" + query . get ( "user" ) + ">" + returnmsg . line1 + "\n" + returnmsg . line2 ;
let spoilerTxt = "" ;
// Determine if spoiler flag was on
if ( query . has ( "s" ) ) {
spoilerTxt = "||" ;
}
// Determine if no details flag was on
if ( query . has ( "nd" ) ) {
returnText += "\nDetails suppressed by nd query." ;
} else {
returnText += "\nDetails:\n" + spoilerTxt + returnmsg . line3 + spoilerTxt ;
}
}
// If the roll was a GM roll, send DMs to all the GMs
if ( query . has ( "gms" ) ) {
// Get all the GM user IDs from the query
const gms = ( query . get ( "gms" ) || "" ) . split ( "," ) ;
if ( gms . length === 0 ) {
// Alert API user that they messed up
request . respond ( { status : Status.BadRequest , body : STATUS_TEXT.get ( Status . BadRequest ) } ) ;
// Always log API rolls for abuse detection
2021-03-14 20:27:53 -07:00
dbClient . execute ( "INSERT INTO roll_log(input,result,resultid,api,error) values(?,?,?,1,1)" , [ originalCommand , "NoGMsSent" , null ] ) . catch ( e = > {
utils . log ( LT . ERROR , ` Failed to insert into database: ${ JSON . stringify ( e ) } ` ) ;
2021-02-19 17:44:38 -08:00
} ) ;
break ;
}
// Make a new return line to be sent to the roller
let normalText = apiPrefix + "<@" + query . get ( "user" ) + ">" + returnmsg . line1 + "\nResults have been messaged to the following GMs: " ;
gms . forEach ( e = > {
normalText += "<@" + e + "> " ;
} ) ;
// Send the return message as a DM or normal message depening on if the channel is set
if ( ( query . get ( "channel" ) || "" ) . length > 0 ) {
m = await sendMessage ( query . get ( "channel" ) || "" , normalText ) . catch ( ( ) = > {
request . respond ( { status : Status.InternalServerError , body : "Message 00 failed to send." } ) ;
errorOut = true ;
} ) ;
} else {
m = await sendDirectMessage ( query . get ( "user" ) || "" , normalText ) . catch ( ( ) = > {
request . respond ( { status : Status.InternalServerError , body : "Message 01 failed to send." } ) ;
errorOut = true ;
} ) ;
}
// And message the full details to each of the GMs, alerting roller of every GM that could not be messaged
gms . forEach ( async e = > {
// If its too big, collapse it into a .txt file and send that instead.
const b = await new Blob ( [ returnText as BlobPart ] , { "type" : "text" } ) ;
// Update return text
returnText = apiPrefix + "<@" + query . get ( "user" ) + ">" + returnmsg . line1 + "\n" + returnmsg . line2 + "\nFull details have been attached to this messaged as a `.txt` file for verification purposes." ;
// Attempt to DM the GMs and send a warning if it could not DM a GM
await sendDirectMessage ( e , { "content" : returnText , "file" : { "blob" : b , "name" : "rollDetails.txt" } } ) . catch ( async ( ) = > {
const failedSend = "WARNING: <@" + e + "> could not be messaged. If this issue persists, make sure direct messages are allowed from this server."
// Send the return message as a DM or normal message depening on if the channel is set
if ( ( query . get ( "channel" ) || "" ) . length > 0 ) {
m = await sendMessage ( query . get ( "channel" ) || "" , failedSend ) . catch ( ( ) = > {
2021-03-14 20:27:53 -07:00
request . respond ( { status : Status.InternalServerError , body : "Message failed to send." } ) ;
2021-02-19 17:44:38 -08:00
errorOut = true ;
} ) ;
} else {
m = await sendDirectMessage ( query . get ( "user" ) || "" , failedSend ) . catch ( ( ) = > {
2021-03-14 20:27:53 -07:00
request . respond ( { status : Status.InternalServerError , body : "Message failed to send." } ) ;
2021-02-19 17:44:38 -08:00
errorOut = true ;
} ) ;
}
} ) ;
} ) ;
// Always log API rolls for abuse detection
2021-03-14 20:27:53 -07:00
dbClient . execute ( "INSERT INTO roll_log(input,result,resultid,api,error) values(?,?,?,1,0)" , [ originalCommand , returnText , ( ( typeof m === "object" ) ? m.id : null ) ] ) . catch ( e = > {
utils . log ( LT . ERROR , ` Failed to insert into database: ${ JSON . stringify ( e ) } ` ) ;
2021-02-19 17:44:38 -08:00
} ) ;
// Handle closing the request out
if ( errorOut ) {
break ;
} else {
request . respond ( { status : Status.OK , body : normalText } ) ;
break ;
}
} else {
const newMessage : MessageContent = { } ;
newMessage . content = returnText ;
// When not a GM roll, make sure the message is not too big
if ( returnText . length > 2000 ) {
// If its too big, collapse it into a .txt file and send that instead.
const b = await new Blob ( [ returnText as BlobPart ] , { "type" : "text" } ) ;
// Update return text
returnText = "<@" + query . get ( "user" ) + ">" + returnmsg . line1 + "\n" + returnmsg . line2 + "\nDetails have been ommitted from this message for being over 2000 characters. Full details have been attached to this messaged as a `.txt` file for verification purposes." ;
// Set info into the newMessage
newMessage . content = returnText ;
newMessage . file = { "blob" : b , "name" : "rollDetails.txt" } ;
}
// Send the return message as a DM or normal message depening on if the channel is set
if ( ( query . get ( "channel" ) || "" ) . length > 0 ) {
m = await sendMessage ( query . get ( "channel" ) || "" , newMessage ) . catch ( ( ) = > {
request . respond ( { status : Status.InternalServerError , body : "Message 20 failed to send." } ) ;
errorOut = true ;
} ) ;
} else {
m = await sendDirectMessage ( query . get ( "user" ) || "" , newMessage ) . catch ( ( ) = > {
request . respond ( { status : Status.InternalServerError , body : "Message 21 failed to send." } ) ;
errorOut = true ;
} ) ;
}
// If enabled, log rolls so we can verify the bots math
2021-03-14 20:27:53 -07:00
dbClient . execute ( "INSERT INTO roll_log(input,result,resultid,api,error) values(?,?,?,1,0)" , [ originalCommand , returnText , ( ( typeof m === "object" ) ? m.id : null ) ] ) . catch ( e = > {
utils . log ( LT . ERROR , ` Failed to insert into database: ${ JSON . stringify ( e ) } ` ) ;
2021-02-19 17:44:38 -08:00
} ) ;
// Handle closing the request out
if ( errorOut ) {
break ;
} else {
request . respond ( { status : Status.OK , body : returnText } ) ;
break ;
}
}
} catch ( err ) {
// Handle any errors we missed
2021-03-14 20:27:53 -07:00
utils . log ( LT . ERROR , ` Unhandled Error: ${ JSON . stringify ( err ) } ` ) ;
2021-02-19 17:44:38 -08:00
request . respond ( { status : Status.InternalServerError , body : STATUS_TEXT.get ( Status . InternalServerError ) } ) ;
}
} else {
// Alert API user that they messed up
request . respond ( { status : Status.Forbidden , body : STATUS_TEXT.get ( Status . Forbidden ) } ) ;
}
} else {
// Alert API user that they shouldn't be doing this
request . respond ( { status : Status.BadRequest , body : STATUS_TEXT.get ( Status . BadRequest ) } ) ;
}
break ;
default :
// Alert API user that they messed up
request . respond ( { status : Status.NotFound , body : STATUS_TEXT.get ( Status . NotFound ) } ) ;
break ;
}
break ;
case "POST" :
switch ( path . toLowerCase ( ) ) {
case "/api/channel/add" :
case "/api/channel/add/" :
if ( ( query . has ( "user" ) && ( ( query . get ( "user" ) || "" ) . length > 0 ) ) && ( query . has ( "channel" ) && ( ( query . get ( "channel" ) || "" ) . length > 0 ) ) ) {
if ( apiUserid === BigInt ( query . get ( "user" ) ) ) {
// Flag to see if there is an error inside the catch
let erroredOut = false ;
// Insert new user/channel pair into the db
2021-03-14 20:27:53 -07:00
await dbClient . execute ( "INSERT INTO allowed_channels(userid,channelid) values(?,?)" , [ apiUserid , BigInt ( query . get ( "channel" ) ) ] ) . catch ( e = > {
utils . log ( LT . ERROR , ` Failed to insert into database: ${ JSON . stringify ( e ) } ` ) ;
2021-02-19 17:44:38 -08:00
request . respond ( { status : Status.InternalServerError , body : STATUS_TEXT.get ( Status . InternalServerError ) } ) ;
erroredOut = true ;
} ) ;
// Exit this case now if catch errored
if ( erroredOut ) {
break ;
} else {
// Send API key as response
request . respond ( { status : Status.OK , body : STATUS_TEXT.get ( Status . OK ) } ) ;
break ;
}
} else {
// Alert API user that they shouldn't be doing this
request . respond ( { status : Status.Forbidden , body : STATUS_TEXT.get ( Status . Forbidden ) } ) ;
}
} else {
// Alert API user that they messed up
request . respond ( { status : Status.BadRequest , body : STATUS_TEXT.get ( Status . BadRequest ) } ) ;
}
break ;
default :
// Alert API user that they messed up
request . respond ( { status : Status.NotFound , body : STATUS_TEXT.get ( Status . NotFound ) } ) ;
break ;
}
break ;
case "PUT" :
switch ( path . toLowerCase ( ) ) {
case "/api/key/ban" :
case "/api/key/ban/" :
case "/api/key/unban" :
case "/api/key/unban/" :
case "/api/key/activate" :
case "/api/key/activate/" :
case "/api/key/deactivate" :
case "/api/key/deactivate/" :
if ( ( query . has ( "a" ) && ( ( query . get ( "a" ) || "" ) . length > 0 ) ) && ( query . has ( "user" ) && ( ( query . get ( "user" ) || "" ) . length > 0 ) ) ) {
if ( apiUserid === config . api . admin && apiUserid === BigInt ( query . get ( "a" ) ) ) {
// Flag to see if there is an error inside the catch
let key , value , erroredOut = false ;
// Determine key to edit
if ( path . toLowerCase ( ) . indexOf ( "ban" ) > 0 ) {
key = "banned" ;
} else {
key = "active" ;
}
// Determine value to set
if ( path . toLowerCase ( ) . indexOf ( "de" ) > 0 || path . toLowerCase ( ) . indexOf ( "un" ) > 0 ) {
value = 0 ;
} else {
value = 1 ;
}
// Execute the DB modification
2021-03-14 20:27:53 -07:00
await dbClient . execute ( "UPDATE all_keys SET ?? = ? WHERE userid = ?" , [ key , value , apiUserid ] ) . catch ( e = > {
utils . log ( LT . ERROR , ` Failed to insert into database: ${ JSON . stringify ( e ) } ` ) ;
2021-02-19 17:44:38 -08:00
request . respond ( { status : Status.InternalServerError , body : STATUS_TEXT.get ( Status . InternalServerError ) } ) ;
erroredOut = true ;
} ) ;
// Exit this case now if catch errored
if ( erroredOut ) {
break ;
} else {
// Send API key as response
request . respond ( { status : Status.OK , body : STATUS_TEXT.get ( Status . OK ) } ) ;
break ;
}
} else {
// Alert API user that they shouldn't be doing this
request . respond ( { status : Status.Forbidden , body : STATUS_TEXT.get ( Status . Forbidden ) } ) ;
}
} else {
// Alert API user that they messed up
request . respond ( { status : Status.BadRequest , body : STATUS_TEXT.get ( Status . BadRequest ) } ) ;
}
break ;
case "/api/channel/ban" :
case "/api/channel/ban/" :
case "/api/channel/unban" :
case "/api/channel/unban/" :
if ( ( query . has ( "a" ) && ( ( query . get ( "a" ) || "" ) . length > 0 ) ) && ( query . has ( "channel" ) && ( ( query . get ( "channel" ) || "" ) . length > 0 ) ) && ( query . has ( "user" ) && ( ( query . get ( "user" ) || "" ) . length > 0 ) ) ) {
if ( apiUserid === config . api . admin && apiUserid === BigInt ( query . get ( "a" ) ) ) {
// Flag to see if there is an error inside the catch
let value , erroredOut = false ;
// Determine value to set
if ( path . toLowerCase ( ) . indexOf ( "un" ) > 0 ) {
value = 0 ;
} else {
value = 1 ;
}
// Execute the DB modification
2021-03-14 20:27:53 -07:00
await dbClient . execute ( "UPDATE allowed_channels SET banned = ? WHERE userid = ? AND channelid = ?" , [ value , apiUserid , BigInt ( query . get ( "channel" ) ) ] ) . catch ( e = > {
utils . log ( LT . ERROR , ` Failed to insert into database: ${ JSON . stringify ( e ) } ` ) ;
2021-02-19 17:44:38 -08:00
request . respond ( { status : Status.InternalServerError , body : STATUS_TEXT.get ( Status . InternalServerError ) } ) ;
erroredOut = true ;
} ) ;
// Exit this case now if catch errored
if ( erroredOut ) {
break ;
} else {
// Send API key as response
request . respond ( { status : Status.OK , body : STATUS_TEXT.get ( Status . OK ) } ) ;
break ;
}
} else {
// Alert API user that they shouldn't be doing this
request . respond ( { status : Status.Forbidden , body : STATUS_TEXT.get ( Status . Forbidden ) } ) ;
}
} else {
// Alert API user that they messed up
request . respond ( { status : Status.BadRequest , body : STATUS_TEXT.get ( Status . BadRequest ) } ) ;
}
break ;
case "/api/channel/activate" :
case "/api/channel/activate/" :
case "/api/channel/deactivate" :
case "/api/channel/deactivate/" :
if ( ( query . has ( "channel" ) && ( ( query . get ( "channel" ) || "" ) . length > 0 ) ) && ( query . has ( "user" ) && ( ( query . get ( "user" ) || "" ) . length > 0 ) ) ) {
if ( apiUserid === BigInt ( query . get ( "user" ) ) ) {
// Flag to see if there is an error inside the catch
let value , erroredOut = false ;
// Determine value to set
if ( path . toLowerCase ( ) . indexOf ( "de" ) > 0 ) {
value = 0 ;
} else {
value = 1 ;
}
// Update the requested entry
2021-03-14 20:27:53 -07:00
await dbClient . execute ( "UPDATE allowed_channels SET active = ? WHERE userid = ? AND channelid = ?" , [ value , apiUserid , BigInt ( query . get ( "channel" ) ) ] ) . catch ( e = > {
utils . log ( LT . ERROR , ` Failed to insert into database: ${ JSON . stringify ( e ) } ` ) ;
2021-02-19 17:44:38 -08:00
request . respond ( { status : Status.InternalServerError , body : STATUS_TEXT.get ( Status . InternalServerError ) } ) ;
erroredOut = true ;
} ) ;
// Exit this case now if catch errored
if ( erroredOut ) {
break ;
} else {
// Send API key as response
request . respond ( { status : Status.OK , body : STATUS_TEXT.get ( Status . OK ) } ) ;
break ;
}
} else {
// Alert API user that they shouldn't be doing this
request . respond ( { status : Status.Forbidden , body : STATUS_TEXT.get ( Status . Forbidden ) } ) ;
}
} else {
// Alert API user that they messed up
request . respond ( { status : Status.BadRequest , body : STATUS_TEXT.get ( Status . BadRequest ) } ) ;
}
break ;
default :
// Alert API user that they messed up
request . respond ( { status : Status.NotFound , body : STATUS_TEXT.get ( Status . NotFound ) } ) ;
break ;
}
break ;
case "DELETE" :
switch ( path . toLowerCase ( ) ) {
case "/api/key/delete" :
case "/api/key/delete/" :
if ( query . has ( "user" ) && ( ( query . get ( "user" ) || "" ) . length > 0 ) && query . has ( "email" ) && ( ( query . get ( "email" ) || "" ) . length > 0 ) ) {
if ( apiUserid === BigInt ( query . get ( "user" ) ) && apiUserEmail === query . get ( "email" ) ) {
if ( query . has ( "code" ) && ( ( query . get ( "code" ) || "" ) . length > 0 ) ) {
if ( ( query . get ( "code" ) || "" ) === apiUserDelCode ) {
// User has recieved their delete code and we need to delete the account now
let erroredOut = false ;
2021-03-14 20:27:53 -07:00
await dbClient . execute ( "DELETE FROM allowed_channels WHERE userid = ?" , [ apiUserid ] ) . catch ( e = > {
utils . log ( LT . ERROR , ` Failed to insert into database: ${ JSON . stringify ( e ) } ` ) ;
2021-02-19 17:44:38 -08:00
request . respond ( { status : Status.InternalServerError , body : STATUS_TEXT.get ( Status . InternalServerError ) } ) ;
erroredOut = true ;
} ) ;
if ( erroredOut ) {
break ;
}
2021-03-14 20:27:53 -07:00
await dbClient . execute ( "DELETE FROM all_keys WHERE userid = ?" , [ apiUserid ] ) . catch ( e = > {
utils . log ( LT . ERROR , ` Failed to insert into database: ${ JSON . stringify ( e ) } ` ) ;
2021-02-19 17:44:38 -08:00
request . respond ( { status : Status.InternalServerError , body : STATUS_TEXT.get ( Status . InternalServerError ) } ) ;
erroredOut = true ;
} ) ;
if ( erroredOut ) {
break ;
} else {
// Send API key as response
request . respond ( { status : Status.OK , body : STATUS_TEXT.get ( Status . OK ) } ) ;
break ;
}
} else {
// Alert API user that they shouldn't be doing this
request . respond ( { status : Status.Forbidden , body : STATUS_TEXT.get ( Status . Forbidden ) } ) ;
}
} else {
// User does not have their delete code yet, so we need to generate one and email it to them
const deleteCode = await nanoid ( 10 ) ;
let erroredOut = false ;
// Execute the DB modification
2021-03-14 20:27:53 -07:00
await dbClient . execute ( "UPDATE all_keys SET deleteCode = ? WHERE userid = ?" , [ deleteCode , apiUserid ] ) . catch ( e = > {
utils . log ( LT . ERROR , ` Failed to insert into database: ${ JSON . stringify ( e ) } ` ) ;
2021-02-19 17:44:38 -08:00
request . respond ( { status : Status.InternalServerError , body : STATUS_TEXT.get ( Status . InternalServerError ) } ) ;
erroredOut = true ;
} ) ;
if ( erroredOut ) {
break ;
}
// "Send" the email
await sendMessage ( config . api . email , ` <@ ${ config . api . admin } > A USER HAS REQUESTED A DELETE CODE \ n \ nEmail Address: ${ apiUserEmail } \ n \ nSubject: \` Artificer API Delete Code \` \ n \ n \` \` \` Hello Artificer API User, \ n \ nI am sorry to see you go. If you would like, please respond to this email detailing what I could have done better. \ n \ nAs requested, here is your delete code: ${ deleteCode } \ n \ nSorry to see you go, \ nThe Artificer Developer - Ean Milligan \` \` \` ` ) . catch ( ( ) = > {
request . respond ( { status : Status.InternalServerError , body : "Message 30 failed to send." } ) ;
erroredOut = true ;
} ) ;
if ( erroredOut ) {
break ;
} else {
// Send API key as response
request . respond ( { status : Status.FailedDependency , body : STATUS_TEXT.get ( Status . FailedDependency ) } ) ;
break ;
}
}
} else {
// Alert API user that they shouldn't be doing this
request . respond ( { status : Status.Forbidden , body : STATUS_TEXT.get ( Status . Forbidden ) } ) ;
}
} else {
// Alert API user that they messed up
request . respond ( { status : Status.BadRequest , body : STATUS_TEXT.get ( Status . BadRequest ) } ) ;
}
break ;
default :
// Alert API user that they messed up
request . respond ( { status : Status.NotFound , body : STATUS_TEXT.get ( Status . NotFound ) } ) ;
break ;
}
break ;
default :
// Alert API user that they messed up
request . respond ( { status : Status.MethodNotAllowed , body : STATUS_TEXT.get ( Status . MethodNotAllowed ) } ) ;
break ;
}
if ( updateRateLimitTime ) {
const apiTimeNow = new Date ( ) . getTime ( ) ;
rateLimitTime . set ( apiUseridStr , apiTimeNow ) ;
}
} else if ( ! authenticated && ! rateLimited ) {
// Get path and query as a string
const [ path , tempQ ] = request . url . split ( "?" ) ;
// Turn the query into a map (if it exists)
const query = new Map < string , string > ( ) ;
if ( tempQ !== undefined ) {
tempQ . split ( "&" ) . forEach ( e = > {
const [ option , params ] = e . split ( "=" ) ;
query . set ( option . toLowerCase ( ) , params ) ;
} ) ;
}
// Handle the request
switch ( request . method ) {
case "GET" :
switch ( path . toLowerCase ( ) ) {
case "/api/key" :
case "/api/key/" :
if ( ( query . has ( "user" ) && ( ( query . get ( "user" ) || "" ) . length > 0 ) ) && ( query . has ( "email" ) && ( ( query . get ( "email" ) || "" ) . length > 0 ) ) ) {
// Generate new secure key
const newKey = await nanoid ( 25 ) ;
// Flag to see if there is an error inside the catch
let erroredOut = false ;
// Insert new key/user pair into the db
2021-03-14 20:27:53 -07:00
await dbClient . execute ( "INSERT INTO all_keys(userid,apiKey,email) values(?,?,?)" , [ BigInt ( query . get ( "user" ) ) , newKey , ( query . get ( "email" ) || "" ) . toLowerCase ( ) ] ) . catch ( e = > {
utils . log ( LT . ERROR , ` Failed to insert into database: ${ JSON . stringify ( e ) } ` ) ;
2021-02-19 17:44:38 -08:00
request . respond ( { status : Status.InternalServerError , body : STATUS_TEXT.get ( Status . InternalServerError ) } ) ;
erroredOut = true ;
} ) ;
// Exit this case now if catch errored
if ( erroredOut ) {
break ;
}
// "Send" the email
await sendMessage ( config . api . email , ` <@ ${ config . api . admin } > A USER HAS REQUESTED AN API KEY \ n \ nEmail Address: ${ query . get ( "email" ) } \ n \ nSubject: \` Artificer API Key \` \ n \ n \` \` \` Hello Artificer API User, \ n \ nWelcome aboard The Artificer's API. You can find full details about the API on the GitHub: https://github.com/Burn-E99/TheArtificer \ n \ nYour API Key is: ${ newKey } \ n \ nGuard this well, as there is zero tolerance for API abuse. \ n \ nWelcome aboard, \ nThe Artificer Developer - Ean Milligan \` \` \` ` ) . catch ( ( ) = > {
request . respond ( { status : Status.InternalServerError , body : "Message 31 failed to send." } ) ;
erroredOut = true ;
} ) ;
if ( erroredOut ) {
break ;
} else {
// Send API key as response
request . respond ( { status : Status.OK , body : STATUS_TEXT.get ( Status . OK ) } ) ;
break ;
}
} else {
// Alert API user that they messed up
request . respond ( { status : Status.BadRequest , body : STATUS_TEXT.get ( Status . BadRequest ) } ) ;
}
break ;
default :
// Alert API user that they messed up
request . respond ( { status : Status.NotFound , body : STATUS_TEXT.get ( Status . NotFound ) } ) ;
break ;
}
break ;
default :
// Alert API user that they messed up
request . respond ( { status : Status.MethodNotAllowed , body : STATUS_TEXT.get ( Status . MethodNotAllowed ) } ) ;
break ;
}
} else if ( authenticated && rateLimited ) {
// Alert API user that they are doing this too often
request . respond ( { status : Status.TooManyRequests , body : STATUS_TEXT.get ( Status . TooManyRequests ) } ) ;
} else {
// Alert API user that they shouldn't be doing this
request . respond ( { status : Status.Forbidden , body : STATUS_TEXT.get ( Status . Forbidden ) } ) ;
}
}
} ;
export default { start } ;