2024-07-23 09:13:37 +00:00
import { name , version , repository } from "./package.json" ;
const USER_AGENT = ` ${ name } / ${ version } (+ ${ repository . url } ) ` ;
2024-07-23 10:14:51 +00:00
const { App , ExpressReceiver } = ( await import ( "@slack/bolt" ) ) ;
import postgres from 'postgres' ;
2024-07-23 09:13:37 +00:00
import "dotenv/config" ;
2024-07-23 10:14:51 +00:00
import { spawnSync } from "child_process" ;
2024-07-23 09:13:37 +00:00
const sql = postgres ( {
host : '/var/run/postgresql' ,
database : 'haroon_nxslack' ,
username : 'haroon'
} )
const app = new App ( {
token : process.env.SLACK_BOT_TOKEN ,
signingSecret : process.env.SLACK_SIGNING_SECRET ,
2024-07-23 10:14:51 +00:00
clientSecret : process.env.SLACK_CLIENT_SECRET
2024-07-23 09:13:37 +00:00
} ) ;
app . event ( "app_home_opened" , async ( ctx ) = > {
2024-07-23 13:47:10 +00:00
const slackUser = ( await ctx . client . users . info ( { user : ctx.context.userId ! } ) ) . user ! ;
2024-07-23 09:13:37 +00:00
2024-07-23 10:14:51 +00:00
const [ link = null ] = await sql ` SELECT * FROM links WHERE slackid = ${ ctx . context . userId ! } ; ` ;
if ( link ) {
const frens = getFriends ( ) ;
const me = frens . find ( x = > x . nsaId == link . nsaid ) ! ;
console . log ( me , frens )
await ctx . client . views . publish ( {
user_id : ctx.event.user ,
view : {
type : 'home' ,
blocks : [
{
type : 'header' ,
text : {
type : 'plain_text' ,
text : "You're linked!" ,
emoji : true
}
} ,
{
"type" : "section" ,
"text" : {
"type" : "mrkdwn" ,
2024-07-23 13:47:10 +00:00
"text" : ` Hey * ${ slackUser . profile ? . display_name_normalized } * - you're already linked to a Nintendo Switch account ( ${ me . name } ). To unlink yourself, DM <@U06TBP41C3E>. `
2024-07-23 10:14:51 +00:00
} ,
"accessory" : {
"type" : "image" ,
"image_url" : me . imageUri ,
"alt_text" : "switch profile picture"
}
}
]
}
} ) ;
return
}
2024-07-23 09:13:37 +00:00
await ctx . client . views . publish ( {
// Use the user ID associated with the event
user_id : ctx.event.user ,
view : {
"type" : "home" ,
"blocks" : [
{
"type" : "header" ,
"text" : {
"type" : "plain_text" ,
2024-07-23 10:14:51 +00:00
"text" : "You aren't linked" ,
2024-07-23 09:13:37 +00:00
"emoji" : true
}
} ,
{
"type" : "section" ,
"text" : {
"type" : "mrkdwn" ,
2024-07-23 13:47:10 +00:00
"text" : ` Hey * ${ slackUser . profile ? . display_name_normalized } *! To sync your Nintendo Switch and Slack status, give me your friend code! `
2024-07-23 09:13:37 +00:00
} ,
"accessory" : {
"type" : "button" ,
"text" : {
"type" : "plain_text" ,
"text" : "Sync" ,
"emoji" : true
} ,
"value" : "click_me_123" ,
"action_id" : "get-friend-code"
}
2024-07-23 13:47:10 +00:00
} ,
. . . ( slackUser . is_admin || slackUser . is_owner ? [
{
type : 'section' ,
// @ts-expect-error this feels dumb, but it said text wasn't a value on Block... what
text : {
type : 'mrkdwn' ,
text : ` By the way, * ${ slackUser . profile ? . display_name_normalized } *... I can't actually EDIT your status because of how Slack works. I know that sounds annoying, but hopefully that should be fixed whenever I get an admin API token! `
}
}
] : [ ] )
2024-07-23 09:13:37 +00:00
]
}
} ) ;
} )
app . action ( "get-friend-code" , async ( ctx ) = > {
ctx . ack ( ) ;
ctx . client . views . open ( {
trigger_id : ctx.body.trigger_id ! ,
view : {
"type" : "modal" ,
callback_id : "friend-code-validation" ,
"title" : {
"type" : "plain_text" ,
"text" : "Add me as a friend!" ,
"emoji" : true
} ,
"submit" : {
"type" : "plain_text" ,
"text" : "Submit" ,
"emoji" : true
} ,
"close" : {
"type" : "plain_text" ,
"text" : "Cancel" ,
"emoji" : true
} ,
"blocks" : [
{
"block_id" : "FRIEND_CODE" ,
"type" : "input" ,
"element" : {
"type" : "plain_text_input" ,
"action_id" : "code" ,
"placeholder" : {
"type" : "plain_text" ,
"text" : "SW-xxxx-xxxx-xxxx"
}
} ,
"label" : {
"type" : "plain_text" ,
"text" : "Enter your friend code here..." ,
"emoji" : true
}
}
]
}
} )
} )
const FRIEND_CODE_REGEX = /(?:SW-)?(\d{4}-\d{4}-\d{4})/g ;
app . view ( "friend-code-validation" , async ( ctx ) = > {
// ts is lovely
const friendCode = Object . values ( ctx . view . state . values ) [ 0 ] [ 'code' ] . value ! ;
2024-07-23 10:14:51 +00:00
if ( ! FRIEND_CODE_REGEX . test ( friendCode ) ) return await ctx . ack ( {
2024-07-23 09:13:37 +00:00
response_action : 'errors' ,
errors : {
"FRIEND_CODE" : "This isn't a valid friend code. (SW-xxxx-xxxx-xxxx or xxxx-xxxx-xxxx)"
}
} ) ;
2024-07-23 10:14:51 +00:00
const realCode = friendCode . replace ( "SW-" , "" ) ;
spawnSync ( "/home/haroon/nxslack/node_modules/.bin/nxapi" , [ "nso" , "add-friend" , realCode ] , { cwd : process.cwd ( ) , env : {
'NXAPI_USER_AGENT' : USER_AGENT
} } ) ;
await ctx . ack ( ) ;
ctx . client . chat . postMessage ( {
channel : ctx.body.user.id ,
text : "I've added you on Nintendo Switch! Find my friend request and accept it or add me yourself (SW-2973-7062-1423) and then click this button to finish the process!" ,
blocks : [
{
type : 'section' ,
text : {
type : 'mrkdwn' ,
text : "I've added you on Nintendo Switch! Find my friend request and accept it or add me back (`SW-2973-7062-1423`) and then click this button to finish the process!"
}
} ,
{
"type" : "actions" ,
"elements" : [
{
"type" : "button" ,
"text" : {
"type" : "plain_text" ,
"text" : "I've accepted!" ,
"emoji" : true
} ,
"value" : realCode ,
"action_id" : "check-synced"
}
]
}
]
} )
} )
type NSOUser = {
id : number ,
nsaId : string ,
imageUri : string ,
name : string ,
isFriend : boolean ,
isFavouriteFriend : boolean ,
isServiceUser : boolean ,
friendCreatedAt : number ,
presence : any
}
const getFriends = function getFriends ( ) : NSOUser [ ] {
const { stdout } = spawnSync ( "/home/haroon/nxslack/node_modules/.bin/nxapi" , [ "nso" , "friends" , "--json" ] , { cwd : process.cwd ( ) , env : {
'NXAPI_USER_AGENT' : USER_AGENT
} } ) ;
return JSON . parse ( stdout . toString ( 'utf-8' ) . slice ( ) ) ;
} ;
app . action ( 'check-synced' , async ( ctx ) = > {
if ( ctx . action . type != "button" ) return ;
await ctx . ack ( ) ;
const friends = getFriends ( ) ;
const { stdout } = spawnSync ( "/home/haroon/nxslack/node_modules/.bin/nxapi" , [ "nso" , "lookup" , ctx . action . value ! , "--json" ] , { cwd : process.cwd ( ) , env : {
'NXAPI_USER_AGENT' : USER_AGENT
} } ) ;
console . log ( stdout . toString ( ) )
const user : NSOUser = JSON . parse ( stdout . toString ( 'utf-8' ) . slice ( ) ) ;
if ( ! ! friends . find ( userA = > userA . nsaId == user . nsaId ) ) {
await sql ` INSERT INTO links VALUES ( ${ ctx . context . userId ! } , ${ user . nsaId } ) `
ctx . respond ( {
text : ` Congrats! You've successfully linked your Slack account to your Nintendo Switch user ( ${ user . name } )! `
} )
}
2024-07-23 09:13:37 +00:00
} )
; ( async ( ) = > {
await app . start ( 53371 ) ;
console . log ( '⚡️ Bolt app is running!' ) ;
2024-07-23 13:47:10 +00:00
setInterval ( async ( ) = > {
const frens = getFriends ( ) ;
const links = await sql ` SELECT * FROM links; ` ;
for ( let fren of frens ) {
const dbFren = links . find ( frend = > frend . nsaid == fren . nsaId ) ! ;
const slackUser = ( await app . client . users . info ( { user : dbFren.slackid } ) ) . user ! ;
if ( slackUser . is_admin || slackUser . is_owner ) return ;
switch ( fren . presence . state ) {
// Console offline
case "OFFLINE" :
// Console online; but not playing a game
case "INACTIVE" :
if ( dbFren . last_status != null ) {
await app . client . users . profile . set ( {
token : process.env.SLACK_USER_OAUTH_TOKEN ,
user : dbFren.slackid ,
// @ts-expect-error This is correct, Bolt has incorrect typing
profile : {
status_text : "" ,
status_emoji : ""
}
} )
await sql ` UPDATE links SET last_status = NULL WHERE nsaId = ${ fren . nsaId } ; ` ;
}
break ;
// Playing a game
case "ONLINE" :
if ( dbFren . last_status != ` Playing ${ fren . presence . game . name } ` ) {
await app . client . users . profile . set ( {
token : process.env.SLACK_USER_OAUTH_TOKEN ,
user : dbFren.slackid ,
// @ts-expect-error This is correct, Bolt has incorrect typing
profile : {
status_text : ` Playing ${ fren . presence . game . name } ` ,
status_emoji : ":nintendo:"
}
} )
await sql ` UPDATE links SET last_status = ${ ` Playing ${ fren . presence . game . name } ` } WHERE nsaId = ${ fren . nsaId } ; ` ;
}
break ;
}
}
} , 60 _000 )
2024-07-23 09:13:37 +00:00
} ) ( ) ;