Compare commits

..

No commits in common. "0d3c62c6ad34c38bf4eb95fd7c25bd703553c56c" and "3bd26a9d6cd29e13e7b7c9ee7fd32fef9465263c" have entirely different histories.

24 changed files with 1408 additions and 1415 deletions

View File

@ -1,7 +0,0 @@
root = true
[*]
indent_style = tab
tab_width = 4
[*.ts]
indent_style = tab
tab_width = 4

View File

@ -5,8 +5,8 @@ import { JellyfinHandler } from "./server/jellyfin/handler"
import { attachedImages } from "./server/assets/attachments" import { attachedImages } from "./server/assets/attachments"
const requestId = 'startup' const requestId = 'startup'
export const jellyfinHandler = new JellyfinHandler({ jellyfinToken: config.bot.workaround_token, jellyfinUrl: config.bot.jellyfin_url, movieCollectionId: config.bot.jf_collection_id, collectionUser: config.bot.jf_user }) export const jellyfinHandler = new JellyfinHandler({jellyfinToken: config.bot.workaround_token, jellyfinUrl: config.bot.jellyfin_url, movieCollectionId: config.bot.jf_collection_id, collectionUser: config.bot.jf_user})
export const yavinJellyfinHandler = new JellyfinHandler({ jellyfinToken: config.bot.yavin_jellyfin_token, jellyfinUrl: config.bot.yavin_jellyfin_url, movieCollectionId: config.bot.yavin_collection_id, collectionUser: config.bot.yavin_jellyfin_collection_user }) export const yavinJellyfinHandler = new JellyfinHandler({jellyfinToken: config.bot.yavin_jellyfin_token, jellyfinUrl: config.bot.yavin_jellyfin_url, movieCollectionId: config.bot.yavin_collection_id, collectionUser: config.bot.yavin_jellyfin_collection_user})
export const client = new ExtendedClient(jellyfinHandler) export const client = new ExtendedClient(jellyfinHandler)

View File

