2023-06-17 13:18:52 +02:00
import { addDays , differenceInDays , format , isAfter , toDate } from 'date-fns'
2023-06-14 22:24:39 +02:00
import { Guild , GuildScheduledEvent , GuildScheduledEventEditOptions , GuildScheduledEventSetStatusArg , GuildScheduledEventStatus , Message , MessageCreateOptions , TextChannel } from 'discord.js'
2023-06-10 17:27:32 +02:00
import { v4 as uuid } from 'uuid'
2023-06-14 22:24:39 +02:00
import { client } from '../..'
2023-06-10 17:27:32 +02:00
import { config } from '../configuration'
2023-06-17 12:00:14 +02:00
import { Maybe } from '../interfaces'
2023-06-10 22:53:11 +02:00
import { logger } from '../logger'
import { Command } from '../structures/command'
import { RunOptions } from '../types/commandTypes'
2023-06-25 02:20:45 +02:00
import { messageIsVoteEndedMessage , messageIsVoteMessage } from '../helper/messageIdentifiers'
2023-06-25 22:46:46 +02:00
import { Emotes } from '../constants'
2023-06-10 17:27:32 +02:00
export default new Command ( {
2023-06-24 21:09:56 +02:00
name : 'closepoll' ,
description : 'Aktuelle Umfrage für nächste Watchparty beenden und Gewinner in Event eintragen.' ,
options : [ ] ,
run : async ( interaction : RunOptions ) = > {
const command = interaction . interaction
const requestId = uuid ( )
if ( ! command . guild ) {
logger . error ( "No guild found in interaction. Cancelling closing request" , { requestId } )
command . followUp ( "Es gab leider ein Problem. Ich konnte deine Anfrage nicht bearbeiten :(" )
return
}
const guildId = command . guildId
logger . info ( "Got command for closing poll!" , { guildId , requestId } )
command . followUp ( "Alles klar, beende die Umfrage :)" )
closePoll ( command . guild , requestId )
}
2023-06-10 22:53:11 +02:00
} )
2023-06-10 17:27:32 +02:00
2023-06-10 22:53:11 +02:00
export async function closePoll ( guild : Guild , requestId : string ) {
2023-06-24 21:09:56 +02:00
const guildId = guild . id
logger . info ( "stopping poll" , { guildId , requestId } )
2023-06-11 09:01:25 +02:00
2023-06-24 21:09:56 +02:00
const announcementChannel : Maybe < TextChannel > = client . getAnnouncementChannelForGuild ( guildId )
if ( ! announcementChannel ) {
logger . error ( "Could not find the textchannel. Unable to close poll." , { guildId , requestId } )
return
}
2023-06-10 17:27:32 +02:00
2023-06-24 21:09:56 +02:00
const messages : Message < true > [ ] = ( await announcementChannel . messages . fetch ( ) ) //todo: fetch only pinned messages
. map ( ( value ) = > value )
2023-06-25 02:20:45 +02:00
. filter ( message = > ! messageIsVoteEndedMessage ( message ) && messageIsVoteMessage ( message ) )
2023-06-24 21:09:56 +02:00
. sort ( ( a , b ) = > b . createdTimestamp - a . createdTimestamp )
2023-06-10 17:27:32 +02:00
2023-06-24 21:09:56 +02:00
if ( ! messages || messages . length <= 0 ) {
logger . info ( "Could not find any vote messages. Cancelling pollClose" , { guildId , requestId } )
return
}
2023-06-10 17:27:32 +02:00
2023-06-24 21:09:56 +02:00
const lastMessage : Message < true > = messages [ 0 ]
2023-06-10 17:27:32 +02:00
2023-06-24 21:09:56 +02:00
logger . debug ( ` Found messages: ${ JSON . stringify ( messages , null , 2 ) } ` , { guildId , requestId } )
2023-06-10 17:27:32 +02:00
2023-06-24 21:09:56 +02:00
logger . debug ( ` Last message: ${ JSON . stringify ( lastMessage , null , 2 ) } ` , { guildId , requestId } )
2023-06-10 17:27:32 +02:00
2023-06-17 13:18:52 +02:00
2023-06-24 21:09:56 +02:00
const votes = await ( await getVotesByEmote ( lastMessage , guildId , requestId ) )
. sort ( ( a , b ) = > b . count - a . count )
2023-06-17 13:18:52 +02:00
2023-06-24 21:09:56 +02:00
logger . debug ( ` votes: ${ JSON . stringify ( votes , null , 2 ) } ` , { guildId , requestId } )
2023-06-17 13:18:52 +02:00
2023-06-24 21:09:56 +02:00
logger . info ( "Deleting vote message" )
await lastMessage . delete ( )
const event = await getEvent ( guild , guild . id , requestId )
if ( event ) {
updateEvent ( event , votes , guild , guildId , requestId )
sendVoteClosedMessage ( event , votes [ 0 ] . movie , guildId , requestId )
}
2023-06-10 17:27:32 +02:00
2023-06-24 21:09:56 +02:00
//lastMessage.unpin() //todo: uncomment when bot has permission to pin/unpin
2023-06-10 22:53:11 +02:00
}
2023-06-10 17:27:32 +02:00
2023-06-14 22:24:39 +02:00
async function sendVoteClosedMessage ( event : GuildScheduledEvent , movie : string , guildId : string , requestId : string ) {
2023-06-24 21:09:56 +02:00
const date = event . scheduledStartAt ? format ( event . scheduledStartAt , "dd.MM" ) : "Fehler, event hatte kein Datum"
const time = event . scheduledStartAt ? format ( event . scheduledStartAt , "HH:mm" ) : "Fehler, event hatte kein Datum"
const body = ` [Abstimmung beendet] für https://discord.com/events/ ${ event . guildId } / ${ event . id } \ n<@& ${ config . bot . announcement_role } > Wir gucken ${ movie } am ${ date } um ${ time } `
const options : MessageCreateOptions = {
content : body ,
allowedMentions : { parse : [ "roles" ] }
}
const announcementChannel = client . getAnnouncementChannelForGuild ( guildId )
logger . info ( "Sending vote closed message." , { guildId , requestId } )
if ( ! announcementChannel ) {
logger . error ( "Could not find announcement channel. Please fix!" , { guildId , requestId } )
return
}
announcementChannel . send ( options )
2023-06-10 17:27:32 +02:00
}
2023-06-14 22:24:39 +02:00
async function updateEvent ( voteEvent : GuildScheduledEvent , votes : Vote [ ] , guild : Guild , guildId : string , requestId : string ) {
2023-06-24 21:09:56 +02:00
logger . info ( ` Updating event with movie ${ votes [ 0 ] . movie } . ` , { guildId , requestId } )
const options : GuildScheduledEventEditOptions < GuildScheduledEventStatus.Scheduled , GuildScheduledEventSetStatusArg < GuildScheduledEventStatus.Scheduled > > = {
name : votes [ 0 ] . movie ,
description : ` !wp \ nNummer 2: ${ votes [ 1 ] . movie } mit ${ votes [ 1 ] . count - 1 } Stimmen \ nNummer 3: ${ votes [ 2 ] . movie } mit ${ votes [ 2 ] . count - 1 } Stimmen `
}
logger . debug ( ` Updating event: ${ JSON . stringify ( voteEvent , null , 2 ) } ` , { guildId , requestId } )
logger . info ( "Updating event." , { guildId , requestId } )
voteEvent . edit ( options )
2023-06-14 22:24:39 +02:00
}
async function getEvent ( guild : Guild , guildId : string , requestId : string ) : Promise < GuildScheduledEvent | null > {
2023-06-24 21:09:56 +02:00
const voteEvents = ( await guild . scheduledEvents . fetch ( ) )
. map ( ( value ) = > value )
. filter ( event = > event . name . toLowerCase ( ) . includes ( "voting offen" ) )
logger . debug ( ` Found events: ${ JSON . stringify ( voteEvents , null , 2 ) } ` , { guildId , requestId } )
if ( ! voteEvents || voteEvents . length <= 0 ) {
logger . error ( "Could not find vote event. Cancelling update!" , { guildId , requestId } )
return null
}
return voteEvents [ 0 ]
2023-06-10 17:27:32 +02:00
}
type Vote = {
2023-06-24 21:09:56 +02:00
emote : string , //todo habs nicht hinbekommen hier Emotes zu nutzen
count : number ,
movie : string
2023-06-10 17:27:32 +02:00
}
async function getVotesByEmote ( message : Message , guildId : string , requestId : string ) : Promise < Vote [ ] > {
2023-06-24 21:09:56 +02:00
const votes : Vote [ ] = [ ]
logger . debug ( ` Number of items in emotes: ${ Object . values ( Emotes ) . length } ` , { guildId , requestId } )
for ( let i = 0 ; i < Object . keys ( Emotes ) . length / 2 ; i ++ ) {
const emote = Emotes [ i ]
logger . debug ( ` Getting reaction for emote ${ emote } ` , { guildId , requestId } )
const reaction = await message . reactions . resolve ( emote )
logger . debug ( ` Reaction for emote ${ emote } : ${ JSON . stringify ( reaction , null , 2 ) } ` , { guildId , requestId } )
if ( reaction ) {
const vote : Vote = { emote : emote , count : reaction.count , movie : extractMovieFromMessageByEmote ( message , emote ) }
votes . push ( vote )
}
}
return votes
2023-06-10 17:27:32 +02:00
}
2023-06-15 21:56:15 +02:00
function extractMovieFromMessageByEmote ( message : Message , emote : string ) : string {
2023-06-24 21:09:56 +02:00
const lines = message . cleanContent . split ( "\n" )
const emoteLines = lines . filter ( line = > line . includes ( emote ) )
2023-06-10 17:27:32 +02:00
2023-06-24 21:09:56 +02:00
if ( ! emoteLines ) {
return ""
}
const movie = emoteLines [ 0 ] . substring ( emoteLines [ 0 ] . indexOf ( emote ) + emote . length + 2 ) // plus colon and space
2023-06-10 17:27:32 +02:00
2023-06-24 21:09:56 +02:00
return movie
2023-06-17 12:00:14 +02:00
}
export async function checkForPollsToClose ( guild : Guild ) : Promise < void > {
2023-06-24 21:09:56 +02:00
const requestId = uuid ( )
logger . info ( ` Automatic check for poll closing. ` , { guildId : guild.id , requestId } )
const events = ( await guild . scheduledEvents . fetch ( ) ) . filter ( event = > event . name . toLocaleLowerCase ( ) . includes ( "voting offen" ) ) . map ( event = > event )
if ( events . length > 1 ) {
logger . error ( "Handling more than one Event is not implemented yet. Found more than one poll to close" )
return
} else if ( events . length == 0 ) {
logger . info ( "Could not find any events. Cancelling" , { guildId : guild.id , requestId } )
}
const updatedEvent = events [ 0 ] //add two hours because of different timezones in discord api and Date.now()
if ( ! updatedEvent . scheduledStartTimestamp ) {
logger . error ( "Event does not have a scheduled start time. Cancelling" , { guildId : guild.id , requestId } )
return
}
const createDate : Date = toDate ( updatedEvent . createdTimestamp )
const eventDate : Date = toDate ( updatedEvent . scheduledStartTimestamp )
const difference : number = differenceInDays ( createDate , eventDate )
if ( difference <= 2 ) {
logger . info ( "Less than two days between event create and event start. Not closing poll." , { guildId : guild.id , requestId } )
return
}
const closePollDate : Date = addDays ( eventDate , - 2 )
if ( isAfter ( Date . now ( ) , closePollDate ) ) {
logger . info ( "Less than two days until event. Closing poll" , { guildId : guild.id , requestId } )
closePoll ( guild , requestId )
} else {
logger . info ( ` ScheduledStart: ${ closePollDate } . Now: ${ toDate ( Date . now ( ) ) } ` , { guildId : guild.id , requestId } )
}
}