Compare commits
33 Commits
8f1c6e10fa
...
v1.0.0
Author | SHA1 | Date | |
---|---|---|---|
f78e4c3e3e | |||
a1e1fca650 | |||
2fae61fc1f | |||
71ffc6ba50 | |||
8caf80f54e | |||
1ccb1a7cae | |||
d22e38efbf | |||
68662e72ad | |||
5b99c843b4 | |||
251756c622 | |||
9420eb4366 | |||
220f9dc8ef | |||
198a25d145 | |||
baefcf9bb9 | |||
a5eab2f7be | |||
e774474a55 | |||
24754decf4 | |||
a2c55ad676 | |||
e50cb10c5b | |||
acc38fdcb0 | |||
fdc0fc47b5 | |||
f3669ec34f | |||
c0369fcb49 | |||
40d220ed7b | |||
117ff23a0c | |||
550aa53188 | |||
1ee55f995c | |||
0d5c3d30a9 | |||
c2d8838cf8 | |||
c8fa89ae63 | |||
2707f7d73b | |||
2c5bf1272e | |||
1e1ab93667 |
23
.gitea/workflows/docker-build.yaml
Normal file
23
.gitea/workflows/docker-build.yaml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
name: Build a docker image for node-jellyfin-role-bot
|
||||||
|
run-name: ${{ gitea.actor }} is building an image
|
||||||
|
on: [push]
|
||||||
|
env:
|
||||||
|
REGISTRY: gitea.brudi.xyz
|
||||||
|
IMAGE_NAME: ${{ gitea.repository }}
|
||||||
|
USER: ${{ gitea.actor }}
|
||||||
|
jobs:
|
||||||
|
build-docker-image:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container: catthehacker/ubuntu:act-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Log in to the Container registry
|
||||||
|
run: docker login -u ${{ env.USER }} -p ${{ secrets.TOKEN }} ${{ env.REGISTRY }}
|
||||||
|
- name: Build Container
|
||||||
|
run: docker build -t "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" .
|
||||||
|
- name: Push Container
|
||||||
|
run: docker push "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest"
|
11
Dockerfile
Normal file
11
Dockerfile
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
FROM node:alpine as Build
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY [ "package-lock.json", "package.json", "index.ts", "tsconfig.json", "./" ]
|
||||||
|
COPY server ./server
|
||||||
|
|
||||||
|
RUN npm ci --omit=dev
|
||||||
|
|
||||||
|
RUN npm run build
|
||||||
|
CMD ["npm","run","start"]
|
3
index.ts
3
index.ts
@ -5,7 +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(config)
|
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 client = new ExtendedClient(jellyfinHandler)
|
export const client = new ExtendedClient(jellyfinHandler)
|
||||||
|
|
||||||
|
50
package-lock.json
generated
50
package-lock.json
generated
@ -1,17 +1,18 @@
|
|||||||
{
|
{
|
||||||
"name": "node-jellyfin-discord-bot",
|
"name": "node-jellyfin-discord-bot",
|
||||||
"version": "0.0.1",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "node-jellyfin-discord-bot",
|
"name": "node-jellyfin-discord-bot",
|
||||||
"version": "0.0.1",
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordjs/rest": "^1.7.0",
|
"@discordjs/rest": "^1.7.0",
|
||||||
"@tsconfig/recommended": "^1.0.2",
|
"@tsconfig/recommended": "^1.0.2",
|
||||||
"@types/node": "^18.15.11",
|
"@types/node": "^18.15.11",
|
||||||
|
"@types/node-cron": "^3.0.7",
|
||||||
"@types/request": "^2.48.8",
|
"@types/request": "^2.48.8",
|
||||||
"@types/uuid": "^9.0.1",
|
"@types/uuid": "^9.0.1",
|
||||||
"axios": "^1.3.5",
|
"axios": "^1.3.5",
|
||||||
@ -20,6 +21,7 @@
|
|||||||
"discord.js": "^14.9.0",
|
"discord.js": "^14.9.0",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"jellyfin-apiclient": "^1.10.0",
|
"jellyfin-apiclient": "^1.10.0",
|
||||||
|
"node-cron": "^3.0.2",
|
||||||
"sqlite3": "^5.1.6",
|
"sqlite3": "^5.1.6",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "^5.0.4",
|
"typescript": "^5.0.4",
|
||||||
@ -1585,6 +1587,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz",
|
||||||
"integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q=="
|
"integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/node-cron": {
|
||||||
|
"version": "3.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node-cron/-/node-cron-3.0.7.tgz",
|
||||||
|
"integrity": "sha512-9PuLtBboc/+JJ7FshmJWv769gDonTpItN0Ol5TMwclpSQNjVyB2SRxSKBcTtbSysSL5R7Oea06kTTFNciCoYwA=="
|
||||||
|
},
|
||||||
"node_modules/@types/prettier": {
|
"node_modules/@types/prettier": {
|
||||||
"version": "2.7.2",
|
"version": "2.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz",
|
||||||
@ -5004,6 +5011,25 @@
|
|||||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz",
|
||||||
"integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ=="
|
"integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/node-cron": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"uuid": "8.3.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/node-cron/node_modules/uuid": {
|
||||||
|
"version": "8.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-fetch": {
|
"node_modules/node-fetch": {
|
||||||
"version": "2.6.9",
|
"version": "2.6.9",
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
|
||||||
@ -8124,6 +8150,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz",
|
||||||
"integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q=="
|
"integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q=="
|
||||||
},
|
},
|
||||||
|
"@types/node-cron": {
|
||||||
|
"version": "3.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node-cron/-/node-cron-3.0.7.tgz",
|
||||||
|
"integrity": "sha512-9PuLtBboc/+JJ7FshmJWv769gDonTpItN0Ol5TMwclpSQNjVyB2SRxSKBcTtbSysSL5R7Oea06kTTFNciCoYwA=="
|
||||||
|
},
|
||||||
"@types/prettier": {
|
"@types/prettier": {
|
||||||
"version": "2.7.2",
|
"version": "2.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz",
|
||||||
@ -10702,6 +10733,21 @@
|
|||||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz",
|
||||||
"integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ=="
|
"integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ=="
|
||||||
},
|
},
|
||||||
|
"node-cron": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ==",
|
||||||
|
"requires": {
|
||||||
|
"uuid": "8.3.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"uuid": {
|
||||||
|
"version": "8.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node-fetch": {
|
"node-fetch": {
|
||||||
"version": "2.6.9",
|
"version": "2.6.9",
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "node-jellyfin-discord-bot",
|
"name": "node-jellyfin-discord-bot",
|
||||||
"version": "0.0.1",
|
"version": "1.0.0",
|
||||||
"description": "A discord bot to sync jellyfin accounts with discord roles",
|
"description": "A discord bot to sync jellyfin accounts with discord roles",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -8,6 +8,7 @@
|
|||||||
"@discordjs/rest": "^1.7.0",
|
"@discordjs/rest": "^1.7.0",
|
||||||
"@tsconfig/recommended": "^1.0.2",
|
"@tsconfig/recommended": "^1.0.2",
|
||||||
"@types/node": "^18.15.11",
|
"@types/node": "^18.15.11",
|
||||||
|
"@types/node-cron": "^3.0.7",
|
||||||
"@types/request": "^2.48.8",
|
"@types/request": "^2.48.8",
|
||||||
"@types/uuid": "^9.0.1",
|
"@types/uuid": "^9.0.1",
|
||||||
"axios": "^1.3.5",
|
"axios": "^1.3.5",
|
||||||
@ -16,6 +17,7 @@
|
|||||||
"discord.js": "^14.9.0",
|
"discord.js": "^14.9.0",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"jellyfin-apiclient": "^1.10.0",
|
"jellyfin-apiclient": "^1.10.0",
|
||||||
|
"node-cron": "^3.0.2",
|
||||||
"sqlite3": "^5.1.6",
|
"sqlite3": "^5.1.6",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "^5.0.4",
|
"typescript": "^5.0.4",
|
||||||
|
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 in denen wir zusammen Filme gucken! 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))
|
||||||
|
}
|
||||||
|
|
148
server/commands/closepoll.ts
Normal file
148
server/commands/closepoll.ts
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
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 { format } from 'date-fns'
|
||||||
|
import { Maybe } from '../interfaces'
|
||||||
|
|
||||||
|
export default new Command({
|
||||||
|
name: 'closepoll',
|
||||||
|
description: 'Aktuelle Umfrage für nächste Watchparty beenden und Gewinner in Event eintragen.',
|
||||||
|
options: [],
|
||||||
|
run: async (interaction: RunOptions) => {
|
||||||
|
const command = interaction.interaction
|
||||||
|
const requestId = uuid()
|
||||||
|
if (!command.guild) {
|
||||||
|
logger.error("No guild found in interaction. Cancelling closing request", { requestId })
|
||||||
|
command.followUp("Es gab leider ein Problem. Ich konnte deine Anfrage nicht bearbeiten :(")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const guildId = command.guildId
|
||||||
|
logger.info("Got command for closing poll!", { guildId, requestId })
|
||||||
|
|
||||||
|
command.followUp("Alles klar, beende die Umfrage :)")
|
||||||
|
closePoll(command.guild, requestId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export async function closePoll(guild: Guild, requestId: string) {
|
||||||
|
const guildId = guild.id
|
||||||
|
logger.info("stopping poll", { guildId, requestId })
|
||||||
|
|
||||||
|
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)
|
||||||
|
.filter(message => !message.cleanContent.includes("[Abstimmung beendet]") && message.cleanContent.includes("[Abstimmung]"))
|
||||||
|
.sort((a, b) => b.createdTimestamp - a.createdTimestamp)
|
||||||
|
|
||||||
|
if (!messages || messages.length <= 0) {
|
||||||
|
logger.info("Could not find any vote messages. Cancelling pollClose", { guildId, requestId })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastMessage: Message<true> = messages[0]
|
||||||
|
|
||||||
|
logger.debug(`Found messages: ${JSON.stringify(messages, null, 2)}`, { guildId, requestId })
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
logger.debug(`votes: ${JSON.stringify(votes, null, 2)}`, { guildId, requestId })
|
||||||
|
|
||||||
|
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 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"] }
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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`
|
||||||
|
}
|
||||||
|
logger.debug(`Updating event: ${JSON.stringify(voteEvent, null, 2)}`, { guildId, requestId })
|
||||||
|
logger.info("Updating event.", { guildId, requestId })
|
||||||
|
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,
|
||||||
|
movie: string
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getVotesByEmote(message: Message, 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(`Getting reaction for emote ${emote}`, { guildId, requestId })
|
||||||
|
const reaction = await message.reactions.resolve(emote)
|
||||||
|
logger.debug(`Reaction for emote ${emote}: ${JSON.stringify(reaction, null, 2)}`, { guildId, requestId })
|
||||||
|
if (reaction) {
|
||||||
|
const vote: Vote = { emote: emote, count: reaction.count, movie: extractMovieFromMessageByEmote(message, emote) }
|
||||||
|
votes.push(vote)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return votes
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractMovieFromMessageByEmote(message: Message, emote: string): string {
|
||||||
|
const lines = message.cleanContent.split("\n")
|
||||||
|
const emoteLines = lines.filter(line => line.includes(emote))
|
||||||
|
|
||||||
|
if (!emoteLines) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
const movie = emoteLines[0].substring(emoteLines[0].indexOf(emote) + emote.length + 2) // plus colon and space
|
||||||
|
|
||||||
|
return movie
|
||||||
|
}
|
@ -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 { Command } from '../structures/command'
|
||||||
import { RunOptions } from '../types/commandTypes'
|
import { RunOptions } from '../types/commandTypes'
|
||||||
import { jellyfinHandler } from "../.."
|
|
||||||
import { v4 as uuid } from 'uuid'
|
|
||||||
|
|
||||||
export default new Command({
|
export default new Command({
|
||||||
name: 'passwort_reset',
|
name: 'passwort_reset',
|
||||||
|
@ -22,6 +22,14 @@ export interface Config {
|
|||||||
workaround_token: string
|
workaround_token: string
|
||||||
watcher_role: string
|
watcher_role: string
|
||||||
jf_admin_role: string
|
jf_admin_role: string
|
||||||
|
announcement_role: string
|
||||||
|
announcement_channel_id: string
|
||||||
|
jf_collection_id: string
|
||||||
|
jf_user: string
|
||||||
|
yavin_collection_id: string
|
||||||
|
yavin_jellyfin_url: string
|
||||||
|
yavin_jellyfin_token: string
|
||||||
|
yavin_jellyfin_collection_user: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const config: Config = {
|
export const config: Config = {
|
||||||
@ -53,6 +61,14 @@ export const config: Config = {
|
|||||||
jellyfin_url: process.env.JELLYFIN_URL ?? "",
|
jellyfin_url: process.env.JELLYFIN_URL ?? "",
|
||||||
workaround_token: process.env.TOKEN ?? "",
|
workaround_token: process.env.TOKEN ?? "",
|
||||||
watcher_role: process.env.WATCHER_ROLE ?? "",
|
watcher_role: process.env.WATCHER_ROLE ?? "",
|
||||||
jf_admin_role: process.env.ADMIN_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 ?? "",
|
||||||
|
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 ?? ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ export async function execute(oldMember: GuildMember, newMember: GuildMember) {
|
|||||||
try {
|
try {
|
||||||
const requestId = uuid()
|
const requestId = uuid()
|
||||||
const changedRoles: ChangedRoles = filterRolesFromMemberUpdate(oldMember, newMember)
|
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) => {
|
triggerRoleIds.forEach((level, key) => {
|
||||||
const addedRoleMatches = changedRoles.addedRoles.find(aRole => aRole.id === key)
|
const addedRoleMatches = changedRoles.addedRoles.find(aRole => aRole.id === key)
|
||||||
|
104
server/events/guildScheduledEventCreate.ts
Normal file
104
server/events/guildScheduledEventCreate.ts
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
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, yavinJellyfinHandler } from "../..";
|
||||||
|
import { closePoll } from "../commands/closepoll";
|
||||||
|
import { config } from "../configuration";
|
||||||
|
import { Maybe } from "../interfaces";
|
||||||
|
import { logger } from "../logger";
|
||||||
|
|
||||||
|
|
||||||
|
export const name = 'guildScheduledEventCreate'
|
||||||
|
|
||||||
|
export enum Emotes { "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟" }
|
||||||
|
|
||||||
|
export let task: ScheduledTask | undefined
|
||||||
|
|
||||||
|
export async function execute(event: GuildScheduledEvent) {
|
||||||
|
const requestId = uuid()
|
||||||
|
logger.debug(`New event created: ${JSON.stringify(event, null, 2)}`, { guildId: event.guildId, requestId })
|
||||||
|
|
||||||
|
if (event.name.toLowerCase().includes("!nextwp")) {
|
||||||
|
logger.info("Event was a placeholder event to start a new watchparty and voting. Creating vote!", { guildId: event.guildId, requestId })
|
||||||
|
logger.debug("Renaming event", { guildId: event.guildId, requestId })
|
||||||
|
event.edit({ name: "Watchparty - Voting offen" })
|
||||||
|
const movies = await yavinJellyfinHandler.getRandomMovies(5, event.guildId, requestId)
|
||||||
|
|
||||||
|
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: 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 })
|
||||||
|
|
||||||
|
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 ?? "Film hatte keinen Namen :(").concat("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
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])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!task) {
|
||||||
|
task = schedule("0 * * * * *", () => checkForPollsToClose(event))
|
||||||
|
}
|
||||||
|
|
||||||
|
// sentMessage.pin() //todo: uncomment when bot has permission to pin messages. Also update closepoll.ts to only fetch pinned messages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkForPollsToClose(event: GuildScheduledEvent): Promise<void> {
|
||||||
|
const requestId = uuid()
|
||||||
|
logger.info(`Automatic check for poll closing.`, { guildId: event.guildId, requestId })
|
||||||
|
if (!event.guild) {
|
||||||
|
logger.error("No guild in event. Cancelling.", { guildId: event.guildId, requestId })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//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)
|
||||||
|
|
||||||
|
if (!events || events.length <= 0) {
|
||||||
|
logger.info("Did not find any events. Cancelling", { guildId: event.guildId, requestId })
|
||||||
|
return
|
||||||
|
} else if (events.length > 1) {
|
||||||
|
logger.error(`More than one event found. Don't know which one is the right one :( Events: ${JSON.stringify(events, null, 2)}`, { guildId: event.guildId, requestId })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const updatedEvent = events[0] //add two hours because of different timezones in discord api and Date.now()
|
||||||
|
if (!updatedEvent.scheduledStartTimestamp) {
|
||||||
|
logger.error("Event does not have a scheduled start time. Cancelling", { guildId: event.guildId, requestId })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventDate: Date = toDate(updatedEvent.scheduledStartTimestamp)
|
||||||
|
const closePollDate: Date = addDays(eventDate, -2)
|
||||||
|
|
||||||
|
if (isAfter(Date.now(), closePollDate)) {
|
||||||
|
logger.info("Less than two days until event. Closing poll", { guildId: event.guildId, requestId })
|
||||||
|
closePoll(event.guild, requestId)
|
||||||
|
} else {
|
||||||
|
logger.info(`ScheduledStart: ${closePollDate}. Now: ${toDate(Date.now())}`, { guildId: event.guildId, requestId })
|
||||||
|
}
|
||||||
|
}
|
@ -1,25 +1,45 @@
|
|||||||
import { GuildScheduledEvent, GuildScheduledEventStatus, Collection, Snowflake, GuildScheduledEventUser } from "discord.js";
|
import { GuildMember, GuildScheduledEvent, GuildScheduledEventStatus } from "discord.js";
|
||||||
import { logger } from "../logger";
|
|
||||||
import { jellyfinHandler } from "../.."
|
|
||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
|
import { client, jellyfinHandler } from "../..";
|
||||||
import { getGuildSpecificTriggerRoleId } from "../helper/roleFilter";
|
import { getGuildSpecificTriggerRoleId } from "../helper/roleFilter";
|
||||||
|
import { logger } from "../logger";
|
||||||
|
|
||||||
|
|
||||||
export const name = 'guildScheduledEventUpdate'
|
export const name = 'guildScheduledEventUpdate'
|
||||||
|
|
||||||
export async function execute(oldEvent: GuildScheduledEvent, newEvent: GuildScheduledEvent) {
|
export async function execute(oldEvent: GuildScheduledEvent, newEvent: GuildScheduledEvent) {
|
||||||
try {
|
try {
|
||||||
logger.info(JSON.stringify(newEvent, null, 2))
|
|
||||||
const requestId = uuid()
|
const requestId = uuid()
|
||||||
|
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().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))
|
||||||
|
members.push(member)
|
||||||
|
}
|
||||||
|
|
||||||
if (newEvent.description?.includes("!WP") && [GuildScheduledEventStatus.Active, GuildScheduledEventStatus.Completed].includes(newEvent.status)) {
|
|
||||||
const roles = getGuildSpecificTriggerRoleId(newEvent.guildId).map((key, value)=> value)
|
|
||||||
const members = (await newEvent.fetchSubscribers({ withMember: true })).filter(member => !member.member.roles.cache.hasAny(...roles))
|
|
||||||
if (newEvent.status === GuildScheduledEventStatus.Active)
|
if (newEvent.status === GuildScheduledEventStatus.Active)
|
||||||
createJFUsers(members, newEvent.name, requestId)
|
createJFUsers(members, newEvent.name, requestId)
|
||||||
else {
|
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 => {
|
members.forEach(member => {
|
||||||
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.`))
|
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.`))
|
||||||
})
|
})
|
||||||
deleteJFUsers(newEvent.guildId, requestId)
|
deleteJFUsers(newEvent.guildId, requestId)
|
||||||
}
|
}
|
||||||
@ -28,15 +48,16 @@ export async function execute(oldEvent: GuildScheduledEvent, newEvent: GuildSche
|
|||||||
logger.error(error)
|
logger.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function createJFUsers(members: Collection<Snowflake, GuildScheduledEventUser<true>>, movieName: string, requestId?: string) {
|
|
||||||
|
async function createJFUsers(members: GuildMember[], movieName: string, requestId?: string) {
|
||||||
logger.info(`Creating users for: \n ${JSON.stringify(members, null, 2)}`)
|
logger.info(`Creating users for: \n ${JSON.stringify(members, null, 2)}`)
|
||||||
members.forEach(member => {
|
members.forEach(member => {
|
||||||
member.member.createDM().then(channel => channel.send(`Hey! Du hast dich für die Watchparty von ${movieName} angemeldet! Es geht gleich los!`))
|
member.createDM().then(channel => channel.send(`Hey! Du hast dich für die Watchparty von ${movieName} angemeldet! Es geht gleich los!`))
|
||||||
jellyfinHandler.upsertUser(member.member, "TEMPORARY", requestId)
|
jellyfinHandler.upsertUser(member, "TEMPORARY", requestId)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteJFUsers(guildId: string, requestId?: string) {
|
async function deleteJFUsers(guildId: string, requestId?: string) {
|
||||||
logger.info(`Watchparty ended, deleting tmp users`)
|
logger.info(`Watchparty ended, deleting tmp users`, { guildId, requestId })
|
||||||
jellyfinHandler.purge(guildId, requestId)
|
jellyfinHandler.purge(guildId, requestId)
|
||||||
}
|
}
|
@ -1,4 +0,0 @@
|
|||||||
export const name = 'ready'
|
|
||||||
export function execute(client: any) {
|
|
||||||
//console.log(`Processing ready: ${JSON.stringify(client)} has been created.`)
|
|
||||||
}
|
|
@ -1,7 +1,8 @@
|
|||||||
import { GuildScheduledEvent, GuildScheduledEventStatus, Collection, Snowflake, GuildScheduledEventUser, User, VoiceState } from "discord.js";
|
import { VoiceState } from "discord.js";
|
||||||
import { logger } from "../logger";
|
|
||||||
import { jellyfinHandler } from "../.."
|
|
||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
|
import { jellyfinHandler } from "../..";
|
||||||
|
import { UserUpsertResult } from "../jellyfin/handler";
|
||||||
|
import { logger } from "../logger";
|
||||||
|
|
||||||
|
|
||||||
export const name = 'voiceStateUpdate'
|
export const name = 'voiceStateUpdate'
|
||||||
@ -9,14 +10,16 @@ export const name = 'voiceStateUpdate'
|
|||||||
export async function execute(oldState: VoiceState, newState: VoiceState) {
|
export async function execute(oldState: VoiceState, newState: VoiceState) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
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)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const scheduledEvents = (await newState.guild.scheduledEvents.fetch())
|
const scheduledEvents = (await newState.guild.scheduledEvents.fetch())
|
||||||
.filter((key, value) => key.description?.includes("!WP") && key.isActive())
|
.filter((key) => key.description?.toLowerCase().includes("!wp") && key.isActive())
|
||||||
.map((key, value) => 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}))))
|
||||||
|
|
||||||
@ -29,19 +32,26 @@ export async function execute(oldState: VoiceState, newState: VoiceState) {
|
|||||||
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)}`)
|
||||||
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())
|
||||||
newState.member.createDM().then(channel => channel.send(`Hey! Du bist unserer Watchparty beigetreten, ich leg dir mal nen Account an, damit du mitschauen kannst!`))
|
if (result === UserUpsertResult.created) {
|
||||||
jellyfinHandler.upsertUser(newState.member, "TEMPORARY", uuid())
|
newState.member.createDM().then(channel => channel.send(`Hey! Du bist unserer Watchparty beigetreten, ich hab dir gerade die Zugangsdaten für den Mediaserver geschickt!`))
|
||||||
|
} else {
|
||||||
|
newState.member.createDM().then(channel => channel.send(`Hey! Du bist unserer Watchparty beigetreten aber du hast bereits einen Account. Falls du ein neues Passwort brauchst nutze /reset_passwort!`))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.error("WTF? Expected Member?? When doing things")
|
logger.error("WTF? Expected Member?? When doing things")
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
logger.info("VoiceState channelId was not the id of any channel with events")
|
||||||
}
|
}
|
||||||
}catch(error){
|
}catch(error){
|
||||||
logger.error(error)
|
logger.error(error)
|
||||||
|
@ -16,7 +16,7 @@ export function filterRolesFromMemberUpdate(oldMember: GuildMember, newMember: G
|
|||||||
return { addedRoles, removedRoles }
|
return { addedRoles, removedRoles }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getGuildSpecificTriggerRoleId(guildId: string): Collection<string, PermissionLevel> {
|
export function getGuildSpecificTriggerRoleId(): Collection<string, PermissionLevel> {
|
||||||
const outVal = new Collection<string, PermissionLevel>()
|
const outVal = new Collection<string, PermissionLevel>()
|
||||||
outVal.set(config.bot.watcher_role, "VIEWER")
|
outVal.set(config.bot.watcher_role, "VIEWER")
|
||||||
outVal.set(config.bot.jf_admin_role, "ADMIN")
|
outVal.set(config.bot.jf_admin_role, "ADMIN")
|
||||||
|
@ -32,4 +32,10 @@ export interface ChangedRoles {
|
|||||||
addedRoles: Collection<string, Role>
|
addedRoles: Collection<string, Role>
|
||||||
removedRoles: Collection<string, Role>
|
removedRoles: Collection<string, Role>
|
||||||
}
|
}
|
||||||
|
export interface JellyfinConfig {
|
||||||
|
jellyfinUrl: string,
|
||||||
|
jellyfinToken: string,
|
||||||
|
movieCollectionId: string,
|
||||||
|
collectionUser: string
|
||||||
|
}
|
||||||
export type PermissionLevel = "VIEWER" | "ADMIN" | "TEMPORARY"
|
export type PermissionLevel = "VIEWER" | "ADMIN" | "TEMPORARY"
|
||||||
|
@ -1,48 +1,46 @@
|
|||||||
import { GuildMember } from "discord.js";
|
import { GuildMember } from "discord.js";
|
||||||
import { CreateUserByNameOperationRequest, DeleteUserRequest, SystemApi, UpdateUserPasswordOperationRequest, UpdateUserPolicyOperationRequest, UserApi } from "./apis";
|
import { JellyfinConfig, Maybe, PermissionLevel } from "../interfaces";
|
||||||
|
import { logger } from "../logger";
|
||||||
|
import { CreateUserByNameOperationRequest, DeleteUserRequest, GetItemsRequest, ItemsApi, SystemApi, UpdateUserPasswordOperationRequest, UpdateUserPolicyOperationRequest, UserApi } from "./apis";
|
||||||
|
import { BaseItemDto, UpdateUserPasswordRequest } from "./models";
|
||||||
import { UserDto } from "./models/UserDto";
|
import { UserDto } from "./models/UserDto";
|
||||||
import { Configuration, ConfigurationParameters } from "./runtime";
|
import { Configuration, ConfigurationParameters } from "./runtime";
|
||||||
import { CreateUserByNameRequest, UpdateUserEasyPasswordRequest, UpdateUserPasswordRequest, UpdateUserPolicyRequest } from "./models";
|
|
||||||
import { Config } from "../configuration";
|
|
||||||
import { logger } from "../logger";
|
|
||||||
import { Maybe, PermissionLevel } from "../interfaces";
|
|
||||||
import { v4 as uuid } from "uuid";
|
|
||||||
|
|
||||||
|
|
||||||
export class JellyfinHandler {
|
export class JellyfinHandler {
|
||||||
|
|
||||||
private userApi: UserApi
|
private userApi: UserApi
|
||||||
private systemApi: SystemApi
|
private systemApi: SystemApi
|
||||||
|
private moviesApi: ItemsApi
|
||||||
private token: string
|
private token: string
|
||||||
private authHeader: { headers: { 'X-Emby-Authorization': string } }
|
private authHeader: { headers: { 'X-Emby-Authorization': string } }
|
||||||
private config: Config
|
private config: JellyfinConfig
|
||||||
private serverName = "";
|
private serverName = "";
|
||||||
|
|
||||||
public async ServerName(): Promise<string> {
|
constructor(_config: JellyfinConfig, _userApi?: UserApi, _systemApi?: SystemApi, _itemsApi?: ItemsApi) {
|
||||||
if (this.serverName === "") {
|
|
||||||
const info = await this.systemApi.getSystemInfo(this.authHeader)
|
|
||||||
this.serverName = info.serverName ?? this.config.bot.jellyfin_url
|
|
||||||
}
|
|
||||||
return this.serverName
|
|
||||||
}
|
|
||||||
constructor(_config: Config, _userApi?: UserApi, _systemApi?: SystemApi) {
|
|
||||||
this.config = _config
|
this.config = _config
|
||||||
this.token = this.config.bot.jellfin_token
|
this.token = this.config.jellyfinToken
|
||||||
this.authHeader = {
|
this.authHeader = {
|
||||||
headers: {
|
headers: {
|
||||||
"X-Emby-Authorization": this.config.bot.workaround_token
|
"X-Emby-Authorization": this.config.jellyfinToken
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const userApiConfigurationParams: ConfigurationParameters = {
|
const userApiConfigurationParams: ConfigurationParameters = {
|
||||||
basePath: this.config.bot.jellyfin_url,
|
basePath: this.config.jellyfinUrl,
|
||||||
headers: this.authHeader.headers
|
headers: this.authHeader.headers
|
||||||
}
|
}
|
||||||
const systemApiConfigurationParams: ConfigurationParameters = {
|
const systemApiConfigurationParams: ConfigurationParameters = {
|
||||||
basePath: this.config.bot.jellyfin_url,
|
basePath: this.config.jellyfinUrl,
|
||||||
headers: this.authHeader.headers
|
headers: this.authHeader.headers
|
||||||
}
|
}
|
||||||
|
const libraryApiConfigurationParams: ConfigurationParameters = {
|
||||||
|
basePath: this.config.jellyfinUrl,
|
||||||
|
headers: this.authHeader.headers
|
||||||
|
}
|
||||||
|
|
||||||
this.userApi = _userApi ?? new UserApi(new Configuration(userApiConfigurationParams))
|
this.userApi = _userApi ?? new UserApi(new Configuration(userApiConfigurationParams))
|
||||||
this.systemApi = _systemApi ?? new SystemApi(new Configuration(systemApiConfigurationParams))
|
this.systemApi = _systemApi ?? new SystemApi(new Configuration(systemApiConfigurationParams))
|
||||||
|
this.moviesApi = _itemsApi ?? new ItemsApi(new Configuration(libraryApiConfigurationParams))
|
||||||
logger.info(`Initialized Jellyfin handler`, { requestId: 'Init' })
|
logger.info(`Initialized Jellyfin handler`, { requestId: 'Init' })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,10 +48,6 @@ export class JellyfinHandler {
|
|||||||
return `${discordUser.displayName}${level == "TEMPORARY" ? "_tmp" : ""}`
|
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 {
|
private generatePasswordForUser(): string {
|
||||||
return (Math.random() * 10000 + 10000).toFixed(0)
|
return (Math.random() * 10000 + 10000).toFixed(0)
|
||||||
}
|
}
|
||||||
@ -210,15 +204,49 @@ export class JellyfinHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async upsertUser(newMember: GuildMember, level: PermissionLevel, requestId?: string) {
|
public async upsertUser(newMember: GuildMember, level: PermissionLevel, requestId?: string): Promise<UserUpsertResult> {
|
||||||
logger.error(`Trying to upsert user ${newMember.displayName}, with permissionLevel ${level}`, { guildId: newMember.guild.id, requestId })
|
logger.info(`Trying to upsert user ${newMember.displayName}, with permissionLevel ${level}`, { guildId: newMember.guild.id, requestId })
|
||||||
const jfuser = await this.getUser(newMember, requestId)
|
const jfuser = await this.getUser(newMember, requestId)
|
||||||
if (jfuser) {
|
if (jfuser && !jfuser.policy?.isDisabled) {
|
||||||
logger.info(`User with name ${newMember.displayName} is already present`, { guildId: newMember.guild.id, requestId })
|
logger.info(`User with name ${newMember.displayName} is already present`, { guildId: newMember.guild.id, requestId })
|
||||||
await this.enableUser(jfuser, newMember.guild.id, requestId)
|
await this.enableUser(jfuser, newMember.guild.id, requestId)
|
||||||
|
return UserUpsertResult.enabled
|
||||||
} else {
|
} else {
|
||||||
this.createUserAccountForDiscordUser(newMember, level, newMember.guild.id, requestId)
|
this.createUserAccountForDiscordUser(newMember, level, newMember.guild.id, requestId)
|
||||||
|
return UserUpsertResult.created
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public async getAllMovies(guildId: string, requestId: string): Promise<BaseItemDto[]> {
|
||||||
|
logger.info("requesting all movies from jellyfin", { guildId, requestId })
|
||||||
|
|
||||||
|
const searchParams: GetItemsRequest = {
|
||||||
|
userId: this.config.collectionUser,
|
||||||
|
parentId: this.config.movieCollectionId // collection ID for all movies
|
||||||
|
}
|
||||||
|
const movies = (await (this.moviesApi.getItems(searchParams))).items?.filter(item => !item.isFolder)
|
||||||
|
// logger.debug(JSON.stringify(movies, null, 2), { guildId: guildId, requestId })
|
||||||
|
logger.info(`Found ${movies?.length} movies in total`, { guildId, requestId })
|
||||||
|
return movies ?? []
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getRandomMovies(count: number, guildId: string, requestId: string): Promise<BaseItemDto[]> {
|
||||||
|
logger.info(`${count} random movies requested.`, { guildId, requestId })
|
||||||
|
const allMovies = await this.getAllMovies(guildId, requestId)
|
||||||
|
if (count >= allMovies.length) {
|
||||||
|
logger.info(`${count} random movies requested but found only ${allMovies.length}. Returning all Movies.`, { guildId, requestId })
|
||||||
|
return allMovies
|
||||||
|
}
|
||||||
|
const movies: BaseItemDto[] = []
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const index = Math.floor(Math.random() * allMovies.length)
|
||||||
|
movies.push(...allMovies.splice(index, 1)) // maybe out of bounds? ?
|
||||||
|
}
|
||||||
|
|
||||||
|
return movies
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
export enum UserUpsertResult { enabled, created }
|
||||||
|
|
||||||
|
@ -1,15 +1,23 @@
|
|||||||
import { ApplicationCommandDataResolvable, Client, ClientOptions, Collection, GatewayIntentBits, Guild, IntentsBitField, Snowflake } from "discord.js";
|
import { ApplicationCommandDataResolvable, Client, ClientOptions, Collection, Guild, IntentsBitField, Snowflake, TextChannel } from "discord.js";
|
||||||
import { CommandType } from "../types/commandTypes";
|
import fs from 'fs';
|
||||||
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 { config } from "../configuration";
|
||||||
import { logger } from "../logger";
|
import { Maybe } from "../interfaces";
|
||||||
import { JellyfinHandler } from "../jellyfin/handler";
|
import { JellyfinHandler } from "../jellyfin/handler";
|
||||||
|
import { logger } from "../logger";
|
||||||
|
import { CommandType } from "../types/commandTypes";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class ExtendedClient extends Client {
|
export class ExtendedClient extends Client {
|
||||||
private eventFilePath = `${__dirname}/../events`
|
private eventFilePath = `${__dirname}/../events`
|
||||||
private commandFilePath = `${__dirname}/../commands`
|
private commandFilePath = `${__dirname}/../commands`
|
||||||
private jellyfin: JellyfinHandler
|
private jellyfin: JellyfinHandler
|
||||||
public commands: Collection<string, CommandType> = new Collection()
|
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) {
|
public constructor(jf: JellyfinHandler) {
|
||||||
const intents: IntentsBitField = new IntentsBitField()
|
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)
|
intents.add(IntentsBitField.Flags.GuildMembers, IntentsBitField.Flags.MessageContent, IntentsBitField.Flags.Guilds, IntentsBitField.Flags.DirectMessages, IntentsBitField.Flags.GuildScheduledEvents, IntentsBitField.Flags.GuildVoiceStates)
|
||||||
@ -25,7 +33,6 @@ export class ExtendedClient extends Client {
|
|||||||
Promise.all(promises).then(() => {
|
Promise.all(promises).then(() => {
|
||||||
this.login(config.bot.token)
|
this.login(config.bot.token)
|
||||||
})
|
})
|
||||||
logger.info(`Connected with ${await this.jellyfin.ServerName()}`)
|
|
||||||
}
|
}
|
||||||
private async importFile(filepath: string): Promise<any> {
|
private async importFile(filepath: string): Promise<any> {
|
||||||
logger.debug(`Importing ${filepath}`)
|
logger.debug(`Importing ${filepath}`)
|
||||||
@ -57,24 +64,43 @@ export class ExtendedClient extends Client {
|
|||||||
this.commands.set(command.name, command)
|
this.commands.set(command.name, command)
|
||||||
slashCommands.push(command)
|
slashCommands.push(command)
|
||||||
}
|
}
|
||||||
this.on("ready", (client: Client) => {
|
this.on("ready", async (client: Client) => {
|
||||||
//logger.info(`Ready processing ${JSON.stringify(client)}`)
|
//logger.info(`Ready processing ${JSON.stringify(client)}`)
|
||||||
logger.info(`SlashCommands: ${JSON.stringify(slashCommands)}`)
|
logger.info(`SlashCommands: ${JSON.stringify(slashCommands)}`)
|
||||||
const guilds = client.guilds.cache
|
const guilds = client.guilds.cache
|
||||||
|
|
||||||
this.registerCommands(slashCommands, guilds)
|
this.registerCommands(slashCommands, guilds)
|
||||||
this.cacheUsers(guilds)
|
this.cacheUsers(guilds)
|
||||||
|
await this.cacheAnnouncementServer(guilds)
|
||||||
|
this.startAnnouncementRoleBackgroundTask(guilds)
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.info(`Error refreshing slash commands: ${error}`)
|
logger.info(`Error refreshing slash commands: ${error}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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)
|
||||||
|
|
||||||
|
if (!channels || channels.length != 1) {
|
||||||
|
logger.error(`Could not find announcement channel for guild ${guild.name} with guildId ${guild.id}. Found ${channels}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
logger.info(`Fetched announcement channel: ${JSON.stringify(channels[0])}`)
|
||||||
|
this.announcementChannels.set(guild.id, channels[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public getAnnouncementChannelForGuild(guildId: string): Maybe<TextChannel> {
|
||||||
|
return this.announcementChannels.get(guildId)
|
||||||
|
}
|
||||||
public async cacheUsers(guilds: Collection<Snowflake, Guild>) {
|
public async cacheUsers(guilds: Collection<Snowflake, Guild>) {
|
||||||
guilds.forEach((guild: Guild, id: Snowflake) => {
|
guilds.forEach((guild: Guild, id: Snowflake) => {
|
||||||
logger.info(`Fetching members for ${guild.name}|${id}`)
|
logger.info(`Fetching members for ${guild.name}|${id}`)
|
||||||
guild.members.fetch()
|
guild.members.fetch()
|
||||||
logger.info(`Fetched: ${guild.memberCount} members`)
|
logger.info(`Fetched: ${guild.memberCount} members`)
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
public async registerEventCallback() {
|
public async registerEventCallback() {
|
||||||
try {
|
try {
|
||||||
@ -96,4 +122,51 @@ export class ExtendedClient extends Client {
|
|||||||
logger.error(error)
|
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]"))
|
||||||
|
|
||||||
|
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
|
||||||
|
} 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 })
|
||||||
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user