@ -13,22 +13,22 @@ export default new Command({
options: [{ options: [{
name: "typ", name: "typ",
type: ApplicationCommandOptionType.String, type: ApplicationCommandOptionType.String,
description: "Was für ein announcement?", description:"Was für ein announcement?",
choices: [{ name: "initial", value: "initial" }, { name: "votepls", value: "votepls" }, { name: "cancel", value: "cancel" }], choices: [{name: "initial", value:"initial"},{name: "votepls", value:"votepls"},{name: "cancel", value:"cancel"}],
required: true required: true
}], }],
run: async (interaction: RunOptions) => { run: async (interaction: RunOptions) => {
const command = interaction.interaction const command = interaction.interaction
const requestId = uuid() const requestId = uuid()
if (!command.guildId) { if(!command.guildId) {
logger.error("COMMAND DOES NOT HAVE A GUILD ID; CANCELLING!!!", { requestId }) logger.error("COMMAND DOES NOT HAVE A GUILD ID; CANCELLING!!!", {requestId})
return return
} }
const guildId = command.guildId const guildId = command.guildId
const announcementType = command.options.data.find(option => option.name.includes("typ")) const announcementType = command.options.data.find(option => option.name.includes("typ"))
logger.info(`Got command for announcing ${announcementType?.value}!`, { guildId, requestId }) logger.info(`Got command for announcing ${announcementType?.value}!`, { guildId, requestId })
if (!announcementType) { if(!announcementType) {
logger.error("Did not get an announcement type!", { guildId, requestId }) logger.error("Did not get an announcement type!", { guildId, requestId })
return return
} }
@ -40,7 +40,7 @@ export default new Command({
logger.info(`User ${command.member.displayName} seems to be admin`) logger.info(`User ${command.member.displayName} seems to be admin`)
} }
if ((<string>announcementType.value).includes("initial")) { if((<string>announcementType.value).includes("initial")) {
sendInitialAnnouncement(guildId, requestId) sendInitialAnnouncement(guildId, requestId)
command.followUp("Ist rausgeschickt!") command.followUp("Ist rausgeschickt!")
} else { } else {
@ -56,7 +56,7 @@ function isAdmin(member: GuildMember): boolean {
async function sendInitialAnnouncement(guildId: string, requestId: string): Promise<void> { async function sendInitialAnnouncement(guildId: string, requestId: string): Promise<void> {
logger.info("Sending initial announcement") logger.info("Sending initial announcement")
const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(guildId) const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(guildId)
if (!announcementChannel) { if(!announcementChannel) {
logger.error("Could not find announcement channel. Aborting", { guildId, requestId }) logger.error("Could not find announcement channel. Aborting", { guildId, requestId })
return return
} }
@ -96,7 +96,7 @@ export async function manageAnnouncementRoles(guild: Guild, reaction: MessageRea
const allUsers = (await guild.members.fetch()) const allUsers = (await guild.members.fetch())
const usersWhoHaveRole: GuildMember[] = allUsers const usersWhoHaveRole: GuildMember[] = allUsers
.filter(member => member.roles.cache .filter(member=> member.roles.cache
.find(role => role.id === config.bot.announcement_role) !== undefined) .find(role => role.id === config.bot.announcement_role) !== undefined)
.map(member => member) .map(member => member)
@ -105,15 +105,15 @@ export async function manageAnnouncementRoles(guild: Guild, reaction: MessageRea
const usersWhoDontHaveRole: GuildMember[] = allUsers const usersWhoDontHaveRole: GuildMember[] = allUsers
.filter(member => member.roles.cache .filter(member => member.roles.cache
.find(role => role.id === config.bot.announcement_role) === undefined) .find(role=> role.id === config.bot.announcement_role) === undefined)
.map(member => member) .map(member => member)
const usersWhoNeedRole: GuildMember[] = usersWhoDontHaveRole const usersWhoNeedRole: GuildMember[] = usersWhoDontHaveRole
.filter(userWhoNeeds => usersWhoWantRole.map(wanter => wanter.id).includes(userWhoNeeds.id)) .filter(userWhoNeeds => usersWhoWantRole.map(wanter => wanter.id).includes(userWhoNeeds.id))
logger.debug(`Theses users will get the role removed: ${JSON.stringify(usersWhoNeedRoleRevoked)}`, { guildId, requestId }) logger.debug(`Theses users will get the role removed: ${JSON.stringify(usersWhoNeedRoleRevoked)}`, {guildId, requestId})
logger.debug(`Theses users will get the role added: ${JSON.stringify(usersWhoNeedRole)}`, { guildId, requestId }) logger.debug(`Theses users will get the role added: ${JSON.stringify(usersWhoNeedRole)}`, {guildId, requestId})
usersWhoNeedRoleRevoked.forEach(user => user.roles.remove(announcementRole)) usersWhoNeedRoleRevoked.forEach(user => user.roles.remove(announcementRole))
usersWhoNeedRole.forEach(user => user.roles.add(announcementRole)) usersWhoNeedRole.forEach(user => user.roles.add(announcementRole))

View File

@ -20,7 +20,7 @@ export async function execute(event: GuildScheduledEvent) {
if (event.description.includes("!wp")) { if (event.description.includes("!wp")) {
logger.info("Got manual create event of watchparty event!", { guildId, requestId }) logger.info("Got manual create event of watchparty event!", { guildId, requestId })
if (event.description.includes("!private")) { if(event.description.includes("!private")) {
logger.info("Event description contains \"!private\". Won't announce.", { guildId, requestId }) logger.info("Event description contains \"!private\". Won't announce.", { guildId, requestId })
return return
} }

View File

@ -28,14 +28,14 @@ export async function execute(event: GuildScheduledEvent) {
logger.debug(`Movies: ${JSON.stringify(movies)}`, { guildId: event.guildId, requestId }) logger.debug(`Movies: ${JSON.stringify(movies)}`, { guildId: event.guildId, requestId })
const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(event.guildId) const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(event.guildId)
if (!announcementChannel) { if(!announcementChannel) {
logger.error("Could not find announcement channel. Aborting", { guildId: event.guildId, requestId }) logger.error("Could not find announcement channel. Aborting", { guildId: event.guildId, requestId })
return return
} }
logger.debug(`Found channel ${JSON.stringify(announcementChannel, null, 2)}`, { guildId: event.guildId, requestId }) logger.debug(`Found channel ${JSON.stringify(announcementChannel, null, 2)}`, { guildId: event.guildId, requestId })
if (!event.scheduledStartAt) { if(!event.scheduledStartAt) {
logger.info("EVENT DOES NOT HAVE STARTDATE; CANCELLING", { guildId: event.guildId, requestId }) logger.info("EVENT DOES NOT HAVE STARTDATE; CANCELLING", {guildId: event.guildId, requestId})
return return
} }
let message = `[Abstimmung] für https://discord.com/events/${event.guildId}/${event.id}\n<@&${config.bot.announcement_role}> Es gibt eine neue Abstimmung für die nächste Watchparty ${createDateStringFromEvent(event, event.guildId, requestId)}! Stimme hierunter für den nächsten Film ab!\n` let message = `[Abstimmung] für https://discord.com/events/${event.guildId}/${event.id}\n<@&${config.bot.announcement_role}> Es gibt eine neue Abstimmung für die nächste Watchparty ${createDateStringFromEvent(event, event.guildId, requestId)}! Stimme hierunter für den nächsten Film ab!\n`
@ -46,7 +46,7 @@ export async function execute(event: GuildScheduledEvent) {
message = message.concat(NONE_OF_THAT).concat(": Wenn dir nichts davon gefällt.") message = message.concat(NONE_OF_THAT).concat(": Wenn dir nichts davon gefällt.")
const options: MessageCreateOptions = { const options: MessageCreateOptions = {
allowedMentions: { parse: ["roles"] }, allowedMentions: { parse: ["roles"]},
content: message, content: message,
} }

View File

@ -27,7 +27,7 @@ export async function execute(oldEvent: GuildScheduledEvent, newEvent: GuildSche
const wpAnnouncements = (await announcementChannel.messages.fetch()).filter(message => !message.cleanContent.includes("[initial]")) const wpAnnouncements = (await announcementChannel.messages.fetch()).filter(message => !message.cleanContent.includes("[initial]"))
const announcementsWithoutEvent = filterAnnouncementsByPendingWPs(wpAnnouncements, events) const announcementsWithoutEvent = filterAnnouncementsByPendingWPs(wpAnnouncements, events)
logger.info(`Deleting ${announcementsWithoutEvent.length} announcements.`, { guildId, requestId }) logger.info(`Deleting ${announcementsWithoutEvent.length} announcements.`, {guildId, requestId})
announcementsWithoutEvent.forEach(message => message.delete()) announcementsWithoutEvent.forEach(message => message.delete())
} }
} catch (error) { } catch (error) {
@ -44,7 +44,7 @@ function filterAnnouncementsByPendingWPs(messages: Collection<string, Message<tr
foundEventForMessage = true foundEventForMessage = true
} }
} }
if (!foundEventForMessage) { if(!foundEventForMessage){
filteredMessages.push(message) filteredMessages.push(message)
} }
} }

View File

@ -12,7 +12,7 @@ export async function execute(oldState: VoiceState, newState: VoiceState) {
try { try {
logger.info(JSON.stringify(newState, null, 2)) logger.info(JSON.stringify(newState, null, 2))
//ignore events like mute/unmute //ignore events like mute/unmute
if (newState.channel?.id === oldState.channel?.id) { if(newState.channel?.id === oldState.channel?.id) {
logger.info("Not handling VoiceState event because channelid of old and new was the same (i.e. mute/unmute event)") logger.info("Not handling VoiceState event because channelid of old and new was the same (i.e. mute/unmute event)")
return return
} }
@ -21,25 +21,25 @@ export async function execute(oldState: VoiceState, newState: VoiceState) {
.filter((key) => key.description?.toLowerCase().includes("!wp") && key.isActive()) .filter((key) => key.description?.toLowerCase().includes("!wp") && key.isActive())
.map((key) => key) .map((key) => key)
const scheduledEventUsers = (await Promise.all(scheduledEvents.map(event => event.fetchSubscribers({ withMember: true })))) const scheduledEventUsers = (await Promise.all(scheduledEvents.map(event => event.fetchSubscribers({withMember: true}))))
//Dont handle users, that are already subscribed to the event. We only want to handle unsubscribed users here //Dont handle users, that are already subscribed to the event. We only want to handle unsubscribed users here
let userFound = false; let userFound = false;
scheduledEventUsers.forEach(collection => { scheduledEventUsers.forEach(collection => {
collection.each(key => { collection.each(key => {
logger.info(JSON.stringify(key, null, 2)) logger.info(JSON.stringify(key, null, 2))
if (key.member.user.id === newState.member?.user.id) if(key.member.user.id === newState.member?.user.id)
userFound = true; userFound = true;
}) })
}) })
if (userFound) { if(userFound) {
logger.info(`Not handling VoiceState event because user was already subscribed and got an account from there. User: ${JSON.stringify(newState.member, null, 2)}`) logger.info(`Not handling VoiceState event because user was already subscribed and got an account from there. User: ${JSON.stringify(newState.member, null, 2)}`)
return return
} }
if (scheduledEvents.find(event => event.channelId === newState.channelId)) { if (scheduledEvents.find(event => event.channelId === newState.channelId)) {
if (newState.member) { if(newState.member){
logger.info("YO! Da ist jemand dem Channel mit dem Event beigetreten, ich kümmer mich mal um nen Account!") logger.info("YO! Da ist jemand dem Channel mit dem Event beigetreten, ich kümmer mich mal um nen Account!")
const result = await jellyfinHandler.upsertUser(newState.member, "TEMPORARY", uuid()) const result = await jellyfinHandler.upsertUser(newState.member, "TEMPORARY", uuid())
if (result === UserUpsertResult.created) { if (result === UserUpsertResult.created) {
@ -53,7 +53,7 @@ export async function execute(oldState: VoiceState, newState: VoiceState) {
} else { } else {
logger.info("VoiceState channelId was not the id of any channel with events") logger.info("VoiceState channelId was not the id of any channel with events")
} }
} catch (error) { }catch(error){
logger.error(error) logger.error(error)
} }
} }

View File

@ -1,23 +1,23 @@
import { format, isToday, toDate } from "date-fns"; import { format, isToday, toDate } from "date-fns";
import { utcToZonedTime } from "date-fns-tz" import {utcToZonedTime} from "date-fns-tz"
import { GuildScheduledEvent } from "discord.js"; 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";
export function createDateStringFromEvent(event: GuildScheduledEvent, requestId: string, guildId?: string): string { export function createDateStringFromEvent(event: GuildScheduledEvent, requestId: string, guildId?: string): string {
if (!event.scheduledStartAt) { if(!event.scheduledStartAt) {
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"`
} }
const timeZone = 'Europe/Berlin' const timeZone = 'Europe/Berlin'
const zonedDateTime = utcToZonedTime(event.scheduledStartAt, timeZone) const zonedDateTime = utcToZonedTime(event.scheduledStartAt, timeZone)
const time = format(zonedDateTime, "HH:mm", { locale: de }) const time = format(zonedDateTime, "HH:mm", {locale: de})
if (isToday(zonedDateTime)) { if(isToday(zonedDateTime)) {
return `heute um ${time}` 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}` return `am ${date} um ${time}`
} }

View File

@ -64,7 +64,7 @@ export class JellyfinHandler {
logger.debug(JSON.stringify(req), { requestId, guildId }) logger.debug(JSON.stringify(req), { requestId, guildId })
const createResult = await this.userApi.createUserByName(req) const createResult = await this.userApi.createUserByName(req)
if (createResult) { if (createResult) {
if (createResult.policy) { if(createResult.policy) {
this.setUserPermissions(createResult, requestId, guildId) this.setUserPermissions(createResult, requestId, guildId)
} }
(await discordUser.createDM()).send(`Ich hab dir mal nen Account angelegt :)\nDein Username ist ${createResult.name}, dein Password ist "${req.createUserByNameRequest.password}"!`) (await discordUser.createDM()).send(`Ich hab dir mal nen Account angelegt :)\nDein Username ist ${createResult.name}, dein Password ist "${req.createUserByNameRequest.password}"!`)
@ -74,8 +74,8 @@ export class JellyfinHandler {
} }
public async setUserPermissions(user: UserDto, requestId: string, guildId?: string) { public async setUserPermissions(user: UserDto, requestId: string, guildId?: string) {
if (!user.policy || !user.id) { if(!user.policy || !user.id) {
logger.error(`Cannot update user policy. User ${user.name} has no policy to modify`, { guildId, requestId }) logger.error(`Cannot update user policy. User ${user.name} has no policy to modify`, {guildId, requestId})
return return
} }
user.policy.enableVideoPlaybackTranscoding = false user.policy.enableVideoPlaybackTranscoding = false
@ -273,7 +273,7 @@ export class JellyfinHandler {
let movieCount = 0 let movieCount = 0
let movieNames: string[] let movieNames: string[]
do { do {
movieNames = (await this.getRandomMovies(count, guildId, requestId)).filter(movie => movie.name && movie.name.length > 0).map(movie => <string>movie.name) movieNames = (await this.getRandomMovies(count, guildId, requestId)).filter(movie => movie.name && movie.name.length > 0).map(movie => <string> movie.name)
movieCount = movieNames.length movieCount = movieNames.length
} while (movieCount < count) } while (movieCount < count)
return movieNames return movieNames

View File

@ -29,7 +29,7 @@ export interface ConfigurationParameters {
} }
export class Configuration { export class Configuration {
constructor(private configuration: ConfigurationParameters = {}) { } constructor(private configuration: ConfigurationParameters = {}) {}
set config(configuration: Configuration) { set config(configuration: Configuration) {
this.configuration = configuration; this.configuration = configuration;
@ -393,7 +393,7 @@ export interface ResponseTransformer<T> {
} }
export class JSONApiResponse<T> { export class JSONApiResponse<T> {
constructor(public raw: Response, private transformer: ResponseTransformer<T> = (jsonValue: any) => jsonValue) { } constructor(public raw: Response, private transformer: ResponseTransformer<T> = (jsonValue: any) => jsonValue) {}
async value(): Promise<T> { async value(): Promise<T> {
return this.transformer(await this.raw.json()); return this.transformer(await this.raw.json());
@ -401,7 +401,7 @@ export class JSONApiResponse<T> {
} }
export class VoidApiResponse { export class VoidApiResponse {
constructor(public raw: Response) { } constructor(public raw: Response) {}
async value(): Promise<void> { async value(): Promise<void> {
return undefined; return undefined;
@ -409,7 +409,7 @@ export class VoidApiResponse {
} }
export class BlobApiResponse { export class BlobApiResponse {
constructor(public raw: Response) { } constructor(public raw: Response) {}
async value(): Promise<Blob> { async value(): Promise<Blob> {
return await this.raw.blob(); return await this.raw.blob();
@ -417,7 +417,7 @@ export class BlobApiResponse {
} }
export class TextApiResponse { export class TextApiResponse {
constructor(public raw: Response) { } constructor(public raw: Response) {}
async value(): Promise<string> { async value(): Promise<string> {
return await this.raw.text(); return await this.raw.text();

View File

@ -130,7 +130,7 @@ export class ExtendedClient extends Client {
for (const guild of guilds.values()) { for (const guild of guilds.values()) {
logger.info("Starting background task for announcement role", { guildId: guild.id }) logger.info("Starting background task for announcement role", { guildId: guild.id })
const textChannel: Maybe<TextChannel> = this.getAnnouncementChannelForGuild(guild.id) const textChannel: Maybe<TextChannel> = this.getAnnouncementChannelForGuild(guild.id)
if (!textChannel) { if(!textChannel) {
logger.error("Could not find announcement channel. Aborting", { guildId: guild.id }) logger.error("Could not find announcement channel. Aborting", { guildId: guild.id })
return return
} }
@ -174,7 +174,7 @@ export class ExtendedClient extends Client {
} }
private async startPollCloseBackgroundTasks() { private async startPollCloseBackgroundTasks() {
for (const guild of this.guilds.cache) { for(const guild of this.guilds.cache) {
this.pollCloseBackgroundTasks.set(guild[1].id, schedule("0 * * * * *", () => checkForPollsToClose(guild[1]))) this.pollCloseBackgroundTasks.set(guild[1].id, schedule("0 * * * * *", () => checkForPollsToClose(guild[1])))
} }
} }