Compare commits
24 Commits
feat/cicd
...
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 |
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
|
@ -14,4 +14,4 @@ jobs:
|
|||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
- name: Build Container
|
- name: Build Container
|
||||||
run: docker build .
|
run: docker build --target compile .
|
||||||
|
@ -11,7 +11,6 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
build-docker-image:
|
build-docker-image:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
#if: gitea.ref == 'refs/heads/master'
|
|
||||||
container: catthehacker/ubuntu:act-latest
|
container: catthehacker/ubuntu:act-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@ -22,6 +21,8 @@ jobs:
|
|||||||
- name: Log in to the Container registry
|
- name: Log in to the Container registry
|
||||||
run: docker login -u ${{ env.USER }} -p ${{ secrets.TOKEN }} ${{ env.REGISTRY }}
|
run: docker login -u ${{ env.USER }} -p ${{ secrets.TOKEN }} ${{ env.REGISTRY }}
|
||||||
- name: Build Container
|
- name: Build Container
|
||||||
run: docker build -t "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" .
|
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
|
- name: Push Container
|
||||||
run: docker push --all-tags "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}"
|
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
|
FROM node:alpine as files
|
||||||
ENV NODE_ENV=production
|
ENV TZ="Europe/Berlin"
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY [ "package-lock.json", "package.json", "index.ts", "tsconfig.json", "./" ]
|
COPY [ "package-lock.json", "package.json", "index.ts", "tsconfig.json", "./" ]
|
||||||
COPY server ./server
|
COPY server ./server
|
||||||
|
|
||||||
|
FROM files as proddependencies
|
||||||
|
ENV NODE_ENV=production
|
||||||
RUN npm ci --omit=dev
|
RUN npm ci --omit=dev
|
||||||
|
|
||||||
|
FROM proddependencies as compile
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
CMD ["npm","run","start"]
|
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
|
||||||
|
4
index.ts
4
index.ts
@ -5,8 +5,8 @@ import { JellyfinHandler } from "./server/jellyfin/handler"
|
|||||||
import { attachedImages } from "./server/assets/attachments"
|
import { attachedImages } from "./server/assets/attachments"
|
||||||
const requestId = 'startup'
|
const requestId = 'startup'
|
||||||
|
|
||||||
export const jellyfinHandler = new JellyfinHandler({jellyfinToken: config.bot.workaround_token, jellyfinUrl: config.bot.jellyfin_url, movieCollectionId: config.bot.jf_collection_id, collectionUser: config.bot.jf_user})
|
export const jellyfinHandler = new JellyfinHandler({ jellyfinToken: config.bot.workaround_token, jellyfinUrl: config.bot.jellyfin_url, movieCollectionId: config.bot.jf_collection_id, collectionUser: config.bot.jf_user })
|
||||||
export const yavinJellyfinHandler = new JellyfinHandler({jellyfinToken: config.bot.yavin_jellyfin_token, jellyfinUrl: config.bot.yavin_jellyfin_url, movieCollectionId: config.bot.yavin_collection_id, collectionUser: config.bot.yavin_jellyfin_collection_user})
|
export const yavinJellyfinHandler = new JellyfinHandler({ jellyfinToken: config.bot.yavin_jellyfin_token, jellyfinUrl: config.bot.yavin_jellyfin_url, movieCollectionId: config.bot.yavin_collection_id, collectionUser: config.bot.yavin_jellyfin_collection_user })
|
||||||
|
|
||||||
export const client = new ExtendedClient(jellyfinHandler)
|
export const client = new ExtendedClient(jellyfinHandler)
|
||||||
|
|
||||||
|
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'
|
||||||
|
],
|
||||||
|
};
|
27
package-lock.json
generated
27
package-lock.json
generated
@ -30,12 +30,13 @@
|
|||||||
"winston": "^3.8.2"
|
"winston": "^3.8.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.5.0",
|
"@types/jest": "^29.5.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.58.0",
|
"@typescript-eslint/eslint-plugin": "^5.58.0",
|
||||||
"@typescript-eslint/parser": "^5.58.0",
|
"@typescript-eslint/parser": "^5.58.0",
|
||||||
"eslint": "^8.38.0",
|
"eslint": "^8.38.0",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"jest-cli": "^29.5.0",
|
"jest-cli": "^29.5.0",
|
||||||
|
"mockdate": "^3.0.5",
|
||||||
"nodemon": "^2.0.22",
|
"nodemon": "^2.0.22",
|
||||||
"rimraf": "^5.0.0",
|
"rimraf": "^5.0.0",
|
||||||
"ts-jest": "^29.1.0"
|
"ts-jest": "^29.1.0"
|
||||||
@ -1568,9 +1569,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/jest": {
|
"node_modules/@types/jest": {
|
||||||
"version": "29.5.0",
|
"version": "29.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.2.tgz",
|
||||||
"integrity": "sha512-3Emr5VOl/aoBwnWcH/EFQvlSAmjV+XtV9GGu5mwdYew5vhQh0IUZx/60x0TzHDu09Bi7HMx10t/namdJw5QIcg==",
|
"integrity": "sha512-mSoZVJF5YzGVCk+FsDxzDuH7s+SCkzrgKZzf0Z0T2WudhBUPoF6ktoTPC4R0ZoCPCV5xUvuU6ias5NvxcBcMMg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"expect": "^29.0.0",
|
"expect": "^29.0.0",
|
||||||
@ -4989,6 +4990,12 @@
|
|||||||
"node": ">=10"
|
"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": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
@ -8139,9 +8146,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/jest": {
|
"@types/jest": {
|
||||||
"version": "29.5.0",
|
"version": "29.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.2.tgz",
|
||||||
"integrity": "sha512-3Emr5VOl/aoBwnWcH/EFQvlSAmjV+XtV9GGu5mwdYew5vhQh0IUZx/60x0TzHDu09Bi7HMx10t/namdJw5QIcg==",
|
"integrity": "sha512-mSoZVJF5YzGVCk+FsDxzDuH7s+SCkzrgKZzf0Z0T2WudhBUPoF6ktoTPC4R0ZoCPCV5xUvuU6ias5NvxcBcMMg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"expect": "^29.0.0",
|
"expect": "^29.0.0",
|
||||||
@ -10720,6 +10727,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
|
"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": {
|
"ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
@ -33,15 +33,18 @@
|
|||||||
"debuggable": "node build/index.js --inspect-brk",
|
"debuggable": "node build/index.js --inspect-brk",
|
||||||
"monitor": "nodemon build/index.js",
|
"monitor": "nodemon build/index.js",
|
||||||
"lint": "eslint . --ext .ts",
|
"lint": "eslint . --ext .ts",
|
||||||
"lint-fix": "eslint . --ext .ts --fix"
|
"lint-fix": "eslint . --ext .ts --fix",
|
||||||
|
"test": "jest",
|
||||||
|
"test-watch": "jest --watch"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.5.0",
|
"@types/jest": "^29.5.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.58.0",
|
"@typescript-eslint/eslint-plugin": "^5.58.0",
|
||||||
"@typescript-eslint/parser": "^5.58.0",
|
"@typescript-eslint/parser": "^5.58.0",
|
||||||
"eslint": "^8.38.0",
|
"eslint": "^8.38.0",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"jest-cli": "^29.5.0",
|
"jest-cli": "^29.5.0",
|
||||||
|
"mockdate": "^3.0.5",
|
||||||
"nodemon": "^2.0.22",
|
"nodemon": "^2.0.22",
|
||||||
"rimraf": "^5.0.0",
|
"rimraf": "^5.0.0",
|
||||||
"ts-jest": "^29.1.0"
|
"ts-jest": "^29.1.0"
|
||||||
|
@ -13,22 +13,22 @@ export default new Command({
|
|||||||
options: [{
|
options: [{
|
||||||
name: "typ",
|
name: "typ",
|
||||||
type: ApplicationCommandOptionType.String,
|
type: ApplicationCommandOptionType.String,
|
||||||
description:"Was für ein announcement?",
|
description: "Was für ein announcement?",
|
||||||
choices: [{name: "initial", value:"initial"},{name: "votepls", value:"votepls"},{name: "cancel", value:"cancel"}],
|
choices: [{ name: "initial", value: "initial" }, { name: "votepls", value: "votepls" }, { name: "cancel", value: "cancel" }],
|
||||||
required: true
|
required: true
|
||||||
}],
|
}],
|
||||||
run: async (interaction: RunOptions) => {
|
run: async (interaction: RunOptions) => {
|
||||||
const command = interaction.interaction
|
const command = interaction.interaction
|
||||||
const requestId = uuid()
|
const requestId = uuid()
|
||||||
if(!command.guildId) {
|
if (!command.guildId) {
|
||||||
logger.error("COMMAND DOES NOT HAVE A GUILD ID; CANCELLING!!!", {requestId})
|
logger.error("COMMAND DOES NOT HAVE A GUILD ID; CANCELLING!!!", { requestId })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const guildId = command.guildId
|
const guildId = command.guildId
|
||||||
const announcementType = command.options.data.find(option => option.name.includes("typ"))
|
const announcementType = command.options.data.find(option => option.name.includes("typ"))
|
||||||
logger.info(`Got command for announcing ${announcementType?.value}!`, { guildId, requestId })
|
logger.info(`Got command for announcing ${announcementType?.value}!`, { guildId, requestId })
|
||||||
|
|
||||||
if(!announcementType) {
|
if (!announcementType) {
|
||||||
logger.error("Did not get an announcement type!", { guildId, requestId })
|
logger.error("Did not get an announcement type!", { guildId, requestId })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -40,7 +40,7 @@ export default new Command({
|
|||||||
logger.info(`User ${command.member.displayName} seems to be admin`)
|
logger.info(`User ${command.member.displayName} seems to be admin`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if((<string>announcementType.value).includes("initial")) {
|
if ((<string>announcementType.value).includes("initial")) {
|
||||||
sendInitialAnnouncement(guildId, requestId)
|
sendInitialAnnouncement(guildId, requestId)
|
||||||
command.followUp("Ist rausgeschickt!")
|
command.followUp("Ist rausgeschickt!")
|
||||||
} else {
|
} else {
|
||||||
@ -56,7 +56,7 @@ function isAdmin(member: GuildMember): boolean {
|
|||||||
async function sendInitialAnnouncement(guildId: string, requestId: string): Promise<void> {
|
async function sendInitialAnnouncement(guildId: string, requestId: string): Promise<void> {
|
||||||
logger.info("Sending initial announcement")
|
logger.info("Sending initial announcement")
|
||||||
const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(guildId)
|
const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(guildId)
|
||||||
if(!announcementChannel) {
|
if (!announcementChannel) {
|
||||||
logger.error("Could not find announcement channel. Aborting", { guildId, requestId })
|
logger.error("Could not find announcement channel. Aborting", { guildId, requestId })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -96,7 +96,7 @@ export async function manageAnnouncementRoles(guild: Guild, reaction: MessageRea
|
|||||||
const allUsers = (await guild.members.fetch())
|
const allUsers = (await guild.members.fetch())
|
||||||
|
|
||||||
const usersWhoHaveRole: GuildMember[] = allUsers
|
const usersWhoHaveRole: GuildMember[] = allUsers
|
||||||
.filter(member=> member.roles.cache
|
.filter(member => member.roles.cache
|
||||||
.find(role => role.id === config.bot.announcement_role) !== undefined)
|
.find(role => role.id === config.bot.announcement_role) !== undefined)
|
||||||
.map(member => member)
|
.map(member => member)
|
||||||
|
|
||||||
@ -105,15 +105,15 @@ export async function manageAnnouncementRoles(guild: Guild, reaction: MessageRea
|
|||||||
|
|
||||||
const usersWhoDontHaveRole: GuildMember[] = allUsers
|
const usersWhoDontHaveRole: GuildMember[] = allUsers
|
||||||
.filter(member => member.roles.cache
|
.filter(member => member.roles.cache
|
||||||
.find(role=> role.id === config.bot.announcement_role) === undefined)
|
.find(role => role.id === config.bot.announcement_role) === undefined)
|
||||||
.map(member => member)
|
.map(member => member)
|
||||||
|
|
||||||
const usersWhoNeedRole: GuildMember[] = usersWhoDontHaveRole
|
const usersWhoNeedRole: GuildMember[] = usersWhoDontHaveRole
|
||||||
.filter(userWhoNeeds => usersWhoWantRole.map(wanter => wanter.id).includes(userWhoNeeds.id))
|
.filter(userWhoNeeds => usersWhoWantRole.map(wanter => wanter.id).includes(userWhoNeeds.id))
|
||||||
|
|
||||||
|
|
||||||
logger.debug(`Theses users will get the role removed: ${JSON.stringify(usersWhoNeedRoleRevoked)}`, {guildId, requestId})
|
logger.debug(`Theses users will get the role removed: ${JSON.stringify(usersWhoNeedRoleRevoked)}`, { guildId, requestId })
|
||||||
logger.debug(`Theses users will get the role added: ${JSON.stringify(usersWhoNeedRole)}`, {guildId, requestId})
|
logger.debug(`Theses users will get the role added: ${JSON.stringify(usersWhoNeedRole)}`, { guildId, requestId })
|
||||||
|
|
||||||
usersWhoNeedRoleRevoked.forEach(user => user.roles.remove(announcementRole))
|
usersWhoNeedRoleRevoked.forEach(user => user.roles.remove(announcementRole))
|
||||||
usersWhoNeedRole.forEach(user => user.roles.add(announcementRole))
|
usersWhoNeedRole.forEach(user => user.roles.add(announcementRole))
|
||||||
|
@ -20,7 +20,7 @@ export async function execute(event: GuildScheduledEvent) {
|
|||||||
|
|
||||||
if (event.description.includes("!wp")) {
|
if (event.description.includes("!wp")) {
|
||||||
logger.info("Got manual create event of watchparty event!", { guildId, requestId })
|
logger.info("Got manual create event of watchparty event!", { guildId, requestId })
|
||||||
if(event.description.includes("!private")) {
|
if (event.description.includes("!private")) {
|
||||||
logger.info("Event description contains \"!private\". Won't announce.", { guildId, requestId })
|
logger.info("Event description contains \"!private\". Won't announce.", { guildId, requestId })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -28,14 +28,14 @@ export async function execute(event: GuildScheduledEvent) {
|
|||||||
logger.debug(`Movies: ${JSON.stringify(movies)}`, { guildId: event.guildId, requestId })
|
logger.debug(`Movies: ${JSON.stringify(movies)}`, { guildId: event.guildId, requestId })
|
||||||
|
|
||||||
const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(event.guildId)
|
const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(event.guildId)
|
||||||
if(!announcementChannel) {
|
if (!announcementChannel) {
|
||||||
logger.error("Could not find announcement channel. Aborting", { guildId: event.guildId, requestId })
|
logger.error("Could not find announcement channel. Aborting", { guildId: event.guildId, requestId })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.debug(`Found channel ${JSON.stringify(announcementChannel, null, 2)}`, { guildId: event.guildId, requestId })
|
logger.debug(`Found channel ${JSON.stringify(announcementChannel, null, 2)}`, { guildId: event.guildId, requestId })
|
||||||
|
|
||||||
if(!event.scheduledStartAt) {
|
if (!event.scheduledStartAt) {
|
||||||
logger.info("EVENT DOES NOT HAVE STARTDATE; CANCELLING", {guildId: event.guildId, requestId})
|
logger.info("EVENT DOES NOT HAVE STARTDATE; CANCELLING", { guildId: event.guildId, requestId })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let message = `[Abstimmung] für https://discord.com/events/${event.guildId}/${event.id}\n<@&${config.bot.announcement_role}> Es gibt eine neue Abstimmung für die nächste Watchparty ${createDateStringFromEvent(event, event.guildId, requestId)}! Stimme hierunter für den nächsten Film ab!\n`
|
let message = `[Abstimmung] für https://discord.com/events/${event.guildId}/${event.id}\n<@&${config.bot.announcement_role}> Es gibt eine neue Abstimmung für die nächste Watchparty ${createDateStringFromEvent(event, event.guildId, requestId)}! Stimme hierunter für den nächsten Film ab!\n`
|
||||||
@ -46,7 +46,7 @@ export async function execute(event: GuildScheduledEvent) {
|
|||||||
message = message.concat(NONE_OF_THAT).concat(": Wenn dir nichts davon gefällt.")
|
message = message.concat(NONE_OF_THAT).concat(": Wenn dir nichts davon gefällt.")
|
||||||
|
|
||||||
const options: MessageCreateOptions = {
|
const options: MessageCreateOptions = {
|
||||||
allowedMentions: { parse: ["roles"]},
|
allowedMentions: { parse: ["roles"] },
|
||||||
content: message,
|
content: message,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ export async function execute(oldEvent: GuildScheduledEvent, newEvent: GuildSche
|
|||||||
|
|
||||||
const wpAnnouncements = (await announcementChannel.messages.fetch()).filter(message => !message.cleanContent.includes("[initial]"))
|
const wpAnnouncements = (await announcementChannel.messages.fetch()).filter(message => !message.cleanContent.includes("[initial]"))
|
||||||
const announcementsWithoutEvent = filterAnnouncementsByPendingWPs(wpAnnouncements, events)
|
const announcementsWithoutEvent = filterAnnouncementsByPendingWPs(wpAnnouncements, events)
|
||||||
logger.info(`Deleting ${announcementsWithoutEvent.length} announcements.`, {guildId, requestId})
|
logger.info(`Deleting ${announcementsWithoutEvent.length} announcements.`, { guildId, requestId })
|
||||||
announcementsWithoutEvent.forEach(message => message.delete())
|
announcementsWithoutEvent.forEach(message => message.delete())
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -44,7 +44,7 @@ function filterAnnouncementsByPendingWPs(messages: Collection<string, Message<tr
|
|||||||
foundEventForMessage = true
|
foundEventForMessage = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!foundEventForMessage){
|
if (!foundEventForMessage) {
|
||||||
filteredMessages.push(message)
|
filteredMessages.push(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ export async function execute(oldState: VoiceState, newState: VoiceState) {
|
|||||||
try {
|
try {
|
||||||
logger.info(JSON.stringify(newState, null, 2))
|
logger.info(JSON.stringify(newState, null, 2))
|
||||||
//ignore events like mute/unmute
|
//ignore events like mute/unmute
|
||||||
if(newState.channel?.id === oldState.channel?.id) {
|
if (newState.channel?.id === oldState.channel?.id) {
|
||||||
logger.info("Not handling VoiceState event because channelid of old and new was the same (i.e. mute/unmute event)")
|
logger.info("Not handling VoiceState event because channelid of old and new was the same (i.e. mute/unmute event)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -21,25 +21,25 @@ export async function execute(oldState: VoiceState, newState: VoiceState) {
|
|||||||
.filter((key) => key.description?.toLowerCase().includes("!wp") && key.isActive())
|
.filter((key) => key.description?.toLowerCase().includes("!wp") && key.isActive())
|
||||||
.map((key) => key)
|
.map((key) => key)
|
||||||
|
|
||||||
const scheduledEventUsers = (await Promise.all(scheduledEvents.map(event => event.fetchSubscribers({withMember: true}))))
|
const scheduledEventUsers = (await Promise.all(scheduledEvents.map(event => event.fetchSubscribers({ withMember: true }))))
|
||||||
|
|
||||||
//Dont handle users, that are already subscribed to the event. We only want to handle unsubscribed users here
|
//Dont handle users, that are already subscribed to the event. We only want to handle unsubscribed users here
|
||||||
let userFound = false;
|
let userFound = false;
|
||||||
scheduledEventUsers.forEach(collection => {
|
scheduledEventUsers.forEach(collection => {
|
||||||
collection.each(key => {
|
collection.each(key => {
|
||||||
logger.info(JSON.stringify(key, null, 2))
|
logger.info(JSON.stringify(key, null, 2))
|
||||||
if(key.member.user.id === newState.member?.user.id)
|
if (key.member.user.id === newState.member?.user.id)
|
||||||
userFound = true;
|
userFound = true;
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
if(userFound) {
|
if (userFound) {
|
||||||
logger.info(`Not handling VoiceState event because user was already subscribed and got an account from there. User: ${JSON.stringify(newState.member, null, 2)}`)
|
logger.info(`Not handling VoiceState event because user was already subscribed and got an account from there. User: ${JSON.stringify(newState.member, null, 2)}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (scheduledEvents.find(event => event.channelId === newState.channelId)) {
|
if (scheduledEvents.find(event => event.channelId === newState.channelId)) {
|
||||||
if(newState.member){
|
if (newState.member) {
|
||||||
logger.info("YO! Da ist jemand dem Channel mit dem Event beigetreten, ich kümmer mich mal um nen Account!")
|
logger.info("YO! Da ist jemand dem Channel mit dem Event beigetreten, ich kümmer mich mal um nen Account!")
|
||||||
const result = await jellyfinHandler.upsertUser(newState.member, "TEMPORARY", uuid())
|
const result = await jellyfinHandler.upsertUser(newState.member, "TEMPORARY", uuid())
|
||||||
if (result === UserUpsertResult.created) {
|
if (result === UserUpsertResult.created) {
|
||||||
@ -53,7 +53,7 @@ export async function execute(oldState: VoiceState, newState: VoiceState) {
|
|||||||
} else {
|
} else {
|
||||||
logger.info("VoiceState channelId was not the id of any channel with events")
|
logger.info("VoiceState channelId was not the id of any channel with events")
|
||||||
}
|
}
|
||||||
}catch(error){
|
} catch (error) {
|
||||||
logger.error(error)
|
logger.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,23 +1,23 @@
|
|||||||
import { format, isToday, toDate } from "date-fns";
|
import { format, isToday, toDate } from "date-fns";
|
||||||
import {utcToZonedTime} from "date-fns-tz"
|
import { utcToZonedTime } from "date-fns-tz"
|
||||||
import { GuildScheduledEvent } from "discord.js";
|
import { GuildScheduledEvent } from "discord.js";
|
||||||
import { logger } from "../logger";
|
import { logger } from "../logger";
|
||||||
import de from "date-fns/locale/de";
|
import de from "date-fns/locale/de";
|
||||||
|
|
||||||
export function createDateStringFromEvent(event: GuildScheduledEvent, requestId: string, guildId?: string): string {
|
export function createDateStringFromEvent(event: GuildScheduledEvent, requestId: string, guildId?: string): string {
|
||||||
if(!event.scheduledStartAt) {
|
if (!event.scheduledStartAt) {
|
||||||
logger.error("Event has no start. Cannot create dateString.", {guildId, requestId})
|
logger.error("Event has no start. Cannot create dateString.", { guildId, requestId })
|
||||||
return `"habe keinen Startzeitpunkt ermitteln können"`
|
return `"habe keinen Startzeitpunkt ermitteln können"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeZone = 'Europe/Berlin'
|
const timeZone = 'Europe/Berlin'
|
||||||
const zonedDateTime = utcToZonedTime(event.scheduledStartAt, timeZone)
|
const zonedDateTime = utcToZonedTime(event.scheduledStartAt, timeZone)
|
||||||
const time = format(zonedDateTime, "HH:mm", {locale: de})
|
const time = format(zonedDateTime, "HH:mm", { locale: de })
|
||||||
|
|
||||||
if(isToday(zonedDateTime)) {
|
if (isToday(zonedDateTime)) {
|
||||||
return `heute um ${time}`
|
return `heute um ${time}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const date = format(zonedDateTime, "eeee dd.MM", {locale: de})
|
const date = format(zonedDateTime, "eeee dd.MM", { locale: de })
|
||||||
return `am ${date} um ${time}`
|
return `am ${date} um ${time}`
|
||||||
}
|
}
|
@ -64,7 +64,7 @@ export class JellyfinHandler {
|
|||||||
logger.debug(JSON.stringify(req), { requestId, guildId })
|
logger.debug(JSON.stringify(req), { requestId, guildId })
|
||||||
const createResult = await this.userApi.createUserByName(req)
|
const createResult = await this.userApi.createUserByName(req)
|
||||||
if (createResult) {
|
if (createResult) {
|
||||||
if(createResult.policy) {
|
if (createResult.policy) {
|
||||||
this.setUserPermissions(createResult, requestId, guildId)
|
this.setUserPermissions(createResult, requestId, guildId)
|
||||||
}
|
}
|
||||||
(await discordUser.createDM()).send(`Ich hab dir mal nen Account angelegt :)\nDein Username ist ${createResult.name}, dein Password ist "${req.createUserByNameRequest.password}"!`)
|
(await discordUser.createDM()).send(`Ich hab dir mal nen Account angelegt :)\nDein Username ist ${createResult.name}, dein Password ist "${req.createUserByNameRequest.password}"!`)
|
||||||
@ -74,8 +74,8 @@ export class JellyfinHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async setUserPermissions(user: UserDto, requestId: string, guildId?: string) {
|
public async setUserPermissions(user: UserDto, requestId: string, guildId?: string) {
|
||||||
if(!user.policy || !user.id) {
|
if (!user.policy || !user.id) {
|
||||||
logger.error(`Cannot update user policy. User ${user.name} has no policy to modify`, {guildId, requestId})
|
logger.error(`Cannot update user policy. User ${user.name} has no policy to modify`, { guildId, requestId })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user.policy.enableVideoPlaybackTranscoding = false
|
user.policy.enableVideoPlaybackTranscoding = false
|
||||||
@ -273,7 +273,7 @@ export class JellyfinHandler {
|
|||||||
let movieCount = 0
|
let movieCount = 0
|
||||||
let movieNames: string[]
|
let movieNames: string[]
|
||||||
do {
|
do {
|
||||||
movieNames = (await this.getRandomMovies(count, guildId, requestId)).filter(movie => movie.name && movie.name.length > 0).map(movie => <string> movie.name)
|
movieNames = (await this.getRandomMovies(count, guildId, requestId)).filter(movie => movie.name && movie.name.length > 0).map(movie => <string>movie.name)
|
||||||
movieCount = movieNames.length
|
movieCount = movieNames.length
|
||||||
} while (movieCount < count)
|
} while (movieCount < count)
|
||||||
return movieNames
|
return movieNames
|
||||||
|
@ -29,7 +29,7 @@ export interface ConfigurationParameters {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Configuration {
|
export class Configuration {
|
||||||
constructor(private configuration: ConfigurationParameters = {}) {}
|
constructor(private configuration: ConfigurationParameters = {}) { }
|
||||||
|
|
||||||
set config(configuration: Configuration) {
|
set config(configuration: Configuration) {
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
@ -393,7 +393,7 @@ export interface ResponseTransformer<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class JSONApiResponse<T> {
|
export class JSONApiResponse<T> {
|
||||||
constructor(public raw: Response, private transformer: ResponseTransformer<T> = (jsonValue: any) => jsonValue) {}
|
constructor(public raw: Response, private transformer: ResponseTransformer<T> = (jsonValue: any) => jsonValue) { }
|
||||||
|
|
||||||
async value(): Promise<T> {
|
async value(): Promise<T> {
|
||||||
return this.transformer(await this.raw.json());
|
return this.transformer(await this.raw.json());
|
||||||
@ -401,7 +401,7 @@ export class JSONApiResponse<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class VoidApiResponse {
|
export class VoidApiResponse {
|
||||||
constructor(public raw: Response) {}
|
constructor(public raw: Response) { }
|
||||||
|
|
||||||
async value(): Promise<void> {
|
async value(): Promise<void> {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -409,7 +409,7 @@ export class VoidApiResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class BlobApiResponse {
|
export class BlobApiResponse {
|
||||||
constructor(public raw: Response) {}
|
constructor(public raw: Response) { }
|
||||||
|
|
||||||
async value(): Promise<Blob> {
|
async value(): Promise<Blob> {
|
||||||
return await this.raw.blob();
|
return await this.raw.blob();
|
||||||
@ -417,7 +417,7 @@ export class BlobApiResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class TextApiResponse {
|
export class TextApiResponse {
|
||||||
constructor(public raw: Response) {}
|
constructor(public raw: Response) { }
|
||||||
|
|
||||||
async value(): Promise<string> {
|
async value(): Promise<string> {
|
||||||
return await this.raw.text();
|
return await this.raw.text();
|
||||||
|
@ -130,7 +130,7 @@ export class ExtendedClient extends Client {
|
|||||||
for (const guild of guilds.values()) {
|
for (const guild of guilds.values()) {
|
||||||
logger.info("Starting background task for announcement role", { guildId: guild.id })
|
logger.info("Starting background task for announcement role", { guildId: guild.id })
|
||||||
const textChannel: Maybe<TextChannel> = this.getAnnouncementChannelForGuild(guild.id)
|
const textChannel: Maybe<TextChannel> = this.getAnnouncementChannelForGuild(guild.id)
|
||||||
if(!textChannel) {
|
if (!textChannel) {
|
||||||
logger.error("Could not find announcement channel. Aborting", { guildId: guild.id })
|
logger.error("Could not find announcement channel. Aborting", { guildId: guild.id })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -174,7 +174,7 @@ export class ExtendedClient extends Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async startPollCloseBackgroundTasks() {
|
private async startPollCloseBackgroundTasks() {
|
||||||
for(const guild of this.guilds.cache) {
|
for (const guild of this.guilds.cache) {
|
||||||
this.pollCloseBackgroundTasks.set(guild[1].id, schedule("0 * * * * *", () => checkForPollsToClose(guild[1])))
|
this.pollCloseBackgroundTasks.set(guild[1].id, schedule("0 * * * * *", () => checkForPollsToClose(guild[1])))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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