2023-11-15 11:03:01 +01:00
import { Client , CommandInteraction , GuildMember , EmbedBuilder , TextChannel , AutocompleteInteraction , User } from 'discord.js' ;
2023-11-15 11:57:20 +01:00
import { getSign } from '../util' ;
import { Counter , CounterConfiguration , CounterUserLink , db } from '../db' ;
2023-11-15 11:03:01 +01:00
import { formatItems , getItem , getItemQuantity , getMaxStack , giveItem } from './items' ;
2023-11-21 21:28:32 +01:00
import { resetInvincible } from './pvp' ;
2022-07-19 21:39:34 +02:00
2023-11-13 17:29:49 +01:00
export async function getCounter ( id : number ) {
2023-11-11 00:01:38 +01:00
const counter = await db < Counter > ( 'counters' )
2023-11-13 17:29:49 +01:00
. select ( 'value' )
. where ( 'id' , id )
2023-11-11 00:01:38 +01:00
. first ( ) ;
2022-07-19 21:39:34 +02:00
2023-11-11 00:01:38 +01:00
if ( ! counter ) throw 'No such counter' ;
2023-04-19 21:12:45 +02:00
2023-11-11 00:01:38 +01:00
return counter . value ;
2022-07-19 21:39:34 +02:00
}
2023-11-13 17:29:49 +01:00
export async function changeCounter ( id : number , delta : number ) {
const value = await getCounter ( id ) ;
2023-11-11 00:01:38 +01:00
const newValue = value + delta ;
2022-07-19 21:39:34 +02:00
2023-11-11 00:01:38 +01:00
await db < Counter > ( 'counters' )
2023-11-13 17:29:49 +01:00
. where ( 'id' , id )
2023-11-11 00:01:38 +01:00
. update ( {
'value' : newValue
} ) ;
2023-04-19 21:05:15 +02:00
2023-11-11 00:01:38 +01:00
return newValue ;
2022-07-19 21:39:34 +02:00
}
2023-11-13 17:29:49 +01:00
export async function getCounterData ( id : number ) {
const counter = await db < Counter > ( 'counters' )
. select ( '*' )
. where ( 'id' , id )
. first ( ) ;
if ( ! counter ) throw 'No such counter' ;
return counter ;
}
export async function findCounter ( key : string , guild : string ) {
2023-11-11 00:01:38 +01:00
const counter = await db < Counter > ( 'counters' )
. select ( '*' )
2023-11-13 17:29:49 +01:00
. where ( 'key' , key )
. where ( 'guild' , guild )
2023-11-11 00:01:38 +01:00
. first ( ) ;
2023-04-19 21:05:15 +02:00
2023-11-11 00:01:38 +01:00
if ( ! counter ) throw 'No such counter' ;
2023-04-19 21:05:15 +02:00
2023-11-11 00:01:38 +01:00
return counter ;
2022-07-19 21:39:34 +02:00
}
2023-11-13 17:29:49 +01:00
export async function getCounterConfigRaw ( counter : Counter ) {
2023-11-13 16:10:33 +01:00
const configs = await db < CounterConfiguration > ( 'counterConfigurations' )
. select ( 'configName' , 'value' )
2023-11-13 17:29:49 +01:00
. where ( 'id' , counter . id ) ;
2023-11-13 16:10:33 +01:00
const config = new Map < string , string > ( ) ;
configs . forEach ( ( { configName , value } ) = > {
config . set ( configName , value ) ;
} ) ;
// just the ugly way of life
config . set ( 'emoji' , counter . emoji ) ;
return config ;
}
2023-11-13 17:29:49 +01:00
export async function getCounterConfig ( id : number , key : string ) {
2023-11-13 16:10:33 +01:00
const config = await db < CounterConfiguration > ( 'counterConfigurations' )
. select ( 'value' )
2023-11-13 17:29:49 +01:00
. where ( 'id' , id )
2023-11-14 16:24:22 +01:00
. where ( 'configName' , key )
2023-11-13 16:10:33 +01:00
. first ( ) ;
const valueStr = config ? . value ;
let value ;
if ( valueStr ) {
value = parseConfig ( valueStr , counterConfigs . get ( key ) ! . type ) ;
} else {
value = counterConfigs . get ( key ) ! . default ;
}
return value ;
}
2023-11-15 11:03:01 +01:00
export async function setCounterConfig ( counter : Counter , option : string , value : string ) {
2023-11-13 16:10:33 +01:00
// just the ugly way of life
2023-11-15 11:03:01 +01:00
if ( option === 'emoji' && ! counter . linkedItem ) {
2023-11-13 16:10:33 +01:00
await db < Counter > ( 'counters' )
2023-11-15 11:03:01 +01:00
. where ( 'id' , counter . id )
2023-11-13 16:10:33 +01:00
. update ( {
2023-11-15 12:01:51 +01:00
emoji : value
2023-11-13 16:10:33 +01:00
} ) ;
return ;
}
const updated = await db < CounterConfiguration > ( 'counterConfigurations' )
. update ( {
value : value
} )
2023-11-15 11:03:01 +01:00
. where ( 'id' , counter . id )
2023-11-13 16:10:33 +01:00
. where ( 'configName' , option ) ;
if ( updated === 0 ) {
await db < CounterConfiguration > ( 'counterConfigurations' )
. insert ( {
2023-11-15 12:01:51 +01:00
id : counter.id ,
configName : option ,
value : value
2023-11-13 16:10:33 +01:00
} ) ;
}
}
export enum ConfigType {
Bool ,
String ,
Number
}
export function parseConfig ( str : string , type : ConfigType . Bool ) : boolean
export function parseConfig ( str : string , type : ConfigType . String ) : string
export function parseConfig ( str : string , type : ConfigType . Number ) : number
export function parseConfig ( str : string , type : ConfigType ) : boolean | string | number
export function parseConfig ( str : string , type : ConfigType ) {
switch ( type ) {
case ConfigType . Bool :
return str === 'true' ;
case ConfigType . String :
return str . trim ( ) ;
case ConfigType . Number : {
const n = parseInt ( str ) ;
if ( isNaN ( n ) ) throw 'Not a valid number' ;
return n ;
}
}
}
export function getOptions ( type : ConfigType ) : string [ ] {
switch ( type ) {
case ConfigType . Bool :
return [ 'true' , 'false' ] ;
case ConfigType . String :
return [ ] ;
case ConfigType . Number :
return [ ] ;
}
}
export function toStringConfig ( value : boolean | string | number , type : ConfigType ) : string {
switch ( type ) {
case ConfigType . Bool :
return value ? 'true' : 'false' ;
case ConfigType . String :
return ( value as string ) ;
case ConfigType . Number :
return ( value as number ) . toString ( ) ;
}
}
export const counterConfigs = new Map ( [
[ 'anonymous' , {
type : ConfigType . Bool ,
default : false
} ] ,
2023-11-13 18:01:19 +01:00
[ 'messageTemplate' , {
type : ConfigType . String ,
default : '**%user** has %action the counter by **%amt**.'
} ] ,
2023-11-13 18:07:13 +01:00
[ 'messageTemplateIncrease' , {
type : ConfigType . String ,
2023-11-13 18:11:11 +01:00
default : 'null'
2023-11-13 18:07:13 +01:00
} ] ,
[ 'messageTemplateDecrease' , {
type : ConfigType . String ,
2023-11-13 18:11:11 +01:00
default : 'null'
2023-11-13 18:07:13 +01:00
} ] ,
2023-11-15 11:03:01 +01:00
[ 'messageTemplateTake' , {
type : ConfigType . String ,
default : '**%user** has taken **%amt** from the counter.'
} ] ,
[ 'messageTemplatePut' , {
type : ConfigType . String ,
default : '**%user** has put **%amt** into the counter.'
} ] ,
[ 'canIncrement' , {
type : ConfigType . Bool ,
default : true
} ] ,
[ 'canDecrement' , {
type : ConfigType . Bool ,
default : true
} ] ,
[ 'canPut' , {
type : ConfigType . Bool ,
default : false
} ] ,
[ 'canTake' , {
type : ConfigType . Bool ,
default : false
} ] ,
2023-11-15 11:18:25 +01:00
[ 'min' , {
type : ConfigType . Number ,
default : - Number . MIN_SAFE_INTEGER
} ] ,
[ 'max' , {
type : ConfigType . Number ,
default : Number . MAX_SAFE_INTEGER
} ] ,
2023-11-13 16:10:33 +01:00
// these ones are fake and are just stand-ins for values defined inside the actual counters table
[ 'emoji' , {
type : ConfigType . String ,
default : ''
} ]
] ) ;
2023-11-12 14:35:04 +01:00
export async function updateCounter ( bot : Client , counter : Counter , value : number ) {
2023-11-11 00:01:38 +01:00
const channel = await bot . channels . fetch ( counter . channel ) as TextChannel ;
const messageID = counter . message ;
2022-07-19 21:39:34 +02:00
2023-11-12 14:35:04 +01:00
const content = ` [ ${ counter . emoji } ] x ${ value } ` ;
2022-07-19 21:39:34 +02:00
// bit janky
2023-11-11 00:01:38 +01:00
// yeah you don't say
2022-07-19 21:39:34 +02:00
try {
if ( messageID ) {
const message = await channel . messages . fetch ( messageID ) ;
if ( ! message ) throw new Error ( ) ;
await message . edit ( content ) ;
} else {
throw new Error ( ) ;
}
} catch ( err ) {
const message = await channel . send ( content ) ;
message . pin ( ) ;
2023-11-11 00:01:38 +01:00
await db < Counter > ( 'counters' )
2023-11-13 17:29:49 +01:00
. where ( 'id' , counter . id )
2023-11-11 00:01:38 +01:00
. update ( {
2023-11-15 12:01:51 +01:00
message : message.id
2023-11-11 00:01:38 +01:00
} ) ;
2022-07-19 21:39:34 +02:00
}
}
2023-11-15 11:03:01 +01:00
export async function announceCounterUpdate ( bot : Client , member : GuildMember , delta : number , counter : Counter , value : number , linked : boolean = false ) {
2023-11-11 00:01:38 +01:00
const channel = await bot . channels . fetch ( counter . channel ) as TextChannel ;
2022-07-19 21:39:34 +02:00
2023-11-13 18:07:13 +01:00
let template = await getCounterConfig ( counter . id , 'messageTemplate' ) as string ;
const templateIncrease = await getCounterConfig ( counter . id , 'messageTemplateIncrease' ) as string ;
2023-11-13 18:11:11 +01:00
if ( templateIncrease !== 'null' && delta > 0 ) template = templateIncrease ;
2023-11-13 18:07:13 +01:00
const templateDecrease = await getCounterConfig ( counter . id , 'messageTemplateDecrease' ) as string ;
2023-11-13 18:11:11 +01:00
if ( templateDecrease !== 'null' && delta < 0 ) template = templateDecrease ;
2023-11-15 11:03:01 +01:00
const templatePut = await getCounterConfig ( counter . id , 'messageTemplatePut' ) as string ;
if ( templatePut !== 'null' && delta > 0 && linked ) template = templatePut ;
const templateTake = await getCounterConfig ( counter . id , 'messageTemplateTake' ) as string ;
if ( templateTake !== 'null' && delta < 0 && linked ) template = templateTake ;
2023-11-13 18:07:13 +01:00
2023-11-13 18:01:19 +01:00
const anonymous = await getCounterConfig ( counter . id , 'anonymous' ) as boolean ;
2023-11-13 16:10:33 +01:00
2023-06-11 17:23:57 +02:00
const embed = new EmbedBuilder ( )
2023-11-13 16:10:33 +01:00
//.setDescription(`**${member.toString()}** has ${delta > 0 ? 'increased' : 'decreased'} the counter by **${Math.abs(delta)}**.`)
. setDescription (
template
. replaceAll ( '%user' , anonymous ? 'someone' : member . toString ( ) )
2023-11-15 11:03:01 +01:00
. replaceAll ( '%action' , delta > 0 ? ( linked ? 'put into' : 'increased' ) : ( linked ? 'taken from' : 'decreased' ) )
2023-11-13 16:10:33 +01:00
. replaceAll ( '%amt' , Math . abs ( delta ) . toString ( ) )
2023-11-13 18:07:13 +01:00
. replaceAll ( '%total' , value . toString ( ) )
2023-11-13 16:10:33 +01:00
)
2022-07-19 21:39:34 +02:00
. setTimestamp ( )
2023-06-11 17:23:57 +02:00
. setFooter ( {
2023-11-12 14:35:04 +01:00
text : ` [ ${ counter . emoji } ] x ${ value } `
2023-06-11 17:23:57 +02:00
} ) ;
2022-07-19 21:39:34 +02:00
2023-11-13 16:10:33 +01:00
if ( ! anonymous ) {
embed
. setAuthor ( {
2023-11-13 16:13:59 +01:00
name : member.displayName ,
iconURL : member.displayAvatarURL ( )
2023-11-13 16:10:33 +01:00
} )
. setColor ( member . displayColor ) ;
}
2022-07-19 21:39:34 +02:00
await channel . send ( {
embeds : [ embed ]
} ) ;
}
2023-11-15 11:03:01 +01:00
async function canUseCounter ( user : User , counter : Counter , amount : number , isLinkedAction = false ) : Promise < boolean > {
if ( amount > 0 && ! ( await getCounterConfig ( counter . id , isLinkedAction ? 'canPut' : 'canIncrement' ) as boolean ) ) return false ;
if ( amount > 0 && counter . allowlistProducer ) {
const userLink = await db < CounterUserLink > ( 'counterUserLink' )
. where ( 'id' , counter . id )
. where ( 'user' , user . id )
. where ( 'producer' , true )
. first ( ) ;
if ( ! userLink ) return false ;
}
if ( amount < 0 && ! ( await getCounterConfig ( counter . id , isLinkedAction ? 'canTake' : 'canDecrement' ) as boolean ) ) return false ;
if ( amount < 0 && counter . allowlistConsumer ) {
const userLink = await db < CounterUserLink > ( 'counterUserLink' )
. where ( 'id' , counter . id )
. where ( 'user' , user . id )
. where ( 'producer' , false )
. first ( ) ;
2023-11-11 00:01:38 +01:00
2023-11-15 11:03:01 +01:00
if ( ! userLink ) return false ;
}
2023-11-12 20:44:03 +01:00
2023-11-15 11:03:01 +01:00
return true ;
}
2023-11-12 20:44:03 +01:00
2023-11-15 11:03:01 +01:00
function changeCounterInteractionBuilder ( linked : boolean ) {
return async ( interaction : CommandInteraction , member : GuildMember , amount : number , type : string ) = > {
try {
const counter = await findCounter ( type , member . guild . id ) ;
if ( linked && ! counter . linkedItem ) return interaction . followUp ( 'There is no such linked counter!' ) ;
const canUse = await canUseCounter ( member . user , counter , amount , linked ) ;
if ( ! canUse ) {
return interaction . followUp ( ` You cannot ** ${ amount > 0 ? ( linked ? 'put' : 'produce' ) : ( linked ? 'take' : 'consume' ) } ** ${ counter . emoji } . ` ) ;
}
2023-11-15 21:09:43 +01:00
const min = await getCounterConfig ( counter . id , 'min' ) as number ;
const max = await getCounterConfig ( counter . id , 'max' ) as number ;
if ( counter . value + amount < min ) {
if ( min === 0 ) {
return interaction . followUp ( ` You cannot remove more than how much is in the counter ( ${ counter . value } x ${ counter . emoji } )! ` ) ;
} else {
return interaction . followUp ( ` You cannot decrement past the minimum value ( ${ min } )! ` ) ;
}
}
if ( counter . value + amount > max ) {
return interaction . followUp ( ` You are adding more than the counter can hold ( ${ max } x)! ` ) ;
}
2023-11-15 11:03:01 +01:00
let item ;
let newInv ;
if ( linked ) {
const inv = await getItemQuantity ( member . id , counter . linkedItem ! ) ;
item = ( await getItem ( counter . linkedItem ! ) ) ! ;
// change counter by -10 = increment own counter by 10
const amtInv = - amount ;
const amtAbs = Math . abs ( amtInv ) ;
if ( amtInv > getMaxStack ( item ) ) {
return interaction . followUp ( ` You cannot take ${ formatItems ( item , amtAbs ) } , because the max stack size is ${ getMaxStack ( item ) } x! ` ) ;
}
if ( ( inv . quantity + amtInv ) > getMaxStack ( item ) ) {
return interaction . followUp ( ` You cannot take ${ formatItems ( item , amtAbs ) } , because the max stack size is ${ getMaxStack ( item ) } x and you already have ${ inv . quantity } x! ` ) ;
}
if ( ( inv . quantity + amtInv ) < 0 ) {
return interaction . followUp ( ` You cannot put in ${ formatItems ( item , amtAbs ) } , as you only have ${ formatItems ( item , inv . quantity ) } ! ` ) ;
}
newInv = await giveItem ( member . id , item , amtInv ) ;
}
2023-11-21 21:28:32 +01:00
await resetInvincible ( member . id ) ;
2023-11-15 11:03:01 +01:00
const newCount = await changeCounter ( counter . id , amount ) ;
await updateCounter ( interaction . client , counter , newCount ) ;
await announceCounterUpdate ( interaction . client , member , amount , counter , newCount , linked ) ;
await interaction . followUp ( {
content : ` ${ counter . emoji } **You have ${ amount > 0 ? ( linked ? 'put into' : 'increased' ) : ( linked ? 'taken from' : 'decreased' ) } the counter.** \ n \` \` \` diff \ n ${ newCount - amount } \ n ${ getSign ( amount ) } ${ Math . abs ( amount ) } \ n ${ newCount } \` \` \` ${ newInv ? ` \ nYou now have ${ formatItems ( item , newInv . quantity ) } . ` : '' } `
} ) ;
} catch ( err ) {
2023-11-12 20:44:03 +01:00
await interaction . followUp ( {
2023-11-15 11:03:01 +01:00
content : ( err as Error ) . toString ( )
2023-11-12 20:44:03 +01:00
} ) ;
}
2023-11-15 11:03:01 +01:00
} ;
2023-11-11 00:01:38 +01:00
}
2023-11-15 11:03:01 +01:00
export const changeCounterInteraction = changeCounterInteractionBuilder ( false ) ;
export const changeLinkedCounterInteraction = changeCounterInteractionBuilder ( true ) ;
2023-11-11 00:01:38 +01:00
2023-11-15 11:03:01 +01:00
function counterAutocompleteBuilder ( linked : boolean ) {
return async ( interaction : AutocompleteInteraction ) = > {
const focusedValue = interaction . options . getFocused ( ) ;
const guild = interaction . guildId ;
2023-11-11 00:01:38 +01:00
2023-11-15 11:03:01 +01:00
const query = db < Counter > ( 'counters' )
. select ( 'emoji' , 'key' )
. whereLike ( 'key' , ` % ${ focusedValue . toLowerCase ( ) } % ` )
. limit ( 25 ) ;
2023-11-11 00:01:38 +01:00
2023-11-15 11:03:01 +01:00
if ( guild ) {
query . where ( 'guild' , guild ) ;
}
if ( linked ) {
query . whereNotNull ( 'linkedItem' ) ;
}
const foundCounters = await query ;
await interaction . respond (
2023-11-15 11:08:01 +01:00
foundCounters . map ( choice = > ( { name : ` ${ choice . emoji } ${ choice . key } ` , value : choice.key } ) )
2023-11-15 11:03:01 +01:00
) ;
} ;
}
2023-11-11 00:01:38 +01:00
2023-11-15 11:03:01 +01:00
export const counterAutocomplete = counterAutocompleteBuilder ( false ) ;
export const linkedCounterAutocomplete = counterAutocompleteBuilder ( true ) ;