Compare commits

..

2 Commits

Author SHA1 Message Date
7d794a8001 refactor voteInfo to include event instead of eventid and startDate
All checks were successful
Run unit tests / test (pull_request) Successful in 16s
Compile the repository / compile (pull_request) Successful in 18s
2023-11-19 20:04:30 +01:00
8df180898e pad logging level to always be 5 characters 2023-11-19 20:04:06 +01:00
5 changed files with 68 additions and 61 deletions

View File

@ -1,10 +1,10 @@
import { format, isToday } from "date-fns"; import { format, isToday } from "date-fns";
import { utcToZonedTime } from "date-fns-tz" import { utcToZonedTime } from "date-fns-tz"
import { GuildScheduledEvent } from "discord.js";
import { logger } from "../logger"; import { logger } from "../logger";
import de from "date-fns/locale/de"; import de from "date-fns/locale/de";
import { Maybe } from "../interfaces";
export function createDateStringFromEvent(eventStartDate:Date, requestId: string, guildId?: string): string { export function createDateStringFromEvent(eventStartDate: Maybe<Date>, requestId: string, guildId?: string): string {
if (!eventStartDate) { if (!eventStartDate) {
logger.error("Event has no start. Cannot create dateString.", { guildId, requestId }) logger.error("Event has no start. Cannot create dateString.", { guildId, requestId })
return `"habe keinen Startzeitpunkt ermitteln können"` return `"habe keinen Startzeitpunkt ermitteln können"`

View File

@ -21,8 +21,7 @@ export type Vote = {
} }
export type VoteMessageInfo = { export type VoteMessageInfo = {
votes: Vote[], votes: Vote[],
eventId: string, event: GuildScheduledEvent,
eventDate: Date
} }
export default class VoteController { export default class VoteController {
private client: ExtendedClient private client: ExtendedClient
@ -74,11 +73,8 @@ export default class VoteController {
logger.debug(`${vote.movie} : ${vote.count} -> above: ${overOneVote}`) logger.debug(`${vote.movie} : ${vote.count} -> above: ${overOneVote}`)
return overOneVote return overOneVote
} }
public async handleReroll(voteMessage: VoteMessage, guildId: string, requestId: string) {
// get the movies currently being voted on, their votes, the eventId and its date
const voteInfo: VoteMessageInfo = await this.parseVoteInfoFromVoteMessage(voteMessage, requestId)
let movies: string[] = Array() public async generateRerollMovieList(voteInfo: VoteMessageInfo, guildId: string, requestId: string) {
if (config.bot.reroll_retains_top_picks) { if (config.bot.reroll_retains_top_picks) {
const votedOnMovies = voteInfo.votes.filter(this.hasAtLeastOneVote).filter(x => x.emote !== NONE_OF_THAT) const votedOnMovies = voteInfo.votes.filter(this.hasAtLeastOneVote).filter(x => x.emote !== NONE_OF_THAT)
logger.info(`Found ${votedOnMovies.length} with votes`, { requestId, guildId }) logger.info(`Found ${votedOnMovies.length} with votes`, { requestId, guildId })
@ -86,40 +82,53 @@ export default class VoteController {
logger.info(`Fetching ${newMovieCount} from jellyfin`) logger.info(`Fetching ${newMovieCount} from jellyfin`)
const newMovies: string[] = await this.yavinJellyfinHandler.getRandomMovieNames(newMovieCount, guildId, requestId) const newMovies: string[] = await this.yavinJellyfinHandler.getRandomMovieNames(newMovieCount, guildId, requestId)
// merge // merge
movies = newMovies.concat(votedOnMovies.map(x => x.movie)) return newMovies.concat(votedOnMovies.map(x => x.movie))
} else { } else {
// get movies from jellyfin to fill the remaining slots // get movies from jellyfin to fill the remaining slots
const newMovieCount: number = config.bot.random_movie_count const newMovieCount: number = config.bot.random_movie_count
logger.info(`Fetching ${newMovieCount} from jellyfin`) logger.info(`Fetching ${newMovieCount} from jellyfin`)
movies = await this.yavinJellyfinHandler.getRandomMovieNames(newMovieCount, guildId, requestId) return await this.yavinJellyfinHandler.getRandomMovieNames(newMovieCount, guildId, requestId)
} }
// create new message }
public async handleReroll(voteMessage: VoteMessage, guildId: string, requestId: string) {
// get the movies currently being voted on, their votes, the eventId and its date
const voteInfo: VoteMessageInfo = await this.parseVoteInfoFromVoteMessage(voteMessage, requestId)
if (!voteInfo.event.scheduledStartAt) {
logger.info("Event does not have a start date, cancelling", { guildId: voteInfo.event.guildId, requestId })
return
}
let movies: string[] = await this.generateRerollMovieList(voteInfo, guildId, requestId)
logger.info(`Creating new poll message with new movies: ${movies}`, { requestId, guildId })
const messageText = this.createVoteMessageText(voteInfo.eventId, voteInfo.eventDate, movies, guildId, requestId)
const announcementChannel = this.client.getAnnouncementChannelForGuild(guildId) const announcementChannel = this.client.getAnnouncementChannelForGuild(guildId)
if (!announcementChannel) { if (!announcementChannel) {
logger.error(`No announcementChannel found for ${guildId}, can't post poll`) logger.error(`No announcementChannel found for ${guildId}, can't post poll`)
return return
} }
try { try {
logger.info(`Trying to remove old vote Message`, { requestId, guildId }) logger.info(`Trying to remove old vote Message`, { requestId, guildId })
this.removeMessage(voteMessage) this.removeMessage(voteMessage)
} catch (err) { } catch (err) {
// TODO: integrate failure DM to media Admin to inform about inability to delete old message
logger.error(`Error during removeMessage: ${err}`) logger.error(`Error during removeMessage: ${err}`)
} }
const sentMessage = this.prepareAndSendVoteMessage({
const sentMessage = await this.sendVoteMessage(messageText, movies.length, announcementChannel) event: voteInfo.event,
sentMessage.pin() movies,
logger.info(`Sent and pinned new poll message`, { requestId, guildId }) announcementChannel,
startDate: voteInfo.event.scheduledStartAt,
pinAfterSending: true
},
guildId,
requestId)
logger.debug(`Sent reroll message: ${JSON.stringify(sentMessage)}`, { requestId, guildId })
} }
private async fetchEventStartDateByEventId(guild: Guild, eventId: string, requestId: string): Promise<Maybe<Date>> { private async fetchEventByEventId(guild: Guild, eventId: string, requestId: string): Promise<Maybe<GuildScheduledEvent>> {
const guildEvent: GuildScheduledEvent = await guild.scheduledEvents.fetch(eventId) const guildEvent: GuildScheduledEvent = await guild.scheduledEvents.fetch(eventId)
if (!guildEvent) logger.error(`GuildScheduledEvent with id${eventId} could not be found`, { requestId, guildId: guild.id }) if (!guildEvent) logger.error(`GuildScheduledEvent with id${eventId} could not be found`, { requestId, guildId: guild.id })
if (guildEvent.scheduledStartAt) return guildEvent
return guildEvent.scheduledStartAt
} }
public async parseVoteInfoFromVoteMessage(message: VoteMessage, requestId: string): Promise<VoteMessageInfo> { public async parseVoteInfoFromVoteMessage(message: VoteMessage, requestId: string): Promise<VoteMessageInfo> {
@ -129,8 +138,7 @@ export default class VoteController {
if (!message.guild) if (!message.guild)
throw new Error(`Message ${message.id} not a guild message`) throw new Error(`Message ${message.id} not a guild message`)
let eventStartDate: Maybe<Date> = await this.fetchEventStartDateByEventId(message.guild, parsedIds.eventId, requestId) const event: Maybe<GuildScheduledEvent> = await this.fetchEventByEventId(message.guild, parsedIds.eventId, requestId)
if (!eventStartDate) eventStartDate = this.parseEventDateFromMessage(message.cleanContent, message.guild.id, requestId)
let votes: Vote[] = [] let votes: Vote[] = []
for (const line of lines) { for (const line of lines) {
@ -149,7 +157,7 @@ export default class VoteController {
} }
} }
} }
return <VoteMessageInfo>{ eventId: parsedIds.eventId, eventDate: eventStartDate, votes } return <VoteMessageInfo>{ event, votes }
} }
public parseEventDateFromMessage(message: string, guildId: string, requestId: string): Date { public parseEventDateFromMessage(message: string, guildId: string, requestId: string): Date {
logger.warn(`Falling back to RegEx parsing to get Event Date`, { guildId, requestId }) logger.warn(`Falling back to RegEx parsing to get Event Date`, { guildId, requestId })
@ -168,15 +176,15 @@ export default class VoteController {
} }
public async prepareAndSendVoteMessage(inputInfo: prepareVoteMessageInput, guildId: string, requestId: string) { public async prepareAndSendVoteMessage(inputInfo: prepareVoteMessageInput, guildId: string, requestId: string) {
const messageText = this.createVoteMessageText(inputInfo.event.id, inputInfo.startDate, inputInfo.movies, guildId, requestId) const messageText = this.createVoteMessageText(inputInfo.event, inputInfo.movies, guildId, requestId)
const sentMessage = await this.sendVoteMessage(messageText, inputInfo.movies.length, inputInfo.announcementChannel) const sentMessage = await this.sendVoteMessage(messageText, inputInfo.movies.length, inputInfo.announcementChannel)
if (inputInfo.pinAfterSending) if (inputInfo.pinAfterSending)
sentMessage.pin() sentMessage.pin()
return sentMessage return sentMessage
} }
public createVoteMessageText(eventId: string, eventStartDate: Date, movies: string[], guildId: string, requestId: string): string { public createVoteMessageText(event: GuildScheduledEvent, movies: string[], guildId: string, requestId: string): string {
let message = `[Abstimmung] für https://discord.com/events/${guildId}/${eventId} \n<@&${config.bot.announcement_role}> Es gibt eine neue Abstimmung für die nächste Watchparty ${createDateStringFromEvent(eventStartDate, guildId, requestId)}! Stimme hierunter für den nächsten Film ab!\n` let message = `[Abstimmung] für https://discord.com/events/${guildId}/${event.id} \n<@&${config.bot.announcement_role}> Es gibt eine neue Abstimmung für die nächste Watchparty ${createDateStringFromEvent(event.scheduledStartAt, guildId, requestId)}! Stimme hierunter für den nächsten Film ab!\n`
for (let i = 0; i < movies.length; i++) { for (let i = 0; i < movies.length; i++) {
message = message.concat(Emotes[i]).concat(": ").concat(movies[i]).concat("\n") message = message.concat(Emotes[i]).concat(": ").concat(movies[i]).concat("\n")
@ -186,6 +194,7 @@ export default class VoteController {
return message return message
} }
// TODO: Refactor into separate message controller
public async sendVoteMessage(messageText: string, movieCount: number, announcementChannel: TextChannel) { public async sendVoteMessage(messageText: string, movieCount: number, announcementChannel: TextChannel) {
const options: MessageCreateOptions = { const options: MessageCreateOptions = {

View File

@ -6,7 +6,7 @@ export const noGuildId = 'NoGuildId'
const printFn = format.printf(({ guildId, level, message, errorCode, requestId, timestamp: logTimestamp }: { [k: string]: string }) => { const printFn = format.printf(({ guildId, level, message, errorCode, requestId, timestamp: logTimestamp }: { [k: string]: string }) => {
return `[${guildId ?? ''}][${level}][${logTimestamp}][${errorCode ?? ''}][${requestId ?? ''}]:${message}` return `[${guildId ?? ''}][${level.padStart(5, " ")}][${logTimestamp}][${errorCode ?? ''}][${requestId ?? ''}]:${message}`
}) })
const logFormat = format.combine( const logFormat = format.combine(

View File

@ -29,11 +29,16 @@ describe('vote controller - none_of_that functions', () => {
id: 'mockId' id: 'mockId'
} }
} }
const mockEvent: GuildScheduledEvent = <GuildScheduledEvent><unknown>{
scheduledStartAt: testEventDate,
id: testEventId,
guild: testGuildId
}
const mockJellyfinHandler: JellyfinHandler = <JellyfinHandler><unknown>{ const mockJellyfinHandler: JellyfinHandler = <JellyfinHandler><unknown>{
getRandomMovieNames: jest.fn().mockReturnValue(["movie1"]) getRandomMovieNames: jest.fn().mockReturnValue(["movie1"])
} }
const votes = new VoteController(mockClient, mockJellyfinHandler) const votes = new VoteController(mockClient, mockJellyfinHandler)
const mockMessageContent = votes.createVoteMessageText(testEventId, testEventDate, testMovies, testGuildId, "requestId") const mockMessageContent = votes.createVoteMessageText(mockEvent, testMovies, testGuildId, "requestId")
test('sendVoteClosedMessage', async () => { test('sendVoteClosedMessage', async () => {
mockClient.getAnnouncementChannelForGuild = jest.fn().mockReturnValue({ mockClient.getAnnouncementChannelForGuild = jest.fn().mockReturnValue({
@ -57,29 +62,6 @@ describe('vote controller - none_of_that functions', () => {
content: `[Abstimmung beendet] für https://discord.com/events/${testGuildId}/${testEventId}\n<@&WATCHPARTY_ANNOUNCEMENT_ROLE> Wir gucken MovieNew am 01.01. um 01:00` content: `[Abstimmung beendet] für https://discord.com/events/${testGuildId}/${testEventId}\n<@&WATCHPARTY_ANNOUNCEMENT_ROLE> Wir gucken MovieNew am 01.01. um 01:00`
}) })
}) })
// test('checkForPollsToClose', async () => {
//
// const testGuild: Guild = <Guild><unknown>{
// scheduledEvents: {
// fetch: jest.fn().mockImplementation(() => {
// return new Promise(resolve => {
// resolve([
// { name: "Event Name" },
// { name: "Event: VOTING OFFEN", scheduledStartTimestamp: "" },
// { name: "another voting" },
// ]
// )
// })
// })
// }
// }
//
// const result = await votes.checkForPollsToClose(testGuild)
//
//
//
//
// })
test('getVotesByEmote', async () => { test('getVotesByEmote', async () => {
const mockMessage: Message = <Message><unknown>{ const mockMessage: Message = <Message><unknown>{

View File

@ -1,9 +1,9 @@
import { Emoji, NONE_OF_THAT } from "../../server/constants" import { Emoji, NONE_OF_THAT } from "../../server/constants"
import VoteController, { Vote, VoteMessageInfo } from "../../server/helper/vote.controller" import VoteController, { VoteMessageInfo } from "../../server/helper/vote.controller"
import { JellyfinHandler } from "../../server/jellyfin/handler" import { JellyfinHandler } from "../../server/jellyfin/handler"
import { ExtendedClient } from "../../server/structures/client" import { ExtendedClient } from "../../server/structures/client"
import { VoteMessage } from "../../server/helper/messageIdentifiers" import { VoteMessage } from "../../server/helper/messageIdentifiers"
import { Message, MessageReaction } from "discord.js" import { GuildScheduledEvent, MessageReaction } from "discord.js"
test('parse votes from vote message', async () => { test('parse votes from vote message', async () => {
const testMovies = [ const testMovies = [
'Movie1', 'Movie1',
@ -16,12 +16,16 @@ test('parse votes from vote message', async () => {
const testEventDate = new Date('2023-01-01') const testEventDate = new Date('2023-01-01')
const testGuildId = "888999888" const testGuildId = "888999888"
const voteController: VoteController = new VoteController(<ExtendedClient>{}, <JellyfinHandler>{}) const voteController: VoteController = new VoteController(<ExtendedClient>{}, <JellyfinHandler>{})
const testMessage = voteController.createVoteMessageText(testEventId, testEventDate, testMovies, testGuildId, "requestId") const mockEvent: GuildScheduledEvent = <GuildScheduledEvent><unknown>{
scheduledStartAt: testEventDate,
id: testEventId,
guild: testGuildId
}
const testMessage = voteController.createVoteMessageText(mockEvent, testMovies, testGuildId, "requestId")
const expectedResult: VoteMessageInfo = { const expectedResult: VoteMessageInfo = {
eventId: testEventId, event: mockEvent,
eventDate: testEventDate,
votes: [ votes: [
{ emote: Emoji.one, count: 1, movie: testMovies[0] }, { emote: Emoji.one, count: 1, movie: testMovies[0] },
{ emote: Emoji.two, count: 2, movie: testMovies[1] }, { emote: Emoji.two, count: 2, movie: testMovies[1] },
@ -40,6 +44,8 @@ test('parse votes from vote message', async () => {
fetch: jest.fn().mockImplementation((input: any) => { fetch: jest.fn().mockImplementation((input: any) => {
if (input === testEventId) if (input === testEventId)
return { return {
id: testEventId,
guild: testGuildId,
scheduledStartAt: testEventDate scheduledStartAt: testEventDate
} }
}) })
@ -61,8 +67,8 @@ test('parse votes from vote message', async () => {
const result = await voteController.parseVoteInfoFromVoteMessage(message, 'requestId') const result = await voteController.parseVoteInfoFromVoteMessage(message, 'requestId')
console.log(JSON.stringify(result)) console.log(JSON.stringify(result))
expect(Array.isArray(result)).toBe(false) expect(Array.isArray(result)).toBe(false)
expect(result.eventId).toEqual(testEventId) expect(result.event.id).toEqual(testEventId)
expect(result.eventDate).toEqual(testEventDate) expect(result.event.scheduledStartAt).toEqual(testEventDate)
expect(result.votes.length).toEqual(expectedResult.votes.length) expect(result.votes.length).toEqual(expectedResult.votes.length)
expect(result).toEqual(expectedResult) expect(result).toEqual(expectedResult)
}) })
@ -79,7 +85,12 @@ test('parse votes from vote message', () => {
const testEventDate = new Date('2023-01-01') const testEventDate = new Date('2023-01-01')
const testGuildId = "888999888" const testGuildId = "888999888"
const voteController: VoteController = new VoteController(<ExtendedClient>{}, <JellyfinHandler>{}) const voteController: VoteController = new VoteController(<ExtendedClient>{}, <JellyfinHandler>{})
const testMessage = voteController.createVoteMessageText(testEventId, testEventDate, testMovies, testGuildId, "requestId") const mockEvent: GuildScheduledEvent = <GuildScheduledEvent><unknown>{
scheduledStartAt: testEventDate,
id: testEventId,
guild: testGuildId
}
const testMessage = voteController.createVoteMessageText(mockEvent, testMovies, testGuildId, "requestId")
const result = voteController.parseGuildIdAndEventIdFromWholeMessage(testMessage) const result = voteController.parseGuildIdAndEventIdFromWholeMessage(testMessage)
expect(result).toEqual({ guildId: testGuildId, eventId: testEventId }) expect(result).toEqual({ guildId: testGuildId, eventId: testEventId })
@ -108,7 +119,12 @@ test.skip('handles complete none_of_that vote', () => {
} }
} }
const voteController = new VoteController(mockClient, mockJellyfinHandler) const voteController = new VoteController(mockClient, mockJellyfinHandler)
const mockMessageContent = voteController.createVoteMessageText(testEventId, testEventDate, testMovies, testGuildId, "requestId") const mockEvent: GuildScheduledEvent = <GuildScheduledEvent><unknown>{
scheduledStartAt: testEventDate,
id: testEventId,
guild: testGuildId
}
const mockMessageContent = voteController.createVoteMessageText(mockEvent, testMovies, testGuildId, "requestId")
const reactedUponMessage: VoteMessage = <VoteMessage><unknown>{ const reactedUponMessage: VoteMessage = <VoteMessage><unknown>{
cleanContent: mockMessageContent, cleanContent: mockMessageContent,
guild: { guild: {