85 Commits

Author SHA1 Message Date
36d1306180 remove test build 2023-06-24 02:13:05 +02:00
69bde313b5 test with latest
All checks were successful
Build a docker image for node-jellyfin-role-bot / build-docker-image (push) Successful in 6s
2023-06-24 02:10:26 +02:00
7af3d87048 use new var in build
All checks were successful
Build a docker image for node-jellyfin-role-bot / build-docker-image (push) Successful in 6s
2023-06-24 02:09:03 +02:00
73741230b1 adjust env method
All checks were successful
Build a docker image for node-jellyfin-role-bot / build-docker-image (push) Successful in 7s
2023-06-24 02:08:26 +02:00
4f6d4f646a adjust echo script
All checks were successful
Build a docker image for node-jellyfin-role-bot / build-docker-image (push) Successful in 6s
2023-06-24 02:07:19 +02:00
6169649261 add new step
All checks were successful
Build a docker image for node-jellyfin-role-bot / build-docker-image (push) Successful in 6s
2023-06-24 02:06:10 +02:00
0560c4620c adjust awk script
All checks were successful
Build a docker image for node-jellyfin-role-bot / build-docker-image (push) Successful in 6s
2023-06-24 02:05:01 +02:00
be3ee5e493 echo version
All checks were successful
Build a docker image for node-jellyfin-role-bot / build-docker-image (push) Successful in 6s
2023-06-24 02:02:42 +02:00
251e6ae3d6 add test build script
Some checks failed
Build a docker image for node-jellyfin-role-bot / build-docker-image (push) Failing after 9s
2023-06-24 02:00:56 +02:00
2edd0312dc Merge branch 'master' into feat/cicd 2023-06-24 02:00:28 +02:00
e52e845851 1.1.3
All checks were successful
Build a docker image for node-jellyfin-role-bot / build-docker-image (push) Successful in 1m59s
2023-06-23 23:46:52 +02:00
61544feaba Fix stupid timezone issues 2023-06-23 23:46:11 +02:00
1966640239 Merge branch 'master' of ssh://gitea.brudi.xyz:222/kenobi/jellyfin-discord-bot 2023-06-23 21:24:32 +02:00
fa9998e92c Unallow transcoding per default for new users 2023-06-23 21:23:54 +02:00
c1a449bafe 1.1.2
All checks were successful
Build a docker image for node-jellyfin-role-bot / build-docker-image (push) Successful in 1m50s
2023-06-23 19:46:20 +02:00
d5d82043f0 temporarily remove second tag on docker build 2023-06-23 19:46:06 +02:00
51ebf2e939 1.1.1
All checks were successful
Build a docker image for node-jellyfin-role-bot / build-docker-image (push) Successful in 12s
2023-06-23 19:44:58 +02:00
f314b2f355 maybe fix a docker build typo 2023-06-23 19:44:44 +02:00
a4d7c57d10 1.1.0
All checks were successful
Build a docker image for node-jellyfin-role-bot / build-docker-image (push) Successful in 14s
2023-06-23 19:33:31 +02:00
2802afa7d5 Merge pull request 'feat/#42_announce_manual_watchparty' (#50) from feat/#42_announce_manual_watchparty into master
Reviewed-on: #50
2023-06-23 19:31:57 +02:00
3a5ea5d4ff improve message clarity when no start date in event found
All checks were successful
Compile the repository / compile (pull_request) Successful in 4m8s
2023-06-23 19:30:17 +02:00
45d87275bf prevent announcement when description contains !private
All checks were successful
Compile the repository / compile (pull_request) Successful in 1m43s
2023-06-23 15:56:36 +02:00
31e440434e properly clean up wp announcements without event only 2023-06-23 15:51:48 +02:00
3d70b56eb7 Merge pull request 'feat/#43_append_event_invite' (#49) from feat/#43_append_event_invite into master
Reviewed-on: #49
2023-06-23 15:49:59 +02:00
3298c7a244 Merge pull request 'Create option "none of that" in voting' (#48) from feat/#39_none_of_that into master
Reviewed-on: #48
2023-06-23 15:49:43 +02:00
5b98c9bf2f Rename event files to specific case
We can add multiple eventhandlers per eventname. To avoid confusion and large files and to improve concise file names the event files were renamed
2023-06-23 14:37:41 +02:00
ee363e065c 1.0.4
All checks were successful
Build a docker image for node-jellyfin-role-bot / build-docker-image (push) Successful in 47s
2023-06-22 23:23:56 +02:00
9af847f234 Fix docker-build for good 2023-06-22 23:23:38 +02:00
a18406e7e4 1.0.3
Some checks failed
Build a docker image for node-jellyfin-role-bot / build-docker-image (push) Failing after 8s
2023-06-22 23:18:41 +02:00
b9f65125dc Hopefully fix ci/cd 2023-06-22 23:18:06 +02:00
d61457cb5f 1.0.2
Some checks failed
Build a docker image for node-jellyfin-role-bot / build-docker-image (push) Failing after 10s
2023-06-22 23:05:47 +02:00
9da8f47784 add event box to vote closed message
All checks were successful
Compile the repository / compile (pull_request) Successful in 1m18s
2023-06-22 19:38:59 +02:00
e8c58d5ff8 add event box to create message 2023-06-22 19:35:47 +02:00
8569a3e1e6 Create option "none of that" in voting
All checks were successful
Compile the repository / compile (pull_request) Successful in 1m19s
2023-06-22 18:53:29 +02:00
8d0dda0fa9 Merge pull request 'fix/ci-tag' (#47) from fix/ci-tag into master
Reviewed-on: #47
2023-06-22 18:38:34 +02:00
777ae330ad remove master branch restriction on docker-build
All checks were successful
Compile the repository / compile (pull_request) Successful in 10s
since the pipeline is only ever called on a tag, which will by convention only be committed to the master branch, we can be reasonably sure
that we are on the correct branch.
Tags are independent on branches which makes it impossible to check for tag AND master branch ref at the same time.
2023-06-19 23:34:25 +02:00
111ccaa880 simplify the test compile
All checks were successful
Compile the repository / compile (pull_request) Successful in 11s
2023-06-19 23:28:39 +02:00
c00453d3d3 push all tags of an image
All checks were successful
Compile the repository / compile (pull_request) Successful in 11s
2023-06-19 23:26:56 +02:00
8a7973a2e3 compile only on PR 2023-06-19 23:26:41 +02:00
0b67b126dd 1.0.1
All checks were successful
Compile the repository / compile (push) Successful in 9s
Build a docker image for node-jellyfin-role-bot / build-docker-image (push) Has been skipped
2023-06-19 23:19:40 +02:00
6d5725be90 Merge pull request 'fix/movie_names_and_background_task' (#46) from fix/movie_names_and_background_task into master
All checks were successful
Compile the repository / compile (push) Successful in 11s
Reviewed-on: #46
2023-06-19 11:29:04 +02:00
59f5b34e5a Merge branch 'master' into fix/movie_names_and_background_task
All checks were successful
Compile the repository / compile (push) Successful in 1m31s
2023-06-19 11:19:17 +02:00
670a64af22 Check if less than 2 days between create and start for deciding if to close
All checks were successful
Build a docker image for node-jellyfin-role-bot / build-docker-image (push) Successful in 1m9s
2023-06-17 13:18:52 +02:00
4cc332820f prevent poll close if event is less than 24h old
All checks were successful
Build a docker image for node-jellyfin-role-bot / build-docker-image (push) Successful in 1m15s
2023-06-17 13:03:48 +02:00
f5928049ea Merge pull request 'fix and refactor mitgucken guide' (#44) from fix/guide into master
All checks were successful
Compile the repository / compile (push) Successful in 9s
Reviewed-on: #44
2023-06-17 12:17:29 +02:00
99905f98d0 Merge pull request 'fix/ci-cd-push' (#45) from fix/ci-cd-push into master
All checks were successful
Compile the repository / compile (push) Successful in 10s
Reviewed-on: #45
2023-06-17 12:16:13 +02:00
07849d331a move scheduling of pollclose task to startup
All checks were successful
Build a docker image for node-jellyfin-role-bot / build-docker-image (push) Successful in 1m37s
Also moved check function to closepoll.ts
2023-06-17 12:00:14 +02:00
1e6a75687a add package version extraction to docker push job
All checks were successful
Compile the repository / compile (push) Successful in 9s
2023-06-17 01:31:03 +02:00
2c09033c3f add new tag restriction to docker push job
All checks were successful
Compile the repository / compile (push) Successful in 9s
2023-06-17 01:27:49 +02:00
ce4441cee3 add restriction to master branch on docker push job
All checks were successful
Compile the repository / compile (push) Successful in 9s
Build a docker image for node-jellyfin-role-bot / build-docker-image (push) Has been skipped
2023-06-17 01:22:53 +02:00
7c8072b295 add separate compile step
should be run on every push to a branch to check for compilability
2023-06-17 01:20:54 +02:00
7899aac5ce adjust server connection message
All checks were successful
Build a docker image for node-jellyfin-role-bot / build-docker-image (push) Successful in 1m22s
remove extraneous repetition of the server address
2023-06-17 01:20:01 +02:00
26c2d91252 fix and refactor mitgucken guide
All checks were successful
Build a docker image for node-jellyfin-role-bot / build-docker-image (push) Successful in 1m16s
- reorder account acquisition in front of login
- rebox explainrole function
- adjust external reference to function
2023-06-17 01:13:33 +02:00
d6300e8bec improve movie name resolving
All checks were successful
Build a docker image for node-jellyfin-role-bot / build-docker-image (push) Successful in 1m55s
When creating the poll the bot will now only request movie names instead of movies
This improves the errorhandling because the movie names cannot be null
Also the movieName function filters empty movienames and will guarantee the requested number of names
2023-06-16 20:15:36 +02:00
f2b5ee502f remove unnecessary scripts
All checks were successful
Build a docker image for node-jellyfin-role-ot / build-docker-image (push) Successful in 10s
2023-06-14 22:35:59 +02:00
749e1c89ab clean up ci yaml 2023-06-14 22:35:51 +02:00
0d5799796a go back to normal docker commands
All checks were successful
Build a docker image for node-jellyfin-role-ot / build-docker-image (push) Successful in 15s
2023-06-14 22:33:15 +02:00
b7986d276b fix env access
All checks were successful
Build a docker image for node-jellyfin-role-ot / build-docker-image (push) Successful in 24s
2023-06-14 22:31:34 +02:00
8540381834 use bash explicitly
All checks were successful
Build a docker image for node-jellyfin-role-ot / build-docker-image (push) Successful in 8s
2023-06-14 22:29:55 +02:00
7e67d1fed9 adjust scripts
All checks were successful
Build a docker image for node-jellyfin-role-ot / build-docker-image (push) Successful in 8s
2023-06-14 22:28:29 +02:00
0cb19ba8f1 use bash scripts for docker stage
All checks were successful
Build a docker image for node-jellyfin-role-ot / build-docker-image (push) Successful in 11s
2023-06-14 22:27:18 +02:00
5dcf766593 dockerbuild and dockerpush scripts 2023-06-14 22:27:05 +02:00
808bdd033e string it
All checks were successful
Build a docker image for node-jellyfin-role-ot / build-docker-image (push) Successful in 9s
2023-06-14 22:20:06 +02:00
33f031d333 simplify push
All checks were successful
Build a docker image for node-jellyfin-role-ot / build-docker-image (push) Successful in 9s
2023-06-14 22:14:46 +02:00
40d9523e21 doing it manually 2023-06-14 22:14:19 +02:00
26e74a62c1 add branch 2023-06-14 21:54:22 +02:00
c0f91aad79 add secrets to 'on' 2023-06-14 21:42:16 +02:00
79ffde5f34 check env step
Some checks failed
Build a docker image for node-jellyfin-role-ot / build-docker-image (push) Failing after 23s
2023-06-14 21:30:23 +02:00
911b9e4884 adjust pipeline to be personalized
Some checks failed
Build a docker image for node-jellyfin-role-ot / build-docker-image (push) Failing after 17s
2023-06-14 21:27:16 +02:00
31a9e0eb28 adjust url
Some checks failed
Build a docker image for node-jellyfin-role-ot / build-docker-image (push) Failing after 22s
2023-06-14 19:44:50 +02:00
bcf788293e hardcoded docker tags
Some checks failed
Build a docker image for node-jellyfin-role-ot / build-docker-image (push) Failing after 28s
2023-06-14 19:27:28 +02:00
934b6dfead remove custom script
Some checks failed
Build a docker image for node-jellyfin-role-ot / build-docker-image (push) Failing after 15s
2023-06-14 19:21:22 +02:00
cd0c8c0017 latest tag by default
Some checks failed
Build a docker image for node-jellyfin-role-ot / build-docker-image (push) Failing after 15s
2023-06-14 19:18:46 +02:00
83f803d0e7 remove metadata action
Some checks failed
Build a docker image for node-jellyfin-role-ot / build-docker-image (push) Failing after 20s
2023-06-14 19:16:23 +02:00
2cb652aee6 add metadata and registry push
Some checks failed
Build a docker image for node-jellyfin-role-ot / build-docker-image (push) Failing after 9s
2023-06-14 19:11:05 +02:00
034d14eb15 use a container with docker
All checks were successful
Build a docker image for node-jellyfin-role-ot / build-docker-image (push) Successful in 2m3s
2023-06-14 18:59:07 +02:00
c8bfc47ddf add docker login
Some checks failed
Build a docker image for node-jellyfin-role-ot / build-docker-image (push) Failing after 14s
2023-06-13 23:47:22 +02:00
b67982ed38 ls
Some checks failed
Build a docker image for node-jellyfin-role-ot / build-docker-image (push) Failing after 9s
2023-06-13 23:29:29 +02:00
e3144fc402 add checkout option 2023-06-13 23:29:03 +02:00
1970f4b0cb adjust docker build stage
Some checks failed
Build a docker image for node-jellyfin-role-ot / build-docker-image (push) Failing after 34s
2023-06-13 23:24:56 +02:00
8ac4f568a0 add on push
Some checks failed
Build a docker image for node-jellyfin-role-ot / build-docker-image (push) Failing after 41s
2023-06-13 21:51:26 +02:00
09f4efc96c change os 2023-06-13 21:50:21 +02:00
6e0c3b8ef6 add gitea cicd 2023-06-13 21:49:04 +02:00
8ee36f7510 add woodpecker integration 2023-06-13 20:31:33 +02:00
1593e126eb add docker build files 2023-06-13 20:31:19 +02:00
17 changed files with 342 additions and 148 deletions

View File

@ -0,0 +1,17 @@
name: Compile the repository
on: [pull_request]
env:
REGISTRY: gitea.brudi.xyz
IMAGE_NAME: ${{ gitea.repository }}
USER: ${{ gitea.actor }}
jobs:
compile:
runs-on: ubuntu-latest
container: catthehacker/ubuntu:act-latest
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Build Container
run: docker build .

View File

@ -1,6 +1,9 @@
name: Build a docker image for node-jellyfin-role-bot
run-name: ${{ gitea.actor }} is building an image
on: [push]
on:
push:
tags:
- '*'
env:
REGISTRY: gitea.brudi.xyz
IMAGE_NAME: ${{ gitea.repository }}
@ -8,6 +11,7 @@ env:
jobs:
build-docker-image:
runs-on: ubuntu-latest
#if: gitea.ref == 'refs/heads/master'
container: catthehacker/ubuntu:act-latest
permissions:
contents: read
@ -20,4 +24,4 @@ jobs:
- name: Build Container
run: docker build -t "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" .
- name: Push Container
run: docker push "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest"
run: docker push --all-tags "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}"

19
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "node-jellyfin-discord-bot",
"version": "1.0.0",
"version": "1.1.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "node-jellyfin-discord-bot",
"version": "1.0.0",
"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",
@ -2626,6 +2627,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",
@ -8905,6 +8914,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",

View File

@ -1,6 +1,6 @@
{
"name": "node-jellyfin-discord-bot",
"version": "1.0.0",
"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",

View File

@ -1,13 +1,13 @@
import { addDays, differenceInDays, format, isAfter, toDate } from 'date-fns'
import { Guild, GuildScheduledEvent, GuildScheduledEventEditOptions, GuildScheduledEventSetStatusArg, GuildScheduledEventStatus, Message, MessageCreateOptions, TextChannel } from 'discord.js'
import { v4 as uuid } from 'uuid'
import { client } from '../..'
import { config } from '../configuration'
import { Emotes } from '../events/guildScheduledEventCreate'
import { Emotes } from '../events/autoCreateVoteByWPEvent'
import { Maybe } from '../interfaces'
import { logger } from '../logger'
import { Command } from '../structures/command'
import { RunOptions } from '../types/commandTypes'
import { format } from 'date-fns'
import { Maybe } from '../interfaces'
export default new Command({
name: 'closepoll',
@ -23,7 +23,7 @@ export default new Command({
}
const guildId = command.guildId
logger.info("Got command for closing poll!", { guildId, requestId })
command.followUp("Alles klar, beende die Umfrage :)")
closePoll(command.guild, requestId)
}
@ -34,7 +34,7 @@ export async function closePoll(guild: Guild, requestId: string) {
logger.info("stopping poll", { guildId, requestId })
const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(guildId)
if(!announcementChannel) {
if (!announcementChannel) {
logger.error("Could not find the textchannel. Unable to close poll.", { guildId, requestId })
return
}
@ -55,16 +55,16 @@ export async function closePoll(guild: Guild, requestId: string) {
logger.debug(`Last message: ${JSON.stringify(lastMessage, null, 2)}`, { guildId, requestId })
const votes = await (await getVotesByEmote(lastMessage, guildId, requestId))
.sort((a, b) => b.count - a.count)
.sort((a, b) => b.count - a.count)
logger.debug(`votes: ${JSON.stringify(votes, null, 2)}`, { guildId, requestId })
logger.info("Deleting vote message")
await lastMessage.delete()
const event = await getEvent(guild, guild.id, requestId)
if(event) {
if (event) {
updateEvent(event, votes, guild, guildId, requestId)
sendVoteClosedMessage(event, votes[0].movie, guildId, requestId)
}
@ -75,14 +75,14 @@ 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"] }
}
const announcementChannel = client.getAnnouncementChannelForGuild(guildId)
logger.info("Sending vote closed message.", { guildId, requestId })
if(!announcementChannel) {
if (!announcementChannel) {
logger.error("Could not find announcement channel. Please fix!", { guildId, requestId })
return
}
@ -145,4 +145,40 @@ function extractMovieFromMessageByEmote(message: Message, emote: string): string
const movie = emoteLines[0].substring(emoteLines[0].indexOf(emote) + emote.length + 2) // plus colon and space
return movie
}
export async function checkForPollsToClose(guild: Guild): Promise<void> {
const requestId = uuid()
logger.info(`Automatic check for poll closing.`, { guildId: guild.id, requestId })
const events = (await guild.scheduledEvents.fetch()).filter(event => event.name.toLocaleLowerCase().includes("voting offen")).map(event => event)
if (events.length > 1) {
logger.error("Handling more than one Event is not implemented yet. Found more than one poll to close")
return
} else if (events.length == 0) {
logger.info("Could not find any events. Cancelling", { guildId: guild.id, requestId })
}
const updatedEvent = events[0] //add two hours because of different timezones in discord api and Date.now()
if (!updatedEvent.scheduledStartTimestamp) {
logger.error("Event does not have a scheduled start time. Cancelling", { guildId: guild.id, requestId })
return
}
const createDate: Date = toDate(updatedEvent.createdTimestamp)
const eventDate: Date = toDate(updatedEvent.scheduledStartTimestamp)
const difference: number = differenceInDays(createDate, eventDate)
if (difference <= 2) {
logger.info("Less than two days between event create and event start. Not closing poll.", { guildId: guild.id, requestId })
return
}
const closePollDate: Date = addDays(eventDate, -2)
if (isAfter(Date.now(), closePollDate)) {
logger.info("Less than two days until event. Closing poll", { guildId: guild.id, requestId })
closePoll(guild, requestId)
} else {
logger.info(`ScheduledStart: ${closePollDate}. Now: ${toDate(Date.now())}`, { guildId: guild.id, requestId })
}
}

View File

@ -5,7 +5,7 @@ import { accountChoice, joingroup, leavegroup, loginScreen, overview, resume, se
import { logger } from '../logger'
import { Command } from '../structures/command'
import { RunOptions } from '../types/commandTypes'
import { configureServer, explainRoles, installation, loginInfo, useSyncgroup } from './mitgucken'
import { configureServer, explainRole, installation, loginInfo, useSyncgroup } from './mitgucken'
export default new Command({
name: 'guides',
@ -70,7 +70,7 @@ export default new Command({
userDMChannel.send({ embeds: useSyncgroup(), files: [overview, joingroup, resume, leavegroup] })
} else if (guideSelection.customId === 'explainRoles') {
const userDMChannel = await guideSelection.user.createDM()
userDMChannel.send(explainRoles())
userDMChannel.send({ embeds: explainRole() })
}
guideSelection.update({ content: "Hab ich dir per DM geschickt :)", components: [] })

View File

@ -16,13 +16,9 @@ export default new Command({
const embedList: APIEmbed[] = []
embedList.push(...installation())
embedList.push(...configureServer())
embedList.push(...explainRole())
embedList.push(...loginInfo())
embedList.push(...useSyncgroup())
embedList.push({
color,
title: "Wie du an einen Account kommst",
description: explainRoles()
})
//logger.info(`Trying to use ${splashScreen.name}`, { requestId, guildId: interaction.interaction.guild?.id })
logger.info(`Sending guide to ${interaction.interaction.user.id}`, { requestId, guildId: interaction.interaction.guild?.id })
@ -32,6 +28,13 @@ export default new Command({
})
export function explainRole(): APIEmbed[] {
return [{
color,
title: "Wie du an einen Account kommst",
description: roleExplanation
}]
}
export function installation(): APIEmbed[] {
const embedList: APIEmbed[] = []
// DownloadLink and installation
@ -67,7 +70,7 @@ export function configureServer(): APIEmbed[] {
title: "Server Verbindung",
description: "Stelle eine Verbindung zum Hartzarett Jellyfin Server her",
fields: [
{ name: "Server Adresse", value: "https://media.hartzarett.ruhr" }
{ name: "Server Adresse", value: "`https://media.hartzarett.ruhr`" }
],
image: {
url: 'attachment://server_verbindung.png'
@ -91,7 +94,7 @@ export function loginInfo(): APIEmbed[] {
embedList.push({
color,
title: "Login",
description: "Melde dich mit dem Usernamen und Passwort an, welches dir von mir zugeschickt wird. Falls du ein neues brauchst führe einmal /reset_passwort aus :)",
description: "Melde dich mit dem Usernamen und Passwort an, welches dir von mir zugeschickt wird. Falls du ein neues brauchst führe einmal `/passwort_reset` aus :)",
image: {
url: 'attachment://login_screen.png'
}
@ -139,8 +142,7 @@ export function useSyncgroup(): APIEmbed[] {
return embedList
}
export function explainRoles(): string {
return `Mit einer Rolle kann dafür gesorgt werden, dass du einen dauerhaften Account auf dem Mediaserver hast. Wende dich bei Bedarf an Samantha oder Markus.\n
const roleExplanation = `Mit einer Rolle kann dafür gesorgt werden, dass du einen dauerhaften Account auf dem Mediaserver hast. Wende dich bei Bedarf an Samantha oder Markus.\n
Für eine watchparty bekommst du allerdings automatisch einen Account. Hierfür melde einfach Interesse an dem Event an. Wenn du für das Event Interesse angemeldet hast bekommst du automatisch beim Start des Events einen Benutzernamen und das dazugehörige Passwort zugesendet.\n
Hast du kein Interesse angemeldet bekommst du automatisch einen Nutzernamen und Passwort zugeschickt wenn du den Channel betrittst in dem das Event stattfindet.`
}

View 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 })
}
}

View 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
}
}

View 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
}

View File

@ -1,104 +0,0 @@
import { addDays, format, isAfter } from "date-fns";
import toDate from "date-fns/fp/toDate";
import { GuildScheduledEvent, Message, MessageCreateOptions, TextChannel } from "discord.js";
import { ScheduledTask, schedule } from "node-cron";
import { v4 as uuid } from "uuid";
import { client, yavinJellyfinHandler } from "../..";
import { closePoll } from "../commands/closepoll";
import { config } from "../configuration";
import { Maybe } from "../interfaces";
import { logger } from "../logger";
export const name = 'guildScheduledEventCreate'
export enum Emotes { "1⃣", "2⃣", "3⃣", "4⃣", "5⃣", "6⃣", "7⃣", "8⃣", "9⃣", "🔟" }
export let task: ScheduledTask | undefined
export async function execute(event: GuildScheduledEvent) {
const requestId = uuid()
logger.debug(`New event created: ${JSON.stringify(event, null, 2)}`, { guildId: event.guildId, requestId })
if (event.name.toLowerCase().includes("!nextwp")) {
logger.info("Event was a placeholder event to start a new watchparty and voting. Creating vote!", { guildId: event.guildId, requestId })
logger.debug("Renaming event", { guildId: event.guildId, requestId })
event.edit({ name: "Watchparty - Voting offen" })
const movies = await yavinJellyfinHandler.getRandomMovies(5, event.guildId, requestId)
logger.info(`Got ${movies.length} random movies. Creating voting`, { guildId: event.guildId, requestId })
logger.debug(`Movies: ${JSON.stringify(movies.map(movie => movie.name))}`, { guildId: event.guildId, requestId })
const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(event.guildId)
if(!announcementChannel) {
logger.error("Could not find announcement channel. Aborting", { guildId: event.guildId, requestId })
return
}
logger.debug(`Found channel ${JSON.stringify(announcementChannel, null, 2)}`, { guildId: event.guildId, requestId })
if(!event.scheduledStartAt) {
logger.info("EVENT DOES NOT HAVE STARTDATE; CANCELLING", {guildId: event.guildId, requestId})
return
}
const date = format(event.scheduledStartAt, "dd.MM")
const time = format(event.scheduledStartAt, "HH:mm")
let message = `[Abstimmung]\n<@&${config.bot.announcement_role}> Es gibt eine neue Abstimmung für die nächste Watchparty am ${date} um ${time}}! Stimme hierunter für den nächsten Film ab!\n`
for (let i = 0; i < movies.length; i++) {
message = message.concat(Emotes[i]).concat(": ").concat(movies[i].name ?? "Film hatte keinen Namen :(").concat("\n")
}
const options: MessageCreateOptions = {
allowedMentions: { parse: ["roles"]},
content: message
}
const sentMessage: Message<true> = await (await announcementChannel.fetch()).send(options)
for (let i = 0; i < movies.length; i++) {
sentMessage.react(Emotes[i])
}
if (!task) {
task = schedule("0 * * * * *", () => checkForPollsToClose(event))
}
// sentMessage.pin() //todo: uncomment when bot has permission to pin messages. Also update closepoll.ts to only fetch pinned messages
}
}
async function checkForPollsToClose(event: GuildScheduledEvent): Promise<void> {
const requestId = uuid()
logger.info(`Automatic check for poll closing.`, { guildId: event.guildId, requestId })
if (!event.guild) {
logger.error("No guild in event. Cancelling.", { guildId: event.guildId, requestId })
return
}
//refetch event in case the time changed or the poll is already closed
const events = (await event.guild.scheduledEvents.fetch())
.filter(event => event.name.toLowerCase().includes("voting offen"))
.map((value) => value)
if (!events || events.length <= 0) {
logger.info("Did not find any events. Cancelling", { guildId: event.guildId, requestId })
return
} else if (events.length > 1) {
logger.error(`More than one event found. Don't know which one is the right one :( Events: ${JSON.stringify(events, null, 2)}`, { guildId: event.guildId, requestId })
return
}
const updatedEvent = events[0] //add two hours because of different timezones in discord api and Date.now()
if (!updatedEvent.scheduledStartTimestamp) {
logger.error("Event does not have a scheduled start time. Cancelling", { guildId: event.guildId, requestId })
return
}
const eventDate: Date = toDate(updatedEvent.scheduledStartTimestamp)
const closePollDate: Date = addDays(eventDate, -2)
if (isAfter(Date.now(), closePollDate)) {
logger.info("Less than two days until event. Closing poll", { guildId: event.guildId, requestId })
closePoll(event.guild, requestId)
} else {
logger.info(`ScheduledStart: ${closePollDate}. Now: ${toDate(Date.now())}`, { guildId: event.guildId, requestId })
}
}

View File

@ -1,6 +1,6 @@
import { GuildMember, GuildScheduledEvent, GuildScheduledEventStatus } from "discord.js";
import { v4 as uuid } from "uuid";
import { client, jellyfinHandler } from "../..";
import { jellyfinHandler } from "../..";
import { getGuildSpecificTriggerRoleId } from "../helper/roleFilter";
import { logger } from "../logger";
@ -10,7 +10,7 @@ export const name = 'guildScheduledEventUpdate'
export async function execute(oldEvent: GuildScheduledEvent, newEvent: GuildScheduledEvent) {
try {
const requestId = uuid()
logger.debug(`Got scheduledEvent update. New Event: ${JSON.stringify(newEvent, null, 2)}`, { guildId: newEvent.guildId, requestId })
// logger.debug(`Got scheduledEvent update. New Event: ${JSON.stringify(newEvent, null, 2)}`, { guildId: newEvent.guildId, requestId })
if (!newEvent.guild) {
logger.error("Event has no guild, aborting.", { guildId: newEvent.guildId, requestId })
return
@ -27,17 +27,12 @@ export async function execute(oldEvent: GuildScheduledEvent, newEvent: GuildSche
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.`))
})

View 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}`
}

View File

@ -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 })
@ -242,10 +264,21 @@ export class JellyfinHandler {
const index = Math.floor(Math.random() * allMovies.length)
movies.push(...allMovies.splice(index, 1)) // maybe out of bounds? ?
}
return movies
}
public async getRandomMovieNames(count: number, guildId: string, requestId: string): Promise<string[]> {
logger.info(`${count} random movie names requested`, { guildId, requestId })
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)
movieCount = movieNames.length
} while (movieCount < count)
return movieNames
}
}
export enum UserUpsertResult { enabled, created }

View File

@ -8,6 +8,7 @@ import { Maybe } from "../interfaces";
import { JellyfinHandler } from "../jellyfin/handler";
import { logger } from "../logger";
import { CommandType } from "../types/commandTypes";
import { checkForPollsToClose } from "../commands/closepoll";
@ -16,8 +17,9 @@ export class ExtendedClient extends Client {
private commandFilePath = `${__dirname}/../commands`
private jellyfin: JellyfinHandler
public commands: Collection<string, CommandType> = new Collection()
private announcementChannels: Collection<string, TextChannel> = new Collection //guildId to TextChannel
private announcementRoleHandlerTask: Collection<string, ScheduledTask> = new Collection //one task per guild
private announcementChannels: Collection<string, TextChannel> = new Collection() //guildId to TextChannel
private announcementRoleHandlerTask: Collection<string, ScheduledTask> = new Collection() //one task per guild
private pollCloseBackgroundTasks: Collection<string, ScheduledTask> = new Collection()
public constructor(jf: JellyfinHandler) {
const intents: IntentsBitField = new IntentsBitField()
intents.add(IntentsBitField.Flags.GuildMembers, IntentsBitField.Flags.MessageContent, IntentsBitField.Flags.Guilds, IntentsBitField.Flags.DirectMessages, IntentsBitField.Flags.GuildScheduledEvents, IntentsBitField.Flags.GuildVoiceStates)
@ -73,6 +75,7 @@ export class ExtendedClient extends Client {
this.cacheUsers(guilds)
await this.cacheAnnouncementServer(guilds)
this.startAnnouncementRoleBackgroundTask(guilds)
this.startPollCloseBackgroundTasks()
})
} catch (error) {
logger.info(`Error refreshing slash commands: ${error}`)
@ -169,4 +172,10 @@ export class ExtendedClient extends Client {
}
task.stop()
}
private async startPollCloseBackgroundTasks() {
for(const guild of this.guilds.cache) {
this.pollCloseBackgroundTasks.set(guild[1].id, schedule("0 * * * * *", () => checkForPollsToClose(guild[1])))
}
}
}