Compare commits
53 Commits
v1.0.1
...
0d3c62c6ad
Author | SHA1 | Date | |
---|---|---|---|
0d3c62c6ad | |||
5816db48e6 | |||
66f843b399 | |||
d82a7cffd2 | |||
8a06a661fa | |||
4084f675cd | |||
3bd26a9d6c | |||
e7b21fa658 | |||
2d32f9b680 | |||
5503aa8713 | |||
25bb676fda | |||
9f5abb8a90 | |||
0e67252976 | |||
37b798818c | |||
af414d0bad | |||
c32434a7eb | |||
c133570d8c | |||
65cdee36e9 | |||
6b0e84669a | |||
dd72f8e165 | |||
a6f19ccd2b | |||
c39f9c6ee1 | |||
f41194ba71 | |||
fa49dc0f76 | |||
e52e845851 | |||
61544feaba | |||
1966640239 | |||
fa9998e92c | |||
c1a449bafe | |||
d5d82043f0 | |||
51ebf2e939 | |||
f314b2f355 | |||
a4d7c57d10 | |||
2802afa7d5 | |||
3a5ea5d4ff | |||
45d87275bf | |||
31e440434e | |||
3d70b56eb7 | |||
3298c7a244 | |||
5b98c9bf2f | |||
ee363e065c | |||
9af847f234 | |||
a18406e7e4 | |||
b9f65125dc | |||
d61457cb5f | |||
9da8f47784 | |||
e8c58d5ff8 | |||
8569a3e1e6 | |||
8d0dda0fa9 | |||
777ae330ad | |||
111ccaa880 | |||
c00453d3d3 | |||
8a7973a2e3 |
7
.editorconfig
Normal file
7
.editorconfig
Normal file
@ -0,0 +1,7 @@
|
||||
root = true
|
||||
[*]
|
||||
indent_style = tab
|
||||
tab_width = 4
|
||||
[*.ts]
|
||||
indent_style = tab
|
||||
tab_width = 4
|
@ -1,5 +1,5 @@
|
||||
name: Compile the repository
|
||||
on: [push]
|
||||
on: [pull_request]
|
||||
env:
|
||||
REGISTRY: gitea.brudi.xyz
|
||||
IMAGE_NAME: ${{ gitea.repository }}
|
||||
@ -14,4 +14,4 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Build Container
|
||||
run: docker build -t "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" .
|
||||
run: docker build --target compile .
|
||||
|
@ -11,7 +11,6 @@ env:
|
||||
jobs:
|
||||
build-docker-image:
|
||||
runs-on: ubuntu-latest
|
||||
if: gitea.ref == 'refs/heads/master'
|
||||
container: catthehacker/ubuntu:act-latest
|
||||
permissions:
|
||||
contents: read
|
||||
@ -19,11 +18,11 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Get Package Version
|
||||
run: VERSION = node -p "require('./package.json').version"
|
||||
- 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" -t "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.VERSION }}".
|
||||
run: docker build --target compile -t "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" -t "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.VERSION }}" .
|
||||
env:
|
||||
version: $(cat package.json | awk 'match($0, /version/) {print $2}' | sed 's/[\",]//g') # extracts the version number from the package.json with bash magic
|
||||
- name: Push Container
|
||||
run: docker push "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest"
|
||||
run: docker push --all-tags "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}"
|
||||
|
18
.gitea/workflows/test.yaml
Normal file
18
.gitea/workflows/test.yaml
Normal file
@ -0,0 +1,18 @@
|
||||
name: Run unit tests
|
||||
on: [pull_request]
|
||||
env:
|
||||
REGISTRY: gitea.brudi.xyz
|
||||
IMAGE_NAME: ${{ gitea.repository }}
|
||||
USER: ${{ gitea.actor }}
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
container: catthehacker/ubuntu:act-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Run Tests
|
||||
run: docker build --target test .
|
||||
|
16
Dockerfile
16
Dockerfile
@ -1,11 +1,21 @@
|
||||
FROM node:alpine as Build
|
||||
ENV NODE_ENV=production
|
||||
FROM node:alpine as files
|
||||
ENV TZ="Europe/Berlin"
|
||||
WORKDIR /app
|
||||
|
||||
COPY [ "package-lock.json", "package.json", "index.ts", "tsconfig.json", "./" ]
|
||||
COPY server ./server
|
||||
|
||||
FROM files as proddependencies
|
||||
ENV NODE_ENV=production
|
||||
RUN npm ci --omit=dev
|
||||
|
||||
FROM proddependencies as compile
|
||||
RUN npm run build
|
||||
CMD ["npm","run","start"]
|
||||
|
||||
FROM files as dependencies
|
||||
RUN npm ci
|
||||
|
||||
FROM dependencies as test
|
||||
COPY jest.config.js .
|
||||
COPY tests ./tests
|
||||
RUN npm run test
|
||||
|
18
jest.config.js
Normal file
18
jest.config.js
Normal file
@ -0,0 +1,18 @@
|
||||
module.exports = {
|
||||
'roots': [
|
||||
'<rootDir>/tests',
|
||||
'<rootDir>/server'
|
||||
],
|
||||
'transform': {
|
||||
'^.+\\.tsx?$': 'ts-jest'
|
||||
},
|
||||
'testRegex': '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
|
||||
'moduleFileExtensions': [
|
||||
'ts',
|
||||
'tsx',
|
||||
'js',
|
||||
'jsx',
|
||||
'json',
|
||||
'node'
|
||||
],
|
||||
};
|
46
package-lock.json
generated
46
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "node-jellyfin-discord-bot",
|
||||
"version": "1.0.1",
|
||||
"version": "1.1.3",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "node-jellyfin-discord-bot",
|
||||
"version": "1.0.1",
|
||||
"version": "1.1.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@discordjs/rest": "^1.7.0",
|
||||
@ -17,6 +17,7 @@
|
||||
"@types/uuid": "^9.0.1",
|
||||
"axios": "^1.3.5",
|
||||
"date-fns": "^2.29.3",
|
||||
"date-fns-tz": "^2.0.0",
|
||||
"discord-api-types": "^0.37.38",
|
||||
"discord.js": "^14.9.0",
|
||||
"dotenv": "^16.0.3",
|
||||
@ -29,12 +30,13 @@
|
||||
"winston": "^3.8.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.0",
|
||||
"@types/jest": "^29.5.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.58.0",
|
||||
"@typescript-eslint/parser": "^5.58.0",
|
||||
"eslint": "^8.38.0",
|
||||
"jest": "^29.5.0",
|
||||
"jest-cli": "^29.5.0",
|
||||
"mockdate": "^3.0.5",
|
||||
"nodemon": "^2.0.22",
|
||||
"rimraf": "^5.0.0",
|
||||
"ts-jest": "^29.1.0"
|
||||
@ -1567,9 +1569,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/jest": {
|
||||
"version": "29.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.0.tgz",
|
||||
"integrity": "sha512-3Emr5VOl/aoBwnWcH/EFQvlSAmjV+XtV9GGu5mwdYew5vhQh0IUZx/60x0TzHDu09Bi7HMx10t/namdJw5QIcg==",
|
||||
"version": "29.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.2.tgz",
|
||||
"integrity": "sha512-mSoZVJF5YzGVCk+FsDxzDuH7s+SCkzrgKZzf0Z0T2WudhBUPoF6ktoTPC4R0ZoCPCV5xUvuU6ias5NvxcBcMMg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"expect": "^29.0.0",
|
||||
@ -2626,6 +2628,14 @@
|
||||
"url": "https://opencollective.com/date-fns"
|
||||
}
|
||||
},
|
||||
"node_modules/date-fns-tz": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-2.0.0.tgz",
|
||||
"integrity": "sha512-OAtcLdB9vxSXTWHdT8b398ARImVwQMyjfYGkKD2zaGpHseG2UPHbHjXELReErZFxWdSLph3c2zOaaTyHfOhERQ==",
|
||||
"peerDependencies": {
|
||||
"date-fns": ">=2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
@ -4980,6 +4990,12 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/mockdate": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/mockdate/-/mockdate-3.0.5.tgz",
|
||||
"integrity": "sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
@ -8130,9 +8146,9 @@
|
||||
}
|
||||
},
|
||||
"@types/jest": {
|
||||
"version": "29.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.0.tgz",
|
||||
"integrity": "sha512-3Emr5VOl/aoBwnWcH/EFQvlSAmjV+XtV9GGu5mwdYew5vhQh0IUZx/60x0TzHDu09Bi7HMx10t/namdJw5QIcg==",
|
||||
"version": "29.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.2.tgz",
|
||||
"integrity": "sha512-mSoZVJF5YzGVCk+FsDxzDuH7s+SCkzrgKZzf0Z0T2WudhBUPoF6ktoTPC4R0ZoCPCV5xUvuU6ias5NvxcBcMMg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"expect": "^29.0.0",
|
||||
@ -8905,6 +8921,12 @@
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz",
|
||||
"integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA=="
|
||||
},
|
||||
"date-fns-tz": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-2.0.0.tgz",
|
||||
"integrity": "sha512-OAtcLdB9vxSXTWHdT8b398ARImVwQMyjfYGkKD2zaGpHseG2UPHbHjXELReErZFxWdSLph3c2zOaaTyHfOhERQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
@ -10705,6 +10727,12 @@
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
|
||||
},
|
||||
"mockdate": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/mockdate/-/mockdate-3.0.5.tgz",
|
||||
"integrity": "sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
|
10
package.json
10
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "node-jellyfin-discord-bot",
|
||||
"version": "1.0.1",
|
||||
"version": "1.1.3",
|
||||
"description": "A discord bot to sync jellyfin accounts with discord roles",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
@ -13,6 +13,7 @@
|
||||
"@types/uuid": "^9.0.1",
|
||||
"axios": "^1.3.5",
|
||||
"date-fns": "^2.29.3",
|
||||
"date-fns-tz": "^2.0.0",
|
||||
"discord-api-types": "^0.37.38",
|
||||
"discord.js": "^14.9.0",
|
||||
"dotenv": "^16.0.3",
|
||||
@ -32,15 +33,18 @@
|
||||
"debuggable": "node build/index.js --inspect-brk",
|
||||
"monitor": "nodemon build/index.js",
|
||||
"lint": "eslint . --ext .ts",
|
||||
"lint-fix": "eslint . --ext .ts --fix"
|
||||
"lint-fix": "eslint . --ext .ts --fix",
|
||||
"test": "jest",
|
||||
"test-watch": "jest --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.0",
|
||||
"@types/jest": "^29.5.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.58.0",
|
||||
"@typescript-eslint/parser": "^5.58.0",
|
||||
"eslint": "^8.38.0",
|
||||
"jest": "^29.5.0",
|
||||
"jest-cli": "^29.5.0",
|
||||
"mockdate": "^3.0.5",
|
||||
"nodemon": "^2.0.22",
|
||||
"rimraf": "^5.0.0",
|
||||
"ts-jest": "^29.1.0"
|
||||
|
@ -3,7 +3,7 @@ import { Guild, GuildScheduledEvent, GuildScheduledEventEditOptions, GuildSchedu
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import { client } from '../..'
|
||||
import { config } from '../configuration'
|
||||
import { Emotes } from '../events/guildScheduledEventCreate'
|
||||
import { Emotes } from '../events/autoCreateVoteByWPEvent'
|
||||
import { Maybe } from '../interfaces'
|
||||
import { logger } from '../logger'
|
||||
import { Command } from '../structures/command'
|
||||
@ -75,7 +75,7 @@ export async function closePoll(guild: Guild, requestId: string) {
|
||||
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 body = `[Abstimmung beendet] für https://discord.com/events/${event.guildId}/${event.id}\n<@&${config.bot.announcement_role}> Wir gucken ${movie} am ${date} um ${time}`
|
||||
const options: MessageCreateOptions = {
|
||||
content: body,
|
||||
allowedMentions: { parse: ["roles"] }
|
||||
|
48
server/events/announceManualWatchparty.ts
Normal file
48
server/events/announceManualWatchparty.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { GuildScheduledEvent, TextChannel } from "discord.js";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import { client } from "../..";
|
||||
import { config } from "../configuration";
|
||||
import { createDateStringFromEvent } from "../helper/dateHelper";
|
||||
import { Maybe } from "../interfaces";
|
||||
import { logger } from "../logger";
|
||||
|
||||
|
||||
export const name = 'guildScheduledEventCreate'
|
||||
|
||||
export async function execute(event: GuildScheduledEvent) {
|
||||
const guildId = event.guildId
|
||||
const requestId = uuid()
|
||||
try {
|
||||
if (!event.description) {
|
||||
logger.debug("Got GuildScheduledEventCreate event. But has no description. Aborting.")
|
||||
return
|
||||
}
|
||||
|
||||
if (event.description.includes("!wp")) {
|
||||
logger.info("Got manual create event of watchparty event!", { guildId, requestId })
|
||||
if (event.description.includes("!private")) {
|
||||
logger.info("Event description contains \"!private\". Won't announce.", { guildId, requestId })
|
||||
return
|
||||
}
|
||||
|
||||
const channel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(guildId)
|
||||
|
||||
if (!channel) {
|
||||
logger.error("Could not obtain announcement channel. Aborting announcement.", { guildId, requestId })
|
||||
return
|
||||
}
|
||||
|
||||
const message = `[Watchparty] https://discord.com/events/${event.guildId}/${event.id} \nHey <@&${config.bot.announcement_role}>, wir gucken ${event.name} ${createDateStringFromEvent(event, guildId, requestId)}`
|
||||
|
||||
channel.send(message)
|
||||
} else {
|
||||
logger.debug("Got GuildScheduledEventCreate event but no !wp in description. Not creating manual wp announcement.", { guildId, requestId })
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
// sendFailureDM(error)
|
||||
logger.error(<string>error, { guildId, requestId })
|
||||
}
|
||||
|
||||
|
||||
}
|
63
server/events/autoCreateVoteByWPEvent.ts
Normal file
63
server/events/autoCreateVoteByWPEvent.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import { GuildScheduledEvent, Message, MessageCreateOptions, TextChannel } from "discord.js";
|
||||
import { ScheduledTask } from "node-cron";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import { client, yavinJellyfinHandler } from "../..";
|
||||
import { config } from "../configuration";
|
||||
import { createDateStringFromEvent } from "../helper/dateHelper";
|
||||
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 const NONE_OF_THAT = "❌"
|
||||
|
||||
export let task: ScheduledTask | undefined
|
||||
|
||||
export async function execute(event: GuildScheduledEvent) {
|
||||
const requestId = uuid()
|
||||
|
||||
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.getRandomMovieNames(5, event.guildId, requestId)
|
||||
|
||||
logger.info(`Got ${movies.length} random movies. Creating voting`, { guildId: event.guildId, requestId })
|
||||
logger.debug(`Movies: ${JSON.stringify(movies)}`, { 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
|
||||
}
|
||||
let message = `[Abstimmung] für https://discord.com/events/${event.guildId}/${event.id}\n<@&${config.bot.announcement_role}> Es gibt eine neue Abstimmung für die nächste Watchparty ${createDateStringFromEvent(event, event.guildId, requestId)}! Stimme hierunter für den nächsten Film ab!\n`
|
||||
|
||||
for (let i = 0; i < movies.length; i++) {
|
||||
message = message.concat(Emotes[i]).concat(": ").concat(movies[i]).concat("\n")
|
||||
}
|
||||
message = message.concat(NONE_OF_THAT).concat(": Wenn dir nichts davon gefällt.")
|
||||
|
||||
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])
|
||||
}
|
||||
sentMessage.react(NONE_OF_THAT)
|
||||
|
||||
// sentMessage.pin() //todo: uncomment when bot has permission to pin messages. Also update closepoll.ts to only fetch pinned messages
|
||||
}
|
||||
}
|
||||
|
52
server/events/deleteAnnouncementsWhenWPEnds.ts
Normal file
52
server/events/deleteAnnouncementsWhenWPEnds.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { Collection, GuildScheduledEvent, GuildScheduledEventStatus, Message } from "discord.js";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import { client } from "../..";
|
||||
import { logger } from "../logger";
|
||||
|
||||
|
||||
export const name = 'guildScheduledEventUpdate'
|
||||
|
||||
export async function execute(oldEvent: GuildScheduledEvent, newEvent: GuildScheduledEvent) {
|
||||
const requestId = uuid()
|
||||
try {
|
||||
if (!newEvent.guild) {
|
||||
logger.error("Event has no guild, aborting.", { guildId: newEvent.guildId, requestId })
|
||||
return
|
||||
}
|
||||
const guildId = newEvent.guildId
|
||||
|
||||
if (newEvent.description?.toLowerCase().includes("!wp") && newEvent.status === GuildScheduledEventStatus.Completed) {
|
||||
logger.info("A watchparty ended. Cleaning up announcements!", { guildId, requestId })
|
||||
const announcementChannel = client.getAnnouncementChannelForGuild(newEvent.guild.id)
|
||||
if (!announcementChannel) {
|
||||
logger.error("Could not find announcement channel. Aborting", { guildId: newEvent.guild.id, requestId })
|
||||
return
|
||||
}
|
||||
|
||||
const events = await newEvent.guild.scheduledEvents.fetch()
|
||||
|
||||
const wpAnnouncements = (await announcementChannel.messages.fetch()).filter(message => !message.cleanContent.includes("[initial]"))
|
||||
const announcementsWithoutEvent = filterAnnouncementsByPendingWPs(wpAnnouncements, events)
|
||||
logger.info(`Deleting ${announcementsWithoutEvent.length} announcements.`, { guildId, requestId })
|
||||
announcementsWithoutEvent.forEach(message => message.delete())
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(<string>error, { guildId: newEvent.guildId, requestId })
|
||||
}
|
||||
}
|
||||
|
||||
function filterAnnouncementsByPendingWPs(messages: Collection<string, Message<true>>, events: Collection<string, GuildScheduledEvent<GuildScheduledEventStatus>>): Message<true>[] {
|
||||
const filteredMessages: Message<true>[] = []
|
||||
for (const message of messages.values()) {
|
||||
let foundEventForMessage = false
|
||||
for (const event of events.values()) {
|
||||
if (message.cleanContent.includes(event.id)) { //announcement always has eventid because of eventbox
|
||||
foundEventForMessage = true
|
||||
}
|
||||
}
|
||||
if (!foundEventForMessage) {
|
||||
filteredMessages.push(message)
|
||||
}
|
||||
}
|
||||
return filteredMessages
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
import { Collection, GuildMember } from "discord.js"
|
||||
import { filterRolesFromMemberUpdate, getGuildSpecificTriggerRoleId } from "../helper/roleFilter"
|
||||
import { ChangedRoles, PermissionLevel } from "../interfaces"
|
||||
import { jellyfinHandler } from "../.."
|
||||
import { v4 as uuid } from "uuid"
|
||||
|
||||
export const name = 'guildMemberUpdate'
|
||||
export async function execute(oldMember: GuildMember, newMember: GuildMember) {
|
||||
try {
|
||||
const requestId = uuid()
|
||||
const changedRoles: ChangedRoles = filterRolesFromMemberUpdate(oldMember, newMember)
|
||||
const triggerRoleIds: Collection<string, PermissionLevel> = getGuildSpecificTriggerRoleId()
|
||||
|
||||
triggerRoleIds.forEach((level, key) => {
|
||||
const addedRoleMatches = changedRoles.addedRoles.find(aRole => aRole.id === key)
|
||||
if (addedRoleMatches) {
|
||||
jellyfinHandler.upsertUser(newMember, level, requestId)
|
||||
}
|
||||
const removedRoleMatches = changedRoles.removedRoles.find(rRole => rRole.id === key)
|
||||
if (removedRoleMatches) {
|
||||
jellyfinHandler.removeUser(newMember, level, requestId)
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,63 +0,0 @@
|
||||
import { format } from "date-fns";
|
||||
import { GuildScheduledEvent, Message, MessageCreateOptions, TextChannel } from "discord.js";
|
||||
import { ScheduledTask } from "node-cron";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import { client, yavinJellyfinHandler } from "../..";
|
||||
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.getRandomMovieNames(5, event.guildId, requestId)
|
||||
|
||||
logger.info(`Got ${movies.length} random movies. Creating voting`, { guildId: event.guildId, requestId })
|
||||
logger.debug(`Movies: ${JSON.stringify(movies)}`, { 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]).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])
|
||||
}
|
||||
|
||||
// sentMessage.pin() //todo: uncomment when bot has permission to pin messages. Also update closepoll.ts to only fetch pinned messages
|
||||
}
|
||||
}
|
||||
|
@ -1,64 +0,0 @@
|
||||
import { GuildMember, GuildScheduledEvent, GuildScheduledEventStatus } from "discord.js";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import { client, jellyfinHandler } from "../..";
|
||||
import { getGuildSpecificTriggerRoleId } from "../helper/roleFilter";
|
||||
import { logger } from "../logger";
|
||||
|
||||
|
||||
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 })
|
||||
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.status === GuildScheduledEventStatus.Active)
|
||||
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.`))
|
||||
})
|
||||
deleteJFUsers(newEvent.guildId, requestId)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function createJFUsers(members: GuildMember[], movieName: string, requestId?: string) {
|
||||
logger.info(`Creating users for: \n ${JSON.stringify(members, null, 2)}`)
|
||||
members.forEach(member => {
|
||||
member.createDM().then(channel => channel.send(`Hey! Du hast dich für die Watchparty von ${movieName} angemeldet! Es geht gleich los!`))
|
||||
jellyfinHandler.upsertUser(member, "TEMPORARY", requestId)
|
||||
})
|
||||
}
|
||||
|
||||
async function deleteJFUsers(guildId: string, requestId?: string) {
|
||||
logger.info(`Watchparty ended, deleting tmp users`, { guildId, requestId })
|
||||
jellyfinHandler.purge(guildId, requestId)
|
||||
}
|
30
server/events/handlePermJFAccountByRole.ts
Normal file
30
server/events/handlePermJFAccountByRole.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { Collection, GuildMember } from "discord.js"
|
||||
import { filterRolesFromMemberUpdate, getGuildSpecificTriggerRoleId } from "../helper/roleFilter"
|
||||
import { ChangedRoles, PermissionLevel } from "../interfaces"
|
||||
import { jellyfinHandler } from "../.."
|
||||
import { v4 as uuid } from "uuid"
|
||||
|
||||
export const name = 'guildMemberUpdate'
|
||||
export async function execute(oldMember: GuildMember, newMember: GuildMember) {
|
||||
try {
|
||||
const requestId = uuid()
|
||||
const changedRoles: ChangedRoles = filterRolesFromMemberUpdate(oldMember, newMember)
|
||||
const triggerRoleIds: Collection<string, PermissionLevel> = getGuildSpecificTriggerRoleId()
|
||||
|
||||
triggerRoleIds.forEach((level, key) => {
|
||||
const addedRoleMatches = changedRoles.addedRoles.find(aRole => aRole.id === key)
|
||||
if (addedRoleMatches) {
|
||||
jellyfinHandler.upsertUser(newMember, level, requestId)
|
||||
}
|
||||
const removedRoleMatches = changedRoles.removedRoles.find(rRole => rRole.id === key)
|
||||
if (removedRoleMatches) {
|
||||
jellyfinHandler.removeUser(newMember, level, requestId)
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
59
server/events/handleTempJFUserByVoiceEvent.ts
Normal file
59
server/events/handleTempJFUserByVoiceEvent.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { VoiceState } from "discord.js";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import { jellyfinHandler } from "../..";
|
||||
import { UserUpsertResult } from "../jellyfin/handler";
|
||||
import { logger } from "../logger";
|
||||
|
||||
|
||||
export const name = 'voiceStateUpdate'
|
||||
|
||||
export async function execute(oldState: VoiceState, newState: VoiceState) {
|
||||
|
||||
try {
|
||||
logger.info(JSON.stringify(newState, null, 2))
|
||||
//ignore events like mute/unmute
|
||||
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
|
||||
}
|
||||
|
||||
const scheduledEvents = (await newState.guild.scheduledEvents.fetch())
|
||||
.filter((key) => key.description?.toLowerCase().includes("!wp") && key.isActive())
|
||||
.map((key) => key)
|
||||
|
||||
const scheduledEventUsers = (await Promise.all(scheduledEvents.map(event => event.fetchSubscribers({ withMember: true }))))
|
||||
|
||||
//Dont handle users, that are already subscribed to the event. We only want to handle unsubscribed users here
|
||||
let userFound = false;
|
||||
scheduledEventUsers.forEach(collection => {
|
||||
collection.each(key => {
|
||||
logger.info(JSON.stringify(key, null, 2))
|
||||
if (key.member.user.id === newState.member?.user.id)
|
||||
userFound = true;
|
||||
})
|
||||
})
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
if (scheduledEvents.find(event => event.channelId === newState.channelId)) {
|
||||
if (newState.member) {
|
||||
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())
|
||||
if (result === UserUpsertResult.created) {
|
||||
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 {
|
||||
logger.error("WTF? Expected Member?? When doing things")
|
||||
}
|
||||
} else {
|
||||
logger.info("VoiceState channelId was not the id of any channel with events")
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error)
|
||||
}
|
||||
}
|
58
server/events/handleTempJFUsersByWPEvents.ts
Normal file
58
server/events/handleTempJFUsersByWPEvents.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { GuildMember, GuildScheduledEvent, GuildScheduledEventStatus } from "discord.js";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import { jellyfinHandler } from "../..";
|
||||
import { getGuildSpecificTriggerRoleId } from "../helper/roleFilter";
|
||||
import { logger } from "../logger";
|
||||
|
||||
|
||||
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 })
|
||||
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.status === GuildScheduledEventStatus.Active)
|
||||
createJFUsers(members, newEvent.name, requestId)
|
||||
else {
|
||||
|
||||
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.`))
|
||||
})
|
||||
deleteJFUsers(newEvent.guildId, requestId)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function createJFUsers(members: GuildMember[], movieName: string, requestId?: string) {
|
||||
logger.info(`Creating users for: \n ${JSON.stringify(members, null, 2)}`)
|
||||
members.forEach(member => {
|
||||
member.createDM().then(channel => channel.send(`Hey! Du hast dich für die Watchparty von ${movieName} angemeldet! Es geht gleich los!`))
|
||||
jellyfinHandler.upsertUser(member, "TEMPORARY", requestId)
|
||||
})
|
||||
}
|
||||
|
||||
async function deleteJFUsers(guildId: string, requestId?: string) {
|
||||
logger.info(`Watchparty ended, deleting tmp users`, { guildId, requestId })
|
||||
jellyfinHandler.purge(guildId, requestId)
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
import { VoiceState } from "discord.js";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import { jellyfinHandler } from "../..";
|
||||
import { UserUpsertResult } from "../jellyfin/handler";
|
||||
import { logger } from "../logger";
|
||||
|
||||
|
||||
export const name = 'voiceStateUpdate'
|
||||
|
||||
export async function execute(oldState: VoiceState, newState: VoiceState) {
|
||||
|
||||
try {
|
||||
logger.info(JSON.stringify(newState, null, 2))
|
||||
//ignore events like mute/unmute
|
||||
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
|
||||
}
|
||||
|
||||
const scheduledEvents = (await newState.guild.scheduledEvents.fetch())
|
||||
.filter((key) => key.description?.toLowerCase().includes("!wp") && key.isActive())
|
||||
.map((key) => key)
|
||||
|
||||
const scheduledEventUsers = (await Promise.all(scheduledEvents.map(event => event.fetchSubscribers({withMember: true}))))
|
||||
|
||||
//Dont handle users, that are already subscribed to the event. We only want to handle unsubscribed users here
|
||||
let userFound = false;
|
||||
scheduledEventUsers.forEach(collection => {
|
||||
collection.each(key => {
|
||||
logger.info(JSON.stringify(key, null, 2))
|
||||
if(key.member.user.id === newState.member?.user.id)
|
||||
userFound = true;
|
||||
})
|
||||
})
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
if (scheduledEvents.find(event => event.channelId === newState.channelId)) {
|
||||
if(newState.member){
|
||||
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())
|
||||
if (result === UserUpsertResult.created) {
|
||||
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 {
|
||||
logger.error("WTF? Expected Member?? When doing things")
|
||||
}
|
||||
} else {
|
||||
logger.info("VoiceState channelId was not the id of any channel with events")
|
||||
}
|
||||
}catch(error){
|
||||
logger.error(error)
|
||||
}
|
||||
}
|
23
server/helper/dateHelper.ts
Normal file
23
server/helper/dateHelper.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { format, isToday, toDate } from "date-fns";
|
||||
import { utcToZonedTime } from "date-fns-tz"
|
||||
import { GuildScheduledEvent } from "discord.js";
|
||||
import { logger } from "../logger";
|
||||
import de from "date-fns/locale/de";
|
||||
|
||||
export function createDateStringFromEvent(event: GuildScheduledEvent, requestId: string, guildId?: string): string {
|
||||
if (!event.scheduledStartAt) {
|
||||
logger.error("Event has no start. Cannot create dateString.", { guildId, requestId })
|
||||
return `"habe keinen Startzeitpunkt ermitteln können"`
|
||||
}
|
||||
|
||||
const timeZone = 'Europe/Berlin'
|
||||
const zonedDateTime = utcToZonedTime(event.scheduledStartAt, timeZone)
|
||||
const time = format(zonedDateTime, "HH:mm", { locale: de })
|
||||
|
||||
if (isToday(zonedDateTime)) {
|
||||
return `heute um ${time}`
|
||||
}
|
||||
|
||||
const date = format(zonedDateTime, "eeee dd.MM", { locale: de })
|
||||
return `am ${date} um ${time}`
|
||||
}
|
@ -2,7 +2,7 @@ import { GuildMember } from "discord.js";
|
||||
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 { BaseItemDto, UpdateUserPasswordRequest, UpdateUserPolicyRequest } from "./models";
|
||||
import { UserDto } from "./models/UserDto";
|
||||
import { Configuration, ConfigurationParameters } from "./runtime";
|
||||
|
||||
@ -52,24 +52,46 @@ export class JellyfinHandler {
|
||||
return (Math.random() * 10000 + 10000).toFixed(0)
|
||||
}
|
||||
|
||||
public async createUserAccountForDiscordUser(discordUser: GuildMember, level: PermissionLevel, guildId?: string, requestId?: string): Promise<UserDto> {
|
||||
public async createUserAccountForDiscordUser(discordUser: GuildMember, level: PermissionLevel, requestId: string, guildId?: string): Promise<UserDto> {
|
||||
const newUserName = this.generateJFUserName(discordUser, level)
|
||||
logger.info(`New Username for ${discordUser.displayName}: ${newUserName}`, { guildId, requestId })
|
||||
const req: CreateUserByNameOperationRequest = {
|
||||
createUserByNameRequest: {
|
||||
name: newUserName,
|
||||
password: this.generatePasswordForUser(),
|
||||
password: this.generatePasswordForUser()
|
||||
}
|
||||
}
|
||||
logger.debug(JSON.stringify(req), { requestId, guildId })
|
||||
const createResult = await this.userApi.createUserByName(req)
|
||||
if (createResult) {
|
||||
if (createResult.policy) {
|
||||
this.setUserPermissions(createResult, requestId, guildId)
|
||||
}
|
||||
(await discordUser.createDM()).send(`Ich hab dir mal nen Account angelegt :)\nDein Username ist ${createResult.name}, dein Password ist "${req.createUserByNameRequest.password}"!`)
|
||||
return createResult
|
||||
}
|
||||
else throw new Error('Could not create User in Jellyfin')
|
||||
}
|
||||
|
||||
public async setUserPermissions(user: UserDto, requestId: string, guildId?: string) {
|
||||
if (!user.policy || !user.id) {
|
||||
logger.error(`Cannot update user policy. User ${user.name} has no policy to modify`, { guildId, requestId })
|
||||
return
|
||||
}
|
||||
user.policy.enableVideoPlaybackTranscoding = false
|
||||
|
||||
const operation: UpdateUserPolicyRequest = {
|
||||
...user.policy,
|
||||
enableVideoPlaybackTranscoding: false
|
||||
}
|
||||
|
||||
const request: UpdateUserPolicyOperationRequest = {
|
||||
userId: user.id,
|
||||
updateUserPolicyRequest: operation
|
||||
}
|
||||
this.userApi.updateUserPolicy(request)
|
||||
}
|
||||
|
||||
public async isUserAlreadyPresent(discordUser: GuildMember, requestId?: string): Promise<boolean> {
|
||||
const jfuser = await this.getUser(discordUser, requestId)
|
||||
logger.debug(`Presence for DiscordUser ${discordUser.id}:${jfuser !== undefined}`, { guildId: discordUser.guild.id, requestId })
|
||||
|
16
tests/helpers/date.test.ts
Normal file
16
tests/helpers/date.test.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { GuildScheduledEvent } from "discord.js"
|
||||
import { createDateStringFromEvent } from "../../server/helper/dateHelper"
|
||||
import MockDate from 'mockdate'
|
||||
|
||||
beforeAll(() => {
|
||||
MockDate.set('01-01-2023')
|
||||
})
|
||||
|
||||
function getTestDate(date: string): GuildScheduledEvent {
|
||||
return <GuildScheduledEvent>{ scheduledStartAt: new Date(date) }
|
||||
}
|
||||
test('createDateStringFromEvent - correct formatting', () => {
|
||||
expect(createDateStringFromEvent(getTestDate('01-01-2023 12:30'), "")).toEqual('heute um 12:30')
|
||||
expect(createDateStringFromEvent(getTestDate('01-02-2023 12:30'), "")).toEqual('am Montag 02.01 um 12:30')
|
||||
expect(createDateStringFromEvent(getTestDate('01-03-2023 12:30'), "")).toEqual('am Dienstag 03.01 um 12:30')
|
||||
})
|
28
tests/helpers/memberRoles.test.ts
Normal file
28
tests/helpers/memberRoles.test.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { Collection, GuildMember, Role } from "discord.js"
|
||||
import { filterRolesFromMemberUpdate } from "../../server/helper/roleFilter"
|
||||
|
||||
function buildFakeRole(id: string, name: string): Role {
|
||||
return <Role>{ id, name }
|
||||
|
||||
}
|
||||
test('filterRolesFromMemberUpdate', () => {
|
||||
const oldMemberRoles: Collection<string, Role> = new Collection<string, Role>()
|
||||
oldMemberRoles.set('1', buildFakeRole('01', 'Role01'))
|
||||
oldMemberRoles.set('2', buildFakeRole('02', 'Role02'))
|
||||
|
||||
const newMemberRoles: Collection<string, Role> = new Collection<string, Role>()
|
||||
newMemberRoles.set('1', buildFakeRole('01', 'Role01'))
|
||||
newMemberRoles.set('2', buildFakeRole('02', 'Role02'))
|
||||
newMemberRoles.set('3', buildFakeRole('03', 'Role03'))
|
||||
|
||||
const oldMember: GuildMember = <GuildMember>{ roles: { cache: oldMemberRoles }, guild: { id: "guildid" } }
|
||||
const newMember: GuildMember = <GuildMember>{ roles: { cache: newMemberRoles }, guild: { id: "guildid" } }
|
||||
const output = filterRolesFromMemberUpdate(oldMember, newMember)
|
||||
|
||||
const expectedAddedRoles: Collection<string, Role> = new Collection<string, Role>()
|
||||
expectedAddedRoles.set('3', buildFakeRole('03', 'Role03'))
|
||||
const expectedRemovedRoles: Collection<string, Role> = new Collection<string, Role>()
|
||||
|
||||
expect(output.addedRoles).toEqual(expectedAddedRoles)
|
||||
expect(output.removedRoles).toEqual(expectedRemovedRoles)
|
||||
})
|
Reference in New Issue
Block a user