50 Commits

Author SHA1 Message Date
fec0bc31f1 1.1.4
All checks were successful
Build a docker image for node-jellyfin-role-bot / build-docker-image (push) Successful in 56s
2023-11-19 20:25:32 +01:00
1bfcaa95f9 Merge pull request 'feat/40-reroll-on-disinterest' (#54) from feat/40-reroll-on-disinterest into master
Reviewed-on: #54
2023-11-19 20:24:35 +01:00
fb4ab59dc6 rename emotes to validvoteemotes
All checks were successful
Run unit tests / test (pull_request) Successful in 14s
Compile the repository / compile (pull_request) Successful in 16s
2023-11-19 20:22:14 +01:00
6d40930dc1 fix incorrect log regarding update cancellation, fixes return type of function to use Maybe
All checks were successful
Compile the repository / compile (pull_request) Successful in 17s
Run unit tests / test (pull_request) Successful in 14s
2023-11-19 20:17:51 +01:00
4e9fe587b0 rename to getOpenPollEvent
All checks were successful
Compile the repository / compile (pull_request) Successful in 17s
Run unit tests / test (pull_request) Successful in 13s
2023-11-19 20:13:49 +01:00
03b6a30ffa remove unnecessary if
All checks were successful
Compile the repository / compile (pull_request) Successful in 16s
Run unit tests / test (pull_request) Successful in 18s
2023-11-19 20:11:03 +01:00
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
976175242b reorder close poll and use message identifier
All checks were successful
Compile the repository / compile (pull_request) Successful in 28s
Run unit tests / test (pull_request) Successful in 16s
2023-11-19 18:56:39 +01:00
68546b0b50 adjust message identifier in test 2023-11-19 18:56:08 +01:00
1348abbd48 make message identifiers actually work properly with LSP 2023-11-19 18:55:51 +01:00
fce9091114 rename message type union to better reflect its intention 2023-11-19 18:24:33 +01:00
081f3c6201 fix incorrect branded type 2023-11-19 18:24:13 +01:00
ca99987a20 clean up variable and function names
Some checks failed
Compile the repository / compile (pull_request) Failing after 16s
Run unit tests / test (pull_request) Failing after 13s
2023-11-19 18:21:51 +01:00
fc64728a78 msg -> message
All checks were successful
Compile the repository / compile (pull_request) Successful in 21s
Run unit tests / test (pull_request) Successful in 13s
2023-11-18 18:26:45 +01:00
20da25f2bf comment filter function
All checks were successful
Compile the repository / compile (pull_request) Successful in 39s
Run unit tests / test (pull_request) Successful in 14s
2023-11-18 18:22:11 +01:00
a455fd8ff7 message -> messageText
All checks were successful
Compile the repository / compile (pull_request) Successful in 16s
Run unit tests / test (pull_request) Successful in 13s
2023-11-18 18:15:27 +01:00
119343c916 fix comment 2023-11-18 18:15:13 +01:00
296a490e93 rename filter function 2023-11-18 18:14:56 +01:00
66507cb08f msg -> message
All checks were successful
Compile the repository / compile (pull_request) Successful in 16s
Run unit tests / test (pull_request) Successful in 13s
2023-11-18 17:40:50 +01:00
4600820889 move preparation of vote Message sending into vote controller
All checks were successful
Compile the repository / compile (pull_request) Successful in 17s
Run unit tests / test (pull_request) Successful in 13s
event only needs to supply information, text creation, sending and pinning happens in the vote controller
2023-11-18 17:28:44 +01:00
4a3e8809be Merge branch 'master' into feat/40-reroll-on-disinterest
All checks were successful
Compile the repository / compile (pull_request) Successful in 15s
Run unit tests / test (pull_request) Successful in 13s
2023-11-18 16:46:28 +01:00
690ba697b6 Merge pull request 'Unit Test Setup' (#58) from feat/unit-test-setup into master
Reviewed-on: #58
2023-11-18 16:45:58 +01:00
71343d6742 update packagelock
All checks were successful
Run unit tests / test (pull_request) Successful in 1m23s
Compile the repository / compile (pull_request) Successful in 59s
2023-11-18 16:42:40 +01:00
3f6e558d39 make logger silent during unit tests, add logging const for more concise requestId/guildid handling 2023-11-18 16:42:27 +01:00
ca259c5f24 update tsconfig
All checks were successful
Compile the repository / compile (pull_request) Successful in 1m10s
Run unit tests / test (pull_request) Successful in 2m0s
2023-11-18 16:38:52 +01:00
b1c581ca6e npm test script
All checks were successful
Compile the repository / compile (pull_request) Successful in 1m40s
Run unit tests / test (pull_request) Successful in 1m21s
2023-11-18 16:28:51 +01:00
96189c2392 adjust docker file to enable better build flow for tests 2023-11-18 16:28:40 +01:00
700353cff4 include a test-env file to setup environment variables for unit tests 2023-11-18 16:28:18 +01:00
f705b97804 move testenv to correct location
All checks were successful
Run unit tests / test (pull_request) Successful in 1m0s
Compile the repository / compile (pull_request) Successful in 20s
2023-10-24 22:42:03 +02:00
9cdc6e1934 add fake env vars for unit tests
Some checks failed
Compile the repository / compile (pull_request) Successful in 39s
Run unit tests / test (pull_request) Failing after 45s
2023-10-24 22:39:57 +02:00
c73cd20ccf add test-relevant fallback values for unit tests
All checks were successful
Compile the repository / compile (pull_request) Successful in 38s
Run unit tests / test (pull_request) Successful in 1m26s
2023-10-21 15:05:25 +02:00
e66aebc88c make top pick retain optional during reroll via env var
Some checks failed
Compile the repository / compile (pull_request) Successful in 32s
Run unit tests / test (pull_request) Failing after 40s
2023-10-21 14:56:33 +02:00
599243990e remove console.logs 2023-10-21 14:56:15 +02:00
eef3a9c358 add missing role to test
Some checks failed
Compile the repository / compile (pull_request) Successful in 2m13s
Run unit tests / test (pull_request) Failing after 1m10s
2023-10-21 14:11:03 +02:00
1e912b20ef formatting for package.json
All checks were successful
Compile the repository / compile (pull_request) Successful in 1m23s
Run unit tests / test (pull_request) Successful in 1m55s
2023-08-13 18:35:48 +02:00
ce4dc81f7d fix incorrect reroll behaviour
now correctly fetches old movies, filters already voted on movies, gets new movies, creates new poll message, deletes old message
2023-08-13 18:35:22 +02:00
b76df79d2a testcases 2023-08-13 18:33:45 +02:00
4e563d57fd fix else branch of memberthreshold 2023-08-13 18:33:45 +02:00
b6a1e06b03 update default movie env var 2023-08-13 18:14:16 +02:00
2ebc7fbdbe restructure docker build a bit
All checks were successful
Compile the repository / compile (pull_request) Successful in 1m12s
Run unit tests / test (pull_request) Successful in 1m39s
2023-08-06 02:37:49 +02:00
8ff5aeff03 logging
All checks were successful
Compile the repository / compile (pull_request) Successful in 1m21s
Run unit tests / test (pull_request) Successful in 1m26s
2023-08-06 02:33:28 +02:00
1101a84501 imports 2023-08-06 02:33:23 +02:00
91ec2ece7e explicit typing 2023-08-06 02:33:17 +02:00
5e58765cf4 also enabled NONE_OF_THAT to be handled 2023-08-06 02:32:44 +02:00
a2adef808f add guildscheduledevents to unit test mock
All checks were successful
Compile the repository / compile (pull_request) Successful in 14m58s
Run unit tests / test (pull_request) Successful in 4m18s
2023-07-17 23:31:00 +02:00
dc66c277b2 big refactoring of none_of_that handler
extracting, better typing, reduction of complexity
2023-07-17 23:30:48 +02:00
c022cc32d5 refactor eventId parsing to separate function
All checks were successful
Compile the repository / compile (pull_request) Successful in 49s
Run unit tests / test (pull_request) Successful in 1m35s
prepare for querying discord api for event info instead of parsing via regex
2023-07-17 22:50:24 +02:00
e763e76413 add new test for eventId parsing 2023-07-17 22:49:12 +02:00
137d156981 fix date string in vote message 2023-07-17 22:48:57 +02:00
22 changed files with 12653 additions and 12332 deletions

View File

@ -2,13 +2,13 @@ FROM node:alpine as files
ENV TZ="Europe/Berlin"
WORKDIR /app
COPY [ "package-lock.json", "package.json", "index.ts", "tsconfig.json", "./" ]
COPY server ./server
FROM files as proddependencies
ENV NODE_ENV=production
RUN npm ci --omit=dev
FROM proddependencies as compile
COPY server ./server
RUN npm run build
CMD ["npm","run","start"]
@ -16,6 +16,7 @@ FROM files as dependencies
RUN npm ci
FROM dependencies as test
COPY server ./server
COPY jest.config.js .
COPY tests ./tests
RUN npm run test

View File

@ -7,6 +7,7 @@ module.exports = {
'^.+\\.tsx?$': 'ts-jest'
},
'testRegex': '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
'setupFiles': ["<rootDir>/tests/testenv.js"],
'moduleFileExtensions': [
'ts',
'tsx',

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "node-jellyfin-discord-bot",
"version": "1.1.3",
"version": "1.1.4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "node-jellyfin-discord-bot",
"version": "1.1.3",
"version": "1.1.4",
"license": "MIT",
"dependencies": {
"@discordjs/rest": "^1.7.0",

View File

@ -1,6 +1,6 @@
{
"name": "node-jellyfin-discord-bot",
"version": "1.1.3",
"version": "1.1.4",
"description": "A discord bot to sync jellyfin accounts with discord roles",
"main": "index.js",
"license": "MIT",
@ -34,7 +34,7 @@
"monitor": "nodemon build/index.js",
"lint": "eslint . --ext .ts",
"lint-fix": "eslint . --ext .ts --fix",
"test": "jest",
"test": "jest --runInBand",
"test-watch": "jest --watch"
},
"devDependencies": {

View File

@ -1,6 +1,7 @@
import { ApplicationCommandOptionType } from 'discord.js'
import { Command } from '../structures/command'
import { RunOptions } from '../types/commandTypes'
import { logger } from '../logger'
export default new Command({
name: 'echo',
description: 'Echoes a text',
@ -13,7 +14,7 @@ export default new Command({
}
],
run: async (interaction: RunOptions) => {
console.log('echo called')
logger.info('echo called')
interaction.interaction.reply(interaction.toString())
}
})

View File

@ -2,15 +2,16 @@ import { v4 as uuid } from 'uuid'
import { jellyfinHandler } from "../.."
import { Command } from '../structures/command'
import { RunOptions } from '../types/commandTypes'
import { logger } from '../logger'
export default new Command({
name: 'passwort_reset',
description: 'Ich vergebe dir ein neues Passwort und schicke es dir per DM zu. Kostet auch nix! Versprochen! 😉',
options: [],
run: async (interaction: RunOptions) => {
console.log('PasswortReset called')
logger.info('PasswortReset called')
interaction.interaction.followUp('Yo, ich schick dir eins!')
console.log(JSON.stringify(interaction.interaction.member, null, 2))
logger.info(JSON.stringify(interaction.interaction.member, null, 2))
jellyfinHandler.resetUserPasswort(interaction.interaction.member, uuid())
}
})

View File

@ -31,6 +31,7 @@ export interface Config {
yavin_jellyfin_token: string
yavin_jellyfin_collection_user: string
random_movie_count: number
reroll_retains_top_picks: boolean
}
}
export const config: Config = {
@ -60,17 +61,18 @@ export const config: Config = {
client_id: process.env.CLIENT_ID ?? "",
jellfin_token: process.env.JELLYFIN_TOKEN ?? "",
jellyfin_url: process.env.JELLYFIN_URL ?? "",
workaround_token: process.env.TOKEN ?? "",
watcher_role: process.env.WATCHER_ROLE ?? "",
jf_admin_role: process.env.ADMIN_ROLE ?? "",
announcement_role: process.env.WATCHPARTY_ANNOUNCEMENT_ROLE ?? "",
announcement_channel_id: process.env.CHANNEL_ID ?? "",
workaround_token: process.env.TOKEN ?? "TOKEN",
watcher_role: process.env.WATCHER_ROLE ?? "WATCHER_ROLE",
jf_admin_role: process.env.ADMIN_ROLE ?? "ADMIN_ROLE",
announcement_role: process.env.WATCHPARTY_ANNOUNCEMENT_ROLE ?? "ANNOUNCE_ROLE",
announcement_channel_id: process.env.CHANNEL_ID ?? "ANNOUNCE_CHANNEL",
jf_collection_id: process.env.JELLYFIN_COLLECTION_ID ?? "",
yavin_collection_id: process.env.YAVIN_COLLECTION_ID ?? "",
yavin_jellyfin_url: process.env.YAVIN_JELLYFIN_URL ?? "",
yavin_jellyfin_token: process.env.YAVIN_TOKEN ?? "",
yavin_jellyfin_collection_user: process.env.YAVIN_COLLECTION_USER ?? "",
jf_user: process.env.JELLYFIN_USER ?? "",
random_movie_count: parseInt(process.env.RANDOM_MOVIE_COUNT ?? "") ?? 5
random_movie_count: parseInt(process.env.RANDOM_MOVIE_COUNT ?? "5") ?? 5,
reroll_retains_top_picks: process.env.REROLL_RETAIN === "true"
}
}

View File

@ -1,6 +1,7 @@
export enum Emotes { "1⃣", "2⃣", "3⃣", "4⃣", "5⃣", "6⃣", "7⃣", "8⃣", "9⃣", "🔟" }
export enum ValidVoteEmotes { "1⃣", "2⃣", "3⃣", "4⃣", "5⃣", "6⃣", "7⃣", "8⃣", "9⃣", "🔟" }
export const NONE_OF_THAT = "❌"
// WIP
export const Emoji = {
"one": "\u0031\uFE0F\u20E3",
"two": "\u0032\uFE0F\u20E3",

View File

@ -4,7 +4,6 @@ import { client, yavinJellyfinHandler } from "../..";
import { Maybe } from "../interfaces";
import { logger } from "../logger";
export const name = 'guildScheduledEventCreate'
export async function execute(event: GuildScheduledEvent) {
@ -25,15 +24,21 @@ export async function execute(event: GuildScheduledEvent) {
return
}
logger.debug(`Found channel ${JSON.stringify(announcementChannel, null, 2)}`, { guildId: event.guildId, requestId })
if (!event.scheduledStartAt) {
logger.info("EVENT DOES NOT HAVE STARTDATE; CANCELLING", { guildId: event.guildId, requestId })
logger.info("Event does not have a start date, cancelling", { guildId: event.guildId, requestId })
return
}
const sentMessageText = client.voteController.createVoteMessageText(event.id, event.scheduledStartAt, movies, event.guild?.id ?? "", requestId)
const sentMessage = await client.voteController.sendVoteMessage(sentMessageText, movies.length, announcementChannel)
const sentMessage = await client.voteController.prepareAndSendVoteMessage({
movies,
startDate: event.scheduledStartAt,
event,
announcementChannel,
pinAfterSending: true
},
event.guildId,
requestId)
sentMessage.pin()
logger.debug(JSON.stringify(sentMessage))
}
}

View File

@ -1,7 +1,7 @@
import { Message, MessageReaction, User } from "discord.js";
import { logger, newRequestId, noGuildId } from "../logger";
import { Emoji, Emotes, NONE_OF_THAT } from "../constants";
import { Emoji, ValidVoteEmotes, NONE_OF_THAT } from "../constants";
import { client } from "../..";
import { isInitialAnnouncement, isVoteMessage } from "../helper/messageIdentifiers";
@ -26,7 +26,7 @@ export async function execute(messageReaction: MessageReaction, user: User) {
logger.info(`emoji: ${messageReaction.emoji.toString()}`)
if (!Object.values(Emotes).includes(messageReaction.emoji.toString())) {
if (!Object.values(ValidVoteEmotes).includes(messageReaction.emoji.toString()) && messageReaction.emoji.toString() !== NONE_OF_THAT) {
logger.info(`${messageReaction.emoji.toString()} currently not handled`)
return
}
@ -34,10 +34,7 @@ export async function execute(messageReaction: MessageReaction, user: User) {
if (isVoteMessage(reactedUponMessage)) {
if (messageReaction.emoji.toString() === NONE_OF_THAT) {
logger.info(`Reaction is NONE_OF_THAT on a vote message. Handling`, { requestId, guildId })
return client.voteController.handleNoneOfThatVote(messageReaction, user, reactedUponMessage, requestId, guildId)
}
if (messageReaction.emoji.toString() === Emoji.one) {
// do something
return client.voteController.handleNoneOfThatVote(messageReaction, reactedUponMessage, requestId, guildId)
}
}
else if (isInitialAnnouncement(reactedUponMessage)) {

View File

@ -1,6 +1,7 @@
import { Message } from "discord.js"
import { logger } from "../logger"
export const name = 'messageCreate'
export function execute(message: Message) {
console.log(`${JSON.stringify(message)} has been created`)
logger.info(`${JSON.stringify(message)} has been created`)
}

View File

@ -1,10 +1,10 @@
import { format, isToday } from "date-fns";
import { utcToZonedTime } from "date-fns-tz"
import { GuildScheduledEvent } from "discord.js";
import { logger } from "../logger";
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) {
logger.error("Event has no start. Cannot create dateString.", { guildId, requestId })
return `"habe keinen Startzeitpunkt ermitteln können"`
@ -18,6 +18,6 @@ export function createDateStringFromEvent(eventStartDate:Date, requestId: string
return `heute um ${time}`
}
const date = format(zonedDateTime, "eeee dd.MM", { locale: de })
const date = format(zonedDateTime, "eeee dd.MM.", { locale: de })
return `am ${date} um ${time}`
}

View File

@ -2,19 +2,19 @@ import { Message } from "discord.js";
// branded types to differentiate objects of identical Type but different contents
export type VoteEndMessage = Message & { readonly __brand: 'vote' }
export type AnnouncementMessage = Message & { readonly __brand: 'announcement' }
export type VoteMessage = Message & { readonly __brand: 'voteend' }
export type VoteEndMessage = Message<true> & { readonly __brand: 'voteend' }
export type AnnouncementMessage = Message<true> & { readonly __brand: 'announcement' }
export type VoteMessage = Message<true> & { readonly __brand: 'vote' }
export type DiscordMessage = VoteMessage | VoteEndMessage | AnnouncementMessage
export type KnownDiscordMessage = VoteMessage | VoteEndMessage | AnnouncementMessage
export function isVoteMessage(msg: Message): msg is VoteMessage {
return msg.cleanContent.includes('[Abstimmung]')
export function isVoteMessage(message: Message): message is VoteMessage {
return message.cleanContent.includes('[Abstimmung]')
}
export function isInitialAnnouncement(msg: Message): msg is AnnouncementMessage {
return msg.cleanContent.includes("[initial]")
export function isInitialAnnouncement(message: Message): message is AnnouncementMessage {
return message.cleanContent.includes("[initial]")
}
export function isVoteEndedMessage(msg: Message): msg is VoteEndMessage {
return msg.cleanContent.includes("[Abstimmung beendet]")
export function isVoteEndedMessage(message: Message): message is VoteEndMessage {
return message.cleanContent.includes("[Abstimmung beendet]")
}

View File

@ -1,10 +1,11 @@
import { CustomError, errorCodes } from "../interfaces"
import { logger } from "../logger"
import { ExtendedClient } from "../structures/client"
export async function sendFailureDM(creatorMessage: string, client: ExtendedClient, creatorId?: string): Promise<void> {
if (!creatorId) throw new CustomError('No creator ID present', errorCodes.no_creator_id)
const creator = await client.users.fetch(creatorId)
console.log(`Creator ${JSON.stringify(creator)}`)
logger.info(`Creator ${JSON.stringify(creator)}`)
if (creator)
if (!creator.dmChannel)
await creator.createDM()

View File

@ -1,11 +1,11 @@
import { Guild, GuildScheduledEvent, GuildScheduledEventEditOptions, GuildScheduledEventSetStatusArg, GuildScheduledEventStatus, Message, MessageCreateOptions, MessageReaction, PartialMessage, TextChannel, User } from "discord.js"
import { Emoji, Emotes, NONE_OF_THAT } from "../constants"
import { Guild, GuildScheduledEvent, GuildScheduledEventEditOptions, GuildScheduledEventSetStatusArg, GuildScheduledEventStatus, Message, MessageCreateOptions, MessageReaction, TextChannel } from "discord.js"
import { ValidVoteEmotes, NONE_OF_THAT } from "../constants"
import { logger, newRequestId } from "../logger"
import { getMembersWithRoleFromGuild } from "./roleFilter"
import { config } from "../configuration"
import { VoteMessage, isVoteEndedMessage, isVoteMessage } from "./messageIdentifiers"
import { createDateStringFromEvent } from "./dateHelper"
import { Maybe } from "../interfaces"
import { Maybe, voteMessageInputInformation as prepareVoteMessageInput } from "../interfaces"
import format from "date-fns/format"
import toDate from "date-fns/toDate"
import differenceInDays from "date-fns/differenceInDays"
@ -21,8 +21,7 @@ export type Vote = {
}
export type VoteMessageInfo = {
votes: Vote[],
eventId: string,
eventDate: Date
event: GuildScheduledEvent,
}
export default class VoteController {
private client: ExtendedClient
@ -33,8 +32,9 @@ export default class VoteController {
this.yavinJellyfinHandler = _yavin
}
public async handleNoneOfThatVote(messageReaction: MessageReaction, user: User, reactedUponMessage: VoteMessage, requestId: string, guildId: string) {
public async handleNoneOfThatVote(messageReaction: MessageReaction, reactedUponMessage: VoteMessage, requestId: string, guildId: string) {
if (!messageReaction.message.guild) return 'No guild'
const guild = messageReaction.message.guild
logger.debug(`${reactedUponMessage.id} is vote message`, { requestId, guildId })
const watcherRoleMember = await getMembersWithRoleFromGuild(config.bot.announcement_role, messageReaction.message.guild)
@ -43,58 +43,106 @@ export default class VoteController {
const watcherRoleMemberCount = watcherRoleMember.size
logger.info(`MEMBER COUNT: ${watcherRoleMemberCount}`, { requestId, guildId })
const noneOfThatReactions = messageReaction.message.reactions.cache.get(NONE_OF_THAT)?.users.cache.filter(x => x.id !== this.client.user?.id).size ?? 0
const noneOfThatReactions = reactedUponMessage.reactions.cache.get(NONE_OF_THAT)?.users.cache.filter(x => x.id !== this.client.user?.id).size ?? 0
const memberThreshold = (watcherRoleMemberCount / 2)
logger.info(`Reroll ${noneOfThatReactions} > ${memberThreshold} ?`, { requestId, guildId })
if (noneOfThatReactions > memberThreshold) {
if (noneOfThatReactions > memberThreshold)
logger.info(`No reroll`, { requestId, guildId })
else {
logger.info('Starting poll reroll', { requestId, guildId })
messageReaction.message.edit((messageReaction.message.content ?? "").concat('\nDiese Abstimmung muss wiederholt werden.'))
// get movies that _had_ votes
//const oldMovieNames: Vote[] = this.parseVotesFromVoteMessage(messageReaction.message, requestId)
const eventId = this.parseEventIdFromMessage(messageReaction.message, requestId)
const eventStartDate: Date = this.fetchEventStartDateByEventId(eventId, requestId) //TODO
//
// get movies from jellyfin to fill the remaining slots
const newMovieCount = config.bot.random_movie_count //- oldMovieNames.length
const newMovies = await this.yavinJellyfinHandler.getRandomMovieNames(newMovieCount, guildId, requestId)
await this.handleReroll(reactedUponMessage, guild.id, requestId)
logger.info(`Finished handling NONE_OF_THAT vote`, { requestId, guildId })
}
}
private async removeMessage(message: Message): Promise<Message<boolean>> {
if (message.pinned) {
await message.unpin()
}
return await message.delete()
}
/**
* returns true if a Vote object contains at least one vote
* @param {Vote} vote
*/
private hasAtLeastOneVote(vote: Vote): boolean {
// subtracting the bots initial vote
const overOneVote = (vote.count - 1) >= 1
logger.debug(`${vote.movie} : ${vote.count} -> above: ${overOneVote}`)
return overOneVote
}
public async generateRerollMovieList(voteInfo: VoteMessageInfo, guildId: string, requestId: string) {
if (config.bot.reroll_retains_top_picks) {
const votedOnMovies = voteInfo.votes.filter(this.hasAtLeastOneVote).filter(x => x.emote !== NONE_OF_THAT)
logger.info(`Found ${votedOnMovies.length} with votes`, { requestId, guildId })
const newMovieCount: number = config.bot.random_movie_count - votedOnMovies.length
logger.info(`Fetching ${newMovieCount} from jellyfin`)
const newMovies: string[] = await this.yavinJellyfinHandler.getRandomMovieNames(newMovieCount, guildId, requestId)
// merge
const movies = newMovies
return newMovies.concat(votedOnMovies.map(x => x.movie))
} else {
// get movies from jellyfin to fill the remaining slots
const newMovieCount: number = config.bot.random_movie_count
logger.info(`Fetching ${newMovieCount} from jellyfin`)
return await this.yavinJellyfinHandler.getRandomMovieNames(newMovieCount, guildId, requestId)
}
}
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)
// create new message
await this.closePoll(messageReaction.message.guild, requestId)
const message = this.createVoteMessageText(eventId, eventStartDate, movies, guildId, requestId)
const announcementChannel = this.client.getAnnouncementChannelForGuild(guildId)
if (!announcementChannel) {
logger.error(`No announcementChannel found for ${guildId}, can't post poll`)
return
}
const sentMessage = await this.sendVoteMessage(message, movies.length, announcementChannel)
sentMessage.pin()
try {
logger.info(`Trying to remove old vote Message`, { requestId, guildId })
this.removeMessage(voteMessage)
} catch (err) {
// TODO: integrate failure DM to media Admin to inform about inability to delete old message
logger.error(`Error during removeMessage: ${err}`)
}
logger.info(`No reroll`, { requestId, guildId })
const sentMessage = this.prepareAndSendVoteMessage({
event: voteInfo.event,
movies,
announcementChannel,
startDate: voteInfo.event.scheduledStartAt,
pinAfterSending: true
},
guildId,
requestId)
logger.debug(`Sent reroll message: ${JSON.stringify(sentMessage)}`, { requestId, guildId })
}
parseEventIdFromMessage(message: Message<boolean> | PartialMessage, requestId: string): string {
throw new Error("Method not implemented.")
private async fetchEventByEventId(guild: Guild, eventId: string, requestId: string): Promise<Maybe<GuildScheduledEvent>> {
const guildEvent: GuildScheduledEvent = await guild.scheduledEvents.fetch(eventId)
if (!guildEvent) logger.error(`GuildScheduledEvent with id${eventId} could not be found`, { requestId, guildId: guild.id })
return guildEvent
}
private fetchEventStartDateByEventId(eventId: string, requestId: string): Date {
throw new Error("Method not implemented.")
}
public parseVotesFromVoteMessage(message: VoteMessage, requestId: string): VoteMessageInfo {
public async parseVoteInfoFromVoteMessage(message: VoteMessage, requestId: string): Promise<VoteMessageInfo> {
const lines = message.cleanContent.split('\n')
let eventId = ""
let eventDate: Date = new Date()
let parsedIds = this.parseGuildIdAndEventIdFromWholeMessage(message.cleanContent)
if (!message.guild)
throw new Error(`Message ${message.id} not a guild message`)
const event: Maybe<GuildScheduledEvent> = await this.fetchEventByEventId(message.guild, parsedIds.eventId, requestId)
let votes: Vote[] = []
for (const line of lines) {
if (line.includes('https://discord.com/events')) {
const urlMatcher = RegExp(/(http|https|ftp):\/\/(\S*)/ig)
const result = line.match(urlMatcher)
if (!result) throw Error('No event url found in Message')
eventId = result?.[0].split('/').at(-1) ?? ""
} else if (!line.slice(0, 5).includes(':')) {
eventDate = this.parseEventDateFromLine(line)
} else if (line.slice(0, 5).includes(':')) {
if (line.slice(0, 5).includes(':')) {
const splitLine = line.split(":")
const [emoji, movie] = splitLine
const fetchedVoteFromMessage = message.reactions.cache.get(emoji)
@ -109,38 +157,55 @@ export default class VoteController {
}
}
}
return <VoteMessageInfo>{ eventId, eventDate, votes }
return <VoteMessageInfo>{ event, votes }
}
public parseEventDateFromLine(line: string): Date {
const datematcher = RegExp(/((0[1-9]|[12][0-9]|3[01])\.(0[1-9]|1[012]))(\ um\ )(([012][0-9]:[0-5][0-9]))/i)
const result: RegExpMatchArray | null = line.match(datematcher)
public parseEventDateFromMessage(message: string, guildId: string, requestId: string): Date {
logger.warn(`Falling back to RegEx parsing to get Event Date`, { guildId, requestId })
const datematcher = RegExp(/((?:0[1-9]|[12][0-9]|3[01])\.(?:0[1-9]|1[012])\.)(?:\ um\ )((?:(?:[01][0-9]|[2][0-3])\:[0-5][0-9])|(?:[2][4]\:00))!/i)
const result: RegExpMatchArray | null = message.match(datematcher)
const timeFromResult = result?.at(-1)
const dateFromResult = result?.at(1)?.concat(format(new Date(), '.yyyy')).concat(" " + timeFromResult) ?? ""
const dateFromResult = result?.at(1)?.concat(format(new Date(), 'yyyy')).concat(" " + timeFromResult) ?? ""
return new Date(dateFromResult)
}
public parseGuildIdAndEventIdFromWholeMessage(message: string) {
const idmatch = RegExp(/(?:http|https):\/\/discord\.com\/events\/(\d*)\/(\d*)/)
const matches = message.match(idmatch)
if (matches && matches.length == 3)
return { guildId: matches[1], eventId: matches[2] }
throw Error(`Could not find eventId in Vote Message`)
}
public createVoteMessageText(eventId: string, eventStartDate: Date, movies: string[], guildId: string, requestId: string): string {
public async prepareAndSendVoteMessage(inputInfo: prepareVoteMessageInput, guildId: string, requestId: string) {
const messageText = this.createVoteMessageText(inputInfo.event, inputInfo.movies, guildId, requestId)
const sentMessage = await this.sendVoteMessage(messageText, inputInfo.movies.length, inputInfo.announcementChannel)
if (inputInfo.pinAfterSending)
sentMessage.pin()
return sentMessage
}
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`
public createVoteMessageText(event: GuildScheduledEvent, movies: string[], guildId: string, requestId: string): string {
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++) {
message = message.concat(Emotes[i]).concat(": ").concat(movies[i]).concat("\n")
message = message.concat(ValidVoteEmotes[i]).concat(": ").concat(movies[i]).concat("\n")
}
message = message.concat(NONE_OF_THAT).concat(": Wenn dir nichts davon gefällt.")
return message
}
public async sendVoteMessage(message: string, movieCount: number, announcementChannel: TextChannel) {
// TODO: Refactor into separate message controller
public async sendVoteMessage(messageText: string, movieCount: number, announcementChannel: TextChannel) {
const options: MessageCreateOptions = {
allowedMentions: { parse: ["roles"] },
content: message,
content: messageText,
}
const sentMessage: Message<true> = await (await announcementChannel.fetch()).send(options)
for (let i = 0; i < movieCount; i++) {
sentMessage.react(Emotes[i])
sentMessage.react(ValidVoteEmotes[i])
}
sentMessage.react(NONE_OF_THAT)
@ -169,32 +234,35 @@ export default class VoteController {
const lastMessage: Message<true> = messages[0]
if (!isVoteMessage(lastMessage)) {
logger.error(`Found message that is not a vote message, can't proceed`, { guildId, requestId })
logger.debug(`Found messages: ${JSON.stringify(messages, null, 2)}`, { guildId, requestId })
logger.debug(`Last message: ${JSON.stringify(lastMessage, null, 2)}`, { guildId, requestId })
}
else {
const votes = (await this.getVotesByEmote(lastMessage, guildId, requestId))
.sort((a, b) => b.count - a.count)
logger.debug(`votes: ${JSON.stringify(votes, null, 2)}`, { guildId, requestId })
logger.info("Deleting vote message")
lastMessage.unpin()
await lastMessage.delete()
const event = await this.getEvent(guild, guild.id, requestId)
if (event) {
this.updateEvent(event, votes, guild, guildId, requestId)
const event = await this.getOpenPollEvent(guild, guild.id, requestId)
if (event && votes?.length > 0) {
this.updateOpenPollEventWithVoteResults(event, votes, guild, guildId, requestId)
this.sendVoteClosedMessage(event, votes[0].movie, guildId, requestId)
}
lastMessage.unpin() //todo: uncomment when bot has permission to pin/unpin
}
public async getVotesByEmote(message: Message, guildId: string, requestId: string): Promise<Vote[]> {
}
/**
* gets votes for the movies without the NONE_OF_THAT votes
*/
public async getVotesByEmote(message: VoteMessage, guildId: string, requestId: string): Promise<Vote[]> {
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(`Number of items in emotes: ${Object.values(ValidVoteEmotes).length}`, { guildId, requestId })
for (let i = 0; i < Object.keys(ValidVoteEmotes).length / 2; i++) {
const emote = ValidVoteEmotes[i]
logger.debug(`Getting reaction for emote ${emote}`, { guildId, requestId })
const reaction = message.reactions.resolve(emote)
logger.debug(`Reaction for emote ${emote}: ${JSON.stringify(reaction, null, 2)}`, { guildId, requestId })
@ -205,19 +273,19 @@ export default class VoteController {
}
return votes
}
public async getEvent(guild: Guild, guildId: string, requestId: string): Promise<GuildScheduledEvent | null> {
public async getOpenPollEvent(guild: Guild, guildId: string, requestId: string): Promise<Maybe<GuildScheduledEvent>> {
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
logger.error("Could not find an open vote event.", { guildId, requestId })
return
}
return voteEvents[0]
}
public async updateEvent(voteEvent: GuildScheduledEvent, votes: Vote[], guild: Guild, guildId: string, requestId: string) {
public async updateOpenPollEventWithVoteResults(voteEvent: GuildScheduledEvent, votes: Vote[], guild: Guild, guildId: string, requestId: string) {
logger.info(`Updating event with movie ${votes[0].movie}.`, { guildId, requestId })
const options: GuildScheduledEventEditOptions<GuildScheduledEventStatus.Scheduled, GuildScheduledEventSetStatusArg<GuildScheduledEventStatus.Scheduled>> = {
name: votes[0].movie,
@ -227,9 +295,9 @@ export default class VoteController {
logger.info("Updating event.", { guildId, requestId })
voteEvent.edit(options)
}
public async sendVoteClosedMessage(event: GuildScheduledEvent, movie: string, guildId: string, requestId: string) {
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"
public async sendVoteClosedMessage(event: GuildScheduledEvent, movie: string, guildId: string, requestId: string): Promise<Message<boolean>> {
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 keine Uhrzeit"
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,
@ -238,13 +306,14 @@ export default class VoteController {
const announcementChannel = this.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
const errorMessageText = "Could not find announcement channel. Please fix!"
logger.error(errorMessageText, { guildId, requestId })
throw errorMessageText
}
announcementChannel.send(options)
return announcementChannel.send(options)
}
private extractMovieFromMessageByEmote(message: Message, emote: string): string {
const lines = message.cleanContent.split("\n")
private extractMovieFromMessageByEmote(voteMessage: VoteMessage, emote: string): string {
const lines = voteMessage.cleanContent.split("\n")
const emoteLines = lines.filter(line => line.includes(emote))
if (!emoteLines) {

View File

@ -1,5 +1,5 @@
import { Collection } from "@discordjs/collection"
import { Role } from "discord.js"
import { GuildScheduledEvent, Role, TextChannel } from "discord.js"
export type Maybe<T> = T | undefined | null
export interface Player {
@ -39,3 +39,10 @@ export interface JellyfinConfig {
collectionUser: string
}
export type PermissionLevel = "VIEWER" | "ADMIN" | "TEMPORARY"
export interface voteMessageInputInformation {
movies: string[],
startDate: Date,
event: GuildScheduledEvent,
announcementChannel: TextChannel,
pinAfterSending: boolean,
}

View File

@ -253,22 +253,22 @@ function isFormData(value: any): value is FormData {
export class ResponseError extends Error {
override name: "ResponseError" = "ResponseError";
constructor(public response: Response, msg?: string) {
super(msg);
constructor(public response: Response, errorMessage?: string) {
super(errorMessage);
}
}
export class FetchError extends Error {
override name: "FetchError" = "FetchError";
constructor(public cause: Error, msg?: string) {
super(msg);
constructor(public cause: Error, errorMessage?: string) {
super(errorMessage);
}
}
export class RequiredError extends Error {
override name: "RequiredError" = "RequiredError";
constructor(public field: string, msg?: string) {
super(msg);
constructor(public field: string, errorMessage?: string) {
super(errorMessage);
}
}

View File

@ -6,7 +6,7 @@ export const noGuildId = 'NoGuildId'
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(
@ -16,7 +16,8 @@ const logFormat = format.combine(
const consoleTransports = [
new transports.Console({
format: logFormat
format: logFormat,
silent: process.env.NODE_ENV === 'testing'
})
]
export const logger = createLogger({

View File

@ -0,0 +1,81 @@
import { Guild, GuildScheduledEvent, Message } from "discord.js"
import VoteController from "../../server/helper/vote.controller"
import { JellyfinHandler } from "../../server/jellyfin/handler"
import { ExtendedClient } from "../../server/structures/client"
import { Emoji, NONE_OF_THAT } from "../../server/constants"
import { isVoteMessage } from "../../server/helper/messageIdentifiers"
describe('vote controller - none_of_that functions', () => {
const testEventId = '1234321'
const testEventDate = new Date('2023-01-01')
const testGuildId = "888999888"
const testMovies = [
'Movie1',
'Movie2',
'Movie3',
'Movie4',
'Movie5',
]
const votesList = [
{ emote: Emoji.one, count: 1, movie: testMovies[0] },
{ emote: Emoji.two, count: 2, movie: testMovies[1] },
{ emote: Emoji.three, count: 3, movie: testMovies[2] },
{ emote: Emoji.four, count: 1, movie: testMovies[3] },
{ emote: Emoji.five, count: 1, movie: testMovies[4] },
{ emote: NONE_OF_THAT, count: 2, movie: NONE_OF_THAT },
]
const mockClient: ExtendedClient = <ExtendedClient><unknown>{
user: {
id: 'mockId'
}
}
const mockEvent: GuildScheduledEvent = <GuildScheduledEvent><unknown>{
scheduledStartAt: testEventDate,
id: testEventId,
guild: testGuildId
}
const mockJellyfinHandler: JellyfinHandler = <JellyfinHandler><unknown>{
getRandomMovieNames: jest.fn().mockReturnValue(["movie1"])
}
const votes = new VoteController(mockClient, mockJellyfinHandler)
const mockMessageContent = votes.createVoteMessageText(mockEvent, testMovies, testGuildId, "requestId")
test('sendVoteClosedMessage', async () => {
mockClient.getAnnouncementChannelForGuild = jest.fn().mockReturnValue({
send: jest.fn().mockImplementation((options: any) => {
return new Promise((resolve) => {
resolve(options)
})
})
})
const scheduledEvent: GuildScheduledEvent = <GuildScheduledEvent>{
scheduledStartAt: testEventDate,
guildId: testGuildId,
id: testEventId
}
const res = await votes.sendVoteClosedMessage(scheduledEvent, 'MovieNew', 'guild', 'request')
expect(res).toEqual({
allowedMentions: {
parse: ["roles"]
},
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('getVotesByEmote', async () => {
const mockMessage: Message = <Message><unknown>{
cleanContent: mockMessageContent,
reactions: {
resolve: jest.fn().mockImplementation((input: any) => {
return votesList.find(e => e.emote === input)
})
}
}
if (isVoteMessage(mockMessage)) {
const result = await votes.getVotesByEmote(mockMessage, 'guildId', 'requestId')
expect(result.length).toEqual(5)
expect(result).toEqual(votesList.filter(x => x.movie != NONE_OF_THAT))
}
})
})

View File

@ -1,9 +1,10 @@
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 { ExtendedClient } from "../../server/structures/client"
import { VoteMessage } from "../../server/helper/messageIdentifiers"
test('parse votes from vote message', () => {
import { GuildScheduledEvent, MessageReaction } from "discord.js"
test('parse votes from vote message', async () => {
const testMovies = [
'Movie1',
'Movie2',
@ -13,13 +14,18 @@ test('parse votes from vote message', () => {
]
const testEventId = '1234321'
const testEventDate = new Date('2023-01-01')
const testGuildId = "888999888"
const voteController: VoteController = new VoteController(<ExtendedClient>{}, <JellyfinHandler>{})
const testMessage = voteController.createVoteMessageText(testEventId, testEventDate, testMovies, "guildid", "requestId")
const mockEvent: GuildScheduledEvent = <GuildScheduledEvent><unknown>{
scheduledStartAt: testEventDate,
id: testEventId,
guild: testGuildId
}
const testMessage = voteController.createVoteMessageText(mockEvent, testMovies, testGuildId, "requestId")
const expectedResult: VoteMessageInfo = {
eventId: testEventId,
eventDate: testEventDate,
event: mockEvent,
votes: [
{ emote: Emoji.one, count: 1, movie: testMovies[0] },
{ emote: Emoji.two, count: 2, movie: testMovies[1] },
@ -30,8 +36,21 @@ test('parse votes from vote message', () => {
]
}
const msg: VoteMessage = <VoteMessage><unknown>{
const message: VoteMessage = <VoteMessage><unknown>{
cleanContent: testMessage,
guild: {
id: testGuildId,
scheduledEvents: {
fetch: jest.fn().mockImplementation((input: any) => {
if (input === testEventId)
return {
id: testEventId,
guild: testGuildId,
scheduledStartAt: testEventDate
}
})
}
},
reactions: {
cache: {
get: jest.fn().mockImplementation((input: any) => {
@ -45,11 +64,129 @@ test('parse votes from vote message', () => {
}
}
const result = voteController.parseVotesFromVoteMessage(msg, 'requestId')
const result = await voteController.parseVoteInfoFromVoteMessage(message, 'requestId')
console.log(JSON.stringify(result))
expect(Array.isArray(result)).toBe(false)
expect(result.eventId).toEqual(testEventId)
expect(result.eventDate).toEqual(testEventDate)
expect(result.event.id).toEqual(testEventId)
expect(result.event.scheduledStartAt).toEqual(testEventDate)
expect(result.votes.length).toEqual(expectedResult.votes.length)
expect(result).toEqual(expectedResult)
})
test('parse votes from vote message', () => {
const testMovies = [
'Movie1',
'Movie2',
'Movie3',
'Movie4',
'Movie5',
]
const testEventId = '1234321'
const testEventDate = new Date('2023-01-01')
const testGuildId = "888999888"
const voteController: VoteController = new VoteController(<ExtendedClient>{}, <JellyfinHandler>{})
const mockEvent: GuildScheduledEvent = <GuildScheduledEvent><unknown>{
scheduledStartAt: testEventDate,
id: testEventId,
guild: testGuildId
}
const testMessage = voteController.createVoteMessageText(mockEvent, testMovies, testGuildId, "requestId")
const result = voteController.parseGuildIdAndEventIdFromWholeMessage(testMessage)
expect(result).toEqual({ guildId: testGuildId, eventId: testEventId })
})
test.skip('handles complete none_of_that vote', () => {
const mockJellyfinHandler: JellyfinHandler = <JellyfinHandler><unknown>{
getRandomMovieNames: jest.fn().mockReturnValue(["movie1"])
}
const testMovies = [
'Movie1',
'Movie2',
'Movie3',
'Movie4',
'Movie5',
]
const testEventId = '1234321'
const testEventDate = new Date('2023-01-01')
const testGuildId = "888999888"
const mockClient: ExtendedClient = <ExtendedClient><unknown>{
user: {
id: 'mockId'
}
}
const voteController = new VoteController(mockClient, mockJellyfinHandler)
const mockEvent: GuildScheduledEvent = <GuildScheduledEvent><unknown>{
scheduledStartAt: testEventDate,
id: testEventId,
guild: testGuildId
}
const mockMessageContent = voteController.createVoteMessageText(mockEvent, testMovies, testGuildId, "requestId")
const reactedUponMessage: VoteMessage = <VoteMessage><unknown>{
cleanContent: mockMessageContent,
guild: {
id: 'id',
roles: {
resolve: jest.fn().mockReturnValue({
members: [{}, {}, {}, {}, {}]//content does not matter
})
},
scheduledEvents: {
fetch: jest.fn().mockReturnValue([
{
name: 'voting offen'
}
])
}
},
unpin: jest.fn().mockImplementation(() => {
}),
delete: jest.fn().mockImplementation(() => {
}),
reactions: {
resolve: jest.fn().mockImplementation((input: any) => {
console.log(JSON.stringify(input))
}),
cache: {
get: jest.fn().mockReturnValue({
users: {
cache: [
{
id: "mockId"//to filter out
},
{
id: "userId1"
},
{
id: "userId2"
},
{
id: "userId3"
}
]
}
})
}
}
}
const messageReaction: MessageReaction = <MessageReaction><unknown>{
message: reactedUponMessage
}
mockClient.getAnnouncementChannelForGuild = jest.fn().mockReturnValue({
messages: {
fetch: jest.fn().mockReturnValue([
reactedUponMessage
])
}
})
const res = voteController.handleNoneOfThatVote(messageReaction, reactedUponMessage, 'requestId', 'guildId')
})

View File

@ -10,6 +10,6 @@ function getTestDate(date: string): Date {
}
test('createDateStringFromEvent - correct formatting', () => {
expect(createDateStringFromEvent(getTestDate('01-01-2023 12:30'), "")).toEqual('heute um 12:30')
expect(createDateStringFromEvent(getTestDate('01-02-2023 12:30'), "")).toEqual('am Montag 02.01 um 12:30')
expect(createDateStringFromEvent(getTestDate('01-03-2023 12:30'), "")).toEqual('am Dienstag 03.01 um 12:30')
expect(createDateStringFromEvent(getTestDate('01-02-2023 12:30'), "")).toEqual('am Montag 02.01. um 12:30')
expect(createDateStringFromEvent(getTestDate('01-03-2023 12:30'), "")).toEqual('am Dienstag 03.01. um 12:30')
})

15
tests/testenv.js Normal file
View File

@ -0,0 +1,15 @@
process.env.CLIENT_ID = "CLIENT_ID"
process.env.SECRET = "SECRET"
process.env.BOT_TOKEN = "BOT_TOKEN"
process.env.WATCHER_ROLE = "WATCHER_ROLE"
process.env.ADMIN_ROLE = "ADMIN_ROLE"
process.env.CHANNEL_ID = "CHANNEL_ID"
process.env.WATCHPARTY_ANNOUNCEMENT_ROLE = "WATCHPARTY_ANNOUNCEMENT_ROLE"
process.env.YAVIN_JELLYFIN_URL = "YAVIN_JELLYFIN_URL"
process.env.YAVIN_COLLECTION_ID = "YAVIN_COLLECTION_ID"
process.env.YAVIN_COLLECTION_USER = "YAVIN_COLLECTION_USER"
process.env.YAVIN_TOKEN = "YAVIN_TOKEN"
process.env.TOKEN = "TOKEN"
process.env.JELLYFIN_USER = "JELLYFIN_USER"
process.env.JELLYFIN_COLLECTION_ID = "JELLYFIN_COLLECTION_ID"
process.env.JELLYFIN_URL = "JELLYFIN_URL"