Compare commits
	
		
			46 Commits
		
	
	
		
			v1.0.4
			...
			b1c581ca6e
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| b1c581ca6e | |||
| 96189c2392 | |||
| 700353cff4 | |||
| 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 | |||
| 9da8f47784 | |||
| e8c58d5ff8 | |||
| 8569a3e1e6 | 
							
								
								
									
										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
 | 
			
		||||
        uses: actions/checkout@v3
 | 
			
		||||
      - name: Build Container
 | 
			
		||||
        run: docker build .
 | 
			
		||||
        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
 | 
			
		||||
@@ -22,6 +21,8 @@ jobs:
 | 
			
		||||
      - 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 }}:${{ node -p "require('./package.json').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 --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 .
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										19
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								Dockerfile
									
									
									
									
									
								
							@@ -1,11 +1,22 @@
 | 
			
		||||
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 
 | 
			
		||||
COPY server ./server
 | 
			
		||||
RUN npm run build
 | 
			
		||||
CMD ["npm","run","start"]
 | 
			
		||||
 | 
			
		||||
FROM files as dependencies
 | 
			
		||||
RUN npm ci
 | 
			
		||||
 | 
			
		||||
FROM dependencies as test
 | 
			
		||||
COPY server ./server
 | 
			
		||||
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"
 | 
			
		||||
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 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 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)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								jest.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								jest.config.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
module.exports = {
 | 
			
		||||
	'roots': [
 | 
			
		||||
		'<rootDir>/tests',
 | 
			
		||||
		'<rootDir>/server'
 | 
			
		||||
	],
 | 
			
		||||
	'transform': {
 | 
			
		||||
		'^.+\\.tsx?$': 'ts-jest'
 | 
			
		||||
	},
 | 
			
		||||
	'testRegex': '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
 | 
			
		||||
	'setupFiles': ["<rootDir>/tests/testenv.js"],
 | 
			
		||||
	'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.4",
 | 
			
		||||
  "version": "1.1.3",
 | 
			
		||||
  "lockfileVersion": 2,
 | 
			
		||||
  "requires": true,
 | 
			
		||||
  "packages": {
 | 
			
		||||
    "": {
 | 
			
		||||
      "name": "node-jellyfin-discord-bot",
 | 
			
		||||
      "version": "1.0.4",
 | 
			
		||||
      "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.4",
 | 
			
		||||
	"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 --runInBand",
 | 
			
		||||
		"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"
 | 
			
		||||
 
 | 
			
		||||
@@ -13,22 +13,22 @@ export default new Command({
 | 
			
		||||
	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"}],
 | 
			
		||||
		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})
 | 
			
		||||
		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) {
 | 
			
		||||
		if (!announcementType) {
 | 
			
		||||
			logger.error("Did not get an announcement type!", { guildId, requestId })
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
@@ -40,7 +40,7 @@ export default new Command({
 | 
			
		||||
			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)
 | 
			
		||||
			command.followUp("Ist rausgeschickt!")
 | 
			
		||||
		} else {
 | 
			
		||||
@@ -56,7 +56,7 @@ function isAdmin(member: GuildMember): boolean {
 | 
			
		||||
async function sendInitialAnnouncement(guildId: string, requestId: string): Promise<void> {
 | 
			
		||||
	logger.info("Sending initial announcement")
 | 
			
		||||
	const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(guildId)
 | 
			
		||||
    if(!announcementChannel) {
 | 
			
		||||
	if (!announcementChannel) {
 | 
			
		||||
		logger.error("Could not find announcement channel. Aborting", { guildId, requestId })
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -96,7 +96,7 @@ export async function manageAnnouncementRoles(guild: Guild, reaction: MessageRea
 | 
			
		||||
	const allUsers = (await guild.members.fetch())
 | 
			
		||||
 | 
			
		||||
	const usersWhoHaveRole: GuildMember[] = allUsers
 | 
			
		||||
        .filter(member=> member.roles.cache
 | 
			
		||||
		.filter(member => member.roles.cache
 | 
			
		||||
			.find(role => role.id === config.bot.announcement_role) !== undefined)
 | 
			
		||||
		.map(member => member)
 | 
			
		||||
 | 
			
		||||
@@ -105,15 +105,15 @@ export async function manageAnnouncementRoles(guild: Guild, reaction: MessageRea
 | 
			
		||||
 | 
			
		||||
	const usersWhoDontHaveRole: GuildMember[] = allUsers
 | 
			
		||||
		.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)
 | 
			
		||||
 | 
			
		||||
	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})
 | 
			
		||||
	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))
 | 
			
		||||
 
 | 
			
		||||
@@ -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 })
 | 
			
		||||
@@ -251,7 +273,7 @@ export class JellyfinHandler {
 | 
			
		||||
		let movieCount = 0
 | 
			
		||||
		let movieNames: string[]
 | 
			
		||||
		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
 | 
			
		||||
		} while (movieCount < count)
 | 
			
		||||
		return movieNames
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,7 @@ export interface ConfigurationParameters {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Configuration {
 | 
			
		||||
    constructor(private configuration: ConfigurationParameters = {}) {}
 | 
			
		||||
	constructor(private configuration: ConfigurationParameters = {}) { }
 | 
			
		||||
 | 
			
		||||
	set config(configuration: Configuration) {
 | 
			
		||||
		this.configuration = configuration;
 | 
			
		||||
@@ -393,7 +393,7 @@ export interface ResponseTransformer<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> {
 | 
			
		||||
		return this.transformer(await this.raw.json());
 | 
			
		||||
@@ -401,7 +401,7 @@ export class JSONApiResponse<T> {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class VoidApiResponse {
 | 
			
		||||
    constructor(public raw: Response) {}
 | 
			
		||||
	constructor(public raw: Response) { }
 | 
			
		||||
 | 
			
		||||
	async value(): Promise<void> {
 | 
			
		||||
		return undefined;
 | 
			
		||||
@@ -409,7 +409,7 @@ export class VoidApiResponse {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class BlobApiResponse {
 | 
			
		||||
    constructor(public raw: Response) {}
 | 
			
		||||
	constructor(public raw: Response) { }
 | 
			
		||||
 | 
			
		||||
	async value(): Promise<Blob> {
 | 
			
		||||
		return await this.raw.blob();
 | 
			
		||||
@@ -417,7 +417,7 @@ export class BlobApiResponse {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class TextApiResponse {
 | 
			
		||||
    constructor(public raw: Response) {}
 | 
			
		||||
	constructor(public raw: Response) { }
 | 
			
		||||
 | 
			
		||||
	async value(): Promise<string> {
 | 
			
		||||
		return await this.raw.text();
 | 
			
		||||
 
 | 
			
		||||
@@ -130,7 +130,7 @@ export class ExtendedClient extends Client {
 | 
			
		||||
		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) {
 | 
			
		||||
			if (!textChannel) {
 | 
			
		||||
				logger.error("Could not find announcement channel. Aborting", { guildId: guild.id })
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
@@ -174,7 +174,7 @@ export class ExtendedClient extends Client {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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])))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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)
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										15
									
								
								tests/testenv.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								tests/testenv.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
process.env.CLIENT_ID = "CLIENT_ID"
 | 
			
		||||
process.env.SECRET = "SECRET"
 | 
			
		||||
process.env.BOT_TOKEN = "BOT_TOKEN"
 | 
			
		||||
process.env.WATCHER_ROLE = "WATCHER_ROLE"
 | 
			
		||||
process.env.ADMIN_ROLE = "ADMIN_ROLE"
 | 
			
		||||
process.env.CHANNEL_ID = "CHANNEL_ID"
 | 
			
		||||
process.env.WATCHPARTY_ANNOUNCEMENT_ROLE = "WATCHPARTY_ANNOUNCEMENT_ROLE"
 | 
			
		||||
process.env.YAVIN_JELLYFIN_URL = "YAVIN_JELLYFIN_URL"
 | 
			
		||||
process.env.YAVIN_COLLECTION_ID = "YAVIN_COLLECTION_ID"
 | 
			
		||||
process.env.YAVIN_COLLECTION_USER = "YAVIN_COLLECTION_USER"
 | 
			
		||||
process.env.YAVIN_TOKEN = "YAVIN_TOKEN"
 | 
			
		||||
process.env.TOKEN = "TOKEN"
 | 
			
		||||
process.env.JELLYFIN_USER = "JELLYFIN_USER"
 | 
			
		||||
process.env.JELLYFIN_COLLECTION_ID = "JELLYFIN_COLLECTION_ID"
 | 
			
		||||
process.env.JELLYFIN_URL = "JELLYFIN_URL"
 | 
			
		||||
		Reference in New Issue
	
	Block a user