mirror of
https://github.com/hrfee/jfa-go.git
synced 2026-01-19 00:57:37 +01:00
Compare commits
190 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c10f1e3b36 | ||
|
|
3308739619 | ||
|
|
748acc13c0 | ||
|
|
c3bac6c51c | ||
|
|
3e39657642 | ||
|
|
41334e9051 | ||
|
|
e2c34e574d | ||
|
|
b6459b665d | ||
|
|
d72a5c91cf | ||
|
|
cacd992aad | ||
|
|
23ad016476 | ||
|
|
cc571409b9 | ||
|
|
d7bad69d40 | ||
|
|
63bb678a72 | ||
|
|
2a41c5a393 | ||
|
|
9b80492b4f | ||
|
|
315d74eb2d | ||
|
|
c21df253a1 | ||
|
|
e4e9369d54 | ||
|
|
26d05911fd | ||
|
|
85a6d66228 | ||
|
|
1c755306f9 | ||
|
|
2e97142d9e | ||
|
|
a08a0fd3e6 | ||
|
|
420a22970d | ||
|
|
d4109c8cf5 | ||
|
|
945d579f57 | ||
|
|
6e1f07563d | ||
|
|
ce6f8b41dc | ||
|
|
aa75305da4 | ||
|
|
03cf533c9d | ||
|
|
ccf0584db8 | ||
|
|
833fd26091 | ||
|
|
fd72c838c3 | ||
|
|
63c770db73 | ||
|
|
6237620390 | ||
|
|
4a9bac1027 | ||
|
|
817107622a | ||
|
|
ca7c553147 | ||
|
|
d00507fd20 | ||
|
|
2bbf97be19 | ||
|
|
4f135220bc | ||
|
|
a2500add5a | ||
|
|
26284e89f6 | ||
|
|
8030a7c896 | ||
|
|
5010159621 | ||
|
|
92090bcf99 | ||
|
|
655096ea1e | ||
|
|
362984a391 | ||
|
|
9c9e55147d | ||
|
|
f7a72133c6 | ||
|
|
341dd5930e | ||
|
|
5baa3b98cd | ||
|
|
99c6559a54 | ||
|
|
12827f6c84 | ||
|
|
ad942fd194 | ||
|
|
b5e348ad2b | ||
|
|
0b8376a19e | ||
|
|
b64e2ac9a5 | ||
|
|
c9e2e72d60 | ||
|
|
fc990a9ac2 | ||
|
|
8601fbf8e0 | ||
|
|
48b547661f | ||
|
|
423b060857 | ||
|
|
48cf1a7e39 | ||
|
|
47954db751 | ||
|
|
1fe868b41e | ||
|
|
07f683282a | ||
|
|
0a98eea1ac | ||
|
|
40d86214cb | ||
|
|
959b10c2a1 | ||
|
|
f11fc2e3bc | ||
|
|
2e2fcb0beb | ||
|
|
d2d90083be | ||
|
|
355ffd5975 | ||
|
|
0010ece89c | ||
|
|
c8db639701 | ||
|
|
3bb499dc9d | ||
|
|
45d83435cb | ||
|
|
2535aba2fe | ||
|
|
3dc039135a | ||
|
|
726f90bfae | ||
|
|
709c394d2d | ||
|
|
fb5ea43ef5 | ||
|
|
49f2026c47 | ||
|
|
5ad225fa78 | ||
|
|
155edc997a | ||
|
|
591547c167 | ||
|
|
14e8e63f30 | ||
|
|
ccba0e5137 | ||
|
|
a62e6a5dfc | ||
|
|
975c47a79b | ||
|
|
fcfd5f4981 | ||
|
|
e5315095be | ||
|
|
44e4b5fce2 | ||
|
|
4bb116417e | ||
|
|
51f604d061 | ||
|
|
ab7694b50b | ||
|
|
556e1411f4 | ||
|
|
5fa528fd2d | ||
|
|
3635a13682 | ||
|
|
b59cd73e43 | ||
|
|
6e31a7e2dd | ||
|
|
a4b94b4f45 | ||
|
|
622de21dcf | ||
|
|
0ab5bd1705 | ||
|
|
f74e85662a | ||
|
|
862217a627 | ||
|
|
861e72b331 | ||
|
|
550cb36bd1 | ||
|
|
03d3cee18b | ||
|
|
331e7c13fa | ||
|
|
429538688c | ||
|
|
70ee98f9f0 | ||
|
|
320e9cd9d0 | ||
|
|
50455b828d | ||
|
|
b525b03ef8 | ||
|
|
641669873d | ||
|
|
fb1b673dee | ||
|
|
598a389e3d | ||
|
|
1a0e32504f | ||
|
|
cbff3085fa | ||
|
|
0ecacc6064 | ||
|
|
f36a32773a | ||
|
|
58a3fe1f72 | ||
|
|
4d58fc5f88 | ||
|
|
7d947015d3 | ||
|
|
77d2ad3b6b | ||
|
|
f83695190d | ||
|
|
815721adb2 | ||
|
|
836974e1b2 | ||
|
|
96983d70c8 | ||
|
|
9400a5bc66 | ||
|
|
033319af29 | ||
|
|
787d0e7b4c | ||
|
|
d90617c027 | ||
|
|
98303a286a | ||
|
|
aa791f1948 | ||
|
|
e46466180d | ||
|
|
3b956ca82e | ||
|
|
a0e69009f0 | ||
|
|
59400dbc61 | ||
|
|
0b06dd29c4 | ||
|
|
0152acde9a | ||
|
|
273e5caa6b | ||
|
|
8d5aa0d0ae | ||
|
|
e75c71e0a2 | ||
|
|
f423b221e6 | ||
|
|
702e42b8b3 | ||
|
|
bbc99bbeaa | ||
|
|
e2543bda67 | ||
|
|
442ce1fac1 | ||
|
|
03367b2cac | ||
|
|
a2d212e396 | ||
|
|
cecf9ba0d4 | ||
|
|
5aebc323d5 | ||
|
|
2543cd08c2 | ||
|
|
9c353f2a91 | ||
|
|
722e7e66c2 | ||
|
|
1296992752 | ||
|
|
ab5a82858e | ||
|
|
073772ad60 | ||
|
|
d7e4431bd8 | ||
|
|
96ec12f2bd | ||
|
|
85eea23d98 | ||
|
|
51961d16ba | ||
|
|
d6d73e81d6 | ||
|
|
27a80734f9 | ||
|
|
1d4ea7d0a0 | ||
|
|
6d2e517e82 | ||
|
|
982d3ec4c9 | ||
|
|
5e653c51f3 | ||
|
|
875387166e | ||
|
|
909614c3e7 | ||
|
|
3178ca7572 | ||
|
|
442bdd2220 | ||
|
|
a680db92a7 | ||
|
|
08c350d50b | ||
|
|
fe20187b0c | ||
|
|
65a25a7e66 | ||
|
|
607d8e9566 | ||
|
|
8f3b860cc7 | ||
|
|
a3dc8b7e07 | ||
|
|
6bfb345169 | ||
|
|
704157be00 | ||
|
|
b1c578ccf4 | ||
|
|
7c9f917114 | ||
|
|
34f5455ba5 | ||
|
|
1caf9b3c88 | ||
|
|
763d231e64 |
1
.git-blame-ignore-revs
Normal file
1
.git-blame-ignore-revs
Normal file
@@ -0,0 +1 @@
|
||||
817107622a8fe6f2fdaf198da4b2632854aa9bac
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -27,3 +27,6 @@ scripts/langmover/out
|
||||
tinyproxy.conf
|
||||
static/banner.svg
|
||||
start.sh
|
||||
ts/*.tsbuildinfo
|
||||
ts/**/*.tsbuildinfo
|
||||
js/
|
||||
|
||||
@@ -8,8 +8,8 @@ release:
|
||||
name_template: "v{{.Version}}"
|
||||
before:
|
||||
hooks:
|
||||
- npm i
|
||||
- make precompile
|
||||
- npm ci
|
||||
- env GOOS= GOARCH= make precompile
|
||||
builds:
|
||||
- id: notray
|
||||
dir: ./
|
||||
@@ -34,7 +34,7 @@ builds:
|
||||
- PKG_CONFIG_PATH=/usr/lib/{{ if eq .Arch "amd64" }}x86_64{{ else if eq .Arch "arm64" }}aarch64{{ else }}{{ .Arch }}{{ end }}-linux-gnu{{ if eq .Arch "arm" }}eabihf{{ end }}/pkgconfig:$PKG_CONFIG_PATH
|
||||
- GOARM={{ if eq .Arch "arm" }}7{{ end }}
|
||||
flags:
|
||||
- -tags=e2ee,{{ .Env.JFA_GO_TAG }}
|
||||
- -tags=e2ee,goolm,{{ .Env.JFA_GO_TAG }}
|
||||
ldflags:
|
||||
- -X main.version={{.Env.JFA_GO_VERSION}} -X main.commit={{.ShortCommit}} -X main.updater={{.Env.JFA_GO_UPDATER}} {{.Env.JFA_GO_STRIP}} -X main.cssVersion={{.Env.JFA_GO_CSS_VERSION}} -X main.buildTimeUnix={{.Env.JFA_GO_BUILD_TIME}} -X main.builtBy="{{.Env.JFA_GO_BUILT_BY}}"
|
||||
goos:
|
||||
@@ -65,7 +65,7 @@ builds:
|
||||
- CXX=x86_64-linux-gnu-gcc
|
||||
- PKG_CONFIG_PATH=/usr/lib/{{ if eq .Arch "amd64" }}x86_64{{ else if eq .Arch "arm64" }}aarch64{{ else }}{{ .Arch }}{{ end }}-linux-gnu{{ if eq .Arch "arm" }}eabihf{{ end }}/pkgconfig:$PKG_CONFIG_PATH
|
||||
flags:
|
||||
- -tags=tray,e2ee,{{ .Env.JFA_GO_TAG }}
|
||||
- -tags=tray,e2ee,goolm,{{ .Env.JFA_GO_TAG }}
|
||||
ldflags:
|
||||
- -X main.version={{.Env.JFA_GO_VERSION}} -X main.commit={{.ShortCommit}} -X main.updater={{.Env.JFA_GO_UPDATER}} {{.Env.JFA_GO_STRIP}} -X main.cssVersion={{.Env.JFA_GO_CSS_VERSION}} -X main.buildTimeUnix={{.Env.JFA_GO_BUILD_TIME}} -X main.builtBy="{{.Env.JFA_GO_BUILT_BY}}"
|
||||
goos:
|
||||
@@ -177,13 +177,10 @@ nfpms:
|
||||
replaces:
|
||||
- jfa-go
|
||||
dependencies:
|
||||
- libayatana-appindicator
|
||||
- libolm-dev
|
||||
rpm:
|
||||
dependencies:
|
||||
- libappindicator-gtk3
|
||||
- libolm
|
||||
apk:
|
||||
dependencies:
|
||||
- libayatana-appindicator
|
||||
- olm
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
when:
|
||||
- event: push
|
||||
branch: main
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: docker.io/woodpeckerci/plugin-docker-buildx
|
||||
settings:
|
||||
username:
|
||||
from_secret: DOCKER_USERNAME
|
||||
password:
|
||||
from_secret: DOCKER_TOKEN
|
||||
repo: docker.io/hrfee/jfa-go
|
||||
tags: unstable
|
||||
registry: docker.io
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
build_args:
|
||||
- BUILT_BY:
|
||||
from_secret: BUILT_BY
|
||||
- name: buildrone
|
||||
image: docker.io/python
|
||||
environment:
|
||||
BUILDRONE_KEY:
|
||||
from_secret: BUILDRONE_KEY
|
||||
commands:
|
||||
- wget https://builds.hrfee.pw/upload.py
|
||||
- pip install requests
|
||||
- python upload.py https://builds.hrfee.pw hrfee jfa-go --tag docker-unstable=true
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
when:
|
||||
- event: tag
|
||||
branch: main
|
||||
|
||||
clone:
|
||||
git:
|
||||
image: woodpeckerci/plugin-git
|
||||
settings:
|
||||
tags: true
|
||||
partial: false
|
||||
depth: 0
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: docker.io/hrfee/jfa-go-build-docker:latest
|
||||
environment:
|
||||
JFA_GO_BUILT_BY:
|
||||
from_secret: BUILT_BY
|
||||
commands:
|
||||
- curl -sfL https://goreleaser.com/static/run > ../goreleaser
|
||||
- chmod +x ../goreleaser
|
||||
- ./scripts/version.sh ../goreleaser
|
||||
- name: deb-repo
|
||||
image: docker.io/hrfee/jfa-go-build-docker:latest
|
||||
environment:
|
||||
REPO_SSH_ID:
|
||||
from_secret: REPO_SSH_ID
|
||||
commands:
|
||||
- sh -c "echo \"$REPO_SSH_ID\" > /tmp/id_repo && chmod 600 /tmp/id_repo"
|
||||
- bash -c 'sftp -P 2022 -i /tmp/id_repo -o StrictHostKeyChecking=no root@apt.hrfee.dev:/repo/incoming <<< $"put dist/*.deb"'
|
||||
- bash -c 'ssh -i /tmp/id_repo root@apt.hrfee.dev -p 2022 "repo-process-deb trusty"'
|
||||
- bash -c 'ssh -i /tmp/id_repo root@apt.hrfee.dev -p 2022 "repo-process-deb trusty-unstable"'
|
||||
- bash -c 'ssh -i /tmp/id_repo root@apt.hrfee.dev -p 2022 "rm -f /repo/incoming/*.deb"'
|
||||
- name: buildrone
|
||||
image: docker.io/hrfee/jfa-go-build-docker:latest
|
||||
environment:
|
||||
BUILDRONE_KEY:
|
||||
from_secret: BUILDRONE_KEY
|
||||
commands:
|
||||
- wget https://builds.hrfee.pw/upload.py
|
||||
- bash -c 'python3 upload.py https://builds.hrfee.pw hrfee jfa-go --tag internal=true'
|
||||
@@ -1,29 +0,0 @@
|
||||
when:
|
||||
- event: tag
|
||||
branch: main
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: docker.io/woodpeckerci/plugin-docker-buildx
|
||||
settings:
|
||||
username:
|
||||
from_secret: DOCKER_USERNAME
|
||||
password:
|
||||
from_secret: DOCKER_TOKEN
|
||||
repo: docker.io/hrfee/jfa-go
|
||||
tags: latest
|
||||
registry: docker.io
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
build_args:
|
||||
- BUILT_BY:
|
||||
from_secret: BUILT_BY
|
||||
- name: buildrone
|
||||
image: docker.io/python
|
||||
environment:
|
||||
BUILDRONE_KEY:
|
||||
from_secret: BUILDRONE_KEY
|
||||
commands:
|
||||
- wget https://builds.hrfee.pw/upload.py
|
||||
- pip install requests
|
||||
- python upload.py https://builds.hrfee.pw hrfee jfa-go --tag docker-stable=true
|
||||
|
||||
101
.woodpecker/stable.yaml
Normal file
101
.woodpecker/stable.yaml
Normal file
@@ -0,0 +1,101 @@
|
||||
when:
|
||||
- event: tag
|
||||
branch: main
|
||||
|
||||
clone:
|
||||
git:
|
||||
image: woodpeckerci/plugin-git
|
||||
settings:
|
||||
tags: true
|
||||
partial: false
|
||||
depth: 0
|
||||
|
||||
steps:
|
||||
- name: precompile
|
||||
image: docker.io/hrfee/jfa-go-build-docker:latest
|
||||
environment:
|
||||
JFA_GO_SNAPSHOT: y
|
||||
JFA_GO_BUILT_BY:
|
||||
from_secret: BUILT_BY
|
||||
volumes:
|
||||
- jfa-go-build-cache:/root/.cache/go-build
|
||||
- jfa-go-mod-cache:/go/pkg/mod
|
||||
- jfa-go-npm-cache:/npm
|
||||
commands:
|
||||
- npm i
|
||||
- make precompile
|
||||
- go mod download
|
||||
- name: test
|
||||
image: docker.io/hrfee/jfa-go-build-docker:latest
|
||||
environment:
|
||||
JFA_GO_SNAPSHOT: y
|
||||
JFA_GO_BUILT_BY:
|
||||
from_secret: BUILT_BY
|
||||
volumes:
|
||||
- jfa-go-build-cache:/root/.cache/go-build
|
||||
- jfa-go-mod-cache:/go/pkg/mod
|
||||
- jfa-go-npm-cache:/npm
|
||||
commands:
|
||||
- make test
|
||||
- name: build
|
||||
image: docker.io/hrfee/jfa-go-build-docker:latest
|
||||
environment:
|
||||
JFA_GO_BUILT_BY:
|
||||
from_secret: BUILT_BY
|
||||
GITHUB_TOKEN:
|
||||
from_secret: GITHUB_TOKEN
|
||||
volumes:
|
||||
- jfa-go-build-cache:/root/.cache/go-build
|
||||
- jfa-go-mod-cache:/go/pkg/mod
|
||||
- jfa-go-npm-cache:/npm
|
||||
commands:
|
||||
- ./scripts/version.sh goreleaser
|
||||
- name: deb-repo
|
||||
image: docker.io/hrfee/jfa-go-build-docker:latest
|
||||
environment:
|
||||
REPO_SSH_ID:
|
||||
from_secret: REPO_SSH_ID
|
||||
commands:
|
||||
- sh -c "echo \"$REPO_SSH_ID\" > /tmp/id_repo && chmod 600 /tmp/id_repo"
|
||||
- bash -c 'sftp -P 2022 -i /tmp/id_repo -o StrictHostKeyChecking=no root@apt.hrfee.dev:/repo/incoming <<< $"put dist/*.deb"'
|
||||
- bash -c 'ssh -i /tmp/id_repo root@apt.hrfee.dev -p 2022 "repo-process-deb trusty"'
|
||||
- bash -c 'ssh -i /tmp/id_repo root@apt.hrfee.dev -p 2022 "repo-process-deb trusty-unstable"'
|
||||
- bash -c 'ssh -i /tmp/id_repo root@apt.hrfee.dev -p 2022 "rm -f /repo/incoming/*.deb"'
|
||||
- name: build-external
|
||||
image: docker.io/hrfee/jfa-go-build-docker:latest
|
||||
environment:
|
||||
JFA_GO_BUILT_BY:
|
||||
from_secret: BUILT_BY
|
||||
volumes:
|
||||
- jfa-go-build-cache:/root/.cache/go-build
|
||||
- jfa-go-mod-cache:/go/pkg/mod
|
||||
- jfa-go-npm-cache:/npm
|
||||
commands:
|
||||
- sed -i 's#id="password_resets-watch_directory" placeholder="/config/jellyfin"#id="password_resets-watch_directory" value="/jf" disabled#g' ./build/data/html/setup.html
|
||||
- env GOOS=linux INTERNAL=off ./scripts/version.sh goreleaser build --id notray-e2ee --clean
|
||||
- mv ./dist/notray-e2ee_linux_arm_6 ./dist/notray-e2ee_linux_arm
|
||||
- name: container
|
||||
image: docker.io/woodpeckerci/plugin-docker-buildx
|
||||
settings:
|
||||
dry_run: false
|
||||
dockerfile: Dockerfile.ci
|
||||
username:
|
||||
from_secret: DOCKER_USERNAME
|
||||
password:
|
||||
from_secret: DOCKER_TOKEN
|
||||
repo: docker.io/hrfee/jfa-go
|
||||
tags: stable
|
||||
registry: docker.io
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
build_args:
|
||||
- BUILT_BY:
|
||||
from_secret: BUILT_BY
|
||||
- name: buildrone
|
||||
image: docker.io/hrfee/jfa-go-build-docker:latest
|
||||
environment:
|
||||
BUILDRONE_KEY:
|
||||
from_secret: BUILDRONE_KEY
|
||||
commands:
|
||||
- wget https://builds.hrfee.pw/upload.py
|
||||
- bash -c 'python3 upload.py https://builds.hrfee.pw hrfee jfa-go --tag internal=true'
|
||||
- python3 upload.py https://builds.hrfee.pw hrfee jfa-go --tag docker-stable=true
|
||||
@@ -18,8 +18,12 @@ steps:
|
||||
JFA_GO_SNAPSHOT: y
|
||||
JFA_GO_BUILT_BY:
|
||||
from_secret: BUILT_BY
|
||||
volumes:
|
||||
- jfa-go-build-cache:/root/.cache/go-build
|
||||
- jfa-go-mod-cache:/go/pkg/mod
|
||||
- jfa-go-npm-cache:/npm
|
||||
commands:
|
||||
- npm i
|
||||
- npm ci --cache /npm --prefer-offline
|
||||
- make precompile
|
||||
- go mod download
|
||||
- name: test
|
||||
@@ -28,6 +32,10 @@ steps:
|
||||
JFA_GO_SNAPSHOT: y
|
||||
JFA_GO_BUILT_BY:
|
||||
from_secret: BUILT_BY
|
||||
volumes:
|
||||
- jfa-go-build-cache:/root/.cache/go-build
|
||||
- jfa-go-mod-cache:/go/pkg/mod
|
||||
- jfa-go-npm-cache:/npm
|
||||
commands:
|
||||
- make test
|
||||
- name: build
|
||||
@@ -36,10 +44,20 @@ steps:
|
||||
JFA_GO_SNAPSHOT: y
|
||||
JFA_GO_BUILT_BY:
|
||||
from_secret: BUILT_BY
|
||||
volumes:
|
||||
- jfa-go-build-cache:/root/.cache/go-build
|
||||
- jfa-go-mod-cache:/go/pkg/mod
|
||||
- jfa-go-npm-cache:/npm
|
||||
commands:
|
||||
- curl -sfL https://goreleaser.com/static/run > goreleaser
|
||||
- chmod +x goreleaser
|
||||
- ./scripts/version.sh ./goreleaser --snapshot --skip=publish --clean
|
||||
- ./scripts/version.sh goreleaser --snapshot --skip=publish --clean
|
||||
- name: buildrone-binary
|
||||
image: docker.io/hrfee/jfa-go-build-docker:latest
|
||||
environment:
|
||||
BUILDRONE_KEY:
|
||||
from_secret: BUILDRONE_KEY
|
||||
commands:
|
||||
- wget https://builds.hrfee.pw/upload.py
|
||||
- bash -c 'python3 upload.py https://builds.hrfee.pw hrfee jfa-go --upload ./dist/*.zip ./dist/*.rpm ./dist/*.apk --tag internal-git=true'
|
||||
- name: redoc
|
||||
image: docker.io/hrfee/jfa-go-build-docker:latest
|
||||
environment:
|
||||
@@ -59,11 +77,43 @@ steps:
|
||||
- bash -c 'ssh -i /tmp/id_repo root@apt.hrfee.dev -p 2022 "repo-process-deb trusty-unstable"'
|
||||
- bash -c 'ssh -i /tmp/id_repo root@apt.hrfee.dev -p 2022 "repo-process-deb trusty"'
|
||||
- bash -c 'ssh -i /tmp/id_repo root@apt.hrfee.dev -p 2022 "rm -f /repo/incoming/*.deb"'
|
||||
- name: buildrone
|
||||
- name: build-external
|
||||
image: docker.io/hrfee/jfa-go-build-docker:latest
|
||||
environment:
|
||||
JFA_GO_SNAPSHOT: y
|
||||
JFA_GO_BUILT_BY:
|
||||
from_secret: BUILT_BY
|
||||
volumes:
|
||||
- jfa-go-build-cache:/root/.cache/go-build
|
||||
- jfa-go-mod-cache:/go/pkg/mod
|
||||
- jfa-go-npm-cache:/npm
|
||||
commands:
|
||||
- sed -i 's#id="password_resets-watch_directory" placeholder="/config/jellyfin"#id="password_resets-watch_directory" value="/jf" disabled#g' ./build/data/html/setup.html
|
||||
- env GOOS=linux INTERNAL=off ./scripts/version.sh goreleaser build --snapshot --id notray-e2ee --clean
|
||||
- mv ./dist/notray-e2ee_linux_arm_6 ./dist/notray-e2ee_linux_arm
|
||||
- name: container
|
||||
image: docker.io/woodpeckerci/plugin-docker-buildx
|
||||
settings:
|
||||
dry_run: false
|
||||
dockerfile: Dockerfile.ci
|
||||
username:
|
||||
from_secret: DOCKER_USERNAME
|
||||
password:
|
||||
from_secret: DOCKER_TOKEN
|
||||
repo: docker.io/hrfee/jfa-go
|
||||
tags: unstable
|
||||
registry: docker.io
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
build_args:
|
||||
- BUILT_BY:
|
||||
from_secret: BUILT_BY
|
||||
- name: buildrone-container
|
||||
image: docker.io/hrfee/jfa-go-build-docker:latest
|
||||
environment:
|
||||
BUILDRONE_KEY:
|
||||
from_secret: BUILDRONE_KEY
|
||||
commands:
|
||||
- wget https://builds.hrfee.pw/upload.py
|
||||
- bash -c 'python3 upload.py https://builds.hrfee.pw hrfee jfa-go --upload ./dist/*.zip ./dist/*.rpm ./dist/*.apk --tag internal-git=true'
|
||||
- python3 upload.py https://builds.hrfee.pw hrfee jfa-go --tag docker-unstable=true
|
||||
|
||||
|
||||
16
Dockerfile
16
Dockerfile
@@ -1,26 +1,20 @@
|
||||
# Use this instead if hrfee/jfa-go-build-docker doesn't support your architecture
|
||||
# FROM --platform=$BUILDPLATFORM golang:latest AS support
|
||||
FROM --platform=$BUILDPLATFORM docker.io/hrfee/jfa-go-build-docker:latest AS support
|
||||
# FROM --platform=$BUILDPLATFORM jfa-go-bd AS support
|
||||
ARG BUILT_BY
|
||||
ENV JFA_GO_BUILT_BY=$BUILT_BY
|
||||
|
||||
COPY . /opt/build
|
||||
|
||||
# RUN curl -sfL https://goreleaser.com/static/run > /goreleaser && chmod +x /goreleaser
|
||||
RUN cd /opt/build; INTERNAL=off UPDATER=docker ./scripts/version.sh /goreleaser build --snapshot --skip=validate --clean --id notray-e2ee
|
||||
RUN cd /opt/build; INTERNAL=off UPDATER=docker ./scripts/version.sh goreleaser build --snapshot --skip=validate --clean --id notray-e2ee
|
||||
RUN mv /opt/build/dist/*_linux_arm_6 /opt/build/dist/placeholder_linux_arm
|
||||
RUN sed -i 's#id="password_resets-watch_directory" placeholder="/config/jellyfin"#id="password_resets-watch_directory" value="/jf" disabled#g' /opt/build/build/data/html/setup.html
|
||||
|
||||
FROM golang:bookworm AS final
|
||||
FROM gcr.io/distroless/base:latest AS final
|
||||
ARG TARGETARCH
|
||||
|
||||
COPY --from=support /opt/build/dist/*_linux_${TARGETARCH}* /opt/jfa-go
|
||||
COPY --from=support /opt/build/build/data /opt/jfa-go/data
|
||||
|
||||
RUN apt-get update -y && apt-get install libolm-dev -y
|
||||
COPY --from=support /opt/build/dist/*_linux_${TARGETARCH}* /jfa-go
|
||||
COPY --from=support /opt/build/build/data /jfa-go/data
|
||||
|
||||
EXPOSE 8056
|
||||
EXPOSE 8057
|
||||
|
||||
CMD [ "/opt/jfa-go/jfa-go", "-data", "/data" ]
|
||||
CMD [ "/jfa-go/jfa-go", "-data", "/data" ]
|
||||
|
||||
10
Dockerfile.ci
Normal file
10
Dockerfile.ci
Normal file
@@ -0,0 +1,10 @@
|
||||
FROM gcr.io/distroless/base:latest AS final
|
||||
ARG TARGETARCH
|
||||
|
||||
COPY ./dist/notray-e2ee_linux_${TARGETARCH}* /jfa-go
|
||||
COPY ./build/data /jfa-go/data
|
||||
|
||||
EXPOSE 8056
|
||||
EXPOSE 8057
|
||||
|
||||
CMD [ "/jfa-go/jfa-go", "-data", "/data" ]
|
||||
2
LICENSE
2
LICENSE
@@ -2,7 +2,7 @@
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Harvey Tindall
|
||||
Copyright (c) 2025 Harvey Tindall
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
60
Makefile
60
Makefile
@@ -1,6 +1,8 @@
|
||||
.PHONY: configuration email typescript swagger copy compile compress inline-css variants-html install clean npm config-description config-default precompile test
|
||||
.DEFAULT_GOAL := all
|
||||
|
||||
TS ?= npx tsgo
|
||||
|
||||
GOESBUILD ?= off
|
||||
ifeq ($(GOESBUILD), on)
|
||||
ESBUILD := esbuild
|
||||
@@ -9,7 +11,7 @@ else
|
||||
endif
|
||||
GOBINARY ?= go
|
||||
|
||||
CSSVERSION ?= v3
|
||||
CSSVERSION ?= $(shell git describe --tags --abbrev=0)
|
||||
CSS_BUNDLE = $(DATA)/web/css/$(CSSVERSION)bundle.css
|
||||
|
||||
VERSION ?= $(shell git describe --exact-match HEAD 2> /dev/null || echo vgit)
|
||||
@@ -33,7 +35,7 @@ E2EE ?= on
|
||||
TAGS := -tags "
|
||||
|
||||
ifeq ($(INTERNAL), on)
|
||||
DATA := data
|
||||
DATA := build/data
|
||||
COMPDEPS := $(BUILDDEPS)
|
||||
else
|
||||
DATA := build/data
|
||||
@@ -46,7 +48,7 @@ ifeq ($(TRAY), on)
|
||||
endif
|
||||
|
||||
ifeq ($(E2EE), on)
|
||||
TAGS := $(TAGS) e2ee
|
||||
TAGS := $(TAGS) e2ee goolm
|
||||
endif
|
||||
|
||||
TAGS := $(TAGS)"
|
||||
@@ -60,7 +62,7 @@ DEBUG ?= off
|
||||
ifeq ($(DEBUG), on)
|
||||
SOURCEMAP := --sourcemap
|
||||
MINIFY :=
|
||||
TYPECHECK := npx tsc -noEmit --project ts/tsconfig.json
|
||||
TYPECHECK := $(TS) -noEmit --incremental --project ts/tsconfig.json
|
||||
# jank
|
||||
COPYTS := rm -r $(DATA)/web/js/ts; cp -r tempts $(DATA)/web/js/ts
|
||||
UNCSS := cp $(CSS_BUNDLE) $(DATA)/bundle.css
|
||||
@@ -102,6 +104,13 @@ else
|
||||
SWAGINSTALL :=
|
||||
endif
|
||||
|
||||
# FLAG HASHING: To rebuild on flag change.
|
||||
# credit for idea to https://bnikolic.co.uk/blog/sh/make/unix/2021/07/08/makefile.html
|
||||
rebuildFlags := GOESBUILD GOBINARY VERSION COMMIT UPDATER INTERNAL TRAY E2EE TAGS DEBUG RACE
|
||||
rebuildVals := $(foreach v,$(rebuildFlags),$(v)=$($(v)))
|
||||
rebuildHash := $(strip $(shell echo $(rebuildVals) | sha256sum | cut -d " " -f1))
|
||||
rebuildHashFile := $(DATA)/buildhash-$(rebuildHash).txt
|
||||
|
||||
CONFIG_BASE = config/config-base.yaml
|
||||
|
||||
# CONFIG_DESCRIPTION = $(DATA)/config-base.json
|
||||
@@ -116,7 +125,7 @@ $(DATA):
|
||||
|
||||
$(CONFIG_DEFAULT): $(CONFIG_BASE)
|
||||
$(info Generating config-default.ini)
|
||||
go run scripts/ini/main.go -in $(CONFIG_BASE) -out $(DATA)/config-default.ini
|
||||
CGO_ENABLED=0 go run scripts/ini/main.go -in $(CONFIG_BASE) -out $(DATA)/config-default.ini
|
||||
|
||||
configuration: $(CONFIG_DEFAULT)
|
||||
|
||||
@@ -135,11 +144,14 @@ TYPESCRIPT_TEMPSRC = $(TYPESCRIPT_SRC:ts/%=tempts/%)
|
||||
TYPESCRIPT_TARGET = $(DATA)/web/js/admin.js
|
||||
$(TYPESCRIPT_TARGET): $(TYPESCRIPT_FULLSRC) ts/tsconfig.json
|
||||
$(TYPECHECK)
|
||||
# rm -rf tempts
|
||||
# cp -r ts tempts
|
||||
rm -rf tempts
|
||||
cp -r ts tempts
|
||||
mkdir -p tempts
|
||||
$(adding dark variants to typescript)
|
||||
scripts/dark-variant.sh tempts
|
||||
scripts/dark-variant.sh tempts/modules
|
||||
# scripts/dark-variant.sh tempts
|
||||
# scripts/dark-variant.sh tempts/modules
|
||||
CGO_ENABLED=0 go run scripts/variants/main.go -dir ts -out tempts
|
||||
$(info compiling typescript)
|
||||
$(foreach tempsrc,$(TYPESCRIPT_TEMPSRC),$(ESBUILD) --target=es6 --bundle $(tempsrc) $(SOURCEMAP) --outfile=$(patsubst %.ts,%.js,$(subst tempts/,./$(DATA)/web/js/,$(tempsrc))) $(MINIFY);)
|
||||
$(COPYTS)
|
||||
@@ -148,9 +160,9 @@ SWAGGER_SRC = $(wildcard api*.go) $(wildcard *auth.go) views.go
|
||||
SWAGGER_TARGET = docs/docs.go
|
||||
$(SWAGGER_TARGET): $(SWAGGER_SRC)
|
||||
$(SWAGINSTALL)
|
||||
swag init -g main.go
|
||||
swag init --parseDependency --parseInternal -g main.go
|
||||
|
||||
VARIANTS_SRC = $(wildcard html/*.html)
|
||||
VARIANTS_SRC = $(wildcard html/*.html) $(wildcard html/*.txt)
|
||||
VARIANTS_TARGET = $(DATA)/html/admin.html
|
||||
$(VARIANTS_TARGET): $(VARIANTS_SRC)
|
||||
$(info copying html)
|
||||
@@ -160,15 +172,24 @@ $(VARIANTS_TARGET): $(VARIANTS_SRC)
|
||||
|
||||
ICON_SRC = node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2
|
||||
ICON_TARGET = $(ICON_SRC:node_modules/remixicon/fonts/%=$(DATA)/web/css/%)
|
||||
SYNTAX_LIGHT_SRC = node_modules/highlight.js/styles/base16/atelier-sulphurpool-light.min.css
|
||||
SYNTAX_LIGHT_TARGET = $(DATA)/web/css/$(CSSVERSION)highlightjs-light.css
|
||||
SYNTAX_DARK_SRC = node_modules/highlight.js/styles/base16/circus.min.css
|
||||
SYNTAX_DARK_TARGET = $(DATA)/web/css/$(CSSVERSION)highlightjs-dark.css
|
||||
CODEINPUT_SRC = node_modules/@webcoder49/code-input/code-input.min.css
|
||||
CODEINPUT_TARGET = $(DATA)/web/css/$(CSSVERSION)code-input.css
|
||||
CSS_SRC = $(wildcard css/*.css)
|
||||
CSS_TARGET = $(DATA)/web/css/part-bundle.css
|
||||
CSS_FULLTARGET = $(CSS_BUNDLE)
|
||||
ALL_CSS_SRC = $(ICON_SRC) $(CSS_SRC)
|
||||
ALL_CSS_SRC = $(ICON_SRC) $(CSS_SRC) $(SYNTAX_LIGHT_SRC) $(SYNTAX_DARK_SRC)
|
||||
ALL_CSS_TARGET = $(ICON_TARGET)
|
||||
|
||||
$(CSS_FULLTARGET): $(TYPESCRIPT_TARGET) $(VARIANTS_TARGET) $(ALL_CSS_SRC) $(wildcard html/*.html)
|
||||
$(CSS_FULLTARGET): $(TYPESCRIPT_TARGET) $(VARIANTS_TARGET) $(ALL_CSS_SRC) $(wildcard html/*.html) $(wildcard html.*.txt)
|
||||
$(info copying fonts)
|
||||
cp -r node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2 $(DATA)/web/css/
|
||||
cp -r $(SYNTAX_LIGHT_SRC) $(SYNTAX_LIGHT_TARGET)
|
||||
cp -r $(SYNTAX_DARK_SRC) $(SYNTAX_DARK_TARGET)
|
||||
cp -r $(CODEINPUT_SRC) $(CODEINPUT_TARGET)
|
||||
$(info bundling css)
|
||||
rm -f $(CSS_TARGET) $(CSS_FULLTARGET)
|
||||
$(ESBUILD) --bundle css/base.css --outfile=$(CSS_TARGET) --external:remixicon.css --external:../fonts/hanken* --minify
|
||||
@@ -195,7 +216,7 @@ COPY_TARGET = $(DATA)/jfa-go.service
|
||||
# $(DATA)/LICENSE $(LANG_TARGET) $(STATIC_TARGET) $(DATA)/web/css/$(CSSVERSION)bundle.css
|
||||
$(COPY_TARGET): $(INLINE_TARGET) $(STATIC_SRC) $(LANG_SRC) $(CONFIG_BASE)
|
||||
$(info copying $(CONFIG_BASE))
|
||||
cp $(CONFIG_BASE) $(DATA)/
|
||||
CGO_ENABLED=0 go run scripts/yaml/main.go -in $(CONFIG_BASE) -out $(DATA)/$(shell basename $(CONFIG_BASE))
|
||||
$(info copying crash page)
|
||||
cp $(DATA)/crash.html $(DATA)/html/
|
||||
$(info copying static data)
|
||||
@@ -210,11 +231,16 @@ $(COPY_TARGET): $(INLINE_TARGET) $(STATIC_SRC) $(LANG_SRC) $(CONFIG_BASE)
|
||||
BUILDDEPS := $(DATA) $(CONFIG_DEFAULT) $(EMAIL_TARGET) $(COPY_TARGET) $(SWAGGER_TARGET) $(INLINE_TARGET) $(CSS_FULLTARGET) $(TYPESCRIPT_TARGET)
|
||||
precompile: $(BUILDDEPS)
|
||||
|
||||
COMPDEPS =
|
||||
COMPDEPS = $(rebuildHashFile)
|
||||
ifeq ($(INTERNAL), on)
|
||||
COMPDEPS = $(BUILDDEPS)
|
||||
COMPDEPS = $(BUILDDEPS) $(rebuildHashFile)
|
||||
endif
|
||||
|
||||
$(rebuildHashFile):
|
||||
$(info recording new flags $(rebuildVals))
|
||||
rm -f $(DATA)/buildhash-*.txt
|
||||
touch $(rebuildHashFile)
|
||||
|
||||
GO_SRC = $(shell find ./ -name "*.go")
|
||||
GO_TARGET = build/jfa-go
|
||||
$(GO_TARGET): $(COMPDEPS) $(SWAGGER_TARGET) $(GO_SRC) go.mod go.sum
|
||||
@@ -222,12 +248,12 @@ $(GO_TARGET): $(COMPDEPS) $(SWAGGER_TARGET) $(GO_SRC) go.mod go.sum
|
||||
$(GOBINARY) mod download
|
||||
$(info Building)
|
||||
mkdir -p build
|
||||
$(GOBINARY) build $(RACEDETECTOR) -ldflags="$(LDFLAGS)" $(TAGS) -o $(GO_TARGET)
|
||||
$(GOBINARY) build $(RACEDETECTOR) -ldflags="$(LDFLAGS)" $(TAGS) -o $(GO_TARGET) $(GOBUILDFLAGS)
|
||||
|
||||
test: $(BUILDDEPS) $(COMPDEPS) $(SWAGGER_TARGET) $(GO_SRC) go.mod go.sum
|
||||
$(GOBINARY) test -ldflags="$(LDFLAGS)" $(TAGS) -p 1
|
||||
|
||||
all: $(BUILDDEPS) $(GO_TARGET)
|
||||
all: $(BUILDDEPS) $(GO_TARGET) $(rebuildHashFile)
|
||||
|
||||
compress:
|
||||
upx --lzma $(GO_TARGET)
|
||||
|
||||
45
README.md
45
README.md
@@ -13,39 +13,28 @@
|
||||
Studies mean I can't work on this project a lot outside of breaks, however I hope i'll be able to fit in general support and things like bug fixes into my time. New features and such will likely come in short bursts throughout the year (if they do at all).
|
||||
|
||||
#### Does/Will it still work?
|
||||
jfa-go currently works on Jellyfin 10.11.0, the latest version as of 21/10/25. I should be able to maintain compatability in the future, unless any big changes occur.
|
||||
jfa-go currently works on Jellyfin 10.11.0, the latest version as of 21/10/25. I should be able to maintain compatibility in the future, unless any big changes occur.
|
||||
|
||||
#### Alternatives
|
||||
If you want a bit more of a guarantee of support, I've seen these projects mentioned although haven't tried them myself.
|
||||
If you want a bit more guarantee of support [Wizarr](https://github.com/Wizarrrr/wizarr) is popular and seems very polished. It supports multiple media servers, lots of customization and invitation through Discord.
|
||||
|
||||
* [Wizarr](https://github.com/Wizarrrr/wizarr) focuses on invites, and also includes some Discord & Ombi integration.
|
||||
* [Jellyseerr](https://github.com/Fallenbagel/jellyseerr) is a fork of Overseerr which can manage users and mainly acts as an Ombi alternative.
|
||||
* [jfa-go now integrates with Jellyseerr, much like Ombi, but better.](https://github.com/hrfee/jfa-go/pull/351)
|
||||
* [Organizr](https://github.com/causefx/Organizr) doesn't focus on Jellyfin, but allows putting self-hosted services into "tabs" on a central page, and allows creating users, which lets one control who can access what.
|
||||
---
|
||||
jfa-go is a user management app for [Jellyfin](https://github.com/jellyfin/jellyfin) (and [Emby](https://emby.media/) as 2nd class) that provides invite-based account creation as well as other features that make one's instance much easier to manage.
|
||||
|
||||
jfa-go is a user management app for [Jellyfin](https://github.com/jellyfin/jellyfin) (and [Emby](https://emby.media/) as 2nd class) that provides invite-based account creation as well as other features that make ones instance much easier to manage.
|
||||
|
||||
#### Features
|
||||
* 🧑 Invite based account creation: Send invites to your friends or family, and let them choose their own username and password without relying on you.
|
||||
* Send invites via a link and/or email, discord, telegram or matrix
|
||||
* Granular control over invites: Validity period as well as number of uses can be specified.
|
||||
* Account profiles: Assign settings profiles to invites so new users have your predefined permissions, homescreen layout, etc. applied to their account on creation.
|
||||
* Password validation: Ensure users choose a strong password.
|
||||
* CAPTCHAs and contact method verificatoin can be enabled to avoid bots.
|
||||
* ⌛ User expiry: Specify a validity period, and new users accounts will be disabled/deleted after it. The period can be manually extended too.
|
||||
* 🔗 Ombi/Jellyseerr Integration: Automatically creates and synchronizes details for new accounts. Supports setting permissions with the Profiles feature. **Ombi integration use is risky, see [wiki](https://wiki.jfa-go.com/docs/ombi/)**.
|
||||
* Account management: Bulk or individually; apply settings, delete, disable/enable, send messages and much more.
|
||||
* 📣 Announcements: Bulk message your users with announcements about your server.
|
||||
* Telegram/Discord/Matrix Integration: Verify users via a chat bot, and send Password Resets, Announcements, etc. through it.
|
||||
* "My Account" Page: Allows users to reset their password, manage contact details, view their account expiry date, and send referrals. Can be customized with markdown.
|
||||
* Referrals: Users can be given special invites to send to their friends and families, similar to some invite-only services like Bluesky.
|
||||
* 🔑 Password resets: When users forget their passwords and request a change in Jellyfin, jfa-go reads the PIN from the created file and sends it straight to them via email/telegram.
|
||||
* Can also be done through the "My Account" page if enabled.
|
||||
* Admin Notifications: Get notified when someone creates an account, or an invite expires.
|
||||
* 🌓 Customizations
|
||||
* Customize emails with variables and markdown
|
||||
* Specify contact and help messages to appear in emails and pages
|
||||
* Light and dark themes available
|
||||
* **Invites**: Send invite links to new users so they can sign up without relying on you.
|
||||
* Customize with profiles: Apply Jellyfin settings (library access, transcoding, etc.) on sign-up, with different profiles for each user type.
|
||||
* Limit invites by time or number of uses, enforce strong passwords, require a CAPTCHA, and more
|
||||
* **Password Resets**: Let your users do it themselves. Works with the Jellyfin "Forgot Password" feature, or through the "My Account" page. [See the wiki for your options](https://wiki.jfa-go.com/docs/pwr/).
|
||||
* **Contact your users**: Collect email address, Discord/Telegram/Matrix info when the user signs up or add later, and jfa-go will contact them when needed (e.g. on/before account expiry, disabling/enabling, deletion) or when you wish with Markdown announcements.
|
||||
* "Confirm email" optional, similar is required for Discord/Telegram/Matrix
|
||||
* **"My Account"**: Lets your users change their password or email/contact info themselves and show them relevant info on a special page. Also,
|
||||
* Referrals: Allow users a special, limited invite to give to their friends/family.
|
||||
* **Advanced user management**: See all of your users at once and manage them in bulk (enable/disable/delete, send markdown announcements, apply profiles/settings, and more)
|
||||
* User expiry: Set on an invite, and any new users will be valid for a fixed period (e.g. 30 days). After time passes, account is disabled, deleted, or disabled then deleted.
|
||||
* **Ombi/Jellyseerr integration**: Sync username/passwords & contact details between your services.
|
||||
* **Customizable**: Edit messages sent to users and shown on invites, "My Account" page and more with full Markdown support.
|
||||
|
||||
#### Interface
|
||||
<p align="center">
|
||||
@@ -57,7 +46,7 @@ jfa-go is a user management app for [Jellyfin](https://github.com/jellyfin/jelly
|
||||
|
||||
#### Install
|
||||
|
||||
**Note**: `TrayIcon` builds include a tray icon to start/stop/restart, and an option to automatically start when you log-in to your computer. For Linux users, these builds depend on the `libappindicator3-1`/`libappindicator-gtk3`/`libappindicator` package for Debian/Ubuntu, Fedora, and Alpine respectively.
|
||||
**Note**: `TrayIcon` builds include a tray icon to start/stop/restart, and an option to automatically start when you log-in to your computer.
|
||||
|
||||
`MatrixE2EE` builds (and Linux `TrayIcon` builds) include support for end-to-end encryption for the Matrix bot, but require the `libolm(-dev)` dependency. `.deb/.rpm/.apk` packages list this dependency, and docker images include it.
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ func (app *appContext) generateActivitiesQuery(req ServerFilterReqDTO) *badgerho
|
||||
for _, q := range req.Queries {
|
||||
nq := q.AsDBQuery(query)
|
||||
if nq == nil {
|
||||
nq = ActivityDBQueryFromSpecialField(app.jf, query, q)
|
||||
nq = ActivityDBQueryFromSpecialField(app.jf.MediaBrowser, query, q)
|
||||
}
|
||||
query = nq
|
||||
}
|
||||
@@ -116,7 +116,7 @@ func (app *appContext) generateActivitiesQuery(req ServerFilterReqDTO) *badgerho
|
||||
// @Success 200 {object} GetActivitiesRespDTO
|
||||
// @Router /activity [post]
|
||||
// @Security Bearer
|
||||
// @tags Activity
|
||||
// @tags Activity,Statistics
|
||||
func (app *appContext) GetActivities(gc *gin.Context) {
|
||||
req := ServerSearchReqDTO{}
|
||||
gc.BindJSON(&req)
|
||||
@@ -156,8 +156,8 @@ func (app *appContext) GetActivities(gc *gin.Context) {
|
||||
Value: act.Value,
|
||||
Time: act.Time.Unix(),
|
||||
IP: act.IP,
|
||||
Username: act.MustGetUsername(app.jf),
|
||||
SourceUsername: act.MustGetSourceUsername(app.jf),
|
||||
Username: act.MustGetUsername(app.jf.MediaBrowser),
|
||||
SourceUsername: act.MustGetSourceUsername(app.jf.MediaBrowser),
|
||||
}
|
||||
if act.Type == ActivityDeletion || act.Type == ActivityCreation {
|
||||
// Username would've been in here, clear it to avoid confusion to the consumer
|
||||
@@ -185,7 +185,7 @@ func (app *appContext) DeleteActivity(gc *gin.Context) {
|
||||
// @Success 200 {object} PageCountDTO
|
||||
// @Router /activity/count [get]
|
||||
// @Security Bearer
|
||||
// @tags Activity
|
||||
// @tags Activity,Statistics
|
||||
func (app *appContext) GetActivityCount(gc *gin.Context) {
|
||||
resp := PageCountDTO{}
|
||||
var err error
|
||||
@@ -202,7 +202,7 @@ func (app *appContext) GetActivityCount(gc *gin.Context) {
|
||||
// @Success 200 {object} PageCountDTO
|
||||
// @Router /activity/count [post]
|
||||
// @Security Bearer
|
||||
// @tags Activity
|
||||
// @tags Activity,Statistics
|
||||
func (app *appContext) GetFilteredActivityCount(gc *gin.Context) {
|
||||
resp := PageCountDTO{}
|
||||
req := ServerFilterReqDTO{}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
@@ -29,10 +30,15 @@ func (app *appContext) CreateBackup(gc *gin.Context) {
|
||||
// @Security Bearer
|
||||
// @tags Backups
|
||||
func (app *appContext) GetBackup(gc *gin.Context) {
|
||||
fname := gc.Param("fname")
|
||||
escapedFName := gc.Param("fname")
|
||||
fname, err := url.QueryUnescape(escapedFName)
|
||||
if err != nil {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
// Hopefully this is enough to ensure the path isn't malicious. Hidden behind bearer auth anyway so shouldn't matter too much I guess.
|
||||
b := Backup{}
|
||||
err := b.FromString(fname)
|
||||
err = b.FromString(fname)
|
||||
if err != nil || b.Date.IsZero() {
|
||||
app.debug.Printf(lm.IgnoreInvalidFilename, fname, err)
|
||||
respondBool(400, false, gc)
|
||||
|
||||
381
api-invites.go
381
api-invites.go
@@ -157,6 +157,184 @@ func (app *appContext) sendAdminExpiryNotification(data Invite) *sync.WaitGroup
|
||||
return &wait
|
||||
}
|
||||
|
||||
// @Summary Send an existing invite to an email address or discord user.
|
||||
// @Produce json
|
||||
// @Param SendInviteDTO body SendInviteDTO true "Email address or Discord username"
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 500 {object} stringResponse
|
||||
// @Router /invites/send [post]
|
||||
// @Security Bearer
|
||||
// @tags Invites
|
||||
func (app *appContext) SendInvite(gc *gin.Context) {
|
||||
var req SendInviteDTO
|
||||
gc.BindJSON(&req)
|
||||
inv, ok := app.storage.GetInvitesKey(req.Invite)
|
||||
if !ok {
|
||||
app.err.Printf(lm.FailedGetInvite, req.Invite, lm.NotFound)
|
||||
respond(500, "Invite not found", gc)
|
||||
return
|
||||
}
|
||||
err := app.sendInvite(req.sendInviteDTO, &inv)
|
||||
// Even if failed, some error info might have been stored in the invite.
|
||||
app.storage.SetInvitesKey(req.Invite, inv)
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedSendInviteMessage, req.Invite, req.SendTo, err)
|
||||
respond(500, err.Error(), gc)
|
||||
return
|
||||
}
|
||||
app.info.Printf(lm.SentInviteMessage, req.Invite, req.SendTo)
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Edit an existing invite. Not all fields are modifiable.
|
||||
// @Produce json
|
||||
// @Param EditableInviteDTO body EditableInviteDTO true "Email address or Discord username"
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 500 {object} stringResponse
|
||||
// @Failure 400 {object} stringResponse
|
||||
// @Router /invites/edit [patch]
|
||||
// @Security Bearer
|
||||
// @tags Invites
|
||||
func (app *appContext) EditInvite(gc *gin.Context) {
|
||||
var req EditableInviteDTO
|
||||
gc.BindJSON(&req)
|
||||
inv, ok := app.storage.GetInvitesKey(req.Code)
|
||||
if !ok {
|
||||
msg := fmt.Sprintf(lm.InvalidInviteCode, req.Code)
|
||||
app.err.Println(msg)
|
||||
respond(400, msg, gc)
|
||||
return
|
||||
}
|
||||
changed := false
|
||||
|
||||
if req.NotifyCreation != nil || req.NotifyExpiry != nil {
|
||||
setNotify := map[string]bool{}
|
||||
if req.NotifyExpiry != nil {
|
||||
setNotify["notify-expiry"] = *req.NotifyExpiry
|
||||
}
|
||||
if req.NotifyCreation != nil {
|
||||
setNotify["notify-creation"] = *req.NotifyCreation
|
||||
}
|
||||
ch, ok := app.SetNotify(&inv, setNotify, gc)
|
||||
changed = changed || ch
|
||||
if ch && !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
if req.Profile != nil {
|
||||
ch, ok := app.SetProfile(&inv, *req.Profile, gc)
|
||||
changed = changed || ch
|
||||
if ch && !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
if req.Label != nil {
|
||||
*req.Label = strings.TrimSpace(*req.Label)
|
||||
changed = changed || (*req.Label != inv.Label)
|
||||
inv.Label = *req.Label
|
||||
}
|
||||
if req.UserLabel != nil {
|
||||
*req.UserLabel = strings.TrimSpace(*req.UserLabel)
|
||||
changed = changed || (*req.UserLabel != inv.UserLabel)
|
||||
inv.UserLabel = *req.UserLabel
|
||||
}
|
||||
if req.UserExpiry != nil {
|
||||
changed = changed || (*req.UserExpiry != inv.UserExpiry)
|
||||
inv.UserExpiry = *req.UserExpiry
|
||||
if !inv.UserExpiry {
|
||||
inv.UserMonths = 0
|
||||
inv.UserDays = 0
|
||||
inv.UserHours = 0
|
||||
inv.UserMinutes = 0
|
||||
}
|
||||
}
|
||||
if req.UserMonths != nil || req.UserDays != nil || req.UserHours != nil || req.UserMinutes != nil {
|
||||
if inv.UserMonths == 0 &&
|
||||
inv.UserDays == 0 &&
|
||||
inv.UserHours == 0 &&
|
||||
inv.UserMinutes == 0 {
|
||||
changed = changed || (inv.UserExpiry != false)
|
||||
inv.UserExpiry = false
|
||||
}
|
||||
if req.UserMonths != nil {
|
||||
changed = changed || (*req.UserMonths != inv.UserMonths)
|
||||
inv.UserMonths = *req.UserMonths
|
||||
}
|
||||
if req.UserDays != nil {
|
||||
changed = changed || (*req.UserDays != inv.UserDays)
|
||||
inv.UserDays = *req.UserDays
|
||||
}
|
||||
if req.UserHours != nil {
|
||||
changed = changed || (*req.UserHours != inv.UserHours)
|
||||
inv.UserHours = *req.UserHours
|
||||
}
|
||||
if req.UserMinutes != nil {
|
||||
changed = changed || (*req.UserMinutes != inv.UserMinutes)
|
||||
inv.UserMinutes = *req.UserMinutes
|
||||
}
|
||||
}
|
||||
|
||||
if changed {
|
||||
app.storage.SetInvitesKey(inv.Code, inv)
|
||||
}
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// sendInvite attempts to send an invite to the given email address or discord username.
|
||||
func (app *appContext) sendInvite(req sendInviteDTO, invite *Invite) (err error) {
|
||||
if !(app.config.Section("invite_emails").Key("enabled").MustBool(false)) {
|
||||
// app.err.Printf(lm.FailedSendInviteMessage, invite.Code, req.SendTo, errors.New(lm.InviteMessagesDisabled))
|
||||
err = errors.New(lm.InviteMessagesDisabled)
|
||||
return err
|
||||
}
|
||||
discord := ""
|
||||
if discordEnabled && (!strings.Contains(req.SendTo, "@") || strings.HasPrefix(req.SendTo, "@")) {
|
||||
users := app.discord.GetUsers(req.SendTo)
|
||||
if len(users) == 0 {
|
||||
invite.SentTo.Failed = append(invite.SentTo.Failed, SendFailure{
|
||||
Address: req.SendTo,
|
||||
Reason: NoUser,
|
||||
})
|
||||
err = fmt.Errorf(lm.InvalidAddress, req.SendTo)
|
||||
return err
|
||||
} else if len(users) > 1 {
|
||||
invite.SentTo.Failed = append(invite.SentTo.Failed, SendFailure{
|
||||
Address: req.SendTo,
|
||||
Reason: MultiUser,
|
||||
})
|
||||
err = fmt.Errorf(lm.InvalidAddress, req.SendTo)
|
||||
return err
|
||||
}
|
||||
discord = users[0].User.ID
|
||||
}
|
||||
var msg *Message
|
||||
msg, err = app.email.constructInvite(invite, false)
|
||||
if err != nil {
|
||||
// Slight misuse of the template
|
||||
invite.SentTo.Failed = append(invite.SentTo.Failed, SendFailure{
|
||||
Address: req.SendTo,
|
||||
Reason: CheckLogs,
|
||||
})
|
||||
// app.err.Printf(lm.FailedConstructInviteMessage, req.SendTo, err)
|
||||
return err
|
||||
}
|
||||
if discord != "" {
|
||||
err = app.discord.SendDM(msg, discord)
|
||||
} else {
|
||||
err = app.email.send(msg, req.SendTo)
|
||||
}
|
||||
if err != nil {
|
||||
invite.SentTo.Failed = append(invite.SentTo.Failed, SendFailure{
|
||||
Address: req.SendTo,
|
||||
Reason: CheckLogs,
|
||||
})
|
||||
return err
|
||||
// app.err.Println(invite.SendTo)
|
||||
}
|
||||
invite.SentTo.Success = append(invite.SentTo.Success, req.SendTo)
|
||||
return err
|
||||
}
|
||||
|
||||
// @Summary Create a new invite.
|
||||
// @Produce json
|
||||
// @Param generateInviteDTO body generateInviteDTO true "New invite request object"
|
||||
@@ -198,48 +376,11 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
|
||||
}
|
||||
invite.ValidTill = validTill
|
||||
if req.SendTo != "" {
|
||||
if !(app.config.Section("invite_emails").Key("enabled").MustBool(false)) {
|
||||
app.err.Printf(lm.FailedSendInviteMessage, invite.Code, req.SendTo, errors.New(lm.InviteMessagesDisabled))
|
||||
err := app.sendInvite(req.sendInviteDTO, &invite)
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedSendInviteMessage, invite.Code, req.SendTo, err)
|
||||
} else {
|
||||
addressValid := false
|
||||
discord := ""
|
||||
if discordEnabled && (!strings.Contains(req.SendTo, "@") || strings.HasPrefix(req.SendTo, "@")) {
|
||||
users := app.discord.GetUsers(req.SendTo)
|
||||
if len(users) == 0 {
|
||||
invite.SendTo = fmt.Sprintf(lm.FailedSendToTooltipNoUser, req.SendTo)
|
||||
} else if len(users) > 1 {
|
||||
invite.SendTo = fmt.Sprintf(lm.FailedSendToTooltipMultiUser, req.SendTo)
|
||||
} else {
|
||||
invite.SendTo = req.SendTo
|
||||
addressValid = true
|
||||
discord = users[0].User.ID
|
||||
}
|
||||
} else if emailEnabled {
|
||||
addressValid = true
|
||||
invite.SendTo = req.SendTo
|
||||
}
|
||||
if addressValid {
|
||||
msg, err := app.email.constructInvite(invite, false)
|
||||
if err != nil {
|
||||
// Slight misuse of the template
|
||||
invite.SendTo = fmt.Sprintf(lm.FailedConstructInviteMessage, req.SendTo, err)
|
||||
|
||||
app.err.Printf(lm.FailedConstructInviteMessage, invite.Code, err)
|
||||
} else {
|
||||
var err error
|
||||
if discord != "" {
|
||||
err = app.discord.SendDM(msg, discord)
|
||||
} else {
|
||||
err = app.email.send(msg, req.SendTo)
|
||||
}
|
||||
if err != nil {
|
||||
invite.SendTo = fmt.Sprintf(lm.FailedSendInviteMessage, invite.Code, req.SendTo, err)
|
||||
app.err.Println(invite.SendTo)
|
||||
} else {
|
||||
app.info.Printf(lm.SentInviteMessage, invite.Code, req.SendTo)
|
||||
}
|
||||
}
|
||||
}
|
||||
app.info.Printf(lm.SentInviteMessage, invite.Code, req.SendTo)
|
||||
}
|
||||
}
|
||||
if req.Profile != "" {
|
||||
@@ -270,7 +411,7 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
|
||||
// @Success 200 {object} PageCountDTO
|
||||
// @Router /invites/count [get]
|
||||
// @Security Bearer
|
||||
// @tags Invites
|
||||
// @tags Invites,Statistics
|
||||
func (app *appContext) GetInviteCount(gc *gin.Context) {
|
||||
resp := PageCountDTO{}
|
||||
var err error
|
||||
@@ -286,7 +427,7 @@ func (app *appContext) GetInviteCount(gc *gin.Context) {
|
||||
// @Success 200 {object} PageCountDTO
|
||||
// @Router /invites/count/used [get]
|
||||
// @Security Bearer
|
||||
// @tags Invites
|
||||
// @tags Invites,Statistics
|
||||
func (app *appContext) GetInviteUsedCount(gc *gin.Context) {
|
||||
resp := PageCountDTO{}
|
||||
var err error
|
||||
@@ -310,33 +451,36 @@ func (app *appContext) GetInviteUsedCount(gc *gin.Context) {
|
||||
// @Success 200 {object} getInvitesDTO
|
||||
// @Router /invites [get]
|
||||
// @Security Bearer
|
||||
// @tags Invites
|
||||
// @tags Invites,Statistics
|
||||
func (app *appContext) GetInvites(gc *gin.Context) {
|
||||
currentTime := time.Now()
|
||||
// currentTime := time.Now()
|
||||
app.checkInvites()
|
||||
var invites []inviteDTO
|
||||
for _, inv := range app.storage.GetInvites() {
|
||||
if inv.IsReferral {
|
||||
continue
|
||||
}
|
||||
years, months, days, hours, minutes, _ := timeDiff(inv.ValidTill, currentTime)
|
||||
months += years * 12
|
||||
// years, months, days, hours, minutes, _ := timeDiff(inv.ValidTill, currentTime)
|
||||
// months += years * 12
|
||||
invite := inviteDTO{
|
||||
Code: inv.Code,
|
||||
Months: months,
|
||||
Days: days,
|
||||
Hours: hours,
|
||||
Minutes: minutes,
|
||||
UserExpiry: inv.UserExpiry,
|
||||
UserMonths: inv.UserMonths,
|
||||
UserDays: inv.UserDays,
|
||||
UserHours: inv.UserHours,
|
||||
UserMinutes: inv.UserMinutes,
|
||||
Created: inv.Created.Unix(),
|
||||
Profile: inv.Profile,
|
||||
NoLimit: inv.NoLimit,
|
||||
Label: inv.Label,
|
||||
UserLabel: inv.UserLabel,
|
||||
EditableInviteDTO: EditableInviteDTO{
|
||||
Code: inv.Code,
|
||||
Label: &inv.Label,
|
||||
UserLabel: &inv.UserLabel,
|
||||
Profile: &inv.Profile,
|
||||
UserExpiry: &inv.UserExpiry,
|
||||
UserMonths: &inv.UserMonths,
|
||||
UserDays: &inv.UserDays,
|
||||
UserHours: &inv.UserHours,
|
||||
UserMinutes: &inv.UserMinutes,
|
||||
},
|
||||
ValidTill: inv.ValidTill.Unix(),
|
||||
// Months: months,
|
||||
// Days: days,
|
||||
// Hours: hours,
|
||||
// Minutes: minutes,
|
||||
Created: inv.Created.Unix(),
|
||||
NoLimit: inv.NoLimit,
|
||||
}
|
||||
if len(inv.UsedBy) != 0 {
|
||||
invite.UsedBy = map[string]int64{}
|
||||
@@ -357,6 +501,9 @@ func (app *appContext) GetInvites(gc *gin.Context) {
|
||||
if inv.RemainingUses != 0 {
|
||||
invite.RemainingUses = inv.RemainingUses
|
||||
}
|
||||
if len(inv.SentTo.Success) != 0 || len(inv.SentTo.Failed) != 0 {
|
||||
invite.SentTo = inv.SentTo
|
||||
}
|
||||
if inv.SendTo != "" {
|
||||
invite.SendTo = inv.SendTo
|
||||
}
|
||||
@@ -369,10 +516,12 @@ func (app *appContext) GetInvites(gc *gin.Context) {
|
||||
}
|
||||
if _, ok := inv.Notify[addressOrID]; ok {
|
||||
if _, ok = inv.Notify[addressOrID]["notify-expiry"]; ok {
|
||||
invite.NotifyExpiry = inv.Notify[addressOrID]["notify-expiry"]
|
||||
notifyExpiry := inv.Notify[addressOrID]["notify-expiry"]
|
||||
invite.NotifyExpiry = ¬ifyExpiry
|
||||
}
|
||||
if _, ok = inv.Notify[addressOrID]["notify-creation"]; ok {
|
||||
invite.NotifyCreation = inv.Notify[addressOrID]["notify-creation"]
|
||||
notifyCreation := inv.Notify[addressOrID]["notify-creation"]
|
||||
invite.NotifyCreation = ¬ifyCreation
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -384,82 +533,54 @@ func (app *appContext) GetInvites(gc *gin.Context) {
|
||||
gc.JSON(200, resp)
|
||||
}
|
||||
|
||||
// @Summary Set profile for an invite
|
||||
// @Produce json
|
||||
// @Param inviteProfileDTO body inviteProfileDTO true "Invite profile object"
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 500 {object} stringResponse
|
||||
// @Router /invites/profile [post]
|
||||
// @Security Bearer
|
||||
// @tags Invites
|
||||
func (app *appContext) SetProfile(gc *gin.Context) {
|
||||
var req inviteProfileDTO
|
||||
gc.BindJSON(&req)
|
||||
func (app *appContext) SetProfile(inv *Invite, name string, gc *gin.Context) (changed, ok bool) {
|
||||
changed = false
|
||||
ok = false
|
||||
// "" means "Don't apply profile"
|
||||
if _, ok := app.storage.GetProfileKey(req.Profile); !ok && req.Profile != "" {
|
||||
app.err.Printf(lm.FailedGetProfile, req.Profile)
|
||||
if _, profileExists := app.storage.GetProfileKey(name); !profileExists && name != "" {
|
||||
app.err.Printf(lm.FailedGetProfile, name)
|
||||
respond(500, "Profile not found", gc)
|
||||
return
|
||||
}
|
||||
inv, _ := app.storage.GetInvitesKey(req.Invite)
|
||||
inv.Profile = req.Profile
|
||||
app.storage.SetInvitesKey(req.Invite, inv)
|
||||
respondBool(200, true, gc)
|
||||
changed = name != inv.Profile
|
||||
inv.Profile = name
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// @Summary Set notification preferences for an invite.
|
||||
// @Produce json
|
||||
// @Param setNotifyDTO body setNotifyDTO true "Map of invite codes to notification settings objects"
|
||||
// @Success 200
|
||||
// @Failure 400 {object} stringResponse
|
||||
// @Failure 500 {object} stringResponse
|
||||
// @Router /invites/notify [post]
|
||||
// @Security Bearer
|
||||
// @tags Other
|
||||
func (app *appContext) SetNotify(gc *gin.Context) {
|
||||
var req map[string]map[string]bool
|
||||
gc.BindJSON(&req)
|
||||
changed := false
|
||||
for code, settings := range req {
|
||||
invite, ok := app.storage.GetInvitesKey(code)
|
||||
if !ok {
|
||||
msg := fmt.Sprintf(lm.InvalidInviteCode, code)
|
||||
app.err.Println(msg)
|
||||
respond(400, msg, gc)
|
||||
func (app *appContext) SetNotify(inv *Invite, settings map[string]bool, gc *gin.Context) (changed, ok bool) {
|
||||
changed = false
|
||||
ok = false
|
||||
var address string
|
||||
jellyfinLogin := app.config.Section("ui").Key("jellyfin_login").MustBool(false)
|
||||
if jellyfinLogin {
|
||||
var addressAvailable bool = app.getAddressOrName(gc.GetString("jfId")) != ""
|
||||
if !addressAvailable {
|
||||
app.err.Printf(lm.FailedGetContactMethod, gc.GetString("jfId"))
|
||||
respond(500, fmt.Sprintf(lm.FailedGetContactMethod, "admin"), gc)
|
||||
return
|
||||
}
|
||||
var address string
|
||||
jellyfinLogin := app.config.Section("ui").Key("jellyfin_login").MustBool(false)
|
||||
if jellyfinLogin {
|
||||
var addressAvailable bool = app.getAddressOrName(gc.GetString("jfId")) != ""
|
||||
if !addressAvailable {
|
||||
app.err.Printf(lm.FailedGetContactMethod, gc.GetString("jfId"))
|
||||
respond(500, fmt.Sprintf(lm.FailedGetContactMethod, "admin"), gc)
|
||||
return
|
||||
}
|
||||
address = gc.GetString("jfId")
|
||||
} else {
|
||||
address = app.config.Section("ui").Key("email").String()
|
||||
}
|
||||
if invite.Notify == nil {
|
||||
invite.Notify = map[string]map[string]bool{}
|
||||
}
|
||||
if _, ok := invite.Notify[address]; !ok {
|
||||
invite.Notify[address] = map[string]bool{}
|
||||
} /*else {
|
||||
if _, ok := invite.Notify[address]["notify-expiry"]; !ok {
|
||||
*/
|
||||
for _, notifyType := range []string{"notify-expiry", "notify-creation"} {
|
||||
if _, ok := settings[notifyType]; ok && invite.Notify[address][notifyType] != settings[notifyType] {
|
||||
invite.Notify[address][notifyType] = settings[notifyType]
|
||||
app.debug.Printf(lm.SetAdminNotify, notifyType, settings[notifyType], address)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
app.storage.SetInvitesKey(code, invite)
|
||||
address = gc.GetString("jfId")
|
||||
} else {
|
||||
address = app.config.Section("ui").Key("email").String()
|
||||
}
|
||||
if inv.Notify == nil {
|
||||
inv.Notify = map[string]map[string]bool{}
|
||||
}
|
||||
if _, ok := inv.Notify[address]; !ok {
|
||||
inv.Notify[address] = map[string]bool{}
|
||||
} /*else {
|
||||
if _, ok := invite.Notify[address]["notify-expiry"]; !ok {
|
||||
*/
|
||||
for _, notifyType := range []string{"notify-expiry", "notify-creation"} {
|
||||
if _, ok := settings[notifyType]; ok && inv.Notify[address][notifyType] != settings[notifyType] {
|
||||
inv.Notify[address][notifyType] = settings[notifyType]
|
||||
app.debug.Printf(lm.SetAdminNotify, notifyType, settings[notifyType], address)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// @Summary Delete an invite.
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/hrfee/jfa-go/common"
|
||||
"github.com/hrfee/jfa-go/jellyseerr"
|
||||
lm "github.com/hrfee/jfa-go/logmessages"
|
||||
)
|
||||
@@ -61,7 +62,7 @@ func (app *appContext) SetJellyseerrProfile(gc *gin.Context) {
|
||||
}
|
||||
u, err := app.js.UserByID(jellyseerrID)
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedGetUsers, lm.Jellyseerr, err)
|
||||
app.err.Printf(lm.FailedGetUser, strconv.FormatInt(jellyseerrID, 10), lm.Jellyseerr, err)
|
||||
respond(500, "Couldn't get user", gc)
|
||||
return
|
||||
}
|
||||
@@ -111,6 +112,9 @@ func (js *JellyseerrWrapper) ImportUser(jellyfinID string, req newUserDTO, profi
|
||||
return
|
||||
}
|
||||
ok = true
|
||||
if !profile.Jellyseerr.Enabled {
|
||||
return
|
||||
}
|
||||
err = js.ApplyTemplateToUser(jellyfinID, profile.Jellyseerr.User)
|
||||
if err != nil {
|
||||
err = fmt.Errorf(lm.FailedApplyTemplate, "user", lm.Jellyseerr, jellyfinID, err)
|
||||
@@ -124,29 +128,62 @@ func (js *JellyseerrWrapper) ImportUser(jellyfinID string, req newUserDTO, profi
|
||||
return
|
||||
}
|
||||
|
||||
func (js *JellyseerrWrapper) AddContactMethods(jellyfinID string, req newUserDTO, discord *DiscordUser, telegram *TelegramUser) (err error) {
|
||||
func (js *JellyseerrWrapper) SetContactMethods(jellyfinID string, email *string, discord *DiscordUser, telegram *TelegramUser, contactPrefs *common.ContactPreferences) (err error) {
|
||||
_, err = js.MustGetUser(jellyfinID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
contactMethods := map[jellyseerr.NotificationsField]any{}
|
||||
if emailEnabled {
|
||||
err = js.ModifyMainUserSettings(jellyfinID, jellyseerr.MainUserSettings{Email: req.Email})
|
||||
if err != nil {
|
||||
// FIXME: This is a little ugly, considering all other errors are unformatted
|
||||
err = fmt.Errorf(lm.FailedSetEmailAddress, lm.Jellyseerr, jellyfinID, err)
|
||||
return
|
||||
} else {
|
||||
contactMethods[jellyseerr.FieldEmailEnabled] = req.EmailContact
|
||||
if contactPrefs == nil {
|
||||
contactPrefs = &common.ContactPreferences{
|
||||
Email: nil,
|
||||
Discord: nil,
|
||||
Telegram: nil,
|
||||
Matrix: nil,
|
||||
}
|
||||
}
|
||||
if discordEnabled && discord != nil {
|
||||
contactMethods[jellyseerr.FieldDiscord] = discord.ID
|
||||
contactMethods[jellyseerr.FieldDiscordEnabled] = req.DiscordContact
|
||||
contactMethods := map[jellyseerr.NotificationsField]any{}
|
||||
if emailEnabled {
|
||||
if contactPrefs.Email != nil {
|
||||
contactMethods[jellyseerr.FieldEmailEnabled] = *(contactPrefs.Email)
|
||||
} else if email != nil && *email != "" {
|
||||
contactMethods[jellyseerr.FieldEmailEnabled] = true
|
||||
}
|
||||
if email != nil {
|
||||
err = js.ModifyMainUserSettings(jellyfinID, jellyseerr.MainUserSettings{Email: *email})
|
||||
if err != nil {
|
||||
// FIXME: This is a little ugly, considering all other errors are unformatted
|
||||
err = fmt.Errorf(lm.FailedSetEmailAddress, lm.Jellyseerr, jellyfinID, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if telegramEnabled && discord != nil {
|
||||
contactMethods[jellyseerr.FieldTelegram] = telegram.ChatID
|
||||
contactMethods[jellyseerr.FieldTelegramEnabled] = req.TelegramContact
|
||||
if discordEnabled {
|
||||
if contactPrefs.Discord != nil {
|
||||
contactMethods[jellyseerr.FieldDiscordEnabled] = *(contactPrefs.Discord)
|
||||
} else if discord != nil && discord.ID != "" {
|
||||
contactMethods[jellyseerr.FieldDiscordEnabled] = true
|
||||
}
|
||||
if discord != nil {
|
||||
contactMethods[jellyseerr.FieldDiscord] = discord.ID
|
||||
// Whether this is still necessary or not, i don't know.
|
||||
if discord.ID == "" {
|
||||
contactMethods[jellyseerr.FieldDiscord] = jellyseerr.BogusIdentifier
|
||||
}
|
||||
}
|
||||
}
|
||||
if telegramEnabled {
|
||||
if contactPrefs.Telegram != nil {
|
||||
contactMethods[jellyseerr.FieldTelegramEnabled] = *(contactPrefs.Telegram)
|
||||
} else if telegram != nil && telegram.ChatID != 0 {
|
||||
contactMethods[jellyseerr.FieldTelegramEnabled] = true
|
||||
}
|
||||
if telegram != nil {
|
||||
contactMethods[jellyseerr.FieldTelegram] = strconv.FormatInt(telegram.ChatID, 10)
|
||||
// Whether this is still necessary or not, i don't know.
|
||||
if telegram.ChatID == 0 {
|
||||
contactMethods[jellyseerr.FieldTelegram] = jellyseerr.BogusIdentifier
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(contactMethods) > 0 {
|
||||
err = js.ModifyNotifications(jellyfinID, contactMethods)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/hrfee/jfa-go/jellyseerr"
|
||||
"github.com/hrfee/jfa-go/common"
|
||||
lm "github.com/hrfee/jfa-go/logmessages"
|
||||
"github.com/lithammer/shortuuid/v3"
|
||||
"gopkg.in/ini.v1"
|
||||
@@ -158,7 +159,7 @@ func (app *appContext) GetCustomMessageTemplate(gc *gin.Context) {
|
||||
case "ExpiryReminder":
|
||||
msg, err = app.email.constructExpiryReminder("", time.Now().AddDate(0, 0, 3), true)
|
||||
case "InviteEmail":
|
||||
msg, err = app.email.constructInvite(Invite{Code: ""}, true)
|
||||
msg, err = app.email.constructInvite(&Invite{Code: ""}, true)
|
||||
case "WelcomeEmail":
|
||||
msg, err = app.email.constructWelcome("", time.Time{}, true)
|
||||
case "EmailConfirmation":
|
||||
@@ -169,6 +170,7 @@ func (app *appContext) GetCustomMessageTemplate(gc *gin.Context) {
|
||||
case "UserPage":
|
||||
case "UserLogin":
|
||||
case "PostSignupCard":
|
||||
case "PreSignupCard":
|
||||
// These don't have any example content
|
||||
msg = nil
|
||||
}
|
||||
@@ -254,30 +256,32 @@ func (app *appContext) TelegramAddUser(gc *gin.Context) {
|
||||
return
|
||||
}
|
||||
tgUser := TelegramUser{
|
||||
ChatID: tgToken.ChatID,
|
||||
Username: tgToken.Username,
|
||||
Contact: true,
|
||||
TelegramVerifiedToken: TelegramVerifiedToken{
|
||||
ChatID: tgToken.ChatID,
|
||||
Username: tgToken.Username,
|
||||
},
|
||||
Contact: true,
|
||||
}
|
||||
if lang, ok := app.telegram.languages[tgToken.ChatID]; ok {
|
||||
tgUser.Lang = lang
|
||||
}
|
||||
app.storage.SetTelegramKey(req.ID, tgUser)
|
||||
|
||||
if err := app.js.ModifyNotifications(gc.GetString("jfId"), map[jellyseerr.NotificationsField]any{
|
||||
jellyseerr.FieldTelegram: tgUser.ChatID,
|
||||
jellyseerr.FieldTelegramEnabled: tgUser.Contact,
|
||||
}); err != nil {
|
||||
app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err)
|
||||
for _, tps := range app.thirdPartyServices {
|
||||
if err := tps.SetContactMethods(req.ID, nil, nil, &tgUser, &common.ContactPreferences{
|
||||
Telegram: &tgUser.Contact,
|
||||
}); err != nil {
|
||||
app.err.Printf(lm.FailedSyncContactMethods, tps.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
linkExistingOmbiDiscordTelegram(app)
|
||||
app.InvalidateWebUserCache()
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Sets whether to notify a user through telegram/discord/matrix/email or not.
|
||||
// @Produce json
|
||||
// @Param SetContactMethodsDTO body SetContactMethodsDTO true "User's Jellyfin ID and whether or not to notify then through Telegram."
|
||||
// @Param SetContactPreferencesDTO body SetContactPreferencesDTO true "User's Jellyfin ID and whether or not to notify then through Telegram."
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Success 400 {object} boolResponse
|
||||
// @Success 500 {object} boolResponse
|
||||
@@ -285,24 +289,24 @@ func (app *appContext) TelegramAddUser(gc *gin.Context) {
|
||||
// @Security Bearer
|
||||
// @tags Other
|
||||
func (app *appContext) SetContactMethods(gc *gin.Context) {
|
||||
var req SetContactMethodsDTO
|
||||
var req SetContactPreferencesDTO
|
||||
gc.BindJSON(&req)
|
||||
if req.ID == "" {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
app.setContactMethods(req, gc)
|
||||
app.setContactPreferences(req, gc)
|
||||
}
|
||||
|
||||
func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Context) {
|
||||
jsPrefs := map[jellyseerr.NotificationsField]any{}
|
||||
func (app *appContext) setContactPreferences(req SetContactPreferencesDTO, gc *gin.Context) {
|
||||
contactPrefs := common.ContactPreferences{}
|
||||
if tgUser, ok := app.storage.GetTelegramKey(req.ID); ok {
|
||||
change := tgUser.Contact != req.Telegram
|
||||
tgUser.Contact = req.Telegram
|
||||
app.storage.SetTelegramKey(req.ID, tgUser)
|
||||
if change {
|
||||
app.debug.Printf(lm.SetContactPrefForService, lm.Telegram, tgUser.Username, req.Telegram)
|
||||
jsPrefs[jellyseerr.FieldTelegramEnabled] = req.Telegram
|
||||
contactPrefs.Telegram = &req.Telegram
|
||||
}
|
||||
}
|
||||
if dcUser, ok := app.storage.GetDiscordKey(req.ID); ok {
|
||||
@@ -311,7 +315,7 @@ func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Conte
|
||||
app.storage.SetDiscordKey(req.ID, dcUser)
|
||||
if change {
|
||||
app.debug.Printf(lm.SetContactPrefForService, lm.Discord, dcUser.Username, req.Discord)
|
||||
jsPrefs[jellyseerr.FieldDiscordEnabled] = req.Discord
|
||||
contactPrefs.Discord = &req.Discord
|
||||
}
|
||||
}
|
||||
if mxUser, ok := app.storage.GetMatrixKey(req.ID); ok {
|
||||
@@ -320,6 +324,7 @@ func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Conte
|
||||
app.storage.SetMatrixKey(req.ID, mxUser)
|
||||
if change {
|
||||
app.debug.Printf(lm.SetContactPrefForService, lm.Matrix, mxUser.UserID, req.Matrix)
|
||||
contactPrefs.Matrix = &req.Matrix
|
||||
}
|
||||
}
|
||||
if email, ok := app.storage.GetEmailsKey(req.ID); ok {
|
||||
@@ -328,13 +333,13 @@ func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Conte
|
||||
app.storage.SetEmailsKey(req.ID, email)
|
||||
if change {
|
||||
app.debug.Printf(lm.SetContactPrefForService, lm.Email, email.Addr, req.Email)
|
||||
jsPrefs[jellyseerr.FieldEmailEnabled] = req.Email
|
||||
contactPrefs.Email = &req.Email
|
||||
}
|
||||
}
|
||||
if app.config.Section("jellyseerr").Key("enabled").MustBool(false) {
|
||||
err := app.js.ModifyNotifications(req.ID, jsPrefs)
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err)
|
||||
|
||||
for _, tps := range app.thirdPartyServices {
|
||||
if err := tps.SetContactMethods(req.ID, nil, nil, nil, &contactPrefs); err != nil {
|
||||
app.err.Printf(lm.FailedSyncContactMethods, tps.Name(), err)
|
||||
}
|
||||
}
|
||||
app.InvalidateWebUserCache()
|
||||
@@ -580,8 +585,9 @@ func (app *appContext) MatrixConnect(gc *gin.Context) {
|
||||
// @Security Bearer
|
||||
// @tags Other
|
||||
func (app *appContext) DiscordGetUsers(gc *gin.Context) {
|
||||
name := gc.Param("username")
|
||||
if name == "" {
|
||||
escapedName := gc.Param("username")
|
||||
name, err := url.QueryUnescape(escapedName)
|
||||
if err != nil || name == "" {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
@@ -621,11 +627,12 @@ func (app *appContext) DiscordConnect(gc *gin.Context) {
|
||||
|
||||
app.storage.SetDiscordKey(req.JellyfinID, user)
|
||||
|
||||
if err := app.js.ModifyNotifications(req.JellyfinID, map[jellyseerr.NotificationsField]any{
|
||||
jellyseerr.FieldDiscord: req.DiscordID,
|
||||
jellyseerr.FieldDiscordEnabled: true,
|
||||
}); err != nil {
|
||||
app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err)
|
||||
for _, tps := range app.thirdPartyServices {
|
||||
if err := tps.SetContactMethods(req.JellyfinID, nil, &user, nil, &common.ContactPreferences{
|
||||
Discord: &user.Contact,
|
||||
}); err != nil {
|
||||
app.err.Printf(lm.FailedSyncContactMethods, tps.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
@@ -659,12 +666,14 @@ func (app *appContext) UnlinkDiscord(gc *gin.Context) {
|
||||
} */
|
||||
app.storage.DeleteDiscordKey(req.ID)
|
||||
|
||||
// May not actually remove Discord ID, but should disable interaction.
|
||||
if err := app.js.ModifyNotifications(gc.GetString("jfId"), map[jellyseerr.NotificationsField]any{
|
||||
jellyseerr.FieldDiscord: jellyseerr.BogusIdentifier,
|
||||
jellyseerr.FieldDiscordEnabled: false,
|
||||
}); err != nil {
|
||||
app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err)
|
||||
contact := false
|
||||
|
||||
for _, tps := range app.thirdPartyServices {
|
||||
if err := tps.SetContactMethods(req.ID, nil, EmptyDiscordUser(), nil, &common.ContactPreferences{
|
||||
Discord: &contact,
|
||||
}); err != nil {
|
||||
app.err.Printf(lm.FailedSyncContactMethods, tps.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
@@ -697,11 +706,14 @@ func (app *appContext) UnlinkTelegram(gc *gin.Context) {
|
||||
} */
|
||||
app.storage.DeleteTelegramKey(req.ID)
|
||||
|
||||
if err := app.js.ModifyNotifications(gc.GetString("jfId"), map[jellyseerr.NotificationsField]any{
|
||||
jellyseerr.FieldTelegram: jellyseerr.BogusIdentifier,
|
||||
jellyseerr.FieldTelegramEnabled: false,
|
||||
}); err != nil {
|
||||
app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err)
|
||||
contact := false
|
||||
|
||||
for _, tps := range app.thirdPartyServices {
|
||||
if err := tps.SetContactMethods(req.ID, nil, nil, EmptyTelegramUser(), &common.ContactPreferences{
|
||||
Telegram: &contact,
|
||||
}); err != nil {
|
||||
app.err.Printf(lm.FailedSyncContactMethods, tps.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
|
||||
84
api-ombi.go
84
api-ombi.go
@@ -8,21 +8,26 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/hrfee/jfa-go/common"
|
||||
lm "github.com/hrfee/jfa-go/logmessages"
|
||||
"github.com/hrfee/jfa-go/ombi"
|
||||
ombiLib "github.com/hrfee/jfa-go/ombi"
|
||||
"github.com/hrfee/mediabrowser"
|
||||
)
|
||||
|
||||
func (app *appContext) getOmbiUser(jfID string) (map[string]interface{}, error) {
|
||||
// getOmbiUser searches for an ombi user given a Jellyfin user ID. It looks for matching username or matching email address.
|
||||
// If "email"=nil, an email address will be acquired from the DB instead. Passing it manually is useful when changing email address.
|
||||
func (app *appContext) getOmbiUser(jfID string, email *string) (map[string]interface{}, error) {
|
||||
jfUser, err := app.jf.UserByID(jfID, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
username := jfUser.Name
|
||||
email := ""
|
||||
if e, ok := app.storage.GetEmailsKey(jfID); ok {
|
||||
email = e.Addr
|
||||
if email == nil {
|
||||
addr := ""
|
||||
if e, ok := app.storage.GetEmailsKey(jfID); ok {
|
||||
addr = e.Addr
|
||||
}
|
||||
email = &addr
|
||||
}
|
||||
user, err := app.ombi.getUser(username, email)
|
||||
user, err := app.ombi.getUser(username, *email)
|
||||
return user, err
|
||||
}
|
||||
|
||||
@@ -147,7 +152,8 @@ func (app *appContext) DeleteOmbiProfile(gc *gin.Context) {
|
||||
}
|
||||
|
||||
type OmbiWrapper struct {
|
||||
*ombi.Ombi
|
||||
OmbiUserByJfID func(jfID string, email *string) (map[string]interface{}, error)
|
||||
*ombiLib.Ombi
|
||||
}
|
||||
|
||||
func (ombi *OmbiWrapper) applyProfile(user map[string]interface{}, profile map[string]interface{}) (err error) {
|
||||
@@ -189,23 +195,69 @@ func (ombi *OmbiWrapper) ImportUser(jellyfinID string, req newUserDTO, profile P
|
||||
return
|
||||
}
|
||||
|
||||
func (ombi *OmbiWrapper) AddContactMethods(jellyfinID string, req newUserDTO, discord *DiscordUser, telegram *TelegramUser) (err error) {
|
||||
var ombiUser map[string]interface{}
|
||||
ombiUser, err = ombi.getUser(req.Username, req.Email)
|
||||
func (ombi *OmbiWrapper) SetContactMethods(jellyfinID string, email *string, discord *DiscordUser, telegram *TelegramUser, contactPrefs *common.ContactPreferences) (err error) {
|
||||
ombiUser, err := ombi.OmbiUserByJfID(jellyfinID, email)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if discordEnabled || telegramEnabled {
|
||||
dID := ""
|
||||
tUser := ""
|
||||
if contactPrefs == nil {
|
||||
contactPrefs = &common.ContactPreferences{
|
||||
Email: nil,
|
||||
Discord: nil,
|
||||
Telegram: nil,
|
||||
Matrix: nil,
|
||||
}
|
||||
}
|
||||
if emailEnabled && email != nil {
|
||||
ombiUser["emailAddress"] = *email
|
||||
err = ombi.ModifyUser(ombiUser)
|
||||
if err != nil {
|
||||
// FIXME: This is a little ugly, considering all other errors are unformatted
|
||||
err = fmt.Errorf(lm.FailedSetEmailAddress, lm.Ombi, jellyfinID, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
data := make([]ombiLib.NotificationPref, 0, 2)
|
||||
if discordEnabled {
|
||||
pref := ombiLib.NotificationPref{
|
||||
Agent: ombiLib.NotifAgentDiscord,
|
||||
UserID: ombiUser["id"].(string),
|
||||
}
|
||||
valid := false
|
||||
if contactPrefs.Discord != nil {
|
||||
pref.Enabled = *(contactPrefs.Discord)
|
||||
valid = true
|
||||
} else if discord != nil && discord.ID != "" {
|
||||
pref.Enabled = true
|
||||
valid = true
|
||||
}
|
||||
if discord != nil {
|
||||
dID = discord.ID
|
||||
pref.Value = discord.ID
|
||||
valid = true
|
||||
}
|
||||
if valid {
|
||||
data = append(data, pref)
|
||||
}
|
||||
}
|
||||
if telegramEnabled && telegram != nil {
|
||||
pref := ombiLib.NotificationPref{
|
||||
Agent: ombiLib.NotifAgentTelegram,
|
||||
UserID: ombiUser["id"].(string),
|
||||
}
|
||||
if contactPrefs.Telegram != nil {
|
||||
pref.Enabled = *(contactPrefs.Telegram)
|
||||
} else if telegram != nil && telegram.Username != "" {
|
||||
pref.Enabled = true
|
||||
}
|
||||
if telegram != nil {
|
||||
tUser = telegram.Username
|
||||
pref.Value = telegram.Username
|
||||
}
|
||||
data = append(data, pref)
|
||||
}
|
||||
if len(data) > 0 {
|
||||
var resp string
|
||||
resp, err = ombi.SetNotificationPrefs(ombiUser, dID, tUser)
|
||||
resp, err = ombi.SetNotificationPrefs(ombiUser, data)
|
||||
if err != nil {
|
||||
if resp != "" {
|
||||
err = fmt.Errorf("%v, %s", err, resp)
|
||||
|
||||
@@ -2,6 +2,9 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -69,6 +72,68 @@ func (app *appContext) GetProfiles(gc *gin.Context) {
|
||||
gc.JSON(200, out)
|
||||
}
|
||||
|
||||
// @Summary Get the raw values stored in a profile (Configuration, Policy, Jellyseerr/Ombi if applicable, etc.).
|
||||
// @Produce json
|
||||
// @Success 200 {object} ProfileDTO
|
||||
// @Failure 400 {object} boolResponse
|
||||
// @Param name path string true "name of profile (url encoded if necessary)"
|
||||
// @Router /profiles/raw/{name} [get]
|
||||
// @Security Bearer
|
||||
// @tags Profiles & Settings
|
||||
func (app *appContext) GetRawProfile(gc *gin.Context) {
|
||||
escapedName := gc.Param("name")
|
||||
name, err := url.QueryUnescape(escapedName)
|
||||
if err != nil {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
if profile, ok := app.storage.GetProfileKey(name); ok {
|
||||
gc.JSON(200, profile.ProfileDTO)
|
||||
return
|
||||
}
|
||||
respondBool(400, false, gc)
|
||||
}
|
||||
|
||||
// @Summary Update the raw data of a profile (Configuration, Policy, Jellyseerr/Ombi if applicable, etc.).
|
||||
// @Produce json
|
||||
// @Param ProfileDTO body ProfileDTO true "Raw profile data (all of it, do not omit anything)"
|
||||
// @Success 204 {object} boolResponse
|
||||
// @Success 201 {object} boolResponse
|
||||
// @Failure 400 {object} boolResponse
|
||||
// @Router /profiles/raw/{name} [put]
|
||||
// @Security Bearer
|
||||
// @tags Profiles & Settings
|
||||
func (app *appContext) ReplaceRawProfile(gc *gin.Context) {
|
||||
escapedName := gc.Param("name")
|
||||
name, err := url.QueryUnescape(escapedName)
|
||||
if err != nil {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
existingProfile, ok := app.storage.GetProfileKey(name)
|
||||
if !ok {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
var req ProfileDTO
|
||||
gc.BindJSON(&req)
|
||||
existingProfile.ProfileDTO = req
|
||||
if req.Name == "" {
|
||||
req.Name = name
|
||||
}
|
||||
status := http.StatusNoContent
|
||||
app.storage.SetProfileKey(req.Name, existingProfile)
|
||||
if req.Name != name {
|
||||
// Name change
|
||||
app.storage.DeleteProfileKey(name)
|
||||
if discordEnabled {
|
||||
app.discord.UpdateCommands()
|
||||
}
|
||||
status = http.StatusCreated
|
||||
}
|
||||
respondBool(status, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Set the default profile to use.
|
||||
// @Produce json
|
||||
// @Param profileChangeDTO body profileChangeDTO true "Default profile object"
|
||||
@@ -119,7 +184,7 @@ func (app *appContext) CreateProfile(gc *gin.Context) {
|
||||
}
|
||||
profile := Profile{
|
||||
FromUser: user.Name,
|
||||
Policy: user.Policy,
|
||||
ProfileDTO: ProfileDTO{Policy: user.Policy},
|
||||
Homescreen: req.Homescreen,
|
||||
}
|
||||
app.debug.Printf(lm.CreateProfileFromUser, user.Name)
|
||||
@@ -132,6 +197,21 @@ func (app *appContext) CreateProfile(gc *gin.Context) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if req.Jellyseerr && app.config.Section("jellyseerr").Key("enabled").MustBool(false) {
|
||||
user, err := app.js.MustGetUser(req.ID)
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedGetUser, user.Name(), lm.Jellyseerr, err)
|
||||
} else {
|
||||
profile.Jellyseerr.User = user.UserTemplate
|
||||
n, err := app.js.GetNotificationPreferencesByID(user.ID)
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedGetJellyseerrNotificationPrefs, strconv.FormatInt(user.ID, 10), err)
|
||||
} else {
|
||||
profile.Jellyseerr.Notifications = n.NotificationsTemplate
|
||||
profile.Jellyseerr.Enabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
app.storage.SetProfileKey(req.Name, profile)
|
||||
// Refresh discord bots, profile list
|
||||
if discordEnabled {
|
||||
@@ -167,7 +247,13 @@ func (app *appContext) DeleteProfile(gc *gin.Context) {
|
||||
// @Security Bearer
|
||||
// @tags Profiles & Settings
|
||||
func (app *appContext) EnableReferralForProfile(gc *gin.Context) {
|
||||
profileName := gc.Param("profile")
|
||||
escapedProfileName := gc.Param("profile")
|
||||
profileName, err := url.QueryUnescape(escapedProfileName)
|
||||
if err != nil {
|
||||
respond(400, "Invalid profile", gc)
|
||||
app.err.Printf(lm.FailedGetProfile, profileName)
|
||||
return
|
||||
}
|
||||
invCode := gc.Param("invite")
|
||||
useExpiry := gc.Param("useExpiry") == "with-expiry"
|
||||
inv, ok := app.storage.GetInvitesKey(invCode)
|
||||
@@ -214,7 +300,13 @@ func (app *appContext) EnableReferralForProfile(gc *gin.Context) {
|
||||
// @Security Bearer
|
||||
// @tags Profiles & Settings
|
||||
func (app *appContext) DisableReferralForProfile(gc *gin.Context) {
|
||||
profileName := gc.Param("profile")
|
||||
escapedProfileName := gc.Param("profile")
|
||||
profileName, err := url.QueryUnescape(escapedProfileName)
|
||||
if err != nil {
|
||||
respond(400, "Invalid profile", gc)
|
||||
app.err.Printf(lm.FailedGetProfile, profileName)
|
||||
return
|
||||
}
|
||||
profile, ok := app.storage.GetProfileKey(profileName)
|
||||
if !ok {
|
||||
respondBool(200, true, gc)
|
||||
|
||||
@@ -107,7 +107,7 @@ func (app *appContext) MyDetails(gc *gin.Context) {
|
||||
|
||||
// @Summary Sets whether to notify yourself through telegram/discord/matrix/email or not.
|
||||
// @Produce json
|
||||
// @Param SetContactMethodsDTO body SetContactMethodsDTO true "User's Jellyfin ID and whether or not to notify then through Telegram."
|
||||
// @Param SetContactPreferencesDTO body SetContactPreferencesDTO true "User's Jellyfin ID and whether or not to notify then through Telegram."
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Success 400 {object} boolResponse
|
||||
// @Success 500 {object} boolResponse
|
||||
@@ -115,14 +115,14 @@ func (app *appContext) MyDetails(gc *gin.Context) {
|
||||
// @Security Bearer
|
||||
// @tags User Page
|
||||
func (app *appContext) SetMyContactMethods(gc *gin.Context) {
|
||||
var req SetContactMethodsDTO
|
||||
var req SetContactPreferencesDTO
|
||||
gc.BindJSON(&req)
|
||||
req.ID = gc.GetString("jfId")
|
||||
if req.ID == "" {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
app.setContactMethods(req, gc)
|
||||
app.setContactPreferences(req, gc)
|
||||
}
|
||||
|
||||
// @Summary Logout by deleting refresh token from cookies.
|
||||
@@ -164,7 +164,7 @@ func (app *appContext) confirmMyAction(gc *gin.Context, key string) {
|
||||
var target ConfirmationTarget
|
||||
var id string
|
||||
fail := func() {
|
||||
app.gcHTML(gc, 404, "404.html", OtherPage, gin.H{
|
||||
app.gcHTML(gc, 404, "404.html", OtherPage, "en-us", gin.H{
|
||||
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
||||
})
|
||||
}
|
||||
@@ -199,22 +199,22 @@ func (app *appContext) confirmMyAction(gc *gin.Context, key string) {
|
||||
|
||||
// Perform an Action
|
||||
if target == NoOp {
|
||||
gc.Redirect(http.StatusSeeOther, PAGES.MyAccount)
|
||||
gc.Redirect(http.StatusSeeOther, MustGetNonEmptyURL(PAGES.MyAccount))
|
||||
return
|
||||
} else if target == UserEmailChange {
|
||||
app.modifyEmail(id, claims["email"].(string))
|
||||
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
Type: ActivityContactLinked,
|
||||
UserID: gc.GetString("jfId"),
|
||||
UserID: id,
|
||||
SourceType: ActivityUser,
|
||||
Source: gc.GetString("jfId"),
|
||||
Source: id,
|
||||
Value: "email",
|
||||
Time: time.Now(),
|
||||
}, gc, true)
|
||||
|
||||
app.info.Printf(lm.UserEmailAdjusted, gc.GetString("jfId"))
|
||||
gc.Redirect(http.StatusSeeOther, PAGES.MyAccount)
|
||||
app.info.Printf(lm.UserEmailAdjusted, id)
|
||||
gc.Redirect(http.StatusSeeOther, MustGetNonEmptyURL(PAGES.MyAccount))
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -270,7 +270,7 @@ func (app *appContext) ModifyMyEmail(gc *gin.Context) {
|
||||
} else if err := app.email.send(msg, req.Email); err != nil {
|
||||
app.err.Printf(lm.FailedSendConfirmationEmail, id, req.Email, err)
|
||||
} else {
|
||||
app.err.Printf(lm.SentConfirmationEmail, id, req.Email)
|
||||
app.info.Printf(lm.SentConfirmationEmail, id, req.Email)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -394,9 +394,11 @@ func (app *appContext) MyTelegramVerifiedInvite(gc *gin.Context) {
|
||||
return
|
||||
}
|
||||
tgUser := TelegramUser{
|
||||
ChatID: token.ChatID,
|
||||
Username: token.Username,
|
||||
Contact: true,
|
||||
TelegramVerifiedToken: TelegramVerifiedToken{
|
||||
ChatID: token.ChatID,
|
||||
Username: token.Username,
|
||||
},
|
||||
Contact: true,
|
||||
}
|
||||
if lang, ok := app.telegram.languages[tgUser.ChatID]; ok {
|
||||
tgUser.Lang = lang
|
||||
@@ -714,7 +716,7 @@ func (app *appContext) ChangeMyPassword(gc *gin.Context) {
|
||||
|
||||
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
||||
func() {
|
||||
ombiUser, err := app.getOmbiUser(gc.GetString("jfId"))
|
||||
ombiUser, err := app.getOmbiUser(gc.GetString("jfId"), nil)
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedGetUser, user.Name, lm.Ombi, err)
|
||||
return
|
||||
|
||||
370
api-users.go
370
api-users.go
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/hrfee/jfa-go/jellyseerr"
|
||||
"github.com/hrfee/jfa-go/common"
|
||||
lm "github.com/hrfee/jfa-go/logmessages"
|
||||
"github.com/hrfee/mediabrowser"
|
||||
"github.com/lithammer/shortuuid/v3"
|
||||
@@ -54,12 +54,29 @@ func (app *appContext) NewUserFromAdmin(gc *gin.Context) {
|
||||
nu.Log()
|
||||
}
|
||||
|
||||
var emailStore *EmailAddress = nil
|
||||
if emailEnabled && req.Email != "" {
|
||||
emailStore := EmailAddress{
|
||||
emailStore = &EmailAddress{
|
||||
Addr: req.Email,
|
||||
Contact: true,
|
||||
}
|
||||
app.storage.SetEmailsKey(nu.User.ID, emailStore)
|
||||
app.storage.SetEmailsKey(nu.User.ID, *emailStore)
|
||||
}
|
||||
|
||||
for _, tps := range app.thirdPartyServices {
|
||||
if !tps.Enabled(app, &profile) {
|
||||
continue
|
||||
}
|
||||
// We only have email
|
||||
if emailStore == nil {
|
||||
continue
|
||||
}
|
||||
err := tps.SetContactMethods(nu.User.ID, &req.Email, nil, nil, &common.ContactPreferences{
|
||||
Email: &(emailStore.Contact),
|
||||
})
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedSyncContactMethods, tps.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
welcomeMessageSentIfNecessary := true
|
||||
@@ -268,13 +285,16 @@ func (app *appContext) PostNewUserFromInvite(nu NewUserData, req ConfirmationKey
|
||||
|
||||
referralsEnabled := profile != nil && profile.ReferralTemplateKey != "" && app.config.Section("user_page").Key("enabled").MustBool(false) && app.config.Section("user_page").Key("referrals").MustBool(false)
|
||||
|
||||
contactPrefs := common.ContactPreferences{}
|
||||
if (emailEnabled && req.Email != "") || invite.UserLabel != "" || referralsEnabled {
|
||||
emailStore := EmailAddress{
|
||||
Addr: req.Email,
|
||||
Contact: (req.Email != ""),
|
||||
Label: invite.UserLabel,
|
||||
}
|
||||
contactPrefs.Email = &(emailStore.Contact)
|
||||
if profile != nil {
|
||||
// FIXME: Why?
|
||||
profile.ReferralTemplateKey = profile.ReferralTemplateKey
|
||||
}
|
||||
/// Ensures at least one contact method is enabled.
|
||||
@@ -334,18 +354,22 @@ func (app *appContext) PostNewUserFromInvite(nu NewUserData, req ConfirmationKey
|
||||
|
||||
var discordUser *DiscordUser = nil
|
||||
var telegramUser *TelegramUser = nil
|
||||
// FIXME: Make sure its okay to, then change this check to len(app.tps) != 0 && (for loop of tps.Enabled )
|
||||
if app.ombi.Enabled(app, profile) || app.js.Enabled(app, profile) {
|
||||
// FIXME: figure these out in a nicer way? this relies on the current ordering,
|
||||
// which may not be fixed.
|
||||
if discordEnabled {
|
||||
if req.completeContactMethods[0].User != nil {
|
||||
discordUser = req.completeContactMethods[0].User.(*DiscordUser)
|
||||
contactPrefs.Discord = &discordUser.Contact
|
||||
}
|
||||
if telegramEnabled && req.completeContactMethods[1].User != nil {
|
||||
telegramUser = req.completeContactMethods[1].User.(*TelegramUser)
|
||||
contactPrefs.Telegram = &telegramUser.Contact
|
||||
}
|
||||
} else if telegramEnabled && req.completeContactMethods[0].User != nil {
|
||||
telegramUser = req.completeContactMethods[0].User.(*TelegramUser)
|
||||
contactPrefs.Telegram = &telegramUser.Contact
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,7 +378,7 @@ func (app *appContext) PostNewUserFromInvite(nu NewUserData, req ConfirmationKey
|
||||
continue
|
||||
}
|
||||
// User already created, now we can link contact methods
|
||||
err := tps.AddContactMethods(nu.User.ID, req.newUserDTO, discordUser, telegramUser)
|
||||
err := tps.SetContactMethods(nu.User.ID, &(req.Email), discordUser, telegramUser, &contactPrefs)
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedSyncContactMethods, tps.Name(), err)
|
||||
}
|
||||
@@ -525,6 +549,24 @@ func (app *appContext) ExtendExpiry(gc *gin.Context) {
|
||||
base := time.Now()
|
||||
if expiry, ok := app.storage.GetUserExpiryKey(id); ok {
|
||||
base = expiry.Expiry
|
||||
app.debug.Printf(lm.FoundExistingExpiry)
|
||||
} else if req.TryExtendFromPreviousExpiry {
|
||||
var acts []Activity
|
||||
app.storage.db.Find(&acts, badgerhold.Where("Type").Eq(ActivityDisabled).And("UserID").Eq(id).SortBy("Time").Reverse().Limit(1))
|
||||
if len(acts) != 0 {
|
||||
// Only do it if the most recent reason for disabling was expiry
|
||||
if acts[0].SourceType == ActivityDaemon {
|
||||
app.debug.Printf(lm.FoundPreviousExpiryLog, acts[0].Time)
|
||||
newExpiry := acts[0].Time.AddDate(0, req.Months, req.Days).Add(time.Duration(((60 * req.Hours) + req.Minutes)) * time.Minute)
|
||||
if newExpiry.After(base) {
|
||||
base = acts[0].Time
|
||||
} else {
|
||||
app.debug.Printf(lm.ExpiryWouldBeInPast)
|
||||
}
|
||||
} else {
|
||||
app.debug.Printf(lm.PreviousExpiryNotExpiry)
|
||||
}
|
||||
}
|
||||
}
|
||||
app.debug.Printf(lm.ExtendCreateExpiry, id)
|
||||
expiry := UserExpiry{}
|
||||
@@ -584,7 +626,12 @@ func (app *appContext) EnableReferralForUsers(gc *gin.Context) {
|
||||
gc.BindJSON(&req)
|
||||
mode := gc.Param("mode")
|
||||
|
||||
source := gc.Param("source")
|
||||
escapedSource := gc.Param("source")
|
||||
source, err := url.QueryUnescape(escapedSource)
|
||||
if err != nil {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
useExpiry := gc.Param("useExpiry") == "with-expiry"
|
||||
baseInv := Invite{}
|
||||
if mode == "profile" {
|
||||
@@ -772,13 +819,19 @@ func (app *appContext) GetAnnounceTemplate(gc *gin.Context) {
|
||||
// @Summary Delete an announcement template.
|
||||
// @Produce json
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 400 {object} boolResponse
|
||||
// @Failure 500 {object} boolResponse
|
||||
// @Param name path string true "name of template"
|
||||
// @Param name path string true "name of template (url encoded if necessary)"
|
||||
// @Router /users/announce/template/{name} [delete]
|
||||
// @Security Bearer
|
||||
// @tags Users
|
||||
func (app *appContext) DeleteAnnounceTemplate(gc *gin.Context) {
|
||||
name := gc.Param("name")
|
||||
escapedName := gc.Param("name")
|
||||
name, err := url.QueryUnescape(escapedName)
|
||||
if err != nil {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
app.storage.DeleteAnnouncementsKey(name)
|
||||
respondBool(200, false, gc)
|
||||
}
|
||||
@@ -850,60 +903,90 @@ func (app *appContext) AdminPasswordReset(gc *gin.Context) {
|
||||
respondBool(204, true, gc)
|
||||
}
|
||||
|
||||
// userSummary generates a respUser for to be displayed to the user, or sorted/filtered.
|
||||
// also, consider it a source of which data fields/struct modifications need to trigger a cache invalidation.
|
||||
func (app *appContext) userSummary(jfUser mediabrowser.User) respUser {
|
||||
// userSummary functions the same as userSummary, but pulls from the given caches rather than the database.
|
||||
func (app *appContext) userSummary(jfUser mediabrowser.User, email *EmailAddress, expiry *UserExpiry, discord *DiscordUser, telegram *TelegramUser, matrix *MatrixUser, referralActive bool) respUser {
|
||||
adminOnly := app.config.Section("ui").Key("admin_only").MustBool(true)
|
||||
allowAll := app.config.Section("ui").Key("allow_all").MustBool(false)
|
||||
referralsEnabled := app.config.Section("user_page").Key("referrals").MustBool(false)
|
||||
|
||||
user := respUser{
|
||||
ID: jfUser.ID,
|
||||
Name: jfUser.Name,
|
||||
Admin: jfUser.Policy.IsAdministrator,
|
||||
Disabled: jfUser.Policy.IsDisabled,
|
||||
ReferralsEnabled: false,
|
||||
ReferralsEnabled: referralActive || (email != nil && email.ReferralTemplateKey != ""),
|
||||
}
|
||||
if !jfUser.LastActivityDate.IsZero() {
|
||||
user.LastActive = jfUser.LastActivityDate.Unix()
|
||||
}
|
||||
if email, ok := app.storage.GetEmailsKey(jfUser.ID); ok {
|
||||
if email != nil {
|
||||
user.Email = email.Addr
|
||||
user.NotifyThroughEmail = email.Contact
|
||||
user.Label = email.Label
|
||||
user.AccountsAdmin = (app.jellyfinLogin) && (email.Admin || (adminOnly && jfUser.Policy.IsAdministrator) || allowAll)
|
||||
}
|
||||
expiry, ok := app.storage.GetUserExpiryKey(jfUser.ID)
|
||||
if ok {
|
||||
if expiry != nil {
|
||||
user.Expiry = expiry.Expiry.Unix()
|
||||
}
|
||||
if tgUser, ok := app.storage.GetTelegramKey(jfUser.ID); ok {
|
||||
user.Telegram = tgUser.Username
|
||||
user.NotifyThroughTelegram = tgUser.Contact
|
||||
if telegram != nil {
|
||||
user.Telegram = telegram.Username
|
||||
user.NotifyThroughTelegram = telegram.Contact
|
||||
}
|
||||
if mxUser, ok := app.storage.GetMatrixKey(jfUser.ID); ok {
|
||||
user.Matrix = mxUser.UserID
|
||||
user.NotifyThroughMatrix = mxUser.Contact
|
||||
if matrix != nil {
|
||||
user.Matrix = matrix.UserID
|
||||
user.NotifyThroughMatrix = matrix.Contact
|
||||
}
|
||||
if dcUser, ok := app.storage.GetDiscordKey(jfUser.ID); ok {
|
||||
user.Discord = RenderDiscordUsername(dcUser)
|
||||
// user.Discord = dcUser.Username + "#" + dcUser.Discriminator
|
||||
user.DiscordID = dcUser.ID
|
||||
user.NotifyThroughDiscord = dcUser.Contact
|
||||
}
|
||||
// FIXME: Send referral data
|
||||
referrerInv := Invite{}
|
||||
if referralsEnabled {
|
||||
// 1. Directly attached invite.
|
||||
err := app.storage.db.FindOne(&referrerInv, badgerhold.Where("ReferrerJellyfinID").Eq(jfUser.ID))
|
||||
if err == nil {
|
||||
user.ReferralsEnabled = true
|
||||
// 2. Referrals via profile template. Shallow check, doesn't look for the thing in the database.
|
||||
} else if email, ok := app.storage.GetEmailsKey(jfUser.ID); ok && email.ReferralTemplateKey != "" {
|
||||
user.ReferralsEnabled = true
|
||||
}
|
||||
if discord != nil {
|
||||
user.Discord = RenderDiscordUsername(*discord)
|
||||
// user.Discord = discord.Username + "#" + discord.Discriminator
|
||||
user.DiscordID = discord.ID
|
||||
user.NotifyThroughDiscord = discord.Contact
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
// GetUserSummary generates a respUser for to be displayed to the user, or sorted/filtered.
|
||||
// It fetches information from the db quite a lot. If calling lots, consider collecting data for all fields and calling app.userSummary().
|
||||
// also, consider it a source of which data fields/struct modifications need to trigger a cache invalidation.
|
||||
func (app *appContext) GetUserSummary(jfUser mediabrowser.User) respUser {
|
||||
referralsEnabled := app.config.Section("user_page").Key("referrals").MustBool(false)
|
||||
var emailPtr *EmailAddress = nil
|
||||
if email, ok := app.storage.GetEmailsKey(jfUser.ID); ok {
|
||||
emailPtr = &email
|
||||
}
|
||||
var expiryPtr *UserExpiry = nil
|
||||
if expiry, ok := app.storage.GetUserExpiryKey(jfUser.ID); ok {
|
||||
expiryPtr = &expiry
|
||||
}
|
||||
var discordPtr *DiscordUser = nil
|
||||
if discordEnabled {
|
||||
if discord, ok := app.storage.GetDiscordKey(jfUser.ID); ok {
|
||||
discordPtr = &discord
|
||||
}
|
||||
}
|
||||
var telegramPtr *TelegramUser = nil
|
||||
if telegramEnabled {
|
||||
if telegram, ok := app.storage.GetTelegramKey(jfUser.ID); ok {
|
||||
telegramPtr = &telegram
|
||||
}
|
||||
}
|
||||
var matrixPtr *MatrixUser = nil
|
||||
if matrixEnabled {
|
||||
if matrix, ok := app.storage.GetMatrixKey(jfUser.ID); ok {
|
||||
matrixPtr = &matrix
|
||||
}
|
||||
}
|
||||
referralsActive := false
|
||||
// FIXME: Send referral data
|
||||
referrerInv := Invite{}
|
||||
// FIXME: This is veeery slow when running an arm64 binary through qemu
|
||||
if referralsEnabled {
|
||||
// 1. Directly attached invite.
|
||||
if err := app.storage.db.FindOne(&referrerInv, badgerhold.Where("IsReferral").Eq(true).And("ReferrerJellyfinID").Eq(jfUser.ID)); err == nil {
|
||||
referralsActive = true
|
||||
}
|
||||
// 2. performed by userSummaryFixme
|
||||
}
|
||||
return app.userSummary(jfUser, emailPtr, expiryPtr, discordPtr, telegramPtr, matrixPtr, referralsActive)
|
||||
}
|
||||
|
||||
// @Summary Returns the total number of Jellyfin users.
|
||||
@@ -911,7 +994,7 @@ func (app *appContext) userSummary(jfUser mediabrowser.User) respUser {
|
||||
// @Success 200 {object} PageCountDTO
|
||||
// @Router /users/count [get]
|
||||
// @Security Bearer
|
||||
// @tags Activity
|
||||
// @tags Users,Statistics
|
||||
func (app *appContext) GetUserCount(gc *gin.Context) {
|
||||
resp := PageCountDTO{}
|
||||
users, err := app.jf.GetUsers(false)
|
||||
@@ -924,6 +1007,52 @@ func (app *appContext) GetUserCount(gc *gin.Context) {
|
||||
gc.JSON(200, resp)
|
||||
}
|
||||
|
||||
// @Summary Returns the list of all labels on accounts.
|
||||
// @Produce json
|
||||
// @Success 200 {object} LabelsDTO
|
||||
// @Router /users/labels [get]
|
||||
// @Security Bearer
|
||||
// @tags Users,Statistics
|
||||
func (app *appContext) GetLabels(gc *gin.Context) {
|
||||
if err := app.userCache.MaybeSync(app); err != nil {
|
||||
app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err)
|
||||
respond(500, "Couldn't get users", gc)
|
||||
return
|
||||
}
|
||||
gc.JSON(200, LabelsDTO{Labels: app.userCache.Labels})
|
||||
}
|
||||
|
||||
// @Summary Get the details of a given user. Skips the user cache and gets fresh information.
|
||||
// @Produce json
|
||||
// @Success 200 {object} respUser
|
||||
// @Failure 400 {object} boolResponse
|
||||
// @Failure 500 {object} stringResponse
|
||||
// @Param id path string true "id of user to fetch details of"
|
||||
// @Router /users/{id} [get]
|
||||
// @Security Bearer
|
||||
// @tags Users
|
||||
func (app *appContext) GetUser(gc *gin.Context) {
|
||||
userID := gc.Param("id")
|
||||
if userID == "" {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
user, err := app.jf.UserByID(userID, false)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case mediabrowser.ErrUserNotFound:
|
||||
respondBool(400, false, gc)
|
||||
app.err.Printf(lm.FailedGetUser, userID, lm.Jellyfin, err)
|
||||
return
|
||||
default:
|
||||
respond(500, "Check logs", gc)
|
||||
app.err.Printf(lm.FailedGetUser, userID, lm.Jellyfin, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
gc.JSON(200, app.GetUserSummary(user))
|
||||
}
|
||||
|
||||
// @Summary Get a list of -all- Jellyfin users.
|
||||
// @Produce json
|
||||
// @Success 200 {object} getUsersDTO
|
||||
@@ -952,7 +1081,7 @@ func (app *appContext) GetUsers(gc *gin.Context) {
|
||||
// @Failure 500 {object} stringResponse
|
||||
// @Router /users [post]
|
||||
// @Security Bearer
|
||||
// @tags Users
|
||||
// @tags Users,Statistics
|
||||
func (app *appContext) SearchUsers(gc *gin.Context) {
|
||||
req := ServerSearchReqDTO{}
|
||||
gc.BindJSON(&req)
|
||||
@@ -991,6 +1120,38 @@ func (app *appContext) SearchUsers(gc *gin.Context) {
|
||||
gc.JSON(200, resp)
|
||||
}
|
||||
|
||||
// @Summary Get a count of users matching the search provided
|
||||
// @Produce json
|
||||
// @Param ServerSearchReqDTO body ServerSearchReqDTO true "search / pagination parameters"
|
||||
// @Success 200 {object} PageCountDTO
|
||||
// @Failure 500 {object} stringResponse
|
||||
// @Router /users/count [post]
|
||||
// @Security Bearer
|
||||
// @tags Users,Statistics
|
||||
func (app *appContext) GetFilteredUserCount(gc *gin.Context) {
|
||||
req := ServerSearchReqDTO{}
|
||||
gc.BindJSON(&req)
|
||||
if req.SortByField == "" {
|
||||
req.SortByField = USER_DEFAULT_SORT_FIELD
|
||||
}
|
||||
|
||||
var resp PageCountDTO
|
||||
// No need to sort
|
||||
userList, err := app.userCache.GetUserDTOs(app, false)
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err)
|
||||
respond(500, "Couldn't get users", gc)
|
||||
return
|
||||
}
|
||||
if len(req.SearchTerms) != 0 || len(req.Queries) != 0 {
|
||||
resp.Count = uint64(len(app.userCache.Filter(userList, req.SearchTerms, req.Queries)))
|
||||
} else {
|
||||
resp.Count = uint64(len(userList))
|
||||
}
|
||||
|
||||
gc.JSON(200, resp)
|
||||
}
|
||||
|
||||
// @Summary Set whether or not a user can access jfa-go. Redundant if the user is a Jellyfin admin.
|
||||
// @Produce json
|
||||
// @Param setAccountsAdminDTO body setAccountsAdminDTO true "Map of userIDs to whether or not they have access."
|
||||
@@ -1058,39 +1219,21 @@ func (app *appContext) ModifyLabels(gc *gin.Context) {
|
||||
}
|
||||
|
||||
func (app *appContext) modifyEmail(jfID string, addr string) {
|
||||
contactPrefChanged := false
|
||||
emailStore, ok := app.storage.GetEmailsKey(jfID)
|
||||
// Auto enable contact by email for newly added addresses
|
||||
if !ok || emailStore.Addr == "" {
|
||||
emailStore = EmailAddress{
|
||||
Contact: true,
|
||||
}
|
||||
contactPrefChanged = true
|
||||
}
|
||||
emailStore.Addr = addr
|
||||
app.storage.SetEmailsKey(jfID, emailStore)
|
||||
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
||||
ombiUser, err := app.getOmbiUser(jfID)
|
||||
if err == nil {
|
||||
ombiUser["emailAddress"] = addr
|
||||
err = app.ombi.ModifyUser(ombiUser)
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedSetEmailAddress, lm.Ombi, jfID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if app.config.Section("jellyseerr").Key("enabled").MustBool(false) {
|
||||
err := app.js.ModifyMainUserSettings(jfID, jellyseerr.MainUserSettings{Email: addr})
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedSetEmailAddress, lm.Jellyseerr, jfID, err)
|
||||
} else if contactPrefChanged {
|
||||
contactMethods := map[jellyseerr.NotificationsField]any{
|
||||
jellyseerr.FieldEmailEnabled: true,
|
||||
}
|
||||
err := app.js.ModifyNotifications(jfID, contactMethods)
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err)
|
||||
}
|
||||
|
||||
for _, tps := range app.thirdPartyServices {
|
||||
if err := tps.SetContactMethods(jfID, &addr, nil, nil, &common.ContactPreferences{
|
||||
Email: &(emailStore.Contact),
|
||||
}); err != nil {
|
||||
app.err.Printf(lm.FailedSetEmailAddress, tps.Name(), jfID, err)
|
||||
}
|
||||
}
|
||||
app.InvalidateWebUserCache()
|
||||
@@ -1258,7 +1401,7 @@ func (app *appContext) ApplySettings(gc *gin.Context) {
|
||||
}
|
||||
if ombi != nil {
|
||||
errorString := ""
|
||||
user, err := app.getOmbiUser(id)
|
||||
user, err := app.getOmbiUser(id, nil)
|
||||
if err != nil {
|
||||
errorString += fmt.Sprintf("Ombi GetUser: %v ", err)
|
||||
} else {
|
||||
@@ -1307,3 +1450,100 @@ func (app *appContext) ApplySettings(gc *gin.Context) {
|
||||
app.InvalidateUserCaches()
|
||||
gc.JSON(code, errors)
|
||||
}
|
||||
|
||||
// @Summary Gets the number of Jellyfin/Emby activities stored by jfa-go related to the given user ID. As the total collected by jfa-go is limited, this may not include all those held by Jellyfin.
|
||||
// @Produce json
|
||||
// @Success 200 {object} PageCountDTO
|
||||
// @Failure 400 {object} boolResponse
|
||||
// @Param id path string true "id of user to fetch activities of."
|
||||
// @Router /users/{id}/activities/jellyfin/count [get]
|
||||
// @Security Bearer
|
||||
// @tags Users
|
||||
func (app *appContext) CountJFActivitesForUser(gc *gin.Context) {
|
||||
userID := gc.Param("id")
|
||||
if userID == "" {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
activities, err := app.jf.activity.ByUserID(userID)
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedGetJFActivities, err)
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
gc.JSON(200, PageCountDTO{Count: uint64(len(activities))})
|
||||
}
|
||||
|
||||
// @Summary Get the latest Jellyfin/Emby activities related to the given user ID. Returns as many as the server has recorded. As the total collected by jfa-go is limited, this may not include all those held by Jellyfin.
|
||||
// @Produce json
|
||||
// @Success 200 {object} ActivityLogEntriesDTO
|
||||
// @Failure 400 {object} boolResponse
|
||||
// @Param id path string true "id of user to fetch activities of."
|
||||
// @Router /users/{id}/activities/jellyfin [get]
|
||||
// @Security Bearer
|
||||
// @tags Users
|
||||
func (app *appContext) GetJFActivitesForUser(gc *gin.Context) {
|
||||
userID := gc.Param("id")
|
||||
if userID == "" {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
activities, err := app.jf.activity.ByUserID(userID)
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedGetJFActivities, err)
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
out := ActivityLogEntriesDTO{
|
||||
Entries: make([]ActivityLogEntryDTO, len(activities)),
|
||||
}
|
||||
for i := range activities {
|
||||
out.Entries[i].ActivityLogEntry = activities[i]
|
||||
out.Entries[i].Date = activities[i].Date.Unix()
|
||||
}
|
||||
app.debug.Printf(lm.GotNEntries, len(activities))
|
||||
gc.JSON(200, out)
|
||||
}
|
||||
|
||||
// @Summary Get the latest Jellyfin/Emby activities related to the given user ID, paginated. As the total collected by jfa-go is limited, this may not include all those held by Jellyfin.
|
||||
// @Produce json
|
||||
// @Param PaginatedReqDTO body PaginatedReqDTO true "pagination parameters"
|
||||
// @Success 200 {object} PaginatedActivityLogEntriesDTO
|
||||
// @Failure 400 {object} boolResponse
|
||||
// @Param id path string true "id of user to fetch activities of."
|
||||
// @Router /users/{id}/activities/jellyfin [post]
|
||||
// @Security Bearer
|
||||
// @tags Users
|
||||
func (app *appContext) GetPaginatedJFActivitesForUser(gc *gin.Context) {
|
||||
var req PaginatedReqDTO
|
||||
gc.BindJSON(&req)
|
||||
userID := gc.Param("id")
|
||||
if userID == "" {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
activities, err := app.jf.activity.ByUserID(userID)
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedGetJFActivities, err)
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
out := PaginatedActivityLogEntriesDTO{}
|
||||
startIndex := req.Page * req.Limit
|
||||
if startIndex >= len(activities) {
|
||||
out.LastPage = true
|
||||
gc.JSON(200, out)
|
||||
return
|
||||
}
|
||||
endIndex := min(startIndex+req.Limit, len(activities))
|
||||
activities = activities[startIndex:endIndex]
|
||||
|
||||
out.Entries = make([]ActivityLogEntryDTO, len(activities))
|
||||
out.LastPage = len(activities) != req.Limit
|
||||
for i := range activities {
|
||||
out.Entries[i].ActivityLogEntry = activities[i]
|
||||
out.Entries[i].Date = activities[i].Date.Unix()
|
||||
}
|
||||
app.debug.Printf(lm.GotNEntries, len(activities))
|
||||
gc.JSON(200, out)
|
||||
}
|
||||
|
||||
2
api.go
2
api.go
@@ -201,7 +201,7 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
|
||||
respondBool(200, true, gc)
|
||||
return
|
||||
} */
|
||||
ombiUser, err := app.getOmbiUser(user.ID)
|
||||
ombiUser, err := app.getOmbiUser(user.ID, nil)
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedGetUser, user.ID, lm.Ombi, err)
|
||||
respondBool(200, true, gc)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//go:build tray
|
||||
// +build tray
|
||||
|
||||
package main
|
||||
@@ -8,7 +9,7 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/emersion/go-autostart"
|
||||
"github.com/getlantern/systray"
|
||||
"github.com/lutischan-ferenc/systray"
|
||||
)
|
||||
|
||||
type Autostart struct {
|
||||
@@ -48,8 +49,8 @@ func NewAutostart(name, displayname, trayName, trayTooltip string) *Autostart {
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *Autostart) HandleCheck() {
|
||||
for range a.menuitem.ClickedCh {
|
||||
func (a *Autostart) Register() {
|
||||
a.menuitem.Click(func() {
|
||||
if !a.menuitem.Checked() {
|
||||
if err := a.as.Enable(); err != nil {
|
||||
log.Printf("Failed to enable autostart on login: %v", err)
|
||||
@@ -65,5 +66,5 @@ func (a *Autostart) HandleCheck() {
|
||||
log.Printf("Disabled autostart")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
9
biome.json
Normal file
9
biome.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 4,
|
||||
"formatWithErrors": false,
|
||||
"lineWidth": 120
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,16 @@ import (
|
||||
lm "github.com/hrfee/jfa-go/logmessages"
|
||||
)
|
||||
|
||||
const (
|
||||
BogusIdentifier = "123412341234123456"
|
||||
)
|
||||
|
||||
// ContactPreferences holds whether or not a user should be contacted through each of the available
|
||||
// methods. If nil, leave setting alone.
|
||||
type ContactPreferences struct {
|
||||
Email, Discord, Telegram, Matrix *bool
|
||||
}
|
||||
|
||||
// TimeoutHandler recovers from an http timeout or panic.
|
||||
type TimeoutHandler func()
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package common
|
||||
|
||||
type SectionMeta struct {
|
||||
Name string `json:"name" yaml:"name" example:"My Section"` // friendly name of the section
|
||||
Description string `json:"description" yaml:"description"`
|
||||
Advanced bool `json:"advanced,omitempty" yaml:"advanced,omitempty"`
|
||||
Disabled bool `json:"disabled,omitempty" yaml:"disabled,omitempty"`
|
||||
DependsTrue string `json:"depends_true,omitempty" yaml:"depends_true,omitempty"`
|
||||
DependsFalse string `json:"depends_false,omitempty" yaml:"depends_false,omitempty"`
|
||||
WikiLink string `json:"wiki_link,omitempty" yaml:"wiki_link,omitempty"`
|
||||
Name string `json:"name" yaml:"name" example:"My Section"` // friendly name of the section
|
||||
Description string `json:"description" yaml:"description"`
|
||||
Advanced bool `json:"advanced,omitempty" yaml:"advanced,omitempty"`
|
||||
Disabled bool `json:"disabled,omitempty" yaml:"disabled,omitempty"`
|
||||
DependsTrue string `json:"depends_true,omitempty" yaml:"depends_true,omitempty"`
|
||||
DependsFalse string `json:"depends_false,omitempty" yaml:"depends_false,omitempty"`
|
||||
WikiLink string `json:"wiki_link,omitempty" yaml:"wiki_link,omitempty"`
|
||||
Aliases []string `json:"aliases,omitempty" yaml:"aliases,omitempty"`
|
||||
}
|
||||
|
||||
type Option [2]string
|
||||
@@ -40,6 +41,7 @@ type Setting struct {
|
||||
Style string `json:"style,omitempty" yaml:"style,omitempty"`
|
||||
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
|
||||
WikiLink string `json:"wiki_link,omitempty" yaml:"wiki_link,omitempty"`
|
||||
Aliases []string `json:"aliases,omitempty" yaml:"aliases,omitempty"`
|
||||
}
|
||||
|
||||
type Section struct {
|
||||
@@ -48,8 +50,25 @@ type Section struct {
|
||||
Settings []Setting `json:"settings" yaml:"settings"`
|
||||
}
|
||||
|
||||
// Member is a member of a group, and can either reference a Section or another Group, hence the two fields.
|
||||
type Member struct {
|
||||
Group string `json:"group,omitempty", yaml:"group,omitempty"`
|
||||
Section string `json:"section,omitempty", yaml:"section,omitempty"`
|
||||
}
|
||||
|
||||
type Group struct {
|
||||
Group string `json:"group" yaml:"group" example:"messaging_providers"`
|
||||
Name string `json:"name" yaml:"name" example:"Messaging Providers"`
|
||||
Description string `json:"description" yaml:"description" example:"Options for setting up messaging providers."`
|
||||
Members []Member `json:"members" yaml:"members"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Sections []Section `json:"sections" yaml:"sections"`
|
||||
Groups []Group `json:"groups" yaml:"groups"`
|
||||
// Optional order, which can interleave sections and groups.
|
||||
// If unset, falls back to sections in order, then groups in order.
|
||||
Order []Member `json:"order,omitempty" yaml:"order,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Config) removeSection(section string) {
|
||||
|
||||
18
config.go
18
config.go
@@ -66,6 +66,13 @@ func FixFullURL(v string) string {
|
||||
return v
|
||||
}
|
||||
|
||||
func MustGetNonEmptyURL(path string) string {
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
return "/" + path
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func FormatSubpath(path string, removeSingleSlash bool) string {
|
||||
if path == "/" {
|
||||
if removeSingleSlash {
|
||||
@@ -202,8 +209,8 @@ func NewConfig(configPathOrContents any, dataPath string, logs LoggerSet) (*Conf
|
||||
config.Section("email").Key("no_username").SetValue(strconv.FormatBool(config.Section("email").Key("no_username").MustBool(false)))
|
||||
|
||||
// FIXME: Remove all these, eventually
|
||||
// config.MustSetValue("password_resets", "email_html", "jfa-go:"+"email.html")
|
||||
// config.MustSetValue("password_resets", "email_text", "jfa-go:"+"email.txt")
|
||||
// config.MustSetValue("password_resets", "email_html", "jfa-go:"+"password-reset.html")
|
||||
// config.MustSetValue("password_resets", "email_text", "jfa-go:"+"password-reset.txt")
|
||||
|
||||
// config.MustSetValue("invite_emails", "email_html", "jfa-go:"+"invite-email.html")
|
||||
// config.MustSetValue("invite_emails", "email_text", "jfa-go:"+"invite-email.txt")
|
||||
@@ -266,6 +273,11 @@ func NewConfig(configPathOrContents any, dataPath string, logs LoggerSet) (*Conf
|
||||
config.Section("discord").Key("start_command").SetValue(strings.TrimPrefix(strings.TrimPrefix(sc, "/"), "!"))
|
||||
|
||||
config.MustSetValue("email", "collect", "true")
|
||||
collect := config.Section("email").Key("collect").MustBool(true)
|
||||
required := config.Section("email").Key("required").MustBool(false) && collect
|
||||
config.Section("email").Key("required").SetValue(strconv.FormatBool(required))
|
||||
unique := config.Section("email").Key("require_unique").MustBool(false) && collect
|
||||
config.Section("email").Key("require_unique").SetValue(strconv.FormatBool(unique))
|
||||
|
||||
config.MustSetValue("matrix", "topic", "Jellyfin notifications")
|
||||
config.MustSetValue("matrix", "show_on_reg", "true")
|
||||
@@ -286,6 +298,7 @@ func NewConfig(configPathOrContents any, dataPath string, logs LoggerSet) (*Conf
|
||||
config.MustSetValue("jellyfin", "cache_timeout", "30")
|
||||
config.MustSetValue("jellyfin", "web_cache_async_timeout", "1")
|
||||
config.MustSetValue("jellyfin", "web_cache_sync_timeout", "10")
|
||||
config.MustSetValue("jellyfin", "activity_cache_sync_timeout_seconds", "20")
|
||||
|
||||
LOGIP = config.Section("advanced").Key("log_ips").MustBool(false)
|
||||
LOGIPU = config.Section("advanced").Key("log_ips_users").MustBool(false)
|
||||
@@ -411,6 +424,7 @@ func (config *Config) ReloadDependents(app *appContext) {
|
||||
}
|
||||
|
||||
app.email = NewEmailer(config, app.storage, app.LoggerSet)
|
||||
|
||||
}
|
||||
|
||||
func (app *appContext) ReloadConfig() {
|
||||
|
||||
@@ -1,3 +1,60 @@
|
||||
order:
|
||||
- section: ui
|
||||
- section: advanced
|
||||
- section: jellyfin
|
||||
- group: sign_up
|
||||
- group: accounts
|
||||
- section: messages
|
||||
- group: external_services
|
||||
- section: activity_log
|
||||
- section: backups
|
||||
- section: updates
|
||||
- section: url_paths
|
||||
- section: template_email
|
||||
- section: files
|
||||
groups:
|
||||
- group: external_services
|
||||
name: "Integrations"
|
||||
description: "Integrations with external services."
|
||||
members:
|
||||
- group: email
|
||||
- group: chatbots
|
||||
- section: ombi
|
||||
- section: jellyseerr
|
||||
- section: webhooks
|
||||
- group: email
|
||||
name: "Email"
|
||||
description: "Options for sending emails through jfa-go."
|
||||
members:
|
||||
- section: email
|
||||
- section: smtp
|
||||
- section: mailgun
|
||||
- section: email_confirmation
|
||||
- group: chatbots
|
||||
name: "Chatbots"
|
||||
description: "Options for messaging through chat services."
|
||||
members:
|
||||
- section: discord
|
||||
- section: telegram
|
||||
- section: matrix
|
||||
- group: sign_up
|
||||
name: "Invites & Referrals"
|
||||
description: "Settings relating to invites, the sign up page and referrals."
|
||||
members:
|
||||
- section: captcha
|
||||
- section: password_validation
|
||||
- section: invite_emails
|
||||
- section: notifications
|
||||
- section: welcome_email
|
||||
- group: accounts
|
||||
name: "Accounts"
|
||||
description: "Settings relating to account management."
|
||||
members:
|
||||
- section: user_page
|
||||
- section: password_resets
|
||||
- section: user_expiry
|
||||
- section: disable_enable
|
||||
- section: deletion
|
||||
sections:
|
||||
- section: updates
|
||||
meta:
|
||||
@@ -79,6 +136,13 @@ sections:
|
||||
type: number
|
||||
value: 10
|
||||
description: "Synchronise after cache is this old, and wait for it: The accounts tab may take a little longer to load while it does."
|
||||
- setting: activity_cache_sync_timeout
|
||||
name: "Activity cache timeout (minutes)"
|
||||
requires_restart: true
|
||||
advanced: true
|
||||
type: number
|
||||
value: 0.1
|
||||
description: "Synchronise Jellyfin's activity log after cache is this old. It can be pretty low as syncing only pulls new records and so is quick. Note this is unrelated to jfa-go's activity log."
|
||||
- setting: type
|
||||
name: Server type
|
||||
requires_restart: true
|
||||
@@ -516,7 +580,7 @@ sections:
|
||||
meta:
|
||||
name: Captcha
|
||||
description: Settings related to user creation CAPTCHAs.
|
||||
wiki_link: https://wiki.jfa-go.com/docs/captcha/
|
||||
wiki_link: https://wiki.jfa-go.com/docs/external-services/captcha/
|
||||
settings:
|
||||
- setting: enabled
|
||||
name: Enabled
|
||||
@@ -670,7 +734,7 @@ sections:
|
||||
meta:
|
||||
name: Messages/Notifications
|
||||
description: General settings for emails/messages.
|
||||
wiki_link: https://wiki.jfa-go.com/docs/emails/
|
||||
wiki_link: https://wiki.jfa-go.com/docs/customization/emails/
|
||||
settings:
|
||||
- setting: enabled
|
||||
name: Enabled
|
||||
@@ -682,19 +746,19 @@ sections:
|
||||
etc.
|
||||
- setting: use_24h
|
||||
name: Use 24h time
|
||||
depends_true: method
|
||||
depends_true: enabled
|
||||
type: bool
|
||||
value: true
|
||||
- setting: date_format
|
||||
name: Date format
|
||||
advanced: true
|
||||
depends_true: method
|
||||
advanced: false
|
||||
depends_true: enabled
|
||||
type: text
|
||||
value: '%d/%m/%y'
|
||||
description: Date format used in emails. Follows datetime.strftime format.
|
||||
- setting: message
|
||||
name: Help message
|
||||
depends_true: method
|
||||
depends_true: enabled
|
||||
type: text
|
||||
value: Need help? contact me.
|
||||
description: Message displayed at bottom of emails.
|
||||
@@ -719,9 +783,27 @@ sections:
|
||||
- ["en-us", "English (US)"]
|
||||
value: en-us
|
||||
description: Default email language. Submit a PR on github if you'd like to translate.
|
||||
- setting: collect
|
||||
name: Collect on sign-up
|
||||
type: bool
|
||||
value: true
|
||||
description: Ask for an email address on the sign-up form.
|
||||
- setting: required
|
||||
name: Require on sign-up
|
||||
depends_true: collect
|
||||
type: bool
|
||||
value: false
|
||||
description: Require an email address on sign-up.
|
||||
- setting: require_unique
|
||||
name: Require unique address
|
||||
requires_restart: true
|
||||
depends_true: method
|
||||
type: bool
|
||||
value: false
|
||||
description: Disables using the same address on multiple accounts.
|
||||
- setting: no_username
|
||||
name: Use email addresses as username
|
||||
depends_true: method
|
||||
depends_true: collect
|
||||
type: bool
|
||||
value: false
|
||||
description: Use email address from invite form as username on Jellyfin.
|
||||
@@ -733,6 +815,7 @@ sections:
|
||||
- ["smtp", "SMTP"]
|
||||
- ["mailgun", "Mailgun"]
|
||||
value: smtp
|
||||
depends_true: messages|enabled
|
||||
description: Method of sending email to use.
|
||||
- setting: address
|
||||
name: Sent from (address)
|
||||
@@ -753,25 +836,6 @@ sections:
|
||||
type: bool
|
||||
value: false
|
||||
description: Send emails as plain text instead of HTML.
|
||||
- setting: collect
|
||||
name: Collect on sign-up
|
||||
depends_true: method
|
||||
type: bool
|
||||
value: true
|
||||
description: Ask for an email address on the sign-up form.
|
||||
- setting: required
|
||||
name: Require on sign-up
|
||||
depends_true: collect
|
||||
type: bool
|
||||
value: false
|
||||
description: Require an email address on sign-up.
|
||||
- setting: require_unique
|
||||
name: Require unique address
|
||||
requires_restart: true
|
||||
depends_true: method
|
||||
type: bool
|
||||
value: false
|
||||
description: Disables using the same address on multiple accounts.
|
||||
- setting: test_note
|
||||
name: 'Test your settings:'
|
||||
type: note
|
||||
@@ -780,7 +844,7 @@ sections:
|
||||
description: Go over to the accounts tab, select your user (ensuring you've assigned it an email address) and send yourself an announcement.
|
||||
- section: mailgun
|
||||
meta:
|
||||
name: Mailgun (Email)
|
||||
name: Mailgun
|
||||
description: Mailgun API connection settings
|
||||
depends_true: email|method
|
||||
settings:
|
||||
@@ -794,7 +858,7 @@ sections:
|
||||
value: your api key
|
||||
- section: smtp
|
||||
meta:
|
||||
name: SMTP (Email)
|
||||
name: SMTP
|
||||
description: SMTP Server connection settings.
|
||||
depends_true: email|method
|
||||
settings:
|
||||
@@ -860,7 +924,7 @@ sections:
|
||||
meta:
|
||||
name: Discord
|
||||
description: Settings for Discord invites/signup/notifications
|
||||
wiki_link: https://wiki.jfa-go.com/docs/bots/discord/
|
||||
wiki_link: https://wiki.jfa-go.com/docs/external-services/bots/discord/
|
||||
settings:
|
||||
- setting: enabled
|
||||
name: Enabled
|
||||
@@ -955,7 +1019,7 @@ sections:
|
||||
name: Telegram
|
||||
description: Settings for Telegram signup/notifications. See the jfa-go wiki for
|
||||
info on setting this up.
|
||||
wiki_link: https://wiki.jfa-go.com/docs/bots/telegram/
|
||||
wiki_link: https://wiki.jfa-go.com/docs/external-services/bots/telegram/
|
||||
settings:
|
||||
- setting: enabled
|
||||
name: Enabled
|
||||
@@ -999,12 +1063,18 @@ sections:
|
||||
value: en-us
|
||||
description: Default telegram message language. Visit weblate if you'd like to
|
||||
translate.
|
||||
- setting: ignore_client_language
|
||||
name: Always use default language
|
||||
depends_true: enabled
|
||||
type: bool
|
||||
value: false
|
||||
description: When disabled, jfa-go will check the telegram user's language and use it if possible. Enable to ignore it and use your configured default language always.
|
||||
- section: matrix
|
||||
meta:
|
||||
name: Matrix
|
||||
description: Settings for Matrix invites/signup/notifications. See the jfa-go
|
||||
wiki for info on setting this up.
|
||||
wiki_link: https://wiki.jfa-go.com/docs/bots/matrix/
|
||||
wiki_link: https://wiki.jfa-go.com/docs/external-services/bots/matrix/
|
||||
settings:
|
||||
- setting: enabled
|
||||
name: Enabled
|
||||
@@ -1236,7 +1306,7 @@ sections:
|
||||
description: Path to custom email text template for announcements/custom messages.
|
||||
- section: notifications
|
||||
meta:
|
||||
name: Admin invite notifications
|
||||
name: Admin notifications
|
||||
description: Allows toggling "user created" and "invite expired" notifications
|
||||
to be sent to the admin per-invite.
|
||||
depends_true: messages|enabled
|
||||
@@ -1276,13 +1346,13 @@ sections:
|
||||
description: Path to user creation notification email in plaintext.
|
||||
- section: ombi
|
||||
meta:
|
||||
name: Ombi Integration
|
||||
name: Ombi
|
||||
description: Connect to Ombi to automatically create both Ombi and Jellyfin accounts
|
||||
for new users. You'll need to add a ombi template to an existing User Profile
|
||||
for accounts to be created, which you can do by refreshing then checking Settings
|
||||
> User Profiles. To handle password resets for Ombi & Jellyfin, enable "Use
|
||||
reset link instead of PIN".
|
||||
wiki_link: https://wiki.jfa-go.com/docs/ombi/
|
||||
wiki_link: https://wiki.jfa-go.com/docs/external-services/ombi/
|
||||
settings:
|
||||
- setting: enabled
|
||||
name: Enabled
|
||||
@@ -1305,12 +1375,16 @@ sections:
|
||||
description: API Key. Get this from the first tab in Ombi settings.
|
||||
- section: jellyseerr
|
||||
meta:
|
||||
name: Jellyseerr Integration
|
||||
name: Jellyseerr
|
||||
description: Connect to Jellyseerr to automatically trigger the import of users
|
||||
on account creation, and to automatically link contact methods (email, discord
|
||||
and telegram). A template must be added to a User Profile for accounts to be
|
||||
created.
|
||||
wiki_link: https://wiki.jfa-go.com/docs/external-services/jellyseerr/
|
||||
aliases:
|
||||
- Jellyseerr
|
||||
- Overseerr
|
||||
- Seerr
|
||||
settings:
|
||||
- setting: enabled
|
||||
name: Enabled
|
||||
@@ -1346,6 +1420,7 @@ sections:
|
||||
depends_true: enabled
|
||||
description: Existing users (and those created outside jfa-go) will have their
|
||||
contact info imported to Jellyseerr.
|
||||
deprecated: true
|
||||
- setting: constraints_note
|
||||
name: 'Unique Emails:'
|
||||
type: note
|
||||
@@ -1425,7 +1500,7 @@ sections:
|
||||
name: Email confirmation
|
||||
description: If enabled, a user will be sent an email confirmation link to ensure
|
||||
their password is right before they can make an account.
|
||||
depends_true: email|method
|
||||
depends_true: email|collect
|
||||
settings:
|
||||
- setting: enabled
|
||||
name: Enabled
|
||||
@@ -1448,7 +1523,7 @@ sections:
|
||||
description: Path to custom email in plain text
|
||||
- section: user_expiry
|
||||
meta:
|
||||
name: User Expiry
|
||||
name: Account Expiry
|
||||
description: When set on an invite, users will be deleted or disabled a specified
|
||||
amount of time after they create their account. Expiries can also be set and
|
||||
extended for invididual users, optionally with a message why.
|
||||
@@ -1590,7 +1665,7 @@ sections:
|
||||
description: jfa-go will send a POST request to these URLs when an event occurs,
|
||||
with relevant information. Request information is logged when debug logging
|
||||
is enabled.
|
||||
wiki_link: https://wiki.jfa-go.com/docs/webhooks/
|
||||
wiki_link: https://wiki.jfa-go.com/docs/dev/webhooks/
|
||||
settings:
|
||||
- setting: created
|
||||
name: User Created
|
||||
|
||||
58
css/base.css
58
css/base.css
@@ -66,9 +66,6 @@ html:not(.dark) .card.\@low:not(.\~neutral):not(.\~positive):not(.\~urge):not(.\
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
:root {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.table-responsive table {
|
||||
min-width: 800px;
|
||||
}
|
||||
@@ -207,9 +204,9 @@ sup.\~critical, .text-critical {
|
||||
font-size: 1rem;
|
||||
padding-top: 0.1rem;
|
||||
padding-bottom: 0.1rem;
|
||||
margin-left: 0.5rem;
|
||||
margin-right: 1rem;
|
||||
max-width: 75%;
|
||||
margin-inline-start: 0.5rem;
|
||||
margin-inline-end: 1rem;
|
||||
width: 5rem;;
|
||||
}
|
||||
|
||||
.stealth-input-hidden {
|
||||
@@ -221,15 +218,8 @@ sup.\~critical, .text-critical {
|
||||
padding-bottom: 0.1rem;
|
||||
}
|
||||
|
||||
.settings-section-button {
|
||||
width: 100%;
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
.settings-section-button:hover, .settings-section-button:focus {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 2.5rem;
|
||||
background-color: var(--color-neutral-normal-fill);
|
||||
filter: brightness(var(--settings-section-button-filter)) !important;
|
||||
}
|
||||
@@ -242,7 +232,7 @@ sup.\~critical, .text-critical {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
.textarea:not(code-input *) {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
@@ -254,7 +244,7 @@ sup.\~critical, .text-critical {
|
||||
overflow-y: visible;
|
||||
}
|
||||
|
||||
select, textarea {
|
||||
select, textarea:not(code-input *) {
|
||||
color: inherit;
|
||||
border: 0 solid var(--color-neutral-300);
|
||||
appearance: none;
|
||||
@@ -262,7 +252,7 @@ select, textarea {
|
||||
-moz-appearance: none;
|
||||
}
|
||||
|
||||
html.dark textarea {
|
||||
html.dark textarea:not(code-input *) {
|
||||
background-color: #202020
|
||||
}
|
||||
|
||||
@@ -286,13 +276,12 @@ table.table-p-0 th, table.table-p-0 td {
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
|
||||
p.top {
|
||||
margin-top: 0px;
|
||||
td:dir(rtl), th:dir(rtl) {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.table-responsive {
|
||||
overflow-x: auto;
|
||||
font-size: 0.9rem;
|
||||
p.top {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
#notification-box {
|
||||
@@ -320,7 +309,7 @@ p.top {
|
||||
bottom: 115%;
|
||||
}
|
||||
|
||||
pre {
|
||||
pre:not(code-input *) {
|
||||
white-space: pre-wrap; /* css-3 */
|
||||
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
||||
white-space: -pre-wrap; /* Opera 4-6 */
|
||||
@@ -466,13 +455,13 @@ section.section:not(.\~neutral) {
|
||||
|
||||
@layer components {
|
||||
.switch {
|
||||
@apply flex flex-row gap-1 items-center;
|
||||
@apply flex flex-row gap-2 items-center;
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
/* seems to be the sweet spot */
|
||||
--inside-input-base: -2.6rem;
|
||||
--inside-input-base: -2.1rem;
|
||||
|
||||
/* thought --spacing would do the trick but apparently not */
|
||||
--tailwind-spacing: 0.25rem;
|
||||
@@ -480,9 +469,26 @@ section.section:not(.\~neutral) {
|
||||
|
||||
/* places buttons inside a sibling input element (hopefully), based on the flex gap of the parent. */
|
||||
.gap-1 > .button.inside-input {
|
||||
margin-left: calc(var(--inside-input-base) - 1.0*var(--tailwind-spacing));
|
||||
margin-inline-start: calc(var(--inside-input-base) - 1.0*var(--tailwind-spacing));
|
||||
}
|
||||
|
||||
.gap-2 > .button.inside-input {
|
||||
margin-left: calc(var(--inside-input-base) - 2.0*var(--tailwind-spacing));
|
||||
margin-inline-start: calc(var(--inside-input-base) - 2.0*var(--tailwind-spacing));
|
||||
}
|
||||
|
||||
.force-ltr {
|
||||
direction: ltr !important;
|
||||
}
|
||||
|
||||
.content ul, .content ol {
|
||||
margin-left: unset;
|
||||
margin-inline-start: 2rem;
|
||||
}
|
||||
|
||||
.content li {
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.input {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ html:not(.dark) .wall {
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
float: right;
|
||||
float: inline-end;
|
||||
color: #aaa;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
.tooltip .content {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
max-width: 10rem;
|
||||
max-width: 16rem;
|
||||
min-width: 6rem;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
color: #fff;
|
||||
@@ -22,15 +22,18 @@
|
||||
}
|
||||
|
||||
.tooltip.below .content {
|
||||
top: 2.5rem;
|
||||
left: 0;
|
||||
top: calc(100% + 0.125rem);
|
||||
left: 50%;
|
||||
right: 0;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.tooltip.above .content {
|
||||
bottom: 2.5rem;
|
||||
left: 0;
|
||||
top: unset;
|
||||
bottom: calc(100% + 0.125rem);
|
||||
left: 50%;
|
||||
right: 0;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.tooltip.darker .content {
|
||||
@@ -41,10 +44,20 @@
|
||||
left: 120%;
|
||||
}
|
||||
|
||||
.tooltip.right:dir(rtl):not(.force-ltr) .content {
|
||||
right: 120%;
|
||||
left: unset;
|
||||
}
|
||||
|
||||
.tooltip.left .content {
|
||||
right: 120%;
|
||||
}
|
||||
|
||||
.tooltip.left:dir(rtl):not(.force-ltr) .content {
|
||||
left: 120%;
|
||||
right: unset;
|
||||
}
|
||||
|
||||
.tooltip .content.sm {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
@@ -352,9 +352,33 @@ var customContent = map[string]CustomContentInfo{
|
||||
"myAccountURL",
|
||||
),
|
||||
Placeholders: defaultVals(map[string]any{
|
||||
"myAccountURL": "https://sub2.test.url/my/account",
|
||||
"myAccountURL": "https://example.url/my/account",
|
||||
}),
|
||||
},
|
||||
"PreSignupCard": {
|
||||
Name: "PreSignupCard",
|
||||
ContentType: CustomCard,
|
||||
DisplayName: func(dict *Lang, lang string) string {
|
||||
if _, ok := dict.Admin[lang]; !ok {
|
||||
lang = dict.chosenAdminLang
|
||||
}
|
||||
return dict.Admin[lang].Strings["preSignupCard"]
|
||||
},
|
||||
Description: func(dict *Lang, lang string) string {
|
||||
if _, ok := dict.Admin[lang]; !ok {
|
||||
lang = dict.chosenAdminLang
|
||||
}
|
||||
return dict.Admin[lang].Strings["preSignupCardDescription"]
|
||||
},
|
||||
Variables: []string{
|
||||
"myAccountURL",
|
||||
"profile",
|
||||
},
|
||||
Placeholders: map[string]any{
|
||||
"myAccountURL": "https://example.url/my/account",
|
||||
"profile": "Default User Profile",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var EmptyCustomContent = CustomContentInfo{
|
||||
|
||||
23
discord.go
23
discord.go
@@ -32,6 +32,17 @@ type DiscordDaemon struct {
|
||||
retryOpts *common.MustAuthenticateOptions
|
||||
}
|
||||
|
||||
func EmptyDiscordUser() *DiscordUser {
|
||||
return &DiscordUser{
|
||||
ID: "",
|
||||
Username: "",
|
||||
Discriminator: "",
|
||||
Lang: "",
|
||||
Contact: false,
|
||||
JellyfinID: "",
|
||||
}
|
||||
}
|
||||
|
||||
func newDiscordDaemon(app *appContext) (*DiscordDaemon, error) {
|
||||
token := app.config.Section("discord").Key("token").String()
|
||||
if token == "" {
|
||||
@@ -727,7 +738,6 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
|
||||
|
||||
var invname *dg.Member = nil
|
||||
invname, err = d.bot.GuildMember(d.guildID, recipient.ID)
|
||||
invite.SendTo = invname.User.Username
|
||||
|
||||
if err == nil && !(d.app.config.Section("invite_emails").Key("enabled").MustBool(false)) {
|
||||
err = errors.New(lm.InviteMessagesDisabled)
|
||||
@@ -735,11 +745,14 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
|
||||
|
||||
var msg *Message
|
||||
if err == nil {
|
||||
msg, err = d.app.email.constructInvite(invite, false)
|
||||
msg, err = d.app.email.constructInvite(&invite, false)
|
||||
if err != nil {
|
||||
// Print extra message, ideally we'd just print this, or get rid of it though.
|
||||
invite.SendTo = fmt.Sprintf(lm.FailedConstructInviteMessage, invite.Code, err)
|
||||
d.app.err.Println(invite.SendTo)
|
||||
invite.SentTo.Failed = append(invite.SentTo.Failed, SendFailure{
|
||||
Address: invname.User.Username,
|
||||
Reason: CheckLogs,
|
||||
})
|
||||
d.app.err.Printf(lm.FailedConstructInviteMessage, invite.Code, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -749,12 +762,12 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
|
||||
|
||||
if err == nil {
|
||||
d.app.info.Printf(lm.SentInviteMessage, invite.Code, RenderDiscordUsername(recipient))
|
||||
invite.SentTo.Success = append(invite.SentTo.Success, invname.User.Username)
|
||||
sendResponse("sentInvite")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
invite.SendTo = fmt.Sprintf(lm.FailedSendInviteMessage, invite.Code, RenderDiscordUsername(recipient), err)
|
||||
d.app.err.Println(invite.SendTo)
|
||||
sendResponse("sentInviteFailure")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
module github.com/hrfee/jfa-go/easyproxy
|
||||
|
||||
go 1.23.0
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.24.5
|
||||
|
||||
require golang.org/x/net v0.42.0
|
||||
require golang.org/x/net v0.47.0
|
||||
|
||||
require github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b h1:xZ59n7Frzh8CwyfAapUZLSg+gXH5m63YEaFCMpDHhpI=
|
||||
github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b/go.mod h1:uDd4sYVYsqcxAB8j+Q7uhL6IJCs/r1kxib1HV4bgOMg=
|
||||
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
|
||||
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
|
||||
8
email.go
8
email.go
@@ -19,6 +19,8 @@ import (
|
||||
textTemplate "text/template"
|
||||
"time"
|
||||
|
||||
sTemplate "github.com/hrfee/simple-template"
|
||||
|
||||
"github.com/gomarkdown/markdown"
|
||||
"github.com/gomarkdown/markdown/html"
|
||||
"github.com/hrfee/jfa-go/easyproxy"
|
||||
@@ -251,13 +253,13 @@ func (emailer *Emailer) construct(contentInfo CustomContentInfo, cc CustomConten
|
||||
Subject: contentInfo.Subject(emailer.config, &emailer.lang),
|
||||
}
|
||||
// Template the subject for bonus points
|
||||
if subject, err := templateEmail(msg.Subject, contentInfo.Variables, contentInfo.Conditionals, data); err == nil {
|
||||
if subject, err := sTemplate.Template(msg.Subject, data); err == nil {
|
||||
msg.Subject = subject
|
||||
}
|
||||
if cc.Enabled {
|
||||
// Use template email, rather than the built-in's email file.
|
||||
contentInfo.SourceFile = customContent["TemplateEmail"].SourceFile
|
||||
content, err := templateEmail(cc.Content, contentInfo.Variables, contentInfo.Conditionals, data)
|
||||
content, err := sTemplate.Template(cc.Content, data)
|
||||
if err != nil {
|
||||
emailer.err.Printf(lm.FailedConstructCustomContent, msg.Subject, err)
|
||||
return msg, err
|
||||
@@ -377,7 +379,7 @@ func (emailer *Emailer) constructConfirmation(code, username, key string, placeh
|
||||
return emailer.construct(contentInfo, cc, template)
|
||||
}
|
||||
|
||||
func (emailer *Emailer) constructInvite(invite Invite, placeholders bool) (*Message, error) {
|
||||
func (emailer *Emailer) constructInvite(invite *Invite, placeholders bool) (*Message, error) {
|
||||
expiry := invite.ValidTill
|
||||
d, t, expiresIn := emailer.formatExpiry(expiry, false)
|
||||
inviteLink := fmt.Sprintf("%s%s/%s", ExternalURI(nil), PAGES.Form, invite.Code)
|
||||
|
||||
@@ -180,7 +180,7 @@ func TestInvite(t *testing.T) {
|
||||
Created: time.Now(),
|
||||
ValidTill: time.Now().Add(30 * time.Minute),
|
||||
}
|
||||
msg, err := e.constructInvite(inv, false)
|
||||
msg, err := e.constructInvite(&inv, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed construct: %+v", err)
|
||||
}
|
||||
|
||||
137
go.mod
137
go.mod
@@ -1,8 +1,6 @@
|
||||
module github.com/hrfee/jfa-go
|
||||
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.24.0
|
||||
go 1.24.0
|
||||
|
||||
replace github.com/hrfee/jfa-go/docs => ./docs
|
||||
|
||||
@@ -22,124 +20,123 @@ replace github.com/hrfee/jfa-go/easyproxy => ./easyproxy
|
||||
|
||||
replace github.com/hrfee/jfa-go/jellyseerr => ./jellyseerr
|
||||
|
||||
// replace github.com/hrfee/mediabrowser => ../mediabrowser
|
||||
|
||||
require (
|
||||
github.com/bwmarrin/discordgo v0.29.0
|
||||
github.com/dgraph-io/badger/v4 v4.8.0
|
||||
github.com/emersion/go-autostart v0.0.0-20250403115856-34830d6457d2
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/getlantern/systray v1.2.2
|
||||
github.com/gin-contrib/pprof v1.5.3
|
||||
github.com/gin-gonic/gin v1.10.1
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
|
||||
github.com/goccy/go-yaml v1.18.0
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b
|
||||
github.com/hrfee/jfa-go/common v0.0.0-20250716174732-bcb6346f8115
|
||||
github.com/hrfee/jfa-go/docs v0.0.0-20250716174732-bcb6346f8115
|
||||
github.com/hrfee/jfa-go/easyproxy v0.0.0-20250716174732-bcb6346f8115
|
||||
github.com/hrfee/jfa-go/jellyseerr v0.0.0-20250716174732-bcb6346f8115
|
||||
github.com/hrfee/jfa-go/linecache v0.0.0-20250716174732-bcb6346f8115
|
||||
github.com/hrfee/jfa-go/logger v0.0.0-20250716174732-bcb6346f8115
|
||||
github.com/hrfee/jfa-go/logmessages v0.0.0-20250716174732-bcb6346f8115
|
||||
github.com/hrfee/jfa-go/ombi v0.0.0-20250716174732-bcb6346f8115
|
||||
github.com/hrfee/mediabrowser v0.3.29
|
||||
github.com/itchyny/timefmt-go v0.1.6
|
||||
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a
|
||||
github.com/hrfee/jfa-go/common v0.0.0-20251123165523-7c9f91711460
|
||||
github.com/hrfee/jfa-go/docs v0.0.0-20251123165523-7c9f91711460
|
||||
github.com/hrfee/jfa-go/easyproxy v0.0.0-20251123165523-7c9f91711460
|
||||
github.com/hrfee/jfa-go/jellyseerr v0.0.0-20251123165523-7c9f91711460
|
||||
github.com/hrfee/jfa-go/linecache v0.0.0-20251123165523-7c9f91711460
|
||||
github.com/hrfee/jfa-go/logger v0.0.0-20251123165523-7c9f91711460
|
||||
github.com/hrfee/jfa-go/logmessages v0.0.0-20251123165523-7c9f91711460
|
||||
github.com/hrfee/jfa-go/ombi v0.0.0-20251123165523-7c9f91711460
|
||||
github.com/hrfee/mediabrowser v0.3.36
|
||||
github.com/hrfee/simple-template v1.1.0
|
||||
github.com/itchyny/timefmt-go v0.1.7
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7
|
||||
github.com/lutischan-ferenc/systray v1.2.1
|
||||
github.com/mailgun/mailgun-go/v4 v4.23.0
|
||||
github.com/mattn/go-sqlite3 v1.14.28
|
||||
github.com/mattn/go-sqlite3 v1.14.32
|
||||
github.com/robert-nix/ansihtml v1.0.1
|
||||
github.com/steambap/captcha v1.4.1
|
||||
github.com/swaggo/files v1.0.1
|
||||
github.com/swaggo/gin-swagger v1.6.0
|
||||
github.com/swaggo/gin-swagger v1.6.1
|
||||
github.com/timshannon/badgerhold/v4 v4.0.3
|
||||
github.com/writeas/go-strip-markdown v2.0.1+incompatible
|
||||
github.com/xhit/go-simple-mail/v2 v2.16.0
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
maunium.net/go/mautrix v0.24.2
|
||||
maunium.net/go/mautrix v0.26.0
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/bytedance/sonic v1.13.3 // indirect
|
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic v1.14.2 // indirect
|
||||
github.com/bytedance/sonic/loader v0.4.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/dgraph-io/ristretto v1.0.0 // indirect
|
||||
github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/dgraph-io/ristretto/v2 v2.3.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||
github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201 // indirect
|
||||
github.com/getlantern/errors v1.0.4 // indirect
|
||||
github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65 // indirect
|
||||
github.com/getlantern/hex v0.0.0-20220104173244-ad7e4b9194dc // indirect
|
||||
github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770 // indirect
|
||||
github.com/getlantern/ops v0.0.0-20231025133620-f368ab734534 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.11 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-chi/chi/v5 v5.2.2 // indirect
|
||||
github.com/go-chi/chi/v5 v5.2.3 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.22.3 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.3 // indirect
|
||||
github.com/go-openapi/spec v0.22.1 // indirect
|
||||
github.com/go-openapi/swag/conv v0.25.3 // indirect
|
||||
github.com/go-openapi/swag/jsonname v0.25.3 // indirect
|
||||
github.com/go-openapi/swag/jsonutils v0.25.3 // indirect
|
||||
github.com/go-openapi/swag/loading v0.25.3 // indirect
|
||||
github.com/go-openapi/swag/stringutils v0.25.3 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.25.3 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.25.3 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||
github.com/go-stack/stack v1.8.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.28.0 // indirect
|
||||
github.com/go-test/deep v1.1.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/godbus/dbus/v5 v5.2.0 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/flatbuffers v25.2.10+incompatible // indirect
|
||||
github.com/google/flatbuffers v25.9.23+incompatible // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/compress v1.18.1 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b // indirect
|
||||
github.com/mailgun/errors v0.4.0 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20250508124226-395b08cebbdb // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20251121121749-a11dd1a45f9a // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.57.0 // indirect
|
||||
github.com/rs/zerolog v1.34.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/swaggo/swag v1.16.4 // indirect
|
||||
github.com/swaggo/swag v1.16.6 // indirect
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
|
||||
github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/match v1.2.0 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/toorop/go-dkim v0.0.0-20250226130143-9025cce95817 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||
go.mau.fi/util v0.8.8 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/arch v0.19.0 // indirect
|
||||
golang.org/x/crypto v0.40.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect
|
||||
golang.org/x/image v0.29.0 // indirect
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
golang.org/x/tools v0.35.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||
go.mau.fi/util v0.9.3 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/otel v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||
go.uber.org/mock v0.6.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/arch v0.23.0 // indirect
|
||||
golang.org/x/crypto v0.45.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 // indirect
|
||||
golang.org/x/image v0.33.0 // indirect
|
||||
golang.org/x/mod v0.30.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
golang.org/x/tools v0.39.0 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
)
|
||||
|
||||
394
go.sum
394
go.sum
@@ -11,20 +11,14 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4=
|
||||
github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
||||
github.com/bwmarrin/discordgo v0.29.0 h1:FmWeXFaKUwrcL3Cx65c20bTRW+vOb6k8AnaP+EgjDno=
|
||||
github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
||||
github.com/bytedance/sonic v1.12.4 h1:9Csb3c9ZJhfUWeMtpCDCq6BUoH5ogfDFLUgQ/jG+R0k=
|
||||
github.com/bytedance/sonic v1.12.4/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
||||
github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
|
||||
github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
|
||||
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||
github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE=
|
||||
github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980=
|
||||
github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o=
|
||||
github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
@@ -32,12 +26,8 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
@@ -49,23 +39,17 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgraph-io/badger/v4 v4.1.0/go.mod h1:P50u28d39ibBRmIJuQC/NSdBOg46HnHw7al2SW5QRHg=
|
||||
github.com/dgraph-io/badger/v4 v4.3.1 h1:7r5wKqmoRpGgSxqa0S/nGdpOpvvzuREGPLSua73C8tw=
|
||||
github.com/dgraph-io/badger/v4 v4.3.1/go.mod h1:oObz97DImXpd6O/Dt8BqdKLLTDmEmarAimo72VV5whQ=
|
||||
github.com/dgraph-io/badger/v4 v4.8.0 h1:JYph1ChBijCw8SLeybvPINizbDKWZ5n/GYbz2yhN/bs=
|
||||
github.com/dgraph-io/badger/v4 v4.8.0/go.mod h1:U6on6e8k/RTbUWxqKR0MvugJuVmkxSNc79ap4917h4w=
|
||||
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
|
||||
github.com/dgraph-io/ristretto v1.0.0 h1:SYG07bONKMlFDUYu5pEu3DGAh8c2OFNzKm6G9J4Si84=
|
||||
github.com/dgraph-io/ristretto v1.0.0/go.mod h1:jTi2FiYEhQ1NsMmA7DeBykizjOuY88NhKBkepyu1jPc=
|
||||
github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM=
|
||||
github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI=
|
||||
github.com/dgraph-io/ristretto/v2 v2.3.0 h1:qTQ38m7oIyd4GAed/QkUZyPFNMnvVWyazGXRwvOt5zk=
|
||||
github.com/dgraph-io/ristretto/v2 v2.3.0/go.mod h1:gpoRV3VzrEY1a9dWAYV6T1U7YzfgttXdd/ZzL1s9OZM=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
|
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:M88ob4TyDnEqNuL3PgsE/p3bDujfspnulR+0dQWNYZs=
|
||||
github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:buzQsO8HHkZX2Q45fdfGH1xejPjuDQaXH8btcYMFzPM=
|
||||
github.com/emersion/go-autostart v0.0.0-20250403115856-34830d6457d2 h1:CgF8+TNFvlnxEbplSgS70ZI4IUFEzVkY+ICNqTVE/AM=
|
||||
github.com/emersion/go-autostart v0.0.0-20250403115856-34830d6457d2/go.mod h1:buzQsO8HHkZX2Q45fdfGH1xejPjuDQaXH8btcYMFzPM=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
@@ -76,64 +60,28 @@ github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGE
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
|
||||
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
|
||||
github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201 h1:oEZYEpZo28Wdx+5FZo4aU7JFXu0WG/4wJWese5reQSA=
|
||||
github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201/go.mod h1:Y9WZUHEb+mpra02CbQ/QczLUe6f0Dezxaw5DCJlJQGo=
|
||||
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
|
||||
github.com/getlantern/errors v1.0.1/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
|
||||
github.com/getlantern/errors v1.0.4 h1:i2iR1M9GKj4WuingpNqJ+XQEw6i6dnAgKAmLj6ZB3X0=
|
||||
github.com/getlantern/errors v1.0.4/go.mod h1:/Foq8jtSDGP8GOXzAjeslsC4Ar/3kB+UiQH+WyV4pzY=
|
||||
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7/go.mod h1:zx/1xUUeYPy3Pcmet8OSXLbF47l+3y6hIPpyLWoR9oc=
|
||||
github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65 h1:NlQedYmPI3pRAXJb+hLVVDGqfvvXGRPV8vp7XOjKAZ0=
|
||||
github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65/go.mod h1:+ZU1h+iOVqWReBpky6d5Y2WL0sF2Llxu+QcxJFs2+OU=
|
||||
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o=
|
||||
github.com/getlantern/hex v0.0.0-20220104173244-ad7e4b9194dc h1:sue+aeVx7JF5v36H1HfvcGFImLpSD5goj8d+MitovDU=
|
||||
github.com/getlantern/hex v0.0.0-20220104173244-ad7e4b9194dc/go.mod h1:D9RWpXy/EFPYxiKUURo2TB8UBosbqkiLhttRrZYtvqM=
|
||||
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
|
||||
github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770 h1:cSrD9ryDfTV2yaur9Qk3rHYD414j3Q1rl7+L0AylxrE=
|
||||
github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770/go.mod h1:GOQsoDnEHl6ZmNIL+5uVo+JWRFWozMEp18Izcb++H+A=
|
||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
|
||||
github.com/getlantern/ops v0.0.0-20220713155959-1315d978fff7/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
|
||||
github.com/getlantern/ops v0.0.0-20231025133620-f368ab734534 h1:3BwvWj0JZzFEvNNiMhCu4bf60nqcIuQpTYb00Ezm1ag=
|
||||
github.com/getlantern/ops v0.0.0-20231025133620-f368ab734534/go.mod h1:ZsLfOY6gKQOTyEcPYNA9ws5/XHZQFroxqCOhHjGcs9Y=
|
||||
github.com/getlantern/systray v1.2.2 h1:dCEHtfmvkJG7HZ8lS/sLklTH4RKUcIsKrAD9sThoEBE=
|
||||
github.com/getlantern/systray v1.2.2/go.mod h1:pXFOI1wwqwYXEhLPm9ZGjS2u/vVELeIgNMY5HvhHhcE=
|
||||
github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik=
|
||||
github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w=
|
||||
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
||||
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
||||
github.com/gin-contrib/pprof v1.5.0 h1:E/Oy7g+kNw94KfdCy3bZxQFtyDnAX2V7axRS7sNYVrU=
|
||||
github.com/gin-contrib/pprof v1.5.0/go.mod h1:GqFL6LerKoCQ/RSWnkYczkTJ+tOAUVN/8sbnEtaqOKs=
|
||||
github.com/gin-contrib/pprof v1.5.3 h1:Bj5SxJ3kQDVez/s/+f9+meedJIqLS+xlkIVDe/lcvgM=
|
||||
github.com/gin-contrib/pprof v1.5.3/go.mod h1:0+LQSZ4SLO0B6+2n6JBzaEygpTBxe/nI+YEYpfQQ6xY=
|
||||
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
|
||||
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
||||
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
@@ -141,50 +89,61 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
|
||||
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
||||
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
|
||||
github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
|
||||
github.com/go-openapi/jsonpointer v0.22.3 h1:dKMwfV4fmt6Ah90zloTbUKWMD+0he+12XYAsPotrkn8=
|
||||
github.com/go-openapi/jsonpointer v0.22.3/go.mod h1:0lBbqeRsQ5lIanv3LHZBrmRGHLHcQoOXQnf88fHlGWo=
|
||||
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
|
||||
github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
|
||||
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
|
||||
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/jsonreference v0.21.3 h1:96Dn+MRPa0nYAR8DR1E03SblB5FJvh7W6krPI0Z7qMc=
|
||||
github.com/go-openapi/jsonreference v0.21.3/go.mod h1:RqkUP0MrLf37HqxZxrIAtTWW4ZJIK1VzduhXYBEeGc4=
|
||||
github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||
github.com/go-openapi/spec v0.22.1 h1:beZMa5AVQzRspNjvhe5aG1/XyBSMeX1eEOs7dMoXh/k=
|
||||
github.com/go-openapi/spec v0.22.1/go.mod h1:c7aeIQT175dVowfp7FeCvXXnjN/MrpaONStibD2WtDA=
|
||||
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
|
||||
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
|
||||
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||
github.com/go-openapi/swag/conv v0.25.3 h1:PcB18wwfba7MN5BVlBIV+VxvUUeC2kEuCEyJ2/t2X7E=
|
||||
github.com/go-openapi/swag/conv v0.25.3/go.mod h1:n4Ibfwhn8NJnPXNRhBO5Cqb9ez7alBR40JS4rbASUPU=
|
||||
github.com/go-openapi/swag/jsonname v0.25.3 h1:U20VKDS74HiPaLV7UZkztpyVOw3JNVsit+w+gTXRj0A=
|
||||
github.com/go-openapi/swag/jsonname v0.25.3/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag=
|
||||
github.com/go-openapi/swag/jsonutils v0.25.3 h1:kV7wer79KXUM4Ea4tBdAVTU842Rg6tWstX3QbM4fGdw=
|
||||
github.com/go-openapi/swag/jsonutils v0.25.3/go.mod h1:ILcKqe4HC1VEZmJx51cVuZQ6MF8QvdfXsQfiaCs0z9o=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.3 h1:/i3E9hBujtXfHy91rjtwJ7Fgv5TuDHgnSrYjhFxwxOw=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.3/go.mod h1:8kYfCR2rHyOj25HVvxL5Nm8wkfzggddgjZm6RgjT8Ao=
|
||||
github.com/go-openapi/swag/loading v0.25.3 h1:Nn65Zlzf4854MY6Ft0JdNrtnHh2bdcS/tXckpSnOb2Y=
|
||||
github.com/go-openapi/swag/loading v0.25.3/go.mod h1:xajJ5P4Ang+cwM5gKFrHBgkEDWfLcsAKepIuzTmOb/c=
|
||||
github.com/go-openapi/swag/stringutils v0.25.3 h1:nAmWq1fUTWl/XiaEPwALjp/8BPZJun70iDHRNq/sH6w=
|
||||
github.com/go-openapi/swag/stringutils v0.25.3/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0=
|
||||
github.com/go-openapi/swag/typeutils v0.25.3 h1:2w4mEEo7DQt3V4veWMZw0yTPQibiL3ri2fdDV4t2TQc=
|
||||
github.com/go-openapi/swag/typeutils v0.25.3/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.3 h1:LKTJjCn/W1ZfMec0XDL4Vxh8kyAnv1orH5F2OREDUrg=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.3/go.mod h1:Y7QN6Wc5DOBXK14/xeo1cQlq0EA0wvLoSv13gDQoCao=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg=
|
||||
github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
|
||||
github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
|
||||
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
|
||||
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
|
||||
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
|
||||
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
|
||||
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
|
||||
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/godbus/dbus/v5 v5.2.0 h1:3WexO+U+yg9T70v9FdHr9kCxYlazaAXUhx2VMkbfax8=
|
||||
github.com/godbus/dbus/v5 v5.2.0/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
@@ -194,10 +153,7 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
|
||||
github.com/golang/glog v1.1.1/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@@ -211,20 +167,14 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gomarkdown/markdown v0.0.0-20241105142532-d03b89096d81 h1:5lyLWsV+qCkoYqsKUDuycESh9DEIPVKN6iCFeL7ag50=
|
||||
github.com/gomarkdown/markdown v0.0.0-20241105142532-d03b89096d81/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b h1:EY/KpStFl60qA17CptGXhwfZ+k1sFNJIUNR8DdbcuUk=
|
||||
github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a h1:l7A0loSszR5zHd/qK53ZIHMO8b3bBSmENnQ6eKnUT0A=
|
||||
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/flatbuffers v23.5.9+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI=
|
||||
github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q=
|
||||
github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/flatbuffers v25.9.23+incompatible h1:rGZKv+wOb6QPzIdkM2KxhBZCDrA0DeN6DNmRDrqIsQU=
|
||||
github.com/google/flatbuffers v25.9.23+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
@@ -233,9 +183,8 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@@ -245,15 +194,19 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hrfee/mediabrowser v0.3.28 h1:KkSgODXxUnZLrkmjSWpma8mXwEVxlOtI51uS2QP/e+c=
|
||||
github.com/hrfee/mediabrowser v0.3.28/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U=
|
||||
github.com/hrfee/mediabrowser v0.3.29 h1:xTqGS9u8HuolZAhouYHxutnE0fF/8aVCInbByKZEzIo=
|
||||
github.com/hrfee/mediabrowser v0.3.29/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U=
|
||||
github.com/hrfee/mediabrowser v0.3.33 h1:kjUFZc46hNhbOEU4xZNyhGVNjfZ5lENmX95Md1thxiA=
|
||||
github.com/hrfee/mediabrowser v0.3.33/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U=
|
||||
github.com/hrfee/mediabrowser v0.3.34 h1:AKnd1V9wt+KWZmHDjj1GMkCgcgcpBKxPw5iUcYgD6Tg=
|
||||
github.com/hrfee/mediabrowser v0.3.34/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U=
|
||||
github.com/hrfee/mediabrowser v0.3.35 h1:xEq4cL96Di0G+S3ONBH1HHeQJU6IfUMZiaeGeuJSFS8=
|
||||
github.com/hrfee/mediabrowser v0.3.35/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U=
|
||||
github.com/hrfee/mediabrowser v0.3.36 h1:erYWzmaz4b6wfxEfeEWQHLMI9fSpGDy1m7NxkQmIVsw=
|
||||
github.com/hrfee/mediabrowser v0.3.36/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U=
|
||||
github.com/hrfee/simple-template v1.1.0 h1:PNQDTgc2H0s19/pWuhRh4bncuNJjPrW0fIX77YtY78M=
|
||||
github.com/hrfee/simple-template v1.1.0/go.mod h1:s9a5QgfqbmT7j9WCC3GD5JuEqvihBEohyr+oYZmr4bA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q=
|
||||
github.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/itchyny/timefmt-go v0.1.7 h1:xyftit9Tbw+Dc/huSSPJaEmX1TVL8lw5vxjJLK4GMMA=
|
||||
github.com/itchyny/timefmt-go v0.1.7/go.mod h1:5E46Q+zj7vbTgWY8o5YkMeYb4I6GeWLFnetPy5oBrAI=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
@@ -262,16 +215,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
|
||||
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
@@ -284,26 +231,19 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts=
|
||||
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
||||
github.com/lutischan-ferenc/systray v1.2.1 h1:gPNrEpmg4hMwXyKNSlrkuuXqvxgqCYPjF5H/pG9I1+c=
|
||||
github.com/lutischan-ferenc/systray v1.2.1/go.mod h1:YYaJ28AVuhMrlI5JfqrMsYMIl3Aa4Q02bpXXCl9caqo=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b h1:xZ59n7Frzh8CwyfAapUZLSg+gXH5m63YEaFCMpDHhpI=
|
||||
github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b/go.mod h1:uDd4sYVYsqcxAB8j+Q7uhL6IJCs/r1kxib1HV4bgOMg=
|
||||
github.com/mailgun/errors v0.4.0 h1:6LFBvod6VIW83CMIOT9sYNp28TCX0NejFPP4dSX++i8=
|
||||
github.com/mailgun/errors v0.4.0/go.mod h1:xGBaaKdEdQT0/FhwvoXv4oBaqqmVZz9P1XEnvD/onc0=
|
||||
github.com/mailgun/mailgun-go/v4 v4.18.1 h1:ShNH/wzj7albTF/6le011FF+DGMd3azcSKL4iO9AgeI=
|
||||
github.com/mailgun/mailgun-go/v4 v4.18.1/go.mod h1:+d4FCswFAukgYc1XtKK2IxOYaVxjVm8AN2z/5TBiT8M=
|
||||
github.com/mailgun/mailgun-go/v4 v4.23.0 h1:jPEMJzzin2s7lvehcfv/0UkyBu18GvcURPr2+xtZRbk=
|
||||
github.com/mailgun/mailgun-go/v4 v4.23.0/go.mod h1:imTtizoFtpfZqPqGP8vltVBB6q9yWcv6llBhfFeElZU=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
@@ -315,10 +255,8 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
|
||||
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
||||
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -327,31 +265,25 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/petermattis/goid v0.0.0-20241025130422-66cb2e6d7274 h1:qli3BGQK0tYDkSEvZ/FzZTi9ZrOX86Q6CIhKLGc489A=
|
||||
github.com/petermattis/goid v0.0.0-20241025130422-66cb2e6d7274/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||
github.com/petermattis/goid v0.0.0-20250508124226-395b08cebbdb h1:3PrKuO92dUTMrQ9dx0YNejC6U/Si6jqKmyQ9vWjwqR4=
|
||||
github.com/petermattis/goid v0.0.0-20250508124226-395b08cebbdb/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||
github.com/petermattis/goid v0.0.0-20251121121749-a11dd1a45f9a h1:VweslR2akb/ARhXfqSfRbj1vpWwYXf3eeAUyw/ndms0=
|
||||
github.com/petermattis/goid v0.0.0-20251121121749-a11dd1a45f9a/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||
github.com/quic-go/quic-go v0.57.0 h1:AsSSrrMs4qI/hLrKlTH/TGQeTMY0ib1pAOX7vA3AdqE=
|
||||
github.com/quic-go/quic-go v0.57.0/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s=
|
||||
github.com/robert-nix/ansihtml v1.0.1 h1:VTiyQ6/+AxSJoSSLsMecnkh8i0ZqOEdiRl/odOc64fc=
|
||||
github.com/robert-nix/ansihtml v1.0.1/go.mod h1:CJwclxYaTPc2RfcxtanEACsYuTksh4yDXcNeHHKZINE=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
@@ -360,7 +292,6 @@ github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdh
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
@@ -373,6 +304,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
@@ -381,25 +313,30 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E=
|
||||
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
|
||||
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
|
||||
github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI=
|
||||
github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M=
|
||||
github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
|
||||
github.com/swaggo/gin-swagger v1.6.1 h1:Ri06G4gc9N4t4k8hekMigJ9zKTFSlqj/9paAQCQs7cY=
|
||||
github.com/swaggo/gin-swagger v1.6.1/go.mod h1:LQ+hJStHakCWRiK/YNYtJOu4mR2FP+pxLnILT/qNiTw=
|
||||
github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y=
|
||||
github.com/swaggo/swag v1.6.7/go.mod h1:xDhTyuFIujYiN3DKWC/H/83xcfHp+UE/IzWWampG7Zc=
|
||||
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
|
||||
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
|
||||
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
|
||||
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
|
||||
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
|
||||
github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c h1:coVla7zpsycc+kA9NXpcvv2E4I7+ii6L5hZO2S6C3kw=
|
||||
github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
|
||||
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
@@ -408,8 +345,6 @@ github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6
|
||||
github.com/timshannon/badgerhold/v4 v4.0.3 h1:W6pd2qckoXw2cl8eH0ZCV/9CXNaXvaM26tzFi5Tj+v8=
|
||||
github.com/timshannon/badgerhold/v4 v4.0.3/go.mod h1:IkZIr0kcZLMdD7YJfW/G6epb6ZXHD/h0XR2BTk/VZg8=
|
||||
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns=
|
||||
github.com/toorop/go-dkim v0.0.0-20240103092955-90b7d1423f92 h1:flbMkdl6HxQkLs6DDhH1UkcnFpNBOu70391STjMS0O4=
|
||||
github.com/toorop/go-dkim v0.0.0-20240103092955-90b7d1423f92/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns=
|
||||
github.com/toorop/go-dkim v0.0.0-20250226130143-9025cce95817 h1:q0hKh5a5FRkhuTb5JNfgjzpzvYLHjH0QOgPZPYnRWGA=
|
||||
github.com/toorop/go-dkim v0.0.0-20250226130143-9025cce95817/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
@@ -419,10 +354,8 @@ github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/o
|
||||
github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
||||
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
||||
github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6FkrCNfXkvbR+Nbu1ls48pXYcw=
|
||||
@@ -432,45 +365,25 @@ github.com/xhit/go-simple-mail/v2 v2.16.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4te
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.mau.fi/util v0.8.1 h1:Ga43cz6esQBYqcjZ/onRoVnYWoUwjWbsxVeJg2jOTSo=
|
||||
go.mau.fi/util v0.8.1/go.mod h1:T1u/rD2rzidVrBLyaUdPpZiJdP/rsyi+aTzn0D+Q6wc=
|
||||
go.mau.fi/util v0.8.8 h1:OnuEEc/sIJFhnq4kFggiImUpcmnmL/xpvQMRu5Fiy5c=
|
||||
go.mau.fi/util v0.8.8/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c=
|
||||
go.mau.fi/util v0.9.3 h1:aqNF8KDIN8bFpFbybSk+mEBil7IHeBwlujfyTnvP0uU=
|
||||
go.mau.fi/util v0.9.3/go.mod h1:krWWfBM1jWTb5f8NCa2TLqWMQuM81X7TGQjhMjBeXmQ=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.9.0/go.mod h1:np4EoPGzoPs3O67xUVNoPPcmSvsfOxNlNA4F4AC+0Eo=
|
||||
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
|
||||
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
|
||||
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/trace v1.9.0/go.mod h1:2737Q0MuG8q1uILYm2YYVkAyLtOofiTNGg6VODnOiPo=
|
||||
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
|
||||
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4=
|
||||
golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/arch v0.19.0 h1:LmbDQUodHThXE+htjrnmVD73M//D9GTH6wFZjyDkjyU=
|
||||
golang.org/x/arch v0.19.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg=
|
||||
golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@@ -478,32 +391,23 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
|
||||
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc h1:TS73t7x3KarrNd5qAipmspBDS1rkMcgVG/fS1aRb4Rc=
|
||||
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
|
||||
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0=
|
||||
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
|
||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||
golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
|
||||
golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78=
|
||||
golang.org/x/image v0.29.0 h1:HcdsyR4Gsuys/Axh0rDEmlBmB68rW1U9BUdB3UVHsas=
|
||||
golang.org/x/image v0.29.0/go.mod h1:RVJROnf3SLK8d26OW91j4FrIHGbsJ8QnbEocVTOWQDA=
|
||||
golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ=
|
||||
golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
||||
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -520,15 +424,12 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
|
||||
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -537,12 +438,10 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -555,25 +454,19 @@ golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@@ -585,10 +478,10 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
@@ -600,13 +493,10 @@ golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgw
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
|
||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
||||
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -635,11 +525,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -651,15 +538,10 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
maunium.net/go/mautrix v0.21.1 h1:Z+e448jtlY977iC1kokNJTH5kg2WmDpcQCqn+v9oZOA=
|
||||
maunium.net/go/mautrix v0.21.1/go.mod h1:7F/S6XAdyc/6DW+Q7xyFXRSPb6IjfqMb1OMepQ8C8OE=
|
||||
maunium.net/go/mautrix v0.24.2 h1:+AVT5kbcA/QuT5svrJKp4ivwoUmz+RRplMp3DnfpheI=
|
||||
maunium.net/go/mautrix v0.24.2/go.mod h1:1ut900w++eE9by9yqCR2dQdMqwsHwZG5L+1bKB1EvSA=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
maunium.net/go/mautrix v0.26.0 h1:valc2VmZF+oIY4bMq4Cd5H9cEKMRe8eP4FM7iiaYLxI=
|
||||
maunium.net/go/mautrix v0.26.0/go.mod h1:NWMv+243NX/gDrLofJ2nNXJPrG8vzoM+WUCWph85S6Q=
|
||||
|
||||
@@ -31,6 +31,7 @@ func (app *appContext) clearEmails() {
|
||||
func (app *appContext) clearDiscord() {
|
||||
app.debug.Println(lm.HousekeepingDiscord)
|
||||
discordUsers := app.storage.GetDiscord()
|
||||
removeRoleOnDisable := app.config.Section("discord").Key("disable_enable_role").MustBool(false)
|
||||
for _, discordUser := range discordUsers {
|
||||
user, err := app.jf.UserByID(discordUser.JellyfinID, false)
|
||||
// Make sure the user doesn't exist, and no other error has occured
|
||||
@@ -40,7 +41,7 @@ func (app *appContext) clearDiscord() {
|
||||
app.discord.RemoveRole(discordUser.MethodID().(string))
|
||||
app.storage.DeleteDiscordKey(discordUser.JellyfinID)
|
||||
default:
|
||||
if user.Policy.IsDisabled {
|
||||
if removeRoleOnDisable && user.Policy.IsDisabled {
|
||||
app.discord.RemoveRole(discordUser.MethodID().(string))
|
||||
}
|
||||
continue
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="{{ .cssClass }}">
|
||||
<html lang="{{ .shortLang }}" dir="{{ .pageDirection }}" class="{{ .cssClass }}">
|
||||
<head>
|
||||
<title>404 - jfa-go</title>
|
||||
{{ template "header.html" . }}
|
||||
{{ template "header.txt" . }}
|
||||
</head>
|
||||
<body class="section">
|
||||
<div class="page-container m-2 lg:my-20 lg:mx-64">
|
||||
|
||||
16
html/account-linking-discord.html
Normal file
16
html/account-linking-discord.html
Normal file
@@ -0,0 +1,16 @@
|
||||
{{ if .discordEnabled }}
|
||||
<div id="modal-discord" class="modal">
|
||||
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3 flex flex-col gap-4">
|
||||
<span class="heading">{{ .strings.linkDiscord }}</span>
|
||||
<p class="content"> {{ .discordSendPINMessage }}</p>
|
||||
<h1 class="text-center text-2xl pin"></h1>
|
||||
<div class="flex flex-row gap-2 justify-center items-center">
|
||||
<a class="hover:underline flex flex-row gap-4 items-center">
|
||||
<span>{{ .strings.joinTheServer }}</span>
|
||||
<span id="discord-invite" class="flex flex-row gap-2 items-center"></span>
|
||||
</a>
|
||||
</div>
|
||||
<span class="button ~info @low full-width center" id="discord-waiting">{{ .strings.success }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
18
html/account-linking-matrix.html
Normal file
18
html/account-linking-matrix.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{{ if .matrixEnabled }}
|
||||
<div id="modal-matrix" class="modal">
|
||||
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3 flex flex-col gap-4">
|
||||
<span class="heading">{{ .strings.linkMatrix }}</span>
|
||||
<p class="content"> {{ .strings.matrixEnterUser }}</p>
|
||||
<input type="text" class="input ~neutral @high" placeholder="@user:riot.im" id="matrix-userid">
|
||||
<div class="subheading flex flex-row gap-2 justify-center items-center">
|
||||
<span class="shield ~info">
|
||||
<span class="icon">
|
||||
<i class="ri-chat-3-line"></i>
|
||||
</span>
|
||||
</span>
|
||||
<span>{{ .matrixUser }}</span>
|
||||
</div>
|
||||
<span class="button ~info @low full-width center" id="matrix-send">{{ .strings.submit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
18
html/account-linking-telegram.html
Normal file
18
html/account-linking-telegram.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{{ if .telegramEnabled }}
|
||||
<div id="modal-telegram" class="modal">
|
||||
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3 flex flex-col gap-4">
|
||||
<span class="heading">{{ .strings.linkTelegram }}</span>
|
||||
<p class="content">{{ .strings.sendPIN }}</p>
|
||||
<p class="text-center text-2xl pin"></p>
|
||||
<a class="subheading link flex flex-row gap-2 justify-center items-center" href="{{ .telegramURL }}" target="_blank">
|
||||
<span class="shield ~info">
|
||||
<span class="icon">
|
||||
<i class="ri-telegram-line"></i>
|
||||
</span>
|
||||
</span>
|
||||
<span class="hover:underline">@<span class="username">{{ .telegramUsername }}</span></span>
|
||||
</a>
|
||||
<span class="button ~info @low full-width center" id="telegram-waiting">{{ .strings.success }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
@@ -1,52 +1,3 @@
|
||||
{{ if .discordEnabled }}
|
||||
<div id="modal-discord" class="modal">
|
||||
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3">
|
||||
<span class="heading mb-4">{{ .strings.linkDiscord }}</span>
|
||||
<p class="content mb-4"> {{ .discordSendPINMessage }}</p>
|
||||
<h1 class="text-center text-2xl mb-2 pin"></h1>
|
||||
<div class="row center">
|
||||
<a class="my-5 hover:underline">
|
||||
<span class="mr-2">{{ .strings.joinTheServer }}</span>
|
||||
<span id="discord-invite"></span>
|
||||
</a>
|
||||
</div>
|
||||
<span class="button ~info @low full-width center mt-4" id="discord-waiting">{{ .strings.success }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ if .telegramEnabled }}
|
||||
<div id="modal-telegram" class="modal">
|
||||
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3">
|
||||
<span class="heading mb-4">{{ .strings.linkTelegram }}</span>
|
||||
<p class="content mb-4">{{ .strings.sendPIN }}</p>
|
||||
<p class="text-center text-2xl mb-2 pin"></p>
|
||||
<a class="subheading link-center" href="{{ .telegramURL }}" target="_blank">
|
||||
<span class="shield ~info mr-4">
|
||||
<span class="icon">
|
||||
<i class="ri-telegram-line"></i>
|
||||
</span>
|
||||
</span>
|
||||
@{{ .telegramUsername }}
|
||||
</a>
|
||||
<span class="button ~info @low full-width center mt-4" id="telegram-waiting">{{ .strings.success }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ if .matrixEnabled }}
|
||||
<div id="modal-matrix" class="modal">
|
||||
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3">
|
||||
<span class="heading mb-4">{{ .strings.linkMatrix }}</span>
|
||||
<p class="content mb-4"> {{ .strings.matrixEnterUser }}</p>
|
||||
<input type="text" class="input ~neutral @high" placeholder="@user:riot.im" id="matrix-userid">
|
||||
<div class="subheading link-center mt-4">
|
||||
<span class="shield ~info mr-4">
|
||||
<span class="icon">
|
||||
<i class="ri-chat-3-line"></i>
|
||||
</span>
|
||||
</span>
|
||||
{{ .matrixUser }}
|
||||
</div>
|
||||
<span class="button ~info @low full-width center mt-4" id="matrix-send">{{ .strings.submit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ template "account-linking-discord.html" . }}
|
||||
{{ template "account-linking-telegram.html" . }}
|
||||
{{ template "account-linking-matrix.html" . }}
|
||||
|
||||
24
html/accounts-table-header.txt
Normal file
24
html/accounts-table-header.txt
Normal file
@@ -0,0 +1,24 @@
|
||||
<thead id="accounts-table-header">
|
||||
<tr>
|
||||
<th><input type="checkbox" value="" id="accounts-select-all"></th>
|
||||
<th class="table-inline my-2 grid gap-4 place-items-stretch accounts-header-username">{{ .strings.username }}</th>
|
||||
{{ if .jellyfinLogin }}
|
||||
<th class="text-center-i grid gap-4 place-items-stretch accounts-header-access-jfa">{{ .strings.accessJFA }}</th>
|
||||
{{ end }}
|
||||
<th class="grid gap-4 place-items-stretch accounts-header-email">{{ .strings.emailAddress }}</th>
|
||||
{{ if .telegramEnabled }}
|
||||
<th class="text-center-i grid gap-4 place-items-stretch accounts-header-telegram">Telegram</th>
|
||||
{{ end }}
|
||||
{{ if .matrixEnabled }}
|
||||
<th class="text-center-i grid gap-4 place-items-stretch accounts-header-matrix">Matrix</th>
|
||||
{{ end }}
|
||||
{{ if .discordEnabled }}
|
||||
<th class="text-center-i grid gap-4 place-items-stretch accounts-header-discord">Discord</th>
|
||||
{{ end }}
|
||||
{{ if .referralsEnabled }}
|
||||
<th class="text-center-i grid gap-4 place-items-stretch accounts-header-referrals">{{ .strings.referrals }}</th>
|
||||
{{ end }}
|
||||
<th class="grid gap-4 place-items-stretch accounts-header-expiry">{{ .strings.expiry }}</th>
|
||||
<th class="grid gap-4 place-items-stretch accounts-header-last-active">{{ .strings.lastActiveTime }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
573
html/admin.html
573
html/admin.html
@@ -1,6 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="{{ .cssClass }}">
|
||||
<html lang="{{ .shortLang }}" dir="{{ .pageDirection }}" class="{{ .cssClass }}">
|
||||
<head>
|
||||
{{ template "syntaxhighlighting.txt" . }}
|
||||
<title>Admin - jfa-go</title>
|
||||
{{ template "header.txt" . }}
|
||||
<script>
|
||||
window.usernameEnabled = {{ .username }};
|
||||
window.langFile = JSON.parse({{ .language }});
|
||||
@@ -11,10 +14,8 @@
|
||||
window.jfAllowAll = {{ .jfAllowAll }};
|
||||
window.loginAppearance = "{{ .loginAppearance }}";
|
||||
</script>
|
||||
<title>Admin - jfa-go</title>
|
||||
{{ template "header.html" . }}
|
||||
</head>
|
||||
<body class="max-w-full overflow-x-hidden section">
|
||||
<body class="max-w-full section"><div class="overflow-x-hidden relative"><!-- for whatever reason position:relative stops hidden x overflow on ios and samsung web -->
|
||||
{{ template "login-modal.html" . }}
|
||||
<div id="modal-add-user" class="modal">
|
||||
<form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3 flex flex-col gap-2" id="form-add-user" href="">
|
||||
@@ -44,50 +45,48 @@
|
||||
<p>{{ .strings.buildTime }} <span class="text-black dark:text-white font-mono bg-inherit">{{ .buildTime }}</span></p>
|
||||
<p>{{ .strings.builtBy }} <span class="text-black dark:text-white font-mono bg-inherit">{{ .builtBy }}</span></p>
|
||||
<p>{{ .strings.buildTags }} <span class="text-black dark:text-white font-mono bg-inherit">{{ .buildTags }}</span></p>
|
||||
<div class="flex flex-row flex-wrap gap-2 my-2">
|
||||
<a class="button ~neutral lang-link" href="https://github.com/hrfee/jfa-go"><i class="ri-github-line mr-2"></i>github</a>
|
||||
<div class="flex flex-row flex-wrap gap-2">
|
||||
<a class="button ~neutral lang-link flex flex-row gap-2" href="https://github.com/hrfee/jfa-go"><i class="ri-github-line"></i>github</a>
|
||||
<a class="button ~urge lang-link" href="https://wiki.jfa-go.com">wiki/docs</a>
|
||||
<a class="button ~positive lang-link" href="https://weblate.jfa-go.com">translation</a>
|
||||
<div class="dropdown" tabindex="0">
|
||||
<a href="https://github.com/sponsors/hrfee" target="_blank" class="button ~info dropdown-button lang-link">
|
||||
<i class="ri-hand-heart-line mr-2"></i>
|
||||
<a href="https://github.com/sponsors/hrfee" target="_blank" class="button ~info dropdown-button lang-link flex flex-row gap-2">
|
||||
<i class="ri-hand-heart-line"></i>
|
||||
donate
|
||||
<span class="ml-2 chev"></span>
|
||||
<span class="chev"></span>
|
||||
</a>
|
||||
<div class="dropdown-display">
|
||||
<div class="card ~neutral @low">
|
||||
<a href="https://github.com/sponsors/hrfee" target="_blank" class="button ~neutral mb-2 w-full lang-link">GitHub</a>
|
||||
<a href="https://ko-fi.com/hrfee" target="_blank" class="button ~neutral mb-2 w-full lang-link">Ko-fi</a>
|
||||
<div class="card ~neutral @low flex flex-col gap-2">
|
||||
<a href="https://github.com/sponsors/hrfee" target="_blank" class="button ~neutral w-full lang-link">GitHub</a>
|
||||
<a href="https://ko-fi.com/hrfee" target="_blank" class="button ~neutral w-full lang-link">Ko-fi</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a class="button ~urge @low discord lang-link" href="https://discord.com/invite/MrtvuQmyhP" target="_blank"><i class="ri-discord-line mr-2"></i>discord</a>
|
||||
<a class="button ~urge @low discord lang-link flex flex-row gap-2" href="https://discord.com/invite/MrtvuQmyhP" target="_blank"><i class="ri-discord-line"></i>discord</a>
|
||||
</div>
|
||||
<p><a href="https://github.com/hrfee/jfa-go/blob/main/LICENSE">Available under the MIT License. Font "Hanken Grotesk" available under SIL OFL 1.1 License.</a></p>
|
||||
<pre class="font-mono bg-inherit">{{ .license }}</pre>
|
||||
<pre class="font-mono bg-inherit force-ltr">{{ .license }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div id="modal-logs" class="modal">
|
||||
<div class="relative mx-auto my-[10%] w-4/5 lg:w-2/3 content content card">
|
||||
<div class="relative mx-auto my-[10%] w-4/5 lg:w-2/3 content card">
|
||||
<span class="heading">{{ .strings.logs }}<span class="modal-close">×</span></span>
|
||||
<pre class="monospace" id="log-area"></pre>
|
||||
<pre class="monospace force-ltr" id="log-area"></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div id="modal-tasks" class="modal">
|
||||
<div class="relative mx-auto my-[10%] w-min card flex flex-col gap-2">
|
||||
<h1 class="heading">{{ .strings.tasks }}<span class="modal-close">×</span></h1>
|
||||
<p class="content">{{ .strings.tasksDescription }}</p>
|
||||
<div id="modal-tasks-list" class="flex flex-col gap-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="modal-modify-user" class="modal">
|
||||
<form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3" id="form-modify-user" href="">
|
||||
<form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3 flex flex-col gap-2" id="form-modify-user" href="">
|
||||
<span class="heading"><span id="header-modify-user"></span> <span class="modal-close">×</span></span>
|
||||
<p class="content my-4">{{ .strings.modifySettingsDescription }}</p>
|
||||
<div class="flex flex-col gap-4 my-2">
|
||||
<div class="flex flex-row gap-2">
|
||||
<label class="grow">
|
||||
<input type="radio" name="modify-user-source" class="unfocused" id="radio-use-profile" checked>
|
||||
<span class="button ~neutral @high supra full-width center">{{ .strings.profile }}</span>
|
||||
</label>
|
||||
<label class="grow">
|
||||
<input type="radio" name="modify-user-source" class="unfocused" id="radio-use-user">
|
||||
<span class="button ~neutral @low supra full-width center">{{ .strings.user }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<p class="content">{{ .strings.modifySettingsDescription }}</p>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div id="modify-user-profile-source"></div>
|
||||
<div class="select ~neutral @low">
|
||||
<select id="modify-user-profiles"></select>
|
||||
</div>
|
||||
@@ -102,14 +101,18 @@
|
||||
<input type="checkbox" id="modify-user-homescreen" checked>
|
||||
<span>{{ .strings.applyHomescreenLayout }}</span>
|
||||
</label>
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="modify-user-ombi" checked>
|
||||
<span>{{ .strings.applyOmbi }}</span>
|
||||
</label>
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="modify-user-jellyseerr" checked>
|
||||
<span>{{ .strings.applyJellyseerr }}</span>
|
||||
</label>
|
||||
{{ if .ombiEnabled }}
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="modify-user-ombi" checked>
|
||||
<span>{{ .strings.applyOmbi }}</span>
|
||||
</label>
|
||||
{{ end }}
|
||||
{{ if .jellyseerrEnabled }}
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="modify-user-jellyseerr" checked>
|
||||
<span>{{ .strings.applyJellyseerr }}</span>
|
||||
</label>
|
||||
{{ end }}
|
||||
<label>
|
||||
<input type="submit" class="unfocused">
|
||||
<span class="button ~urge @low full-width center supra submit">{{ .strings.apply }}</span>
|
||||
@@ -119,29 +122,20 @@
|
||||
</div>
|
||||
{{ if .referralsEnabled }}
|
||||
<div id="modal-enable-referrals-user" class="modal">
|
||||
<form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3" id="form-enable-referrals-user" href="">
|
||||
<form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3 flex flex-col gap-4" id="form-enable-referrals-user" href="">
|
||||
<span class="heading"><span id="header-enable-referrals-user"></span> <span class="modal-close">×</span></span>
|
||||
<p class="content my-4">{{ .strings.enableReferralsDescription }}</p>
|
||||
<div class="flex flex-row mb-4">
|
||||
<label class="grow mr-2">
|
||||
<input type="radio" name="enable-referrals-user-source" class="unfocused" id="radio-referrals-use-profile" checked>
|
||||
<span class="button ~neutral @high supra full-width center">{{ .strings.profile }}</span>
|
||||
</label>
|
||||
<label class="grow ml-2">
|
||||
<input type="radio" name="enable-referrals-user-source" class="unfocused" id="radio-referrals-use-invite">
|
||||
<span class="button ~neutral @low supra full-width center">{{ .strings.invite }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="select ~neutral @low mb-4">
|
||||
<p class="content">{{ .strings.enableReferralsDescription }}</p>
|
||||
<div id="enable-referrals-user-source"></div>
|
||||
<div class="select ~neutral @low">
|
||||
<select id="enable-referrals-user-profiles"></select>
|
||||
</div>
|
||||
<div class="select ~neutral @low mb-4 unfocused">
|
||||
<div class="select ~neutral @low unfocused">
|
||||
<select id="enable-referrals-user-invites"></select>
|
||||
</div>
|
||||
<label class="switch mb-4">
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="enable-referrals-user-expiry">
|
||||
<span>{{ .strings.useInviteExpiry }}</span>
|
||||
<span class="flex flex-row support mt-2">{{ .strings.useInviteExpiryNote }}</span>
|
||||
<span class="flex flex-row support">{{ .strings.useInviteExpiryNote }}</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="submit" class="unfocused">
|
||||
@@ -150,17 +144,19 @@
|
||||
</form>
|
||||
</div>
|
||||
<div id="modal-enable-referrals-profile" class="modal">
|
||||
<form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3" id="form-enable-referrals-profile" href="">
|
||||
<form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3 flex flex-col gap-4" id="form-enable-referrals-profile" href="">
|
||||
<span class="heading"><span id="header-enable-referrals-profile">{{ .strings.enableReferrals }}</span> <span class="modal-close">×</span></span>
|
||||
<p class="content my-4">{{ .strings.enableReferralsProfileDescription }}</p>
|
||||
<label class="supra" for="enable-referrals-profile-invites">{{ .strings.invite }}</label>
|
||||
<div class="select ~neutral @low mb-4 mt-2">
|
||||
<select id="enable-referrals-profile-invites"></select>
|
||||
<p class="content">{{ .strings.enableReferralsProfileDescription }}</p>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="supra" for="enable-referrals-profile-invites">{{ .strings.invite }}</label>
|
||||
<div class="select ~neutral @low">
|
||||
<select id="enable-referrals-profile-invites"></select>
|
||||
</div>
|
||||
</div>
|
||||
<label class="switch mb-4">
|
||||
<label class="switch flex flex-row gap-2">
|
||||
<input type="checkbox" id="enable-referrals-profile-expiry">
|
||||
<span>{{ .strings.useInviteExpiry }}</span>
|
||||
<span class="flex flex-row support mt-2">{{ .strings.useInviteExpiryNote }}</span>
|
||||
<span class="flex flex-row support">{{ .strings.useInviteExpiryNote }}</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="submit" class="unfocused">
|
||||
@@ -170,14 +166,14 @@
|
||||
</div>
|
||||
{{ end }}
|
||||
<div id="modal-delete-user" class="modal">
|
||||
<form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3" id="form-delete-user" href="">
|
||||
<form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3 flex flex-col gap-4" id="form-delete-user" href="">
|
||||
<span class="heading"><span id="header-delete-user"></span> <span class="modal-close">×</span></span>
|
||||
<div class="content mt-8">
|
||||
<label class="switch mb-4">
|
||||
<div class="content">
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="delete-user-notify" checked>
|
||||
<span>{{ .strings.sendDeleteNotificationEmail }}</span>
|
||||
</label>
|
||||
<textarea id="textarea-delete-user" class="textarea full-width ~neutral @low mb-4" placeholder="{{ .strings.sendDeleteNotificationExample }}"></textarea>
|
||||
<textarea id="textarea-delete-user" class="textarea full-width ~neutral @low" placeholder="{{ .strings.sendDeleteNotificationExample }}"></textarea>
|
||||
<label>
|
||||
<input type="submit" class="unfocused">
|
||||
<span class="button ~critical @low full-width center supra submit">{{ .strings.delete }}</span>
|
||||
@@ -186,60 +182,64 @@
|
||||
</form>
|
||||
</div>
|
||||
<div id="modal-extend-expiry" class="modal">
|
||||
<form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3" id="form-extend-expiry" href="">
|
||||
<form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3 flex flex-col gap-2" id="form-extend-expiry" href="">
|
||||
<span class="heading"><span id="header-extend-expiry"></span> <span class="modal-close">×</span></span>
|
||||
<div class="content mt-8">
|
||||
<aside class="aside sm ~urge dark:~d_info mb-2 @low row unfocused" id="extend-expiry-date"></aside>
|
||||
<div>
|
||||
<span class="text-xl supra row py-1">{{ .strings.setExpiry }}</span>
|
||||
<div class="row">
|
||||
<input type="text" id="extend-expiry-text" class="input ~neutral @low mb-2 mt-4" placeholder="{{ .strings.enterExpiry }}">
|
||||
</div>
|
||||
<div class="flex flex-col gap-3">
|
||||
<aside class="aside sm ~urge dark:~d_info @low unfocused" id="extend-expiry-date"></aside>
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="text-xl supra">{{ .strings.setExpiry }}</span>
|
||||
<input type="text" id="extend-expiry-text" class="input ~neutral @low" placeholder="{{ .strings.enterExpiry }}">
|
||||
</div>
|
||||
<div id="extend-expiry-field-inputs">
|
||||
<span class="text-xl supra row py-1">{{ .strings.extendExpiry }}</span>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div id="extend-expiry-field-inputs" class="flex flex-col gap-2">
|
||||
<span class="text-xl supra">{{ .strings.extendExpiry }}</span>
|
||||
<div class="grid grid-cols-2 grid-rows-2 gap-2">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="label supra" for="extend-expiry-months">{{ .strings.inviteMonths }}</label>
|
||||
<div class="select ~neutral @low mb-2 mt-4">
|
||||
<div class="select ~neutral @low">
|
||||
<select id="extend-expiry-months">
|
||||
<option>0</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="label supra" for="extend-expiry-days">{{ .strings.inviteDays }}</label>
|
||||
<div class="select ~neutral @low mb-2 mt-4">
|
||||
<div class="select ~neutral @low">
|
||||
<select id="extend-expiry-days">
|
||||
<option>0</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="label supra" for="extend-expiry-hours">{{ .strings.inviteHours }}</label>
|
||||
<div class="select ~neutral @low mb-2 mt-4">
|
||||
<div class="select ~neutral @low">
|
||||
<select id="extend-expiry-hours">
|
||||
<option>0</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="label supra" for="extend-expiry-minutes">{{ .strings.inviteMinutes }}</label>
|
||||
<div class="select ~neutral @low mb-2 mt-4">
|
||||
<div class="select ~neutral @low">
|
||||
<select id="extend-expiry-minutes">
|
||||
<option>0</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="expiry-use-previous">
|
||||
<span>{{ .strings.extendFromPreviousExpiry }}</span>
|
||||
<div class="tooltip left">
|
||||
<i class="icon ri-information-line align-middle"></i>
|
||||
<div class="content sm w-max">{{ .strings.extendFromPreviousExpiryDescription }}</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<label class="switch mb-4">
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="expiry-extend-enable" checked>
|
||||
<span>{{ .strings.sendDeleteNotificationEmail }}</span>
|
||||
</label>
|
||||
<textarea id="textarea-extend-enable" class="textarea full-width ~neutral @low mb-4" placeholder="{{ .strings.sendDeleteNotificationExample }}"></textarea>
|
||||
<textarea id="textarea-extend-enable" class="textarea full-width ~neutral @low" placeholder="{{ .strings.sendDeleteNotificationExample }}"></textarea>
|
||||
<label>
|
||||
<input type="submit" class="unfocused">
|
||||
<span class="button ~critical @low full-width center supra submit">{{ .strings.submit }}</span>
|
||||
@@ -250,21 +250,23 @@
|
||||
<div id="modal-announce" class="modal">
|
||||
<form class="relative mx-auto my-[10%] w-4/5 lg:w-2/3 content card" id="form-announce" href="">
|
||||
<span class="heading"><span id="header-announce"></span> <span class="modal-close">×</span></span>
|
||||
<div class="flex flex-col md:flex-row">
|
||||
<div class="col card ~neutral @low">
|
||||
<div id="announce-details">
|
||||
<div class="flex flex-row flex-wrap gap-4">
|
||||
<div class="card ~neutral @low flex flex-col gap-2 justify-between basis-[24rem] grow-[4]">
|
||||
<div id="announce-details" class="flex flex-col gap-2">
|
||||
<span class="label supra" for="editor-variables" id="label-editor-variables">{{ .strings.variables }}</span>
|
||||
<div id="announce-variables">
|
||||
<span class="button ~urge @low mb-2 mt-4" id="announce-variables-username" style="margin-left: 0.25rem; margin-right: 0.25rem;"><span class="font-mono bg-inherit">{username}</span></span>
|
||||
<div id="announce-variables" class="flex flex-row flex-wrap gap-2">
|
||||
<span class="button ~urge @low" id="announce-variables-username"><span class="font-mono bg-inherit">{username}</span></span>
|
||||
</div>
|
||||
<label class="label supra" for="announce-subject"> {{ .strings.subject }}</label>
|
||||
<input type="text" id="announce-subject" class="input ~neutral @low mb-2 mt-4">
|
||||
<input type="text" id="announce-subject" class="input ~neutral @low">
|
||||
<label class="label supra" for="textarea-announce">{{ .strings.message }}</label>
|
||||
<textarea id="textarea-announce" class="textarea full-width ~neutral @low mt-4 font-mono"></textarea>
|
||||
<p class="support mt-4 mb-2">{{ .strings.markdownSupported }}</p>
|
||||
<textarea id="textarea-announce" class="textarea full-width ~neutral @low font-mono"></textarea>
|
||||
<p class="support">{{ .strings.markdownSupported }}</p>
|
||||
<p class="support editor-syntax-description">{{ .strings.syntaxDescription }}</p>
|
||||
|
||||
</div>
|
||||
<label class="label unfocused" id="announce-name"><p class="supra">{{ .strings.name }}</p>
|
||||
<input type="text" class="input ~neutral @low mb-2 mt-4">
|
||||
<input type="text" class="input ~neutral @low">
|
||||
<p class="support">{{ .strings.templateEnterName }}</p>
|
||||
</label>
|
||||
<div class="flex flex-row justify-between">
|
||||
@@ -275,17 +277,17 @@
|
||||
<span class="button ~info @low center supra" id="save-announce">{{ .strings.saveAsTemplate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col card ~neutral @low">
|
||||
<div class="card ~neutral @low flex flex-col gap-2 basis-[24rem] grow">
|
||||
<span class="subheading supra">{{ .strings.preview }}</span>
|
||||
<div class="mt-8" id="announce-preview"></div>
|
||||
<div id="announce-preview"></div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div id="modal-customize" class="modal">
|
||||
<div class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3">
|
||||
<div class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3 flex flex-col gap-2">
|
||||
<span class="heading">{{ .strings.customizeMessages }} <span class="modal-close">×</span></span>
|
||||
<p class="content my-4">{{ .strings.customizeMessagesDescription }}</p>
|
||||
<p class="content">{{ .strings.customizeMessagesDescription }}</p>
|
||||
<div class="">
|
||||
<table class="table">
|
||||
<thead>
|
||||
@@ -303,8 +305,8 @@
|
||||
<div id="modal-editor" class="modal">
|
||||
<form class="relative mx-auto my-[10%] w-4/5 lg:w-2/3 content card" id="form-editor" href="">
|
||||
<span class="heading"><span id="header-editor"></span> <span class="modal-close">×</span></span>
|
||||
<div class="row">
|
||||
<div class="col card ~neutral @low flex flex-col gap-2 justify-between">
|
||||
<div class="flex flex-row flex-wrap gap-4">
|
||||
<div class="card ~neutral @low flex flex-col gap-2 justify-between basis-[24rem] grow-[4]">
|
||||
<div class="flex flex-col gap-2">
|
||||
<aside class="aside sm ~urge dark:~d_info @low" id="aside-editor"></aside>
|
||||
<label class="label supra" for="editor-variables" id="label-editor-variables">{{ .strings.variables }}</label>
|
||||
@@ -313,16 +315,17 @@
|
||||
<div id="editor-conditionals"></div>
|
||||
<label class="label supra" for="textarea-editor">{{ .strings.message }}</label>
|
||||
<textarea id="textarea-editor" class="textarea full-width flex-auto ~neutral @low font-mono"></textarea>
|
||||
<p class="support">{{ .strings.markdownSupported }}</p>
|
||||
<p class="support editor-syntax-description">{{ .strings.syntaxDescription }}</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<p class="support">{{ .strings.markdownSupported }}</p>
|
||||
<label class="w-full">
|
||||
<input type="submit" class="unfocused">
|
||||
<span class="button ~urge @low w-full supra submit">{{ .strings.submit }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col card ~neutral @low flex flex-col gap-2">
|
||||
<div class="card ~neutral @low flex flex-col gap-2 basis-[24rem] grow">
|
||||
<span class="subheading supra">{{ .strings.preview }}</span>
|
||||
<div id="editor-preview"></div>
|
||||
</div>
|
||||
@@ -330,19 +333,19 @@
|
||||
</form>
|
||||
</div>
|
||||
<div id="modal-restart" class="modal">
|
||||
<div class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3 ~critical @low">
|
||||
<div class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3 ~critical @low flex flex-col gap-4">
|
||||
<span class="heading">{{ .strings.settingsRestartRequired }} <span class="modal-close">×</span></span>
|
||||
<p class="content my-4">{{ .strings.settingsRestartRequiredDescription }}</p>
|
||||
<div class="float-right">
|
||||
<span class="button ~info @low mb-2" id="settings-apply-no-restart">{{ .strings.settingsApplyRestartLater }}</span>
|
||||
<p class="content">{{ .strings.settingsRestartRequiredDescription }}</p>
|
||||
<div class="flex flex-row justify-end gap-2">
|
||||
<span class="button ~info @low" id="settings-apply-no-restart">{{ .strings.settingsApplyRestartLater }}</span>
|
||||
<span class="button ~critical @low" id="settings-apply-restart">{{ .strings.settingsApplyRestartNow }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="modal-backups" class="modal">
|
||||
<div class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-2/3">
|
||||
<div class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-2/3 flex flex-col gap-4">
|
||||
<span class="heading">{{ .strings.backups }} <span class="modal-close">×</span></span>
|
||||
<div class="content my-4">
|
||||
<div class="content">
|
||||
{{ .strings.backupsDescription }}
|
||||
<ul>
|
||||
<li>{{ .strings.backupsCopy }}</li>
|
||||
@@ -350,11 +353,11 @@
|
||||
<li><a target="_blank" href="https://wiki.jfa-go.com/docs/backups/">{{ .strings.wikiPage }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="flex flex-row flex-wrap my-2">
|
||||
<button class="button ~info @low mr-2 mb-2" id="settings-backups-backup">{{ .strings.backupNow }}</button>
|
||||
<button class="button ~neutral @low mr-2 mb-2" id="settings-backups-upload">{{ .strings.backupUpload }}</button>
|
||||
<div class="flex flex-row flex-wrap gap-2">
|
||||
<button class="button ~info @low" id="settings-backups-backup">{{ .strings.backupNow }}</button>
|
||||
<button class="button ~neutral @low" id="settings-backups-upload">{{ .strings.backupUpload }}</button>
|
||||
<input id="backups-file" name="backups-file" type="file" hidden>
|
||||
<button class="button ~neutral @low mr-2 mb-2" id="settings-backups-sort-direction">{{ .strings.sortDirection }}</button>
|
||||
<button class="button ~neutral @low flex flex-row gap-2" id="settings-backups-sort-direction">{{ .strings.sortDirection }}</button>
|
||||
</div>
|
||||
<div class="overflow-x-auto text-xs md:text-sm">
|
||||
<table class="table">
|
||||
@@ -372,12 +375,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="modal-backed-up" class="modal">
|
||||
<div class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3 ~neutral @low">
|
||||
<div class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3 ~neutral @low flex flex-col gap-4">
|
||||
<span class="heading">{{ .strings.backupCreated }} <span class="modal-close">×</span></span>
|
||||
<p class="content my-4" id="settings-backed-up-location"></p>
|
||||
<p class="content my-4">{{ .strings.backupCanDownload }}</p>
|
||||
<div class="flex flex-col gap-2">
|
||||
<p class="content" id="settings-backed-up-location"></p>
|
||||
<p class="content">{{ .strings.backupCanDownload }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<button class="button flex w-full ~info @low mb-2"><span class="flex items-center" id="settings-backed-up-download">{{ .strings.download }}</span></button>
|
||||
<button class="button flex w-full ~info @low"><span class="flex flex-row gap-2 items-center" id="settings-backed-up-download">{{ .strings.download }}</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -388,17 +393,17 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="modal-send-pwr" class="modal">
|
||||
<div class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3 ~neutral @low">
|
||||
<div class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3 ~neutral @low flex flex-col gap-4">
|
||||
<span class="heading">{{ .strings.sendPWR }}</span>
|
||||
<p class="content my-2" id="send-pwr-note"></p>
|
||||
<span class="button ~urge @low mt-2" id="send-pwr-link">{{ .strings.copy }}</span>
|
||||
<p class="content" id="send-pwr-note"></p>
|
||||
<span class="button ~urge @low" id="send-pwr-link">{{ .strings.copy }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="modal-ombi-profile" class="modal">
|
||||
<form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3" id="form-ombi-defaults" href="">
|
||||
<form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3 flex flex-col gap-4" id="form-ombi-defaults" href="">
|
||||
<span class="heading">{{ .strings.ombiProfile }} <span class="modal-close">×</span></span>
|
||||
<p class="content my-4">{{ .strings.ombiUserDefaultsDescription }}</p>
|
||||
<div class="select ~neutral @low mb-4">
|
||||
<p class="content">{{ .strings.ombiUserDefaultsDescription }}</p>
|
||||
<div class="select ~neutral @low">
|
||||
<select></select>
|
||||
</div>
|
||||
<label>
|
||||
@@ -408,10 +413,10 @@
|
||||
</form>
|
||||
</div>
|
||||
<div id="modal-jellyseerr-profile" class="modal">
|
||||
<form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3" id="form-jellyseerr-defaults" href="">
|
||||
<form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3 flex flex-col gap-4" id="form-jellyseerr-defaults" href="">
|
||||
<span class="heading">{{ .strings.jellyseerrProfile }} <span class="modal-close">×</span></span>
|
||||
<p class="content my-4">{{ .strings.jellyseerrUserDefaultsDescription }}</p>
|
||||
<div class="select ~neutral @low mb-4">
|
||||
<p class="content">{{ .strings.jellyseerrUserDefaultsDescription }}</p>
|
||||
<div class="select ~neutral @low">
|
||||
<select></select>
|
||||
</div>
|
||||
<label>
|
||||
@@ -421,9 +426,9 @@
|
||||
</form>
|
||||
</div>
|
||||
<div id="modal-user-profiles" class="modal">
|
||||
<div class="relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-2/3 content card">
|
||||
<div class="relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-2/3 card flex flex-col gap-4">
|
||||
<span class="heading">{{ .strings.userProfiles }} <span class="modal-close">×</span></span>
|
||||
<p class="content my-4">{{ .strings.userProfilesDescription }}</p>
|
||||
<p class="content">{{ .strings.userProfilesDescription }}</p>
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
@@ -441,6 +446,7 @@
|
||||
{{ end }}
|
||||
<th>{{ .strings.from }}</th>
|
||||
<th>{{ .strings.userProfilesLibraries }}</th>
|
||||
<th></th>
|
||||
<th><span class="button ~neutral @high" id="button-profile-create">{{ .strings.create }}</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -449,68 +455,69 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="modal-edit-profile" class="modal">
|
||||
<form class="relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-2/3 card flex flex-col gap-2" id="form-edit-profile">
|
||||
<span class="heading">{{ .strings.editProfile }} <span class="modal-close">×</span></span>
|
||||
<p class="content">{{ .strings.editProfileDescription }}</p>
|
||||
<div id="modal-edit-profile-editor"></div>
|
||||
<label>
|
||||
<input type="submit" class="unfocused">
|
||||
<span class="button ~urge @low full-width center supra submit">{{ .strings.submit }}</span>
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
<div id="modal-add-profile" class="modal">
|
||||
<form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3" id="form-add-profile" href="">
|
||||
<span class="heading">{{ .strings.addProfile }} <span class="modal-close">×</span></span>
|
||||
<p class="content my-4">{{ .strings.addProfileDescription }}</p>
|
||||
<label>
|
||||
<form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3 flex flex-col gap-2" id="form-add-profile" href="">
|
||||
<h1 class="heading">{{ .strings.addProfile }} <span class="modal-close">×</span></h1>
|
||||
<p class="content">{{ .strings.addProfileDescription }}</p>
|
||||
<label class="flex flex-col gap-2">
|
||||
<span class="supra">{{ .strings.addProfileNameOf }} </span>
|
||||
<input type="text" class="field input ~neutral @high mt-4 mb-2" placeholder="{{ .strings.name }}" id="add-profile-name">
|
||||
<label>
|
||||
<input type="text" class="field input ~neutral @high" placeholder="{{ .strings.name }}" id="add-profile-name">
|
||||
</label>
|
||||
<label class="flex flex-col gap-2">
|
||||
<span class="supra">{{ .strings.user }}</span>
|
||||
<div class="select ~neutral @low mt-4 mb-2">
|
||||
<div class="select ~neutral @low">
|
||||
<select id="add-profile-user"></select>
|
||||
</div>
|
||||
</label>
|
||||
<label class="switch mb-4">
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="add-profile-homescreen" checked>
|
||||
<span>{{ .strings.addProfileStoreHomescreenLayout }}</span>
|
||||
</label>
|
||||
{{ if .jellyseerrEnabled }}
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="add-profile-jellyseerr" checked>
|
||||
<span>{{ .strings.addProfileStoreJellyseerr }}</span>
|
||||
</label>
|
||||
{{ end }}
|
||||
<label>
|
||||
<input type="submit" class="unfocused">
|
||||
<span class="button ~urge @low full-width center supra submit">{{ .strings.create }}</span>
|
||||
<span class="button ~urge @low w-full center supra submit">{{ .strings.create }}</span>
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
<div id="modal-update" class="modal">
|
||||
<div class="relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3 content card">
|
||||
<div class="relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3 card flex flex-col gap-2">
|
||||
<span class="heading">{{ .strings.updates }} <span class="modal-close">×</span></span>
|
||||
<p class="content">
|
||||
<h2 class="mt-2">
|
||||
<div class="content flex flex-col">
|
||||
<h2>
|
||||
<a id="update-version"></a> (<span class="font-mono bg-inherit" id="update-commit"></span>)
|
||||
</h2>
|
||||
<p class="content mt-2" id="update-description"></p>
|
||||
<p class="support mt-2" id="update-date"></p>
|
||||
<div class="content markdown-box mt-2" id="update-changelog"></div>
|
||||
</p>
|
||||
<span class="button ~info @low full-width center mt-2" id="update-download">{{ .strings.download }}</span>
|
||||
<span class="button ~urge @low full-width center mt-2" id="update-update">{{ .strings.update }}</span>
|
||||
<p class="content" id="update-description"></p>
|
||||
<div class="content markdown-box" id="update-changelog"></div>
|
||||
<p class="support" id="update-date"></p>
|
||||
</div>
|
||||
<span class="button ~info @low full-width center" id="update-download">{{ .strings.download }}</span>
|
||||
<span class="button ~urge @low full-width center" id="update-update">{{ .strings.update }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ if .telegramEnabled }}
|
||||
<div id="modal-telegram" class="modal">
|
||||
<div class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3">
|
||||
<span class="heading mb-4">{{ .strings.linkTelegram }}</span>
|
||||
<p class="content mb-4">{{ .strings.sendPIN }}</p>
|
||||
<h1 class="ac" id="telegram-pin"></h1>
|
||||
<a class="subheading link-center" id="telegram-link" target="_blank">
|
||||
<span class="shield ~info mr-2">
|
||||
<span class="icon">
|
||||
<i class="ri-telegram-line"></i>
|
||||
</span>
|
||||
</span>
|
||||
@<span id="telegram-username">
|
||||
</a>
|
||||
<span class="button ~info @low full-width center mt-4" id="telegram-waiting">{{ .strings.success }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ template "account-linking-telegram.html" . }}
|
||||
{{ if .discordEnabled }}
|
||||
<div id="modal-discord" class="modal">
|
||||
<div class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3">
|
||||
<span class="heading mb-4"><span id="discord-header"></span><span class="modal-close">×</span></span>
|
||||
<p class="content mb-4" id="discord-description"></p>
|
||||
<div class="row">
|
||||
<div class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3 flex flex-col gap-2">
|
||||
<span class="heading"><span id="discord-header"></span><span class="modal-close">×</span></span>
|
||||
<p class="content" id="discord-description"></p>
|
||||
<div>
|
||||
<input type="search" class="col sm field ~neutral @low input" id="discord-search" placeholder="user#1234">
|
||||
</div>
|
||||
<table class="table"><tbody id="discord-list"></tbody></table>
|
||||
@@ -518,20 +525,29 @@
|
||||
</div>
|
||||
{{ end }}
|
||||
<div id="modal-matrix" class="modal">
|
||||
<form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3" id="form-matrix" href="">
|
||||
<form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3 flex flex-col gap-2" id="form-matrix" href="">
|
||||
<span class="heading">{{ .strings.linkMatrix }}</span>
|
||||
<p class="content my-4">{{ .strings.linkMatrixDescription }}</p>
|
||||
<input type="text" class="field input ~neutral @high mt-4 mb-2" placeholder="{{ .strings.matrixHomeServer }}" id="matrix-homeserver">
|
||||
<input type="text" class="field input ~neutral @high mt-4 mb-2" placeholder="{{ .strings.username }}" id="matrix-user">
|
||||
<input type="password" class="field input ~neutral @high mt-4 mb-2" placeholder="{{ .strings.password }}" id="matrix-password">
|
||||
<p class="content">{{ .strings.linkMatrixDescription }}</p>
|
||||
<label class="flex flex-col gap-2">
|
||||
<span class="supra">{{ .strings.matrixHomeServer }}</span>
|
||||
<input type="text" class="field input ~neutral @high" placeholder="{{ .strings.matrixHomeServer }}" id="matrix-homeserver">
|
||||
</label>
|
||||
<label class="flex flex-col gap-2">
|
||||
<span class="supra">{{ .strings.username }}</span>
|
||||
<input type="text" class="field input ~neutral @high" placeholder="{{ .strings.username }}" id="matrix-user">
|
||||
</label>
|
||||
<label class="flex flex-col gap-2">
|
||||
<span class="supra">{{ .strings.password }}</span>
|
||||
<input type="password" class="field input ~neutral @high" placeholder="{{ .strings.password }}" id="matrix-password">
|
||||
</label>
|
||||
<label>
|
||||
<input type="submit" class="unfocused">
|
||||
<span class="button ~urge @low full-width center supra submit">{{ .strings.submit }}</span>
|
||||
</label>
|
||||
</label>"
|
||||
</form>
|
||||
</div>
|
||||
<div id="notification-box"></div>
|
||||
<div class="page-container m-2 lg:my-20 lg:mx-64 flex flex-col gap-4">
|
||||
<div class="page-container m-2 lg:my-20 lg:mx-64 flex flex-col gap-4 overflow-x-hidden">
|
||||
<div class="top-2 inset-x-2 lg:absolute flex flex-row justify-between">
|
||||
<div class="flex flex-row gap-2">
|
||||
{{ template "lang-select.html" . }}
|
||||
@@ -541,38 +557,29 @@
|
||||
<span class="button ~critical @low unfocused" id="logout-button">{{ .strings.logout }}</span>
|
||||
{{ if .userPageEnabled }}
|
||||
<div class="">
|
||||
<a class="button ~info" href="{{ .pages.Base }}{{ .pages.MyAccount }}/"><i class="ri-account-circle-fill mr-2"></i>{{ .strings.myAccount }}</a>
|
||||
<a class="button ~info flex flex-row gap-2" href="{{ .pages.Base }}{{ .pages.MyAccount }}/"><i class="ri-account-circle-fill"></i>{{ .strings.myAccount }}</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
<header>
|
||||
<div class="flex flex-row overflow-x-auto items-center gap-2">
|
||||
<span id="button-tab-invites" class="text-3xl button portal ~neutral dark:~d_neutral @low px-5">{{ .strings.invites }}</span>
|
||||
<span id="button-tab-accounts" class="text-3xl button portal ~neutral dark:~d_neutral @low px-5">{{ .strings.accounts }}</span>
|
||||
<span id="button-tab-activity" class="text-3xl button portal ~neutral dark:~d_neutral @low px-5">{{ .strings.activity }}</span>
|
||||
<span id="button-tab-settings" class="text-3xl button portal ~neutral dark:~d_neutral @low px-5">{{ .strings.settings }}</span>
|
||||
<div class="flex flex-row overflow-x-auto items-center gap-2 scroll-smooth">
|
||||
<button type="button" id="button-tab-invites" class="text-3xl button portal ~neutral dark:~d_neutral @low px-5">{{ .strings.invites }}</button>
|
||||
<button type="button" id="button-tab-accounts" class="text-3xl button portal ~neutral dark:~d_neutral @low px-5">{{ .strings.accounts }}</button>
|
||||
<button type="button" id="button-tab-activity" class="text-3xl button portal ~neutral dark:~d_neutral @low px-5">{{ .strings.activity }}</button>
|
||||
<button type="button" id="button-tab-settings" class="text-3xl button portal ~neutral dark:~d_neutral @low px-5">{{ .strings.settings }}</button>
|
||||
</div>
|
||||
</header>
|
||||
<div id="tab-invites" class="flex flex-col gap-4">
|
||||
<div class="card @low dark:~d_neutral flex flex-col gap-2 overflow-visible invites">
|
||||
<span class="heading">{{ .strings.invites }}</span>
|
||||
<div id="invites"></div>
|
||||
<div id="invites" class="flex flex-col gap-2"></div>
|
||||
</div>
|
||||
<div class="card @low dark:~d_neutral flex flex-col gap-2">
|
||||
<span class="heading">{{ .strings.create }}</span>
|
||||
<div class="flex flex-col md:flex-row gap-3" id="create-inv">
|
||||
<div class="card ~neutral @low flex flex-col gap-2 flex-1">
|
||||
<div class="flex flex-row gap-2">
|
||||
<label class="w-1/2">
|
||||
<input type="radio" name="duration" class="unfocused" id="radio-inv-duration" checked>
|
||||
<span class="button ~neutral @high supra full-width center">{{ .strings.inviteDuration }}</span>
|
||||
</label>
|
||||
<label class="w-1/2">
|
||||
<input type="radio" name="duration" class="unfocused" id="radio-user-expiry">
|
||||
<span class="button ~neutral @low supra full-width center">{{ .strings.userExpiry }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div id="invites-duration-type-tabs"></div>
|
||||
<div id="inv-duration" class="flex flex-col gap-2">
|
||||
<div class="flex flex-row gap-2">
|
||||
<div class="grow flex flex-col gap-4">
|
||||
@@ -615,9 +622,9 @@
|
||||
<div class="flex flex-row gap-2">
|
||||
<p class="support">{{ .strings.userExpiryDescription }}</p>
|
||||
<div>
|
||||
<label for="create-user-expiry-enabled" class="button ~neutral @low">
|
||||
<label for="create-user-expiry-enabled" class="button ~neutral @low flex flex-row gap-2">
|
||||
<input type="checkbox" id="create-user-expiry-enabled" aria-label="User duration enabled">
|
||||
<span class="ml-2">{{ .strings.enabled }} </span>
|
||||
<span>{{ .strings.enabled }} </span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -691,20 +698,6 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="create-send-to-container" class="flex flex-col gap-4">
|
||||
<label class="label supra">{{ .strings.inviteSendToEmail }}</label>
|
||||
<div class="flex flex-row gap-2">
|
||||
{{ if .discordEnabled }}
|
||||
<input type="text" id="create-send-to" class="input ~neutral @low" placeholder="example@example.com | user#1234">
|
||||
<span id="create-send-to-search" class="button ~neutral @low">
|
||||
<i class="icon ri-search-2-line" title="{{ .strings.search }}"></i>
|
||||
</span>
|
||||
{{ else }}
|
||||
<input type="email" id="create-send-to" class="input ~neutral @low" placeholder="example@example.com">
|
||||
{{ end }}
|
||||
<label for="create-send-to-enabled" class="button ~neutral @low">
|
||||
<input type="checkbox" id="create-send-to-enabled" aria-label="Send to address enabled">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@@ -715,18 +708,18 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab-accounts" class="flex flex-col gap-4 unfocused">
|
||||
<div class="card @low dark:~d_neutral accounts mb-4 overflow-visible flex flex-col gap-2">
|
||||
<div class="card @low dark:~d_neutral accounts overflow-visible flex flex-col gap-2">
|
||||
<div id="accounts-filter-dropdown" class="dropdown manual z-10 w-full">
|
||||
<div class="flex flex-col md:flex-row align-middle gap-2">
|
||||
<div class="flex flex-row align-middle justify-between md:justify-normal">
|
||||
<span class="text-3xl font-bold mr-4">{{ .strings.accounts }}</span>
|
||||
<div class="flex flex-row gap-4 align-middle justify-between md:justify-normal">
|
||||
<span class="text-3xl font-bold">{{ .strings.accounts }}</span>
|
||||
<span class="dropdown-manual-toggle"><button class="h-full button ~neutral @low center" id="accounts-filter-button" tabindex="0">{{ .strings.filters }}</button></span>
|
||||
</div>
|
||||
<div class="flex flex-row align-middle w-full gap-2">
|
||||
<input type="search" class="field ~neutral @low input search mr-2" id="accounts-search" placeholder="{{ .strings.search }}">
|
||||
<input type="search" class="field ~neutral @low input search" id="accounts-search" placeholder="{{ .strings.search }}">
|
||||
<span class="button ~neutral @low center inside-input rounded-s-none accounts-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></span>
|
||||
<div class="tooltip left">
|
||||
<button class="button ~info @low center h-full accounts-search-server flex flex-row gap-1" aria-label="{{ .strings.searchAllRecords }}" text="{{ .strings.searchAllRecords }}">
|
||||
<button class="button ~info @low center h-full accounts-search-server gap-1" aria-label="{{ .strings.searchAllRecords }}" text="{{ .strings.searchAllRecords }}">
|
||||
<i class="ri-search-line"></i>
|
||||
<span>{{ .strings.searchAll }}</span>
|
||||
</button>
|
||||
@@ -736,7 +729,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown-display max-w-full">
|
||||
<div class="card ~neutral @low mt-2 overflow-x-scroll" id="accounts-filter-list">
|
||||
<div class="card ~neutral @low overflow-x-scroll" id="accounts-filter-list">
|
||||
<p class="supra pb-2">{{ .strings.filters }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -751,14 +744,15 @@
|
||||
</div>
|
||||
<div class="supra sm">{{ .strings.actions }}</div>
|
||||
<div class="flex flex-row flex-wrap gap-3">
|
||||
<button type="button" title="{{ .strings.back }}" class="button ~neutral @low inline-flex gap-1 unfocused" id="user-details-back" aria-label="{{ .strings.back }}"><i class="icon ri-arrow-left-fill"></i>{{ .strings.back }}</button>
|
||||
<button class="button ~neutral @low center accounts-load-all">{{ .strings.loadAll }}</button>
|
||||
<span class="button ~neutral @low center " id="accounts-add-user">{{ .quantityStrings.addUser.Singular }}</span>
|
||||
<div id="accounts-announce-dropdown" class="dropdown pb-0i " tabindex="0">
|
||||
<span class="w-full button ~info @low center items-baseline" id="accounts-announce">{{ .strings.announce }}</span>
|
||||
<span class="w-full button ~info @low center items-baseline flex flex-row gap-2" id="accounts-announce">{{ .strings.announce }}</span>
|
||||
<div class="dropdown-display">
|
||||
<div class="card ~neutral @low">
|
||||
<span class="supra sm">{{ .strings.templates }}</span>
|
||||
<div id="accounts-announce-templates"></div>
|
||||
<div id="accounts-announce-templates" class="flex flex-col gap-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -767,11 +761,11 @@
|
||||
<span class="button ~urge @low center " id="accounts-enable-referrals">{{ .strings.enableReferrals }}</span>
|
||||
{{ end }}
|
||||
<div id="accounts-expiry-dropdown" class="dropdown pb-0i " tabindex="0">
|
||||
<span class="w-full button ~positive @low center items-baseline" id="accounts-expiry-dropdown-button">{{ .strings.expiry }} <i class="ri-arrow-down-s-line ml-2"></i></span>
|
||||
<span class="w-full button ~positive @low center items-baseline flex flex-row gap-2" id="accounts-expiry-dropdown-button">{{ .strings.expiry }}<i class="ri-arrow-down-s-line"></i></span>
|
||||
<div class="dropdown-display">
|
||||
<div class="card ~neutral @low">
|
||||
<div class="card ~neutral @low flex flex-col gap-2">
|
||||
<span class="button ~warning full-width @low center" id="accounts-extend-expiry">{{ .strings.extendExpiry }}</span>
|
||||
<span class="button ~critical full-width @low center mt-2" id="accounts-remove-expiry">{{ .strings.removeExpiry }}</span>
|
||||
<span class="button ~critical full-width @low center" id="accounts-remove-expiry">{{ .strings.removeExpiry }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -786,73 +780,56 @@
|
||||
<span class="button ~info @low center unfocused " id="accounts-send-pwr">{{ .strings.sendPWR }}</span>
|
||||
<span class="button ~critical @low center " id="accounts-delete-user">{{ .quantityStrings.deleteUser.Singular }}</span>
|
||||
</div>
|
||||
<div class="card @low accounts-header table-responsive">
|
||||
<table class="table text-base leading-5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><input type="checkbox" value="" id="accounts-select-all"></th>
|
||||
<th class="table-inline my-2 grid gap-4 place-items-stretch accounts-header-username">{{ .strings.username }}</th>
|
||||
{{ if .jellyfinLogin }}
|
||||
<th class="text-center-i grid gap-4 place-items-stretch accounts-header-access-jfa">{{ .strings.accessJFA }}</th>
|
||||
{{ end }}
|
||||
<th class="grid gap-4 place-items-stretch accounts-header-email">{{ .strings.emailAddress }}</th>
|
||||
{{ if .telegramEnabled }}
|
||||
<th class="text-center-i grid gap-4 place-items-stretch accounts-header-telegram">Telegram</th>
|
||||
{{ end }}
|
||||
{{ if .matrixEnabled }}
|
||||
<th class="text-center-i grid gap-4 place-items-stretch accounts-header-matrix">Matrix</th>
|
||||
{{ end }}
|
||||
{{ if .discordEnabled }}
|
||||
<th class="text-center-i grid gap-4 place-items-stretch accounts-header-discord">Discord</th>
|
||||
{{ end }}
|
||||
{{ if .referralsEnabled }}
|
||||
<th class="text-center-i grid gap-4 place-items-stretch accounts-header-referrals">{{ .strings.referrals }}</th>
|
||||
{{ end }}
|
||||
<th class="grid gap-4 place-items-stretch accounts-header-expiry">{{ .strings.expiry }}</th>
|
||||
<th class="grid gap-4 place-items-stretch accounts-header-last-active">{{ .strings.lastActiveTime }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="accounts-list"></tbody>
|
||||
</table>
|
||||
<div id="accounts-loader"></div>
|
||||
<div class="unfocused h-[100%] my-3" id="accounts-not-found">
|
||||
<div class="flex flex-col gap-2 h-[100%] justify-center items-center">
|
||||
<span class="text-2xl font-medium italic text-center">{{ .strings.noResultsFound }}</span>
|
||||
<span class="text-sm font-light italic unfocused text-center" id="accounts-no-local-results">{{ .strings.noResultsFoundLocally }}</span>
|
||||
<div class="flex flex-row">
|
||||
<button class="button ~neutral @low accounts-search-clear flex flex-row gap-2">
|
||||
<span>{{ .strings.clearSearch }}</span><i class="ri-close-line"></i>
|
||||
</button>
|
||||
<div id="accounts-table-details-container">
|
||||
<div class="card @low accounts-details transition-opacity transition-display unfocused">
|
||||
<div id="accounts-loader"></div>
|
||||
</div>
|
||||
<div class="card @low accounts-header overflow-x-scroll transition-opacity transition-display" id="accounts-table">
|
||||
<table class="table text-base leading-5">
|
||||
{{ template "accounts-table-header.txt" . }}
|
||||
<tbody id="accounts-list"></tbody>
|
||||
</table>
|
||||
<div id="accounts-loader"></div>
|
||||
<div class="unfocused h-[100%] my-3" id="accounts-not-found">
|
||||
<div class="flex flex-col gap-2 h-[100%] justify-center items-center">
|
||||
<span class="text-2xl font-medium italic text-center">{{ .strings.noResultsFound }}</span>
|
||||
<span class="text-sm font-light italic unfocused text-center" id="accounts-no-local-results">{{ .strings.noResultsFoundLocally }}</span>
|
||||
<div class="flex flex-row">
|
||||
<button class="button ~neutral @low accounts-search-clear gap-1">
|
||||
<i class="ri-close-line"></i>
|
||||
<span>{{ .strings.clearSearch }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row gap-2 justify-center">
|
||||
<button class="button ~neutral @low" id="accounts-load-more">{{ .strings.loadMore }}</button>
|
||||
<button class="button ~neutral @low accounts-load-all">{{ .strings.loadAll }}</button>
|
||||
<button class="button ~info @low center accounts-search-server flex flex-row gap-1" aria-label="{{ .strings.searchAllRecords }}" text="{{ .strings.searchAllRecords }}">
|
||||
<i class="ri-search-line"></i>
|
||||
<span>{{ .strings.searchAllRecords }}</span>
|
||||
</button>
|
||||
<div class="flex flex-row gap-2 my-3 justify-center">
|
||||
<button class="button ~neutral @low" id="accounts-load-more">{{ .strings.loadMore }}</button>
|
||||
<button class="button ~neutral @low accounts-load-all">{{ .strings.loadAll }}</button>
|
||||
<button class="button ~info @low center accounts-search-server gap-1" aria-label="{{ .strings.searchAllRecords }}" text="{{ .strings.searchAllRecords }}">
|
||||
<i class="ri-search-line"></i>
|
||||
<span>{{ .strings.searchAllRecords }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab-activity" class="flex flex-col gap-4 unfocused">
|
||||
<div class="card @low dark:~d_neutral activity mb-4 overflow-visible flex flex-col gap-2">
|
||||
<div class="card @low dark:~d_neutral activity overflow-visible flex flex-col gap-2">
|
||||
<div id="activity-filter-dropdown" class="dropdown manual z-10 w-full" tabindex="0">
|
||||
<div class="flex flex-col md:flex-row align-middle gap-2">
|
||||
<div class="flex flex-row align-middle justify-between md:justify-normal">
|
||||
<span class="text-3xl font-bold mr-4">{{ .strings.activity }}</span>
|
||||
<div class="flex flex-row align-middle">
|
||||
<div class="flex flex-row gap-4 align-middle justify-between md:justify-normal">
|
||||
<span class="text-3xl font-bold">{{ .strings.activity }}</span>
|
||||
<div class="flex flex-row gap-2 align-middle">
|
||||
<span class="dropdown-manual-toggle"><button class="h-full button ~neutral @low center" id="activity-filter-button">{{ .strings.filters }}</button></span>
|
||||
<button class="button ~neutral @low ml-2" id="activity-sort-direction">{{ .strings.sortDirection }}</button>
|
||||
<button class="button ~neutral @low" id="activity-sort-direction">{{ .strings.sortDirection }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row align-middle w-full gap-2">
|
||||
<input type="search" class="field ~neutral @low input search mr-2" id="activity-search" placeholder="{{ .strings.search }}">
|
||||
<input type="search" class="field ~neutral @low input search" id="activity-search" placeholder="{{ .strings.search }}">
|
||||
<span class="button ~neutral @low center inside-input rounded-s-none activity-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></span>
|
||||
<div class="tooltip left">
|
||||
<button class="button ~info @low center h-full activity-search-server flex flex-row gap-1" aria-label="{{ .strings.searchAllRecords }}" text="{{ .strings.searchAllRecords }}">
|
||||
<button class="button ~info @low center h-full activity-search-server gap-1" aria-label="{{ .strings.searchAllRecords }}" text="{{ .strings.searchAllRecords }}">
|
||||
<i class="ri-search-line"></i>
|
||||
<span>{{ .strings.searchAll }}</span>
|
||||
</button>
|
||||
@@ -862,7 +839,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown-display max-w-full">
|
||||
<div class="card ~neutral @low mt-2 overflow-x-scroll" id="activity-filter-list">
|
||||
<div class="card ~neutral @low overflow-x-scroll" id="activity-filter-list">
|
||||
<p class="supra pb-2">{{ .strings.filters }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -879,19 +856,20 @@
|
||||
<span class="text-2xl font-medium italic text-center">{{ .strings.noResultsFound }}</span>
|
||||
<span class="text-sm font-light italic unfocused text-center" id="activity-no-local-results">{{ .strings.noResultsFoundLocally }}</span>
|
||||
<div class="flex flex-row">
|
||||
<button class="button ~neutral @low activity-search-clear flex flex-row gap-2">
|
||||
<span>{{ .strings.clearSearch }}</span><i class="ri-close-line"></i>
|
||||
<button class="button ~neutral @low activity-search-clear gap-1">
|
||||
<i class="ri-close-line"></i>
|
||||
<span>{{ .strings.clearSearch }}</span>
|
||||
</button>
|
||||
<button class="button ~neutral @low unfocused" id="activity-keep-searching">{{ .strings.keepSearching }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="activity-card-list"></div>
|
||||
<div id="activity-card-list" class="flex flex-col gap-2"></div>
|
||||
<div id="activity-loader"></div>
|
||||
<div class="flex flex-row gap-2 justify-center">
|
||||
<button class="button ~neutral @low" id="activity-load-more">{{ .strings.loadMore }}</button>
|
||||
<button class="button ~neutral @low activity-load-all">{{ .strings.loadAll }}</button>
|
||||
<button class="button ~info @low center activity-search-server flex flex-row gap-1" aria-label="{{ .strings.searchAllRecords }}" text="{{ .strings.searchAllRecords }}">
|
||||
<button class="button ~info @low center activity-search-server gap-1" aria-label="{{ .strings.searchAllRecords }}" text="{{ .strings.searchAllRecords }}">
|
||||
<i class="ri-search-line"></i>
|
||||
<span>{{ .strings.searchAllRecords }}</span>
|
||||
</button>
|
||||
@@ -901,40 +879,41 @@
|
||||
<div id="tab-settings" class="flex flex-col gap-4 unfocused">
|
||||
<div class="card @low dark:~d_neutral settings overflow flex flex-col gap-2">
|
||||
<div class="flex flex-col md:flex-row align-middle gap-2">
|
||||
<div class="flex flex-row align-middle justify-between md:justify-normal">
|
||||
<div class="flex flex-row align-middle justify-between md:justify-normal gap-2">
|
||||
<span class="heading">{{ .strings.settings }}</span>
|
||||
<label for="settings-advanced-enabled" class="button ~neutral @low ml-2">
|
||||
<label for="settings-advanced-enabled" class="button ~neutral @low flex flex-row gap-2">
|
||||
<input type="checkbox" id="settings-advanced-enabled" aria-label="Advanced settings enabled">
|
||||
<span class="ml-2">{{ .strings.advancedSettings }} </span>
|
||||
<span>{{ .strings.advancedSettings }} </span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex flex-row justify-start md:justify-end gap-2 w-full">
|
||||
<span class="button ~neutral @low gap-1 unfocused" id="settings-tasks"><i class="ri-calendar-schedule-line"></i>{{ .strings.tasks }}</span>
|
||||
<span class="button ~neutral @low" id="settings-logs">{{ .strings.logs }}</span>
|
||||
<span class="button ~info @low" id="settings-backups">{{ .strings.backups }}</span>
|
||||
<span class="button ~neutral @low" id="settings-restart">{{ .strings.settingsRestart }}</span>
|
||||
<span class="button ~urge @low unfocused" id="settings-save">{{ .strings.settingsSave }}</span>
|
||||
<span class="button ~info @low gap-1" id="settings-backups"><i class="icon ri-file-copy-line"></i>{{ .strings.backups }}</span>
|
||||
<span class="button ~neutral @low gap-1" id="settings-restart"><i class="icon ri-restart-line"></i>{{ .strings.settingsRestart }}</span>
|
||||
<span class="button ~urge @low unfocused gap-1" id="settings-save"><i class="icon ri-save-line"></i>{{ .strings.settingsSave }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col md:flex-row gap-3">
|
||||
<div class="md:card @low dark:~d_neutral flex md:flex flex-col gap-2 flex-1" id="settings-sidebar">
|
||||
<div class="flex flex-col md:flex-row gap-3 force-ltr">
|
||||
<div class="@low dark:~d_neutral flex md:flex flex-col gap-2" id="settings-sidebar">
|
||||
<div class="flex flex-row justify-between">
|
||||
<input type="search" class="field ~neutral @low input settings-section-button justify-between" id="settings-search" placeholder="{{ .strings.search }}">
|
||||
<button class="button ~neutral @low center -ml-10 rounded-s-none settings-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></button>
|
||||
<button class="button ~neutral @low center inside-input rounded-s-none settings-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></button>
|
||||
</div>
|
||||
<aside class="aside sm ~urge dark:~d_info @low" id="settings-message">Note: <span class="badge ~critical">*</span> indicates a required field, <span class="badge ~info dark:~d_warning">R</span> indicates changes require a restart.</aside>
|
||||
<div id="settings-loader" class="flex flex-row flex-wrap gap-2">
|
||||
<span class="button ~neutral @low justify-center grow" id="setting-about"><span class="flex">{{ .strings.aboutProgram }} <i class="ri-information-line ml-2"></i></span></span>
|
||||
<a class="button ~urge dark:~d_info @low justify-center grow" target="_blank" href="https://wiki.jfa-go.com"><span class="flex">{{ .strings.wiki }} <i class="ri-book-shelf-line ml-2"></i></a>
|
||||
<span class="button ~neutral @low justify-center grow" id="setting-profiles"><span class="flex">{{ .strings.userProfiles }} <i class="ri-user-line ml-2"></i></span></span>
|
||||
<span class="button ~neutral @low justify-center grow flex flex-row gap-2" id="setting-about"><span class="flex flex-row gap-2">{{ .strings.aboutProgram }} <i class="ri-information-line"></i></span></span>
|
||||
<a class="button ~urge dark:~d_info @low justify-center grow flex flex-row gap-2" target="_blank" href="https://wiki.jfa-go.com"><span class="flex flex-row gap-2">{{ .strings.wiki }} <i class="ri-book-shelf-line"></i></a>
|
||||
<span class="button ~neutral @low justify-center grow flex flex-row gap-2" id="setting-profiles"><span class="flex flex-row gap-2">{{ .strings.userProfiles }} <i class="ri-user-line"></i></span></span>
|
||||
</div>
|
||||
<div class="flex md:flex flex-col gap-2 overflow-y-scroll" id="settings-sidebar-items"></div>
|
||||
</div>
|
||||
<div class="card ~neutral @low overflow flex-1" id="settings-panel">
|
||||
<div class="card ~neutral @low overflow flex-1 grow" id="settings-panel">
|
||||
<div class="settings-section unfocused h-[100%]" id="settings-not-found">
|
||||
<div class="flex flex-col h-[100%] justify-center items-center">
|
||||
<span class="text-2xl font-medium italic mb-2">{{ .strings.noResultsFound }}</span>
|
||||
<span class="mb-2 px-12 text-center">{{ .strings.settingsMaybeUnderAdvanced }}</span>
|
||||
<button class="button ~neutral @low settings-search-clear">
|
||||
<span class="mr-2">{{ .strings.clearSearch }}</span><i class="ri-close-line"></i>
|
||||
<div class="flex flex-col gap-4 h-[100%] justify-center items-center">
|
||||
<span class="text-2xl font-medium italic">{{ .strings.noResultsFound }}</span>
|
||||
<span class="px-12 text-center">{{ .strings.settingsMaybeUnderAdvanced }}</span>
|
||||
<button class="button ~neutral @low settings-search-clear flex flex-row gap-2">
|
||||
<span>{{ .strings.clearSearch }}</span><i class="ri-close-line"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -944,5 +923,5 @@
|
||||
</div>
|
||||
</div>
|
||||
<script src="{{ .pages.Base }}/js/admin.js" type="module"></script>
|
||||
</body>
|
||||
</div></body>
|
||||
</html>
|
||||
|
||||
@@ -1,42 +1,38 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="{{ .shortLang }}" dir="{{ .pageDirection }}">
|
||||
<head>
|
||||
<!--- This CSS is inlined so we should keep this here! -->
|
||||
<link inline rel="stylesheet" type="text/css" href="web/css/v3bundle.css">
|
||||
{{ template "header.html" . }}
|
||||
<link inline rel="stylesheet" type="text/css" href="web/css/v0.6.0bundle.css">
|
||||
{{ template "header.txt" . }}
|
||||
<title>Crash report</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-container m-2 lg:my-20 lg:mx-64">
|
||||
<div class="card ~critical sectioned">
|
||||
<section class="section ~critical">
|
||||
<section class="section ~critical flex flex-col gap-2">
|
||||
<span class="heading">Crash report for jfa-go</span>
|
||||
{{ if .Err }}
|
||||
<div class="font-mono bg-inherit pre-line mt-4 mb-4">
|
||||
<div class="font-mono bg-inherit pre-line">
|
||||
Error: {{ .Err }}
|
||||
</div>
|
||||
{{ end }}
|
||||
<a class="button ~critical mb-4" target="_blank" href="https://github.com/hrfee/jfa-go/issues/new/choose">Create an Issue</a>
|
||||
<a class="button ~critical w-full center" target="_blank" href="https://github.com/hrfee/jfa-go/issues/new/choose">Create an Issue</a>
|
||||
</section>
|
||||
<section class="section ~neutral @low">
|
||||
<div class="flex flex-row justify-between">
|
||||
<span class="subheading">Full Log</span>
|
||||
<span class="button ~urge ml-4" id="copy-log">Copy</span>
|
||||
<section class="section ~neutral @low flex flex-col gap-4">
|
||||
<div class="flex flex-row justify-between gap-4">
|
||||
<span class="subheading font-medium">Full Log</span>
|
||||
<span class="button ~urge" id="copy-log">Copy</span>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<label class="col mr-4">
|
||||
<span class="button ~neutral @high supra full-width center" id="button-log-normal">Normal</span>
|
||||
</label>
|
||||
<label class="col mr-4">
|
||||
<span class="button ~neutral @low supra full-width center" id="button-log-sanitized">Sanitized</span>
|
||||
</label>
|
||||
<div class="flex flex-row gap-2 justify-between">
|
||||
<button class="button ~neutral @high supra w-full center" id="button-log-normal">Normal</button>
|
||||
<button class="button ~neutral @low supra w-full center" id="button-log-sanitized">Sanitized</button>
|
||||
</div>
|
||||
<div id="log-normal">
|
||||
<pre class="font-mono bg-inherit pre-line">{{ .Log }}</pre>
|
||||
<pre class="card font-mono bg-inherit pre-line">{{ .Log }}</pre>
|
||||
</div>
|
||||
<div id="log-sanitized" class="unfocused">
|
||||
<p class="subheading">An attempt has been made to remove sensitive info, but make sure to check yourself.</p>
|
||||
<pre class="font-mono bg-inherit pre-line">{{ .SanitizedLog }}</pre>
|
||||
<div id="log-sanitized" class="flex flex-col gap-2 unfocused">
|
||||
<p class="support subheading">An attempt has been made to remove sensitive info, but make sure to check yourself.</p>
|
||||
<pre class="card font-mono bg-inherit pre-line">{{ .SanitizedLog }}</pre>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="{{ .cssClass }}">
|
||||
<html lang="{{ .shortLang }}" dir="{{ .pageDirection }}" class="{{ .cssClass }}">
|
||||
<head>
|
||||
{{ template "header.html" . }}
|
||||
{{ template "header.txt" . }}
|
||||
<title>{{ .strings.successHeader }} - jfa-go</title>
|
||||
</head>
|
||||
<body class="section">
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
window.reCAPTCHASiteKey = "{{ .reCAPTCHASiteKey }}";
|
||||
window.userPageEnabled = {{ .userPageEnabled }};
|
||||
window.userPageAddress = "{{ .userPageAddress }}";
|
||||
window.collectEmail = {{ .collectEmail }};
|
||||
{{ if index . "customSuccessCard" }}
|
||||
window.customSuccessCard = {{ .customSuccessCard }};
|
||||
{{ else }}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="{{ .cssClass }}">
|
||||
<html lang="{{ .shortLang }}" dir="{{ .pageDirection }}" class="{{ .cssClass }}">
|
||||
<head>
|
||||
{{ template "header.html" . }}
|
||||
{{ template "header.txt" . }}
|
||||
{{ if .passwordReset }}
|
||||
<title>{{ .strings.passwordReset }}</title>
|
||||
{{ else }}
|
||||
@@ -41,7 +41,7 @@
|
||||
</div>
|
||||
<div class="card dark:~d_neutral @low">
|
||||
<div class="flex flex-col md:flex-row gap-3 items-baseline mb-2">
|
||||
<span class="heading mr-5">
|
||||
<span class="heading">
|
||||
{{ if .passwordReset }}
|
||||
{{ .strings.passwordReset }}
|
||||
{{ else }}
|
||||
@@ -68,8 +68,10 @@
|
||||
<input type="text" class="input ~neutral @high mt-2 mb-4" placeholder="{{ .strings.username }}" id="create-username" aria-label="{{ .strings.username }}">
|
||||
</label>
|
||||
|
||||
<label class="label supra" for="create-email">{{ .strings.emailAddress }}</label>
|
||||
<input type="email" class="input ~neutral @high mt-2 mb-4" placeholder="{{ .strings.emailAddress }}" id="create-email" aria-label="{{ .strings.emailAddress }}" value="{{ .email }}">
|
||||
<div>
|
||||
<label class="label supra" for="create-email">{{ .strings.emailAddress }}</label>
|
||||
<input type="email" class="input ~neutral @high mt-2 mb-4" placeholder="{{ .strings.emailAddress }}" id="create-email" aria-label="{{ .strings.emailAddress }}" value="{{ .email }}">
|
||||
</div>
|
||||
{{ if .telegramEnabled }}
|
||||
<span class="button ~info @low full-width center mb-4" id="link-telegram">{{ .strings.linkTelegram }} {{ if .telegramRequired }}({{ .strings.required }}){{ end }}</span>
|
||||
{{ end }}
|
||||
@@ -80,23 +82,23 @@
|
||||
<span class="button ~info @low full-width center mb-4" id="link-matrix">{{ .strings.linkMatrix }} {{ if .matrixRequired }}({{ .strings.required }}){{ end }}</span>
|
||||
{{ end }}
|
||||
{{ if or (.telegramEnabled) (or .discordEnabled .matrixEnabled) }}
|
||||
<div id="contact-via" class="unfocused">
|
||||
<label class="row switch pb-4 unfocused">
|
||||
<input type="checkbox" name="contact-via" value="email" id="contact-via-email" class="mr-2"><span>Contact through Email</span>
|
||||
<div id="contact-via" class="unfocused flex flex-col gap-2">
|
||||
<label class="flex flex-row gap-2 switch unfocused">
|
||||
<input type="checkbox" name="contact-via" value="email" id="contact-via-email"><span>Contact through Email</span>
|
||||
</label>
|
||||
{{ if .telegramEnabled }}
|
||||
<label class="row switch pb-4 unfocused">
|
||||
<input type="checkbox" name="contact-via" value="telegram" id="contact-via-telegram" class="mr-2"><span>Contact through Telegram</span>
|
||||
<label class="flex flex-row gap-2 switch unfocused">
|
||||
<input type="checkbox" name="contact-via" value="telegram" id="contact-via-telegram"><span>Contact through Telegram</span>
|
||||
</label>
|
||||
{{ end }}
|
||||
{{ if .discordEnabled }}
|
||||
<label class="row switch pb-4 unfocused">
|
||||
<input type="checkbox" name="contact-via" value="discord" id="contact-via-discord" class="mr-2"><span>Contact through Discord</span>
|
||||
<label class="flex flex-row gap-2 switch unfocused">
|
||||
<input type="checkbox" name="contact-via" value="discord" id="contact-via-discord"><span>Contact through Discord</span>
|
||||
</label>
|
||||
{{ end }}
|
||||
{{ if .matrixEnabled }}
|
||||
<label class="row switch pb-4 unfocused">
|
||||
<input type="checkbox" name="contact-via" value="matrix" id="contact-via-matrix" class="mr-2"><span>Contact through Matrix</span>
|
||||
<label class="flex flex-row gap-2 switch unfocused">
|
||||
<input type="checkbox" name="contact-via" value="matrix" id="contact-via-matrix"><span>Contact through Matrix</span>
|
||||
</label>
|
||||
{{ end }}
|
||||
</div>
|
||||
@@ -123,6 +125,11 @@
|
||||
{{ if .fromUser }}
|
||||
<aside class="col aside sm ~positive mb-4" id="invite-from-user" data-from="{{ .fromUser }}">{{ .strings.invitedBy }}</aside>
|
||||
{{ end }}
|
||||
{{ if .preSignupCard }}
|
||||
<div class="card @low dark:~d_neutral break-words content">
|
||||
{{ .preSignupCardContent }}
|
||||
</div>
|
||||
{{ end }}
|
||||
<div class="card ~neutral @low mb-4">
|
||||
<span class="label supra">{{ .strings.passwordRequirementsHeader }}</span>
|
||||
<ul>
|
||||
@@ -134,11 +141,19 @@
|
||||
</ul>
|
||||
</div>
|
||||
{{ if .captcha }}
|
||||
<div class="card ~neutral @low mb-4">
|
||||
<span class="label supra mb-2">CAPTCHA {{ if not .reCAPTCHA }}<span id="captcha-regen" title="{{ .strings.refresh }}" class="badge lg @low ~info ml-2 float-right"><i class="ri-refresh-line"></i></span><span id="captcha-success" class="badge lg @low ~critical ml-2 float-right"><i class="ri-close-line"></i></span>{{ end }}</span>
|
||||
<div id="captcha-img" class="mt-2 mb-2 {{ if .reCAPTCHA }}g-recaptcha{{ end }}"></div>
|
||||
<div class="card ~neutral @low mb-4 flex flex-col gap-2">
|
||||
<div class="flex flex-row justify-between gap-2">
|
||||
<span class="label supra">CAPTCHA</span>
|
||||
{{ if not .reCAPTCHA }}
|
||||
<div class="flex flex-row gap-2">
|
||||
<button id="captcha-regen" aria-label="{{ .strings.refresh }}" title="{{ .strings.refresh }}" class="badge lg @low ~info"><i class="ri-refresh-line"></i></button>
|
||||
<span id="captcha-success" class="badge lg @low ~critical"><i class="ri-close-line"></i></span>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div id="captcha-img" class="{{ if .reCAPTCHA }}g-recaptcha{{ end }}"></div>
|
||||
{{ if not .reCAPTCHA }}
|
||||
<input class="field ~neutral @low" id="captcha-input" class="mt-2" placeholder="CAPTCHA">
|
||||
<input class="field ~neutral @low" id="captcha-input" placeholder="CAPTCHA">
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="Description" content="jfa-go, a better way to manage Jellyfin users.">
|
||||
<meta name="color-scheme" content="dark light">
|
||||
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
|
||||
<meta name="theme-color" content="#101010" media="(prefers-color-scheme: dark)">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<meta name="robots" content="noindex">
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ .pages.Base }}/apple-touch-icon.png">
|
||||
@@ -11,7 +14,6 @@
|
||||
<link rel="manifest" href="{{ .pages.Base }}/site.webmanifest">
|
||||
<link rel="mask-icon" href="{{ .pages.Base }}/safari-pinned-tab.svg" color="#5bbad5">
|
||||
<meta name="msapplication-TileColor" content="#603cba">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<script>
|
||||
window.pages = {
|
||||
"Base": "{{ .pages.Base }}",
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="{{ .cssClass }}">
|
||||
<html lang="{{ .shortLang }}" dir="{{ .pageDirection }}" class="{{ .cssClass }}">
|
||||
<head>
|
||||
{{ template "header.html" . }}
|
||||
{{ template "header.txt" . }}
|
||||
<title>Invalid Code - jfa-go</title>
|
||||
</head>
|
||||
<body class="section">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<span class="dropdown z-[11]" tabindex="0" id="lang-dropdown">
|
||||
<span class="button ~urge dropdown-button">
|
||||
<i class="ri-global-line"></i>
|
||||
<span class="ml-2 chev"></span>
|
||||
<span class="button ~urge dropdown-button flex flex-row gap-2 h-full" title="{{ .strings.language }}" aria-label="{{ .strings.language }}">
|
||||
<i class="icon ri-global-line"></i>
|
||||
<i class="icon ri-arrow-down-s-line"></i>
|
||||
</span>
|
||||
<div class="dropdown-display">
|
||||
<div class="card ~neutral @low flex flex-col gap-2">
|
||||
@@ -13,7 +13,7 @@
|
||||
<input type="radio" name="lang-time" id="lang-24h">
|
||||
<span>{{ .strings.time24h }}</span>
|
||||
</label>
|
||||
<div id="lang-list"></div>
|
||||
<div id="lang-list" class="flex flex-col gap-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
@@ -15,23 +15,23 @@
|
||||
{{ $hasTwoCards = 1 }}
|
||||
<div class="card mx-2 flex-initial w-full lg:w-[35%] mb-4 lg:mb-0 dark:~d_neutral @low content">
|
||||
<span class="heading row">{{ .strings.loginNotAdmin }}</span>
|
||||
<a class="button ~info h-12 w-full" href="{{ .pages.Base }}{{ .pages.MyAccount }}"><i class="ri-account-circle-fill mr-2"></i>{{ .strings.myAccount }}</a>
|
||||
<a class="button ~info h-12 w-full flex flex-row gap-2" href="{{ .pages.Base }}{{ .pages.MyAccount }}"><i class="ri-account-circle-fill"></i>{{ .strings.myAccount }}</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
<form class="card mx-2 form-login w-full {{ if eq $hasTwoCards 1 }}lg:w-[55%]{{ end }} mb-0" href="">
|
||||
<form class="card mx-2 form-login w-full flex flex-col gap-2 {{ if eq $hasTwoCards 1 }}lg:w-[55%]{{ end }} mb-0" href="">
|
||||
<span class="heading">{{ .strings.login }}</span>
|
||||
<input type="text" class="field input ~neutral @high mt-4 mb-2" placeholder="{{ .strings.username }}" id="login-user">
|
||||
<input type="password" class="field input ~neutral @high mb-4" placeholder="{{ .strings.password }}" id="login-password">
|
||||
<input type="text" class="field input ~neutral @high" placeholder="{{ .strings.username }}" id="login-user">
|
||||
<input type="password" class="field input ~neutral @high" placeholder="{{ .strings.password }}" id="login-password">
|
||||
<label>
|
||||
<input type="submit" class="unfocused">
|
||||
<span class="button ~urge @low full-width center supra submit">{{ .strings.login }}</span>
|
||||
{{ if index . "pwrEnabled" }}
|
||||
{{ if .pwrEnabled }}
|
||||
<span class="button ~info @low full-width center supra submit my-2" id="modal-login-pwr">{{ .strings.resetPassword }}</span>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</label>
|
||||
{{ if index . "pwrEnabled" }}
|
||||
{{ if .pwrEnabled }}
|
||||
<span class="button ~info @low full-width center supra" id="modal-login-pwr">{{ .strings.resetPassword }}</span>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="{{ .cssClass }}">
|
||||
<html lang="{{ .shortLang }}" dir="{{ .pageDirection }}" class="{{ .cssClass }}">
|
||||
<head>
|
||||
{{ template "header.html" . }}
|
||||
{{ template "header.txt" . }}
|
||||
<title>{{ .strings.passwordReset }} - jfa-go</title>
|
||||
</head>
|
||||
<body class="section">
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="light">
|
||||
<html lang="{{ .shortLang }}" dir="{{ .pageDirection }}" class="light">
|
||||
<head>
|
||||
{{ template "header.html" . }}
|
||||
{{ template "header.txt" . }}
|
||||
<title>{{ .lang.Strings.pageTitle }}</title>
|
||||
</head>
|
||||
<body class="max-w-full overflow-x-hidden section">
|
||||
<div id="notification-box"></div>
|
||||
<div class="page-container m-2 lg:my-20 lg:mx-64 flex flex-col gap-4 items-center">
|
||||
<div class="page-container m-2 lg:my-20 lg:mx-64 flex flex-col gap-4">
|
||||
<div class="top-2 inset-x-2 lg:absolute flex flex-row justify-between">
|
||||
<div class="flex flex-row gap-2">
|
||||
{{ template "lang-select.html" . }}
|
||||
@@ -322,10 +322,10 @@
|
||||
</label>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="label flex flex-col gap-2">
|
||||
<div class="switch"><input type="radio" class="mr-2" name="email-24h" value="true" checked><span>{{ .lang.Strings.time24h }}</span></div>
|
||||
<div class="switch flex flex-row gap-2"><input type="radio" name="email-24h" value="true" checked><span>{{ .lang.Strings.time24h }}</span></div>
|
||||
</label>
|
||||
<label class="label flex flex-col gap-2">
|
||||
<div class="switch"><input type="radio" class="mr-2" name="email-24h" value="false"><span>{{ .lang.Strings.time12h }}</span></div>
|
||||
<div class="switch flex flex-row gap-2"><input type="radio" name="email-24h" value="false"><span>{{ .lang.Strings.time12h }}</span></div>
|
||||
</label>
|
||||
</div>
|
||||
<div id="email-sect" class="flex flex-row gap-2 justify-between">
|
||||
|
||||
3
html/syntaxhighlighting.txt
Normal file
3
html/syntaxhighlighting.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
<link rel="stylesheet" type="text/css" href="{{ .pages.Base }}/css/{{ .cssVersion }}highlightjs-light.css" data-theme="light">
|
||||
<link rel="stylesheet" type="text/css" href="{{ .pages.Base }}/css/{{ .cssVersion }}highlightjs-dark.css" data-theme="dark">
|
||||
<link rel="stylesheet" type="text/css" href="{{ .pages.Base }}/css/{{ .cssVersion }}code-input.css">
|
||||
106
html/user.html
106
html/user.html
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="light">
|
||||
<html lang="{{ .shortLang }}" dir="{{ .pageDirection }}" class="light">
|
||||
<head>
|
||||
<script>
|
||||
window.langFile = JSON.parse({{ .language }});
|
||||
@@ -17,52 +17,50 @@
|
||||
window.matrixUserID = "{{ .matrixUser }}";
|
||||
window.validationStrings = JSON.parse({{ .validationStrings }});
|
||||
</script>
|
||||
{{ template "header.html" . }}
|
||||
{{ template "header.txt" . }}
|
||||
<title>{{ .strings.myAccount }}</title>
|
||||
</head>
|
||||
<body class="max-w-full overflow-x-hidden section">
|
||||
<div id="modal-email" class="modal">
|
||||
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3">
|
||||
<div class="content">
|
||||
<span class="heading mb-4 my-2"></span>
|
||||
<label class="label supra row m-1" for="modal-email-input">{{ .strings.emailAddress }}</label>
|
||||
<div class="row">
|
||||
<input type="email" class="col sm field ~neutral @low input" id="modal-email-input" placeholder="{{ .strings.emailAddress }}">
|
||||
</div>
|
||||
<button class="button ~urge @low supra full-width center lg my-2 modal-submit">{{ .strings.submit }}</button>
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="heading"></span>
|
||||
<label class="label flex flex-col gap-2">
|
||||
<span class="supra">{{ .strings.emailAddress }}</span>
|
||||
<input type="email" class="field ~neutral @low input" id="modal-email-input" placeholder="{{ .strings.emailAddress }}">
|
||||
</label>
|
||||
<button class="button ~urge @low supra full-width center lg modal-submit">{{ .strings.submit }}</button>
|
||||
</div>
|
||||
<div class="confirmation-required unfocused">
|
||||
<span class="heading mb-4">{{ .strings.confirmationRequired }} <span class="modal-close">×</span></span>
|
||||
<p class="content mb-4">{{ .strings.confirmationRequiredMessage }}</p>
|
||||
<div class="confirmation-required unfocused flex flex-col gap-2">
|
||||
<span class="heading">{{ .strings.confirmationRequired }} <span class="modal-close">×</span></span>
|
||||
<p class="content">{{ .strings.confirmationRequiredMessage }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ if .pwrEnabled }}
|
||||
<div id="modal-pwr" class="modal">
|
||||
<div class="card content relative mx-auto my-[10%] w-4/5 lg:w-1/3 ~neutral @low">
|
||||
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3 ~neutral @low flex flex-col gap-2">
|
||||
<span class="heading">{{ .strings.resetPassword }}</span>
|
||||
<p class="content my-2">
|
||||
{{ if .linkResetEnabled }}
|
||||
{{ .strings.resetPasswordThroughLinkStart }}
|
||||
<ul class="content">
|
||||
{{ if .resetPasswordUsername }}<li>{{ .strings.resetPasswordUsername }}</li>{{ end }}
|
||||
{{ if .resetPasswordEmail }}<li>{{ .strings.resetPasswordEmail }}</li>{{ end }}
|
||||
{{ if .resetPasswordContactMethod }}<li>{{ .strings.resetPasswordContactMethod }}</li>{{ end }}
|
||||
</ul>
|
||||
{{ .strings.resetPasswordThroughLinkEnd }}
|
||||
{{ else }}
|
||||
{{ .strings.resetPasswordThroughJellyfin }}
|
||||
{{ end }}
|
||||
</p>
|
||||
<div class="row">
|
||||
<input type="text" class="col sm field ~neutral @low input" id="pwr-address" placeholder="username | example@example.com | user#1234 | @user:host | @username">
|
||||
<div class="content">
|
||||
{{ if .linkResetEnabled }}
|
||||
<p>{{ .strings.resetPasswordThroughLinkStart }}</p>
|
||||
<ul class="content">
|
||||
{{ if .resetPasswordUsername }}<li>{{ .strings.resetPasswordUsername }}</li>{{ end }}
|
||||
{{ if .resetPasswordEmail }}<li>{{ .strings.resetPasswordEmail }}</li>{{ end }}
|
||||
{{ if .resetPasswordContactMethod }}<li>{{ .strings.resetPasswordContactMethod }}</li>{{ end }}
|
||||
</ul>
|
||||
<p>{{ .strings.resetPasswordThroughLinkEnd }}</p>
|
||||
{{ else }}
|
||||
<p>{{ .strings.resetPasswordThroughJellyfin }}</p>
|
||||
{{ end }}
|
||||
</div>
|
||||
<input type="text" class="col sm field ~neutral @low input" id="pwr-address" placeholder="username | example@example.com | user#1234 | @user:host | @username">
|
||||
{{ if .linkResetEnabled }}
|
||||
<span class="button ~info @low full-width center mt-4" id="pwr-submit">
|
||||
<span class="button ~info @low full-width center" id="pwr-submit">
|
||||
{{ .strings.submit }}
|
||||
</span>
|
||||
{{ else }}
|
||||
<a class="button ~info @low full-width center mt-4" href="{{ .jfLink }}" target="_blank">{{ .strings.continue }}</a>
|
||||
<a class="button ~info @low full-width center" href="{{ .jfLink }}" target="_blank">{{ .strings.continue }}</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -75,12 +73,12 @@
|
||||
<div class="flex flex-row gap-2">
|
||||
{{ template "lang-select.html" . }}
|
||||
<span class="button ~warning h-min" alt="{{ .strings.theme }}" id="button-theme"><i class="ri-sun-line"></i></span>
|
||||
<span class="button ~critical @low mb-4 unfocused" id="logout-button">{{ .strings.logout }}</span>
|
||||
<span class="button ~critical @low unfocused" id="logout-button">{{ .strings.logout }}</span>
|
||||
</div>
|
||||
<a class="button ~info unfocused h-min" href="/" id="admin-back-button"><i class="ri-arrow-left-fill mr-2"></i>{{ .strings.admin }}</a>
|
||||
<a class="button ~info unfocused h-min flex flex-row gap-1" href="/" id="admin-back-button"><i class="ri-arrow-left-fill"></i>{{ .strings.admin }}</a>
|
||||
</div>
|
||||
<div class="card @low dark:~d_neutral mb-4" id="card-user">
|
||||
<span class="heading mb-2"></span>
|
||||
<div class="card @low dark:~d_neutral" id="card-user">
|
||||
<span class="heading flex flex-row gap-4"></span>
|
||||
</div>
|
||||
<div class="columns-1 sm:columns-2 gap-4" id="user-cardlist">
|
||||
{{ if index . "PageMessageEnabled" }}
|
||||
@@ -90,15 +88,15 @@
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
<div class="card @low dark:~d_neutral flex-col" id="card-contact">
|
||||
<span class="heading mb-2">{{ .strings.contactMethods }}</span>
|
||||
<div class="card @low dark:~d_neutral flex flex-col gap-2" id="card-contact">
|
||||
<span class="heading">{{ .strings.contactMethods }}</span>
|
||||
<div class="content flex justify-between flex-col h-100"></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="card @low dark:~d_neutral content" id="card-password">
|
||||
<span class="heading row mb-2">{{ .strings.changePassword }}</span>
|
||||
<div class="">
|
||||
<div class="my-2">
|
||||
<div class="card @low dark:~d_neutral flex flex-col gap-2" id="card-password">
|
||||
<span class="heading">{{ .strings.changePassword }}</span>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="content">
|
||||
<span class="label supra row">{{ .strings.passwordRequirementsHeader }}</span>
|
||||
<ul>
|
||||
{{ range $key, $value := .requirements }}
|
||||
@@ -108,15 +106,15 @@
|
||||
{{ end }}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="my-2">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="label supra" for="user-old-password">{{ .strings.oldPassword }}</label>
|
||||
<input type="password" class="input ~neutral @low mt-2 mb-4" placeholder="{{ .strings.password }}" id="user-old-password" aria-label="{{ .strings.oldPassword }}">
|
||||
<input type="password" class="input ~neutral @low" placeholder="{{ .strings.password }}" id="user-old-password" aria-label="{{ .strings.oldPassword }}">
|
||||
<label class="label supra" for="user-new-password">{{ .strings.newPassword }}</label>
|
||||
<input type="password" class="input ~neutral @low mt-2 mb-4" placeholder="{{ .strings.password }}" id="user-new-password" aria-label="{{ .strings.newPassword }}">
|
||||
<input type="password" class="input ~neutral @low" placeholder="{{ .strings.password }}" id="user-new-password" aria-label="{{ .strings.newPassword }}">
|
||||
|
||||
<label class="label supra" for="user-reenter-password">{{ .strings.reEnterPassword }}</label>
|
||||
<input type="password" class="input ~neutral @low mt-2 mb-4" placeholder="{{ .strings.password }}" id="user-reenter-new-password" aria-label="{{ .strings.reEnterPassword }}">
|
||||
<span class="button ~info @low full-width center mt-4" id="user-password-submit">
|
||||
<input type="password" class="input ~neutral @low" placeholder="{{ .strings.password }}" id="user-reenter-new-password" aria-label="{{ .strings.reEnterPassword }}">
|
||||
<span class="button ~info @low full-width center" id="user-password-submit">
|
||||
{{ .strings.changePassword }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -124,21 +122,21 @@
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="card @low dark:~d_neutral unfocused" id="card-status">
|
||||
<span class="heading mb-2">{{ .strings.expiry }}</span>
|
||||
<aside class="aside ~warning user-expiry my-4"></aside>
|
||||
<div class="card @low dark:~d_neutral unfocused flex flex-col gap-2" id="card-status">
|
||||
<span class="heading">{{ .strings.expiry }}</span>
|
||||
<aside class="aside ~warning user-expiry"></aside>
|
||||
<div class="user-expiry-countdown"></div>
|
||||
</div>
|
||||
</div>
|
||||
{{ if .referralsEnabled }}
|
||||
<div>
|
||||
<div class="card @low dark:~d_neutral unfocused" id="card-referrals">
|
||||
<span class="heading mb-2">{{ .strings.referrals }}</span>
|
||||
<aside class="aside ~neutral my-4 col user-referrals-description"></aside>
|
||||
<div class="card @low dark:~d_neutral unfocused flex flex-col gap-2" id="card-referrals">
|
||||
<span class="heading">{{ .strings.referrals }}</span>
|
||||
<aside class="aside ~neutral col user-referrals-description"></aside>
|
||||
<div class="flex flex-row justify-between gap-2">
|
||||
<div class="user-referrals-info"></div>
|
||||
<div class="grid my-2">
|
||||
<button type="button" class="user-referrals-button button ~info dark:~d_info @low" title="Copy">{{ .strings.copyReferral }}<i class="ri-file-copy-line ml-2"></i></button>
|
||||
<div class="user-referrals-info flex flex-col gap-2"></div>
|
||||
<div class="grid">
|
||||
<button type="button" class="user-referrals-button button ~info dark:~d_info @low flex flex-row gap-2" title="Copy">{{ .strings.copyReferral }}<i class="ri-file-copy-line"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,7 @@ const binaryType = "internal"
|
||||
|
||||
func BuildTagsExternal() {}
|
||||
|
||||
//go:embed data data/html data/web data/web/css data/web/js
|
||||
//go:embed build/data build/data/html build/data/web build/data/web/css build/data/web/js
|
||||
var loFS embed.FS
|
||||
|
||||
//go:embed lang/common lang/admin lang/email lang/form lang/setup lang/pwreset lang/telegram
|
||||
@@ -38,6 +38,6 @@ func FSJoin(elem ...string) string {
|
||||
|
||||
func loadFilesystems(rootDir string, logger *logger.Logger) {
|
||||
langFS = rewriteFS{laFS, "lang/"}
|
||||
localFS = rewriteFS{loFS, "data/"}
|
||||
localFS = rewriteFS{loFS, "build/data/"}
|
||||
logger.Println("Using internal storage")
|
||||
}
|
||||
|
||||
@@ -2,14 +2,20 @@ package main
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hrfee/jfa-go/jellyseerr"
|
||||
lm "github.com/hrfee/jfa-go/logmessages"
|
||||
)
|
||||
|
||||
type JellyseerrInitialSyncStatus struct {
|
||||
Done bool
|
||||
}
|
||||
|
||||
// Ensure the Jellyseerr cache is up to date before calling.
|
||||
func (app *appContext) SynchronizeJellyseerrUser(jfID string) {
|
||||
user, imported, err := app.js.GetOrImportUser(jfID)
|
||||
user, imported, err := app.js.GetOrImportUser(jfID, true)
|
||||
if err != nil {
|
||||
app.debug.Printf(lm.FailedImportUser, lm.Jellyseerr, jfID, err)
|
||||
return
|
||||
@@ -28,7 +34,11 @@ func (app *appContext) SynchronizeJellyseerrUser(jfID string) {
|
||||
if ok && email.Addr != "" && user.Email != email.Addr {
|
||||
err = app.js.ModifyMainUserSettings(jfID, jellyseerr.MainUserSettings{Email: email.Addr})
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedSetEmailAddress, lm.Jellyseerr, jfID, err)
|
||||
if strings.Contains(err.Error(), "INVALID_EMAIL") {
|
||||
app.err.Printf(lm.FailedSetEmailAddress, lm.Jellyseerr, jfID, err.Error()+"\""+email.Addr+"\"")
|
||||
} else {
|
||||
app.err.Printf(lm.FailedSetEmailAddress, lm.Jellyseerr, jfID, err)
|
||||
}
|
||||
} else {
|
||||
contactMethods[jellyseerr.FieldEmailEnabled] = email.Contact
|
||||
}
|
||||
@@ -45,7 +55,7 @@ func (app *appContext) SynchronizeJellyseerrUser(jfID string) {
|
||||
chatID, _ := strconv.ParseInt(notif.TelegramChatID, 10, 64)
|
||||
if ok && tgUser.ChatID != 0 && chatID != tgUser.ChatID {
|
||||
u, _ := app.storage.GetTelegramKey(jfID)
|
||||
contactMethods[jellyseerr.FieldTelegram] = u.ChatID
|
||||
contactMethods[jellyseerr.FieldTelegram] = strconv.FormatInt(u.ChatID, 10)
|
||||
contactMethods[jellyseerr.FieldTelegramEnabled] = tgUser.Contact
|
||||
}
|
||||
}
|
||||
@@ -58,19 +68,30 @@ func (app *appContext) SynchronizeJellyseerrUser(jfID string) {
|
||||
}
|
||||
|
||||
func (app *appContext) SynchronizeJellyseerrUsers() {
|
||||
jsSync := JellyseerrInitialSyncStatus{}
|
||||
app.storage.db.Get("jellyseerr_inital_sync_status", &jsSync)
|
||||
if jsSync.Done {
|
||||
return
|
||||
}
|
||||
|
||||
users, err := app.jf.GetUsers(false)
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err)
|
||||
return
|
||||
}
|
||||
app.js.ReloadCache()
|
||||
// I'm sure Jellyseerr can handle it,
|
||||
// but past issues with the Jellyfin db scare me from
|
||||
// running these concurrently. W/e, its a bg task anyway.
|
||||
for _, user := range users {
|
||||
app.SynchronizeJellyseerrUser(user.ID)
|
||||
}
|
||||
// Don't run again until this flag is unset
|
||||
// Stored in the DB as it's not something the user needs to see.
|
||||
app.storage.db.Upsert("jellyseerr_inital_sync_status", JellyseerrInitialSyncStatus{true})
|
||||
}
|
||||
|
||||
// Not really a normal daemon, since it'll only fire once when the feature is enabled.
|
||||
func newJellyseerrDaemon(interval time.Duration, app *appContext) *GenericDaemon {
|
||||
d := NewGenericDaemon(interval, app,
|
||||
func(app *appContext) {
|
||||
@@ -78,5 +99,12 @@ func newJellyseerrDaemon(interval time.Duration, app *appContext) *GenericDaemon
|
||||
},
|
||||
)
|
||||
d.Name("Jellyseerr import")
|
||||
|
||||
jsSync := JellyseerrInitialSyncStatus{}
|
||||
app.storage.db.Get("jellyseerr_inital_sync_status", &jsSync)
|
||||
if jsSync.Done {
|
||||
return nil
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
@@ -25,7 +25,9 @@ type Jellyseerr struct {
|
||||
server, key string
|
||||
header map[string]string
|
||||
httpClient *http.Client
|
||||
userCache map[string]User // Map of jellyfin IDs to users
|
||||
userCache map[string]User // Map of jellyfin IDs to users
|
||||
jsToJfID map[int64]string // Map of jellyseerr IDs to jellyfin IDs
|
||||
invalidatedUsers map[int64]bool // Map of jellyseerr IDs needing a re-caching
|
||||
cacheExpiry time.Time
|
||||
cacheLength time.Duration
|
||||
timeoutHandler co.TimeoutHandler
|
||||
@@ -51,6 +53,8 @@ func NewJellyseerr(server, key string, timeoutHandler co.TimeoutHandler) *Jellys
|
||||
cacheExpiry: time.Now(),
|
||||
timeoutHandler: timeoutHandler,
|
||||
userCache: map[string]User{},
|
||||
jsToJfID: map[int64]string{},
|
||||
invalidatedUsers: map[int64]bool{},
|
||||
LogRequestBodies: false,
|
||||
}
|
||||
}
|
||||
@@ -92,8 +96,9 @@ func (js *Jellyseerr) req(mode string, uri string, data any, queryParams url.Val
|
||||
var responseText string
|
||||
defer resp.Body.Close()
|
||||
if response || err != nil {
|
||||
responseText, err = js.decodeResp(resp)
|
||||
if err != nil {
|
||||
var decodeErr error
|
||||
responseText, decodeErr = js.decodeResp(resp)
|
||||
if decodeErr != nil {
|
||||
return responseText, resp.StatusCode, err
|
||||
}
|
||||
}
|
||||
@@ -157,6 +162,7 @@ func (js *Jellyseerr) ImportFromJellyfin(jfIDs ...string) ([]User, error) {
|
||||
for _, u := range data {
|
||||
if u.JellyfinUserID != "" {
|
||||
js.userCache[u.JellyfinUserID] = u
|
||||
js.jsToJfID[u.ID] = u.JellyfinUserID
|
||||
}
|
||||
}
|
||||
return data, err
|
||||
@@ -165,8 +171,13 @@ func (js *Jellyseerr) ImportFromJellyfin(jfIDs ...string) ([]User, error) {
|
||||
func (js *Jellyseerr) getUsers() error {
|
||||
if js.cacheExpiry.After(time.Now()) {
|
||||
return nil
|
||||
if len(js.invalidatedUsers) != 0 {
|
||||
return js.getInvalidatedUsers()
|
||||
}
|
||||
}
|
||||
js.cacheExpiry = time.Now().Add(js.cacheLength)
|
||||
userCache := map[string]User{}
|
||||
jsToJfID := map[int64]string{}
|
||||
pageCount := 1
|
||||
pageIndex := 0
|
||||
for {
|
||||
@@ -178,7 +189,8 @@ func (js *Jellyseerr) getUsers() error {
|
||||
if u.JellyfinUserID == "" {
|
||||
continue
|
||||
}
|
||||
js.userCache[u.JellyfinUserID] = u
|
||||
userCache[u.JellyfinUserID] = u
|
||||
jsToJfID[u.ID] = u.JellyfinUserID
|
||||
}
|
||||
pageCount = res.Page.Pages
|
||||
pageIndex++
|
||||
@@ -186,6 +198,10 @@ func (js *Jellyseerr) getUsers() error {
|
||||
break
|
||||
}
|
||||
}
|
||||
js.userCache = userCache
|
||||
js.jsToJfID = jsToJfID
|
||||
js.invalidatedUsers = map[int64]bool{}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -206,15 +222,15 @@ func (js *Jellyseerr) getUserPage(page int) (GetUsersDTO, error) {
|
||||
}
|
||||
|
||||
func (js *Jellyseerr) MustGetUser(jfID string) (User, error) {
|
||||
u, _, err := js.GetOrImportUser(jfID)
|
||||
u, _, err := js.GetOrImportUser(jfID, false)
|
||||
return u, err
|
||||
}
|
||||
|
||||
// GetImportedUser provides the same function as ImportFromJellyfin, but will always return the user,
|
||||
// even if they already existed. Also returns whether the user was imported or not,
|
||||
func (js *Jellyseerr) GetOrImportUser(jfID string) (u User, imported bool, err error) {
|
||||
func (js *Jellyseerr) GetOrImportUser(jfID string, fixedCache bool) (u User, imported bool, err error) {
|
||||
imported = false
|
||||
u, err = js.GetExistingUser(jfID)
|
||||
u, err = js.GetExistingUser(jfID, fixedCache)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
@@ -232,15 +248,24 @@ func (js *Jellyseerr) GetOrImportUser(jfID string) (u User, imported bool, err e
|
||||
return
|
||||
}
|
||||
|
||||
func (js *Jellyseerr) GetExistingUser(jfID string) (u User, err error) {
|
||||
func (js *Jellyseerr) GetExistingUser(jfID string, fixedCache bool) (u User, err error) {
|
||||
js.getUsers()
|
||||
ok := false
|
||||
err = nil
|
||||
if u, ok = js.userCache[jfID]; ok {
|
||||
u, ok = js.userCache[jfID]
|
||||
_, invalidated := js.invalidatedUsers[u.ID]
|
||||
if ok && !invalidated {
|
||||
return
|
||||
}
|
||||
js.cacheExpiry = time.Now()
|
||||
js.getUsers()
|
||||
if invalidated {
|
||||
err = js.getInvalidatedUsers()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else if !fixedCache {
|
||||
js.cacheExpiry = time.Now()
|
||||
js.getUsers()
|
||||
}
|
||||
if u, ok = js.userCache[jfID]; ok {
|
||||
err = nil
|
||||
return
|
||||
@@ -253,7 +278,7 @@ func (js *Jellyseerr) getUser(jfID string) (User, error) {
|
||||
if js.AutoImportUsers {
|
||||
return js.MustGetUser(jfID)
|
||||
}
|
||||
return js.GetExistingUser(jfID)
|
||||
return js.GetExistingUser(jfID, false)
|
||||
}
|
||||
|
||||
func (js *Jellyseerr) Me() (User, error) {
|
||||
@@ -267,6 +292,25 @@ func (js *Jellyseerr) Me() (User, error) {
|
||||
return data, err
|
||||
}
|
||||
|
||||
func (js *Jellyseerr) getInvalidatedUsers() error {
|
||||
// FIXME: Collect errors and return
|
||||
for jellyseerrID, _ := range js.invalidatedUsers {
|
||||
jfID, ok := js.jsToJfID[jellyseerrID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
user, err := js.UserByID(jellyseerrID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
js.userCache[jfID] = user
|
||||
js.jsToJfID[jellyseerrID] = jfID
|
||||
delete(js.invalidatedUsers, jellyseerrID)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (js *Jellyseerr) GetPermissions(jfID string) (Permissions, error) {
|
||||
data := permissionsDTO{Permissions: -1}
|
||||
u, err := js.getUser(jfID)
|
||||
@@ -294,6 +338,7 @@ func (js *Jellyseerr) SetPermissions(jfID string, perm Permissions) error {
|
||||
}
|
||||
u.Permissions = perm
|
||||
js.userCache[jfID] = u
|
||||
js.jsToJfID[u.ID] = jfID
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -309,6 +354,7 @@ func (js *Jellyseerr) ApplyTemplateToUser(jfID string, tmpl UserTemplate) error
|
||||
}
|
||||
u.UserTemplate = tmpl
|
||||
js.userCache[jfID] = u
|
||||
js.jsToJfID[u.ID] = jfID
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -325,8 +371,7 @@ func (js *Jellyseerr) ModifyUser(jfID string, conf map[UserField]any) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Lazily just invalidate the cache.
|
||||
js.cacheExpiry = time.Now()
|
||||
js.invalidatedUsers[u.ID] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -412,12 +457,19 @@ func (js *Jellyseerr) ModifyMainUserSettings(jfID string, conf MainUserSettings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return js.ModifyMainUserSettingsByID(u.ID, conf)
|
||||
}
|
||||
|
||||
_, _, err = js.post(fmt.Sprintf(js.server+"/user/%d/settings/main", u.ID), conf, false)
|
||||
func (js *Jellyseerr) ModifyMainUserSettingsByID(jellyseerrID int64, conf MainUserSettings) error {
|
||||
_, _, err := js.post(fmt.Sprintf(js.server+"/user/%d/settings/main", jellyseerrID), conf, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Lazily just invalidate the cache.
|
||||
js.cacheExpiry = time.Now()
|
||||
js.invalidatedUsers[jellyseerrID] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (js *Jellyseerr) ReloadCache() error {
|
||||
js.cacheExpiry = time.Now()
|
||||
return js.getUsers()
|
||||
}
|
||||
|
||||
248
jf_activity.go
Normal file
248
jf_activity.go
Normal file
@@ -0,0 +1,248 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hrfee/mediabrowser"
|
||||
)
|
||||
|
||||
const (
|
||||
// ActivityLimit is the maximum number of ActivityLogEntries to keep in memory.
|
||||
// The array they are stored in is fixed, so (ActivityLimit*unsafe.Sizeof(mediabrowser.ActivityLogEntry))
|
||||
// At writing ActivityLogEntries take up ~160 bytes each, so 1M of memory gives us room for ~6250 records
|
||||
ActivityLimit int = 1e6 / 160
|
||||
// If ByUserLimitLength is true, ByUserLengthOrBaseLength is the maximum number of records attached
|
||||
// to a user.
|
||||
// If false, it is the base amount of entries to allocate for for each user ID, and more will be allocated as needed.
|
||||
ByUserLengthOrBaseLength = 128
|
||||
ByUserLimitLength = false
|
||||
)
|
||||
|
||||
type activityLogEntrySource interface {
|
||||
GetActivityLog(skip, limit int, since time.Time, hasUserID bool) (mediabrowser.ActivityLog, error)
|
||||
}
|
||||
|
||||
// JFActivityCache is a cache for Jellyfin ActivityLogEntries, intended to be refreshed frequently
|
||||
// and suited to it by only querying for changes since the last refresh.
|
||||
type JFActivityCache struct {
|
||||
jf activityLogEntrySource
|
||||
cache [ActivityLimit]mediabrowser.ActivityLogEntry
|
||||
// index into Cache of the entry that should be considered the start (i.e. most recent), and end (i.e. oldest).
|
||||
start, end int
|
||||
// Map of activity entry IDs to their index.
|
||||
byEntryID map[int64]int
|
||||
// Map of user IDs to a slice of entry indexes they are referenced in, chronologically ordered.
|
||||
byUserID map[string][]int
|
||||
LastSync, LastYieldingSync time.Time
|
||||
// Age of cache before it should be refreshed.
|
||||
WaitForSyncTimeout time.Duration
|
||||
syncLock sync.Mutex
|
||||
syncing bool
|
||||
// Total number of entries.
|
||||
Total int
|
||||
dupesInLastSync int
|
||||
}
|
||||
|
||||
func (c *JFActivityCache) debugString() string {
|
||||
var b strings.Builder
|
||||
places := len(strconv.Itoa(ActivityLimit - 1))
|
||||
b.Grow((ActivityLimit * (places + 1) * 2) + 1)
|
||||
for i := range c.cache {
|
||||
fmt.Fprintf(&b, "%0"+strconv.Itoa(places)+"d|", i)
|
||||
}
|
||||
b.WriteByte('\n')
|
||||
for i := range c.cache {
|
||||
fmt.Fprintf(&b, "%0"+strconv.Itoa(places)+"d|", c.cache[i].ID)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// NewJFActivityCache returns a Jellyfin ActivityLogEntry cache.
|
||||
// You should set the timeout low, as events are likely to happen frequently,
|
||||
// and refreshing should be quick anyway
|
||||
func NewJFActivityCache(jf activityLogEntrySource, waitForSyncTimeout time.Duration) *JFActivityCache {
|
||||
c := &JFActivityCache{
|
||||
jf: jf,
|
||||
WaitForSyncTimeout: waitForSyncTimeout,
|
||||
start: -1,
|
||||
end: -1,
|
||||
byEntryID: map[int64]int{},
|
||||
byUserID: map[string][]int{},
|
||||
Total: 0,
|
||||
dupesInLastSync: 0,
|
||||
}
|
||||
for i := range ActivityLimit {
|
||||
c.cache[i].ID = -1
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// ByUserID returns a slice of ActivitLogEntries with the given jellyfin ID attached.
|
||||
func (c *JFActivityCache) ByUserID(jellyfinID string) ([]mediabrowser.ActivityLogEntry, error) {
|
||||
if err := c.MaybeSync(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arr, ok := c.byUserID[jellyfinID]
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
out := make([]mediabrowser.ActivityLogEntry, len(arr))
|
||||
for i, aleIdx := range arr {
|
||||
out[i] = c.cache[aleIdx]
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ByEntryID returns the ActivityLogEntry with the corresponding ID.
|
||||
func (c *JFActivityCache) ByEntryID(entryID int64) (entry mediabrowser.ActivityLogEntry, ok bool, err error) {
|
||||
err = c.MaybeSync()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var idx int
|
||||
idx, ok = c.byEntryID[entryID]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
entry = c.cache[idx]
|
||||
return
|
||||
}
|
||||
|
||||
// MaybeSync returns once the cache is in a suitable state to read:
|
||||
// return if cache is fresh, sync if not, or wait if another sync is happening already.
|
||||
func (c *JFActivityCache) MaybeSync() error {
|
||||
shouldWaitForSync := time.Now().After(c.LastSync.Add(c.WaitForSyncTimeout))
|
||||
|
||||
if !shouldWaitForSync {
|
||||
return nil
|
||||
}
|
||||
|
||||
syncStatus := make(chan error)
|
||||
|
||||
go func(status chan error, c *JFActivityCache) {
|
||||
c.syncLock.Lock()
|
||||
alreadySyncing := c.syncing
|
||||
// We're either already syncing or will be
|
||||
c.syncing = true
|
||||
c.syncLock.Unlock()
|
||||
if !alreadySyncing {
|
||||
// If we haven't synced, this'll just get max (ActivityLimit),
|
||||
// If we have, it'll get anything that's happened since then
|
||||
thisSync := time.Now()
|
||||
al, err := c.jf.GetActivityLog(-1, ActivityLimit, c.LastYieldingSync, true)
|
||||
if err != nil {
|
||||
c.syncLock.Lock()
|
||||
c.syncing = false
|
||||
c.syncLock.Unlock()
|
||||
status <- err
|
||||
return
|
||||
}
|
||||
|
||||
// Can't trust the source fully, so we need to check for anything we've already got stored
|
||||
// -before- we decide where the data should go.
|
||||
recvLength := len(al.Items)
|
||||
c.dupesInLastSync = 0
|
||||
for i, ale := range al.Items {
|
||||
if _, ok := c.byEntryID[ale.ID]; ok {
|
||||
c.dupesInLastSync = len(al.Items) - i
|
||||
// If we got the same as before, everything after it we'll also have.
|
||||
recvLength = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if recvLength > 0 {
|
||||
// Lazy strategy: rebuild user ID maps each time.
|
||||
// Wipe them, and then append each new refresh element as we process them.
|
||||
// Then loop through all the old entries and append them too.
|
||||
for uid := range c.byUserID {
|
||||
c.byUserID[uid] = c.byUserID[uid][:0]
|
||||
}
|
||||
|
||||
previousStart := c.start
|
||||
|
||||
if c.start == -1 {
|
||||
c.start = 0
|
||||
c.end = recvLength - 1
|
||||
} else {
|
||||
c.start = ((c.start-recvLength)%ActivityLimit + ActivityLimit) % ActivityLimit
|
||||
}
|
||||
if c.cache[c.start].ID != -1 {
|
||||
c.end = ((c.end-1)%ActivityLimit + ActivityLimit) % ActivityLimit
|
||||
}
|
||||
for i := range recvLength {
|
||||
ale := al.Items[i]
|
||||
ci := (c.start + i) % ActivityLimit
|
||||
if c.cache[ci].ID != -1 {
|
||||
// Since we're overwriting it, remove it from index
|
||||
delete(c.byEntryID, c.cache[ci].ID)
|
||||
// don't increment total since we're adding and removing
|
||||
} else {
|
||||
c.Total++
|
||||
}
|
||||
if ale.UserID != "" {
|
||||
arr, ok := c.byUserID[ale.UserID]
|
||||
if !ok {
|
||||
arr = make([]int, 0, ByUserLengthOrBaseLength)
|
||||
}
|
||||
if !ByUserLimitLength || len(arr) < ByUserLengthOrBaseLength {
|
||||
arr = append(arr, ci)
|
||||
c.byUserID[ale.UserID] = arr
|
||||
}
|
||||
}
|
||||
|
||||
c.cache[ci] = ale
|
||||
c.byEntryID[ale.ID] = ci
|
||||
}
|
||||
// If this was the first sync, everything has already been processed in the previous loop.
|
||||
if previousStart != -1 {
|
||||
i := previousStart
|
||||
for {
|
||||
if c.cache[i].UserID != "" {
|
||||
arr, ok := c.byUserID[c.cache[i].UserID]
|
||||
if !ok {
|
||||
arr = make([]int, 0, ByUserLengthOrBaseLength)
|
||||
}
|
||||
if !ByUserLimitLength || len(arr) < ByUserLengthOrBaseLength {
|
||||
arr = append(arr, i)
|
||||
c.byUserID[c.cache[i].UserID] = arr
|
||||
}
|
||||
}
|
||||
|
||||
if i == c.end {
|
||||
break
|
||||
}
|
||||
i = (i + 1) % ActivityLimit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for i := range c.cache {
|
||||
// fmt.Printf("%04d|", i)
|
||||
// }
|
||||
// fmt.Print("\n")
|
||||
// for i := range c.cache {
|
||||
// fmt.Printf("%04d|", c.cache[i].ID)
|
||||
// }
|
||||
// fmt.Print("\n")
|
||||
|
||||
c.syncLock.Lock()
|
||||
c.LastSync = thisSync
|
||||
if recvLength > 0 {
|
||||
c.LastYieldingSync = thisSync
|
||||
}
|
||||
c.syncing = false
|
||||
c.syncLock.Unlock()
|
||||
} else {
|
||||
for c.syncing {
|
||||
continue
|
||||
}
|
||||
}
|
||||
status <- nil
|
||||
}(syncStatus, c)
|
||||
err := <-syncStatus
|
||||
return err
|
||||
}
|
||||
136
jf_activity_test.go
Normal file
136
jf_activity_test.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hrfee/mediabrowser"
|
||||
)
|
||||
|
||||
type MockActivityLogSource struct {
|
||||
logs []mediabrowser.ActivityLogEntry
|
||||
lock sync.Mutex
|
||||
i int
|
||||
}
|
||||
|
||||
func (m *MockActivityLogSource) run(size int, delay time.Duration, finished *bool) {
|
||||
m.logs = make([]mediabrowser.ActivityLogEntry, size)
|
||||
for i := range len(m.logs) {
|
||||
m.logs[i].ID = -1
|
||||
}
|
||||
m.i = 0
|
||||
for i := range len(m.logs) {
|
||||
m.lock.Lock()
|
||||
log := mediabrowser.ActivityLogEntry{
|
||||
ID: int64(i),
|
||||
Date: mediabrowser.Time{time.Now()},
|
||||
}
|
||||
m.logs[i] = log
|
||||
m.i = i + 1
|
||||
m.lock.Unlock()
|
||||
time.Sleep(delay)
|
||||
}
|
||||
*finished = true
|
||||
time.Sleep(delay)
|
||||
}
|
||||
|
||||
func (m *MockActivityLogSource) GetActivityLog(skip, limit int, since time.Time, hasUserID bool) (mediabrowser.ActivityLog, error) {
|
||||
// This may introduce duplicates, but those are handled fine.
|
||||
// If we don't do this, things go wrong in a way that seems
|
||||
// very specific to this test setup, and (imo) is not necessarily
|
||||
// applicable to a real scenario.
|
||||
// since = since.Add(-time.Millisecond)
|
||||
out := make([]mediabrowser.ActivityLogEntry, 0, limit)
|
||||
count := 0
|
||||
loopCount := 0
|
||||
m.lock.Lock()
|
||||
for i := m.i - 1; count < limit && i >= 0; i-- {
|
||||
loopCount++
|
||||
if m.logs[i].Date.After(since) {
|
||||
out = append(out, m.logs[i])
|
||||
count++
|
||||
}
|
||||
}
|
||||
m.lock.Unlock()
|
||||
return mediabrowser.ActivityLog{Items: out}, nil
|
||||
}
|
||||
|
||||
func TestJFActivityLog(t *testing.T) {
|
||||
t.Parallel()
|
||||
// FIXME: This test is failing
|
||||
t.Run("Completeness", func(t *testing.T) {
|
||||
mock := MockActivityLogSource{}
|
||||
waitForSync := time.Microsecond
|
||||
cache := NewJFActivityCache(&mock, waitForSync)
|
||||
finished := false
|
||||
count := len(cache.cache) - 10
|
||||
go mock.run(count, time.Millisecond, &finished)
|
||||
for {
|
||||
if err := cache.MaybeSync(); err != nil {
|
||||
t.Errorf("sync failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if cache.dupesInLastSync > 1 {
|
||||
t.Logf("got %d dupes in last sync\n", cache.dupesInLastSync)
|
||||
}
|
||||
|
||||
if finished {
|
||||
// Make sure we got everything
|
||||
time.Sleep(5 * waitForSync)
|
||||
if err := cache.MaybeSync(); err != nil {
|
||||
t.Errorf("sync failed: %v", err)
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
t.Log(">-\n" + cache.debugString())
|
||||
if cache.Total != count {
|
||||
t.Errorf("not all collected: %d < %d", cache.Total, count)
|
||||
}
|
||||
})
|
||||
t.Run("Ordering", func(t *testing.T) {
|
||||
mock := MockActivityLogSource{}
|
||||
waitForSync := 300 * time.Microsecond
|
||||
cache := NewJFActivityCache(&mock, waitForSync)
|
||||
finished := false
|
||||
count := len(cache.cache) * 2
|
||||
go mock.run(count, time.Millisecond, &finished)
|
||||
for {
|
||||
if err := cache.MaybeSync(); err != nil {
|
||||
t.Errorf("sync failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if finished {
|
||||
// Make sure we got everything
|
||||
time.Sleep(waitForSync)
|
||||
if err := cache.MaybeSync(); err != nil {
|
||||
t.Errorf("sync failed: %v", err)
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
t.Log(">-\n" + cache.debugString())
|
||||
i := cache.start
|
||||
lastID := int64(-1)
|
||||
t.Logf("cache start=%d, end=%d, total=%d\n", cache.start, cache.end, cache.Total)
|
||||
for {
|
||||
if i != cache.start {
|
||||
if cache.cache[i].ID != lastID-1 {
|
||||
t.Errorf("next was not previous ID: %d != %d-1 = %d", cache.cache[i].ID, lastID, lastID-1)
|
||||
return
|
||||
}
|
||||
}
|
||||
lastID = cache.cache[i].ID
|
||||
|
||||
if i == cache.end {
|
||||
break
|
||||
}
|
||||
i = (i + 1) % len(cache.cache)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -11,16 +11,16 @@
|
||||
"inviteHours": "ساعات",
|
||||
"inviteMinutes": "دقائق",
|
||||
"inviteNumberOfUses": "عدد الاستخدامات",
|
||||
"inviteDuration": "مدة الدعوة",
|
||||
"inviteDuration": "صلاحية الدعوة",
|
||||
"warning": "تحذير",
|
||||
"inviteInfiniteUsesWarning": "الدعوات ذات الاستخدامات اللانهائية يمكن ان تستخدم بشكل مسيئ",
|
||||
"inviteInfiniteUsesWarning": "الدعوات ذات الاستخدامات اللامحدودة يمكن إساءة استخدامها",
|
||||
"inviteSendToEmail": "إرسال إلى",
|
||||
"create": "إنشاء",
|
||||
"apply": "تطبيق",
|
||||
"select": "تحديد",
|
||||
"name": "الاسم",
|
||||
"date": "التاريخ",
|
||||
"setExpiry": "تعيين انتهاء الصلاحية",
|
||||
"setExpiry": "تعيين مدة الصلاحية",
|
||||
"updates": "التحديثات",
|
||||
"update": "تحديث",
|
||||
"download": "تنزيل",
|
||||
@@ -30,195 +30,316 @@
|
||||
"from": "من",
|
||||
"after": "بعد",
|
||||
"before": "قبل",
|
||||
"user": "مستخدم",
|
||||
"userExpiry": "انتهاء صلاحية المستخدم",
|
||||
"userExpiryDescription": "بعد وقت محدد من تسجيل مستخدم جديد, jfa-go سوف يمسح\\يلغي تفعيل الحساب. بامكانك تغيير هذا السلوك في الاعدادات.",
|
||||
"aboutProgram": "حول",
|
||||
"user": "المستخدم",
|
||||
"userExpiry": "صلاحية المستخدم",
|
||||
"userExpiryDescription": "عند التفعيل، سيقوم jfa-go بحذف/تعطيل الحساب بعد وقت محدد من التسجيل عبر الدعوة. يمكنك اختيار الإجراء في الإعدادات.",
|
||||
"aboutProgram": "نُبذة",
|
||||
"version": "إصدار",
|
||||
"commitNoun": "فرض",
|
||||
"commitNoun": "تعديل",
|
||||
"newUser": "مستخدم جديد",
|
||||
"profile": "حساب تعريفي",
|
||||
"profile": "ملف التعريف",
|
||||
"unknown": "غير معروف",
|
||||
"label": "وسم",
|
||||
"label": "الوسم",
|
||||
"logs": "السجلات",
|
||||
"announce": "إعلان",
|
||||
"templates": "قوالب",
|
||||
"templates": "القوالب",
|
||||
"subject": "الموضوع",
|
||||
"message": "الرسالة",
|
||||
"variables": "المتغيرات",
|
||||
"conditionals": "",
|
||||
"conditionals": "الاشتراطات",
|
||||
"preview": "معاينة",
|
||||
"reset": "إعادة ضبط",
|
||||
"donate": "تبرع",
|
||||
"reset": "إعادة التعيين",
|
||||
"donate": "تبرّع",
|
||||
"unlink": "إلغاء ربط الحساب",
|
||||
"sendPWR": "إرسال إعادة تعيين كلمة المرور",
|
||||
"contactThrough": "تواصل عن طريق:",
|
||||
"extendExpiry": "تمديد إنتهاء الصلاحية",
|
||||
"sendPWRManual": "",
|
||||
"contactThrough": "تواصل عبر:",
|
||||
"extendExpiry": "تمديد مدة الصلاحية",
|
||||
"sendPWRManual": "المستخدم {n} ليس لديه أي وسيلة اتصال، اضغط \"نسخ\" لتحصل على رابط لإرساله إليه.",
|
||||
"sendPWRSuccess": "تم إرسال رابط إعادة تعيين كلمة المرور.",
|
||||
"sendPWRSuccessManual": "",
|
||||
"sendPWRValidFor": "",
|
||||
"customizeMessages": "",
|
||||
"customizeMessagesDescription": "",
|
||||
"markdownSupported": "",
|
||||
"modifySettings": "",
|
||||
"modifySettingsDescription": "",
|
||||
"applyHomescreenLayout": "تطبيق ترتيب الصفحه الرئيسيه",
|
||||
"sendDeleteNotificationEmail": "ارسال رساله اشعار",
|
||||
"sendPWRSuccessManual": "إذا لم يستلمه المستخدم، فاضغط \"نسخ\" للحصول على رابط لإرساله إليه يدوياً.",
|
||||
"sendPWRValidFor": "الرابط صالح لمدة 30 دقيقة.",
|
||||
"customizeMessages": "تخصيص الرسائل",
|
||||
"customizeMessagesDescription": "إن لم ترغب في استخدام قوالب رسائل jfa-go، يمكنك إنشاء قوالب مخصصة باستخدام ترميز Markdown.",
|
||||
"markdownSupported": "ترميز Markdown مدعوم.",
|
||||
"modifySettings": "تغيير الإعدادات",
|
||||
"modifySettingsDescription": "طبّق الإعدادات من ملف تعريف موجود، أو انسخها مباشرة من مستخدم.",
|
||||
"applyHomescreenLayout": "تطبيق مخطط الصفحة الرئيسية",
|
||||
"sendDeleteNotificationEmail": "إرسال رسالة إشعار",
|
||||
"sendDeleteNotifiationExample": "تم حذف حسابك.",
|
||||
"settingsRestart": "اعاده تشغيل",
|
||||
"settingsRestarting": "اعاده التشغيل…",
|
||||
"settingsRestartRequired": "يجب اعاده التشغيل",
|
||||
"settingsRestartRequiredDescription": "يجب اعاده التشغيل لتطبيق بعض الاعدادات التي تم تغييرها. اعاده التشغيل الان ام لاحقا؟",
|
||||
"settingsApplyRestartLater": "تطبيق الاعدادات, اعاده التشغيل لاحقا",
|
||||
"settingsApplyRestartNow": "تطبيق الاعدادات و اعاده التشغيل",
|
||||
"settingsApplied": "تم تطبيق الاعدادات.",
|
||||
"settingsRefreshPage": "اعد انعاش الصفحه بعد بضع ثوان.",
|
||||
"settingsRequiredOrRestartMessage": "ملاحظه: {n} تشير الى حقل اجباري, {n} تشير ان التغييرات تحتاج لاعاده التشغيل.",
|
||||
"settingsRestart": "إعادة التشغيل",
|
||||
"settingsRestarting": "يتم إعادة التشغيل…",
|
||||
"settingsRestartRequired": "يلزم إعادة التشغيل",
|
||||
"settingsRestartRequiredDescription": "يلزم إعادة التشغيل لتطبيق بعض الإعدادات التي تم تغييرها. هل ترغب بإعادة التشغيل الآن أم لاحقاً؟",
|
||||
"settingsApplyRestartLater": "تطبيق، إعادة التشغيل لاحقاً",
|
||||
"settingsApplyRestartNow": "تطبيق وإعادة التشغيل",
|
||||
"settingsApplied": "تم تطبيق الإعدادات.",
|
||||
"settingsRefreshPage": "حدّث الصفحة بعد عدة ثوانٍ.",
|
||||
"settingsRequiredOrRestartMessage": "ملاحظة: {n} تشير إلى حقل مطلوب، و{n} تشير إلى أن التغييرات تتطلب إعادة التشغيل.",
|
||||
"settingsSave": "حفظ",
|
||||
"ombiProfile": "",
|
||||
"ombiUserDefaultsDescription": "",
|
||||
"userProfiles": "",
|
||||
"userProfilesDescription": "",
|
||||
"userProfilesIsDefault": "",
|
||||
"userProfilesLibraries": "",
|
||||
"addProfile": "",
|
||||
"addProfileDescription": "",
|
||||
"addProfileNameOf": "",
|
||||
"addProfileStoreHomescreenLayout": "",
|
||||
"inviteNoUsersCreated": "",
|
||||
"inviteUsersCreated": "",
|
||||
"inviteNoProfile": "",
|
||||
"inviteDateCreated": "",
|
||||
"ombiProfile": "ملف تعريف مستخدم Ombi",
|
||||
"ombiUserDefaultsDescription": "أنشئ مستخدم Ombi وقم بإعداده، ثم اختره أدناه. سيتم تخزين إعداداته/أذوناته وتطبيقها على مستخدمي Ombi الجدد الذين أُنشئوا بواسطة jfa-go عند اختيار ملف التعريف هذا.",
|
||||
"userProfiles": "ملفات التعريف",
|
||||
"userProfilesDescription": "تُطبّق ملفات التعريف على المستخدمين عند إنشاء حساباتهم. يشمل ملف التعريف صلاحيات الوصول للمكتبات ومخطط الصفحة الرئيسية.",
|
||||
"userProfilesIsDefault": "الملف الافتراضي",
|
||||
"userProfilesLibraries": "المكتبات",
|
||||
"addProfile": "إضافة ملف تعريف",
|
||||
"addProfileDescription": "أنشئ مستخدم Jellyfin وقم بإعداده، ثم اختره أدناه. عند تطبيق ملف التعريف هذا على دعوة، ستُطبّق إعداداته على المستخدمين المُنشئين من خلال تلك الدعوة.",
|
||||
"addProfileNameOf": "اسم ملف التعريف",
|
||||
"addProfileStoreHomescreenLayout": "تخزين مخطط الصفحة الرئيسية",
|
||||
"inviteNoUsersCreated": "لا أحد حتى الآن!",
|
||||
"inviteUsersCreated": "المستخدمون المنشئون",
|
||||
"inviteNoProfile": "بدون ملف تعريف",
|
||||
"inviteDateCreated": "أُنشئَت في",
|
||||
"inviteRemainingUses": "",
|
||||
"inviteNoInvites": "",
|
||||
"inviteExpiresInTime": "",
|
||||
"notifyEvent": "",
|
||||
"notifyInviteExpiry": "",
|
||||
"notifyUserCreation": "",
|
||||
"sendPIN": "",
|
||||
"searchDiscordUser": "",
|
||||
"findDiscordUser": "",
|
||||
"linkMatrixDescription": "",
|
||||
"matrixHomeServer": "",
|
||||
"saveAsTemplate": "",
|
||||
"deleteTemplate": "",
|
||||
"templateEnterName": "",
|
||||
"accessJFA": "",
|
||||
"accessJFASettings": "",
|
||||
"sortingBy": "",
|
||||
"filters": "",
|
||||
"clickToRemoveFilter": "",
|
||||
"clearSearch": "",
|
||||
"actions": "",
|
||||
"searchOptions": "",
|
||||
"matchText": "",
|
||||
"jellyfinID": "",
|
||||
"userPageLogin": "",
|
||||
"userPagePage": "",
|
||||
"buildTime": "",
|
||||
"builtBy": "",
|
||||
"activity": "الانشطه",
|
||||
"inviteNoInvites": "لا شيء",
|
||||
"inviteExpiresInTime": "تنتهي بعد {n}",
|
||||
"notifyEvent": "الإبلاغ عند:",
|
||||
"notifyInviteExpiry": "عند انتهاء الصلاحية",
|
||||
"notifyUserCreation": "عند إنشاء مستخدم",
|
||||
"sendPIN": "اطلب من المستخدم إرسال الرمز أدناه إلى البوت.",
|
||||
"searchDiscordUser": "أدخل اسم مستخدم Discord للعثور عليه.",
|
||||
"findDiscordUser": "ابحث عن مستخدم Discord",
|
||||
"linkMatrixDescription": "أدخل اسم المستخدم وكلمة المرور للمستخدم المراد استخدامه كبوت. بعد إدخالهما، سيُعاد تشغيل التطبيق.",
|
||||
"matrixHomeServer": "عنوان الخادم الرئيسي",
|
||||
"saveAsTemplate": "حفظ كقالب",
|
||||
"deleteTemplate": "حذف القالب",
|
||||
"templateEnterName": "أدخل اسماً لحفظ هذا القالب.",
|
||||
"accessJFA": "مسؤول في jfa-go",
|
||||
"accessJFASettings": "لا يمكن تغيير ذلك حيث تم تفعيل \"المسؤول فقط\" أو \"السماح للجميع\" في الإعدادات > عام.",
|
||||
"sortingBy": "الفرز حسب",
|
||||
"filters": "المُرشِّحات",
|
||||
"clickToRemoveFilter": "اضغط لإزالة المُرشِّح.",
|
||||
"clearSearch": "إلغاء البحث",
|
||||
"actions": "الإجراءات",
|
||||
"searchOptions": "خيارات البحث",
|
||||
"matchText": "مطابقة النص",
|
||||
"jellyfinID": "مُعرّف Jellyfin",
|
||||
"userPageLogin": "صفحة المستخدم: تسجيل الدخول",
|
||||
"userPagePage": "صفحة المستخدم: الصفحة",
|
||||
"buildTime": "وقت بناء النُسخة",
|
||||
"builtBy": "بُنيَت بواسطة",
|
||||
"activity": "الأنشطة",
|
||||
"userLabel": "وسم المستخدم",
|
||||
"userLabelDescription": "الوسام للمستخدمين المفعلين من هذه الدعوه.",
|
||||
"enableReferrals": "تفعيل الاحالات",
|
||||
"disableReferrals": "ابطال الاحالات",
|
||||
"invite": "دعوه",
|
||||
"enableReferralsProfileDescription": "تمكين المستخدمين من هذا الحساب التعريفي للاحالات الخاصه, لارسالها للعائله\\الاصدقاء. انشاء دعوه بالاعدادات المطلوبه, ثم اختارها هنا. كل احاله سوف تكون مبنيه على اعدادات هذه الدعوه. بامكانك مسح الدعوه عند لانتهاء.",
|
||||
"enableReferralsDescription": "تمكين المستخدمين لاستعمال احالات خاصه مثل الدعوه, لارسالها للعائله\\للاصدقاء. ممكن اصدارها من قوالب الاحالات في الحساب التعريفي, او من دعوه مفعله."
|
||||
"userLabelDescription": "وسم يتم تطبيقه على المستخدمين المُنشئين بهذه الدعوة.",
|
||||
"enableReferrals": "تفعيل الإحالات",
|
||||
"disableReferrals": "تعطيل الإحالات",
|
||||
"invite": "دعوة",
|
||||
"enableReferralsProfileDescription": "امنح المستخدمين المنشئين بملف التعريف هذا رابط إحالة شخصي شبيه بالدعوة، لإرساله إلى الأصدقاء/العائلة. أنشئ دعوة بالإعدادات المطلوبة، ثم اخترها هنا. ستستند كل إحالة بعد ذلك إلى هذه الدعوة. يمكنك حذف الدعوة بمجرد الانتهاء.",
|
||||
"enableReferralsDescription": "امنح المستخدمين رابط إحالة شخصي شبيه بالدعوة، لإرساله إلى الأصدقاء/العائلة. تستند الإحالة إلى قالب الإحالة في ملف التعريف، أو من دعوة موجودة.",
|
||||
"disabled": "معطّل",
|
||||
"wikiPage": "صفحة الويكي",
|
||||
"wiki": "الويكي",
|
||||
"enterExpiry": "أدخل تاريخ انتهاء الصلاحية",
|
||||
"removeExpiry": "إزالة مدة الصلاحية",
|
||||
"useInviteExpiry": "عيّن مدة الصلاحية من ملف التعريف/الدعوة",
|
||||
"extendFromPreviousExpiryDescription": "إذا عُثر على تاريخ انتهاء الصلاحية لمستخدم منتهي الصلاحية بالفعل، فسيتم تمديد المدة من ذلك التاريخ، بدلاً من التاريخ الحالي، إلا إذا كان التاريخ الجديد لانتهاء الصلاحية سيكون قد انقضى بالفعل.",
|
||||
"deleted": "محذوف",
|
||||
"keepSearching": "واصل البحث",
|
||||
"keepSearchingDescription": "تم البحث فقط في الأنشطة الحالية المُحمّلة. اضغط أدناه للبحث في جميع الأنشطة.",
|
||||
"noResultsFound": "لا توجد نتائج",
|
||||
"extendFromPreviousExpiry": "تمديد من تاريخ انتهاء الصلاحية السابق (إن أمكن)",
|
||||
"useInviteExpiryNote": "تنتهي صلاحية الدعوات بشكل افتراضي بعد 90 يوم، ولكن يمكن للمستخدم تجديدها. فعّل هذه الخيار لتعطيل الإحالة بعد الوقت المحدد.",
|
||||
"noResultsFoundLocally": "تم البحث في السجلات المُحمّلة فقط. يمكنك تحميل المزيد، أو البحث في جميع سجلات الخادم.",
|
||||
"applyConfigurationAndPolicy": "تطبيق إعدادات/سياسة Jellyfin",
|
||||
"applyOmbi": "تطبيق ملف تعريف Ombi (إن وُجد)",
|
||||
"applyJellyseerr": "تطبيق ملف تعريف Jellyseerr (إن وُجد)",
|
||||
"settingsHiddenDependency": "الإعدادات المطابقة لبحثك مخفية لأنها تعتمد على قيمة إعداد آخر:",
|
||||
"settingsDependsOn": "{setting}: يعتمد على {dependency}",
|
||||
"settingsAdvancedMode": "{setting}: يجب تفعيل الإعدادات المتقدمة",
|
||||
"settingsMaybeUnderAdvanced": "تلميح: قد تجد ما تبحث عنه عند تفعيل الإعدادات المتقدمة.",
|
||||
"jellyseerrProfile": "ملف تعريف مستخدم Jellyseerr",
|
||||
"jellyseerrUserDefaultsDescription": "أنشئ مستخدم Jellyseerr وقم بإعداده، ثم اختره أدناه. سيتم تخزين إعداداته/أذوناته وتطبيقها على مستخدمي Jellyseerr الجدد الذين أُنشئوا بواسطة jfa-go عند اختيار ملف التعريف هذا.",
|
||||
"sortDirection": "اتجاه الفرز",
|
||||
"searchAllRecords": "بحث/فرز جميع السجلات (على الخادم)",
|
||||
"backups": "النسخ الاحتياطية",
|
||||
"backupsCopy": "عند استعادة نسخة احتياطية، سيتم إنشاء نسخة من مجلد \"db\" الأصلي بجواره، تحسبًا لحدوث أي خطأ.",
|
||||
"backupCanDownload": "يمكنك أيضاً الضغط أدناه لتنزيل النسخة الاحتياطية.",
|
||||
"sentTo": "مرسلة إلى",
|
||||
"tasks": "المهام",
|
||||
"editProfile": "تعديل ملف التعريف",
|
||||
"editProfileDescription": "لإجراء تغييرات كبيرة، يُنصح بتعديل الإعدادات في Jellyfin/Jellyseerr/Ombi وإعادة إنشاء ملف التعريف، كما يمكنك أيضاً إجراء تغييرات مباشرة هنا. يُرجى توخي الحذر عند التعديل.",
|
||||
"tasksDescription": "المهام هي إجراءات كبيرة تُنفَّذ دورياً في الخلفية. يمكنك تشغيلها يدوياً هنا إذا أردت.",
|
||||
"run": "تشغيل",
|
||||
"addProfileStoreJellyseerr": "إنشاء ملف تعريف Jellyseerr",
|
||||
"preSignupCard": "بطاقة التعليمات قبل التسجيل",
|
||||
"preSignupCardDescription": "بطاقة اختيارية تظهر في صفحة التسجيل.",
|
||||
"byAdmin": "بواسطة المسؤول",
|
||||
"byUser": "بواسطة المستخدم",
|
||||
"byJfaGo": "بواسطة jfa-go",
|
||||
"accountExpired": "انتهت صلاحية حساب: {user}",
|
||||
"inviteDeleted": "تم حذف دعوة: {invite}",
|
||||
"inviteExpired": "انتهت صلاحية دعوة: {invite}",
|
||||
"fromInvite": "عبر دعوة",
|
||||
"activityID": "مُعرّف النشاط",
|
||||
"title": "العنوان",
|
||||
"usersMentioned": "المستخدم المذكور",
|
||||
"actor": "المسبب",
|
||||
"actorDescription": "الشيء الذي تسبب في هذا الإجراء. \"user\"/\"admin\"/\"daemon\" أو اسم مستخدم.",
|
||||
"accountDisabledFilter": "حساب تم تعطيله",
|
||||
"accountEnabledFilter": "حساب تم تفعيله",
|
||||
"accountCreationFilter": "حساب أُنشئ",
|
||||
"accountDeletionFilter": "حساب حُذِف",
|
||||
"totalRecords": "{n} سجل بالمجمل",
|
||||
"noMoreResults": "لا توجد نتائج أخرى.",
|
||||
"loadMore": "تحميل المزيد",
|
||||
"loadAll": "تحميل الكل",
|
||||
"contactLinkedFilter": "وسيلة اتصال رُبِطت",
|
||||
"contactUnlinkedFilter": "وسيلة اتصال أُزيلت",
|
||||
"passwordChangeFilter": "كلمة مرور تغيّرت",
|
||||
"passwordResetFilter": "كلمة مرور أُعيد تعيينها",
|
||||
"inviteCreatedFilter": "دعوة أُنشئت",
|
||||
"inviteDeletedFilter": "دعوة حُذِفت/انتهت",
|
||||
"loadedRecords": "{n} محمّل",
|
||||
"shownRecords": "{n} معروض",
|
||||
"selectedRecords": "{n} محدد",
|
||||
"allMatchingSelected": "جميع النتائج المطابقة محددة.",
|
||||
"allLoadedSelected": "جميع النتائج المطابقة المحمّلة محددة. اضغط مرة أخرى لتحميل الكل.",
|
||||
"restartRequired": "يلزم إعادة التشغيل",
|
||||
"syntax": "الصياغة",
|
||||
"syntaxDescription": "المتغيرات يشار إليها بـ {variable}. يمكن للعبارات الشرطية (if) تقييم مدى الصحة (مثل {ifTruth}) أو إجراء مقارنات بسيطة (مثل {ifCompare})",
|
||||
"postSignupCard": "بطاقة التعليمات بعد التسجيل",
|
||||
"postSignupCardDescription": "البطاقة التي تظهر للمستخدم بعد التسجيل. تَستبدل \"رسالة النجاح\". يستبدلها إعداد \"Auto redirect on success\".",
|
||||
"buildTags": "وسوم النُسخة",
|
||||
"loginNotAdmin": "لست المسؤول؟",
|
||||
"referrer": "المُحيل (إحالة)",
|
||||
"accountResetPassword": "{user} أعاد تعيين كلمة مروره",
|
||||
"accountChangedPassword": "{user} غير كلمة مروره",
|
||||
"accountCreated": "تم إنشاء حساب: {user}",
|
||||
"accountReEnabled": "تم إعادة تفعيل حساب: {user}",
|
||||
"accountDeleted": "تم حذف حساب: {user}",
|
||||
"accountDisabled": "تم تعطيل حساب: {user}",
|
||||
"accountUnlinked": "{user}: أزال {contactMethod}",
|
||||
"accountLinked": "{user}: ربط {contactMethod}",
|
||||
"backupsDescription": "يمكن إجراء نسخ احتياطية لقاعدة البيانات أو استعادتها أو تنزيلها من هنا.",
|
||||
"backupsFormatNote": "لن يتم عرض سوى النسخ الاحتياطية المسمّاة بالصيغة الأساسية. لاستخدام أي صيغة أخرى، قم برفع النسخة الاحتياطية يدوياً.",
|
||||
"backupDownloadRestore": "تنزيل / استعادة",
|
||||
"backupUpload": "رفع واستعادة نسخة احتياطية",
|
||||
"backupDownload": "تنزيل النسخة الاحتياطية",
|
||||
"backupRestore": "استعادة النسخة الاحتياطية",
|
||||
"backupNow": "إنشاء نسخة احتياطية",
|
||||
"backupCreated": "تم إنشاء النسخة الاحتياطية",
|
||||
"backupCanBeFound": "يمكن العثور على النسخة الاحتياطية على الخادم في {filepath}.",
|
||||
"required": "مطلوب",
|
||||
"searchAll": "بحث/فرز الكل",
|
||||
"accountWillExpire": "ستنتهي صلاحية الحساب في {date}.",
|
||||
"expirationBasedOn": "التاريخ المحدد مبني على أول مستخدم.",
|
||||
"inviteCreated": "تم إنشاء دعوة: {invite}",
|
||||
"userDeleted": "تم حذف المستخدم.",
|
||||
"userDisabled": "تم تعطيل المستخدم"
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "",
|
||||
"userCreated": "",
|
||||
"createProfile": "",
|
||||
"saveSettings": "",
|
||||
"saveEmail": "",
|
||||
"sentAnnouncement": "",
|
||||
"savedAnnouncement": "",
|
||||
"setOmbiProfile": "",
|
||||
"updateApplied": "",
|
||||
"updateAppliedRefresh": "",
|
||||
"telegramVerified": "",
|
||||
"accountConnected": "",
|
||||
"errorSettingsAppliedNoHomescreenLayout": "",
|
||||
"errorHomescreenAppliedNoSettings": "",
|
||||
"errorSettingsFailed": "",
|
||||
"errorSaveEmail": "",
|
||||
"errorBlankFields": "",
|
||||
"errorDeleteProfile": "",
|
||||
"errorLoadProfiles": "",
|
||||
"errorCreateProfile": "",
|
||||
"errorSetDefaultProfile": "",
|
||||
"errorLoadUsers": "",
|
||||
"errorLoadSettings": "",
|
||||
"errorSetOmbiProfile": "",
|
||||
"errorLoadOmbiUsers": "",
|
||||
"errorChangedEmailAddress": "",
|
||||
"errorFailureCheckLogs": "",
|
||||
"errorPartialFailureCheckLogs": "",
|
||||
"errorUserCreated": "",
|
||||
"errorSendWelcomeEmail": "",
|
||||
"errorApplyUpdate": "",
|
||||
"errorCheckUpdate": "",
|
||||
"updateAvailable": "",
|
||||
"noUpdatesAvailable": ""
|
||||
"changedEmailAddress": "تم تغيير عنوان البريد الإلكتروني لـ {n}.",
|
||||
"userCreated": "تم إنشاء المستخدم {n}.",
|
||||
"createProfile": "تم إنشاء ملف التعريف {n}.",
|
||||
"saveSettings": "تم حفظ الإعدادات",
|
||||
"saveEmail": "تم حفظ البريد الإلكتروني.",
|
||||
"sentAnnouncement": "تم إرسال الإعلان.",
|
||||
"savedAnnouncement": "تم حفظ الإعلان.",
|
||||
"setOmbiProfile": "تم تخزين ملف تعريف Ombi.",
|
||||
"updateApplied": "تم تطبيق التحديث، يرجى إعادة التشغيل.",
|
||||
"updateAppliedRefresh": "تم تطبيق التحديث، يرجى تحديث الصفحة.",
|
||||
"telegramVerified": "تم تأكيد حساب Telegram.",
|
||||
"accountConnected": "تم ربط الحساب.",
|
||||
"errorSettingsAppliedNoHomescreenLayout": "تم تطبيق الإعدادات، ولكن ربما فشل تطبيق مخطط الصفحة الرئيسية.",
|
||||
"errorHomescreenAppliedNoSettings": "تم تطبيق مخطط الصفحة الرئيسية، ولكن ربما فشل تطبيق الإعدادات.",
|
||||
"errorSettingsFailed": "فشل التطبيق.",
|
||||
"errorSaveEmail": "فشل حفظ البريد الإلكتروني.",
|
||||
"errorBlankFields": "تُركت الحقول فارغة",
|
||||
"errorDeleteProfile": "فشل حذف ملف التعريف {n}",
|
||||
"errorLoadProfiles": "فشل تحميل ملفات التعريف.",
|
||||
"errorCreateProfile": "فشل إنشاء ملف التعريف {n}",
|
||||
"errorSetDefaultProfile": "فشل تعيين ملف التعريف الافتراضي.",
|
||||
"errorLoadUsers": "فشل تحميل المستخدمين.",
|
||||
"errorLoadSettings": "فشل تحميل الإعدادات.",
|
||||
"errorSetOmbiProfile": "فشل تخزين ملف تعريف Ombi.",
|
||||
"errorLoadOmbiUsers": "فشل تحميل مستخدمي Ombi.",
|
||||
"errorChangedEmailAddress": "تعذر تغيير عنوان البريد الإلكتروني لـ {n}.",
|
||||
"errorFailureCheckLogs": "فشل (تحقق من لوحة التحكم/السجلات)",
|
||||
"errorPartialFailureCheckLogs": "فشل جزئي (تحقق من لوحة التحكم/السجلات)",
|
||||
"errorUserCreated": "فشل إنشاء المستخدم {n}.",
|
||||
"errorSendWelcomeEmail": "فشل إرسال رسالة الترحيب (تحقق من لوحة التحكم/السجلات)",
|
||||
"errorApplyUpdate": "فشل تطبيق التحديث، حاول يدوياً.",
|
||||
"errorCheckUpdate": "فشل التحقق من التحديثات.",
|
||||
"updateAvailable": "يتوفر تحديث جديد، تحقق من الإعدادات.",
|
||||
"noUpdatesAvailable": "لا توجد تحديثات جديدة متاحة.",
|
||||
"errorInviteNoLongerExists": "الدعوة لم تعد موجودة.",
|
||||
"pathCopied": "تم نسخ المسار الكامل إلى الحافظة.",
|
||||
"errorInvalidAddress": "عنوان/اسم غير صالح",
|
||||
"referralsEnabled": "تم تفعيل الإحالات.",
|
||||
"activityDeleted": "تم حذف النشاط.",
|
||||
"errorLoadProfile": "فشل تحميل ملف التعريف.",
|
||||
"errorCheckLogs": "تحقق من لوحة التحكم/السجلات",
|
||||
"errorInvalidJSON": "JSON غير صالح.",
|
||||
"runTask": "تم تشغيل المهمة.",
|
||||
"errorMultiUser": "تم العثور على عدة مستخدمين متطابقين",
|
||||
"errorNoUser": "لم يتم العثور على مستخدم مطابق",
|
||||
"errorInviteNotFound": "الدعوة غير موجودة.",
|
||||
"errorNoReferralTemplate": "لا يحتوي ملف التعريف على قالب إحالة، أضف واحداً في الإعدادات.",
|
||||
"errorLoadActivities": "فشل تحميل الأنشطة.",
|
||||
"errorInvalidDate": "التاريخ غير صالح.",
|
||||
"savedProfile": "تم تخزين تغييرات ملف التعريف.",
|
||||
"errorSavedProfile": "فشل حفظ ملف التعريف {n}"
|
||||
},
|
||||
"quantityStrings": {
|
||||
"modifySettingsFor": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
"singular": "تغيير الإعدادات لمستخدم واحد",
|
||||
"plural": "تغيير الإعدادات لـ {n} من المستخدمين"
|
||||
},
|
||||
"deleteNUsers": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
"singular": "حذف مستخدم واحد",
|
||||
"plural": "حذف {n} من المستخدمين"
|
||||
},
|
||||
"disableUsers": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
"singular": "تعطيل مستخدم واحد",
|
||||
"plural": "تعطيل {n} من المستخدمين"
|
||||
},
|
||||
"reEnableUsers": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
"singular": "إعادة تفعيل مستخدم واحد",
|
||||
"plural": "إعادة تفعيل {n} من المستخدمين"
|
||||
},
|
||||
"addUser": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
"singular": "إضافة مستخدم",
|
||||
"plural": "إضافة مستخدمين"
|
||||
},
|
||||
"deleteUser": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
"singular": "حذف المستخدم",
|
||||
"plural": "حذف المستخدمين"
|
||||
},
|
||||
"deletedUser": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
"singular": "تم حذف مستخدم واحد.",
|
||||
"plural": "تم حذف {n} من المستخدمين."
|
||||
},
|
||||
"disabledUser": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
"singular": "تم تعطيل مستخدم واحد.",
|
||||
"plural": "تم تعطيل {n} من المستخدمين."
|
||||
},
|
||||
"enabledUser": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
"singular": "تم تفعيل مستخدم واحد.",
|
||||
"plural": "تم تفعيل {n} من المستخدمين."
|
||||
},
|
||||
"announceTo": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
"singular": "الإعلان إلى مستخدم واحد",
|
||||
"plural": "الإعلان إلى {n} من المستخدمين"
|
||||
},
|
||||
"appliedSettings": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
"singular": "تم تطبيق الإعدادات على مستخدم واحد.",
|
||||
"plural": "تم تطبيق الإعدادات على {n} من المستخدمين."
|
||||
},
|
||||
"extendExpiry": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
"singular": "تمديد مدة الصلاحية لمستخدم واحد",
|
||||
"plural": "تمديد مدة الصلاحية لـ {n} من المستخدمين"
|
||||
},
|
||||
"setExpiry": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
"singular": "تعيين مدة الصلاحية لمستخدم واحد",
|
||||
"plural": "تعيين مدة الصلاحية لـ {n} من المستخدمين"
|
||||
},
|
||||
"extendedExpiry": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
"singular": "تم تمديد مدة الصلاحية لمستخدم واحد.",
|
||||
"plural": "تم تمديد مدة الصلاحية لـ {n} من المستخدمين."
|
||||
},
|
||||
"enableReferralsFor": {
|
||||
"singular": "تفعيل الإحالات لمستخدم واحد",
|
||||
"plural": "تفعيل الإحالات لـ {n} من المستخدمين"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,37 @@
|
||||
"enableReferrals": "Empfehlungen aktivieren",
|
||||
"disableReferrals": "Empfehlungen deaktivieren",
|
||||
"userLabel": "Benutzer Label",
|
||||
"noResultsFound": "Keine Resultate gefunden"
|
||||
"noResultsFound": "Keine Resultate gefunden",
|
||||
"buildTime": "Erstellungszeit",
|
||||
"accountDisabled": "Konto deaktiviert: {user}",
|
||||
"accountReEnabled": "Konto reaktiviert: {user}",
|
||||
"accountExpired": "Konto abgelaufen: {user}",
|
||||
"accountWillExpire": "Konto läuft ab am {date}.",
|
||||
"expirationBasedOn": "Angegebenes Datum basiert auf dem ersten Benutzer.",
|
||||
"userDeleted": "Benutzer wurde gelöscht.",
|
||||
"userDisabled": "Benutzer wurde deaktiviert",
|
||||
"inviteCreated": "Einladung erstellt: {invite}",
|
||||
"inviteDeleted": "Einladung gelöscht: {invite}",
|
||||
"builtBy": "Erstellt von",
|
||||
"accountLinked": "{contactMethod} verknüpft: {user}",
|
||||
"referrer": "Empfehlungsgeber",
|
||||
"loginNotAdmin": "Kein Administrator?",
|
||||
"jellyseerrProfile": "Jellyseerr-Benutzerprofil",
|
||||
"jellyseerrUserDefaultsDescription": "Erstellen Sie einen Jellyseerr-Benutzer und konfigurieren Sie ihn. Wählen Sie ihn anschließend unten aus. Seine Einstellungen/Berechtigungen werden gespeichert und auf neue Jellyseerr-Benutzer angewendet, die von jfa-go erstellt werden, wenn dieses Profil ausgewählt ist.",
|
||||
"sortDirection": "Sortierreihenfolge",
|
||||
"searchAll": "Alle suchen/sortieren",
|
||||
"searchAllRecords": "Alle Datensätze suchen/sortieren (auf dem Server)",
|
||||
"postSignupCard": "Hilfekarte nach der Anmeldung",
|
||||
"postSignupCardDescription": "Karte, die dem Benutzer nach der Anmeldung angezeigt wird. Überschreibt die „Erfolgsmeldung“. Wird durch die Einstellung „Automatische Weiterleitung bei Erfolg“ überschrieben.",
|
||||
"buildTags": "Build Tags",
|
||||
"accountUnlinked": "{contactMethod} entfernt: {user}",
|
||||
"accountResetPassword": "{user} hat sein Passwort zurückgesetzt",
|
||||
"accountChangedPassword": "{user} hat sein Passwort geändert",
|
||||
"accountCreated": "Konto erstellt: {user}",
|
||||
"accountDeleted": "Konto gelöscht: {user}",
|
||||
"applyConfigurationAndPolicy": "Jellyfin Konfiguration/Richtlinie anwenden",
|
||||
"applyOmbi": "Ombi -Profil anwenden (falls verfügbar)",
|
||||
"applyJellyseerr": "Jellyseerr-Profil anwenden (falls verfügbar)"
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "E-Mail-Adresse von {n} geändert.",
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
"invite": "Invite",
|
||||
"accounts": "Accounts",
|
||||
"activity": "Activity",
|
||||
"activityFromJF": "Activity (from Jellyfin)",
|
||||
"activityFromJfa": "Activity (from jfa-go)",
|
||||
"settings": "Settings",
|
||||
"inviteMonths": "Months",
|
||||
"inviteDays": "Days",
|
||||
@@ -17,6 +19,7 @@
|
||||
"warning": "Warning",
|
||||
"inviteInfiniteUsesWarning": "invites with infinite uses can be used abusively",
|
||||
"inviteSendToEmail": "Send to",
|
||||
"sentTo": "Sent to",
|
||||
"create": "Create",
|
||||
"apply": "Apply",
|
||||
"select": "Select",
|
||||
@@ -39,11 +42,15 @@
|
||||
"commitNoun": "Commit",
|
||||
"newUser": "New User",
|
||||
"profile": "Profile",
|
||||
"editProfile": "Edit profile",
|
||||
"editProfileDescription": "For large changes, it is recommended you modify settings in Jellyfin/Jellyseerr/Ombi and re-generate the profile, but you can also make direct changes here. Please use caution when editing.",
|
||||
"unknown": "Unknown",
|
||||
"label": "Label",
|
||||
"userLabel": "User Label",
|
||||
"userLabelDescription": "Label to apply to users created with this invite.",
|
||||
"logs": "Logs",
|
||||
"tasks": "Tasks",
|
||||
"tasksDescription": "Tasks are large actions that may be run periodically in the background. You can manually trigger them here if you wish.",
|
||||
"announce": "Announce",
|
||||
"templates": "Templates",
|
||||
"subject": "Subject",
|
||||
@@ -56,7 +63,10 @@
|
||||
"unlink": "Unlink Account",
|
||||
"deleted": "Deleted",
|
||||
"disabled": "Disabled",
|
||||
"run": "Run",
|
||||
"sendPWR": "Send Password Reset",
|
||||
"noActivityFound": "No activity found",
|
||||
"noActivityFoundLocally": "If this account hasn't been used in a while, you might find older entries on Jellyfin directly.",
|
||||
"noResultsFound": "No Results Found",
|
||||
"noResultsFoundLocally": "Only loaded records were searched. You can load more, or perform the search over all records on the server.",
|
||||
"keepSearching": "Keep Searching",
|
||||
@@ -66,6 +76,8 @@
|
||||
"setExpiry": "Set expiry",
|
||||
"removeExpiry": "Remove expiry",
|
||||
"enterExpiry": "Enter an expiry",
|
||||
"extendFromPreviousExpiry": "Extend from previous expiry date (if possible)",
|
||||
"extendFromPreviousExpiryDescription": "If a record of an expired user's expiry time is found in the activity log, expiry will be extended from then, rather than the current time, unless the new expiry date would have already passed.",
|
||||
"sendPWRManual": "User {n} has no method of contact, press copy to get a link to send to them.",
|
||||
"sendPWRSuccess": "Password reset link sent.",
|
||||
"sendPWRSuccessManual": "If the user hasn't received it, press copy to get a link to manually send to them.",
|
||||
@@ -113,6 +125,7 @@
|
||||
"addProfileDescription": "Create a Jellyfin user and configure it, then select it below. When this profile is applied to an invite, new users will be created with the settings.",
|
||||
"addProfileNameOf": "Profile Name",
|
||||
"addProfileStoreHomescreenLayout": "Store homescreen layout",
|
||||
"addProfileStoreJellyseerr": "Create Jellyseerr profile",
|
||||
"inviteNoUsersCreated": "None yet!",
|
||||
"inviteUsersCreated": "Created users",
|
||||
"inviteNoProfile": "No Profile",
|
||||
@@ -147,6 +160,8 @@
|
||||
"userPagePage": "User Page: Page",
|
||||
"postSignupCard": "Post-signup help card",
|
||||
"postSignupCardDescription": "Card shown to user after signing up. Overrides \"Success Message\". Overriden by \"Auto redirect on success\" setting.",
|
||||
"preSignupCard": "Pre-signup help card",
|
||||
"preSignupCardDescription": "Optional card shown on the sign-up page.",
|
||||
"buildTime": "Build Time",
|
||||
"builtBy": "Built By",
|
||||
"buildTags": "Build Tags",
|
||||
@@ -209,7 +224,17 @@
|
||||
"backupCanBeFound": "The backup can be found on the server at {filepath}.",
|
||||
"backupCanDownload": "Alternatively, click below to download the backup.",
|
||||
"wikiPage": "Wiki Page",
|
||||
"wiki": "Wiki"
|
||||
"wiki": "Wiki",
|
||||
"restartRequired": "Restart required",
|
||||
"required": "Required",
|
||||
"syntax": "Syntax",
|
||||
"syntaxDescription": "Variables denoted as {variable}. If statements can evaluate truthfulness (e.g. {ifTruth}) or make basic comparisons (e.g. {ifCompare})",
|
||||
"info": "Info",
|
||||
"debug": "Debug",
|
||||
"warn": "Warn",
|
||||
"error": "Error",
|
||||
"fatal": "Fatal",
|
||||
"severity": "Severity"
|
||||
},
|
||||
"notifications": {
|
||||
"pathCopied": "Full path copied to clipboard.",
|
||||
@@ -237,6 +262,7 @@
|
||||
"errorBlankFields": "Fields were left blank",
|
||||
"errorDeleteProfile": "Failed to delete profile {n}",
|
||||
"errorLoadProfiles": "Failed to load profiles.",
|
||||
"errorLoadProfile": "Failed to load profile.",
|
||||
"errorCreateProfile": "Failed to create profile {n}",
|
||||
"errorSavedProfile": "Failed to save profile {n}",
|
||||
"errorSetDefaultProfile": "Failed to set default profile.",
|
||||
@@ -246,6 +272,7 @@
|
||||
"errorLoadOmbiUsers": "Failed to load ombi users.",
|
||||
"errorChangedEmailAddress": "Couldn't change email address of {n}.",
|
||||
"errorFailureCheckLogs": "Failed (check console/logs)",
|
||||
"errorCheckLogs": "Check console/logs",
|
||||
"errorPartialFailureCheckLogs": "Partial failure (check console/logs)",
|
||||
"errorUserCreated": "Failed to create user {n}.",
|
||||
"errorSendWelcomeEmail": "Failed to send welcome message (check console/logs)",
|
||||
@@ -254,8 +281,13 @@
|
||||
"errorNoReferralTemplate": "Profile doesn't contain referral template, add one in settings.",
|
||||
"errorLoadActivities": "Failed to load activities.",
|
||||
"errorInvalidDate": "Date is invalid.",
|
||||
"errorInvalidJSON": "Invalid JSON.",
|
||||
"updateAvailable": "A new update is available, check settings.",
|
||||
"noUpdatesAvailable": "No new updates available."
|
||||
"noUpdatesAvailable": "No new updates available.",
|
||||
"runTask": "Triggered task.",
|
||||
"errorMultiUser": "Multiple matching users found",
|
||||
"errorNoUser": "No matching user found",
|
||||
"errorInvalidAddress": "Invalid address/name"
|
||||
},
|
||||
"quantityStrings": {
|
||||
"modifySettingsFor": {
|
||||
|
||||
@@ -93,14 +93,14 @@
|
||||
"notifyEvent": "Értesítés ekkor:",
|
||||
"notifyInviteExpiry": "Lejáratkor",
|
||||
"notifyUserCreation": "Használatkor",
|
||||
"sendPIN": "",
|
||||
"searchDiscordUser": "",
|
||||
"findDiscordUser": "",
|
||||
"linkMatrixDescription": "",
|
||||
"matrixHomeServer": "",
|
||||
"saveAsTemplate": "",
|
||||
"deleteTemplate": "",
|
||||
"templateEnterName": "",
|
||||
"sendPIN": "Kérd meg a felhasználókat, hogy küldjék el a PIN-t a botnak.",
|
||||
"searchDiscordUser": "Kezd el írni adiscord felhasználó nevet a keresés indításához.",
|
||||
"findDiscordUser": "Discord felhasználó keresése",
|
||||
"linkMatrixDescription": "Add meg a felhasználó nevét és jelszavát hogy botként tudd használni. A beküldés után az alkalmazás újra fog indulni.",
|
||||
"matrixHomeServer": "Otthoni szerver címe",
|
||||
"saveAsTemplate": "Mentés sablonként",
|
||||
"deleteTemplate": "Sablon törlése",
|
||||
"templateEnterName": "Adj meg egy nevet a sablon mentéséhez.",
|
||||
"unlink": "Fiók leválasztása",
|
||||
"after": "Utánna",
|
||||
"before": "Elötte",
|
||||
@@ -117,7 +117,35 @@
|
||||
"invite": "Meghívás",
|
||||
"activity": "Aktivitás",
|
||||
"userLabel": "Felhasználói címke",
|
||||
"userLabelDescription": "Ezzel a meghívóval létrehozott felhasználókra alkalmazandó címke."
|
||||
"userLabelDescription": "Ezzel a meghívóval létrehozott felhasználókra alkalmazandó címke.",
|
||||
"noResultsFoundLocally": "A keresés csak a betöltött adatokon meg végbe. Betölthetsz több adatot is vagy kereshetsz az összes adaton.",
|
||||
"keepSearchingDescription": "Csak a betöltött tevékenységek között futott le a keresés. Kattints ide ha az összes tevékenység között szeretnél keresni.",
|
||||
"enableReferralsDescription": "Adjon a felhasználóknak egy meghívóhoz hasonló személyes hivatkozási linket, amelyet elküldhet barátainak/családjának. Ez származhat a profiljukban található ajánlói sablonból vagy egy meglévő meghívóból.",
|
||||
"enableReferralsProfileDescription": "Adj az ezzel a profillal létrehozott felhasználóknak egy személyre szabott ajánlói linket, hasonlóan egy meghívóhoz, amelyet elküldhetnek barátaiknak és családtagjaiknak. Hozz létre egy meghívót a kívánt beállításokkal, majd válaszd ki itt. Minden ajánlás ezután ezen a meghívón alapul majd. A meghívót törölheted, ha kész vagy.",
|
||||
"postSignupCardDescription": "A felhasználónak a regisztráció után megjelenő kártya. Felülírja a „Sikerüzenet” beállítást. Felülírja az „Automatikus átirányítás siker esetén” beállítás.",
|
||||
"buildTime": "Készítési idő",
|
||||
"accessJFA": "jfa-go hozzáférés",
|
||||
"accessJFASettings": "Nem módosítható, mert a Beállítások > Általános menüpontban engedélyezve van a „Csak rendszergazdai felhasználók” vagy az „Összes Jellyfin felhasználó bejelentkezése” lehetőség.",
|
||||
"disabled": "Tiltva",
|
||||
"userPagePage": "Felhasználói oldal: oldal",
|
||||
"noResultsFound": "Nincs megjeleníthető adat",
|
||||
"settingsHiddenDependency": "Az egyező beállítások rejtve vannak, mert egy másik beállítás értékétől függenek:",
|
||||
"settingsDependsOn": "{setting}: ettől függ: {dependency}",
|
||||
"settingsAdvancedMode": "{setting}: Haladó beállítások engedélyezése szükséges",
|
||||
"keepSearching": "Keresés folytatása",
|
||||
"removeExpiry": "Lejárat eltávolítása",
|
||||
"enterExpiry": "Lejárati dátum megadása",
|
||||
"enableReferrals": "Hivatkozások engedélyezése",
|
||||
"disableReferrals": "Hivatkozások tiltása",
|
||||
"useInviteExpiry": "Lejárat beállítása profilból vagy meghívóból",
|
||||
"useInviteExpiryNote": "Alapértelmezés szerint a meghívók 90 nap után lejárnak, de a felhasználó megújíthatja őket. Engedélyezze, ha azt szeretné, hogy a megadott idő lejárta után a meghívás letiltásra kerüljön.",
|
||||
"settingsMaybeUnderAdvanced": "Tipp: Lehet hogy megtalálod amit keresel ha bekapcsolod a haladó beállíításokat.",
|
||||
"jellyseerrProfile": "Jellyseer felhasználói profil",
|
||||
"jellyseerrUserDefaultsDescription": "Hozz létre egy Jellyseerr felhasználót, állítsd be, majd válaszd ki lent. A beállításait/engedélyeit a rendszer tárolja és alkalmazza a jfa-go által létrehozott új Jellyseerr felhasználókra, amikor ezt a profilt kiválasztod.",
|
||||
"sortDirection": "Rendezés iránya",
|
||||
"searchAll": "Összes keresés/rendezés",
|
||||
"searchAllRecords": "Keresés/rendezés az összes adaton(a szerveren lévő)",
|
||||
"builtBy": "Készítette"
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"create": "",
|
||||
"apply": "",
|
||||
"select": "",
|
||||
"name": "",
|
||||
"name": "Nome",
|
||||
"date": "",
|
||||
"setExpiry": "",
|
||||
"updates": "",
|
||||
@@ -117,7 +117,8 @@
|
||||
"userPageLogin": "",
|
||||
"userPagePage": "",
|
||||
"buildTime": "",
|
||||
"builtBy": ""
|
||||
"builtBy": "",
|
||||
"disabled": "Disabilitato"
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "",
|
||||
|
||||
322
lang/admin/tr-tr.json
Normal file
322
lang/admin/tr-tr.json
Normal file
@@ -0,0 +1,322 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "İngilizce (ABD)"
|
||||
},
|
||||
"strings": {
|
||||
"invites": "Davetler",
|
||||
"invite": "Davet",
|
||||
"accounts": "Hesaplar",
|
||||
"activity": "Aktivite",
|
||||
"settings": "Ayarlar",
|
||||
"inviteMonths": "Ay",
|
||||
"inviteDays": "Gün",
|
||||
"inviteHours": "Saat",
|
||||
"inviteMinutes": "Dakika",
|
||||
"inviteNumberOfUses": "",
|
||||
"inviteDuration": "",
|
||||
"warning": "",
|
||||
"inviteInfiniteUsesWarning": "",
|
||||
"inviteSendToEmail": "",
|
||||
"create": "",
|
||||
"apply": "",
|
||||
"select": "",
|
||||
"name": "",
|
||||
"date": "",
|
||||
"updates": "",
|
||||
"update": "",
|
||||
"download": "",
|
||||
"search": "",
|
||||
"advancedSettings": "",
|
||||
"lastActiveTime": "",
|
||||
"from": "",
|
||||
"after": "",
|
||||
"before": "",
|
||||
"user": "",
|
||||
"userExpiry": "",
|
||||
"userExpiryDescription": "",
|
||||
"aboutProgram": "",
|
||||
"version": "",
|
||||
"commitNoun": "",
|
||||
"newUser": "",
|
||||
"profile": "",
|
||||
"unknown": "",
|
||||
"label": "",
|
||||
"userLabel": "",
|
||||
"userLabelDescription": "",
|
||||
"logs": "",
|
||||
"announce": "",
|
||||
"templates": "",
|
||||
"subject": "",
|
||||
"message": "Mesaj",
|
||||
"variables": "",
|
||||
"conditionals": "",
|
||||
"preview": "",
|
||||
"reset": "",
|
||||
"donate": "",
|
||||
"unlink": "",
|
||||
"deleted": "",
|
||||
"disabled": "Devre Dışı",
|
||||
"sendPWR": "",
|
||||
"noResultsFound": "",
|
||||
"noResultsFoundLocally": "",
|
||||
"keepSearching": "",
|
||||
"keepSearchingDescription": "",
|
||||
"contactThrough": "",
|
||||
"extendExpiry": "",
|
||||
"setExpiry": "",
|
||||
"removeExpiry": "",
|
||||
"enterExpiry": "",
|
||||
"sendPWRManual": "",
|
||||
"sendPWRSuccess": "",
|
||||
"sendPWRSuccessManual": "",
|
||||
"sendPWRValidFor": "",
|
||||
"customizeMessages": "",
|
||||
"customizeMessagesDescription": "",
|
||||
"markdownSupported": "",
|
||||
"modifySettings": "",
|
||||
"modifySettingsDescription": "",
|
||||
"enableReferrals": "",
|
||||
"disableReferrals": "",
|
||||
"enableReferralsDescription": "",
|
||||
"enableReferralsProfileDescription": "",
|
||||
"useInviteExpiry": "",
|
||||
"useInviteExpiryNote": "",
|
||||
"applyHomescreenLayout": "",
|
||||
"applyConfigurationAndPolicy": "",
|
||||
"applyOmbi": "",
|
||||
"applyJellyseerr": "",
|
||||
"sendDeleteNotificationEmail": "",
|
||||
"sendDeleteNotifiationExample": "",
|
||||
"settingsRestart": "",
|
||||
"settingsRestarting": "",
|
||||
"settingsRestartRequired": "",
|
||||
"settingsRestartRequiredDescription": "",
|
||||
"settingsApplyRestartLater": "",
|
||||
"settingsApplyRestartNow": "",
|
||||
"settingsApplied": "",
|
||||
"settingsRefreshPage": "",
|
||||
"settingsRequiredOrRestartMessage": "",
|
||||
"settingsSave": "",
|
||||
"settingsHiddenDependency": "",
|
||||
"settingsDependsOn": "",
|
||||
"settingsAdvancedMode": "",
|
||||
"settingsMaybeUnderAdvanced": "",
|
||||
"ombiProfile": "",
|
||||
"ombiUserDefaultsDescription": "",
|
||||
"jellyseerrProfile": "",
|
||||
"jellyseerrUserDefaultsDescription": "",
|
||||
"userProfiles": "",
|
||||
"userProfilesDescription": "",
|
||||
"userProfilesIsDefault": "",
|
||||
"userProfilesLibraries": "",
|
||||
"addProfile": "",
|
||||
"addProfileDescription": "",
|
||||
"addProfileNameOf": "",
|
||||
"addProfileStoreHomescreenLayout": "",
|
||||
"inviteNoUsersCreated": "",
|
||||
"inviteUsersCreated": "",
|
||||
"inviteNoProfile": "",
|
||||
"inviteDateCreated": "",
|
||||
"inviteNoInvites": "",
|
||||
"inviteExpiresInTime": "",
|
||||
"notifyEvent": "",
|
||||
"notifyInviteExpiry": "",
|
||||
"notifyUserCreation": "",
|
||||
"sendPIN": "",
|
||||
"searchDiscordUser": "",
|
||||
"findDiscordUser": "",
|
||||
"linkMatrixDescription": "",
|
||||
"matrixHomeServer": "",
|
||||
"saveAsTemplate": "",
|
||||
"deleteTemplate": "",
|
||||
"templateEnterName": "",
|
||||
"accessJFA": "",
|
||||
"accessJFASettings": "",
|
||||
"sortingBy": "",
|
||||
"sortDirection": "",
|
||||
"filters": "",
|
||||
"clickToRemoveFilter": "",
|
||||
"clearSearch": "",
|
||||
"searchAll": "",
|
||||
"searchAllRecords": "",
|
||||
"actions": "",
|
||||
"searchOptions": "",
|
||||
"matchText": "",
|
||||
"jellyfinID": "",
|
||||
"userPageLogin": "",
|
||||
"userPagePage": "",
|
||||
"postSignupCard": "",
|
||||
"postSignupCardDescription": "",
|
||||
"buildTime": "",
|
||||
"builtBy": "",
|
||||
"buildTags": "",
|
||||
"loginNotAdmin": "",
|
||||
"referrer": "",
|
||||
"accountLinked": "",
|
||||
"accountUnlinked": "",
|
||||
"accountResetPassword": "",
|
||||
"accountChangedPassword": "",
|
||||
"accountCreated": "",
|
||||
"accountDeleted": "",
|
||||
"accountDisabled": "",
|
||||
"accountReEnabled": "",
|
||||
"accountExpired": "",
|
||||
"accountWillExpire": "",
|
||||
"expirationBasedOn": "",
|
||||
"userDeleted": "",
|
||||
"userDisabled": "",
|
||||
"inviteCreated": "",
|
||||
"inviteDeleted": "",
|
||||
"inviteExpired": "",
|
||||
"fromInvite": "",
|
||||
"byAdmin": "",
|
||||
"byUser": "",
|
||||
"byJfaGo": "",
|
||||
"activityID": "",
|
||||
"title": "",
|
||||
"usersMentioned": "",
|
||||
"actor": "",
|
||||
"actorDescription": "",
|
||||
"accountCreationFilter": "",
|
||||
"accountDeletionFilter": "",
|
||||
"accountDisabledFilter": "",
|
||||
"accountEnabledFilter": "",
|
||||
"contactLinkedFilter": "",
|
||||
"contactUnlinkedFilter": "",
|
||||
"passwordChangeFilter": "",
|
||||
"passwordResetFilter": "",
|
||||
"inviteCreatedFilter": "",
|
||||
"inviteDeletedFilter": "",
|
||||
"loadMore": "",
|
||||
"loadAll": "",
|
||||
"noMoreResults": "",
|
||||
"totalRecords": "",
|
||||
"loadedRecords": "",
|
||||
"shownRecords": "",
|
||||
"selectedRecords": "",
|
||||
"allMatchingSelected": "",
|
||||
"allLoadedSelected": "",
|
||||
"backups": "",
|
||||
"backupsDescription": "",
|
||||
"backupsFormatNote": "",
|
||||
"backupsCopy": "",
|
||||
"backupDownloadRestore": "",
|
||||
"backupUpload": "",
|
||||
"backupDownload": "",
|
||||
"backupRestore": "",
|
||||
"backupNow": "",
|
||||
"backupCreated": "",
|
||||
"backupCanBeFound": "",
|
||||
"backupCanDownload": "",
|
||||
"wikiPage": "",
|
||||
"wiki": ""
|
||||
},
|
||||
"notifications": {
|
||||
"pathCopied": "",
|
||||
"changedEmailAddress": "",
|
||||
"userCreated": "",
|
||||
"createProfile": "",
|
||||
"saveSettings": "",
|
||||
"saveEmail": "",
|
||||
"sentAnnouncement": "",
|
||||
"savedAnnouncement": "",
|
||||
"setOmbiProfile": "",
|
||||
"savedProfile": "",
|
||||
"updateApplied": "",
|
||||
"updateAppliedRefresh": "",
|
||||
"telegramVerified": "",
|
||||
"accountConnected": "",
|
||||
"referralsEnabled": "",
|
||||
"activityDeleted": "",
|
||||
"errorInviteNoLongerExists": "",
|
||||
"errorInviteNotFound": "",
|
||||
"errorSettingsAppliedNoHomescreenLayout": "",
|
||||
"errorHomescreenAppliedNoSettings": "",
|
||||
"errorSettingsFailed": "",
|
||||
"errorSaveEmail": "",
|
||||
"errorBlankFields": "",
|
||||
"errorDeleteProfile": "",
|
||||
"errorLoadProfiles": "",
|
||||
"errorCreateProfile": "",
|
||||
"errorSavedProfile": "",
|
||||
"errorSetDefaultProfile": "",
|
||||
"errorLoadUsers": "",
|
||||
"errorLoadSettings": "",
|
||||
"errorSetOmbiProfile": "",
|
||||
"errorLoadOmbiUsers": "",
|
||||
"errorChangedEmailAddress": "",
|
||||
"errorFailureCheckLogs": "",
|
||||
"errorPartialFailureCheckLogs": "",
|
||||
"errorUserCreated": "",
|
||||
"errorSendWelcomeEmail": "",
|
||||
"errorApplyUpdate": "",
|
||||
"errorCheckUpdate": "",
|
||||
"errorNoReferralTemplate": "",
|
||||
"errorLoadActivities": "",
|
||||
"errorInvalidDate": "",
|
||||
"updateAvailable": "",
|
||||
"noUpdatesAvailable": ""
|
||||
},
|
||||
"quantityStrings": {
|
||||
"modifySettingsFor": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"enableReferralsFor": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"deleteNUsers": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"disableUsers": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"reEnableUsers": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"addUser": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"deleteUser": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"deletedUser": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"disabledUser": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"enabledUser": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"announceTo": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"appliedSettings": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"extendExpiry": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"setExpiry": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"extendedExpiry": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,60 +5,67 @@
|
||||
"strings": {
|
||||
"username": "اسم المستخدم",
|
||||
"password": "كلمة المرور",
|
||||
"emailAddress": "البريد الالكتروني",
|
||||
"emailAddress": "عنوان البريد الإلكتروني",
|
||||
"name": "الاسم",
|
||||
"submit": "ادخال",
|
||||
"success": "نجاح",
|
||||
"continue": "اكمل",
|
||||
"submit": "إرسال",
|
||||
"success": "تم",
|
||||
"continue": "متابعة",
|
||||
"error": "خطأ",
|
||||
"copy": "نسخ",
|
||||
"time24h": "توقيت 24 ساعة",
|
||||
"time12h": "توقيت 12 ساعة",
|
||||
"linkTelegram": "رابط تلغرام",
|
||||
"contactTelegram": "التواصل عبر التلغرام",
|
||||
"linkDiscord": "رابط الدسكورد",
|
||||
"linkTelegram": "ربط Telegram",
|
||||
"contactTelegram": "التواصل عبر Telegram",
|
||||
"linkDiscord": "ربط Discord",
|
||||
"linkMatrix": "ربط Matrix",
|
||||
"contactDiscord": "التواصل عبر الدسكورد",
|
||||
"theme": "القالب",
|
||||
"contactDiscord": "التواصل عبر Discord",
|
||||
"theme": "السمة",
|
||||
"refresh": "تحديث",
|
||||
"required": "مطلوب",
|
||||
"login": "تسجيل الدخول",
|
||||
"admin": "المسؤول",
|
||||
"reEnable": "اعادة تفعيل",
|
||||
"disable": "تجميد",
|
||||
"reEnable": "إعادة تفعيل",
|
||||
"disable": "تعطيل",
|
||||
"accountStatus": "حالة الحساب",
|
||||
"notSet": "لم تحدد",
|
||||
"expiry": "انتهاء الصلاحية",
|
||||
"add": "اضافة",
|
||||
"add": "إضافة",
|
||||
"edit": "تعديل",
|
||||
"delete": "حذف",
|
||||
"myAccount": "حسابي",
|
||||
"disabled": "معطل",
|
||||
"enabled": "مفعل",
|
||||
"send": "ارسال",
|
||||
"disabled": "معطّل",
|
||||
"enabled": "مفعّل",
|
||||
"send": "إرسال",
|
||||
"copied": "تم النسخ",
|
||||
"contactEmail": "التواصل عبر البريد الالكتروني",
|
||||
"contactEmail": "التواصل عبر البريد",
|
||||
"logout": "تسجيل الخروج",
|
||||
"contactMethods": "وسيلة التواصل"
|
||||
"contactMethods": "وسائل الاتصال",
|
||||
"referrals": "الإحالات",
|
||||
"inviteRemainingUses": "الاستخدامات المتبقية",
|
||||
"internal": "داخلي",
|
||||
"external": "خارجي",
|
||||
"sent": "تم إرساُلها",
|
||||
"failed": "فشل"
|
||||
},
|
||||
"notifications": {
|
||||
"errorUnknown": "خطأ غير معروف.",
|
||||
"error401Unauthorized": "غير مخول. حاول تحديث الصفحة.",
|
||||
"errorSaveSettings": "لا يمكن حفظ الاعدادات.",
|
||||
"errorLoginBlank": "اسم المستخدم و/أو كلمة المرور لم يتم ادخالها.",
|
||||
"errorConnection": "لا يمكن الاتصال بـالبرنامج."
|
||||
"error401Unauthorized": "غير مخوّل. حاول تحديث الصفحة.",
|
||||
"errorSaveSettings": "تعذر حفظ الإعدادات.",
|
||||
"errorLoginBlank": "اسم المستخدم و/أو كلمة المرور تُرِكا فارغَين.",
|
||||
"errorConnection": "تعذر الاتصال بـ jfa-go.",
|
||||
"errorSpecialSymbols": "لا يمكن أن يحتوي الحقل على رموز خاصة."
|
||||
},
|
||||
"quantityStrings": {
|
||||
"year": {
|
||||
"singular": "{n} سنة",
|
||||
"singular": "سنة",
|
||||
"plural": "{n} سنوات"
|
||||
},
|
||||
"month": {
|
||||
"singular": "{n} شهر",
|
||||
"singular": "شهر",
|
||||
"plural": "{n} أشهر"
|
||||
},
|
||||
"day": {
|
||||
"singular": "{n} يوم",
|
||||
"singular": "يوم",
|
||||
"plural": "{n} أيام"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
"name": "English (US)"
|
||||
},
|
||||
"strings": {
|
||||
"language": "Language",
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"emailAddress": "Email Address",
|
||||
"name": "Name",
|
||||
"submit": "Submit",
|
||||
"send": "Send",
|
||||
"sent": "Sent",
|
||||
"success": "Success",
|
||||
"continue": "Continue",
|
||||
"error": "Error",
|
||||
@@ -43,7 +45,13 @@
|
||||
"referrals": "Referrals",
|
||||
"inviteRemainingUses": "Remaining uses",
|
||||
"internal": "Internal",
|
||||
"external": "External"
|
||||
"external": "External",
|
||||
"failed": "Failed",
|
||||
"details": "Details",
|
||||
"type": "Type",
|
||||
"other": "Other",
|
||||
"back": "Back",
|
||||
"none": "None"
|
||||
},
|
||||
"notifications": {
|
||||
"errorLoginBlank": "The username and/or password were left blank.",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "انگلیسی"
|
||||
"name": "انگلیسی (FA)"
|
||||
},
|
||||
"strings": {
|
||||
"username": "نام کاربری",
|
||||
|
||||
@@ -39,14 +39,19 @@
|
||||
"contactMethods": "Kapcsolati lehetőségek",
|
||||
"accountStatus": "Fiók státusz",
|
||||
"notSet": "Nincs beállítva",
|
||||
"myAccount": "Saját fiókom"
|
||||
"myAccount": "Saját fiókom",
|
||||
"internal": "Belső",
|
||||
"referrals": "Hivatkozások",
|
||||
"inviteRemainingUses": "Fennmaradó felhasználások",
|
||||
"external": "Külső"
|
||||
},
|
||||
"notifications": {
|
||||
"errorLoginBlank": "A felhasználónév és/vagy a jelszó üresen lett hagyva.",
|
||||
"errorConnection": "Nem lehet csatlakozni a jfa-go-hoz.",
|
||||
"errorUnknown": "Ismeretlen hiba.",
|
||||
"error401Unauthorized": "Nincs jogosultság. Próbáld frissíteni az oldalt.",
|
||||
"errorSaveSettings": "Nem lehet menteni a beállításokat."
|
||||
"errorSaveSettings": "Nem lehet menteni a beállításokat.",
|
||||
"errorSpecialSymbols": "Ez a mező nem tartalmazhat speciális karaktereket."
|
||||
},
|
||||
"quantityStrings": {
|
||||
"year": {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Italiano (IT)"
|
||||
},
|
||||
"strings": {
|
||||
"username": "Username",
|
||||
"username": "Nome Utente",
|
||||
"password": "Password",
|
||||
"emailAddress": "Indirizzo Email",
|
||||
"name": "Nome",
|
||||
|
||||
70
lang/common/tr-tr.json
Normal file
70
lang/common/tr-tr.json
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "İngilizce (ABD)"
|
||||
},
|
||||
"strings": {
|
||||
"username": "Kullanıcı Adı",
|
||||
"password": "Şifre",
|
||||
"emailAddress": "E-posta Adresi",
|
||||
"name": "İsim",
|
||||
"submit": "Kaydet",
|
||||
"send": "Gönder",
|
||||
"success": "Başarılı",
|
||||
"continue": "Devam Et",
|
||||
"error": "Hata",
|
||||
"copy": "Kopyala",
|
||||
"copied": "Kopyalandı",
|
||||
"time24h": "24 Saat",
|
||||
"time12h": "12 Saat",
|
||||
"linkTelegram": "Telegram Bağla",
|
||||
"contactEmail": "E-posta ile İletişim",
|
||||
"contactTelegram": "Telegram ile İletişim",
|
||||
"linkDiscord": "Discord Bağla",
|
||||
"linkMatrix": "Matrix Bağla",
|
||||
"contactDiscord": "Discord ile İletişim",
|
||||
"theme": "Tema",
|
||||
"refresh": "Yenile",
|
||||
"required": "Gerekli",
|
||||
"login": "Oturum Aç",
|
||||
"logout": "Oturumu Kapat",
|
||||
"admin": "Yönetici",
|
||||
"enabled": "Etkin",
|
||||
"disabled": "Devre Dışı",
|
||||
"reEnable": "Yeniden Etkinleştir",
|
||||
"disable": "Devre Dışı Bırak",
|
||||
"contactMethods": "İletişim Yöntemleri",
|
||||
"accountStatus": "Hesap Durumu",
|
||||
"notSet": "Ayarlanmadı",
|
||||
"expiry": "Son Kullanma Tarihi",
|
||||
"add": "Ekle",
|
||||
"edit": "Düzenle",
|
||||
"delete": "Sil",
|
||||
"myAccount": "Hesabım",
|
||||
"referrals": "Referanslar",
|
||||
"inviteRemainingUses": "Kalan Kullanım",
|
||||
"internal": "Dahili",
|
||||
"external": "Harici"
|
||||
},
|
||||
"notifications": {
|
||||
"errorLoginBlank": "Kullanıcı adı ve/veya şifre boş bırakıldı.",
|
||||
"errorConnection": "jfa-go'ya bağlanılamadı.",
|
||||
"errorUnknown": "Bilinmeyen hata.",
|
||||
"error401Unauthorized": "Yetkisiz İşlem. Sayfayı yenilemeyi deneyin.",
|
||||
"errorSaveSettings": "Ayarlar kaydedilemedi.",
|
||||
"errorSpecialSymbols": "Alan özel semboller içeremez."
|
||||
},
|
||||
"quantityStrings": {
|
||||
"year": {
|
||||
"singular": "{n} Yıl",
|
||||
"plural": "{n} Yıl"
|
||||
},
|
||||
"month": {
|
||||
"singular": "{n} Ay",
|
||||
"plural": "{n} Ay"
|
||||
},
|
||||
"day": {
|
||||
"singular": "{n} Gün",
|
||||
"plural": "{n} Gün"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,64 +3,64 @@
|
||||
"name": "العربية (AR)"
|
||||
},
|
||||
"strings": {
|
||||
"ifItWasNotYou": "اذا لم يكن هذا انت، الرجاء تجاهل هذا.",
|
||||
"helloUser": "مرحباً {username}،",
|
||||
"ifItWasNotYou": "اذا لم يكن هذا أنت، الرجاء تجاهل هذه الرسالة.",
|
||||
"helloUser": "أهلاً {username}،",
|
||||
"reason": "السبب"
|
||||
},
|
||||
"userCreated": {
|
||||
"name": "إنشاء حساب",
|
||||
"title": "ملاحظة: تم إنشاء الحساب",
|
||||
"aUserWasCreated": "تم إنشاء الحساب بواسطة الرمز {code}.",
|
||||
"name": "إنشاء مستخدم",
|
||||
"title": "إشعار: تم إنشاء المستخدم",
|
||||
"aUserWasCreated": "تم إنشاء مستخدم باستخدام الرمز {code}.",
|
||||
"time": "الوقت",
|
||||
"notificationNotice": "ملاحظة: الرسائل التذكيرية يمكن تعديلها في لوحة التحكم."
|
||||
},
|
||||
"inviteExpiry": {
|
||||
"name": "انتهاء صلاحية الدعوة",
|
||||
"title": "ملاحظة: انتهت صلاحية الدعوة",
|
||||
"title": "إشعار: انتهت صلاحية الدعوة",
|
||||
"inviteExpired": "انتهت صلاحية الدعوة.",
|
||||
"expiredAt": "انتهت صلاحية الرمز {code} في {time} .",
|
||||
"notificationNotice": "ملاحظة: الرسائل التذكيرية يمكن تعديلها في لوحة التحكم."
|
||||
},
|
||||
"passwordReset": {
|
||||
"name": "إعادة تعيين كلمة المرور",
|
||||
"title": "تم طلب إعادة تعيين كلمة المرور - Jellyfin",
|
||||
"title": "طلب إعادة تعيين كلمة المرور - Jellyfin",
|
||||
"someoneHasRequestedReset": "قام شخص ما بطلب إعادة تعيين كلمة المرور مؤخرا.",
|
||||
"ifItWasYou": "إذا كان هذا انت، أدخل رمز التعريف الشخصي أدناه في الخانة.",
|
||||
"ifItWasYouLink": "إذا كان هذا انت، اضغط على الرابط أدناه.",
|
||||
"ifItWasYou": "إذا كان هذا انت، أدخل الرمز أدناه في الخانة.",
|
||||
"ifItWasYouLink": "إذا كان هذا انت، اضغط الرابط أدناه.",
|
||||
"codeExpiry": "ستنتهي صلاحية الرمز في {date}، {time} UTC، خلال {expiresInMinutes}.",
|
||||
"pin": "رمز التعريف الشخصي"
|
||||
"pin": "الرمز"
|
||||
},
|
||||
"userDeleted": {
|
||||
"name": "حذف المستخدم",
|
||||
"title": "لقد تم حذف حسابك - Jellyfin",
|
||||
"name": "حذف مستخدم",
|
||||
"title": "تم حذف حسابك - Jellyfin",
|
||||
"yourAccountWasDeleted": "لقد تم حذف حسابك في Jellyfin."
|
||||
},
|
||||
"userDisabled": {
|
||||
"name": "تعطيل المستخدم",
|
||||
"title": "لقد تم تعطيل حسابك - Jellyfin",
|
||||
"name": "تعطيل مستخدم",
|
||||
"title": "تم تعطيل حسابك - Jellyfin",
|
||||
"yourAccountWasDisabled": "لقد تم تعطيل حسابك."
|
||||
},
|
||||
"userEnabled": {
|
||||
"name": "تفعيل المستخدم",
|
||||
"title": "لقد تم تفعيل حسابك - Jellyfin",
|
||||
"name": "تفعيل مستخدم",
|
||||
"title": "تم تفعيل حسابك - Jellyfin",
|
||||
"yourAccountWasEnabled": "لقد تم تفعيل حسابك."
|
||||
},
|
||||
"inviteEmail": {
|
||||
"name": "دعوة البريد الإلكتروني",
|
||||
"name": "بريد الدعوة",
|
||||
"title": "دعوة - Jellyfin",
|
||||
"hello": "مرحباً",
|
||||
"hello": "أهلاً",
|
||||
"youHaveBeenInvited": "تمت دعوتك إلى Jellyfin.",
|
||||
"toJoin": "للإنضمام، اتبع الرابط أدناه.",
|
||||
"inviteExpiry": "ستنتهي صلاحية الدعوة في {date} {time}، خلال {expiresInMinutes}، اتخذ اجراءاً.",
|
||||
"linkButton": "قم بإعداد حسابك"
|
||||
},
|
||||
"welcomeEmail": {
|
||||
"name": "مرحباً",
|
||||
"name": "الترحيب",
|
||||
"title": "مرحباً في Jellyfin",
|
||||
"welcome": "مرحباً في Jellyfin!",
|
||||
"youCanLoginWith": "يمكنك تسجيل الدخول بإستخدام المعلومات أدناه",
|
||||
"yourAccountWillExpire": "ستنتهي صلاحية حسابك في {date}.",
|
||||
"jellyfinURL": "رابط"
|
||||
"jellyfinURL": "الرابط"
|
||||
},
|
||||
"emailConfirmation": {
|
||||
"name": "بريد التحقق",
|
||||
@@ -69,9 +69,21 @@
|
||||
"confirmEmail": "تأكيد البريد الإلكتروني"
|
||||
},
|
||||
"userExpired": {
|
||||
"name": "انتهاء صلاحية المستخدم",
|
||||
"name": "انتهاء صلاحية مستخدم",
|
||||
"title": "انتهت صلاحية حسابك - Jellyfin",
|
||||
"yourAccountHasExpired": "انتهت صلاحية حسابك.",
|
||||
"contactTheAdmin": "تواصل مع المشرفين للمزيد من المعلومات."
|
||||
},
|
||||
"expiryReminder": {
|
||||
"name": "تذكير انتهاء الصلاحية",
|
||||
"title": "تذكير: ستنتهي صلاحية حسابك قريباً - Jellyfin",
|
||||
"yourAccountIsDueToExpire": "ستنتهي صلاحية حسابك خلال {expiresIn}، في{date} {time}."
|
||||
},
|
||||
"userExpiryAdjusted": {
|
||||
"yourExpiryWasAdjusted": "تم تغيير تاريخ انتهاء صلاحية حسابك.",
|
||||
"name": "تغيير مدة انتهاء الصلاحية",
|
||||
"title": "تغيرت مدة صلاحية حسابك - Jellyfin",
|
||||
"ifPreviouslyDisabled": "إذا تم تعطيل حسابك مسبقاً، فمن الممكن أنه قد تم تفعيله.",
|
||||
"newExpiry": "ستنتهي صلاحية حسابك في: {date}."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,75 +3,82 @@
|
||||
"name": "Magyar (HU)"
|
||||
},
|
||||
"strings": {
|
||||
"ifItWasNotYou": "",
|
||||
"helloUser": "",
|
||||
"reason": ""
|
||||
"ifItWasNotYou": "Ha nem Te voltál, akkor hagyd figyelmen kívül.",
|
||||
"helloUser": "Szia {username},",
|
||||
"reason": "Ok"
|
||||
},
|
||||
"userCreated": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"aUserWasCreated": "",
|
||||
"time": "",
|
||||
"notificationNotice": ""
|
||||
"name": "Felhasználó létrehozása",
|
||||
"title": "Értesítés: Felhasználó létrehozva",
|
||||
"aUserWasCreated": "Felhasználó létrehozva {code} kóddal.",
|
||||
"time": "Idő",
|
||||
"notificationNotice": "Megjegyzés: A figyelmeztető üzenetek ki- be kapcsolhatók az admin felületen."
|
||||
},
|
||||
"inviteExpiry": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"inviteExpired": "",
|
||||
"expiredAt": "",
|
||||
"notificationNotice": ""
|
||||
"name": "Meghívó lejárata",
|
||||
"title": "Értesítés: A meghívó lejárt",
|
||||
"inviteExpired": "A meghívó lejárt.",
|
||||
"expiredAt": "A {code} kód lejárt ekkor {time}.",
|
||||
"notificationNotice": "Megjegyzés: A figyelmeztető üzenetek ki- be kapcsolhatók az admin felületen."
|
||||
},
|
||||
"passwordReset": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"someoneHasRequestedReset": "",
|
||||
"ifItWasYou": "",
|
||||
"ifItWasYouLink": "",
|
||||
"codeExpiry": "",
|
||||
"pin": ""
|
||||
"name": "Jelszó visszaállítás",
|
||||
"title": "Jelszó visszaállítási kérelem - Jellyfin",
|
||||
"someoneHasRequestedReset": "Valaki mostanában jelszó visszaállítást kért.",
|
||||
"ifItWasYou": "Ha Te voltál, írd be a kódot ide.",
|
||||
"ifItWasYouLink": "Ha Te voltál, kattints a linkre.",
|
||||
"codeExpiry": "A kód lejárt {expiresInMinutes} perce. ({date} {time} UTC).",
|
||||
"pin": "PIN"
|
||||
},
|
||||
"userDeleted": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"yourAccountWasDeleted": ""
|
||||
"name": "Felhasználó törlése",
|
||||
"title": "A fiókod törölve lett - Jellyfin",
|
||||
"yourAccountWasDeleted": "A jellyfin fiókod törölve lett."
|
||||
},
|
||||
"userDisabled": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"yourAccountWasDisabled": ""
|
||||
"name": "Felhsználó letiltva",
|
||||
"title": "A felhasználód le lett tiltva - Jellyfin",
|
||||
"yourAccountWasDisabled": "A fiókod le lett tiltva."
|
||||
},
|
||||
"userEnabled": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"yourAccountWasEnabled": ""
|
||||
"name": "Felhasználó engedélyezve",
|
||||
"title": "A fiókod fel lett oldva - Jellyfin",
|
||||
"yourAccountWasEnabled": "A fiókod fel lett oldva."
|
||||
},
|
||||
"inviteEmail": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"hello": "",
|
||||
"youHaveBeenInvited": "",
|
||||
"toJoin": "",
|
||||
"inviteExpiry": "",
|
||||
"linkButton": ""
|
||||
"name": "Meghívó email",
|
||||
"title": "Meghívó - Jellyfin",
|
||||
"hello": "Szia",
|
||||
"youHaveBeenInvited": "Meghívtak a jellyfin alkalmazásba.",
|
||||
"toJoin": "Csatlakozáshoz kattints a linkre.",
|
||||
"inviteExpiry": "A meghívó {date} {time}-kor lejár, ami {expiresInMinutes} perc múlva lesz, szóval gyorsan cselekedj.",
|
||||
"linkButton": "Fiók beállítása"
|
||||
},
|
||||
"welcomeEmail": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"welcome": "",
|
||||
"youCanLoginWith": "",
|
||||
"yourAccountWillExpire": "",
|
||||
"jellyfinURL": ""
|
||||
"name": "Üdvözöllek",
|
||||
"title": "Üdvözöllek a Jellyfin-ben",
|
||||
"welcome": "Üdvözöllek a Jellyfin-ben!",
|
||||
"youCanLoginWith": "Be tudsz lépni az alábbi adatokkal",
|
||||
"yourAccountWillExpire": "A fiókod {date} dátummal lejár.",
|
||||
"jellyfinURL": "URL"
|
||||
},
|
||||
"emailConfirmation": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"clickBelow": "",
|
||||
"confirmEmail": ""
|
||||
"name": "Megerősítő email cím",
|
||||
"title": "Erősítsd meg az email címed- Jellyfin",
|
||||
"clickBelow": "Kattints az alábbi linkre, hogy megerősítsd az email címed és elkezd használni a jellyfin-t.",
|
||||
"confirmEmail": "Email megerősítése"
|
||||
},
|
||||
"userExpired": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"yourAccountHasExpired": "",
|
||||
"contactTheAdmin": ""
|
||||
"name": "Felhasználó lejárata",
|
||||
"title": "A fiókod lejárt - Jellyfin",
|
||||
"yourAccountHasExpired": "A fiókod lejárt.",
|
||||
"contactTheAdmin": "Lépj kapcsolatba az rendszergazdával további információkért."
|
||||
},
|
||||
"userExpiryAdjusted": {
|
||||
"name": "Lejárat módosítva",
|
||||
"title": "Fiók lejárat módosítva - Jellyfin",
|
||||
"yourExpiryWasAdjusted": "A fiókod lejárata módosult.",
|
||||
"ifPreviouslyDisabled": "Ha fiókod korábban letiltották, előfordulhat, hogy újra engedélyezték.",
|
||||
"newExpiry": "A fiókod {date} napon lejár."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,70 +10,79 @@
|
||||
"username": "اسم المستخدم",
|
||||
"password": "كلمة المرور",
|
||||
"reEnterPassword": "تأكيد كلمة المرور",
|
||||
"reEnterPasswordInvalid": "كلمة المرور غير مطابقة.",
|
||||
"reEnterPasswordInvalid": "كلمات المرور غير متطابقة.",
|
||||
"createAccountButton": "إنشاء الحساب",
|
||||
"passwordRequirementsHeader": "متطلبات كلمة المرور",
|
||||
"successHeader": "تم!",
|
||||
"confirmationRequired": "مطلوب تأكيد البريد الإلكتروني",
|
||||
"confirmationRequiredMessage": "يرجى التحقق من صندوق البريد الإلكتروني الخاص بك للتحقق من عنوانك.",
|
||||
"yourAccountIsValidUntil": "سيكون حسابك ساري المفعول حتى {date}.",
|
||||
"sendPIN": "أرسل رقم التعريف الشخصي أدناه إلى البوت ، ثم عُد إلى هنا لربط حسابك.",
|
||||
"sendPINDiscord": "اكتب {command} في {server_channel} على Discord ، ثم أرسل رقم التعريف الشخصي أدناه.",
|
||||
"matrixEnterUser": "أدخل معرف المستخدم الخاص بك، واضغط على إرسال، وسيتم إرسال رمز PIN إليك. أدخله هنا للمتابعة.",
|
||||
"oldPassword": "كلمة المرور السابقة",
|
||||
"confirmationRequiredMessage": "يرجى التحقق من بريدك الإلكتروني لتأكيد العنوان.",
|
||||
"yourAccountIsValidUntil": "سيكون حسابك صالحاً حتى {date}.",
|
||||
"sendPIN": "أرسل الرمز أدناه إلى البوت، ثم عُد إلى هنا لربط حسابك.",
|
||||
"sendPINDiscord": "اكتب {command} في {server_channel} في Discord، ثم أرسل الرمز أدناه.",
|
||||
"matrixEnterUser": "أدخل مُعرّف المستخدم الخاص بك، واضغط على إرسال، وسيتم إرسال رمز إليك. أدخله هنا للمتابعة.",
|
||||
"oldPassword": "كلمة المرور القديمة",
|
||||
"newPassword": "كلمة المرور الجديدة",
|
||||
"joinTheServer": "انضم إلى السيرفر:",
|
||||
"editContactMethod": "تحرير طريقة الاتصال",
|
||||
"customMessagePlaceholderHeader": "تحرير هذه البطاقة",
|
||||
"joinTheServer": "انضم إلى الخادم:",
|
||||
"editContactMethod": "تغيير وسيلة الاتصال",
|
||||
"customMessagePlaceholderHeader": "تخصيص هذه البطاقة",
|
||||
"resetPassword": "إعادة تعيين كلمة المرور",
|
||||
"resetSent": "تم إرسال إعادة التعيين.",
|
||||
"resetSentDescription": "في حال وجود حساب يملك اسم المستخدم/طريقة الاتصال المحددة، فسيتم إرسال رابط إعادة تعيين كلمة المرور عبر جميع طرق الاتصال المتاحة.ستنتهي صلاحية الرمز خلال 30 دقيقة.",
|
||||
"resetSentDescription": "في حال وجود حساب يملك اسم المستخدم/وسيلة الاتصال المحددة، فسيتم إرسال رابط إعادة تعيين كلمة المرور عبر جميع وسائل الاتصال المتاحة. ستنتهي صلاحية الرمز خلال 30 دقيقة.",
|
||||
"changePassword": "تغيير كلمة المرور",
|
||||
"welcomeUser": "مرحباً، {user}!",
|
||||
"addContactMethod": "إضافة طريقة اتصال",
|
||||
"customMessagePlaceholderContent": "اضغط على زر تحرير صفحة المستخدم في الإعدادات لتخصيص هذه البطاقة، أو أظهر واحدة على صفحة تسجيل الدخول، ولا تقلق، لا يستطيع المستخدم رؤيتها.",
|
||||
"userPageSuccessMessage": "تستطيع رؤية وتحرير التفاصيل حول حسابك لاحقاً في صفحة {myAccount}.",
|
||||
"addContactMethod": "أضف وسيلة اتصال",
|
||||
"customMessagePlaceholderContent": "اضغط على زر التعديل في صفحة المستخدم في الإعدادات لتخصيص هذه البطاقة، أو أظهر واحدة على صفحة تسجيل الدخول، ولا تقلق، لا يستطيع المستخدم رؤيتها.",
|
||||
"userPageSuccessMessage": "يمكنك الاطلاع على تفاصيل حسابك وتغييرها لاحقاً على صفحة {myAccount}.",
|
||||
"resetPasswordThroughJellyfin": "لإعادة تعيين كلمة المرور، قم بزيارة {jfLink} واضغط على زر \"نسيت كلمة المرور\".",
|
||||
"resetPasswordThroughLink": "لإعادة تعيين كلمة المرور، أدخل اسم المستخدم، البريد الإلكتروني، أو اسم مستخدم لطريقة اتصال مرتبطة، ثم أرسل. سيتم إرسال رابط لإعادة تعيين كلمة المرور."
|
||||
"resetPasswordThroughLink": "لإعادة تعيين كلمة المرور، أدخل اسم المستخدم، عنوان البريد الإلكتروني، أو اسم مستخدم لوسيلة اتصال مربوطة، ثم أرسل. سيتم إرسال رابط لإعادة تعيين كلمة المرور.",
|
||||
"resetPasswordThroughLinkEnd": "ثم اضغط على إرسال. سيتم إرسال رابط لإعادة تعيين كلمة مرورك.",
|
||||
"referralsDescription": "ادعُ أصدقاءك وعائلتك إلى Jellyfin باستخدام هذا الرابط. عُد إلى هنا للحصول على رابط جديد إذا انتهت صلاحيته.",
|
||||
"resetPasswordThroughLinkStart": "لإعادة تعيين كلمة مرورك، أدخل أحد الخيارات التالية:",
|
||||
"resetPasswordContactMethod": "اسم المستخدم لأي وسيلة اتصال مرتبطة بحسابك",
|
||||
"copyReferral": "نسخ الرابط",
|
||||
"invitedBy": "تمت دعوتك من قبل المستخدم {user}.",
|
||||
"resetPasswordUsername": "اسم مستخدم Jellyfin الخاص بك",
|
||||
"resetPasswordEmail": "عنوان بريدك الإلكتروني",
|
||||
"referralsWithExpiryDescription": "ادعُ أصدقاءك وعائلتك إلى Jellyfin باستخدام هذا الرابط. سيتم تعطيل الرابط بعد انتهاء صلاحيته."
|
||||
},
|
||||
"notifications": {
|
||||
"errorUserExists": "المستخدم موجود مسبقا.",
|
||||
"errorInvalidCode": "رمز دعوة غير صالح.",
|
||||
"errorTelegramVerification": "مطلوب التحقق من تيليجرام.",
|
||||
"errorDiscordVerification": "مطلوب التحقق من الدسكورد.",
|
||||
"errorMatrixVerification": "مطلوب التحقق من Matrix.",
|
||||
"errorInvalidPIN": "رقم التعريف الشخصي غير صالح.",
|
||||
"errorTelegramVerification": "مطلوب تأكيد Telegram.",
|
||||
"errorDiscordVerification": "مطلوب تأكيد Discord.",
|
||||
"errorMatrixVerification": "مطلوب تأكيد Matrix.",
|
||||
"errorInvalidPIN": "الرمز غير صالح.",
|
||||
"errorUnknown": "خطأ غير معروف.",
|
||||
"errorNoEmail": "البريد الإلكتروني مطلوب.",
|
||||
"errorCaptcha": "كلمة التحقق غير صحيحة.",
|
||||
"errorCaptcha": "كلمة التحقق خاطئة.",
|
||||
"errorPassword": "تحقق من متطلبات كلمة المرور.",
|
||||
"errorNoMatch": "كلمات المرور غير متطابقة.",
|
||||
"verified": "تم التحقق من الحساب.",
|
||||
"errorAccountLinked": "الحساب قيد الاستخدام.",
|
||||
"errorEmailLinked": "البريد الإلكتروني قيد الاستخدام.",
|
||||
"errorOldPassword": "كلمة المرور القديمة غير صحيحة.",
|
||||
"verified": "تم تأكيد الحساب.",
|
||||
"errorAccountLinked": "الحساب مستخدم بالفعل.",
|
||||
"errorEmailLinked": "البريد مستخدم بالفعل.",
|
||||
"errorOldPassword": "كلمة المرور القديمة خاطئة.",
|
||||
"passwordChanged": "تم تغيير كلمة المرور."
|
||||
},
|
||||
"validationStrings": {
|
||||
"length": {
|
||||
"singular": "يجب أن يتألف من {n} حرف على الأقل",
|
||||
"singular": "يجب ألا يقل عدد الأحرف عن {n}",
|
||||
"plural": "يجب ألا يقل عدد الأحرف عن {n}"
|
||||
},
|
||||
"uppercase": {
|
||||
"singular": "يجب أن تحتوي على {n} حرف كبير على الأقل",
|
||||
"singular": "يجب ألا يقل عدد الأحرف الكبيرة عن {n}",
|
||||
"plural": "يجب ألا يقل عدد الأحرف الكبيرة عن {n}"
|
||||
},
|
||||
"lowercase": {
|
||||
"singular": "يجب أن يتألف من {n} حرف صغير على الأقل",
|
||||
"singular": "يجب ألا يقل عدد الأحرف الصغيرة عن {n}",
|
||||
"plural": "يجب ألا يقل عدد الأحرف الصغيرة عن {n}"
|
||||
},
|
||||
"number": {
|
||||
"singular": "يجب أن يحتوي على {n} رقم على الأقل",
|
||||
"plural": "يجب أن يحتوي على {n} رقم على الأقل"
|
||||
"singular": "يجب ألا يقل عدد الأرقام عن {n}",
|
||||
"plural": "يجب ألا يقل عدد الأرقام عن {n}"
|
||||
},
|
||||
"special": {
|
||||
"singular": "يجب أن يتألف من {n} حرف خاص على الأقل",
|
||||
"plural": "يجب أن يتألف من {n} حرف خاص على الأقل"
|
||||
"singular": "يجب ألا يقل عدد الأحرف الخاصة عن {n}",
|
||||
"plural": "يجب ألا يقل عدد الأحرف الخاصة عن {n}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "کوردی سۆرانی"
|
||||
"name": "کوردی سۆرانی (CKB)"
|
||||
},
|
||||
"strings": {
|
||||
"pageTitle": "دروستکردنی هەژماری Jellyfin",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "انگلیسی"
|
||||
"name": "انگلیسی (FA)"
|
||||
},
|
||||
"strings": {
|
||||
"pageTitle": "ساختن حساب کاربری",
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"confirmationRequired": "E-mail megerősítés szükséges",
|
||||
"confirmationRequiredMessage": "Kérjük ellenőrizze az e-mail címére küldött üzenetet, a fiók ellenőrzéséhez.",
|
||||
"yourAccountIsValidUntil": "A fiókja eddig lesz érvényes: {date}.",
|
||||
"sendPIN": "Az alábbi PIN-t küldje el a botnak, majd itt csatolja össze a fiókját.",
|
||||
"sendPIN": "Az alábbi PIN-t küld el a botnak, majd itt csatold össze a fiókoddal.",
|
||||
"sendPINDiscord": "Írja be a {command} parancsot a {server_channel} Discord csatornába, adja meg a PIN-t.",
|
||||
"matrixEnterUser": "Írja be a felhasználója azonosítóját majd nyomja meg a beküldés gombot. A kapott kódot ide írja be.",
|
||||
"customMessagePlaceholderContent": "Kattints a felhasználói oldal szerkesztés gombjára a beállításokban a kártya testreszabásához, vagy jeleníts meg egyet a bejelentkezési képernyőn, ne aggódj, a felhasználó ezt nem láthatja.",
|
||||
@@ -34,7 +34,16 @@
|
||||
"resetPasswordThroughJellyfin": "A jelszavad visszaállításához látogass el a {jfLink} oldalra, és nyomj rá az \"Elfelejtett jelszó\" gombra.",
|
||||
"resetPasswordThroughLink": "A jelszavad visszaállításához, add meg a felhasználóneved, e-mail címed vagy a hozzákötött kapcsolattartási felhasználónevet, és nyomj a gombra. A linket levélben fogod kapni.",
|
||||
"resetSent": "Visszaállítás elküldve.",
|
||||
"changePassword": "Jelszó megváltoztatása"
|
||||
"changePassword": "Jelszó megváltoztatása",
|
||||
"referralsWithExpiryDescription": "Hívd meg barátaidat és családtagjaidat a Jellyfinre ezzel a linkkel. A link nem lesz elérhető, ha lejár.",
|
||||
"referralsDescription": "Hívd meg barátaidat és családtagjaidat a Jellyfinre ezzel a linkkel. Gyere vissza ide egy újért, ha lejár.",
|
||||
"copyReferral": "Link másolása",
|
||||
"invitedBy": "Meghívást kaptál {user} által.",
|
||||
"resetPasswordThroughLinkStart": "Jelszava visszaállításához adja meg az alábbiak egyikét:",
|
||||
"resetPasswordThroughLinkEnd": "Ezután kattints az elküldésre. Egy linket fogsz kapni a jelszó visszaállításához.",
|
||||
"resetPasswordUsername": "Jellyfin felhasználónév",
|
||||
"resetPasswordEmail": "Email cím",
|
||||
"resetPasswordContactMethod": "A fiókodhoz kapcsolt kapcsolatfelvételi mód felhasználóneve"
|
||||
},
|
||||
"notifications": {
|
||||
"errorUserExists": "A felhasználó már létezik.",
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
"name": "Italiano (IT)"
|
||||
},
|
||||
"strings": {
|
||||
"pageTitle": "Crea Un Account Jellyfin",
|
||||
"pageTitle": "Crea Account Jellyfin",
|
||||
"createAccountHeader": "Crea Un Account",
|
||||
"accountDetails": "Dettagli",
|
||||
"emailAddress": "Email",
|
||||
"username": "Username",
|
||||
"username": "Nome Utente",
|
||||
"password": "Password",
|
||||
"reEnterPassword": "Riscrivi La Password",
|
||||
"reEnterPasswordInvalid": "Le password non sono uguali.",
|
||||
@@ -17,7 +17,7 @@
|
||||
"confirmationRequired": "Richiesta la conferma Email",
|
||||
"confirmationRequiredMessage": "Controlla la tua casella email per verificare il tuo indirizzo.",
|
||||
"yourAccountIsValidUntil": "Il tuo account sarà valido fino al {date}.",
|
||||
"sendPIN": "Scrivi il PIN qui sotto al bot, poi torna qui per connettere il tuo account.",
|
||||
"sendPIN": "Invia il PIN riportato sotto al bot, poi torna qui per associare il tuo account.",
|
||||
"sendPINDiscord": "Scrivi {command} in {server_channel} su Discord, poi invia il PIN qui sotto.",
|
||||
"matrixEnterUser": "Inserisci il tuo ID utente, premi invia e ti verrò inviato un PIN. Inseriscilo qui per continuare.",
|
||||
"customMessagePlaceholderHeader": "Personalizza questa scheda",
|
||||
@@ -34,7 +34,15 @@
|
||||
"resetPassword": "Ripristina Password",
|
||||
"resetSent": "Richiesta di ripristino inviata.",
|
||||
"resetSentDescription": "Se l'username/metodo di contatto corrisponde ad un account esistente, verrà inviato un link di reset a tutti i metodi di contatto disponibili. Il codice scadrà tra 30 minuti.",
|
||||
"changePassword": "Cambia Password"
|
||||
"changePassword": "Cambia Password",
|
||||
"resetPasswordThroughLinkStart": "Per reimpostare la password, inserisci uno dei seguenti:",
|
||||
"resetPasswordThroughLinkEnd": "Successivamente premi Invia. Un link verra' inviato per resettare la tua password.",
|
||||
"resetPasswordUsername": "Il tuo nome utente Jellyfin",
|
||||
"resetPasswordEmail": "Il tuo indirizzo email",
|
||||
"referralsWithExpiryDescription": "Invita amici e famigliari su Jellyfin con questo link. Il link verra' disabilitato una volta scaduto.",
|
||||
"referralsDescription": "Invita amici e famigliari su Jellyfin usando questo link. Ritorna su questa pagina per ottenerne uno nuovo.",
|
||||
"copyReferral": "Copia Link",
|
||||
"invitedBy": "Sei stato invitato dall'utente {user}."
|
||||
},
|
||||
"notifications": {
|
||||
"errorUserExists": "L'utente è già esistente.",
|
||||
@@ -76,4 +84,4 @@
|
||||
"plural": "Deve avere almeno {n} caratteri speciali"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
88
lang/form/tr-tr.json
Normal file
88
lang/form/tr-tr.json
Normal file
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "İngilizce (ABD)"
|
||||
},
|
||||
"strings": {
|
||||
"pageTitle": "Jellyfin Hesabı Oluştur",
|
||||
"createAccountHeader": "Hesap Oluştur",
|
||||
"accountDetails": "Ayrıntılar",
|
||||
"emailAddress": "E-posta",
|
||||
"username": "Kullanıcı Adı",
|
||||
"oldPassword": "Eski Şifre",
|
||||
"newPassword": "Yeni Şifre",
|
||||
"password": "Şifre",
|
||||
"reEnterPassword": "Şifreyi Tekrar Girin",
|
||||
"reEnterPasswordInvalid": "Şifreler aynı değil.",
|
||||
"createAccountButton": "Hesap Oluştur",
|
||||
"passwordRequirementsHeader": "Şifre Gereksinimleri",
|
||||
"successHeader": "Başarılı!",
|
||||
"confirmationRequired": "E-posta onayı gerekli",
|
||||
"confirmationRequiredMessage": "Lütfen adresinizi doğrulamak için e-posta gelen kutunuzu kontrol edin.",
|
||||
"yourAccountIsValidUntil": "Hesabınız {date} tarihine kadar geçerli olacaktır.",
|
||||
"sendPIN": "Aşağıdaki **PIN'i** bota gönderin, ardından hesabınızı bağlamak için buraya geri gelin.",
|
||||
"sendPINDiscord": "Discord'da {server_channel} {command} yazın, ardından aşağıdaki PIN'i gönderin.",
|
||||
"matrixEnterUser": "Kullanıcı Kimliğinizi girin, gönderin ve size bir PIN gönderilecektir. Devam etmek için buraya girin.",
|
||||
"welcomeUser": "Hoşgeldin, {user}!",
|
||||
"addContactMethod": "İletişim Yöntemi Ekle",
|
||||
"editContactMethod": "İletişim Yöntemini Düzenle",
|
||||
"joinTheServer": "Sunucuya katıl:",
|
||||
"customMessagePlaceholderHeader": "Bu kartı özelleştir",
|
||||
"customMessagePlaceholderContent": "Bu kartı özelleştirmek için ayarlarda kullanıcı sayfası düzenleme düğmesine tıklayın ya da oturum açma ekranında bir tane gösterin ve endişelenmeyin, kullanıcı bunu göremez.",
|
||||
"userPageSuccessMessage": "Hesabınızla ilgili ayrıntıları daha sonra {myAccount} sayfasında görebilir ve değiştirebilirsiniz.",
|
||||
"resetPassword": "Şifreyi Sıfırla",
|
||||
"resetPasswordThroughJellyfin": "Şifrenizi sıfırlamak için {jfLink} adresini ziyaret edin ve \"Şifremi Unuttum\" düğmesine basın.",
|
||||
"resetPasswordThroughLink": "Şifrenizi sıfırlamak için kullanıcı adınızı, e-posta adresinizi veya bağlı bir iletişim yöntemi kullanıcı adınızı girin ve gönderin. Şifrenizi sıfırlamanız için bir bağlantı gönderilecektir.",
|
||||
"resetPasswordThroughLinkStart": "Şifrenizi sıfırlamak için aşağıdakilerden birini girin:",
|
||||
"resetPasswordThroughLinkEnd": "Şifrenizi sıfırlamanız için bir bağlantı gönderilecektir. Ardından gönder'e basın.",
|
||||
"resetPasswordUsername": "Jellyfin kullanıcı adınız",
|
||||
"resetPasswordEmail": "E-posta adresiniz",
|
||||
"resetPasswordContactMethod": "Hesabınıza bağlı herhangi bir iletişim yönteminin kullanıcı adı",
|
||||
"resetSent": "Sıfırlama Gönderildi.",
|
||||
"resetSentDescription": "Verilen kullanıcı adı/iletişim yöntemine sahip bir hesap varsa, mevcut tüm iletişim yöntemleri aracılığıyla bir şifre sıfırlama bağlantısı gönderilmiştir. Kodun süresi **30 dakika** içinde dolacaktır.",
|
||||
"changePassword": "Şifreyi Değiştir",
|
||||
"referralsDescription": "Bu bağlantı ile arkadaşlarınızı ve ailenizi Jellyfin'e davet edin. Süresi dolarsa yeni bir tane almak için buraya geri gelin.",
|
||||
"referralsWithExpiryDescription": "Bu bağlantı ile arkadaşlarınızı ve ailenizi Jellyfin'e davet edin. Bağlantının süresi dolduğunda devre dışı bırakılacaktır.",
|
||||
"copyReferral": "Linki Kopyala",
|
||||
"invitedBy": "Sizi {user} adlı kullanıcı davet etti."
|
||||
},
|
||||
"notifications": {
|
||||
"errorUserExists": "Kullanıcı zaten mevcut.",
|
||||
"errorInvalidCode": "Geçersiz davet kodu.",
|
||||
"errorAccountLinked": "Hesap zaten kullanımda.",
|
||||
"errorEmailLinked": "E-posta zaten kullanımda.",
|
||||
"errorTelegramVerification": "Telegram doğrulama gerekli.",
|
||||
"errorDiscordVerification": "Discord doğrulama gerekli.",
|
||||
"errorMatrixVerification": "Matrix doğrulama gerekli.",
|
||||
"errorInvalidPIN": "PIN geçersiz.",
|
||||
"errorUnknown": "Bilinmeyen hata.",
|
||||
"errorNoEmail": "E-posta gerekli.",
|
||||
"errorCaptcha": "Captcha yanlış.",
|
||||
"errorPassword": "Şifre gereksinimlerini kontrol edin.",
|
||||
"errorNoMatch": "Şifreler eşleşmiyor.",
|
||||
"errorOldPassword": "Eski şifre yanlış.",
|
||||
"passwordChanged": "Şifre Değiştirildi.",
|
||||
"verified": "Hesap doğrulandı."
|
||||
},
|
||||
"validationStrings": {
|
||||
"length": {
|
||||
"singular": "En az {n} karakter içermeli",
|
||||
"plural": "En az {n} karakter içermeli"
|
||||
},
|
||||
"uppercase": {
|
||||
"singular": "En az {n} büyük harf içermeli",
|
||||
"plural": "En az {n} büyük harf içermeli"
|
||||
},
|
||||
"lowercase": {
|
||||
"singular": "En az {n} küçük harf içermeli",
|
||||
"plural": "En az {n} küçük harf içermeli"
|
||||
},
|
||||
"number": {
|
||||
"singular": "En az {n} küçük harf içermeli",
|
||||
"plural": "En az {n} küçük harf içermeli"
|
||||
},
|
||||
"special": {
|
||||
"singular": "En az {n} özel karakter içermeli",
|
||||
"plural": "En az {n} özel karakter içermeli"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,12 @@
|
||||
},
|
||||
"strings": {
|
||||
"passwordReset": "إعادة تعيين كلمة المرور",
|
||||
"reset": "إعادة ضبط",
|
||||
"reset": "إعادة التعيين",
|
||||
"resetFailed": "فشل إعادة تعيين كلمة المرور",
|
||||
"tryAgain": "حاول مرة اخرى.",
|
||||
"youCanLogin": "يمكنك الآن تسجيل الدخول باستخدام الرمز أدناه ككلمة المرور الخاصة بك.",
|
||||
"youCanLoginOmbi": "يمكنك الآن تسجيل الدخول باستخدام الرمز أدناه ككلمة المرور الخاصة بك.",
|
||||
"youCanLoginPassword": "يمكنك الآن تسجيل الدخول باستخدام الرمز أدناه ككلمة المرور الخاصة بك.",
|
||||
"tryAgain": "حاول مرة أُخرى.",
|
||||
"youCanLogin": "يمكنك الآن الدخول باستخدام الرمز أدناه ككلمة مرورك.",
|
||||
"youCanLoginOmbi": "يمكنك الآن الدخول إلى Jellyfin وOmbi باستخدام الرمز أدناه ككلمة مرورك.",
|
||||
"youCanLoginPassword": "يمكنك الآن الدخول باستخدام كلمة مرورك الجديدة. اضغط أدناه للانتقال إلى Jellyfin.",
|
||||
"changeYourPassword": "تأكد من تغيير كلمة المرور الخاصة بك بعد تسجيل الدخول.",
|
||||
"enterYourPassword": "أدخل كلمة المرور الجديدة أدناه."
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "انگلیسی"
|
||||
"name": "انگلیسی (FA)"
|
||||
},
|
||||
"strings": {
|
||||
"passwordReset": "تنظیم مجدد رمز عبور",
|
||||
|
||||
16
lang/pwreset/tr-tr.json
Normal file
16
lang/pwreset/tr-tr.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "İngilizce (ABD)"
|
||||
},
|
||||
"strings": {
|
||||
"passwordReset": "Şifre sıfırlama",
|
||||
"reset": "Sıfırla",
|
||||
"resetFailed": "Şifre sıfırlama başarısız oldu",
|
||||
"tryAgain": "Lütfen tekrar deneyin.",
|
||||
"youCanLogin": "Artık aşağıdaki kodla şifreniz olarak oturum açabilirsiniz.",
|
||||
"youCanLoginOmbi": "Artık aşağıdaki kodu şifreniz olarak kullanarak Jellyfin & Ombi'ye oturum açabilirsiniz.",
|
||||
"youCanLoginPassword": "Artık yeni şifrenizle oturum açabilirsiniz. Jellyfin'e devam etmek için aşağıya basın.",
|
||||
"changeYourPassword": "Oturum açtıktan sonra şifrenizi değiştirdiğinizden emin olun.",
|
||||
"enterYourPassword": "Yeni şifrenizi aşağıya girin."
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,8 @@
|
||||
"back": "الخلف",
|
||||
"optional": "اختياري",
|
||||
"serverType": "نوع الخادم",
|
||||
"disabled": "معطل",
|
||||
"enabled": "مفعل",
|
||||
"disabled": "معطّل",
|
||||
"enabled": "مفعّل",
|
||||
"port": "المنفذ",
|
||||
"message": "الرسالة",
|
||||
"serverAddress": "عنوان الخادم",
|
||||
@@ -18,135 +18,164 @@
|
||||
"apiKey": "مفتاح API",
|
||||
"error": "خطأ",
|
||||
"errorInvalidUserPass": "اسم مستخدم/كلمة مرور خاطئة.",
|
||||
"errorNotAdmin": "",
|
||||
"errorUserDisabled": "",
|
||||
"error404": "",
|
||||
"errorConnectionRefused": "",
|
||||
"errorUnknown": ""
|
||||
"errorNotAdmin": "لا يُسمح للمستخدم بإدارة الخادم.",
|
||||
"errorUserDisabled": "قد يكون المستخدم معطلاً.",
|
||||
"error404": "404، تحقق من عنوان URL الداخلي.",
|
||||
"errorConnectionRefused": "رُفض الاتصال.",
|
||||
"errorUnknown": "خطأ غير معروف، تحقق من سجلات التطبيق.",
|
||||
"errorProxy": "إعدادات الوكيل غير صالحة."
|
||||
},
|
||||
"startPage": {
|
||||
"welcome": "",
|
||||
"pressStart": "",
|
||||
"httpsNotice": "",
|
||||
"start": ""
|
||||
"welcome": "مرحباً!",
|
||||
"pressStart": "ستحتاج إلى القيام ببعض الخطوات لإعداد jfa-go. اضغط على \"ابدأ\" للمتابعة.",
|
||||
"httpsNotice": "تأكد من الاتصال بهذه الصفحة عبر HTTPS أو على شبكة خاصة.",
|
||||
"start": "ابدأ"
|
||||
},
|
||||
"endPage": {
|
||||
"finished": "",
|
||||
"finished": "انتهينا!",
|
||||
"restartMessage": "",
|
||||
"refreshPage": ""
|
||||
"refreshPage": "تحديث",
|
||||
"moreFeatures": "يمكنك العثور على المزيد من الميزات مثل بوتات Discord/Telegram/Matrix ورسائل ترميز Markdown المخصصة في الإعدادات، لذا تأكد من تصفحها.",
|
||||
"restartReload": "اضغط أدناه لإعادة التشغيل، ثم قم بالدخول إلى jfa-go من أحد عناوين URL الداخلية/الخارجية المحددة.",
|
||||
"ifFailedLoad": "إذا لم يتم تحميل الصفحة، فتحقق من سجلات التطبيق بحثاً عن أي أدلة تشير إلى السبب."
|
||||
},
|
||||
"language": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"defaultAdminLang": "",
|
||||
"defaultFormLang": "",
|
||||
"defaultEmailLang": ""
|
||||
"title": "اللغة",
|
||||
"description": "تتوفر ترجمات تطوعية لمعظم أجزاء jfa-go. يمكنك اختيار اللغات الافتراضية أدناه، ويمكن للمستخدمين تغييرها إذا رغبوا في ذلك. إذا أردت المساعدة في الترجمة، قم بالتسجيل في {n} للمساهمة!",
|
||||
"defaultAdminLang": "لغة المسؤول الافتراضية",
|
||||
"defaultFormLang": "لغة إنشاء الحساب الافتراضية",
|
||||
"defaultEmailLang": "لغة البريد الافتراضية"
|
||||
},
|
||||
"general": {
|
||||
"title": "",
|
||||
"listenAddress": "",
|
||||
"urlBase": "",
|
||||
"urlBaseNotice": "",
|
||||
"lightTheme": "",
|
||||
"darkTheme": "",
|
||||
"useHTTPS": "",
|
||||
"httpsPort": "",
|
||||
"useHTTPSNotice": "",
|
||||
"pathToCertificate": "",
|
||||
"pathToKeyFile": ""
|
||||
"title": "عام",
|
||||
"listenAddress": "عنوان الاستماع",
|
||||
"urlBase": "عنوان URL الأساسي",
|
||||
"urlBaseNotice": "مطلوب فقط في حالة استخدام وكيل عكسي على مجلد فرعي (على سبيل المثال 'jellyf.in/accounts').",
|
||||
"lightTheme": "فاتح",
|
||||
"darkTheme": "داكن",
|
||||
"useHTTPS": "استخدم HTTPS",
|
||||
"httpsPort": "منفذ HTTPS",
|
||||
"useHTTPSNotice": "موصى به فقط في حال عدم استخدامك لوكيل عكسي.",
|
||||
"pathToCertificate": "مسار الشهادة",
|
||||
"pathToKeyFile": "مسار ملف المفتاح (.key)",
|
||||
"externalURLNotice": "عنوان URL المستخدم للوصول إلى jfa-go. يُستخدم لإنشاء روابط لأمور مثل إعادة تعيين كلمة المرور. تأكد من تضمين عنوان URL الأساسي أعلاه إذا قمت بتعيينه.",
|
||||
"externalURL": "عنوان URL الخارجي لـ jfa-go"
|
||||
},
|
||||
"updates": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"updateChannel": "",
|
||||
"stable": "",
|
||||
"unstable": ""
|
||||
"title": "التحديثات",
|
||||
"description": "تلقي إشعارات عند توفر تحديثات جديدة. سيتحقق jfa-go من {n} كل 30 دقيقة. لا يتم جمع أي عناوين IP أو معلومات شخصية.",
|
||||
"updateChannel": "قناة التحديثات",
|
||||
"stable": "مستقرة",
|
||||
"unstable": "غير مستقرة"
|
||||
},
|
||||
"login": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"authorizeWithJellyfin": "",
|
||||
"authorizeManual": "",
|
||||
"adminOnly": "",
|
||||
"allowAll": "",
|
||||
"allowAllDescription": "",
|
||||
"emailNotice": ""
|
||||
"title": "تسجيل الدخول",
|
||||
"description": "للوصول إلى صفحة المسؤول، سجّل الدخول بإحدى الطرق التالية:",
|
||||
"authorizeWithJellyfin": "التفويض باستخدام Jellyfin/Emby: يتم مشاركة تفاصيل تسجيل الدخول مع Jellyfin، مما يسمح بتعدد المستخدمين.",
|
||||
"authorizeManual": "اسم المستخدم وكلمة المرور: قم بتعيين اسم المستخدم وكلمة المرور يدوياً.",
|
||||
"adminOnly": "المسؤولون فقط (مستحسن)",
|
||||
"allowAll": "السماح لجميع مستخدمي Jellyfin بتسجيل الدخول",
|
||||
"allowAllDescription": "لا يُنصح بذلك، ينبغي السماح للمستخدمين الفرديين بتسجيل الدخول بعد الإعداد.",
|
||||
"emailNotice": "يمكن استخدام عنوان بريدك الإلكتروني لتلقي الإشعارات.",
|
||||
"authorizeManualUserPageNotice": "سيؤدي استخدام هذا إلى تعطيل ميزة \"صفحة المستخدم\"."
|
||||
},
|
||||
"jellyfinEmby": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"embyNotice": "",
|
||||
"internal": "",
|
||||
"external": "",
|
||||
"replaceJellyfin": "",
|
||||
"replaceJellyfinNotice": "",
|
||||
"addressExternalNotice": "",
|
||||
"testConnection": ""
|
||||
"title": "إعداد Jellyfin/Emby",
|
||||
"description": "يلزم وجود حساب مسؤول لأن واجهة برمجة التطبيقات (API) لا تسمح بإنشاء مستخدمين من خلالها. يجب عليك إنشاء حساب منفصل وتحديد خيار \"اسمح لهذا المستخدم بالتحكم بالخادم\". يمكنك تعطيل كل الخيارات الأخرى. بعد ذلك، أدخل بيانات تسجيل الدخول هنا.",
|
||||
"embyNotice": "دعم Emby محدود ولا يدعم إعادة تعيين كلمة المرور.",
|
||||
"internal": "داخلي",
|
||||
"external": "خارجي",
|
||||
"replaceJellyfin": "اسم الخادم",
|
||||
"replaceJellyfinNotice": "عند إدخاله، سيحل هذا الاسم محل أي ذكر لكلمة \"Jellyfin\" في التطبيق.",
|
||||
"addressExternalNotice": "اتركه فارغاً لاستخدام نفس العنوان.",
|
||||
"testConnection": "اختبر الاتصال"
|
||||
},
|
||||
"ombi": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"apiKeyNotice": ""
|
||||
"title": "إعداد Ombi",
|
||||
"description": "عند الاتصال بـ Ombi، سيتم إنشاء حساب Jellyfin و Ombi عندما ينضم المستخدم عبر jfa-go. بعد الانتهاء من الإعداد، انتقل إلى الإعدادات لتعيين ملف تعريف افتراضي لمستخدمي Ombi الجدد.",
|
||||
"apiKeyNotice": "تجده في أول قسم لإعدادات Ombi.",
|
||||
"stabilityWarning": "تحذير: تكامل Ombi غير مستقر، وقد يُسبب مشاكل. يُنصح باستخدام Jellyseerr بدلاً منه. راجع {n} لمزيد من المعلومات."
|
||||
},
|
||||
"messages": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "الرسائل",
|
||||
"description": "يُمكن لـ jfa-go إرسال عمليات إعادة تعيين كلمة المرور ورسائل مُختلفة عبر البريد الإلكتروني، ،Discord ،Telegram و/أو Matrix. يُمكنك إعداد البريد الإلكتروني أدناه، ويُمكنك ضبط باقي الخيارات لاحقاً في الإعدادات. يُمكنك الاطلاع على التعليمات في {n}. إذا لم تكن بحاجة إلى هذه الميزة، فيُمكنك تعطيلها هنا."
|
||||
},
|
||||
"email": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"method": "",
|
||||
"useEmailAsUsername": "",
|
||||
"useEmailAsUsernameNotice": "",
|
||||
"fromAddress": "",
|
||||
"senderName": "",
|
||||
"dateFormat": "",
|
||||
"dateFormatNotice": "",
|
||||
"encryption": "",
|
||||
"mailgunApiURL": ""
|
||||
"title": "البريد الإلكتروني",
|
||||
"description": "يُمكن لـ jfa-go إرسال رموز (PIN) لإعادة تعيين كلمة المرور وإرسال إشعارات متنوعة عبر البريد الإلكتروني. يُمكنك الاتصال بخادم SMTP أو استخدام واجهة برمجة التطبيقات (API) لـ{n}.",
|
||||
"method": "وسيلة الإرسال",
|
||||
"useEmailAsUsername": "استخدم عناوين البريد الإلكتروني كاسم المستخدم",
|
||||
"useEmailAsUsernameNotice": "عند التفعيل، فسيقوم المستخدمون الجدد بتسجيل الدخول إلى Jellyfin/Emby باستخدام عنوان بريدهم الإلكتروني بدلاً من اسم المستخدم.",
|
||||
"fromAddress": "عنوان المرسل",
|
||||
"senderName": "اسم المرسل",
|
||||
"dateFormat": "صيغة التاريخ",
|
||||
"dateFormatNotice": "التاريخ يتبع تنسيق strftime. لمزيد من المعلومات، يرجى زيارة {n}.",
|
||||
"encryption": "التشفير",
|
||||
"mailgunApiURL": "رابط API"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "إشعارات المسؤول",
|
||||
"description": "عند التفعيل، ستتلقى رسالة لكل دعوة عند انتهاء صلاحيتها او عند انشاء مستخدم من خلالها. إذا لم تختر طريقة تسجيل الدخول عبر Jellyfin، فتأكد من تقديم عنوان بريدك الإلكتروني أو أضف وسيلة اتصال أخرى لاحقاً."
|
||||
},
|
||||
"welcomeEmails": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "رسائل الترحيب",
|
||||
"description": "عند التفعيل، فسيتم إرسال رسالة إلى المستخدمين الجدد تحتوي على عنوان URL لـ Jellyfin/Emby واسم المستخدم الخاص بهم."
|
||||
},
|
||||
"inviteEmails": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "رسائل الدعوة",
|
||||
"description": "عند التفعيل، يمكنك إرسال الدعوات مباشرةً إلى بريد المستخدم الإلكتروني، أو إلى مستخدم Discord أو Matrix. ولأنك قد تستخدم وكيل عكسي، يجب عليك تحديد عنوان URL الذي تصل إليه الدعوات. اكتب عنوان URL الأساسي، وأضف '/invite'."
|
||||
},
|
||||
"passwordResets": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"pathToJellyfin": "",
|
||||
"pathToJellyfinNotice": "",
|
||||
"resetLinks": "",
|
||||
"resetLinksNotice": "",
|
||||
"resetLinksLanguage": "",
|
||||
"setPassword": "",
|
||||
"setPasswordNotice": ""
|
||||
"title": "عمليات إعادة تعيين كلمة المرور",
|
||||
"description": "عندما يحاول مستخدم إعادة تعيين كلمة مروره، يُنشئ Jellyfin ملفاً باسم 'passwordreset-*.json' يحتوي على رمز (PIN). يقرأ jfa-go الملف ويرسل الرمز (PIN) إلى المستخدم. إذا قمت بتفعيل ميزة \"صفحة المستخدم\"، فيمكن أيضاً إجراء إعادة التعيين هناك، باستخدام اسم المستخدم أو البريد الإلكتروني أو وسيلة الاتصال.",
|
||||
"pathToJellyfin": "مسار مجلد تكوين Jellyfin (config)",
|
||||
"pathToJellyfinNotice": "إذا كنت لا تعرف مكان المسار، فحاول إعادة تعيين كلمة مرورك في Jellyfin. ستظهر نافذة منبثقة تحتوي على \"<path to jellyfin>/passwordreset-*.json\". هذا ليس ضروريًا إذا كنت ترغب فقط في استخدام خدمة إعادة تعيين كلمة المرور الذاتية من خلال \"صفحة المستخدم\".",
|
||||
"resetLinks": "أرسل رابطاً بدلاً من رمز (PIN)",
|
||||
"resetLinksNotice": "إذا تم إعداد Ombi، فاستخدم هذا لمزامنة إعادة تعيين كلمة مرور Jellyfin مع Ombi.",
|
||||
"resetLinksLanguage": "لغة رابط إعادة التعيين الافتراضية",
|
||||
"setPassword": "تعيين كلمة المرور من خلال رابط",
|
||||
"setPasswordNotice": "تفعيل هذه الميزة يعني أن المستخدم لن يضطر إلى تغيير كلمة المرور من الرمز (PIN) بعد إعادة التعيين. سيتم أيضاً فرض متطلبات كلمة المرور.",
|
||||
"moreInfo": "يمكنك العثور على مزيد من المعلومات حول الطرق المختلفة لإعادة تعيين كلمات المرور على {n}.",
|
||||
"resetLinksRequiredForUserPage": "مطلوب للسماح بإعادة تعيين كلمة المرور ذاتياً على صفحة المستخدم."
|
||||
},
|
||||
"passwordValidation": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"length": "",
|
||||
"uppercase": "",
|
||||
"lowercase": "",
|
||||
"numbers": "",
|
||||
"special": ""
|
||||
"title": "متطلبات كلمة المرور",
|
||||
"description": "عند التفعيل، ستظهر مجموعة من متطلبات كلمة المرور على صفحة إنشاء الحساب، مثل الحد الأدنى لطول الكلمة، وعدد الأحرف الكبيرة/الصغيرة، وما إلى ذلك.",
|
||||
"length": "طول الكلمة",
|
||||
"uppercase": "عدد الأحرف الكبيرة",
|
||||
"lowercase": "عدد الأحرف الصغيرة",
|
||||
"numbers": "عدد الأرقام",
|
||||
"special": "عدد الحروف الخاصة (%, *, إلخ.)"
|
||||
},
|
||||
"helpMessages": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"contactMessage": "",
|
||||
"contactMessageNotice": "",
|
||||
"helpMessage": "",
|
||||
"helpMessageNotice": "",
|
||||
"successMessage": "",
|
||||
"successMessageNotice": "",
|
||||
"emailMessage": "",
|
||||
"emailMessageNotice": ""
|
||||
"title": "رسائل المساعدة",
|
||||
"description": "ستظهر هذه الرسائل في صفحة إنشاء الحساب وفي بعض رسائل البريد الإلكتروني.",
|
||||
"contactMessage": "رسالة الاتصال",
|
||||
"contactMessageNotice": "يتم عرضها في أسفل جميع الصفحات باستثناء صفحة المسؤول.",
|
||||
"helpMessage": "رسالة المساعدة",
|
||||
"helpMessageNotice": "يتم عرضها على صفحة إنشاء الحساب.",
|
||||
"successMessage": "رسالة النجاح",
|
||||
"successMessageNotice": "تظهر عندما يُتم المستخدم إنشاء حسابه.",
|
||||
"emailMessage": "رسالة البريد الإلكتروني",
|
||||
"emailMessageNotice": "تظهر في نهاية رسائل البريد الإلكتروني.",
|
||||
"markdownMessageNotice": "يمكن تخصيص محتويات بعض رسائل البريد الإلكتروني والصفحات والرسائل باستخدام ترميز Markdown في الإعدادات."
|
||||
},
|
||||
"jellyseerr": {
|
||||
"description": "Jellyseerr هو بديل لـ Ombi، ويتكامل مع jfa-go بشكل أفضل. بعد الانتهاء من الإعداد، انتقل إلى الإعدادات لإنشاء ملف تعريف وإضافة قالب لحسابات Jellyseerr الجديدة.",
|
||||
"title": "إعداد Jellyseerr",
|
||||
"importExisting": "استيراد المستخدمين الحاليين",
|
||||
"importExistingDescription": "عند التفعيل، سيتم مزامنة تفاصيل وسائل الاتصال والتفضيلات الخاصة بالمستخدمين الحاليين من jfa-go."
|
||||
},
|
||||
"proxy": {
|
||||
"title": "الوكيل",
|
||||
"description": "دع jfa-go يقوم بجميع الاتصالات من خلال وكيل HTTP/SOCKS5. سيتم اختبار الاتصال بـ Jellyfin من خلاله.",
|
||||
"protocol": "البروتوكول",
|
||||
"address": "العنوان (متضمناً المنفذ)"
|
||||
},
|
||||
"userPage": {
|
||||
"description": "تتيح صفحة المستخدم (التي تظهر باسم \"حسابي\") للمستخدمين الوصول إلى معلومات حسابهم، مثل وسائل الاتصال ومدة صلاحية الحساب. كما يمكنهم تغيير كلمة المرور، وطلب إعادة تعيينها ، وربط/تغيير وسائل الاتصال، دون الحاجة إلى طلب ذلك منك. بالإضافة إلى ذلك، يمكن عرض رسائل بترميز Markdown مخصصة للمستخدمين قبل وبعد تسجيل الدخول.",
|
||||
"title": "صفحة المستخدم",
|
||||
"customizeMessages": "اضغط زر التعديل بجوار \"صفحة المستخدم\" في الإعدادات لتعيينها لاحقاً.",
|
||||
"requiredSettings": "يجب تفعيل تسجيل الدخول إلى jfa-go عبر Jellyfin. تأكد من اختيار \"إعادة تعيين كلمة المرور عبر رابط\" لاحقاً للسماح بإعادة تعيين كلمة المرور ذاتياً."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Admin Notifications",
|
||||
"description": "If enabled, you can choose (per invite) to receive an message when an invite expires, or a user is created. If you didn't choose the Jellyfin login method, make sure you provided your email address, or add another contact method later."
|
||||
"description": "If enabled, you can choose (per invite) to receive a message when an invite expires, or a user is created. If you didn't choose the Jellyfin login method, make sure you provided your email address, or add another contact method later."
|
||||
},
|
||||
"inviteEmails": {
|
||||
"title": "Invite Messages",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"apiKey": "API Key",
|
||||
"error": "Error",
|
||||
"errorInvalidUserPass": "Invalid username/password.",
|
||||
"errorNotAdmin": "User is not aEnabledllowed to manage server.",
|
||||
"errorNotAdmin": "User is not allowed to manage server.",
|
||||
"errorUserDisabled": "User may be disabled.",
|
||||
"error404": "404, check the internal URL.",
|
||||
"errorConnectionRefused": "Connection refused.",
|
||||
@@ -126,7 +126,7 @@
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Admin Notifications",
|
||||
"description": "If enabled, you can choose (per invite) to receive an message when an invite expires, or a user is created. If you didn't choose the Jellyfin login method, make sure you provided your email address, or add another contact method later."
|
||||
"description": "If enabled, you can choose (per invite) to receive a message when an invite expires, or a user is created. If you didn't choose the Jellyfin login method, make sure you provided your email address, or add another contact method later."
|
||||
},
|
||||
"userPage": {
|
||||
"title": "User Page",
|
||||
@@ -136,7 +136,7 @@
|
||||
},
|
||||
"welcomeEmails": {
|
||||
"title": "Welcome messages",
|
||||
"description": "If enabled, an message will be sent to new users with the Jellyfin/Emby URL and their username."
|
||||
"description": "If enabled, a message will be sent to new users with the Jellyfin/Emby URL and their username."
|
||||
},
|
||||
"inviteEmails": {
|
||||
"title": "Invite Messages",
|
||||
|
||||
@@ -20,132 +20,162 @@
|
||||
"errorNotAdmin": "A felhasználó számára nincs engedélyezve a szerver kezelése.",
|
||||
"errorUserDisabled": "Lehetséges, hogy a felhasználó le lett tiltva.",
|
||||
"error404": "404, ellenőrizze a belső URL-t.",
|
||||
"errorConnectionRefused": "",
|
||||
"error": "Hiba"
|
||||
"errorConnectionRefused": "Csatlakozás visszautasítva.",
|
||||
"error": "Hiba",
|
||||
"errorUnknown": "Váratlan hiba, ellenőrizd a napló fájlt.",
|
||||
"errorProxy": "Proxy beállítás érvénytelen."
|
||||
},
|
||||
"startPage": {
|
||||
"welcome": "Üdv!",
|
||||
"pressStart": "",
|
||||
"httpsNotice": "",
|
||||
"start": ""
|
||||
"pressStart": "A jfa-go beállításához néhány dolgot el kell végezned. A folytatáshoz nyomd meg a kezdés gombot.",
|
||||
"httpsNotice": "Győződjön meg róla, hogy HTTPS-en vagy privát hálózaton keresztül éri el ezt az oldalt.",
|
||||
"start": "Kezdés"
|
||||
},
|
||||
"endPage": {
|
||||
"finished": "",
|
||||
"finished": "Kész!",
|
||||
"restartMessage": "",
|
||||
"refreshPage": ""
|
||||
"refreshPage": "Újratöltés",
|
||||
"moreFeatures": "Rengeteg további funkció, mint például a Discord/Telegram/Matrix botok és az egyéni Markdown üzenetek, megtalálható a Beállításokban, ezért mindenképpen böngészd át őket.",
|
||||
"restartReload": "Kattints ide az újraindításhoz, majd a megadott belső/külső URL-címek egyikén nyisd meg a jfa-go alkalmazást.",
|
||||
"ifFailedLoad": "Ha nem töltődik be, ellenőrizd az alkalmazás naplóit, hogy miért."
|
||||
},
|
||||
"language": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"defaultAdminLang": "",
|
||||
"defaultFormLang": "",
|
||||
"defaultEmailLang": ""
|
||||
"title": "Nyelv",
|
||||
"description": "A jfa-go legtöbb részéhez elérhetők közösségi fordítások. Az alábbiakban kiválaszthatod az alapértelmezett nyelveket, de a felhasználók továbbra is módosíthatják azokat, ha akarják. Ha szeretnél segíteni a fordításban, regisztrálj a {n}-re, hogy elkezdhesd a közreműködést!",
|
||||
"defaultAdminLang": "Alapártelmezett rendszergazda nyelv",
|
||||
"defaultFormLang": "Alapértelmezett fiók nyelv",
|
||||
"defaultEmailLang": "Alapértelmezett email nyelv"
|
||||
},
|
||||
"general": {
|
||||
"title": "",
|
||||
"listenAddress": "",
|
||||
"urlBase": "",
|
||||
"urlBaseNotice": "",
|
||||
"lightTheme": "",
|
||||
"darkTheme": "",
|
||||
"useHTTPS": "",
|
||||
"httpsPort": "",
|
||||
"useHTTPSNotice": "",
|
||||
"pathToCertificate": "",
|
||||
"pathToKeyFile": ""
|
||||
"title": "Alap",
|
||||
"listenAddress": "Figyelő címe",
|
||||
"urlBase": "Alap URL",
|
||||
"urlBaseNotice": "Csak akkor szükséges, ha fordított proxyt használsz egy almappán (pl. 'jellyf.in/accounts').",
|
||||
"lightTheme": "Fényes",
|
||||
"darkTheme": "Sötét",
|
||||
"useHTTPS": "HTTPS használata",
|
||||
"httpsPort": "HTTPS Port",
|
||||
"useHTTPSNotice": "Csak akkor aljánlott ha fordított proxy-t használsz.",
|
||||
"pathToCertificate": "Tanúsítvány elérési útja",
|
||||
"pathToKeyFile": "Kulcs fájl elérési útja",
|
||||
"externalURLNotice": "Az URL, amelyről a jfa-go címhez fogsz hozzáférni. Linkek generálására szolgál, például jelszó-visszaállításhoz. Ha beállítottál egyet, feltétlenül add meg a fenti alap URL-t is.",
|
||||
"externalURL": "Külső jfa-go URL"
|
||||
},
|
||||
"updates": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"updateChannel": "",
|
||||
"stable": "",
|
||||
"unstable": ""
|
||||
"title": "Frissítések",
|
||||
"description": "Engedélyezd ha szeretnél értesítést az új frissítésekről. A jfa-go 30 percenként ellenőrzi a(z) {n} címet. Nem gyűjt IP-címeket vagy személyes adatokat.",
|
||||
"updateChannel": "Csatorna frissítése",
|
||||
"stable": "Stabil",
|
||||
"unstable": "Instabil"
|
||||
},
|
||||
"login": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"authorizeWithJellyfin": "",
|
||||
"authorizeManual": "",
|
||||
"adminOnly": "",
|
||||
"allowAll": "",
|
||||
"allowAllDescription": "",
|
||||
"emailNotice": ""
|
||||
"title": "Belépés",
|
||||
"description": "Az admin oldal eléréséhez az alábbi módszerrel kell bejelentkezned:",
|
||||
"authorizeWithJellyfin": "Bejelentkezés Jellyfin/Emby segítségével: A bejelentkezési adatok meg vannak osztva a Jellyfin-nel, ami több felhasználó létrehozását teszi lehetővé.",
|
||||
"authorizeManual": "Felhasználónév és Jelszó: Felhasználónév és jelszó manuális beállítása.",
|
||||
"adminOnly": "Csak rendszergazda felhasználók (ajánlott)",
|
||||
"allowAll": "Összes Jellyfin felhasználó belépéssének engedélyezése",
|
||||
"allowAllDescription": "Nem ajánlott, a beállítás után engedélyezni kell az egyes felhasználók bejelentkezését.",
|
||||
"emailNotice": "Az email címed értesítések fogadására lesz használva.",
|
||||
"authorizeManualUserPageNotice": "Ennek használata letiltja a „Felhasználói oldal” funkciót."
|
||||
},
|
||||
"jellyfinEmby": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"embyNotice": "",
|
||||
"internal": "",
|
||||
"external": "",
|
||||
"replaceJellyfin": "",
|
||||
"replaceJellyfinNotice": "",
|
||||
"addressExternalNotice": "",
|
||||
"testConnection": ""
|
||||
"title": "Jellyfin/Emby",
|
||||
"description": "Egy adminisztrátori fiók szükséges, mivel az API nem engedélyezi a felhasználók létrehozását API-kulcs használatával. Létre kell hoznia egy külön fiókot, és engedélyeznie kell az „Ez a felhasználó kezelheti a szervert” beállítást. Minden mást letilthat. Ha ezt megtette, adja meg itt a hitelesítő adatait.",
|
||||
"embyNotice": "Az Emby támogatása korlátozott, és nem támogatja a jelszó-visszaállítást.",
|
||||
"internal": "Belső",
|
||||
"external": "Külső",
|
||||
"replaceJellyfin": "Szerver neve",
|
||||
"replaceJellyfinNotice": "Ha meg van adva, ez felülírja a 'Jellyfin' minden előfordulását az alkalmazásban.",
|
||||
"addressExternalNotice": "Hagyja üresen, ha ugyanazt a címet szeretnéd használni.",
|
||||
"testConnection": "Kapcsolat tesztelése"
|
||||
},
|
||||
"ombi": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"apiKeyNotice": ""
|
||||
"title": "Ombi",
|
||||
"description": "Az Ombihoz való csatlakozással Jellyfin és Ombi fiók is létrejön, amikor a felhasználó a jfa-go-n keresztül csatlakozik. A beállítás befejezése után lépjen a Beállítások menüpontra, hogy alapértelmezett profilt állítson be az új ombi-felhasználók számára.",
|
||||
"apiKeyNotice": "Ezt az Ombi beállítások első lapján találod.",
|
||||
"stabilityWarning": "Figyelmeztetés: Az Ombi integráció instabil, és problémákat okozhat. Helyette a Jellyseerr használata ajánlott. További információkért lásd: {n}."
|
||||
},
|
||||
"messages": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "Üzenetek",
|
||||
"description": "A jfa-go jelszó-visszaállítási információkat és különféle üzeneteket tud küldeni e-mailben, Discordon, Telegramon és/vagy Matrixon keresztül. Az e-mailt alább állíthatod be, a többit pedig később a Beállításokban konfigurálhatod. Az utasításokat a {n} oldalon találod. Ha erre nincs szükséged, itt letilthatod ezeket a funkciókat."
|
||||
},
|
||||
"email": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"method": "",
|
||||
"useEmailAsUsername": "",
|
||||
"useEmailAsUsernameNotice": "",
|
||||
"fromAddress": "",
|
||||
"senderName": "",
|
||||
"dateFormat": "",
|
||||
"dateFormatNotice": "",
|
||||
"encryption": "",
|
||||
"mailgunApiURL": ""
|
||||
"title": "Email",
|
||||
"description": "A jfa-go jelszó-visszaállító PIN-kódokat és különféle értesítéseket tud küldeni e-mailben. Csatlakozhatsz egy SMTP-kiszolgálóhoz, vagy használhatod az {n} API-t.",
|
||||
"method": "Küldési mód",
|
||||
"useEmailAsUsername": "Email cím használata fehasználónévnek",
|
||||
"useEmailAsUsernameNotice": "Ha engedélyezve van, az új felhasználók a Jellyfin/Emby rendszerbe felhasználónév helyett az e-mail címükkel jelentkeznek be.",
|
||||
"fromAddress": "Feladó címe",
|
||||
"senderName": "Küldő címe",
|
||||
"dateFormat": "Dátum formátuma",
|
||||
"dateFormatNotice": "A dátum az strftime formátumot követi. További információkért látogasson el a {n} oldalra.",
|
||||
"encryption": "Titkosítás",
|
||||
"mailgunApiURL": "API URL"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "Rendszergazda értesítések",
|
||||
"description": "Ha engedélyezve van, meghívónként kiválaszthatod, hogy üzenetet kapj-e, amikor egy meghívó lejár, vagy amikor létrejön egy felhasználó. Ha nem a Jellyfin bejelentkezési módot választottad, győződj meg róla, hogy megadtad az e-mail címedet, vagy adj hozzá később egy másik kapcsolatfelvételi módot."
|
||||
},
|
||||
"welcomeEmails": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "Üdvözlő üzenetek",
|
||||
"description": "Ha engedélyezve van, az új felhasználók üzenetben kapják meg a Jellyfin/Emby URL-címet és a felhasználónevüket."
|
||||
},
|
||||
"inviteEmails": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "Meghívó üzenetek",
|
||||
"description": "Ha engedélyezve van, közvetlenül a felhasználó e-mail címére, Discord vagy Matrix felhasználóra küldhet meghívókat. Mivel fordított proxyt használhat, meg kell adnia azt az URL-címet, ahonnan a meghívók elérhetők. Írja be az URL-alapját, és fűzze hozzá a '/invite' részt."
|
||||
},
|
||||
"passwordResets": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"pathToJellyfin": "",
|
||||
"pathToJellyfinNotice": "",
|
||||
"resetLinks": "",
|
||||
"resetLinksNotice": "",
|
||||
"resetLinksLanguage": "",
|
||||
"setPassword": "",
|
||||
"setPasswordNotice": ""
|
||||
"title": "Jelszó visszaállítás",
|
||||
"description": "Amikor egy felhasználó megpróbálja visszaállítani a jelszavát, a Jellyfin létrehoz egy „passwordreset-*.json” nevű fájlt, amely egy PIN-kódot tartalmaz. A jfa-go beolvassa a fájlt, és elküldi a PIN-kódot a felhasználónak. Ha engedélyezte a „Felhasználói oldal” funkciót, a visszaállítás ott is elvégezhető felhasználónév, e-mail cím vagy kapcsolatfelvételi mód megadásával.",
|
||||
"pathToJellyfin": "Jellyfin konfigurációs könyvtár elérési útja",
|
||||
"pathToJellyfinNotice": "Ha nem tudod, hol van ez, próbáld meg visszaállítani a jelszavadat a Jellyfinben. Megjelenik egy felugró ablak a következővel: '<jellyfin elérési útja>/passwordreset-*.json'. Ez nem szükséges, ha csak az önkiszolgáló jelszó-visszaállítást szeretnéd használni a \"Felhasználói oldalon\".",
|
||||
"resetLinks": "Link küldése PIN kód helyett",
|
||||
"resetLinksNotice": "Ha az Ombi integráció engedélyezve van, használja ezt a Jellyfin jelszó-visszaállítások Ombival való szinkronizálásához.",
|
||||
"resetLinksLanguage": "Alapértelmezett jelszó-visszaállítási nyelv",
|
||||
"setPassword": "Jelszó beállítás linken keresztül",
|
||||
"setPasswordNotice": "Ha engedélyezve van, a felhasználónak nem kell PIN-kóddal módosítania a jelszavát. Ez a jelszó-ellenőrzést is kikényszeríti.",
|
||||
"moreInfo": "A jelszavak visszaállításának különböző módjairól további információt a {n} oldalon talál.",
|
||||
"resetLinksRequiredForUserPage": "Szükséges az önkiszolgáló jelszó-visszaállításhoz a felhasználói oldalon."
|
||||
},
|
||||
"passwordValidation": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"length": "",
|
||||
"uppercase": "",
|
||||
"lowercase": "",
|
||||
"numbers": "",
|
||||
"special": ""
|
||||
"title": "Jelszóérvényesítés",
|
||||
"description": "Ha engedélyezve van, a fiók létrehozási oldalán megjelennek a jelszóra vonatkozó követelmények, például a minimális hossz, a nagy- és kisbetűk stb.",
|
||||
"length": "Hossz",
|
||||
"uppercase": "Nagybetűs karakterek",
|
||||
"lowercase": "Kisbetűs karakterek",
|
||||
"numbers": "Számok",
|
||||
"special": "Speciális karakterek"
|
||||
},
|
||||
"helpMessages": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"contactMessage": "",
|
||||
"contactMessageNotice": "",
|
||||
"helpMessage": "",
|
||||
"helpMessageNotice": "",
|
||||
"successMessage": "",
|
||||
"successMessageNotice": "",
|
||||
"emailMessage": "",
|
||||
"emailMessageNotice": ""
|
||||
"title": "Súgóüzenetek",
|
||||
"description": "Ezek az üzenetek a fiók létrehozási oldalán és néhány e-mailben jelennek meg.",
|
||||
"contactMessage": "Kapcsolatfelvételi üzenet",
|
||||
"contactMessageNotice": "Az adminisztrációs oldal kivételével az összes oldal alján megjelenik.",
|
||||
"helpMessage": "Súgóüzenet",
|
||||
"helpMessageNotice": "A fiók létrehozási oldalán jelenik meg.",
|
||||
"successMessage": "Sikeres üzenet",
|
||||
"successMessageNotice": "Akkor jelenik meg, amikor a felhasználó létrehozza a fiókját.",
|
||||
"emailMessage": "Email üzenet",
|
||||
"emailMessageNotice": "Az e-mailek alján jelenik meg.",
|
||||
"markdownMessageNotice": "Egyes e-mailek, oldalak és üzenetek tartalma testreszabható a Markdown segítségével a beállításokban."
|
||||
},
|
||||
"jellyseerr": {
|
||||
"description": "A Jellyseerr az Ombi alternatívája, és jobban integrálódik a jfa-go-val. A beállítás befejezése után a Beállítások menüpontban hozz létre egy profilt, és adj hozzá egy sablont az új Jellyseerr fiókokhoz.",
|
||||
"title": "Jellyseerr",
|
||||
"importExisting": "Meglévő fiókok importálása",
|
||||
"importExistingDescription": "Ha engedélyezve van, a meglévő felhasználók elérhetőségi adatai és beállításai szinkronizálva lesznek a jfa-go rendszerből."
|
||||
},
|
||||
"userPage": {
|
||||
"description": "A felhasználói oldal („Fiókom” néven látható) lehetővé teszi a felhasználók számára, hogy hozzáférjenek a fiókjukkal kapcsolatos információkhoz, például a kapcsolatfelvételi módokhoz és a fiók lejáratához. Megváltoztathatják jelszavukat, jelszó-visszaállítást kezdeményezhetnek, és összekapcsolhatják/módosíthatják a kapcsolatfelvételi módokat anélkül, hogy megkérdeznék Önt. Ezenkívül személyre szabott Markdown-üzenetek jeleníthetők meg a felhasználóknak a bejelentkezés előtt és után.",
|
||||
"title": "Felhasználói oldal",
|
||||
"customizeMessages": "Kattintson a beállításokban a „Felhasználói oldal” melletti szerkesztés gombra a későbbi módosításhoz.",
|
||||
"requiredSettings": "A jfa-go-ba Jellyfinen keresztül történő bejelentkezést be kell állítani. Győződjön meg róla, hogy a „jelszó visszaállítása linken keresztül” lehetőség van kiválasztva később az önkiszolgáló jelszó-visszaállításhoz."
|
||||
},
|
||||
"proxy": {
|
||||
"title": "Proxy",
|
||||
"description": "A jfa-go minden kapcsolatot HTTP/SOCKS5 proxyn keresztül hozzon létre. A Jellyfinhez való csatlakozást ezen a proxyn keresztül fogja tesztelni.",
|
||||
"protocol": "Protokoll",
|
||||
"address": "Cím (Port-al együtt)"
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user