2024-06-19 19:50:14 +00:00
const { App } = require ( '@slack/bolt' ) ;
2024-06-19 20:20:41 +00:00
const postgres = require ( 'postgres' ) ;
2024-06-19 19:50:14 +00:00
require ( 'dotenv' ) . config ( )
2024-06-19 22:07:29 +00:00
const sql = postgres ( {
2024-06-20 01:03:30 +00:00
host : '/var/run/postgresql' ,
2024-06-19 20:20:41 +00:00
database : 'haroon_slackmaster' ,
2024-06-20 01:03:30 +00:00
username : 'haroon'
2024-06-19 20:20:41 +00:00
} )
2024-06-19 19:50:14 +00:00
2024-06-20 01:03:30 +00:00
const BeginnerOpponents = require ( './opponents/beginner' ) ;
const AllOpponents = [
... BeginnerOpponents
]
2024-06-19 19:50:14 +00:00
const app = new App ( {
token : process . env . SLACK _BOT _TOKEN ,
signingSecret : process . env . SLACK _SIGNING _SECRET
} ) ;
2024-06-19 22:07:29 +00:00
async function initializeUser ( slackUserId ) {
let a = await sql ` SELECT * FROM users WHERE slack_id = ${ slackUserId } ; `
2024-06-19 20:20:41 +00:00
if ( a . length === 0 ) {
2024-06-19 22:07:29 +00:00
a = await sql ` INSERT INTO users (slack_id) VALUES ( ${ slackUserId } ) RETURNING *; `
2024-06-19 20:20:41 +00:00
}
2024-06-19 22:07:29 +00:00
return a [ 0 ] ;
}
app . use ( async ( ctx ) => {
2024-06-20 01:03:30 +00:00
await initializeUser ( ctx . context . userId )
2024-06-19 22:07:29 +00:00
2024-06-19 20:20:41 +00:00
await ctx . next ( )
} )
2024-06-19 19:50:14 +00:00
app . command ( '/chooseopponent' , async ( ctx ) => {
await ctx . ack ( ) ;
2024-06-20 01:03:30 +00:00
const user = await initializeUser ( ctx . body . user _id ) ;
if ( user . currentopponent != "None" ) {
const opponent = AllOpponents . find ( x => x . rawId == user . currentopponent )
return await ctx . respond ( {
response _type : 'ephemeral' ,
text : ` You are already in a battle with ${ opponent . name } . ` ,
blocks : [
{
type : 'section' ,
text : {
type : 'mrkdwn' ,
text : ` *Battle Master:* You are already in a battle with * ${ opponent . name } *. Please finish your battle with them before proceeding.
$ { user . battlemessage } `
}
}
]
} )
}
2024-06-19 19:50:14 +00:00
await ctx . client . views . open ( {
trigger _id : ctx . body . trigger _id ,
view : {
2024-06-20 01:03:30 +00:00
"private_metadata" : ctx . payload . channel _id ,
2024-06-19 19:50:14 +00:00
"type" : "modal" ,
"callback_id" : "chooseopponent" ,
"title" : {
"type" : "plain_text" ,
"text" : "Choose an opponent" ,
"emoji" : true
} ,
"submit" : {
"type" : "plain_text" ,
"text" : "Choose" ,
"emoji" : true
} ,
"close" : {
"type" : "plain_text" ,
"text" : "Never mind" ,
"emoji" : true
} ,
"blocks" : [
{
"type" : "section" ,
"text" : {
"type" : "mrkdwn" ,
2024-06-20 17:31:27 +00:00
"text" : ` *Battle Support*: Hiya <@ ${ ctx . body . user _id } >! What rank opponent would you like to battle against? \n \n Not sure yet? Don't worry! Just cancel out and view what opponents you can fight with \` /viewopponents \` ! `
2024-06-19 19:50:14 +00:00
}
} ,
{
"type" : "divider"
} ,
{
"type" : "input" ,
"element" : {
"type" : "radio_buttons" ,
"options" : [
{
"text" : {
"type" : "plain_text" ,
"text" : "Special" ,
"emoji" : true
} ,
"value" : "SPECIAL"
} ,
{
"text" : {
"type" : "plain_text" ,
"text" : "Beginner" ,
"emoji" : true
} ,
"value" : "BEGINNER"
} ,
{
"text" : {
"type" : "plain_text" ,
"text" : "Casual" ,
"emoji" : true
} ,
"value" : "CASUAL"
}
] ,
"action_id" : "rank-selection"
} ,
"label" : {
"type" : "plain_text" ,
"text" : "Choose a rank:" ,
"emoji" : true
}
}
]
}
} )
} ) ;
2024-06-19 22:07:29 +00:00
function generateProfile ( dbUser , slackUser ) {
return [
{
"type" : "section" ,
"fields" : [
{
"type" : "mrkdwn" ,
"text" : "*User:* " + slackUser . display _name _normalized
} ,
{
"type" : "mrkdwn" ,
"text" : "*Rank:* " + dbUser . rank
} ,
{
"type" : "mrkdwn" ,
"text" : "*Battle Power*: " + ( dbUser . health + dbUser . mindmg + dbUser . maxdmg )
} ,
{
"type" : "mrkdwn" ,
"text" : ` *Victories/Losses:* ${ dbUser . victories } / ${ dbUser . losses } ( ${ dbUser . victories / ( dbUser . victories + dbUser . losses ) || 0 } %) `
} ,
{
"type" : "mrkdwn" ,
"text" : "*Current Win Streak:* " + dbUser . curstreak
} ,
{
"type" : "mrkdwn" ,
"text" : "*Highest Win Streak*: " + dbUser . highstreak
}
] ,
"accessory" : {
"type" : "image" ,
"image_url" : slackUser . image _1024 ,
"alt_text" : "user profile"
}
} ,
{
"type" : "section" ,
"text" : {
"type" : "mrkdwn" ,
"text" : ` *Base Health:* ${ dbUser . health } \n *Base Min Damage:* ${ dbUser . mindmg } \n *Base Max Damage:* ${ dbUser . maxdmg } `
}
}
]
}
app . command ( '/profile' , async ( ctx ) => {
await ctx . ack ( ) ;
const args = ctx . body . text . slice ( ) . split ( / +/g ) . filter ( x => x ) ;
let match ;
// If there is an argument and the first one is a Slack ping
if ( args . length && ( match = args [ 0 ] . match ( /\<\@(.+)\|(.+)>/ ) ) ) {
const mentionedUser = match [ 1 ] ;
const dbUser = await initializeUser ( mentionedUser ) ;
const slackUser = ( await ctx . client . users . info ( { user : mentionedUser } ) ) . user . profile ;
ctx . say ( {
"text" : ` @ ${ slackUser . display _name _normalized } 's profile ` ,
"blocks" : [
{
"type" : "section" ,
"text" : {
"type" : "mrkdwn" ,
"text" : ` <@ ${ ctx . body . user _id } > ran \` /profile @ ${ slackUser . display _name _normalized } \` `
}
} ,
... generateProfile ( dbUser , slackUser )
]
} )
}
// If there is an argument but it isn't a Slack ping
else if ( args . length ) {
ctx . respond ( {
blocks : [
{
type : 'section' ,
text : {
type : 'mrkdwn' ,
text : ` *Battle Master:* Greetings <@ ${ ctx . body . user _id } >. You have tried to view the profile of an invalid user. Please ensure you either send a user ping as an argument or provide no argument at all. `
}
}
]
} )
}
// There is no argument
else {
const dbUser = await initializeUser ( ctx . body . user _id ) ;
const slackUser = ( await ctx . client . users . info ( { user : ctx . body . user _id } ) ) . user . profile ;
ctx . say ( {
"text" : ` @ ${ slackUser . display _name _normalized } 's profile ` ,
"blocks" : [
{
"type" : "section" ,
"text" : {
"type" : "mrkdwn" ,
"text" : ` <@ ${ ctx . body . user _id } > ran \` /profile \` `
}
} ,
... generateProfile ( dbUser , slackUser )
]
} )
}
} )
2024-06-19 19:50:14 +00:00
app . view ( "chooseopponent" , async ( ctx ) => {
const { selected _option } = Object . values ( ctx . view . state . values ) [ 0 ] [ 'rank-selection' ] ;
const rank = selected _option . value ;
2024-06-19 20:27:12 +00:00
const messages = {
"SPECIAL" : ` *Battle Special*: Wasn't expecting to see ya here <@ ${ ctx . body . user . id } >... I guess you're up for a challenge huh? Alright then, who do ya wanna annoy today? ` ,
"BEGINNER" : ` *Battle Beginner*: Hey there <@ ${ ctx . body . user . id } >! Let's keep things simple, who do you want to battle against? ` ,
"CASUAL" : ` *Battle Casual*: Alright <@ ${ ctx . body . user . id } >, things are about to get a little bit tougher from here... Who do you feel like taking on today? `
}
2024-06-19 19:50:14 +00:00
await ctx . ack ( {
response _action : 'update' ,
view : {
2024-06-20 01:03:30 +00:00
"private_metadata" : ctx . payload . private _metadata ,
2024-06-19 19:50:14 +00:00
"type" : "modal" ,
"callback_id" : "chooseopponent-" + rank ,
"title" : {
"type" : "plain_text" ,
"text" : "Choose an opponent" ,
"emoji" : true
} ,
"submit" : {
"type" : "plain_text" ,
"text" : "Battle" ,
"emoji" : true
} ,
"close" : {
"type" : "plain_text" ,
"text" : "Never mind" ,
"emoji" : true
} ,
"blocks" : [
{
"type" : "section" ,
"text" : {
"type" : "mrkdwn" ,
2024-06-19 20:27:12 +00:00
"text" : messages [ rank ] || ` *Battle Master*: Greetings <@ ${ ctx . body . user . id } >. Please choose an opponent from below to battle. `
2024-06-19 19:50:14 +00:00
}
} ,
{
"type" : "divider"
} ,
{
"type" : "input" ,
"element" : {
"type" : "static_select" ,
"options" : BeginnerOpponents . map ( opponent =>
2024-06-19 22:07:29 +00:00
( {
"text" : {
"type" : "plain_text" ,
"text" : ` ${ opponent . name } // ${ opponent . stats . health + opponent . stats . min + opponent . stats . max } Battle Power ` ,
"emoji" : true
} ,
"value" : opponent . rawId
} )
2024-06-19 19:50:14 +00:00
) ,
"action_id" : "opponents"
} ,
"label" : {
"type" : "plain_text" ,
"text" : "Choose an opponent:" ,
"emoji" : true
}
}
]
}
} )
} )
2024-06-19 20:20:41 +00:00
app . view ( "chooseopponent-BEGINNER" , async ( ctx ) => {
await ctx . ack ( ) ;
2024-06-20 01:03:30 +00:00
const channelId = ctx . view . private _metadata ;
const userId = ctx . context . userId ;
const slackUser = ( await ctx . client . users . info ( { user : userId } ) ) . user . profile ;
const opponent = BeginnerOpponents . find ( o => o . rawId == Object . values ( ctx . payload . state . values ) [ 0 ] . opponents . selected _option . value ) ;
const player = await initializeUser ( userId ) ;
await sql ` UPDATE users
SET playerhealth = $ { player . health } ,
playermin = $ { player . mindmg } ,
playermax = $ { player . maxdmg } ,
currentOpponent = $ { opponent . rawId } ,
opponenthealth = $ { opponent . stats . health } ,
opponentmin = $ { opponent . stats . min } ,
opponentmax = $ { opponent . stats . max }
WHERE slack _id = $ { userId } ; `
const msg = await ctx . client . chat . postMessage ( {
channel : channelId ,
text : ` ${ slackUser . display _name _normalized } started a battle against ${ opponent . name } . ` ,
blocks : [
{
type : 'section' ,
text : {
type : 'mrkdwn' ,
text : opponent . intro . replaceAll ( "{player}" , slackUser . display _name _normalized )
2024-06-20 17:31:27 +00:00
} ,
2024-06-20 01:03:30 +00:00
"accessory" : {
"type" : "image" ,
"image_url" : opponent . image ,
"alt_text" : opponent . name
}
} ,
{
"type" : "actions" ,
"elements" : [
{
"type" : "button" ,
2024-06-20 17:31:27 +00:00
2024-06-20 01:03:30 +00:00
"text" : {
"type" : "plain_text" ,
"text" : "Continue" ,
"emoji" : true
} ,
2024-06-20 17:31:27 +00:00
"value" : userId ,
2024-06-20 01:03:30 +00:00
"action_id" : "continue"
}
]
}
]
} ) ;
2024-06-19 20:20:41 +00:00
2024-06-20 01:03:30 +00:00
await sql ` UPDATE users SET battlemessage = ${ ` https://hackclub.slack.com/archives/ ${ channelId } /p ${ msg . ts . replace ( '.' , '' ) } ` } WHERE slack_id = ${ userId } ; `
2024-06-19 20:20:41 +00:00
} )
2024-06-20 17:31:27 +00:00
async function checkButton ( ctx ) {
await ctx . ack ( ) ;
if ( ctx . payload . value != ctx . context . userId ) {
return ctx . respond ( {
replace _original : false ,
response _type : 'ephemeral' ,
text : "Battle Support: Please do not click other battlers buttons!" ,
blocks : [
{
type : 'section' ,
text : {
type : 'mrkdwn' ,
text : ` *Battle Support:* Erm, <@ ${ ctx . context . userId } >?... Please don't press other battlers buttons!
If you want to choose an opponent yourself , simply use \ ` /chooseopponent \` to get started! `
}
}
]
} )
}
await ctx . next ( ) ;
}
app . action ( "continue" , checkButton , async ( ctx ) => {
const user = await initializeUser ( ctx . context . userId ) ;
const slackUser = ( await ctx . client . users . info ( { user : ctx . context . userId } ) ) . user . profile ;
ctx . respond ( {
replace _original : true ,
text : "" ,
"blocks" : [
{
"type" : "section" ,
"fields" : [
{
"type" : "mrkdwn" ,
"text" : ` ${ slackUser . display _name _normalized } : \n \n *Health:* 20 \n *Min Damage:* 1 \n *Max Damage:* 9 `
} ,
{
"type" : "mrkdwn" ,
"text" : ` ${ AllOpponents . find ( x => x . rawId == user . currentopponent ) . name } : \n \n *Health:* 25 \n *Min Damage:* 2 \n *Max Damage:* 5 `
}
] ,
"accessory" : {
"type" : "image" ,
"image_url" : "https://media.discordapp.net/attachments/1108683335347212389/1118119440702242816/BM_-_Rap_Star_1.png?ex=6674a820&is=667356a0&hm=520cf408d3cadad838b0d2350fb212470949257cbd2768bfea64523feb33b494&" ,
"alt_text" : AllOpponents . find ( x => x . rawId == user . currentopponent ) . name
}
} ,
{
"type" : "context" ,
"elements" : [
{
"type" : "mrkdwn" ,
"text" : ` * ${ slackUser . display _name _normalized } * vs * ${ AllOpponents . find ( x => x . rawId == user . currentopponent ) . name } * `
}
]
} ,
{
"type" : "actions" ,
"elements" : [
{
"type" : "button" ,
"text" : {
"type" : "plain_text" ,
"text" : "⚔️ Attack" ,
"emoji" : true
} ,
"value" : ctx . context . userId ,
"action_id" : "attack"
} ,
{
"type" : "button" ,
"text" : {
"type" : "plain_text" ,
"text" : "🛡️ Defend" ,
"emoji" : true
} ,
"value" : ctx . context . userId ,
"action_id" : "defend"
} ,
{
"type" : "button" ,
"text" : {
"type" : "plain_text" ,
"text" : " 🎁 Item" ,
"emoji" : true
} ,
"value" : ctx . context . userId ,
"action_id" : "item"
} ,
{
"type" : "button" ,
"text" : {
"type" : "plain_text" ,
"text" : "☠️ Forfeit" ,
"emoji" : true
} ,
"style" : "danger" ,
"value" : ctx . context . userId ,
"action_id" : "actionId-4"
}
]
}
]
} )
} )
2024-06-19 20:49:07 +00:00
app . command ( '/bm-eval' , async ( ctx ) => {
await ctx . ack ( ) ;
const resp = require ( 'util' ) . inspect ( await eval ( ctx . body . text ) , undefined , 1 )
ctx . respond ( {
text : resp ,
response _type : 'ephemeral' ,
blocks : [
{
type : 'section' ,
text : {
type : 'mrkdwn' ,
text : "```" + resp + "```"
}
}
]
} )
} )
2024-06-19 19:50:14 +00:00
app . command ( '/viewopponents' , async ( ctx ) => {
await ctx . ack ( ) ;
2024-06-19 22:07:29 +00:00
const args = ctx . body . text . slice ( ) . split ( / +/g ) ;
2024-06-19 19:50:14 +00:00
switch ( args [ 0 ] . toUpperCase ( ) ) {
case "SPECIAL" :
ctx . respond ( {
text : "You're trying to view Special opponents." ,
response _type : 'ephemeral'
} )
break ;
case "BEGINNER" :
const mappedBeginner = BeginnerOpponents . map ( opponent =>
( {
name : opponent . name ,
battlePower :
opponent . stats . health +
opponent . stats . min +
opponent . stats . max
} )
)
ctx . respond ( {
response _type : 'ephemeral' ,
text : ` *Battle Master:* Greetings battler. Here are the avaliable *Beginner* opponents for you to battle. ` ,
blocks : [
{
type : 'section' ,
text : {
type : 'mrkdwn' ,
text : ` *Battle Master:* Greetings <@ ${ ctx . body . user _id } >. Here are the avaliable *Beginner* opponents for you to battle. `
}
} ,
{
type : 'section' ,
text : {
type : 'mrkdwn' ,
text : mappedBeginner . map ( opponent => ` * ${ opponent . name } :* \n \n ${ opponent . battlePower } Battle Power ` ) . join ( '\n\n\n' )
}
}
]
} )
break ;
case "CASUAL" :
ctx . say ( "You're trying to view Casual opponents." )
break ;
default :
ctx . say ( "You either wrote nothing or just chose a rank that doesn't exist." )
}
} ) ;
; ( async ( ) => {
// Start your app
await app . start ( process . env . PORT ) ;
console . log ( '⚡️ Bolt app is running!' ) ;
} ) ( ) ;