announcements #18
121
server/commands/announce.ts
Normal file
121
server/commands/announce.ts
Normal file
@ -0,0 +1,121 @@
|
||||
import { ApplicationCommandOptionType, Guild, GuildMember, Message, MessageCreateOptions, MessageReaction, Role, TextChannel, User } from 'discord.js'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import { client } from '../..'
|
||||
import { config } from '../configuration'
|
||||
import { Maybe } from '../interfaces'
|
||||
import { logger } from '../logger'
|
||||
import { Command } from '../structures/command'
|
||||
import { RunOptions } from '../types/commandTypes'
|
||||
|
||||
export default new Command({
|
||||
name: 'announce',
|
||||
description: 'Neues announcement im announcement Channel an alle senden.',
|
||||
options: [{
|
||||
name: "typ",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
description:"Was für ein announcement?",
|
||||
choices: [{name: "initial", value:"initial"},{name: "votepls", value:"votepls"},{name: "cancel", value:"cancel"}],
|
||||
required: true
|
||||
}],
|
||||
run: async (interaction: RunOptions) => {
|
||||
const command = interaction.interaction
|
||||
const requestId = uuid()
|
||||
if(!command.guildId) {
|
||||
logger.error("COMMAND DOES NOT HAVE A GUILD ID; CANCELLING!!!", {requestId})
|
||||
return
|
||||
}
|
||||
const guildId = command.guildId
|
||||
const announcementType = command.options.data.find(option => option.name.includes("typ"))
|
||||
logger.info(`Got command for announcing ${announcementType?.value}!`, { guildId, requestId })
|
||||
|
||||
if(!announcementType) {
|
||||
logger.error("Did not get an announcement type!", { guildId, requestId })
|
||||
return
|
||||
}
|
||||
|
||||
if (!isAdmin(command.member)) {
|
||||
logger.info(`Announcement was requested by ${command.member.displayName} but they are not an admin! Not sending announcement.`, { guildId, requestId })
|
||||
return
|
||||
} else {
|
||||
logger.info(`User ${command.member.displayName} seems to be admin`)
|
||||
}
|
||||
|
||||
if((<string>announcementType.value).includes("initial")) {
|
||||
sendInitialAnnouncement(guildId, requestId)
|
||||
command.followUp("Ist rausgeschickt!")
|
||||
} else {
|
||||
command.followUp(`${announcementType.value} ist aktuell noch nicht implementiert`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function isAdmin(member: GuildMember): boolean {
|
||||
return member.roles.cache.find((role) => role.id === config.bot.jf_admin_role) !== undefined
|
||||
}
|
||||
|
||||
async function sendInitialAnnouncement(guildId: string, requestId: string): Promise<void> {
|
||||
logger.info("Sending initial announcement")
|
||||
const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(guildId)
|
||||
if(!announcementChannel) {
|
||||
logger.error("Could not find announcement channel. Aborting", { guildId, requestId })
|
||||
return
|
||||
}
|
||||
|
||||
const currentPinnedAnnouncementMessages = (await announcementChannel.messages.fetchPinned()).filter(message => message.cleanContent.includes("[initial]"))
|
||||
currentPinnedAnnouncementMessages.forEach(async (message) => await message.unpin())
|
||||
currentPinnedAnnouncementMessages.forEach(message => message.delete())
|
||||
|
||||
const body = `[initial] Hey! @everyone! Hier ist der Watchparty Bot vom Hartzarett.
|
||||
|
||||
Wir machen in Zukunft regelmäßig Watchparties! Falls du mitmachen möchtest, reagiere einfach auf diesen Post mit 🎫, dann bekommst du automatisch eine Rolle zugewiesen und wirst benachrichtigt sobald es in der Zukunft weitere Watchparties und Filme zum abstimmen gibt.
|
||||
|
||||
Für eine Erklärung wie das alles funktioniert mach einfach /mitgucken für eine lange Erklärung am Stück oder /guides wenn du auswählen möchtest wozu du Infos bekommst.`
|
||||
|
||||
const options: MessageCreateOptions = {
|
||||
allowedMentions: { parse: ['everyone'] },
|
||||
content: body
|
||||
}
|
||||
const message: Message<true> = await announcementChannel.send(options)
|
||||
await message.react("🎫")
|
||||
await message.pin()
|
||||
|
||||
}
|
||||
|
||||
export async function manageAnnouncementRoles(guild: Guild, reaction: MessageReaction, requestId: string) {
|
||||
const guildId = guild.id
|
||||
logger.info("Managing roles", { guildId, requestId })
|
||||
|
||||
const announcementRole: Role | undefined = (await guild.roles.fetch()).find(role => role.id === config.bot.announcement_role)
|
||||
if (!announcementRole) {
|
||||
logger.error(`Could not find announcement role! Aborting! Was looking for role with id: ${config.bot.announcement_role}`, { guildId, requestId })
|
||||
return
|
||||
}
|
||||
|
||||
const usersWhoWantRole: User[] = (await reaction.users.fetch()).filter(user => !user.bot).map(user => user)
|
||||
|
||||
const allUsers = (await guild.members.fetch())
|
||||
|
||||
const usersWhoHaveRole: GuildMember[] = allUsers
|
||||
.filter(member=> member.roles.cache
|
||||
.find(role => role.id === config.bot.announcement_role) !== undefined)
|
||||
.map(member => member)
|
||||
|
||||
const usersWhoNeedRoleRevoked: GuildMember[] = usersWhoHaveRole
|
||||
.filter(userWhoHas => !usersWhoWantRole.map(wanter => wanter.id).includes(userWhoHas.id))
|
||||
|
||||
const usersWhoDontHaveRole: GuildMember[] = allUsers
|
||||
.filter(member => member.roles.cache
|
||||
.find(role=> role.id === config.bot.announcement_role) === undefined)
|
||||
.map(member => member)
|
||||
|
||||
const usersWhoNeedRole: GuildMember[] = usersWhoDontHaveRole
|
||||
.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 added: ${JSON.stringify(usersWhoNeedRole)}`, {guildId, requestId})
|
||||
|
||||
usersWhoNeedRoleRevoked.forEach(user => user.roles.remove(announcementRole))
|
||||
usersWhoNeedRole.forEach(user => user.roles.add(announcementRole))
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { Guild, GuildScheduledEvent, GuildScheduledEventEditOptions, GuildScheduledEventSetStatusArg, GuildScheduledEventStatus, Message, MessageEditOptions, TextChannel } from 'discord.js'
|
||||
import { Guild, GuildScheduledEvent, GuildScheduledEventEditOptions, GuildScheduledEventSetStatusArg, GuildScheduledEventStatus, Message, MessageCreateOptions, TextChannel } from 'discord.js'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import { client } from '../..'
|
||||
import { config } from '../configuration'
|
||||
import { Emotes } from '../events/guildScheduledEventCreate'
|
||||
import { logger } from '../logger'
|
||||
import { Command } from '../structures/command'
|
||||
import { RunOptions } from '../types/commandTypes'
|
||||
import { client } from '../..'
|
||||
import { format } from 'date-fns'
|
||||
import { Maybe } from '../interfaces'
|
||||
|
||||
export default new Command({
|
||||
name: 'closepoll',
|
||||
@ -14,14 +16,14 @@ export default new Command({
|
||||
run: async (interaction: RunOptions) => {
|
||||
const command = interaction.interaction
|
||||
const requestId = uuid()
|
||||
const guildId = command.guildId!
|
||||
logger.info("Got command for closing poll!", { guildId, requestId })
|
||||
if (!command.guild) {
|
||||
logger.error("No guild found in interaction. Cancelling closing request", { guildId, requestId })
|
||||
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)
|
||||
}
|
||||
@ -31,10 +33,14 @@ export async function closePoll(guild: Guild, requestId: string) {
|
||||
const guildId = guild.id
|
||||
logger.info("stopping poll", { guildId, requestId })
|
||||
|
||||
const announcementChannel: TextChannel = client.getAnnouncementChannelForGuild(guildId)
|
||||
const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(guildId)
|
||||
if(!announcementChannel) {
|
||||
logger.error("Could not find the textchannel. Unable to close poll.", { guildId, requestId })
|
||||
return
|
||||
}
|
||||
|
||||
const messages: Message<true>[] = (await announcementChannel.messages.fetch()) //todo: fetch only pinned messages
|
||||
.map((value, _) => value)
|
||||
.map((value) => value)
|
||||
.filter(message => !message.cleanContent.includes("[Abstimmung beendet]") && message.cleanContent.includes("[Abstimmung]"))
|
||||
.sort((a, b) => b.createdTimestamp - a.createdTimestamp)
|
||||
|
||||
@ -49,42 +55,42 @@ export async function closePoll(guild: Guild, requestId: string) {
|
||||
|
||||
logger.debug(`Last message: ${JSON.stringify(lastMessage, null, 2)}`, { guildId, requestId })
|
||||
|
||||
|
||||
const votes = await (await getVotesByEmote(lastMessage, guildId, requestId))
|
||||
.sort((a, b) => b.count - a.count)
|
||||
|
||||
.sort((a, b) => b.count - a.count)
|
||||
|
||||
logger.debug(`votes: ${JSON.stringify(votes, null, 2)}`, { guildId, requestId })
|
||||
|
||||
updateEvent(votes, guild!, guildId, requestId)
|
||||
updateMessage(votes[0].movie, lastMessage, guildId, requestId)
|
||||
|
||||
kenobi marked this conversation as resolved
|
||||
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)
|
||||
}
|
||||
|
||||
//lastMessage.unpin() //todo: uncomment when bot has permission to pin/unpin
|
||||
}
|
||||
|
||||
async function updateMessage(movie: string, message: Message, guildId: string, requestId: string) {
|
||||
const body = `[Abstimmung beendet] Gewonnen hat: ${movie}`
|
||||
.concat(message.cleanContent.substring("[Abstimmung]".length))
|
||||
|
||||
const options: MessageEditOptions = {
|
||||
async function 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"
|
||||
const body = `[Abstimmung beendet] <@&${config.bot.announcement_role}> Wir gucken ${movie} am ${date} um ${time}`
|
||||
const options: MessageCreateOptions = {
|
||||
content: body,
|
||||
allowedMentions: { parse: ["roles"] }
|
||||
}
|
||||
logger.info("Updating message.", { guildId, requestId })
|
||||
message.edit(options)
|
||||
|
||||
}
|
||||
|
||||
async function updateEvent(votes: Vote[], guild: Guild, guildId: string, requestId: string) {
|
||||
logger.info(`Updating event with movie ${votes[0].movie}.`, { guildId, requestId })
|
||||
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 })
|
||||
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)
|
||||
}
|
||||
|
||||
const voteEvent: GuildScheduledEvent<GuildScheduledEventStatus> = voteEvents[0]
|
||||
async function updateEvent(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,
|
||||
description: `!wp\nNummer 2: ${votes[1].movie} mit ${votes[1].count - 1} Stimmen\nNummer 3: ${votes[2].movie} mit ${votes[2].count - 1} Stimmen`
|
||||
@ -94,6 +100,19 @@ async function updateEvent(votes: Vote[], guild: Guild, guildId: string, request
|
||||
voteEvent.edit(options)
|
||||
}
|
||||
|
||||
async function getEvent(guild: Guild, guildId: string, requestId: string): Promise<GuildScheduledEvent | null> {
|
||||
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]
|
||||
}
|
||||
|
||||
type Vote = {
|
||||
emote: string, //todo habs nicht hinbekommen hier Emotes zu nutzen
|
||||
count: number,
|
||||
@ -109,14 +128,14 @@ async function getVotesByEmote(message: Message, guildId: string, requestId: str
|
||||
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, guildId, requestId) }
|
||||
const vote: Vote = { emote: emote, count: reaction.count, movie: extractMovieFromMessageByEmote(message, emote) }
|
||||
votes.push(vote)
|
||||
}
|
||||
}
|
||||
return votes
|
||||
}
|
||||
|
||||
function extractMovieFromMessageByEmote(message: Message, emote: string, guildId: string, requestId: string): string {
|
||||
function extractMovieFromMessageByEmote(message: Message, emote: string): string {
|
||||
const lines = message.cleanContent.split("\n")
|
||||
const emoteLines = lines.filter(line => line.includes(emote))
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { ApplicationCommandOptionType, BurstHandlerMajorIdKey } from 'discord.js'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import { jellyfinHandler } from "../.."
|
||||
import { Command } from '../structures/command'
|
||||
import { RunOptions } from '../types/commandTypes'
|
||||
import { jellyfinHandler } from "../.."
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
export default new Command({
|
||||
name: 'passwort_reset',
|
||||
|
@ -1,5 +1,4 @@
|
||||
import dotenv from "dotenv"
|
||||
import { AddListingProviderRequestToJSON } from "./jellyfin"
|
||||
dotenv.config()
|
||||
|
||||
interface options {
|
||||
@ -23,6 +22,7 @@ export interface Config {
|
||||
workaround_token: string
|
||||
watcher_role: string
|
||||
jf_admin_role: string
|
||||
announcement_role: string
|
||||
announcement_channel_id: string
|
||||
jf_collection_id: string
|
||||
}
|
||||
@ -57,6 +57,7 @@ export const config: Config = {
|
||||
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 ?? "",
|
||||
jf_collection_id: process.env.JELLYFIN_COLLECTION_ID ?? ""
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ export async function execute(oldMember: GuildMember, newMember: GuildMember) {
|
||||
try {
|
||||
const requestId = uuid()
|
||||
const changedRoles: ChangedRoles = filterRolesFromMemberUpdate(oldMember, newMember)
|
||||
const triggerRoleIds: Collection<string, PermissionLevel> = getGuildSpecificTriggerRoleId(oldMember.guild.id)
|
||||
const triggerRoleIds: Collection<string, PermissionLevel> = getGuildSpecificTriggerRoleId()
|
||||
|
||||
triggerRoleIds.forEach((level, key) => {
|
||||
const addedRoleMatches = changedRoles.addedRoles.find(aRole => aRole.id === key)
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { GuildScheduledEvent, Message, TextChannel } from "discord.js";
|
||||
import { addDays, format, isAfter } from "date-fns";
|
||||
import toDate from "date-fns/fp/toDate";
|
||||
import { GuildScheduledEvent, Message, MessageCreateOptions, TextChannel } from "discord.js";
|
||||
import { ScheduledTask, schedule } from "node-cron";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import { client, jellyfinHandler } from "../..";
|
||||
import { closePoll } from "../commands/closepoll";
|
||||
import { config } from "../configuration";
|
||||
import { logger } from "../logger";
|
||||
import toDate from "date-fns/fp/toDate";
|
||||
import { addDays, isAfter, isBefore } from "date-fns";
|
||||
import { Maybe } from "../interfaces";
|
||||
|
||||
|
||||
export const name = 'guildScheduledEventCreate'
|
||||
@ -28,16 +29,31 @@ export async function execute(event: GuildScheduledEvent) {
|
||||
logger.info(`Got ${movies.length} random movies. Creating voting`, { guildId: event.guildId, requestId })
|
||||
logger.debug(`Movies: ${JSON.stringify(movies.map(movie => movie.name))}`, { guildId: event.guildId, requestId })
|
||||
|
||||
const announcementChannel: TextChannel = client.getAnnouncementChannelForGuild(event.guildId)
|
||||
const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(event.guildId)
|
||||
if(!announcementChannel) {
|
||||
logger.error("Could not find announcement channel. Aborting", { guildId: event.guildId, requestId })
|
||||
return
|
||||
}
|
||||
logger.debug(`Found channel ${JSON.stringify(announcementChannel, null, 2)}`, { guildId: event.guildId, requestId })
|
||||
|
||||
let message = "[Abstimmung]\nEs gibt eine neue Abstimmung für die nächste Watchparty! Stimme hierunter für den nächsten Film ab!\n"
|
||||
if(!event.scheduledStartAt) {
|
||||
logger.info("EVENT DOES NOT HAVE STARTDATE; CANCELLING", {guildId: event.guildId, requestId})
|
||||
return
|
||||
}
|
||||
const date = format(event.scheduledStartAt, "dd.MM")
|
||||
const time = format(event.scheduledStartAt, "HH:mm")
|
||||
let message = `[Abstimmung]\n<@&${config.bot.announcement_role}> Es gibt eine neue Abstimmung für die nächste Watchparty am ${date} um ${time}}! 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].name!).concat("\n")
|
||||
message = message.concat(Emotes[i]).concat(": ").concat(movies[i].name ?? "Film hatte keinen Namen :(").concat("\n")
|
||||
}
|
||||
|
||||
const sentMessage: Message<true> = await (await announcementChannel.fetch()).send(message)
|
||||
const options: MessageCreateOptions = {
|
||||
allowedMentions: { parse: ["roles"]},
|
||||
content: message
|
||||
}
|
||||
|
||||
const sentMessage: Message<true> = await (await announcementChannel.fetch()).send(options)
|
||||
|
||||
for (let i = 0; i < movies.length; i++) {
|
||||
sentMessage.react(Emotes[i])
|
||||
@ -61,7 +77,7 @@ async function checkForPollsToClose(event: GuildScheduledEvent): Promise<void> {
|
||||
//refetch event in case the time changed or the poll is already closed
|
||||
const events = (await event.guild.scheduledEvents.fetch())
|
||||
.filter(event => event.name.toLowerCase().includes("voting offen"))
|
||||
.map((value, _) => value)
|
||||
.map((value) => value)
|
||||
|
||||
if (!events || events.length <= 0) {
|
||||
logger.info("Did not find any events. Cancelling", { guildId: event.guildId, requestId })
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { GuildMember, GuildScheduledEvent, GuildScheduledEventStatus } from "discord.js";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import { jellyfinHandler } from "../..";
|
||||
import { client, jellyfinHandler } from "../..";
|
||||
import { getGuildSpecificTriggerRoleId } from "../helper/roleFilter";
|
||||
import { logger } from "../logger";
|
||||
|
||||
@ -10,23 +10,34 @@ export const name = 'guildScheduledEventUpdate'
|
||||
export async function execute(oldEvent: GuildScheduledEvent, newEvent: GuildScheduledEvent) {
|
||||
try {
|
||||
const requestId = uuid()
|
||||
logger.debug(`Got scheduledEvent update. New Event: ${JSON.stringify(newEvent, null, 2)}`,{guildId: newEvent.guildId, requestId})
|
||||
logger.debug(`Got scheduledEvent update. New Event: ${JSON.stringify(newEvent, null, 2)}`, { guildId: newEvent.guildId, requestId })
|
||||
if (!newEvent.guild) {
|
||||
logger.error("Event has no guild, aborting.", { guildId: newEvent.guildId, requestId })
|
||||
return
|
||||
}
|
||||
|
||||
if (newEvent.description?.toLowerCase().includes("!wp") && [GuildScheduledEventStatus.Active, GuildScheduledEventStatus.Completed].includes(newEvent.status)) {
|
||||
const roles = getGuildSpecificTriggerRoleId(newEvent.guildId).map((key, value)=> value)
|
||||
const eventMembers = (await newEvent.fetchSubscribers({ withMember: true })).filter(member => !member.member.roles.cache.hasAny(...roles)).map((value, _) => value.member)
|
||||
const channelMembers = newEvent.channel?.members.filter(member => !member.roles.cache.hasAny(...roles)).map((value, _) => value )
|
||||
const roles = getGuildSpecificTriggerRoleId().map((key, value) => value)
|
||||
const eventMembers = (await newEvent.fetchSubscribers({ withMember: true })).filter(member => !member.member.roles.cache.hasAny(...roles)).map((value) => value.member)
|
||||
const channelMembers = newEvent.channel?.members.filter(member => !member.roles.cache.hasAny(...roles)).map((value) => value)
|
||||
const allMembers = eventMembers.concat(channelMembers ?? [])
|
||||
|
||||
const members: GuildMember[] = []
|
||||
for(const member of allMembers){
|
||||
if(!members.find(x => x.id == member.id))
|
||||
for (const member of allMembers) {
|
||||
if (!members.find(x => x.id == member.id))
|
||||
members.push(member)
|
||||
}
|
||||
|
||||
|
||||
if (newEvent.status === GuildScheduledEventStatus.Active)
|
||||
kenobi marked this conversation as resolved
Outdated
kenobi
commented
check for presence of check for presence of `newEvent.guild` at the start of the `execute` function.
That way we can also log the absence if it occurs.
|
||||
createJFUsers(members, newEvent.name, requestId)
|
||||
else {
|
||||
const announcementChannel = await client.getAnnouncementChannelForGuild(newEvent.guild.id)
|
||||
if(!announcementChannel) {
|
||||
logger.error("Could not find announcement channel. Aborting", { guildId: newEvent.guild.id, requestId })
|
||||
return
|
||||
}
|
||||
const announcements = (await announcementChannel.messages.fetch()).filter(message => !message.pinned)
|
||||
announcements.forEach(message => message.delete())
|
||||
members.forEach(member => {
|
||||
member.createDM().then(channel => channel.send(`Die Watchparty ist vorbei, dein Account wurde wieder gelöscht. Wenn du einen permanenten Account haben möchtest, melde dich bei Samantha oder Marukus.`))
|
||||
})
|
||||
|
@ -1,4 +0,0 @@
|
||||
export const name = 'ready'
|
||||
export function execute(client: any) {
|
||||
//console.log(`Processing ready: ${JSON.stringify(client)} has been created.`)
|
||||
}
|
@ -18,8 +18,8 @@ export async function execute(oldState: VoiceState, newState: VoiceState) {
|
||||
}
|
||||
|
||||
const scheduledEvents = (await newState.guild.scheduledEvents.fetch())
|
||||
.filter((key, value) => key.description?.toLowerCase().includes("!wp") && key.isActive())
|
||||
.map((key, value) => key)
|
||||
.filter((key) => key.description?.toLowerCase().includes("!wp") && key.isActive())
|
||||
.map((key) => key)
|
||||
|
||||
const scheduledEventUsers = (await Promise.all(scheduledEvents.map(event => event.fetchSubscribers({withMember: true}))))
|
||||
|
||||
|
@ -16,7 +16,7 @@ export function filterRolesFromMemberUpdate(oldMember: GuildMember, newMember: G
|
||||
return { addedRoles, removedRoles }
|
||||
}
|
||||
|
||||
export function getGuildSpecificTriggerRoleId(guildId: string): Collection<string, PermissionLevel> {
|
||||
export function getGuildSpecificTriggerRoleId(): Collection<string, PermissionLevel> {
|
||||
const outVal = new Collection<string, PermissionLevel>()
|
||||
outVal.set(config.bot.watcher_role, "VIEWER")
|
||||
outVal.set(config.bot.jf_admin_role, "ADMIN")
|
||||
|
@ -2,7 +2,7 @@ import { GuildMember } from "discord.js";
|
||||
import { Config } from "../configuration";
|
||||
import { Maybe, PermissionLevel } from "../interfaces";
|
||||
import { logger } from "../logger";
|
||||
import { CreateUserByNameOperationRequest, DeleteUserRequest, GetItemsRequest, GetMovieRemoteSearchResultsOperationRequest, ItemLookupApi, ItemsApi, LibraryApi, SystemApi, UpdateUserPasswordOperationRequest, UpdateUserPolicyOperationRequest, UserApi } from "./apis";
|
||||
import { CreateUserByNameOperationRequest, DeleteUserRequest, GetItemsRequest, ItemsApi, SystemApi, UpdateUserPasswordOperationRequest, UpdateUserPolicyOperationRequest, UserApi } from "./apis";
|
||||
import { BaseItemDto, UpdateUserPasswordRequest } from "./models";
|
||||
import { UserDto } from "./models/UserDto";
|
||||
import { Configuration, ConfigurationParameters } from "./runtime";
|
||||
@ -56,10 +56,6 @@ export class JellyfinHandler {
|
||||
return `${discordUser.displayName}${level == "TEMPORARY" ? "_tmp" : ""}`
|
||||
}
|
||||
|
||||
public async addPermissionsToUserAccount(jfUserAccount: UserDto, guildId: string, requestId: string): Promise<UserDto> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
private generatePasswordForUser(): string {
|
||||
return (Math.random() * 10000 + 10000).toFixed(0)
|
||||
}
|
||||
@ -253,7 +249,7 @@ export class JellyfinHandler {
|
||||
}
|
||||
const movies: BaseItemDto[] = []
|
||||
for (let i = 0; i < count; i++) {
|
||||
const index = Math.random() * allMovies.length
|
||||
const index = Math.floor(Math.random() * allMovies.length)
|
||||
movies.push(...allMovies.splice(index, 1)) // maybe out of bounds? ?
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,13 @@
|
||||
import { ApplicationCommandDataResolvable, Client, ClientOptions, Collection, GatewayIntentBits, Guild, IntentsBitField, Snowflake, TextChannel } from "discord.js";
|
||||
import { CommandType } from "../types/commandTypes";
|
||||
import fs from 'fs'
|
||||
import { ApplicationCommandDataResolvable, Client, ClientOptions, Collection, Guild, IntentsBitField, Snowflake, TextChannel } from "discord.js";
|
||||
import fs from 'fs';
|
||||
import { ScheduledTask, schedule } from "node-cron";
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { manageAnnouncementRoles } from "../commands/announce";
|
||||
import { config } from "../configuration";
|
||||
import { logger } from "../logger";
|
||||
import { Maybe } from "../interfaces";
|
||||
import { JellyfinHandler } from "../jellyfin/handler";
|
||||
import { logger } from "../logger";
|
||||
import { CommandType } from "../types/commandTypes";
|
||||
|
||||
|
||||
|
||||
@ -13,6 +17,7 @@ export class ExtendedClient extends Client {
|
||||
private jellyfin: JellyfinHandler
|
||||
public commands: Collection<string, CommandType> = new Collection()
|
||||
private announcementChannels: Collection<string, TextChannel> = new Collection //guildId to TextChannel
|
||||
private announcementRoleHandlerTask: Collection<string, ScheduledTask> = new Collection //one task per guild
|
||||
public constructor(jf: JellyfinHandler) {
|
||||
const intents: IntentsBitField = new IntentsBitField()
|
||||
intents.add(IntentsBitField.Flags.GuildMembers, IntentsBitField.Flags.MessageContent, IntentsBitField.Flags.Guilds, IntentsBitField.Flags.DirectMessages, IntentsBitField.Flags.GuildScheduledEvents, IntentsBitField.Flags.GuildVoiceStates)
|
||||
@ -60,14 +65,15 @@ export class ExtendedClient extends Client {
|
||||
this.commands.set(command.name, command)
|
||||
slashCommands.push(command)
|
||||
}
|
||||
this.on("ready", (client: Client) => {
|
||||
this.on("ready", async (client: Client) => {
|
||||
//logger.info(`Ready processing ${JSON.stringify(client)}`)
|
||||
logger.info(`SlashCommands: ${JSON.stringify(slashCommands)}`)
|
||||
const guilds = client.guilds.cache
|
||||
|
||||
this.registerCommands(slashCommands, guilds)
|
||||
this.cacheUsers(guilds)
|
||||
this.cacheAnnouncementServer(guilds)
|
||||
await this.cacheAnnouncementServer(guilds)
|
||||
this.startAnnouncementRoleBackgroundTask(guilds)
|
||||
})
|
||||
} catch (error) {
|
||||
logger.info(`Error refreshing slash commands: ${error}`)
|
||||
@ -76,8 +82,8 @@ export class ExtendedClient extends Client {
|
||||
private async cacheAnnouncementServer(guilds: Collection<Snowflake, Guild>) {
|
||||
for (const guild of guilds.values()) {
|
||||
const channels: TextChannel[] = <TextChannel[]>(await guild.channels.fetch())
|
||||
?.filter(channel => channel!.id === config.bot.announcement_channel_id)
|
||||
.map((value, _) => value)
|
||||
?.filter(channel => channel?.id === config.bot.announcement_channel_id)
|
||||
.map((value) => value)
|
||||
|
||||
if (!channels || channels.length != 1) {
|
||||
logger.error(`Could not find announcement channel for guild ${guild.name} with guildId ${guild.id}. Found ${channels}`)
|
||||
@ -87,8 +93,8 @@ export class ExtendedClient extends Client {
|
||||
this.announcementChannels.set(guild.id, channels[0])
|
||||
}
|
||||
}
|
||||
public getAnnouncementChannelForGuild(guildId: string): TextChannel {
|
||||
return this.announcementChannels.get(guildId)! //we set the channel by ourselves only if we find one, I think this is sage (mark my words)
|
||||
public getAnnouncementChannelForGuild(guildId: string): Maybe<TextChannel> {
|
||||
return this.announcementChannels.get(guildId)
|
||||
}
|
||||
public async cacheUsers(guilds: Collection<Snowflake, Guild>) {
|
||||
guilds.forEach((guild: Guild, id: Snowflake) => {
|
||||
@ -117,4 +123,51 @@ export class ExtendedClient extends Client {
|
||||
logger.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
public async startAnnouncementRoleBackgroundTask(guilds: Collection<string, Guild>) {
|
||||
for (const guild of guilds.values()) {
|
||||
logger.info("Starting background task for announcement role", { guildId: guild.id })
|
||||
const textChannel: Maybe<TextChannel> = this.getAnnouncementChannelForGuild(guild.id)
|
||||
if(!textChannel) {
|
||||
logger.error("Could not find announcement channel. Aborting", { guildId: guild.id })
|
||||
return
|
||||
}
|
||||
this.announcementRoleHandlerTask.set(guild.id, schedule("*/10 * * * * *", async () => {
|
||||
const requestId = uuid()
|
||||
const messages = (await textChannel.messages.fetchPinned()).filter(message => message.cleanContent.includes("[initial]"))
|
||||
|
||||
kenobi marked this conversation as resolved
kenobi
commented
Unable to continue, should result in a return. Unable to continue, should result in a return.
|
||||
if (messages.size > 1) {
|
||||
logger.error("More than one pinned announcement Messages found. Unable to know which one people react to. Please fix!", { guildId: guild.id, requestId })
|
||||
return
|
||||
kenobi marked this conversation as resolved
Outdated
kenobi
commented
```
const message = await messages.at(0)?.fetch()
if (!message) {
logger.error(`No pinned message found`,{requestId,guildId})
return
}
```
|
||||
} else if (messages.size == 0) {
|
||||
logger.error("Could not find any pinned announcement messages. Unable to manage roles!", { guildId: guild.id, requestId })
|
||||
return
|
||||
}
|
||||
|
||||
const message = await messages.at(0)?.fetch()
|
||||
if (!message) {
|
||||
logger.error(`No pinned message found`, { guildId: guild.id, requestId })
|
||||
return
|
||||
}
|
||||
//logger.debug(`Message: ${JSON.stringify(message, null, 2)}`, { guildId: guild.id, requestId })
|
||||
|
||||
const reactions = message.reactions.resolve("🎫")
|
||||
//logger.debug(`reactions: ${JSON.stringify(reactions, null, 2)}`, { guildId: guild.id, requestId })
|
||||
kenobi marked this conversation as resolved
Outdated
kenobi
commented
Would it be possible to just use string as type for Would it be possible to just use string as type for `guild` so we don't have to check multiple times if we have a guild object?
|
||||
if (reactions) {
|
||||
manageAnnouncementRoles(message.guild, reactions, requestId)
|
||||
} else {
|
||||
logger.error("Did not get reactions! Aborting!", { guildId: guild.id, requestId })
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
public stopAnnouncementRoleBackgroundTask(guildId: string, requestId: string) {
|
||||
const task: Maybe<ScheduledTask> = this.announcementRoleHandlerTask.get(guildId)
|
||||
if (!task) {
|
||||
logger.error(`No task found for guildID ${guildId}.`, { guildId, requestId })
|
||||
return
|
||||
}
|
||||
task.stop()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user
If
guild
is already used and necessary in line 61, it should already be present and usable, so the non-null-assertion should not be needed.➕