Compare commits
211 Commits
db
...
activity-l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3739634b63 | ||
|
|
3951116bdc | ||
|
|
a288ba4461 | ||
|
|
f34ba5df18 | ||
|
|
44d7e173e3 | ||
|
|
663389693f | ||
|
|
591b843148 | ||
|
|
de3c06129d | ||
|
|
0238c6778c | ||
|
|
d00f3fcfbc | ||
|
|
47ce8a9ec4 | ||
|
|
2d83718f81 | ||
|
|
a0db685af2 | ||
|
|
4fa0630aef | ||
|
|
3cad30a8e5 | ||
|
|
44172074b9 | ||
|
|
1032e4e747 | ||
|
|
a73dfddd3f | ||
|
|
274324557c | ||
|
|
5a0677bac8 | ||
|
|
df1581d48e | ||
|
|
9d1c7bba6f | ||
|
|
b620c0d9ae | ||
|
|
2c787b4d46 | ||
|
|
69dcaf3797 | ||
|
|
43e36ee6fc | ||
|
|
53c9569a37 | ||
|
|
c39a9e80e7 | ||
|
|
3d0f756264 | ||
|
|
85de1c97ff | ||
|
|
2c8afecfbb | ||
|
|
4924700c52 | ||
|
|
e2c24a2593 | ||
|
|
31b7ede665 | ||
|
|
dba7d0bd4e | ||
|
|
73cfa5bef2 | ||
|
|
6909477f45 | ||
|
|
701d1305d3 | ||
|
|
08498074ed | ||
|
|
28d321986a | ||
|
|
943d523f3f | ||
|
|
8f88b6aaa2 | ||
|
|
7f60598d4a | ||
|
|
18e82fd04b | ||
|
|
d7d7146e12 | ||
|
|
aaa5217398 | ||
|
|
9610b89fa5 | ||
|
|
9809611d0d | ||
|
|
b1e38ba15d | ||
|
|
35a765aa01 | ||
|
|
82411f1868 | ||
|
|
b0e01144f4 | ||
|
|
04f354b3d1 | ||
|
|
918f3ad588 | ||
|
|
635c2be32c | ||
|
|
3143d32b45 | ||
|
|
742f5c095a | ||
|
|
7b2a6cdf74 | ||
|
|
2f3d5e4e3a | ||
|
|
2fb2f3ee74 | ||
|
|
7813c8c68b | ||
|
|
e528f7c348 | ||
|
|
77f6b1042e | ||
|
|
7db94dcebf | ||
|
|
70afc21217 | ||
|
|
525c13ff6a | ||
|
|
0366e5116d | ||
|
|
62923d5e45 | ||
|
|
10a32ad1ae | ||
|
|
e52e21a54b | ||
|
|
7c861e5763 | ||
|
|
9c771e193e | ||
|
|
f37451021f | ||
|
|
4aa095d466 | ||
|
|
638be18ea8 | ||
|
|
42264f0547 | ||
|
|
07d738006f | ||
|
|
4bc51570c2 | ||
|
|
cf94fdb2f0 | ||
|
|
4864c6c53c | ||
|
|
5702e8012c | ||
|
|
523902f951 | ||
|
|
dd93758b0e | ||
|
|
b595d3ea03 | ||
|
|
49dfac514d | ||
|
|
543f23c8ef | ||
|
|
f6fdd41b35 | ||
|
|
4f78b7c33b | ||
|
|
9956bbd974 | ||
|
|
ff1ea8549a | ||
|
|
5a2d3d2ee2 | ||
|
|
729548334d | ||
|
|
27f85f866e | ||
|
|
c43d5cf1b0 | ||
|
|
3538935d3b | ||
|
|
edf6c13f03 | ||
|
|
b30d6c3ee1 | ||
|
|
3ff5e6555a | ||
|
|
2430fc68ba | ||
|
|
bc8f6b7cd6 | ||
|
|
e31d11e2bb | ||
|
|
3d45f2b95e | ||
|
|
80ebafa9f9 | ||
|
|
471497ff6a | ||
|
|
1badc4975e | ||
|
|
0728c8bdd3 | ||
|
|
498f7bd29b | ||
|
|
ad3e6ad7dc | ||
|
|
e2b975ac9c | ||
|
|
b7bf1f835e | ||
|
|
525eaab4bb | ||
|
|
a67119d1ec | ||
|
|
10cc130674 | ||
|
|
044ce6fbd8 | ||
|
|
a4bb2de901 | ||
|
|
05df04b754 | ||
|
|
998b719f38 | ||
|
|
2ec34278cc | ||
|
|
0dbe058433 | ||
|
|
4ddb7dce32 | ||
|
|
148c36cb64 | ||
|
|
448df6c1e3 | ||
|
|
72c616811b | ||
|
|
2a816b397c | ||
|
|
bb9e94c632 | ||
|
|
fec7a7aa70 | ||
|
|
f9a5e32ec9 | ||
|
|
4551ae3fa1 | ||
|
|
a2af9ca4d2 | ||
|
|
623934c980 | ||
|
|
720ff1f7a6 | ||
|
|
68e062ff08 | ||
|
|
d6176d3f39 | ||
|
|
edd3aeba16 | ||
|
|
cf3efe770d | ||
|
|
826deec8a8 | ||
|
|
fc8910ffee | ||
|
|
41bd828367 | ||
|
|
ddb996882f | ||
|
|
5d1917efa2 | ||
|
|
9ec54ecec8 | ||
|
|
8f25e18c53 | ||
|
|
a734afaabe | ||
|
|
ca87a3f93f | ||
|
|
a8d9c90bfa | ||
|
|
68a2a945f9 | ||
|
|
3c45fcbef2 | ||
|
|
71efae7300 | ||
|
|
25b5ae398e | ||
|
|
86b540c13a | ||
|
|
971007fc3f | ||
|
|
c006575498 | ||
|
|
51917c5403 | ||
|
|
cc226bfe9e | ||
|
|
a965f7fbb0 | ||
|
|
7bcc949a17 | ||
|
|
a4d9d83fac | ||
|
|
8edf712669 | ||
|
|
084f8aa658 | ||
|
|
084a62e60f | ||
|
|
655dc88c62 | ||
|
|
46109d1ea3 | ||
|
|
f7d931be0c | ||
|
|
8d6af53e54 | ||
|
|
8d3bd52fc5 | ||
|
|
8da95ed824 | ||
|
|
8207a75820 | ||
|
|
2c48ce0152 | ||
|
|
dae0ad1de5 | ||
|
|
7c76b58ab8 | ||
|
|
4ea2dfdfb7 | ||
|
|
d8d478a95e | ||
|
|
4c20250888 | ||
|
|
f5a15905e4 | ||
|
|
53742e5ec2 | ||
|
|
504c75566a | ||
|
|
ed4dcbac3b | ||
|
|
a0f1cd5814 | ||
|
|
4607a30e6a | ||
|
|
fca370b9d9 | ||
|
|
dc3f1661e8 | ||
|
|
463fe97b29 | ||
|
|
b08527bce2 | ||
|
|
41c092f578 | ||
|
|
311ecb7030 | ||
|
|
4a28ea7003 | ||
|
|
0a82f889f3 | ||
|
|
00e6da520d | ||
|
|
0b830e9b5e | ||
|
|
468b2f3284 | ||
|
|
db21131185 | ||
|
|
7d9555fdf7 | ||
|
|
729552a827 | ||
|
|
cdc8f9af4b | ||
|
|
9e5034ebab | ||
|
|
c2f835c897 | ||
|
|
9c2f27bcdb | ||
|
|
423fc4ac80 | ||
|
|
e1292a0780 | ||
|
|
f72960635d | ||
|
|
b5c80e9d27 | ||
|
|
3fa4b01115 | ||
|
|
65f402fd35 | ||
|
|
46f1bc20c8 | ||
|
|
a13a72c626 | ||
|
|
5a80145607 | ||
|
|
baf5e6a593 | ||
|
|
850bb8f44e | ||
|
|
b17d8424e9 | ||
|
|
d2253ff069 | ||
|
|
0946b3a1da |
@@ -103,6 +103,7 @@ steps:
|
||||
from_secret: BUILDRONE_KEY
|
||||
JFA_GO_BUILT_BY:
|
||||
from_secret: BUILT_BY
|
||||
JFA_GO_SNAPSHOT: y
|
||||
|
||||
volumes:
|
||||
- name: ssh_key
|
||||
|
||||
1
.gitignore
vendored
@@ -24,3 +24,4 @@ matacc.txt
|
||||
scripts/langmover/lang
|
||||
scripts/langmover/lang2
|
||||
scripts/langmover/out
|
||||
tinyproxy.conf
|
||||
|
||||
@@ -26,12 +26,13 @@ before:
|
||||
- scripts/dark-variant.sh tempts
|
||||
- scripts/dark-variant.sh tempts/modules
|
||||
- mkdir -p data/web/js
|
||||
- npx esbuild --target=es6 --bundle tempts/admin.ts --outfile=./data/web/js/admin.js --minify
|
||||
- npx esbuild --target=es6 --bundle tempts/user.ts --outfile=./data/web/js/user.js --minify
|
||||
- npx esbuild --target=es6 --bundle tempts/pwr.ts --outfile=./data/web/js/pwr.js --minify
|
||||
- npx esbuild --target=es6 --bundle tempts/form.ts --outfile=./data/web/js/form.js --minify
|
||||
- npx esbuild --target=es6 --bundle tempts/setup.ts --outfile=./data/web/js/setup.js --minify
|
||||
- npx esbuild --target=es6 --bundle tempts/crash.ts --outfile=./data/crash.js --minify
|
||||
- npx esbuild --target=es6 --bundle tempts/admin.ts {{.Env.JFA_GO_SOURCEMAP}} --outfile=./data/web/js/admin.js {{.Env.JFA_GO_MINIFY}}
|
||||
- npx esbuild --target=es6 --bundle tempts/user.ts {{.Env.JFA_GO_SOURCEMAP}} --outfile=./data/web/js/user.js {{.Env.JFA_GO_MINIFY}}
|
||||
- npx esbuild --target=es6 --bundle tempts/pwr.ts {{.Env.JFA_GO_SOURCEMAP}} --outfile=./data/web/js/pwr.js {{.Env.JFA_GO_MINIFY}}
|
||||
- npx esbuild --target=es6 --bundle tempts/form.ts {{.Env.JFA_GO_SOURCEMAP}} --outfile=./data/web/js/form.js {{.Env.JFA_GO_MINIFY}}
|
||||
- npx esbuild --target=es6 --bundle tempts/setup.ts {{.Env.JFA_GO_SOURCEMAP}} --outfile=./data/web/js/setup.js {{.Env.JFA_GO_MINIFY}}
|
||||
- npx esbuild --target=es6 --bundle tempts/crash.ts {{.Env.JFA_GO_SOURCEMAP}} --outfile=./data/crash.js {{.Env.JFA_GO_MINIFY}}
|
||||
- bash -c "{{.Env.JFA_GO_COPYTS}}"
|
||||
- rm -r tempts
|
||||
- npx esbuild --bundle css/base.css --outfile=./data/web/css/bundle.css --external:remixicon.css --external:../fonts/hanken* --minify
|
||||
- cp html/crash.html data/
|
||||
@@ -49,10 +50,11 @@ builds:
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Env.JFA_GO_VERSION}} -X main.commit={{.ShortCommit}} -X main.updater=binary -X main.cssVersion={{.Env.JFA_GO_CSS_VERSION}} -X main.buildTimeUnix={{.Env.JFA_GO_BUILD_TIME}} -X main.builtBy="{{.Env.JFA_GO_BUILT_BY}}"
|
||||
- -X main.version={{.Env.JFA_GO_VERSION}} -X main.commit={{.ShortCommit}} -X main.updater=binary {{.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:
|
||||
- linux
|
||||
- darwin
|
||||
- windows
|
||||
goarch:
|
||||
- arm
|
||||
- arm64
|
||||
@@ -66,7 +68,7 @@ builds:
|
||||
flags:
|
||||
- -tags=tray
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Env.JFA_GO_VERSION}} -X main.commit={{.ShortCommit}} -X main.updater=binary -X main.cssVersion={{.Env.JFA_GO_CSS_VERSION}} -X main.buildTimeUnix={{.Env.JFA_GO_BUILD_TIME}} -X main.builtBy="{{.Env.JFA_GO_BUILT_BY}}" -H=windowsgui
|
||||
- -X main.version={{.Env.JFA_GO_VERSION}} -X main.commit={{.ShortCommit}} -X main.updater=binary {{.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}}" -H=windowsgui
|
||||
goos:
|
||||
- windows
|
||||
goarch:
|
||||
@@ -78,7 +80,7 @@ builds:
|
||||
flags:
|
||||
- -tags=tray
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Env.JFA_GO_VERSION}} -X main.commit={{.ShortCommit}} -X main.updater=binary -X main.cssVersion={{.Env.JFA_GO_CSS_VERSION}} -X main.buildTimeUnix={{.Env.JFA_GO_BUILD_TIME}} -X main.builtBy="{{.Env.JFA_GO_BUILT_BY}}"
|
||||
- -X main.version={{.Env.JFA_GO_VERSION}} -X main.commit={{.ShortCommit}} -X main.updater=binary {{.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:
|
||||
- linux
|
||||
goarch:
|
||||
|
||||
7
Makefile
@@ -88,6 +88,11 @@ else
|
||||
NPMOPTS :=
|
||||
endif
|
||||
|
||||
ifeq (, $(shell which swag))
|
||||
SWAGINSTALL := $(GOBINARY) install github.com/swaggo/swag/cmd/swag@latest
|
||||
else
|
||||
SWAGINSTALL :=
|
||||
endif
|
||||
|
||||
npm:
|
||||
$(info installing npm dependencies)
|
||||
@@ -122,7 +127,7 @@ typescript:
|
||||
$(COPYTS)
|
||||
|
||||
swagger:
|
||||
$(GOBINARY) install github.com/swaggo/swag/cmd/swag@latest
|
||||
$(SWAGINSTALL)
|
||||
swag init -g main.go
|
||||
|
||||
compile:
|
||||
|
||||
15
README.md
@@ -38,9 +38,12 @@ a rewrite of [jellyfin-accounts](https://github.com/hrfee/jellyfin-accounts) (or
|
||||
* 🔗 Ombi Integration: Automatically creates Ombi accounts for new users using their email address and login details, and your own defined set of permissions.
|
||||
* Account management: Apply settings to your users individually or en masse, and delete users, optionally sending them an email notification with a reason.
|
||||
* 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. Custom messages can be added, with markdown.
|
||||
* Referrals: Users can be given special invites to send to their friends and families.
|
||||
* 📨 Email storage: Add your existing users email addresses through the UI, and jfa-go will ask new users for them on account creation.
|
||||
* Email addresses can optionally be used instead of usernames
|
||||
* 🔑 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.
|
||||
* 📣 Announcements: Bulk message your users with announcements about your server.
|
||||
* Authentication via Jellyfin: Instead of using separate credentials for jfa-go and Jellyfin, jfa-go can use it as the authentication provider.
|
||||
@@ -52,13 +55,10 @@ a rewrite of [jellyfin-accounts](https://github.com/hrfee/jellyfin-accounts) (or
|
||||
|
||||
#### Interface
|
||||
<p align="center">
|
||||
<img src="images/demo.gif" width="100%"></img>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="images/invites.png" width="31%" style="margin-left: 1.5%;" alt="Invites tab"></img>
|
||||
<img src="images/accounts.png" width="31%" style="margin-right: 1.5%;" alt="Accounts tab"></img>
|
||||
<img src="images/create.png" width="31%" style="margin-right: 1.5%;" alt="Accounts creation"></img>
|
||||
<img src="images/invites.png" width="47%" style="margin-left: 1.5%;" align="top" alt="Invites tab"></img>
|
||||
<img src="images/create.png" width="47%" style="margin-right: 1.5%;" align="top" alt="Accounts creation"></img>
|
||||
<img src="images/myaccount.png" width="47%" style="margin-left: 1.5%; margin-top: 1rem;" align="top" alt="My Account Page"></img>
|
||||
<img src="images/accounts.png" width="47%" style="margin-right: 1.5%; margin-top: 1rem;" align="top" alt="Accounts tab"></img>
|
||||
</p>
|
||||
|
||||
#### Install
|
||||
@@ -175,3 +175,4 @@ For translations, use the weblate instance [here](https://weblate.jfa-go.com/eng
|
||||
Big thanks to those who sponsor me. You can see them below:
|
||||
|
||||
[<img src="https://sponsors-endpoint.hrfee.pw/sponsor/avatar/0" width="35">](https://sponsors-endpoint.hrfee.pw/sponsor/profile/0)
|
||||
[<img src="https://sponsors-endpoint.hrfee.pw/sponsor/avatar/1" width="35">](https://sponsors-endpoint.hrfee.pw/sponsor/profile/0)
|
||||
|
||||
186
api-activities.go
Normal file
@@ -0,0 +1,186 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/timshannon/badgerhold/v4"
|
||||
)
|
||||
|
||||
func stringToActivityType(v string) ActivityType {
|
||||
switch v {
|
||||
case "creation":
|
||||
return ActivityCreation
|
||||
case "deletion":
|
||||
return ActivityDeletion
|
||||
case "disabled":
|
||||
return ActivityDisabled
|
||||
case "enabled":
|
||||
return ActivityEnabled
|
||||
case "contactLinked":
|
||||
return ActivityContactLinked
|
||||
case "contactUnlinked":
|
||||
return ActivityContactUnlinked
|
||||
case "changePassword":
|
||||
return ActivityChangePassword
|
||||
case "resetPassword":
|
||||
return ActivityResetPassword
|
||||
case "createInvite":
|
||||
return ActivityCreateInvite
|
||||
case "deleteInvite":
|
||||
return ActivityDeleteInvite
|
||||
}
|
||||
return ActivityUnknown
|
||||
}
|
||||
|
||||
func activityTypeToString(v ActivityType) string {
|
||||
switch v {
|
||||
case ActivityCreation:
|
||||
return "creation"
|
||||
case ActivityDeletion:
|
||||
return "deletion"
|
||||
case ActivityDisabled:
|
||||
return "disabled"
|
||||
case ActivityEnabled:
|
||||
return "enabled"
|
||||
case ActivityContactLinked:
|
||||
return "contactLinked"
|
||||
case ActivityContactUnlinked:
|
||||
return "contactUnlinked"
|
||||
case ActivityChangePassword:
|
||||
return "changePassword"
|
||||
case ActivityResetPassword:
|
||||
return "resetPassword"
|
||||
case ActivityCreateInvite:
|
||||
return "createInvite"
|
||||
case ActivityDeleteInvite:
|
||||
return "deleteInvite"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
func stringToActivitySource(v string) ActivitySource {
|
||||
switch v {
|
||||
case "user":
|
||||
return ActivityUser
|
||||
case "admin":
|
||||
return ActivityAdmin
|
||||
case "anon":
|
||||
return ActivityAnon
|
||||
case "daemon":
|
||||
return ActivityDaemon
|
||||
}
|
||||
return ActivityAnon
|
||||
}
|
||||
|
||||
func activitySourceToString(v ActivitySource) string {
|
||||
switch v {
|
||||
case ActivityUser:
|
||||
return "user"
|
||||
case ActivityAdmin:
|
||||
return "admin"
|
||||
case ActivityAnon:
|
||||
return "anon"
|
||||
case ActivityDaemon:
|
||||
return "daemon"
|
||||
}
|
||||
return "anon"
|
||||
}
|
||||
|
||||
// @Summary Get the requested set of activities, Paginated, filtered and sorted.
|
||||
// @Produce json
|
||||
// @Param GetActivitiesDTO body GetActivitiesDTO true "search parameters"
|
||||
// @Success 200 {object} GetActivitiesRespDTO
|
||||
// @Router /activity [post]
|
||||
// @Security Bearer
|
||||
// @tags Activity
|
||||
func (app *appContext) GetActivities(gc *gin.Context) {
|
||||
req := GetActivitiesDTO{}
|
||||
gc.BindJSON(&req)
|
||||
query := &badgerhold.Query{}
|
||||
activityTypes := make([]interface{}, len(req.Type))
|
||||
for i, v := range req.Type {
|
||||
activityTypes[i] = stringToActivityType(v)
|
||||
}
|
||||
if len(activityTypes) != 0 {
|
||||
query = badgerhold.Where("Type").In(activityTypes...)
|
||||
}
|
||||
|
||||
if !req.Ascending {
|
||||
query = query.Reverse()
|
||||
}
|
||||
|
||||
query = query.SortBy("Time")
|
||||
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 10
|
||||
}
|
||||
|
||||
query = query.Skip(req.Page * req.Limit).Limit(req.Limit)
|
||||
|
||||
var results []Activity
|
||||
err := app.storage.db.Find(&results, query)
|
||||
|
||||
if err != nil {
|
||||
app.err.Printf("Failed to read activities from DB: %v\n", err)
|
||||
}
|
||||
|
||||
resp := GetActivitiesRespDTO{
|
||||
Activities: make([]ActivityDTO, len(results)),
|
||||
LastPage: len(results) != req.Limit,
|
||||
}
|
||||
|
||||
for i, act := range results {
|
||||
resp.Activities[i] = ActivityDTO{
|
||||
ID: act.ID,
|
||||
Type: activityTypeToString(act.Type),
|
||||
UserID: act.UserID,
|
||||
SourceType: activitySourceToString(act.SourceType),
|
||||
Source: act.Source,
|
||||
InviteCode: act.InviteCode,
|
||||
Value: act.Value,
|
||||
Time: act.Time.Unix(),
|
||||
}
|
||||
if act.Type == ActivityDeletion || act.Type == ActivityCreation {
|
||||
resp.Activities[i].Username = act.Value
|
||||
resp.Activities[i].Value = ""
|
||||
} else if user, status, err := app.jf.UserByID(act.UserID, false); status == 200 && err == nil {
|
||||
resp.Activities[i].Username = user.Name
|
||||
}
|
||||
|
||||
if (act.SourceType == ActivityUser || act.SourceType == ActivityAdmin) && act.Source != "" {
|
||||
user, status, err := app.jf.UserByID(act.Source, false)
|
||||
if status == 200 && err == nil {
|
||||
resp.Activities[i].SourceUsername = user.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gc.JSON(200, resp)
|
||||
}
|
||||
|
||||
// @Summary Delete the activity with the given ID. No-op if non-existent, always succeeds.
|
||||
// @Produce json
|
||||
// @Param id path string true "ID of activity to delete"
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Router /activity/{id} [delete]
|
||||
// @Security Bearer
|
||||
// @tags Activity
|
||||
func (app *appContext) DeleteActivity(gc *gin.Context) {
|
||||
app.storage.DeleteActivityKey(gc.Param("id"))
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Returns the total number of activities stored in the database.
|
||||
// @Produce json
|
||||
// @Success 200 {object} GetActivityCountDTO
|
||||
// @Router /activity/count [get]
|
||||
// @Security Bearer
|
||||
// @tags Activity
|
||||
func (app *appContext) GetActivityCount(gc *gin.Context) {
|
||||
resp := GetActivityCountDTO{}
|
||||
var err error
|
||||
resp.Count, err = app.storage.db.Count(&Activity{}, &badgerhold.Query{})
|
||||
if err != nil {
|
||||
resp.Count = 0
|
||||
}
|
||||
gc.JSON(200, resp)
|
||||
}
|
||||
109
api-invites.go
@@ -13,9 +13,41 @@ import (
|
||||
"github.com/timshannon/badgerhold/v4"
|
||||
)
|
||||
|
||||
const (
|
||||
CAPTCHA_VALIDITY = 20 * 60 // Seconds
|
||||
)
|
||||
|
||||
// GenerateInviteCode generates an invite code in the correct format.
|
||||
func GenerateInviteCode() string {
|
||||
// make sure code doesn't begin with number
|
||||
inviteCode := shortuuid.New()
|
||||
_, err := strconv.Atoi(string(inviteCode[0]))
|
||||
for err == nil {
|
||||
inviteCode = shortuuid.New()
|
||||
_, err = strconv.Atoi(string(inviteCode[0]))
|
||||
}
|
||||
return inviteCode
|
||||
}
|
||||
|
||||
func (app *appContext) checkInvites() {
|
||||
currentTime := time.Now()
|
||||
for _, data := range app.storage.GetInvites() {
|
||||
captchas := data.Captchas
|
||||
captchasExpired := false
|
||||
for key, capt := range data.Captchas {
|
||||
if time.Now().After(capt.Generated.Add(CAPTCHA_VALIDITY * time.Second)) {
|
||||
delete(captchas, key)
|
||||
captchasExpired = true
|
||||
}
|
||||
}
|
||||
if captchasExpired {
|
||||
data.Captchas = captchas
|
||||
app.storage.SetInvitesKey(data.Code, data)
|
||||
}
|
||||
|
||||
if data.IsReferral {
|
||||
continue
|
||||
}
|
||||
expiry := data.ValidTill
|
||||
if !currentTime.After(expiry) {
|
||||
continue
|
||||
@@ -53,6 +85,14 @@ func (app *appContext) checkInvites() {
|
||||
wait.Wait()
|
||||
}
|
||||
app.storage.DeleteInvitesKey(data.Code)
|
||||
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
Type: ActivityDeleteInvite,
|
||||
SourceType: ActivityDaemon,
|
||||
InviteCode: data.Code,
|
||||
Value: data.Label,
|
||||
Time: time.Now(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,12 +138,26 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool
|
||||
}
|
||||
match = false
|
||||
app.storage.DeleteInvitesKey(code)
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
Type: ActivityDeleteInvite,
|
||||
SourceType: ActivityDaemon,
|
||||
InviteCode: code,
|
||||
Value: inv.Label,
|
||||
Time: time.Now(),
|
||||
})
|
||||
} else if used {
|
||||
del := false
|
||||
newInv := inv
|
||||
if newInv.RemainingUses == 1 {
|
||||
del = true
|
||||
app.storage.DeleteInvitesKey(code)
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
Type: ActivityDeleteInvite,
|
||||
SourceType: ActivityDaemon,
|
||||
InviteCode: code,
|
||||
Value: inv.Label,
|
||||
Time: time.Now(),
|
||||
})
|
||||
} else if newInv.RemainingUses != 0 {
|
||||
// 0 means infinite i guess?
|
||||
newInv.RemainingUses--
|
||||
@@ -130,17 +184,14 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
|
||||
currentTime := time.Now()
|
||||
validTill := currentTime.AddDate(0, req.Months, req.Days)
|
||||
validTill = validTill.Add(time.Hour*time.Duration(req.Hours) + time.Minute*time.Duration(req.Minutes))
|
||||
// make sure code doesn't begin with number
|
||||
inviteCode := shortuuid.New()
|
||||
_, err := strconv.Atoi(string(inviteCode[0]))
|
||||
for err == nil {
|
||||
inviteCode = shortuuid.New()
|
||||
_, err = strconv.Atoi(string(inviteCode[0]))
|
||||
}
|
||||
var invite Invite
|
||||
invite.Code = GenerateInviteCode()
|
||||
if req.Label != "" {
|
||||
invite.Label = req.Label
|
||||
}
|
||||
if req.UserLabel != "" {
|
||||
invite.UserLabel = req.UserLabel
|
||||
}
|
||||
invite.Created = currentTime
|
||||
if req.MultipleUses {
|
||||
if req.NoLimit {
|
||||
@@ -162,8 +213,8 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
|
||||
if req.SendTo != "" && app.config.Section("invite_emails").Key("enabled").MustBool(false) {
|
||||
addressValid := false
|
||||
discord := ""
|
||||
app.debug.Printf("%s: Sending invite message", inviteCode)
|
||||
if discordEnabled && !strings.Contains(req.SendTo, "@") {
|
||||
app.debug.Printf("%s: Sending invite message", invite.Code)
|
||||
if discordEnabled && (!strings.Contains(req.SendTo, "@") || strings.HasPrefix(req.SendTo, "@")) {
|
||||
users := app.discord.GetUsers(req.SendTo)
|
||||
if len(users) == 0 {
|
||||
invite.SendTo = fmt.Sprintf("Failed: User not found: \"%s\"", req.SendTo)
|
||||
@@ -179,10 +230,10 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
|
||||
invite.SendTo = req.SendTo
|
||||
}
|
||||
if addressValid {
|
||||
msg, err := app.email.constructInvite(inviteCode, invite, app, false)
|
||||
msg, err := app.email.constructInvite(invite.Code, invite, app, false)
|
||||
if err != nil {
|
||||
invite.SendTo = fmt.Sprintf("Failed to send to %s", req.SendTo)
|
||||
app.err.Printf("%s: Failed to construct invite message: %v", inviteCode, err)
|
||||
app.err.Printf("%s: Failed to construct invite message: %v", invite.Code, err)
|
||||
} else {
|
||||
var err error
|
||||
if discord != "" {
|
||||
@@ -192,9 +243,9 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
|
||||
}
|
||||
if err != nil {
|
||||
invite.SendTo = fmt.Sprintf("Failed to send to %s", req.SendTo)
|
||||
app.err.Printf("%s: %s: %v", inviteCode, invite.SendTo, err)
|
||||
app.err.Printf("%s: %s: %v", invite.Code, invite.SendTo, err)
|
||||
} else {
|
||||
app.info.Printf("%s: Sent invite email to \"%s\"", inviteCode, req.SendTo)
|
||||
app.info.Printf("%s: Sent invite email to \"%s\"", invite.Code, req.SendTo)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,7 +257,19 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
|
||||
invite.Profile = "Default"
|
||||
}
|
||||
}
|
||||
app.storage.SetInvitesKey(inviteCode, invite)
|
||||
app.storage.SetInvitesKey(invite.Code, invite)
|
||||
|
||||
// Record activity
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
Type: ActivityCreateInvite,
|
||||
UserID: "",
|
||||
SourceType: ActivityAdmin,
|
||||
Source: gc.GetString("jfId"),
|
||||
InviteCode: invite.Code,
|
||||
Value: invite.Label,
|
||||
Time: time.Now(),
|
||||
})
|
||||
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
@@ -222,6 +285,9 @@ func (app *appContext) GetInvites(gc *gin.Context) {
|
||||
app.checkInvites()
|
||||
var invites []inviteDTO
|
||||
for _, inv := range app.storage.GetInvites() {
|
||||
if inv.IsReferral {
|
||||
continue
|
||||
}
|
||||
_, months, days, hours, minutes, _ := timeDiff(inv.ValidTill, currentTime)
|
||||
invite := inviteDTO{
|
||||
Code: inv.Code,
|
||||
@@ -238,6 +304,7 @@ func (app *appContext) GetInvites(gc *gin.Context) {
|
||||
Profile: inv.Profile,
|
||||
NoLimit: inv.NoLimit,
|
||||
Label: inv.Label,
|
||||
UserLabel: inv.UserLabel,
|
||||
}
|
||||
if len(inv.UsedBy) != 0 {
|
||||
invite.UsedBy = map[string]int64{}
|
||||
@@ -396,10 +463,20 @@ func (app *appContext) DeleteInvite(gc *gin.Context) {
|
||||
var req deleteInviteDTO
|
||||
gc.BindJSON(&req)
|
||||
app.debug.Printf("%s: Deletion requested", req.Code)
|
||||
var ok bool
|
||||
_, ok = app.storage.GetInvitesKey(req.Code)
|
||||
inv, ok := app.storage.GetInvitesKey(req.Code)
|
||||
if ok {
|
||||
app.storage.DeleteInvitesKey(req.Code)
|
||||
|
||||
// Record activity
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
Type: ActivityDeleteInvite,
|
||||
SourceType: ActivityAdmin,
|
||||
Source: gc.GetString("jfId"),
|
||||
InviteCode: req.Code,
|
||||
Value: inv.Label,
|
||||
Time: time.Now(),
|
||||
})
|
||||
|
||||
app.info.Printf("%s: Invite deleted", req.Code)
|
||||
respondBool(200, true, gc)
|
||||
return
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/lithammer/shortuuid/v3"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
@@ -162,7 +163,7 @@ func (app *appContext) GetCustomMessageTemplate(gc *gin.Context) {
|
||||
username := app.storage.lang.Email[lang].Strings.get("username")
|
||||
emailAddress := app.storage.lang.Email[lang].Strings.get("emailAddress")
|
||||
customMessage, ok := app.storage.GetCustomContentKey(id)
|
||||
if !ok {
|
||||
if !ok && id != "Announcement" {
|
||||
app.err.Printf("Failed to get custom message with ID \"%s\"", id)
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
@@ -677,7 +678,18 @@ func (app *appContext) DiscordConnect(gc *gin.Context) {
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
|
||||
app.storage.SetDiscordKey(req.JellyfinID, user)
|
||||
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
Type: ActivityContactLinked,
|
||||
UserID: req.JellyfinID,
|
||||
SourceType: ActivityAdmin,
|
||||
Source: gc.GetString("jfId"),
|
||||
Value: "discord",
|
||||
Time: time.Now(),
|
||||
})
|
||||
|
||||
linkExistingOmbiDiscordTelegram(app)
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
@@ -697,6 +709,16 @@ func (app *appContext) UnlinkDiscord(gc *gin.Context) {
|
||||
return
|
||||
} */
|
||||
app.storage.DeleteDiscordKey(req.ID)
|
||||
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
Type: ActivityContactUnlinked,
|
||||
UserID: req.ID,
|
||||
SourceType: ActivityAdmin,
|
||||
Source: gc.GetString("jfId"),
|
||||
Value: "discord",
|
||||
Time: time.Now(),
|
||||
})
|
||||
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
@@ -715,6 +737,16 @@ func (app *appContext) UnlinkTelegram(gc *gin.Context) {
|
||||
return
|
||||
} */
|
||||
app.storage.DeleteTelegramKey(req.ID)
|
||||
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
Type: ActivityContactUnlinked,
|
||||
UserID: req.ID,
|
||||
SourceType: ActivityAdmin,
|
||||
Source: gc.GetString("jfId"),
|
||||
Value: "telegram",
|
||||
Time: time.Now(),
|
||||
})
|
||||
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
@@ -733,5 +765,15 @@ func (app *appContext) UnlinkMatrix(gc *gin.Context) {
|
||||
return
|
||||
} */
|
||||
app.storage.DeleteMatrixKey(req.ID)
|
||||
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
Type: ActivityContactUnlinked,
|
||||
UserID: req.ID,
|
||||
SourceType: ActivityAdmin,
|
||||
Source: gc.GetString("jfId"),
|
||||
Value: "matrix",
|
||||
Time: time.Now(),
|
||||
})
|
||||
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
42
api-ombi.go
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/hrfee/mediabrowser"
|
||||
)
|
||||
|
||||
func (app *appContext) getOmbiUser(jfID string) (map[string]interface{}, int, error) {
|
||||
@@ -29,7 +30,31 @@ func (app *appContext) getOmbiUser(jfID string) (map[string]interface{}, int, er
|
||||
return ombiUser, code, err
|
||||
}
|
||||
}
|
||||
return nil, 400, fmt.Errorf("Couldn't find user")
|
||||
return nil, 400, fmt.Errorf("couldn't find user")
|
||||
}
|
||||
|
||||
// Returns a user with the given name who has been imported from Jellyfin/Emby by Ombi
|
||||
func (app *appContext) getOmbiImportedUser(name string) (map[string]interface{}, int, error) {
|
||||
// Ombi User Types: 3/4 = Emby, 5 = Jellyfin
|
||||
ombiUsers, code, err := app.ombi.GetUsers()
|
||||
if err != nil || code != 200 {
|
||||
return nil, code, err
|
||||
}
|
||||
for _, ombiUser := range ombiUsers {
|
||||
if ombiUser["userName"].(string) == name {
|
||||
uType, ok := ombiUser["userType"].(int)
|
||||
if !ok { // Don't know if Ombi somehow allows duplicate usernames
|
||||
continue
|
||||
}
|
||||
if serverType == mediabrowser.JellyfinServer && uType != 5 { // Jellyfin
|
||||
continue
|
||||
} else if uType != 3 && uType != 4 { // Emby
|
||||
continue
|
||||
}
|
||||
return ombiUser, code, err
|
||||
}
|
||||
}
|
||||
return nil, 400, fmt.Errorf("couldn't find user")
|
||||
}
|
||||
|
||||
// @Summary Get a list of Ombi users.
|
||||
@@ -107,3 +132,18 @@ func (app *appContext) DeleteOmbiProfile(gc *gin.Context) {
|
||||
app.storage.SetProfileKey(profileName, profile)
|
||||
respondBool(204, true, gc)
|
||||
}
|
||||
|
||||
func (app *appContext) applyOmbiProfile(user map[string]interface{}, profile map[string]interface{}) (status int, err error) {
|
||||
for k, v := range profile {
|
||||
switch v.(type) {
|
||||
case map[string]interface{}, []interface{}:
|
||||
user[k] = v
|
||||
default:
|
||||
if v != user[k] {
|
||||
user[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
status, err = app.ombi.ModifyUser(user)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -19,13 +19,23 @@ func (app *appContext) GetProfiles(gc *gin.Context) {
|
||||
DefaultProfile: app.storage.GetDefaultProfile().Name,
|
||||
Profiles: map[string]profileDTO{},
|
||||
}
|
||||
referralsEnabled := app.config.Section("user_page").Key("referrals").MustBool(false)
|
||||
baseInv := Invite{}
|
||||
for _, p := range app.storage.GetProfiles() {
|
||||
out.Profiles[p.Name] = profileDTO{
|
||||
Admin: p.Admin,
|
||||
LibraryAccess: p.LibraryAccess,
|
||||
FromUser: p.FromUser,
|
||||
Ombi: p.Ombi != nil,
|
||||
pdto := profileDTO{
|
||||
Admin: p.Admin,
|
||||
LibraryAccess: p.LibraryAccess,
|
||||
FromUser: p.FromUser,
|
||||
Ombi: p.Ombi != nil,
|
||||
ReferralsEnabled: false,
|
||||
}
|
||||
if referralsEnabled {
|
||||
err := app.storage.db.Get(p.ReferralTemplateKey, &baseInv)
|
||||
if p.ReferralTemplateKey != "" && err == nil {
|
||||
pdto.ReferralsEnabled = true
|
||||
}
|
||||
}
|
||||
out.Profiles[p.Name] = pdto
|
||||
}
|
||||
gc.JSON(200, out)
|
||||
}
|
||||
@@ -79,8 +89,9 @@ func (app *appContext) CreateProfile(gc *gin.Context) {
|
||||
return
|
||||
}
|
||||
profile := Profile{
|
||||
FromUser: user.Name,
|
||||
Policy: user.Policy,
|
||||
FromUser: user.Name,
|
||||
Policy: user.Policy,
|
||||
Homescreen: req.Homescreen,
|
||||
}
|
||||
app.debug.Printf("Creating profile from user \"%s\"", user.Name)
|
||||
if req.Homescreen {
|
||||
@@ -93,6 +104,10 @@ func (app *appContext) CreateProfile(gc *gin.Context) {
|
||||
}
|
||||
}
|
||||
app.storage.SetProfileKey(req.Name, profile)
|
||||
// Refresh discord bots, profile list
|
||||
if discordEnabled {
|
||||
app.discord.UpdateCommands()
|
||||
}
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
@@ -110,3 +125,70 @@ func (app *appContext) DeleteProfile(gc *gin.Context) {
|
||||
app.storage.DeleteProfileKey(name)
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Enable referrals for a profile, sourced from the given invite by its code.
|
||||
// @Produce json
|
||||
// @Param profile path string true "name of profile to enable referrals for."
|
||||
// @Param invite path string true "invite code to create referral template from."
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 400 {object} stringResponse
|
||||
// @Failure 500 {object} stringResponse
|
||||
// @Router /profiles/referral/{profile}/{invite} [post]
|
||||
// @Security Bearer
|
||||
// @tags Profiles & Settings
|
||||
func (app *appContext) EnableReferralForProfile(gc *gin.Context) {
|
||||
profileName := gc.Param("profile")
|
||||
invCode := gc.Param("invite")
|
||||
inv, ok := app.storage.GetInvitesKey(invCode)
|
||||
if !ok {
|
||||
respond(400, "Invalid invite code", gc)
|
||||
app.err.Printf("\"%s\": Failed to enable referrals: invite not found", profileName)
|
||||
return
|
||||
}
|
||||
profile, ok := app.storage.GetProfileKey(profileName)
|
||||
if !ok {
|
||||
respond(400, "Invalid profile", gc)
|
||||
app.err.Printf("\"%s\": Failed to enable referrals: profile not found", profileName)
|
||||
return
|
||||
}
|
||||
|
||||
// Generate new code for referral template
|
||||
inv.Code = GenerateInviteCode()
|
||||
inv.Created = time.Now()
|
||||
inv.ValidTill = inv.Created.Add(REFERRAL_EXPIRY_DAYS * 24 * time.Hour)
|
||||
inv.IsReferral = true
|
||||
// Since this is a template for multiple users, ReferrerJellyfinID is not set.
|
||||
// inv.ReferrerJellyfinID = ...
|
||||
|
||||
app.storage.SetInvitesKey(inv.Code, inv)
|
||||
|
||||
profile.ReferralTemplateKey = inv.Code
|
||||
|
||||
app.storage.SetProfileKey(profile.Name, profile)
|
||||
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Disable referrals for a profile, and removes the referral template. no-op if not enabled.
|
||||
// @Produce json
|
||||
// @Param profile path string true "name of profile to enable referrals for."
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Router /profiles/referral/{profile} [delete]
|
||||
// @Security Bearer
|
||||
// @tags Profiles & Settings
|
||||
func (app *appContext) DisableReferralForProfile(gc *gin.Context) {
|
||||
profileName := gc.Param("profile")
|
||||
profile, ok := app.storage.GetProfileKey(profileName)
|
||||
if !ok {
|
||||
respondBool(200, true, gc)
|
||||
return
|
||||
}
|
||||
|
||||
app.storage.DeleteInvitesKey(profile.ReferralTemplateKey)
|
||||
|
||||
profile.ReferralTemplateKey = ""
|
||||
|
||||
app.storage.SetProfileKey(profileName, profile)
|
||||
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
152
api-userpage.go
@@ -8,6 +8,12 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/lithammer/shortuuid/v3"
|
||||
"github.com/timshannon/badgerhold/v4"
|
||||
)
|
||||
|
||||
const (
|
||||
REFERRAL_EXPIRY_DAYS = 90
|
||||
)
|
||||
|
||||
// @Summary Returns the logged-in user's Jellyfin ID & Username, and other details.
|
||||
@@ -74,6 +80,25 @@ func (app *appContext) MyDetails(gc *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
if app.config.Section("user_page").Key("referrals").MustBool(false) {
|
||||
// 1. Look for existing template bound to this Jellyfin ID
|
||||
// If one exists, that means its just for us and so we
|
||||
// can use it directly.
|
||||
inv := Invite{}
|
||||
err := app.storage.db.FindOne(&inv, badgerhold.Where("ReferrerJellyfinID").Eq(resp.Id))
|
||||
if err == nil {
|
||||
resp.HasReferrals = true
|
||||
} else {
|
||||
// 2. Look for a template matching the key found in the user storage
|
||||
// Since this key is shared between users in a profile, we make a copy.
|
||||
user, ok := app.storage.GetEmailsKey(gc.GetString("jfId"))
|
||||
err = app.storage.db.Get(user.ReferralTemplateKey, &inv)
|
||||
if ok && err == nil {
|
||||
resp.HasReferrals = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gc.JSON(200, resp)
|
||||
}
|
||||
|
||||
@@ -183,6 +208,16 @@ func (app *appContext) confirmMyAction(gc *gin.Context, key string) {
|
||||
}
|
||||
emailStore.Addr = claims["email"].(string)
|
||||
app.storage.SetEmailsKey(id, emailStore)
|
||||
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
Type: ActivityContactLinked,
|
||||
UserID: gc.GetString("jfId"),
|
||||
SourceType: ActivityUser,
|
||||
Source: gc.GetString("jfId"),
|
||||
Value: "email",
|
||||
Time: time.Now(),
|
||||
})
|
||||
|
||||
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
||||
ombiUser, code, err := app.getOmbiUser(id)
|
||||
if code == 200 && err == nil {
|
||||
@@ -335,6 +370,16 @@ func (app *appContext) MyDiscordVerifiedInvite(gc *gin.Context) {
|
||||
dcUser.Contact = existingUser.Contact
|
||||
}
|
||||
app.storage.SetDiscordKey(gc.GetString("jfId"), dcUser)
|
||||
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
Type: ActivityContactLinked,
|
||||
UserID: gc.GetString("jfId"),
|
||||
SourceType: ActivityUser,
|
||||
Source: gc.GetString("jfId"),
|
||||
Value: "discord",
|
||||
Time: time.Now(),
|
||||
})
|
||||
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
@@ -373,6 +418,16 @@ func (app *appContext) MyTelegramVerifiedInvite(gc *gin.Context) {
|
||||
tgUser.Contact = existingUser.Contact
|
||||
}
|
||||
app.storage.SetTelegramKey(gc.GetString("jfId"), tgUser)
|
||||
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
Type: ActivityContactLinked,
|
||||
UserID: gc.GetString("jfId"),
|
||||
SourceType: ActivityUser,
|
||||
Source: gc.GetString("jfId"),
|
||||
Value: "telegram",
|
||||
Time: time.Now(),
|
||||
})
|
||||
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
@@ -444,6 +499,16 @@ func (app *appContext) MatrixCheckMyPIN(gc *gin.Context) {
|
||||
}
|
||||
|
||||
app.storage.SetMatrixKey(gc.GetString("jfId"), mxUser)
|
||||
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
Type: ActivityContactLinked,
|
||||
UserID: gc.GetString("jfId"),
|
||||
SourceType: ActivityUser,
|
||||
Source: gc.GetString("jfId"),
|
||||
Value: "matrix",
|
||||
Time: time.Now(),
|
||||
})
|
||||
|
||||
delete(app.matrix.tokens, pin)
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
@@ -456,6 +521,16 @@ func (app *appContext) MatrixCheckMyPIN(gc *gin.Context) {
|
||||
// @Tags User Page
|
||||
func (app *appContext) UnlinkMyDiscord(gc *gin.Context) {
|
||||
app.storage.DeleteDiscordKey(gc.GetString("jfId"))
|
||||
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
Type: ActivityContactUnlinked,
|
||||
UserID: gc.GetString("jfId"),
|
||||
SourceType: ActivityUser,
|
||||
Source: gc.GetString("jfId"),
|
||||
Value: "discord",
|
||||
Time: time.Now(),
|
||||
})
|
||||
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
@@ -467,6 +542,16 @@ func (app *appContext) UnlinkMyDiscord(gc *gin.Context) {
|
||||
// @Tags User Page
|
||||
func (app *appContext) UnlinkMyTelegram(gc *gin.Context) {
|
||||
app.storage.DeleteTelegramKey(gc.GetString("jfId"))
|
||||
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
Type: ActivityContactUnlinked,
|
||||
UserID: gc.GetString("jfId"),
|
||||
SourceType: ActivityUser,
|
||||
Source: gc.GetString("jfId"),
|
||||
Value: "telegram",
|
||||
Time: time.Now(),
|
||||
})
|
||||
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
@@ -478,6 +563,16 @@ func (app *appContext) UnlinkMyTelegram(gc *gin.Context) {
|
||||
// @Tags User Page
|
||||
func (app *appContext) UnlinkMyMatrix(gc *gin.Context) {
|
||||
app.storage.DeleteMatrixKey(gc.GetString("jfId"))
|
||||
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
Type: ActivityContactUnlinked,
|
||||
UserID: gc.GetString("jfId"),
|
||||
SourceType: ActivityUser,
|
||||
Source: gc.GetString("jfId"),
|
||||
Value: "matrix",
|
||||
Time: time.Now(),
|
||||
})
|
||||
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
@@ -596,6 +691,15 @@ func (app *appContext) ChangeMyPassword(gc *gin.Context) {
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
Type: ActivityChangePassword,
|
||||
UserID: user.ID,
|
||||
SourceType: ActivityUser,
|
||||
Source: user.ID,
|
||||
Time: time.Now(),
|
||||
})
|
||||
|
||||
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
||||
func() {
|
||||
ombiUser, status, err := app.getOmbiUser(gc.GetString("jfId"))
|
||||
@@ -621,3 +725,51 @@ func (app *appContext) ChangeMyPassword(gc *gin.Context) {
|
||||
}
|
||||
respondBool(204, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Get or generate a new referral code.
|
||||
// @Produce json
|
||||
// @Success 200 {object} GetMyReferralRespDTO
|
||||
// @Failure 400 {object} boolResponse
|
||||
// @Failure 401 {object} boolResponse
|
||||
// @Failure 500 {object} boolResponse
|
||||
// @Router /my/referral [get]
|
||||
// @Security Bearer
|
||||
// @Tags User Page
|
||||
func (app *appContext) GetMyReferral(gc *gin.Context) {
|
||||
// 1. Look for existing template bound to this Jellyfin ID
|
||||
// If one exists, that means its just for us and so we
|
||||
// can use it directly.
|
||||
inv := Invite{}
|
||||
err := app.storage.db.FindOne(&inv, badgerhold.Where("ReferrerJellyfinID").Eq(gc.GetString("jfId")))
|
||||
if err != nil {
|
||||
// 2. Look for a template matching the key found in the user storage
|
||||
// Since this key is shared between users in a profile, we make a copy.
|
||||
user, ok := app.storage.GetEmailsKey(gc.GetString("jfId"))
|
||||
err = app.storage.db.Get(user.ReferralTemplateKey, &inv)
|
||||
if !ok || err != nil {
|
||||
app.debug.Printf("Ignoring referral request, couldn't find template.")
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
inv.Code = GenerateInviteCode()
|
||||
inv.Created = time.Now()
|
||||
inv.ValidTill = inv.Created.Add(REFERRAL_EXPIRY_DAYS * 24 * time.Hour)
|
||||
inv.IsReferral = true
|
||||
inv.ReferrerJellyfinID = gc.GetString("jfId")
|
||||
app.storage.SetInvitesKey(inv.Code, inv)
|
||||
} else if time.Now().After(inv.ValidTill) {
|
||||
// 3. We found an invite for us, but it's expired.
|
||||
// We delete it from storage, and put it back with a fresh code and expiry.
|
||||
app.storage.DeleteInvitesKey(inv.Code)
|
||||
inv.Code = GenerateInviteCode()
|
||||
inv.Created = time.Now()
|
||||
inv.ValidTill = inv.Created.Add(REFERRAL_EXPIRY_DAYS * 24 * time.Hour)
|
||||
app.storage.SetInvitesKey(inv.Code, inv)
|
||||
}
|
||||
gc.JSON(200, GetMyReferralRespDTO{
|
||||
Code: inv.Code,
|
||||
RemainingUses: inv.RemainingUses,
|
||||
NoLimit: inv.NoLimit,
|
||||
Expiry: inv.ValidTill.Unix(),
|
||||
})
|
||||
}
|
||||
|
||||
307
api-users.go
@@ -9,6 +9,8 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/hrfee/mediabrowser"
|
||||
"github.com/lithammer/shortuuid/v3"
|
||||
"github.com/timshannon/badgerhold/v4"
|
||||
)
|
||||
|
||||
// @Summary Creates a new Jellyfin user without an invite.
|
||||
@@ -44,9 +46,25 @@ func (app *appContext) NewUserAdmin(gc *gin.Context) {
|
||||
return
|
||||
}
|
||||
id := user.ID
|
||||
|
||||
// Record activity
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
Type: ActivityCreation,
|
||||
UserID: id,
|
||||
SourceType: ActivityAdmin,
|
||||
Source: gc.GetString("jfId"),
|
||||
Value: user.Name,
|
||||
Time: time.Now(),
|
||||
})
|
||||
|
||||
profile := app.storage.GetDefaultProfile()
|
||||
// Check profile isn't empty
|
||||
if profile.Policy.BlockedTags != nil {
|
||||
if req.Profile != "" && req.Profile != "none" {
|
||||
if p, ok := app.storage.GetProfileKey(req.Profile); ok {
|
||||
profile = p
|
||||
} else {
|
||||
app.debug.Printf("Couldn't find profile \"%s\", using default", req.Profile)
|
||||
}
|
||||
|
||||
status, err = app.jf.SetPolicy(id, profile.Policy)
|
||||
if !(status == 200 || status == 204 || err == nil) {
|
||||
app.err.Printf("%s: Failed to set user policy (%d): %v", req.Username, status, err)
|
||||
@@ -64,7 +82,6 @@ func (app *appContext) NewUserAdmin(gc *gin.Context) {
|
||||
app.storage.SetEmailsKey(id, EmailAddress{Addr: req.Email, Contact: true})
|
||||
}
|
||||
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
||||
profile := app.storage.GetDefaultProfile()
|
||||
if profile.Ombi == nil {
|
||||
profile.Ombi = map[string]interface{}{}
|
||||
}
|
||||
@@ -297,6 +314,34 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
|
||||
}
|
||||
}
|
||||
id := user.ID
|
||||
|
||||
// Record activity
|
||||
sourceType := ActivityAnon
|
||||
source := ""
|
||||
if invite.ReferrerJellyfinID != "" {
|
||||
sourceType = ActivityUser
|
||||
source = invite.ReferrerJellyfinID
|
||||
}
|
||||
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
Type: ActivityCreation,
|
||||
UserID: id,
|
||||
SourceType: sourceType,
|
||||
Source: source,
|
||||
InviteCode: invite.Code,
|
||||
Value: user.Name,
|
||||
Time: time.Now(),
|
||||
})
|
||||
|
||||
emailStore := EmailAddress{
|
||||
Addr: req.Email,
|
||||
Contact: (req.Email != ""),
|
||||
}
|
||||
|
||||
if invite.UserLabel != "" {
|
||||
emailStore.Label = invite.UserLabel
|
||||
}
|
||||
|
||||
var profile Profile
|
||||
if invite.Profile != "" {
|
||||
app.debug.Printf("Applying settings from profile \"%s\"", invite.Profile)
|
||||
@@ -305,27 +350,28 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
|
||||
if !ok {
|
||||
profile = app.storage.GetDefaultProfile()
|
||||
}
|
||||
if profile.Policy.BlockedTags != nil {
|
||||
app.debug.Printf("Applying policy from profile \"%s\"", invite.Profile)
|
||||
status, err = app.jf.SetPolicy(id, profile.Policy)
|
||||
if !((status == 200 || status == 204) && err == nil) {
|
||||
app.err.Printf("%s: Failed to set user policy (%d): %v", req.Code, status, err)
|
||||
}
|
||||
app.debug.Printf("Applying policy from profile \"%s\"", invite.Profile)
|
||||
status, err = app.jf.SetPolicy(id, profile.Policy)
|
||||
if !((status == 200 || status == 204) && err == nil) {
|
||||
app.err.Printf("%s: Failed to set user policy (%d): %v", req.Code, status, err)
|
||||
}
|
||||
if profile.Configuration.GroupedFolders != nil && len(profile.Displayprefs) != 0 {
|
||||
app.debug.Printf("Applying homescreen from profile \"%s\"", invite.Profile)
|
||||
status, err = app.jf.SetConfiguration(id, profile.Configuration)
|
||||
if (status == 200 || status == 204) && err == nil {
|
||||
status, err = app.jf.SetDisplayPreferences(id, profile.Displayprefs)
|
||||
}
|
||||
if !((status == 200 || status == 204) && err == nil) {
|
||||
app.err.Printf("%s: Failed to set configuration template (%d): %v", req.Code, status, err)
|
||||
}
|
||||
app.debug.Printf("Applying homescreen from profile \"%s\"", invite.Profile)
|
||||
status, err = app.jf.SetConfiguration(id, profile.Configuration)
|
||||
if (status == 200 || status == 204) && err == nil {
|
||||
status, err = app.jf.SetDisplayPreferences(id, profile.Displayprefs)
|
||||
}
|
||||
if !((status == 200 || status == 204) && err == nil) {
|
||||
app.err.Printf("%s: Failed to set configuration template (%d): %v", req.Code, status, err)
|
||||
}
|
||||
if app.config.Section("user_page").Key("enabled").MustBool(false) && app.config.Section("user_page").Key("referrals").MustBool(false) && profile.ReferralTemplateKey != "" {
|
||||
emailStore.ReferralTemplateKey = profile.ReferralTemplateKey
|
||||
// Store here, just incase email are disabled (whether this is even possible, i don't know)
|
||||
app.storage.SetEmailsKey(id, emailStore)
|
||||
}
|
||||
}
|
||||
// if app.config.Section("password_resets").Key("enabled").MustBool(false) {
|
||||
if req.Email != "" {
|
||||
app.storage.SetEmailsKey(id, EmailAddress{Addr: req.Email, Contact: true})
|
||||
if req.Email != "" || invite.UserLabel != "" {
|
||||
app.storage.SetEmailsKey(id, emailStore)
|
||||
}
|
||||
expiry := time.Time{}
|
||||
if invite.UserExpiry {
|
||||
@@ -337,6 +383,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
|
||||
if app.storage.deprecatedDiscord == nil {
|
||||
app.storage.deprecatedDiscord = discordStore{}
|
||||
}
|
||||
// Note we don't log an activity here, since it's part of creating a user.
|
||||
app.storage.SetDiscordKey(user.ID, discordUser)
|
||||
delete(app.discord.verifiedTokens, req.DiscordPIN)
|
||||
}
|
||||
@@ -359,30 +406,47 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
|
||||
if profile.Ombi != nil && len(profile.Ombi) != 0 {
|
||||
template := profile.Ombi
|
||||
errors, code, err := app.ombi.NewUser(req.Username, req.Password, req.Email, template)
|
||||
accountExists := false
|
||||
var ombiUser map[string]interface{}
|
||||
if err != nil || code != 200 {
|
||||
app.info.Printf("Failed to create Ombi user (%d): %s", code, err)
|
||||
app.debug.Printf("Errors reported by Ombi: %s", strings.Join(errors, ", "))
|
||||
} else {
|
||||
app.info.Println("Created Ombi user")
|
||||
if discordVerified || telegramVerified {
|
||||
ombiUser, status, err := app.getOmbiUser(id)
|
||||
// Check if on the off chance, Ombi's user importer has already added the account.
|
||||
ombiUser, status, err = app.getOmbiImportedUser(req.Username)
|
||||
if status == 200 && err == nil {
|
||||
app.info.Println("Found existing Ombi user, applying changes")
|
||||
accountExists = true
|
||||
template["password"] = req.Password
|
||||
status, err = app.applyOmbiProfile(ombiUser, template)
|
||||
if status != 200 || err != nil {
|
||||
app.err.Printf("Failed to get Ombi user (%d): %v", status, err)
|
||||
} else {
|
||||
dID := ""
|
||||
tUser := ""
|
||||
if discordVerified {
|
||||
dID = discordUser.ID
|
||||
}
|
||||
if telegramVerified {
|
||||
u, _ := app.storage.GetTelegramKey(user.ID)
|
||||
tUser = u.Username
|
||||
}
|
||||
resp, status, err := app.ombi.SetNotificationPrefs(ombiUser, dID, tUser)
|
||||
if !(status == 200 || status == 204) || err != nil {
|
||||
app.err.Printf("Failed to link Telegram/Discord to Ombi (%d): %v", status, err)
|
||||
app.debug.Printf("Response: %v", resp)
|
||||
}
|
||||
app.err.Printf("Failed to modify existing Ombi user (%d): %v\n", status, err)
|
||||
}
|
||||
} else {
|
||||
app.info.Printf("Failed to create Ombi user (%d): %s", code, err)
|
||||
app.debug.Printf("Errors reported by Ombi: %s", strings.Join(errors, ", "))
|
||||
}
|
||||
} else {
|
||||
ombiUser, status, err = app.getOmbiUser(id)
|
||||
if status != 200 || err != nil {
|
||||
app.err.Printf("Failed to get Ombi user (%d): %v", status, err)
|
||||
} else {
|
||||
app.info.Println("Created Ombi user")
|
||||
accountExists = true
|
||||
}
|
||||
}
|
||||
if accountExists {
|
||||
if discordVerified || telegramVerified {
|
||||
dID := ""
|
||||
tUser := ""
|
||||
if discordVerified {
|
||||
dID = discordUser.ID
|
||||
}
|
||||
if telegramVerified {
|
||||
u, _ := app.storage.GetTelegramKey(user.ID)
|
||||
tUser = u.Username
|
||||
}
|
||||
resp, status, err := app.ombi.SetNotificationPrefs(ombiUser, dID, tUser)
|
||||
if !(status == 200 || status == 204) || err != nil {
|
||||
app.err.Printf("Failed to link Telegram/Discord to Ombi (%d): %v", status, err)
|
||||
app.debug.Printf("Response: %v", resp)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -506,6 +570,10 @@ func (app *appContext) EnableDisableUsers(gc *gin.Context) {
|
||||
sendMail = false
|
||||
}
|
||||
}
|
||||
activityType := ActivityDisabled
|
||||
if req.Enabled {
|
||||
activityType = ActivityEnabled
|
||||
}
|
||||
for _, userID := range req.Users {
|
||||
user, status, err := app.jf.UserByID(userID, false)
|
||||
if status != 200 || err != nil {
|
||||
@@ -520,6 +588,16 @@ func (app *appContext) EnableDisableUsers(gc *gin.Context) {
|
||||
app.err.Printf("Failed to set policy for user \"%s\" (%d): %v", userID, status, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Record activity
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
Type: activityType,
|
||||
UserID: userID,
|
||||
SourceType: ActivityAdmin,
|
||||
Source: gc.GetString("jfId"),
|
||||
Time: time.Now(),
|
||||
})
|
||||
|
||||
if sendMail && req.Notify {
|
||||
if err := app.sendByID(msg, userID); err != nil {
|
||||
app.err.Printf("Failed to send account enabled/disabled email: %v", err)
|
||||
@@ -572,6 +650,12 @@ func (app *appContext) DeleteUsers(gc *gin.Context) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
username := ""
|
||||
if user, status, err := app.jf.UserByID(userID, false); status == 200 && err == nil {
|
||||
username = user.Name
|
||||
}
|
||||
|
||||
status, err := app.jf.DeleteUser(userID)
|
||||
if !(status == 200 || status == 204) || err != nil {
|
||||
msg := fmt.Sprintf("%d: %v", status, err)
|
||||
@@ -581,6 +665,17 @@ func (app *appContext) DeleteUsers(gc *gin.Context) {
|
||||
errors[userID] += msg
|
||||
}
|
||||
}
|
||||
|
||||
// Record activity
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
Type: ActivityDeletion,
|
||||
UserID: userID,
|
||||
SourceType: ActivityAdmin,
|
||||
Source: gc.GetString("jfId"),
|
||||
Value: username,
|
||||
Time: time.Now(),
|
||||
})
|
||||
|
||||
if sendMail && req.Notify {
|
||||
if err := app.sendByID(msg, userID); err != nil {
|
||||
app.err.Printf("Failed to send account deletion email: %v", err)
|
||||
@@ -629,6 +724,82 @@ func (app *appContext) ExtendExpiry(gc *gin.Context) {
|
||||
respondBool(204, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Enable referrals for the given user(s) based on the rules set in the given invite code, or profile.
|
||||
// @Produce json
|
||||
// @Param EnableDisableReferralDTO body EnableDisableReferralDTO true "List of users"
|
||||
// @Param mode path string true "mode of template sourcing from 'invite' or 'profile'."
|
||||
// @Param source path string true "invite code or profile name, depending on what mode is."
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 400 {object} boolResponse
|
||||
// @Failure 500 {object} boolResponse
|
||||
// @Router /users/referral/{mode}/{source} [post]
|
||||
// @Security Bearer
|
||||
// @tags Users
|
||||
func (app *appContext) EnableReferralForUsers(gc *gin.Context) {
|
||||
var req EnableDisableReferralDTO
|
||||
gc.BindJSON(&req)
|
||||
mode := gc.Param("mode")
|
||||
source := gc.Param("source")
|
||||
|
||||
baseInv := Invite{}
|
||||
if mode == "profile" {
|
||||
profile, ok := app.storage.GetProfileKey(source)
|
||||
err := app.storage.db.Get(profile.ReferralTemplateKey, &baseInv)
|
||||
if !ok || profile.ReferralTemplateKey == "" || err != nil {
|
||||
app.debug.Printf("Couldn't find template to source from")
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
|
||||
}
|
||||
app.debug.Printf("Found referral template in profile: %+v\n", profile.ReferralTemplateKey)
|
||||
} else if mode == "invite" {
|
||||
// Get the invite, and modify it to turn it into a referral
|
||||
err := app.storage.db.Get(source, &baseInv)
|
||||
if err != nil {
|
||||
app.debug.Printf("Couldn't find invite to source from")
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, u := range req.Users {
|
||||
// 1. Wipe out any existing referral codes.
|
||||
app.storage.db.DeleteMatching(Invite{}, badgerhold.Where("ReferrerJellyfinID").Eq(u))
|
||||
|
||||
// 2. Generate referral invite.
|
||||
inv := baseInv
|
||||
inv.Code = GenerateInviteCode()
|
||||
inv.Created = time.Now()
|
||||
inv.ValidTill = inv.Created.Add(REFERRAL_EXPIRY_DAYS * 24 * time.Hour)
|
||||
inv.IsReferral = true
|
||||
inv.ReferrerJellyfinID = u
|
||||
app.storage.SetInvitesKey(inv.Code, inv)
|
||||
}
|
||||
}
|
||||
|
||||
// @Summary Disable referrals for the given user(s).
|
||||
// @Produce json
|
||||
// @Param EnableDisableReferralDTO body EnableDisableReferralDTO true "List of users"
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Router /users/referral [delete]
|
||||
// @Security Bearer
|
||||
// @tags Users
|
||||
func (app *appContext) DisableReferralForUsers(gc *gin.Context) {
|
||||
var req EnableDisableReferralDTO
|
||||
gc.BindJSON(&req)
|
||||
for _, u := range req.Users {
|
||||
// 1. Delete directly bound template
|
||||
app.storage.db.DeleteMatching(Invite{}, badgerhold.Where("ReferrerJellyfinID").Eq(u))
|
||||
// 2. Check for and delete profile-attached template
|
||||
user, ok := app.storage.GetEmailsKey(u)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
user.ReferralTemplateKey = ""
|
||||
app.storage.SetEmailsKey(u, user)
|
||||
}
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Send an announcement via email to a given list of users.
|
||||
// @Produce json
|
||||
// @Param announcementDTO body announcementDTO true "Announcement request object"
|
||||
@@ -833,13 +1004,15 @@ func (app *appContext) GetUsers(gc *gin.Context) {
|
||||
}
|
||||
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)
|
||||
i := 0
|
||||
for _, jfUser := range users {
|
||||
user := respUser{
|
||||
ID: jfUser.ID,
|
||||
Name: jfUser.Name,
|
||||
Admin: jfUser.Policy.IsAdministrator,
|
||||
Disabled: jfUser.Policy.IsDisabled,
|
||||
ID: jfUser.ID,
|
||||
Name: jfUser.Name,
|
||||
Admin: jfUser.Policy.IsAdministrator,
|
||||
Disabled: jfUser.Policy.IsDisabled,
|
||||
ReferralsEnabled: false,
|
||||
}
|
||||
if !jfUser.LastActivityDate.IsZero() {
|
||||
user.LastActive = jfUser.LastActivityDate.Unix()
|
||||
@@ -868,6 +1041,18 @@ func (app *appContext) GetUsers(gc *gin.Context) {
|
||||
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
|
||||
}
|
||||
}
|
||||
resp.UserList[i] = user
|
||||
i++
|
||||
}
|
||||
@@ -974,6 +1159,20 @@ func (app *appContext) ModifyEmails(gc *gin.Context) {
|
||||
|
||||
emailStore.Addr = address
|
||||
app.storage.SetEmailsKey(id, emailStore)
|
||||
|
||||
activityType := ActivityContactLinked
|
||||
if address == "" {
|
||||
activityType = ActivityContactUnlinked
|
||||
}
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
Type: activityType,
|
||||
UserID: id,
|
||||
SourceType: ActivityAdmin,
|
||||
Source: gc.GetString("jfId"),
|
||||
Value: "email",
|
||||
Time: time.Now(),
|
||||
})
|
||||
|
||||
if ombiEnabled {
|
||||
ombiUser, code, err := app.getOmbiUser(id)
|
||||
if code == 200 && err == nil {
|
||||
@@ -1010,13 +1209,13 @@ func (app *appContext) ApplySettings(gc *gin.Context) {
|
||||
if req.From == "profile" {
|
||||
// Check profile exists & isn't empty
|
||||
profile, ok := app.storage.GetProfileKey(req.Profile)
|
||||
if !ok || profile.Policy.BlockedTags == nil {
|
||||
if !ok {
|
||||
app.err.Printf("Couldn't find profile \"%s\" or profile was empty", req.Profile)
|
||||
respond(500, "Couldn't find profile", gc)
|
||||
return
|
||||
}
|
||||
if req.Homescreen {
|
||||
if profile.Configuration.GroupedFolders == nil || len(profile.Displayprefs) == 0 {
|
||||
if !profile.Homescreen {
|
||||
app.err.Printf("No homescreen saved in profile \"%s\"", req.Profile)
|
||||
respond(500, "No homescreen template available", gc)
|
||||
return
|
||||
@@ -1100,17 +1299,7 @@ func (app *appContext) ApplySettings(gc *gin.Context) {
|
||||
// newUser["userName"] = user["userName"]
|
||||
// newUser["alias"] = user["alias"]
|
||||
// newUser["emailAddress"] = user["emailAddress"]
|
||||
for k, v := range ombi {
|
||||
switch v.(type) {
|
||||
case map[string]interface{}, []interface{}:
|
||||
user[k] = v
|
||||
default:
|
||||
if v != user[k] {
|
||||
user[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
status, err = app.ombi.ModifyUser(user)
|
||||
status, err = app.applyOmbiProfile(user, ombi)
|
||||
if status != 200 || err != nil {
|
||||
errorString += fmt.Sprintf("Apply %d: %v ", status, err)
|
||||
}
|
||||
|
||||
13
api.go
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/hrfee/mediabrowser"
|
||||
"github.com/itchyny/timefmt-go"
|
||||
"github.com/lithammer/shortuuid/v3"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
@@ -157,6 +158,7 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
|
||||
}
|
||||
username = resp.UsersReset[0]
|
||||
}
|
||||
|
||||
var user mediabrowser.User
|
||||
var status int
|
||||
var err error
|
||||
@@ -170,6 +172,15 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
Type: ActivityResetPassword,
|
||||
UserID: user.ID,
|
||||
SourceType: ActivityUser,
|
||||
Source: user.ID,
|
||||
Time: time.Now(),
|
||||
})
|
||||
|
||||
prevPassword := req.PIN
|
||||
if isInternal {
|
||||
prevPassword = ""
|
||||
@@ -182,7 +193,7 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
|
||||
}
|
||||
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
||||
// Silently fail for changing ombi passwords
|
||||
if status != 200 || err != nil {
|
||||
if (status != 200 && status != 204) || err != nil {
|
||||
app.err.Printf("Failed to get user \"%s\" from jellyfin/emby (%d): %v", username, status, err)
|
||||
respondBool(200, true, gc)
|
||||
return
|
||||
|
||||
24
config.go
@@ -8,6 +8,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/hrfee/jfa-go/easyproxy"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
@@ -75,6 +76,10 @@ func (app *appContext) loadConfig() error {
|
||||
|
||||
app.MustSetValue("smtp", "hello_hostname", "localhost")
|
||||
app.MustSetValue("smtp", "cert_validation", "true")
|
||||
app.MustSetValue("smtp", "auth_type", "4")
|
||||
|
||||
app.MustSetValue("activity_log", "keep_n_records", "1000")
|
||||
app.MustSetValue("activity_log", "delete_after_days", "90")
|
||||
|
||||
sc := app.config.Section("discord").Key("start_command").MustString("start")
|
||||
app.config.Section("discord").Key("start_command").SetValue(strings.TrimPrefix(strings.TrimPrefix(sc, "/"), "!"))
|
||||
@@ -135,6 +140,22 @@ func (app *appContext) loadConfig() error {
|
||||
messagesEnabled = false
|
||||
}
|
||||
|
||||
if app.proxyEnabled = app.config.Section("advanced").Key("proxy").MustBool(false); app.proxyEnabled {
|
||||
app.proxyConfig = easyproxy.ProxyConfig{}
|
||||
app.proxyConfig.Protocol = easyproxy.HTTP
|
||||
if strings.Contains(app.config.Section("advanced").Key("proxy_protocol").MustString("http"), "socks") {
|
||||
app.proxyConfig.Protocol = easyproxy.SOCKS5
|
||||
}
|
||||
app.proxyConfig.Addr = app.config.Section("advanced").Key("proxy_address").MustString("")
|
||||
app.proxyConfig.User = app.config.Section("advanced").Key("proxy_user").MustString("")
|
||||
app.proxyConfig.Password = app.config.Section("advanced").Key("proxy_password").MustString("")
|
||||
app.proxyTransport, err = easyproxy.NewTransport(app.proxyConfig)
|
||||
if err != nil {
|
||||
app.err.Printf("Failed to initialize Proxy: %v\n", err)
|
||||
}
|
||||
app.proxyEnabled = true
|
||||
}
|
||||
|
||||
app.MustSetValue("updates", "enabled", "true")
|
||||
releaseChannel := app.config.Section("updates").Key("channel").String()
|
||||
if app.config.Section("updates").Key("enabled").MustBool(false) {
|
||||
@@ -147,6 +168,9 @@ func (app *appContext) loadConfig() error {
|
||||
v = "git"
|
||||
}
|
||||
app.updater = newUpdater(baseURL, namespace, repo, v, commit, updater)
|
||||
if app.proxyEnabled {
|
||||
app.updater.SetTransport(app.proxyTransport)
|
||||
}
|
||||
}
|
||||
if releaseChannel == "" {
|
||||
if version == "git" {
|
||||
|
||||
@@ -274,6 +274,18 @@
|
||||
"value": false,
|
||||
"advanced": true,
|
||||
"description": "Navigate directly to the above URL instead of needing the user to click \"Continue\"."
|
||||
},
|
||||
"login_appearance": {
|
||||
"name": "Login screen appearance",
|
||||
"required": false,
|
||||
"requires_restart": false,
|
||||
"type": "select",
|
||||
"options": [
|
||||
["clear", "Transparent"],
|
||||
["opaque", "Opaque"]
|
||||
],
|
||||
"value": "clear",
|
||||
"description": "Appearance of the Admin login screen."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -281,7 +293,8 @@
|
||||
"order": [],
|
||||
"meta": {
|
||||
"name": "Advanced",
|
||||
"description": "Advanced settings."
|
||||
"description": "Advanced settings.",
|
||||
"advanced": true
|
||||
},
|
||||
"settings": {
|
||||
"tls": {
|
||||
@@ -318,6 +331,212 @@
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"description": "Path to .key file. See jfa-go wiki for more info."
|
||||
},
|
||||
"auth_retry_count": {
|
||||
"name": "Initial auth retry count",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"type": "number",
|
||||
"value": 6,
|
||||
"description": "Number of times to retry initial connection to Jellyfin before failing."
|
||||
},
|
||||
"auth_retry_gap": {
|
||||
"name": "Initial auth retry gap (seconds)",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"type": "number",
|
||||
"value": 10,
|
||||
"description": "Duration in seconds to wait between connection retries."
|
||||
},
|
||||
"proxy": {
|
||||
"name": "Use Proxy",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"type": "bool",
|
||||
"value": false,
|
||||
"description": "Whether or not to use a HTTP/SOCKS5 Proxy."
|
||||
},
|
||||
"proxy_protocol": {
|
||||
"name": "Proxy Protocol",
|
||||
"depends_true": "proxy",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"type": "select",
|
||||
"options": [
|
||||
["http", "HTTP"],
|
||||
["socks", "SOCKS5"]
|
||||
],
|
||||
"value": "http",
|
||||
"description": "Protocol to use for proxy connection."
|
||||
},
|
||||
"proxy_address": {
|
||||
"name": "Proxy Address",
|
||||
"depends_true": "proxy",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"description": "Proxy address, including port."
|
||||
},
|
||||
"proxy_user": {
|
||||
"name": "Proxy Username",
|
||||
"depends_true": "proxy",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"description": "Leave blank for no Authentication."
|
||||
},
|
||||
"proxy_password": {
|
||||
"name": "Proxy Password",
|
||||
"depends_true": "proxy",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"type": "password",
|
||||
"value": "",
|
||||
"description": "Leave blank for no Authentication."
|
||||
},
|
||||
"debug_log_emails": {
|
||||
"name": "Debug Storage Logging: Emails",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"type": "select",
|
||||
"options": [
|
||||
["none", "None"],
|
||||
["all", "All Writes"],
|
||||
["deletion", "Deletion Only*"]
|
||||
],
|
||||
"value": "none",
|
||||
"description": "Extra debug logging for writes to the database. *: Deletion also includes blanking out major fields, e.g. an email address."
|
||||
},
|
||||
"debug_log_discord": {
|
||||
"name": "Debug Storage Logging: Discord",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"type": "select",
|
||||
"options": [
|
||||
["none", "None"],
|
||||
["all", "All Writes"],
|
||||
["deletion", "Deletion Only*"]
|
||||
],
|
||||
"value": "none",
|
||||
"description": "Extra debug logging for writes to the database. *: Deletion also includes blanking out major fields, e.g. an email address."
|
||||
},
|
||||
"debug_log_telegram": {
|
||||
"name": "Debug Storage Logging: Telegram",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"type": "select",
|
||||
"options": [
|
||||
["none", "None"],
|
||||
["all", "All Writes"],
|
||||
["deletion", "Deletion Only*"]
|
||||
],
|
||||
"value": "none",
|
||||
"description": "Extra debug logging for writes to the database. *: Deletion also includes blanking out major fields, e.g. an email address."
|
||||
},
|
||||
"debug_log_matrix": {
|
||||
"name": "Debug Storage Logging: Matrix",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"type": "select",
|
||||
"options": [
|
||||
["none", "None"],
|
||||
["all", "All Writes"],
|
||||
["deletion", "Deletion Only*"]
|
||||
],
|
||||
"value": "none",
|
||||
"description": "Extra debug logging for writes to the database. *: Deletion also includes blanking out major fields, e.g. an email address."
|
||||
},
|
||||
"debug_log_invites": {
|
||||
"name": "Debug Storage Logging: Invites",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"type": "select",
|
||||
"options": [
|
||||
["none", "None"],
|
||||
["all", "All Writes"],
|
||||
["deletion", "Deletion Only*"]
|
||||
],
|
||||
"value": "none",
|
||||
"description": "Extra debug logging for writes to the database. *: Deletion also includes blanking out major fields, e.g. an email address."
|
||||
},
|
||||
"debug_log_announcements": {
|
||||
"name": "Debug Storage Logging: Announcements",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"type": "select",
|
||||
"options": [
|
||||
["none", "None"],
|
||||
["all", "All Writes"],
|
||||
["deletion", "Deletion Only*"]
|
||||
],
|
||||
"value": "none",
|
||||
"description": "Extra debug logging for writes to the database. *: Deletion also includes blanking out major fields, e.g. an email address."
|
||||
},
|
||||
"debug_log_expiries": {
|
||||
"name": "Debug Storage Logging: User Expiries",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"type": "select",
|
||||
"options": [
|
||||
["none", "None"],
|
||||
["all", "All Writes"],
|
||||
["deletion", "Deletion Only*"]
|
||||
],
|
||||
"value": "none",
|
||||
"description": "Extra debug logging for writes to the database. *: Deletion also includes blanking out major fields, e.g. an email address."
|
||||
},
|
||||
"debug_log_profiles": {
|
||||
"name": "Debug Storage Logging: Profiles",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"type": "select",
|
||||
"options": [
|
||||
["none", "None"],
|
||||
["all", "All Writes"],
|
||||
["deletion", "Deletion Only*"]
|
||||
],
|
||||
"value": "none",
|
||||
"description": "Extra debug logging for writes to the database. *: Deletion also includes blanking out major fields, e.g. an email address."
|
||||
},
|
||||
"debug_log_custom_content": {
|
||||
"name": "Debug Storage Logging: Custom Message Content",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"type": "select",
|
||||
"options": [
|
||||
["none", "None"],
|
||||
["all", "All Writes"],
|
||||
["deletion", "Deletion Only*"]
|
||||
],
|
||||
"value": "none",
|
||||
"description": "Extra debug logging for writes to the database. *: Deletion also includes blanking out major fields, e.g. an email address."
|
||||
}
|
||||
}
|
||||
},
|
||||
"activity_log": {
|
||||
"order": [],
|
||||
"meta": {
|
||||
"name": "Activity Log",
|
||||
"description": "Settings for data retention of the activity log."
|
||||
},
|
||||
"settings": {
|
||||
"keep_n_records": {
|
||||
"name": "Number of records to keep",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"type": "number",
|
||||
"value": 1000,
|
||||
"description": "How many of the most recent activities to keep. Set to 0 to disable."
|
||||
},
|
||||
"delete_after_days": {
|
||||
"name": "Delete activities older than (days):",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"type": "number",
|
||||
"value": 90,
|
||||
"description": "If an activity was created this many days ago, it will be deleted. Set to 0 to disable."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -341,6 +560,7 @@
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"type": "bool",
|
||||
"depends_true": "enabled",
|
||||
"value": false,
|
||||
"description": "More reliable, but requires some setup. See jfa-go wiki for more info."
|
||||
},
|
||||
@@ -376,15 +596,15 @@
|
||||
"user_page": {
|
||||
"order": [],
|
||||
"meta": {
|
||||
"name": "User Page",
|
||||
"description": "The User Page (My Account) allows users to access and modify info directly, such as changing/adding contact methods, seeing their expiry date, or changing their password. Password resets can also be initiated from here, given a contact method or username. ",
|
||||
"name": "User Page/\"My Account\"",
|
||||
"description": "The User Page (My Account) allows users to access and modify info directly, such as changing/adding contact methods, seeing their expiry date, sending referrals or changing their password. Password resets can also be initiated from here, given a contact method or username. ",
|
||||
"depends_true": "ui|jellyfin_login"
|
||||
},
|
||||
"settings": {
|
||||
"enabled": {
|
||||
"name": "Enabled",
|
||||
"required": false,
|
||||
"requires_restart": false,
|
||||
"requires_restart": true,
|
||||
"type": "bool",
|
||||
"value": true
|
||||
},
|
||||
@@ -404,6 +624,30 @@
|
||||
"depends_true": "enabled",
|
||||
"required": "false",
|
||||
"description": "Click the edit icon next to the \"User Page\" Setting to add custom Markdown messages that will be shown to the user. Note message cards are not private, little effort is required for anyone to view them."
|
||||
},
|
||||
"show_link": {
|
||||
"name": "Show Link on Admin Login page",
|
||||
"required": false,
|
||||
"requires_restart": false,
|
||||
"type": "bool",
|
||||
"value": true,
|
||||
"description": "Whether or not to show a link to the \"My Account\" page on the admin login screen, to direct lost users."
|
||||
},
|
||||
"referrals": {
|
||||
"name": "User Referrals",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"type": "bool",
|
||||
"value": true,
|
||||
"description": "Users are given their own \"invite\" to send to others."
|
||||
},
|
||||
"referrals_note": {
|
||||
"name": "Using Referrals:",
|
||||
"type": "note",
|
||||
"value": "",
|
||||
"depends_true": "referrals",
|
||||
"required": "false",
|
||||
"description": "Create an invite with your desired settings, then either assign it to a user in the accounts tab, or to a profile in settings."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -699,6 +943,22 @@
|
||||
"type": "bool",
|
||||
"value": true,
|
||||
"description": "Warning, disabling this makes you much more vulnerable to man-in-the-middle attacks"
|
||||
},
|
||||
"auth_type": {
|
||||
"name": "Authentication type",
|
||||
"required": false,
|
||||
"requires_restart": false,
|
||||
"advanced": false,
|
||||
"type": "select",
|
||||
"options": [
|
||||
["0", "Plain"],
|
||||
["1", "Login"],
|
||||
["2", "CRAM-MD5"],
|
||||
["3", "None"],
|
||||
["4", "Auto"]
|
||||
],
|
||||
"value": 4,
|
||||
"description": "SMTP authentication method"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -995,6 +1255,14 @@
|
||||
"value": true,
|
||||
"description": "Enable to store provided email addresses, monitor Jellyfin directory for pw-resets, and send reset pins"
|
||||
},
|
||||
"pwr_note": {
|
||||
"name": "Setup:",
|
||||
"type": "note",
|
||||
"value": "",
|
||||
"depends_true": "enabled",
|
||||
"required": "false",
|
||||
"description": "There are multiple ways password resets can be set up. See the <a href=\"https://wiki.jfa-go.com/docs/pwr/\" target=\"_blank\">wiki page</a> for more information."
|
||||
},
|
||||
"watch_directory": {
|
||||
"name": "Jellyfin directory",
|
||||
"required": false,
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
--border-width-4: 5px;
|
||||
--border-width-8: 8px;
|
||||
font-family: 'Hanken Grotesk', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
|
||||
--bg-light: #fff;
|
||||
--bg-dark: #101010;
|
||||
}
|
||||
|
||||
.light {
|
||||
@@ -26,11 +29,11 @@
|
||||
}
|
||||
|
||||
.dark body {
|
||||
background-color: #101010;
|
||||
background-color: var(--bg-dark);
|
||||
}
|
||||
|
||||
html:not(.dark) body {
|
||||
background-color: #fff;
|
||||
background-color: var(--bg-light);
|
||||
}
|
||||
|
||||
.dark select, .dark option, .dark input {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
font-family: 'Hanken Grotesk';
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
src: url('../fonts/hanken-grotesk-v8-cyrillic-ext_latin_vietnamese-500italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ *
|
||||
src: url('../fonts/hanken-grotesk-v8-cyrillic-ext_latin_vietnamese-500italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
|
||||
/* hanken-grotesk-700 - cyrillic-ext_latin_vietnamese */
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
color: rgba(0, 0, 0, 0) !important;
|
||||
}
|
||||
|
||||
.loader.rel {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.loader .dot {
|
||||
--diameter: 0.5rem;
|
||||
--radius: calc(var(--diameter) / 2);
|
||||
@@ -15,6 +19,12 @@
|
||||
left: calc(50% - var(--radius));
|
||||
animation: osc 1s cubic-bezier(.72,.16,.31,.97) infinite;
|
||||
}
|
||||
|
||||
.loader.rel .dot {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
.loader.loader-sm .dot {
|
||||
--deviation: 10%;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,24 @@
|
||||
background-color: rgba(0,0,0,40%);
|
||||
}
|
||||
|
||||
.wall {
|
||||
position: fixed;
|
||||
z-index: 11;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
html.dark .wall {
|
||||
background-color: var(--bg-dark);
|
||||
}
|
||||
|
||||
html:not(.dark) .wall {
|
||||
background-color: var(--bg-light);
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
float: right;
|
||||
color: #aaa;
|
||||
|
||||
86
daemon.go
@@ -1,6 +1,12 @@
|
||||
package main
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/dgraph-io/badger/v3"
|
||||
"github.com/hrfee/mediabrowser"
|
||||
"github.com/timshannon/badgerhold/v4"
|
||||
)
|
||||
|
||||
// clearEmails removes stored emails for users which no longer exist.
|
||||
// meant to be called with other such housekeeping functions, so assumes
|
||||
@@ -9,11 +15,14 @@ func (app *appContext) clearEmails() {
|
||||
app.debug.Println("Housekeeping: removing unused email addresses")
|
||||
emails := app.storage.GetEmails()
|
||||
for _, email := range emails {
|
||||
_, status, err := app.jf.UserByID(email.JellyfinID, false)
|
||||
if status == 200 && err != nil {
|
||||
_, _, err := app.jf.UserByID(email.JellyfinID, false)
|
||||
// Make sure the user doesn't exist, and no other error has occured
|
||||
switch err.(type) {
|
||||
case mediabrowser.ErrUserNotFound:
|
||||
app.storage.DeleteEmailsKey(email.JellyfinID)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
app.storage.DeleteEmailsKey(email.JellyfinID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,11 +31,14 @@ func (app *appContext) clearDiscord() {
|
||||
app.debug.Println("Housekeeping: removing unused Discord IDs")
|
||||
discordUsers := app.storage.GetDiscord()
|
||||
for _, discordUser := range discordUsers {
|
||||
_, status, err := app.jf.UserByID(discordUser.JellyfinID, false)
|
||||
if status == 200 && err != nil {
|
||||
_, _, err := app.jf.UserByID(discordUser.JellyfinID, false)
|
||||
// Make sure the user doesn't exist, and no other error has occured
|
||||
switch err.(type) {
|
||||
case mediabrowser.ErrUserNotFound:
|
||||
app.storage.DeleteDiscordKey(discordUser.JellyfinID)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
app.storage.DeleteDiscordKey(discordUser.JellyfinID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,11 +47,14 @@ func (app *appContext) clearMatrix() {
|
||||
app.debug.Println("Housekeeping: removing unused Matrix IDs")
|
||||
matrixUsers := app.storage.GetMatrix()
|
||||
for _, matrixUser := range matrixUsers {
|
||||
_, status, err := app.jf.UserByID(matrixUser.JellyfinID, false)
|
||||
if status == 200 && err != nil {
|
||||
_, _, err := app.jf.UserByID(matrixUser.JellyfinID, false)
|
||||
// Make sure the user doesn't exist, and no other error has occured
|
||||
switch err.(type) {
|
||||
case mediabrowser.ErrUserNotFound:
|
||||
app.storage.DeleteMatrixKey(matrixUser.JellyfinID)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
app.storage.DeleteMatrixKey(matrixUser.JellyfinID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,11 +63,45 @@ func (app *appContext) clearTelegram() {
|
||||
app.debug.Println("Housekeeping: removing unused Telegram IDs")
|
||||
telegramUsers := app.storage.GetTelegram()
|
||||
for _, telegramUser := range telegramUsers {
|
||||
_, status, err := app.jf.UserByID(telegramUser.JellyfinID, false)
|
||||
if status == 200 && err != nil {
|
||||
_, _, err := app.jf.UserByID(telegramUser.JellyfinID, false)
|
||||
// Make sure the user doesn't exist, and no other error has occured
|
||||
switch err.(type) {
|
||||
case mediabrowser.ErrUserNotFound:
|
||||
app.storage.DeleteTelegramKey(telegramUser.JellyfinID)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
app.storage.DeleteTelegramKey(telegramUser.JellyfinID)
|
||||
}
|
||||
}
|
||||
|
||||
func (app *appContext) clearActivities() {
|
||||
app.debug.Println("Husekeeping: Cleaning up Activity log...")
|
||||
keepCount := app.config.Section("activity_log").Key("keep_n_records").MustInt(1000)
|
||||
maxAgeDays := app.config.Section("activity_log").Key("delete_after_days").MustInt(90)
|
||||
minAge := time.Now().AddDate(0, 0, -maxAgeDays)
|
||||
err := error(nil)
|
||||
errorSource := 0
|
||||
if maxAgeDays != 0 {
|
||||
err = app.storage.db.DeleteMatching(&Activity{}, badgerhold.Where("Time").Lt(minAge))
|
||||
}
|
||||
if err == nil && keepCount != 0 {
|
||||
// app.debug.Printf("Keeping %d records", keepCount)
|
||||
err = app.storage.db.DeleteMatching(&Activity{}, (&badgerhold.Query{}).Reverse().SortBy("Time").Skip(keepCount))
|
||||
if err != nil {
|
||||
errorSource = 1
|
||||
}
|
||||
}
|
||||
if err == badger.ErrTxnTooBig {
|
||||
app.debug.Printf("Activities: Delete txn was too big, doing it manually.")
|
||||
list := []Activity{}
|
||||
if errorSource == 0 {
|
||||
app.storage.db.Find(&list, badgerhold.Where("Time").Lt(minAge))
|
||||
} else {
|
||||
app.storage.db.Find(&list, (&badgerhold.Query{}).Reverse().SortBy("Time").Skip(keepCount))
|
||||
}
|
||||
for _, record := range list {
|
||||
app.storage.DeleteActivityKey(record.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,10 +124,13 @@ func newInviteDaemon(interval time.Duration, app *appContext) *housekeepingDaemo
|
||||
period: interval,
|
||||
app: app,
|
||||
}
|
||||
daemon.jobs = []func(app *appContext){func(app *appContext) {
|
||||
app.debug.Println("Housekeeping: Checking for expired invites")
|
||||
app.checkInvites()
|
||||
}}
|
||||
daemon.jobs = []func(app *appContext){
|
||||
func(app *appContext) {
|
||||
app.debug.Println("Housekeeping: Checking for expired invites")
|
||||
app.checkInvites()
|
||||
},
|
||||
func(app *appContext) { app.clearActivities() },
|
||||
}
|
||||
|
||||
clearEmail := app.config.Section("email").Key("require_unique").MustBool(false)
|
||||
clearDiscord := app.config.Section("discord").Key("require_unique").MustBool(false)
|
||||
|
||||
206
discord.go
@@ -24,6 +24,7 @@ type DiscordDaemon struct {
|
||||
app *appContext
|
||||
commandHandlers map[string]func(s *dg.Session, i *dg.InteractionCreate, lang string)
|
||||
commandIDs []string
|
||||
commandDescriptions []*dg.ApplicationCommand
|
||||
}
|
||||
|
||||
func newDiscordDaemon(app *appContext) (*DiscordDaemon, error) {
|
||||
@@ -50,6 +51,7 @@ func newDiscordDaemon(app *appContext) (*DiscordDaemon, error) {
|
||||
dd.commandHandlers[app.config.Section("discord").Key("start_command").MustString("start")] = dd.cmdStart
|
||||
dd.commandHandlers["lang"] = dd.cmdLang
|
||||
dd.commandHandlers["pin"] = dd.cmdPIN
|
||||
dd.commandHandlers["inv"] = dd.cmdInvite
|
||||
for _, user := range app.storage.GetDiscord() {
|
||||
dd.users[user.ID] = user
|
||||
}
|
||||
@@ -127,6 +129,7 @@ func (d *DiscordDaemon) run() {
|
||||
d.inviteChannelName = invChannel
|
||||
}
|
||||
}
|
||||
err = d.bot.UpdateGameStatus(0, "/"+d.app.config.Section("discord").Key("start_command").MustString("start"))
|
||||
defer d.deregisterCommands()
|
||||
defer d.bot.Close()
|
||||
|
||||
@@ -220,9 +223,7 @@ func (d *DiscordDaemon) NewTempInvite(ageSeconds, maxUses int) (inviteURL, iconU
|
||||
d.app.err.Printf("Discord: Failed to get guild: %v", err)
|
||||
return
|
||||
}
|
||||
// FIXME: Fix CSS, and handle no icon
|
||||
iconURL = guild.IconURL("256")
|
||||
fmt.Println("GOT ICON", iconURL)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -309,7 +310,7 @@ func (d *DiscordDaemon) Shutdown() {
|
||||
}
|
||||
|
||||
func (d *DiscordDaemon) registerCommands() {
|
||||
commands := []*dg.ApplicationCommand{
|
||||
d.commandDescriptions = []*dg.ApplicationCommand{
|
||||
{
|
||||
Name: d.app.config.Section("discord").Key("start_command").MustString("start"),
|
||||
Description: "Start the Discord linking process. The bot will send further instructions.",
|
||||
@@ -339,26 +340,73 @@ func (d *DiscordDaemon) registerCommands() {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "inv",
|
||||
Description: "Send an invite to a discord user (admin only).",
|
||||
Options: []*dg.ApplicationCommandOption{
|
||||
{
|
||||
Type: dg.ApplicationCommandOptionUser,
|
||||
Name: "user",
|
||||
Description: "User to Invite.",
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Type: dg.ApplicationCommandOptionInteger,
|
||||
Name: "expiry",
|
||||
Description: "Time in minutes before expiration.",
|
||||
Required: false,
|
||||
},
|
||||
/* Label should be automatically set to something like "Discord invite for @username"
|
||||
{
|
||||
Type: dg.ApplicationCommandOptionString,
|
||||
Name: "label",
|
||||
Description: "Label given to this invite (shown on the Admin page)",
|
||||
Required: false,
|
||||
}, */
|
||||
{
|
||||
Type: dg.ApplicationCommandOptionString,
|
||||
Name: "user_label",
|
||||
Description: "Label given to users created with this invite.",
|
||||
Required: false,
|
||||
},
|
||||
{
|
||||
Type: dg.ApplicationCommandOptionString,
|
||||
Name: "profile",
|
||||
Description: "Profile to apply to the created user.",
|
||||
Required: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
commands[1].Options[0].Choices = make([]*dg.ApplicationCommandOptionChoice, len(d.app.storage.lang.Telegram))
|
||||
d.commandDescriptions[1].Options[0].Choices = make([]*dg.ApplicationCommandOptionChoice, len(d.app.storage.lang.Telegram))
|
||||
i := 0
|
||||
for code := range d.app.storage.lang.Telegram {
|
||||
d.app.debug.Printf("Registering choice \"%s\":\"%s\"\n", d.app.storage.lang.Telegram[code].Meta.Name, code)
|
||||
commands[1].Options[0].Choices[i] = &dg.ApplicationCommandOptionChoice{
|
||||
d.app.debug.Printf("Discord: registering lang choice \"%s\":\"%s\"\n", d.app.storage.lang.Telegram[code].Meta.Name, code)
|
||||
d.commandDescriptions[1].Options[0].Choices[i] = &dg.ApplicationCommandOptionChoice{
|
||||
Name: d.app.storage.lang.Telegram[code].Meta.Name,
|
||||
Value: code,
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
profiles := d.app.storage.GetProfiles()
|
||||
d.commandDescriptions[3].Options[3].Choices = make([]*dg.ApplicationCommandOptionChoice, len(profiles))
|
||||
for i, profile := range profiles {
|
||||
d.app.debug.Printf("Discord: registering profile choice \"%s\"", profile.Name)
|
||||
d.commandDescriptions[3].Options[3].Choices[i] = &dg.ApplicationCommandOptionChoice{
|
||||
Name: profile.Name,
|
||||
Value: profile.Name,
|
||||
}
|
||||
}
|
||||
|
||||
// d.deregisterCommands()
|
||||
|
||||
d.commandIDs = make([]string, len(commands))
|
||||
d.commandIDs = make([]string, len(d.commandDescriptions))
|
||||
// cCommands, err := d.bot.ApplicationCommandBulkOverwrite(d.bot.State.User.ID, d.guildID, commands)
|
||||
// if err != nil {
|
||||
// d.app.err.Printf("Discord: Cannot create commands: %v", err)
|
||||
// }
|
||||
for i, cmd := range commands {
|
||||
for i, cmd := range d.commandDescriptions {
|
||||
command, err := d.bot.ApplicationCommandCreate(d.bot.State.User.ID, d.guildID, cmd)
|
||||
if err != nil {
|
||||
d.app.err.Printf("Discord: Cannot create command \"%s\": %v", cmd.Name, err)
|
||||
@@ -376,12 +424,32 @@ func (d *DiscordDaemon) deregisterCommands() {
|
||||
return
|
||||
}
|
||||
for _, cmd := range existingCommands {
|
||||
if err := d.bot.ApplicationCommandDelete(d.bot.State.User.ID, "", cmd.ID); err != nil {
|
||||
d.app.err.Printf("Failed to deregister command: %v", err)
|
||||
if err := d.bot.ApplicationCommandDelete(d.bot.State.User.ID, d.guildID, cmd.ID); err != nil {
|
||||
d.app.err.Printf("Discord: Failed to deregister command: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateCommands updates commands which have defined lists of options, to be used when changes occur.
|
||||
func (d *DiscordDaemon) UpdateCommands() {
|
||||
// Reload Profile List
|
||||
profiles := d.app.storage.GetProfiles()
|
||||
d.commandDescriptions[3].Options[3].Choices = make([]*dg.ApplicationCommandOptionChoice, len(profiles))
|
||||
for i, profile := range profiles {
|
||||
d.app.debug.Printf("Discord: registering profile choice \"%s\"", profile.Name)
|
||||
d.commandDescriptions[3].Options[3].Choices[i] = &dg.ApplicationCommandOptionChoice{
|
||||
Name: profile.Name,
|
||||
Value: profile.Name,
|
||||
}
|
||||
}
|
||||
cmd, err := d.bot.ApplicationCommandEdit(d.bot.State.User.ID, d.guildID, d.commandIDs[3], d.commandDescriptions[3])
|
||||
if err != nil {
|
||||
d.app.err.Printf("Discord: Failed to update profile list: %v\n", err)
|
||||
} else {
|
||||
d.commandIDs[3] = cmd.ID
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DiscordDaemon) commandHandler(s *dg.Session, i *dg.InteractionCreate) {
|
||||
if h, ok := d.commandHandlers[i.ApplicationCommandData().Name]; ok {
|
||||
if i.GuildID != "" && d.channelName != "" {
|
||||
@@ -504,6 +572,124 @@ func (d *DiscordDaemon) cmdLang(s *dg.Session, i *dg.InteractionCreate, lang str
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang string) {
|
||||
channel, err := s.UserChannelCreate(i.Interaction.Member.User.ID)
|
||||
if err != nil {
|
||||
d.app.err.Printf("Discord: Failed to create private channel with \"%s\": %v", i.Interaction.Member.User.Username, err)
|
||||
return
|
||||
}
|
||||
requester := d.MustGetUser(channel.ID, i.Interaction.Member.User.ID, i.Interaction.Member.User.Discriminator, i.Interaction.Member.User.Username)
|
||||
d.users[i.Interaction.Member.User.ID] = requester
|
||||
recipient := i.ApplicationCommandData().Options[0].UserValue(s)
|
||||
// d.app.debug.Println(invuser)
|
||||
//label := i.ApplicationCommandData().Options[2].StringValue()
|
||||
//profile := i.ApplicationCommandData().Options[3].StringValue()
|
||||
//mins, err := strconv.Atoi(i.ApplicationCommandData().Options[1].StringValue())
|
||||
//if mins > 0 {
|
||||
// expmin = mins
|
||||
//}
|
||||
// Check whether requestor is linked to the admin account
|
||||
requesterEmail, ok := d.app.storage.GetEmailsKey(requester.JellyfinID)
|
||||
if !ok {
|
||||
d.app.err.Printf("Failed to verify admin")
|
||||
}
|
||||
if !requesterEmail.Admin {
|
||||
d.app.err.Printf("User is not admin")
|
||||
//add response message
|
||||
return
|
||||
}
|
||||
|
||||
var expiryMinutes int64 = 30
|
||||
userLabel := ""
|
||||
profileName := ""
|
||||
|
||||
for i, opt := range i.ApplicationCommandData().Options {
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
switch opt.Name {
|
||||
case "expiry":
|
||||
expiryMinutes = opt.IntValue()
|
||||
case "user_label":
|
||||
userLabel = opt.StringValue()
|
||||
case "profile":
|
||||
profileName = opt.StringValue()
|
||||
}
|
||||
}
|
||||
|
||||
currentTime := time.Now()
|
||||
|
||||
validTill := currentTime.Add(time.Minute * time.Duration(expiryMinutes))
|
||||
|
||||
invite := Invite{
|
||||
Code: GenerateInviteCode(),
|
||||
Created: currentTime,
|
||||
RemainingUses: 1,
|
||||
UserExpiry: false,
|
||||
ValidTill: validTill,
|
||||
UserLabel: userLabel,
|
||||
Profile: "Default",
|
||||
Label: fmt.Sprintf("Discord: %s", RenderDiscordUsername(recipient)),
|
||||
}
|
||||
if profileName != "" {
|
||||
if _, ok := d.app.storage.GetProfileKey(profileName); ok {
|
||||
invite.Profile = profileName
|
||||
}
|
||||
}
|
||||
|
||||
if recipient != nil && d.app.config.Section("invite_emails").Key("enabled").MustBool(false) {
|
||||
d.app.debug.Printf("%s: Sending invite message", invite.Code)
|
||||
invname, err := d.bot.GuildMember(d.guildID, recipient.ID)
|
||||
invite.SendTo = invname.User.Username
|
||||
msg, err := d.app.email.constructInvite(invite.Code, invite, d.app, false)
|
||||
if err != nil {
|
||||
invite.SendTo = fmt.Sprintf("Failed to send to %s", RenderDiscordUsername(recipient))
|
||||
d.app.err.Printf("%s: Failed to construct invite message: %v", invite.Code, err)
|
||||
err := s.InteractionRespond(i.Interaction, &dg.InteractionResponse{
|
||||
Type: dg.InteractionResponseChannelMessageWithSource,
|
||||
Data: &dg.InteractionResponseData{
|
||||
Content: d.app.storage.lang.Telegram[lang].Strings.get("sentInviteFailure"),
|
||||
Flags: 64, // Ephemeral
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
d.app.err.Printf("Discord: Failed to send message to \"%s\": %v", RenderDiscordUsername(requester), err)
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
err = d.app.discord.SendDM(msg, recipient.ID)
|
||||
if err != nil {
|
||||
invite.SendTo = fmt.Sprintf("Failed to send to %s", RenderDiscordUsername(recipient))
|
||||
d.app.err.Printf("%s: %s: %v", invite.Code, invite.SendTo, err)
|
||||
err := s.InteractionRespond(i.Interaction, &dg.InteractionResponse{
|
||||
Type: dg.InteractionResponseChannelMessageWithSource,
|
||||
Data: &dg.InteractionResponseData{
|
||||
Content: d.app.storage.lang.Telegram[lang].Strings.get("sentInviteFailure"),
|
||||
Flags: 64, // Ephemeral
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
d.app.err.Printf("Discord: Failed to send message to \"%s\": %v", RenderDiscordUsername(requester), err)
|
||||
}
|
||||
} else {
|
||||
d.app.info.Printf("%s: Sent invite email to \"%s\"", invite.Code, RenderDiscordUsername(recipient))
|
||||
err := s.InteractionRespond(i.Interaction, &dg.InteractionResponse{
|
||||
Type: dg.InteractionResponseChannelMessageWithSource,
|
||||
Data: &dg.InteractionResponseData{
|
||||
Content: d.app.storage.lang.Telegram[lang].Strings.get("sentInvite"),
|
||||
Flags: 64, // Ephemeral
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
d.app.err.Printf("Discord: Failed to send message to \"%s\": %v", RenderDiscordUsername(requester), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//if profile != "" {
|
||||
d.app.storage.SetInvitesKey(invite.Code, invite)
|
||||
}
|
||||
|
||||
func (d *DiscordDaemon) messageHandler(s *dg.Session, m *dg.MessageCreate) {
|
||||
if m.GuildID != "" && d.channelName != "" {
|
||||
if d.channelID == "" {
|
||||
|
||||
83
easyproxy/easyproxy.go
Normal file
@@ -0,0 +1,83 @@
|
||||
// Package easyproxy provides a method to quickly create a http.Transport or net.Conn using given proxy details (SOCKS5 or HTTP).
|
||||
package easyproxy
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/magisterquis/connectproxy"
|
||||
"golang.org/x/net/proxy"
|
||||
)
|
||||
|
||||
type Protocol int
|
||||
|
||||
const (
|
||||
SOCKS5 Protocol = iota // SOCKS5
|
||||
HTTP // HTTP
|
||||
)
|
||||
|
||||
type ProxyConfig struct {
|
||||
Protocol Protocol
|
||||
Addr string
|
||||
User string
|
||||
Password string
|
||||
}
|
||||
|
||||
// NewTransport returns a http.Transport using the given proxy details. Leave user/pass blank if not needed.
|
||||
func NewTransport(c ProxyConfig) (*http.Transport, error) {
|
||||
t := &http.Transport{}
|
||||
if c.Protocol == HTTP {
|
||||
u := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: c.Addr,
|
||||
}
|
||||
if c.User != "" && c.Password != "" {
|
||||
u.User = url.UserPassword(c.User, c.Password)
|
||||
}
|
||||
t.Proxy = http.ProxyURL(u)
|
||||
return t, nil
|
||||
}
|
||||
var auth *proxy.Auth = nil
|
||||
if c.User != "" && c.Password != "" {
|
||||
auth = &proxy.Auth{User: c.User, Password: c.Password}
|
||||
}
|
||||
dialer, err := proxy.SOCKS5("tcp", c.Addr, auth, proxy.Direct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.Dial = dialer.Dial
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// NewConn returns a tls.Conn to "addr" using the given proxy details. Leave user/pass blank if not needed.
|
||||
func NewConn(c ProxyConfig, addr string, tlsConf *tls.Config) (*tls.Conn, error) {
|
||||
var proxyDialer proxy.Dialer
|
||||
var err error
|
||||
if c.Protocol == SOCKS5 {
|
||||
var auth *proxy.Auth = nil
|
||||
if c.User != "" && c.Password != "" {
|
||||
auth = &proxy.Auth{User: c.User, Password: c.Password}
|
||||
}
|
||||
proxyDialer, err = proxy.SOCKS5("tcp", c.Addr, auth, proxy.Direct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
u := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: c.Addr,
|
||||
}
|
||||
if c.User != "" && c.Password != "" {
|
||||
u.User = url.UserPassword(c.User, c.Password)
|
||||
}
|
||||
proxyDialer, err = connectproxy.New(u, proxy.Direct)
|
||||
}
|
||||
|
||||
dialer, err := proxyDialer.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn := tls.Client(dialer, tlsConf)
|
||||
return conn, nil
|
||||
}
|
||||
7
easyproxy/go.mod
Normal file
@@ -0,0 +1,7 @@
|
||||
module github.com/hrfee/jfa-go/easyproxy
|
||||
|
||||
go 1.20
|
||||
|
||||
require golang.org/x/net v0.15.0
|
||||
|
||||
require github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b // indirect
|
||||
4
easyproxy/go.sum
Normal file
@@ -0,0 +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.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
19
email.go
@@ -19,6 +19,7 @@ import (
|
||||
|
||||
"github.com/gomarkdown/markdown"
|
||||
"github.com/gomarkdown/markdown/html"
|
||||
"github.com/hrfee/jfa-go/easyproxy"
|
||||
"github.com/hrfee/mediabrowser"
|
||||
"github.com/itchyny/timefmt-go"
|
||||
"github.com/mailgun/mailgun-go/v4"
|
||||
@@ -87,7 +88,12 @@ func NewEmailer(app *appContext) *Emailer {
|
||||
if username == "" && password != "" {
|
||||
username = emailer.fromAddr
|
||||
}
|
||||
err := emailer.NewSMTP(app.config.Section("smtp").Key("server").String(), app.config.Section("smtp").Key("port").MustInt(465), username, password, sslTLS, app.config.Section("smtp").Key("ssl_cert").MustString(""), app.config.Section("smtp").Key("hello_hostname").String(), app.config.Section("smtp").Key("cert_validation").MustBool(true))
|
||||
var proxyConf *easyproxy.ProxyConfig = nil
|
||||
if app.proxyEnabled {
|
||||
proxyConf = &app.proxyConfig
|
||||
}
|
||||
authType := sMail.AuthType(app.config.Section("smtp").Key("auth_type").MustInt(4))
|
||||
err := emailer.NewSMTP(app.config.Section("smtp").Key("server").String(), app.config.Section("smtp").Key("port").MustInt(465), username, password, sslTLS, app.config.Section("smtp").Key("ssl_cert").MustString(""), app.config.Section("smtp").Key("hello_hostname").String(), app.config.Section("smtp").Key("cert_validation").MustBool(true), authType, proxyConf)
|
||||
if err != nil {
|
||||
app.err.Printf("Error while initiating SMTP mailer: %v", err)
|
||||
}
|
||||
@@ -113,7 +119,7 @@ type SMTP struct {
|
||||
}
|
||||
|
||||
// NewSMTP returns an SMTP emailClient.
|
||||
func (emailer *Emailer) NewSMTP(server string, port int, username, password string, sslTLS bool, certPath string, helloHostname string, validateCertificate bool) (err error) {
|
||||
func (emailer *Emailer) NewSMTP(server string, port int, username, password string, sslTLS bool, certPath string, helloHostname string, validateCertificate bool, authType sMail.AuthType, proxy *easyproxy.ProxyConfig) (err error) {
|
||||
sender := &SMTP{}
|
||||
sender.Client = sMail.NewSMTPClient()
|
||||
if sslTLS {
|
||||
@@ -122,7 +128,7 @@ func (emailer *Emailer) NewSMTP(server string, port int, username, password stri
|
||||
sender.Client.Encryption = sMail.EncryptionSTARTTLS
|
||||
}
|
||||
if username != "" || password != "" {
|
||||
sender.Client.Authentication = sMail.AuthLogin
|
||||
sender.Client.Authentication = authType
|
||||
sender.Client.Username = username
|
||||
sender.Client.Password = password
|
||||
}
|
||||
@@ -131,12 +137,16 @@ func (emailer *Emailer) NewSMTP(server string, port int, username, password stri
|
||||
sender.Client.Host = server
|
||||
sender.Client.Port = port
|
||||
sender.Client.KeepAlive = false
|
||||
|
||||
// x509.SystemCertPool is unavailable on windows
|
||||
if PLATFORM == "windows" {
|
||||
sender.Client.TLSConfig = &tls.Config{
|
||||
InsecureSkipVerify: !validateCertificate,
|
||||
ServerName: server,
|
||||
}
|
||||
if proxy != nil {
|
||||
sender.Client.CustomConn, err = easyproxy.NewConn(*proxy, fmt.Sprintf("%s:%d", server, port), sender.Client.TLSConfig)
|
||||
}
|
||||
emailer.sender = sender
|
||||
return
|
||||
}
|
||||
@@ -156,6 +166,9 @@ func (emailer *Emailer) NewSMTP(server string, port int, username, password stri
|
||||
ServerName: server,
|
||||
RootCAs: rootCAs,
|
||||
}
|
||||
if proxy != nil {
|
||||
sender.Client.CustomConn, err = easyproxy.NewConn(*proxy, fmt.Sprintf("%s:%d", server, port), sender.Client.TLSConfig)
|
||||
}
|
||||
emailer.sender = sender
|
||||
return
|
||||
}
|
||||
|
||||
60
go.mod
@@ -14,6 +14,8 @@ replace github.com/hrfee/jfa-go/linecache => ./linecache
|
||||
|
||||
replace github.com/hrfee/jfa-go/api => ./api
|
||||
|
||||
replace github.com/hrfee/jfa-go/easyproxy => ./easyproxy
|
||||
|
||||
require (
|
||||
github.com/bwmarrin/discordgo v0.27.1
|
||||
github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
||||
@@ -26,34 +28,35 @@ require (
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/gomarkdown/markdown v0.0.0-20230322041520-c84983bdbf2a
|
||||
github.com/hrfee/jfa-go/common v0.0.0-20230421170108-d800b97f69b6
|
||||
github.com/hrfee/jfa-go/docs v0.0.0-20230421170108-d800b97f69b6
|
||||
github.com/hrfee/jfa-go/linecache v0.0.0-20230421170108-d800b97f69b6
|
||||
github.com/hrfee/jfa-go/logger v0.0.0-20230421170108-d800b97f69b6
|
||||
github.com/hrfee/jfa-go/ombi v0.0.0-20230421170108-d800b97f69b6
|
||||
github.com/hrfee/mediabrowser v0.3.8
|
||||
github.com/hrfee/jfa-go/common v0.0.0-20230626224816-f72960635dc3
|
||||
github.com/hrfee/jfa-go/docs v0.0.0-20230626224816-f72960635dc3
|
||||
github.com/hrfee/jfa-go/linecache v0.0.0-20230626224816-f72960635dc3
|
||||
github.com/hrfee/jfa-go/logger v0.0.0-20230626224816-f72960635dc3
|
||||
github.com/hrfee/jfa-go/ombi v0.0.0-20230626224816-f72960635dc3
|
||||
github.com/hrfee/mediabrowser v0.3.12
|
||||
github.com/itchyny/timefmt-go v0.1.5
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7
|
||||
github.com/mailgun/mailgun-go/v4 v4.9.0
|
||||
github.com/mailgun/mailgun-go/v4 v4.9.1
|
||||
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/timshannon/badgerhold/v4 v4.0.2
|
||||
github.com/writeas/go-strip-markdown v2.0.1+incompatible
|
||||
github.com/xhit/go-simple-mail/v2 v2.13.0
|
||||
github.com/xhit/go-simple-mail/v2 v2.16.0
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
maunium.net/go/mautrix v0.15.2
|
||||
maunium.net/go/mautrix v0.15.3
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/bytedance/sonic v1.9.1 // indirect
|
||||
github.com/bytedance/sonic v1.9.2 // indirect
|
||||
github.com/cespare/xxhash v1.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/dgraph-io/badger/v3 v3.2103.1 // indirect
|
||||
github.com/dgraph-io/ristretto v0.1.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/dgraph-io/badger/v3 v3.2103.5 // indirect
|
||||
github.com/dgraph-io/ristretto v0.1.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201 // indirect
|
||||
github.com/getlantern/errors v1.0.3 // indirect
|
||||
@@ -76,19 +79,21 @@ require (
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/golang/glog v0.0.0-20210429001901-424d2337a529 // indirect
|
||||
github.com/golang/glog v1.1.1 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/flatbuffers v2.0.0+incompatible // indirect
|
||||
github.com/google/flatbuffers v23.5.26+incompatible // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/hrfee/jfa-go/easyproxy v0.0.0-00010101000000-000000000000 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.13.1 // indirect
|
||||
github.com/klauspost/compress v1.16.6 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
@@ -104,11 +109,10 @@ require (
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/timshannon/badgerhold/v4 v4.0.2 // indirect
|
||||
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/otel v1.16.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.16.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.16.0 // indirect
|
||||
@@ -116,14 +120,14 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.24.0 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.9.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
||||
golang.org/x/image v0.7.0 // indirect
|
||||
golang.org/x/net v0.10.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/tools v0.9.3 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
golang.org/x/crypto v0.13.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
|
||||
golang.org/x/image v0.8.0 // indirect
|
||||
golang.org/x/net v0.15.0 // indirect
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/tools v0.10.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
maunium.net/go/maulogger/v2 v2.4.1 // indirect
|
||||
)
|
||||
|
||||
99
go.sum
@@ -3,6 +3,7 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
@@ -14,13 +15,14 @@ github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZx
|
||||
github.com/bwmarrin/discordgo v0.27.1 h1:ib9AIc/dom1E/fSIulrBwnez0CToJE113ZGt4HoliGY=
|
||||
github.com/bwmarrin/discordgo v0.27.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/bytedance/sonic v1.9.2 h1:GDaNjuWSGu09guE9Oql0MSTNhNCLlWwO8y/xM5BzcbM=
|
||||
github.com/bytedance/sonic v1.9.2/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
@@ -36,13 +38,17 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/v3 v3.2103.1 h1:zaX53IRg7ycxVlkd5pYdCeFp1FynD6qBGQoQql3R3Hk=
|
||||
github.com/dgraph-io/badger/v3 v3.2103.1/go.mod h1:dULbq6ehJ5K0cGW/1TQ9iSfUk0gbSiToDWmWmTsJ53E=
|
||||
github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI=
|
||||
github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg=
|
||||
github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw=
|
||||
github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug=
|
||||
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
|
||||
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
@@ -164,8 +170,9 @@ github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzq
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/glog v0.0.0-20210429001901-424d2337a529 h1:2voWjNECnrZRbfwXxHB1/j8wa6xdKn85B5NzgVL/pTU=
|
||||
github.com/golang/glog v0.0.0-20210429001901-424d2337a529/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/glog v1.1.1 h1:jxpi2eWoU84wbX9iIEyAeeoac3FLuifZpY9tcNUD9kw=
|
||||
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=
|
||||
@@ -183,16 +190,19 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
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.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gomarkdown/markdown v0.0.0-20230322041520-c84983bdbf2a h1:AWZzzFrqyjYlRloN6edwTLTUbKxf5flLXNuTBDm3Ews=
|
||||
github.com/gomarkdown/markdown v0.0.0-20230322041520-c84983bdbf2a/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
github.com/google/flatbuffers v1.12.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/flatbuffers v2.0.0+incompatible h1:dicJ2oXwypfwUGnB2/TYWYEKiuk9eYQlQO/AnOHl5mI=
|
||||
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/flatbuffers v2.0.0+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/flatbuffers v23.5.26+incompatible h1:M9dgRyhJemaM4Sw8+66GHBu8ioaQmyPLg1b8VwK5WJg=
|
||||
github.com/google/flatbuffers v23.5.26+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=
|
||||
@@ -214,8 +224,8 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/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.8 h1:y0iBCb6jE3QKcsiCJSYva2fFPHRn4UA+sGRzoPuJ/Dk=
|
||||
github.com/hrfee/mediabrowser v0.3.8/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U=
|
||||
github.com/hrfee/mediabrowser v0.3.12 h1:fqDxt1be3e+ZNjAtlKc8MTqg7peo6fuGCrk2wOXo20k=
|
||||
github.com/hrfee/mediabrowser v0.3.12/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE=
|
||||
github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8=
|
||||
@@ -230,8 +240,9 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
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.13.1 h1:wXr2uRxZTJXHLly6qhJabee5JqIhTRoLBhDOA74hDEQ=
|
||||
github.com/klauspost/compress v1.13.1/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/klauspost/compress v1.16.6 h1:91SKEy4K37vkp255cJ8QesJhjyRO0hn9i9G0GoUwLsk=
|
||||
github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
@@ -253,8 +264,10 @@ github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaK
|
||||
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/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailgun/mailgun-go/v4 v4.9.0 h1:wRbxvVQ5QObFewLxc1uVvipA16D8gxeiO+cBOca51Iw=
|
||||
github.com/mailgun/mailgun-go/v4 v4.9.0/go.mod h1:FJlF9rI5cQT+mrwujtJjPMbIVy3Ebor9bKTVsJ0QU40=
|
||||
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/mailgun-go/v4 v4.9.1 h1:D/jhJXYod4RqRsNOOSrjrtAcMEnz8mPYJmeA5cueHKY=
|
||||
github.com/mailgun/mailgun-go/v4 v4.9.1/go.mod h1:FJlF9rI5cQT+mrwujtJjPMbIVy3Ebor9bKTVsJ0QU40=
|
||||
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=
|
||||
@@ -273,7 +286,7 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||
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=
|
||||
@@ -311,6 +324,7 @@ 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/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
@@ -333,8 +347,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
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.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
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=
|
||||
@@ -357,6 +371,7 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/timshannon/badgerhold/v3 v3.0.0-20210909134927-2b6764d68c1e h1:zWSVsQaifg0cVH9VvR+cMguV7exK6U+SoW8YD1cZpR4=
|
||||
github.com/timshannon/badgerhold/v3 v3.0.0-20210909134927-2b6764d68c1e/go.mod h1:/Seq5xGNo8jLhSbDX3jdbeZrp4yFIpQ6/7n4TjziEWs=
|
||||
github.com/timshannon/badgerhold/v4 v4.0.2 h1:83OLY/NFnEaMnHEPd84bYtkLipVkjTsMbzQRYbk47g4=
|
||||
github.com/timshannon/badgerhold/v4 v4.0.2/go.mod h1:rh6RyXLQFsvrvcKondPQQFZnNovpRzu+gS0FlLxYuHY=
|
||||
@@ -381,14 +396,17 @@ github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6Fk
|
||||
github.com/writeas/go-strip-markdown v2.0.1+incompatible/go.mod h1:Rsyu10ZhbEK9pXdk8V6MVnZmTzRG0alMNLMwa0J01fE=
|
||||
github.com/xhit/go-simple-mail/v2 v2.13.0 h1:OANWU9jHZrVfBkNkvLf8Ww0fexwpQVF/v/5f96fFTLI=
|
||||
github.com/xhit/go-simple-mail/v2 v2.13.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98=
|
||||
github.com/xhit/go-simple-mail/v2 v2.16.0 h1:ouGy/Ww4kuaqu2E2UrDw7SvLaziWTB60ICLkIkNVccA=
|
||||
github.com/xhit/go-simple-mail/v2 v2.16.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98=
|
||||
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.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/otel v1.9.0/go.mod h1:np4EoPGzoPs3O67xUVNoPPcmSvsfOxNlNA4F4AC+0Eo=
|
||||
go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
|
||||
go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
|
||||
@@ -419,14 +437,16 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||
golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw=
|
||||
golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg=
|
||||
golang.org/x/image v0.8.0 h1:agUcRXV/+w6L9ryntYYsF2x9fQTMd4T8fiiYXAVW6Jg=
|
||||
golang.org/x/image v0.8.0/go.mod h1:PwLxp3opCYg4WR2WO9P0L6ESnsD6bLTWcw8zanLMVFM=
|
||||
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=
|
||||
@@ -436,7 +456,7 @@ 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.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
|
||||
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
|
||||
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=
|
||||
@@ -458,8 +478,10 @@ golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
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 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
|
||||
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
||||
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
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=
|
||||
@@ -497,11 +519,14 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
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-20220908164124-27713097b956/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 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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=
|
||||
@@ -511,8 +536,10 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
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 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
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=
|
||||
@@ -527,8 +554,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
|
||||
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.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM=
|
||||
golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
||||
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
|
||||
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
|
||||
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=
|
||||
@@ -558,8 +585,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E=
|
||||
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=
|
||||
@@ -586,6 +613,6 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
maunium.net/go/maulogger/v2 v2.4.1 h1:N7zSdd0mZkB2m2JtFUsiGTQQAdP0YeFWT7YMc80yAL8=
|
||||
maunium.net/go/maulogger/v2 v2.4.1/go.mod h1:omPuYwYBILeVQobz8uO3XC8DIRuEb5rXYlQSuqrbCho=
|
||||
maunium.net/go/mautrix v0.15.2 h1:fUiVajeoOR92uJoSShHbCvh7uG6lDY4ZO4Mvt90LbjU=
|
||||
maunium.net/go/mautrix v0.15.2/go.mod h1:h4NwfKqE4YxGTLSgn/gawKzXAb2sF4qx8agL6QEFtGg=
|
||||
maunium.net/go/mautrix v0.15.3 h1:C9BHSUM0gYbuZmAtopuLjIcH5XHLb/ZjTEz7nN+0jN0=
|
||||
maunium.net/go/mautrix v0.15.3/go.mod h1:zLrQqdxJlLkurRCozTc9CL6FySkgZlO/kpCYxBILSLE=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
||||
157
html/admin.html
@@ -17,6 +17,8 @@
|
||||
window.jellyfinLogin = {{ .jellyfinLogin }};
|
||||
window.jfAdminOnly = {{ .jfAdminOnly }};
|
||||
window.jfAllowAll = {{ .jfAllowAll }};
|
||||
window.referralsEnabled = {{ .referralsEnabled }};
|
||||
window.loginAppearance = "{{ .loginAppearance }}";
|
||||
</script>
|
||||
<title>Admin - jfa-go</title>
|
||||
{{ template "header.html" . }}
|
||||
@@ -29,6 +31,11 @@
|
||||
<input type="text" class="field input ~neutral @high mt-4 mb-2" placeholder="{{ .strings.username }}" id="add-user-user">
|
||||
<input type="email" class="field input ~neutral @high mt-4 mb-2" placeholder="{{ .strings.emailAddress }}">
|
||||
<input type="password" class="field input ~neutral @high mb-4" placeholder="{{ .strings.password }}" id="add-user-password">
|
||||
<label class="label supra">{{ .strings.profile }}</label>
|
||||
<div class="select ~neutral @low mb-2 mt-4">
|
||||
<select id="add-user-profile">
|
||||
</select>
|
||||
</div>
|
||||
<label>
|
||||
<input type="submit" class="unfocused">
|
||||
<span class="button ~urge @low full-width center supra submit">{{ .strings.create }}</span>
|
||||
@@ -76,7 +83,7 @@
|
||||
<form class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3" 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-row mb-4">
|
||||
<div class="flex flex-row mb-4">
|
||||
<label class="flex-row-group mr-2">
|
||||
<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>
|
||||
@@ -102,6 +109,48 @@
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
{{ if .referralsEnabled }}
|
||||
<div id="modal-enable-referrals-user" class="modal">
|
||||
<form class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3" 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="flex-row-group 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="flex-row-group 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">
|
||||
<select id="enable-referrals-user-profiles"></select>
|
||||
</div>
|
||||
<div class="select ~neutral @low mb-4 unfocused">
|
||||
<select id="enable-referrals-user-invites"></select>
|
||||
</div>
|
||||
<label>
|
||||
<input type="submit" class="unfocused">
|
||||
<span class="button ~urge @low full-width center supra submit">{{ .strings.apply }}</span>
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
<div id="modal-enable-referrals-profile" class="modal">
|
||||
<form class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3" 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>
|
||||
</div>
|
||||
<label>
|
||||
<input type="submit" class="unfocused">
|
||||
<span class="button ~urge @low full-width center supra submit">{{ .strings.apply }}</span>
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
{{ end }}
|
||||
<div id="modal-delete-user" class="modal">
|
||||
<form class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3" id="form-delete-user" href="">
|
||||
<span class="heading"><span id="header-delete-user"></span> <span class="modal-close">×</span></span>
|
||||
@@ -298,6 +347,9 @@
|
||||
{{ if .ombiEnabled }}
|
||||
<th>Ombi</th>
|
||||
{{ end }}
|
||||
{{ if .referralsEnabled }}
|
||||
<th>{{ .strings.referrals }}</th>
|
||||
{{ end }}
|
||||
<th>{{ .strings.from }}</th>
|
||||
<th>{{ .strings.userProfilesLibraries }}</th>
|
||||
<th><span class="button ~neutral @high" id="button-profile-create">{{ .strings.create }}</span></th>
|
||||
@@ -391,7 +443,7 @@
|
||||
</div>
|
||||
<div id="notification-box"></div>
|
||||
<div class="top-4 left-4 absolute">
|
||||
<span class="dropdown" tabindex="0" id="lang-dropdown">
|
||||
<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>
|
||||
@@ -412,15 +464,18 @@
|
||||
</span>
|
||||
<span class="button ~warning" alt="{{ .strings.theme }}" id="button-theme"><i class="ri-sun-line"></i></span>
|
||||
</div>
|
||||
<div class="top-4 right-4 absolute">
|
||||
<a class="button ~info" href="/my/account"><i class="ri-account-circle-fill mr-2"></i>{{ .strings.myAccount }}</a>
|
||||
</div>
|
||||
{{ if .userPageEnabled }}
|
||||
<div class="top-4 right-4 absolute">
|
||||
<a class="button ~info" href="{{ .urlBase }}/my/account"><i class="ri-account-circle-fill mr-2"></i>{{ .strings.myAccount }}</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
<div class="page-container">
|
||||
<div class="mb-4">
|
||||
<header class="flex flex-wrap items-center justify-between">
|
||||
<div>
|
||||
<span id="button-tab-invites" class="text-3xl button portal ~neutral dark:~d_neutral @low mr-2 mb-2 px-5">{{ .strings.invites }}</span>
|
||||
<span id="button-tab-accounts" class="text-3xl button portal ~neutral dark:~d_neutral @low mr-2 mb-2 px-5">{{ .strings.accounts }}</span>
|
||||
<span id="button-tab-activity" class="text-3xl button portal ~neutral dark:~d_neutral @low mr-2 mb-2 px-5">{{ .strings.activity }}</span>
|
||||
<span id="button-tab-settings" class="text-3xl button portal ~neutral dark:~d_neutral @low mr-2 mb-2 px-5">{{ .strings.settings }}</span>
|
||||
</div>
|
||||
</header>
|
||||
@@ -536,6 +591,11 @@
|
||||
<label class="label supra" for="create-label"> {{ .strings.label }}</label>
|
||||
<input type="text" id="create-label" class="input ~neutral @low mb-2 mt-4">
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="label supra" for="create-user-label"> {{ .strings.userLabel }}</label>
|
||||
<p class="support">{{ .strings.userLabelDescription }}</p>
|
||||
<input type="text" id="create-user-label" class="input ~neutral @low mb-2 mt-4">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card ~neutral @low col">
|
||||
<label class="label supra" for="create-uses">{{ .strings.inviteNumberOfUses }}</label>
|
||||
@@ -586,11 +646,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<input type="search" class="field ~neutral @low input search ml-2 mr-2" id="accounts-search" placeholder="{{ .strings.search }}">
|
||||
<span class="button ~neutral @low center -ml-8" id="accounts-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></span>
|
||||
<span class="button ~neutral @low center ml-[-2.64rem] rounded-s-none accounts-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></span>
|
||||
</div>
|
||||
<div class="supra py-1 sm hidden" id="accounts-search-options-header">{{ .strings.searchOptions }}</div>
|
||||
<div class="row -mx-2">
|
||||
<button type="button" class="button ~neutral @low center m-2 hidden"><span id="accounts-sort-by-field"></span> <i class="ri-close-line ml-2 text-2xl"></i></button>
|
||||
<div class="row -mx-2 mb-2">
|
||||
<button type="button" class="button ~neutral @low center mx-2 hidden"><span id="accounts-sort-by-field"></span> <i class="ri-close-line ml-2 text-2xl"></i></button>
|
||||
<span id="accounts-filter-area"></span>
|
||||
</div>
|
||||
<div class="supra py-1 sm">{{ .strings.actions }}</div>
|
||||
@@ -606,6 +666,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<span class="col button ~urge @low center max-w-[20%]" id="accounts-modify-user">{{ .strings.modifySettings }}</span>
|
||||
{{ if .referralsEnabled }}
|
||||
<span class="col button ~urge @low center max-w-[20%]" id="accounts-enable-referrals">{{ .strings.enableReferrals }}</span>
|
||||
{{ end }}
|
||||
<span class="col button ~warning @low center max-w-[20%]" id="accounts-extend-expiry">{{ .strings.extendExpiry }}</span>
|
||||
<div id="accounts-disable-enable-dropdown" class="col dropdown manual pb-0i max-w-[20%]" tabindex="0">
|
||||
<span class="w-100 button ~positive @low center" id="accounts-disable-enable">{{ .strings.disable }}</span>
|
||||
@@ -637,12 +700,74 @@
|
||||
{{ 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 class="unfocused h-[100%] my-3" id="accounts-not-found">
|
||||
<div class="flex flex-col h-[100%] justify-center items-center">
|
||||
<span class="text-2xl font-medium italic mb-3">{{ .strings.noResultsFound }}</span>
|
||||
<button class="button ~neutral @low accounts-search-clear">
|
||||
<span class="mr-2">{{ .strings.clearSearch }}</span><i class="ri-close-line"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab-activity" class="unfocused">
|
||||
<div class="card @low dark:~d_neutral activity mb-4 overflow-visible">
|
||||
<div class="flex-expand align-middle">
|
||||
<span class="text-3xl font-bold mr-4">{{ .strings.activity }}</span>
|
||||
<div id="activity-filter-dropdown" class="dropdown z-10" tabindex="0">
|
||||
<span class="h-100 button ~neutral @low center" id="activity-filter-button">{{ .strings.filters }}</span>
|
||||
<div class="dropdown-display">
|
||||
<div class="card ~neutral @low mt-2" id="activity-filter-list">
|
||||
<p class="supra pb-2">{{ .strings.filters }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="button ~neutral @low ml-2" id="activity-sort-direction">{{ .strings.sortDirection }}</button>
|
||||
<input type="search" class="field ~neutral @low input search ml-2 mr-2" id="activity-search" placeholder="{{ .strings.search }}">
|
||||
<span class="button ~neutral @low center ml-[-2.64rem] rounded-s-none activity-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></span>
|
||||
<button class="button ~info @low ml-2" id="activity-refresh" aria-label="{{ .strings.refresh }}" disabled><i class="ri-refresh-line"></i></button>
|
||||
</div>
|
||||
<div class="flex flex-row justify-between py-2">
|
||||
<div class="supra sm hidden" id="activity-search-options-header">{{ .strings.searchOptions }}</div>
|
||||
<div class="supra sm">
|
||||
<span id="activity-total-records" class="mx-2"></span>
|
||||
<span id="activity-loaded-records" class="mx-2"></span>
|
||||
<span id="activity-shown-records" class="mx-2"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row -mx-2 mb-2">
|
||||
<button type="button" class="button ~neutral @low center mx-2 hidden"><span id="activity-sort-by-field"></span> <i class="ri-close-line ml-2 text-2xl"></i></button>
|
||||
<span id="activity-filter-area"></span>
|
||||
</div>
|
||||
<div class="my-2">
|
||||
<div id="activity-card-list"></div>
|
||||
<div id="activity-loader"></div>
|
||||
<div class="unfocused h-[100%] my-3" id="activity-not-found">
|
||||
<div class="flex flex-col h-[100%] justify-center items-center">
|
||||
<span class="text-2xl font-medium italic mb-3">{{ .strings.noResultsFound }}</span>
|
||||
<span class="text-xl font-medium italic mb-3 unfocused" id="activity-keep-searching-description">{{ .strings.keepSearchingDescription }}</span>
|
||||
<div class="flex flex-row">
|
||||
<button class="button ~neutral @low activity-search-clear">
|
||||
<span class="mr-2">{{ .strings.clearSearch }}</span><i class="ri-close-line"></i>
|
||||
</button>
|
||||
<button class="button ~neutral @low unfocused" id="activity-keep-searching">{{ .strings.keepSearching }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<button class="button m-2 ~neutral @low" id="activity-load-more">{{ .strings.loadMore }}</button>
|
||||
<button class="button m-2 ~neutral @low" id="activity-load-all">{{ .strings.loadAll }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -664,11 +789,25 @@
|
||||
</div>
|
||||
<div class="flex flex-col md:flex-row gap-3">
|
||||
<div class="card @low dark:~d_neutral col" id="settings-sidebar">
|
||||
<div class="flex-expand">
|
||||
<input type="search" class="field ~neutral @low input settings-section-button justify-between mb-2" id="settings-search" placeholder="{{ .strings.search }}">
|
||||
<button class="button ~neutral @low center -ml-10 rounded-s-none mb-2 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 mb-2 @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>
|
||||
<span class="button ~neutral @low settings-section-button justify-between mb-2" id="setting-about"><span class="flex">{{ .strings.aboutProgram }} <i class="ri-information-line ml-2"></i></span></span>
|
||||
<span class="button ~neutral @low settings-section-button justify-between mb-2" id="setting-profiles"><span class="flex">{{ .strings.userProfiles }} <i class="ri-user-line ml-2"></i></span></span>
|
||||
</div>
|
||||
<div class="card ~neutral @low col overflow" id="settings-panel"></div>
|
||||
<div class="card ~neutral @low col overflow" 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>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<div id="notification-box"></div>
|
||||
<div class="page-container">
|
||||
<div class="card dark:~d_neutral @low">
|
||||
<div class="flex flex-col md:flex-row gap-3 inline align-baseline">
|
||||
<div class="flex flex-col md:flex-row gap-3 items-baseline mb-2">
|
||||
<span class="heading mr-5">
|
||||
{{ if .passwordReset }}
|
||||
{{ .strings.passwordReset }}
|
||||
@@ -53,9 +53,9 @@
|
||||
</span>
|
||||
<span class="subheading">
|
||||
{{ if .passwordReset }}
|
||||
{{ .strings.enterYourPassword }}
|
||||
{{ .strings.enterYourPassword }}
|
||||
{{ else }}
|
||||
{{ .helpMessage }}
|
||||
{{ .helpMessage }}
|
||||
{{ end }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -123,6 +123,9 @@
|
||||
</form>
|
||||
</div>
|
||||
<div class="flex-initial">
|
||||
{{ if .fromUser }}
|
||||
<aside class="col aside sm ~positive mb-4" id="invite-from-user" data-from="{{ .fromUser }}">{{ .strings.invitedBy }}</aside>
|
||||
{{ end }}
|
||||
<div class="card ~neutral @low mb-4">
|
||||
<span class="label supra">{{ .strings.passwordRequirementsHeader }}</span>
|
||||
<ul>
|
||||
@@ -143,7 +146,7 @@
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ if .contactMessage }}
|
||||
<aside class="col aside sm ~info mt-4">{{ .contactMessage }}</aside>
|
||||
<aside class="col aside sm ~info mt-4">{{ .contactMessage }}</aside>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,14 @@
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ if index . "userPageEnabled" }}
|
||||
{{ if and .userPageEnabled .showUserPageLink }}
|
||||
<div class="card mx-2 flex-initial w-[100%] xl:w-[35%] mb-4 xl:mb-0 dark:~d_neutral @low content">
|
||||
<span class="heading row">{{ .strings.loginNotAdmin }}</span>
|
||||
<a class="button ~info h-12 w-100" href="{{ .urlBase }}/my/account"><i class="ri-account-circle-fill mr-2"></i>{{ .strings.myAccount }}</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
<form class="card mx-2 flex-auto form-login w-[100%] xl:w-[55%] 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">
|
||||
|
||||
@@ -122,6 +122,32 @@
|
||||
</select>
|
||||
</div>
|
||||
</label>
|
||||
<span class="heading">{{ .lang.Proxy.title }}</span>
|
||||
<p class="content my-2" id="proxy-description">{{ .lang.Proxy.description }}</p>
|
||||
<label class="row switch pb-4">
|
||||
<input type="checkbox" class="mr-2" id="advanced-proxy"><span>{{ .lang.Strings.enabled }}</span>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>{{ .lang.Proxy.protocol }}</span>
|
||||
<div class="select ~neutral @low mt-4 mb-2">
|
||||
<select id="advanced-proxy_protocol">
|
||||
<option value="http">HTTP</option>
|
||||
<option value="socks">SOCKS5</option>
|
||||
</select>
|
||||
</div>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-4">{{ .lang.Proxy.address }}</span>
|
||||
<input type="text" class="input ~neutral @low mt-4 mb-2" id="advanced-proxy_address">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-4">{{ .lang.Strings.username }}</span>
|
||||
<input type="text" class="input ~neutral @low mt-4 mb-2" id="advanced-proxy_user">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-4">{{ .lang.Strings.password }}</span>
|
||||
<input type="text" class="input ~neutral @low mt-4 mb-2" id="advanced-proxy_password">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
@@ -146,6 +172,7 @@
|
||||
<label class="row switch pb-4">
|
||||
<input type="radio" class="mr-2" name="ui-jellyfin_login" value="false"><span>{{ .lang.Login.authorizeManual }}</span>
|
||||
</label>
|
||||
<p class="support pb-4 pl-4 mt-1">{{ .lang.Login.authorizeManualUserPageNotice }}</p>
|
||||
</div>
|
||||
<div id="login-manual">
|
||||
<label class="label">
|
||||
@@ -238,6 +265,21 @@
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral @low mb-2 unfocused">
|
||||
<span class="heading">{{ .lang.UserPage.title }}</span>
|
||||
<p class="content my-2">{{ .lang.UserPage.description }}</p>
|
||||
<p class="content my-2">{{ .lang.UserPage.customizeMessages }}</p>
|
||||
<label class="row switch pb-4">
|
||||
<input type="checkbox" class="mr-2" id="userpage-enabled"><span>{{ .lang.Strings.enabled }}</span>
|
||||
</label>
|
||||
<p class="support mb-1 mt-1">{{ .lang.UserPage.requiredSettings }}</p>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
|
||||
<div>
|
||||
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral @low mb-2 unfocused">
|
||||
<span class="heading">{{ .lang.Messages.title }}</span>
|
||||
<p class="content my-2" id="messages-description"></p>
|
||||
@@ -391,7 +433,7 @@
|
||||
</label>
|
||||
<label class="switch">
|
||||
<input type="checkbox" class="mr-2" id="password_resets-link_reset"><span>{{ .lang.PasswordResets.resetLinks }}</span>
|
||||
<p class="support mb-2 mt-1">{{ .lang.PasswordResets.resetLinksNotice }}</p>
|
||||
<p class="support mb-2 mt-1">{{ .lang.PasswordResets.resetLinksNotice }} {{ .lang.PasswordResets.resetLinksRequiredForUserPage }}</p>
|
||||
</label>
|
||||
<label class="switch">
|
||||
<input type="checkbox" class="mr-2" id="password_resets-set_password"><span>{{ .lang.PasswordResets.setPassword }}</span>
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
window.matrixRequired = {{ .matrixRequired }};
|
||||
window.matrixUserID = "{{ .matrixUser }}";
|
||||
window.validationStrings = JSON.parse({{ .validationStrings }});
|
||||
window.referralsEnabled = {{ .referralsEnabled }};
|
||||
</script>
|
||||
{{ template "header.html" . }}
|
||||
<title>{{ .strings.myAccount }}</title>
|
||||
@@ -150,6 +151,20 @@
|
||||
<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">{{ .strings.referralsDescription }}</aside>
|
||||
<div class="row flex-expand">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
<script src="{{ .urlBase }}/js/user.js" type="module"></script>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# Images
|
||||
|
||||
This holds any images on the main README, and the base files for the icons and banner. The font used, like Jellyfin, is [Quicksand](https://fonts.google.com/specimen/Quicksand) by Andrew Paglinawan.
|
||||
This holds any images on the main README, and the base files for the icons and banner. The font used pre-0.5.0, like Jellyfin, is [Quicksand](https://fonts.google.com/specimen/Quicksand) by Andrew Paglinawan. These old versions are prefixed with `-quicksand` in `src/`.
|
||||
|
||||
Post-0.5.0, the font used is Hanken Grotesk, available under SIL OFL 1.1 License.
|
||||
https://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web
|
||||
|
||||
"Go" text logo and Gopher image: Copyright 2018 The Go Authors. All rights reserved.
|
||||
https://creativecommons.org/licenses/by/3.0/legalcode
|
||||
|
||||
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 523 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 384 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 411 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 38 KiB |
BIN
images/myaccount.png
Normal file
|
After Width: | Height: | Size: 535 KiB |
412
images/src/banner-hanken.svg
Normal file
|
After Width: | Height: | Size: 64 KiB |
283
images/src/banner-quicksand.svg
Normal file
|
After Width: | Height: | Size: 58 KiB |
736
images/src/jfa-go-social-hanken.svg
Normal file
|
After Width: | Height: | Size: 91 KiB |
337
images/src/jfa-go-social-quicksand.svg
Normal file
|
After Width: | Height: | Size: 59 KiB |
2
lang.go
@@ -116,6 +116,7 @@ type setupLang struct {
|
||||
EndPage langSection `json:"endPage"`
|
||||
General langSection `json:"general"`
|
||||
Updates langSection `json:"updates"`
|
||||
Proxy langSection `json:"proxy"`
|
||||
Language langSection `json:"language"`
|
||||
Login langSection `json:"login"`
|
||||
JellyfinEmby langSection `json:"jellyfinEmby"`
|
||||
@@ -123,6 +124,7 @@ type setupLang struct {
|
||||
Email langSection `json:"email"`
|
||||
Messages langSection `json:"messages"`
|
||||
Notifications langSection `json:"notifications"`
|
||||
UserPage langSection `json:"userPage"`
|
||||
WelcomeEmails langSection `json:"welcomeEmails"`
|
||||
PasswordResets langSection `json:"passwordResets"`
|
||||
InviteEmails langSection `json:"inviteEmails"`
|
||||
|
||||
216
lang/admin/ar-aa.json
Normal file
@@ -0,0 +1,216 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "العربية (AR)"
|
||||
},
|
||||
"strings": {
|
||||
"invites": "الدعوات",
|
||||
"accounts": "الحسابات",
|
||||
"settings": "الإعدادات",
|
||||
"inviteMonths": "شهور",
|
||||
"inviteDays": "أيام",
|
||||
"inviteHours": "ساعات",
|
||||
"inviteMinutes": "دقائق",
|
||||
"inviteNumberOfUses": "عدد الاستخدامات",
|
||||
"inviteDuration": "مدة الدعوة",
|
||||
"warning": "تحذير",
|
||||
"inviteInfiniteUsesWarning": "الدعوات ذات الاستخدامات اللانهائية يمكن ان تستخدم بشكل مسيئ",
|
||||
"inviteSendToEmail": "إرسال إلى",
|
||||
"create": "إنشاء",
|
||||
"apply": "تطبيق",
|
||||
"select": "تحديد",
|
||||
"name": "الاسم",
|
||||
"date": "التاريخ",
|
||||
"setExpiry": "تعيين انتهاء الصلاحية",
|
||||
"updates": "التحديثات",
|
||||
"update": "تحديث",
|
||||
"download": "تنزيل",
|
||||
"search": "بحث",
|
||||
"advancedSettings": "إعدادات متقدمة",
|
||||
"lastActiveTime": "آخر نشاط",
|
||||
"from": "من",
|
||||
"after": "بعد",
|
||||
"before": "قبل",
|
||||
"user": "مستخدم",
|
||||
"userExpiry": "انتهاء صلاحية المستخدم",
|
||||
"userExpiryDescription": "",
|
||||
"aboutProgram": "حول",
|
||||
"version": "إصدار",
|
||||
"commitNoun": "تعديل",
|
||||
"newUser": "مستخدم جديد",
|
||||
"profile": "ملف",
|
||||
"unknown": "غير معروف",
|
||||
"label": "وسم",
|
||||
"logs": "السجلات",
|
||||
"announce": "إعلان",
|
||||
"templates": "قوالب",
|
||||
"subject": "الموضوع",
|
||||
"message": "الرسالة",
|
||||
"variables": "المتغيرات",
|
||||
"conditionals": "",
|
||||
"preview": "معاينة",
|
||||
"reset": "إعادة ضبط",
|
||||
"donate": "تبرع",
|
||||
"unlink": "إلغاء ربط الحساب",
|
||||
"sendPWR": "إرسال إعادة تعيين كلمة المرور",
|
||||
"contactThrough": "تواصل عن طريق:",
|
||||
"extendExpiry": "تمديد إنتهاء الصلاحية",
|
||||
"sendPWRManual": "",
|
||||
"sendPWRSuccess": "تم إرسال رابط إعادة تعيين كلمة المرور.",
|
||||
"sendPWRSuccessManual": "",
|
||||
"sendPWRValidFor": "",
|
||||
"customizeMessages": "",
|
||||
"customizeMessagesDescription": "",
|
||||
"markdownSupported": "",
|
||||
"modifySettings": "",
|
||||
"modifySettingsDescription": "",
|
||||
"applyHomescreenLayout": "",
|
||||
"sendDeleteNotificationEmail": "",
|
||||
"sendDeleteNotifiationExample": "",
|
||||
"settingsRestart": "",
|
||||
"settingsRestarting": "",
|
||||
"settingsRestartRequired": "",
|
||||
"settingsRestartRequiredDescription": "",
|
||||
"settingsApplyRestartLater": "",
|
||||
"settingsApplyRestartNow": "",
|
||||
"settingsApplied": "",
|
||||
"settingsRefreshPage": "",
|
||||
"settingsRequiredOrRestartMessage": "",
|
||||
"settingsSave": "",
|
||||
"ombiProfile": "",
|
||||
"ombiUserDefaultsDescription": "",
|
||||
"userProfiles": "",
|
||||
"userProfilesDescription": "",
|
||||
"userProfilesIsDefault": "",
|
||||
"userProfilesLibraries": "",
|
||||
"addProfile": "",
|
||||
"addProfileDescription": "",
|
||||
"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": ""
|
||||
},
|
||||
"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": ""
|
||||
},
|
||||
"quantityStrings": {
|
||||
"modifySettingsFor": {
|
||||
"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": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
229
lang/admin/cs-cz.json
Normal file
@@ -0,0 +1,229 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Čeština (CZ)"
|
||||
},
|
||||
"strings": {
|
||||
"invites": "Pozvánky",
|
||||
"invite": "Pozvat",
|
||||
"accounts": "Účty",
|
||||
"settings": "Nastavení",
|
||||
"inviteMonths": "Měsíce",
|
||||
"inviteDays": "Dny",
|
||||
"inviteHours": "Hodiny",
|
||||
"inviteMinutes": "Minut",
|
||||
"inviteNumberOfUses": "Počet použití",
|
||||
"inviteDuration": "Doba trvání pozvánky",
|
||||
"warning": "Varování",
|
||||
"inviteInfiniteUsesWarning": "pozvánky s nekonečným využitím mohou být zneužity",
|
||||
"inviteSendToEmail": "Poslat komu",
|
||||
"create": "Vytvořit",
|
||||
"apply": "Aplikovat",
|
||||
"select": "Vybrat",
|
||||
"name": "Název",
|
||||
"date": "Datum",
|
||||
"setExpiry": "Nastavit expiraci",
|
||||
"updates": "Aktualizace",
|
||||
"update": "Aktualizace",
|
||||
"download": "Stažení",
|
||||
"search": "Vyhledávání",
|
||||
"advancedSettings": "Pokročilé nastavení",
|
||||
"lastActiveTime": "Naposled aktivní",
|
||||
"from": "Z",
|
||||
"after": "Po",
|
||||
"before": "Před",
|
||||
"user": "Uživatel",
|
||||
"userExpiry": "Vypršení platnosti",
|
||||
"userExpiryDescription": "Zadanou dobu po každé registraci jfa-go smaže/zakáže účet. Toto chování můžete změnit v nastavení.",
|
||||
"aboutProgram": "O",
|
||||
"version": "Verze",
|
||||
"commitNoun": "Zavázat se",
|
||||
"newUser": "Nový uživatel",
|
||||
"profile": "Profil",
|
||||
"unknown": "Neznámý",
|
||||
"label": "Štítek",
|
||||
"userLabel": "Uživatelský štítek",
|
||||
"userLabelDescription": "Štítek, který se použije pro uživatele vytvořené pomocí této pozvánky.",
|
||||
"logs": "Protokoly",
|
||||
"announce": "Oznámit",
|
||||
"templates": "Šablony",
|
||||
"subject": "Předmět",
|
||||
"message": "Zpráva",
|
||||
"variables": "Proměnné",
|
||||
"conditionals": "Podmínky",
|
||||
"preview": "Náhled",
|
||||
"reset": "Resetovat",
|
||||
"donate": "Darovat",
|
||||
"unlink": "Odpojit účet",
|
||||
"sendPWR": "Odeslat resetování hesla",
|
||||
"contactThrough": "Kontakt přes:",
|
||||
"extendExpiry": "Prodloužit platnost",
|
||||
"sendPWRManual": "Uživatel {n} nemá žádný způsob kontaktu, stisknutím tlačítka Kopírovat získáte odkaz, který mu chcete poslat.",
|
||||
"sendPWRSuccess": "Odkaz pro resetování hesla byl odeslán.",
|
||||
"sendPWRSuccessManual": "Pokud jej uživatel neobdržel, stisknutím tlačítka Kopírovat získáte odkaz, který mu můžete ručně odeslat.",
|
||||
"sendPWRValidFor": "Odkaz je platný 30m.",
|
||||
"customizeMessages": "Přizpůsobit zprávy",
|
||||
"customizeMessagesDescription": "Pokud nechcete používat šablony zpráv jfa-go, můžete si vytvořit vlastní pomocí Markdown.",
|
||||
"markdownSupported": "Markdown je podporován.",
|
||||
"modifySettings": "Upravit nastavení",
|
||||
"modifySettingsDescription": "Použít nastavení ze stávajícího profilu nebo je získat přímo od uživatele.",
|
||||
"enableReferrals": "Povolit doporučení",
|
||||
"disableReferrals": "Zakázat doporučení",
|
||||
"enableReferralsDescription": "Poskytněte uživatelům osobní doporučující odkaz podobný pozvánce, kterou můžete poslat přátelům/rodině. Lze je získat ze šablony doporučení v profilu nebo z existující pozvánky.",
|
||||
"enableReferralsProfileDescription": "Poskytněte uživatelům vytvořeným pomocí tohoto profilu osobní doporučující odkaz podobný pozvánce, aby jej poslali přátelům/rodině. Vytvořte pozvánku s požadovaným nastavením a poté ji vyberte zde. Každé doporučení pak bude založeno na této pozvánce. Po dokončení můžete pozvánku smazat.",
|
||||
"applyHomescreenLayout": "Použít rozložení domovské obrazovky",
|
||||
"sendDeleteNotificationEmail": "Odeslat zprávu s upozorněním",
|
||||
"sendDeleteNotifiationExample": "Váš účet byl smazán.",
|
||||
"settingsRestart": "Restartovat",
|
||||
"settingsRestarting": "Restartování…",
|
||||
"settingsRestartRequired": "Je potřeba restart",
|
||||
"settingsRestartRequiredDescription": "K použití některých změn, které jste změnili, je nutný restart. Restartovat hned nebo později?",
|
||||
"settingsApplyRestartLater": "Použít, restartovat později",
|
||||
"settingsApplyRestartNow": "Použít a restartovat",
|
||||
"settingsApplied": "Nastavení byla použita.",
|
||||
"settingsRefreshPage": "Obnovte stránku během několika sekund.",
|
||||
"settingsRequiredOrRestartMessage": "Poznámka: {n} označuje povinné pole, {n} označuje, že změny vyžadují restart.",
|
||||
"settingsSave": "Uložit",
|
||||
"ombiProfile": "Ombi uživatelský profil",
|
||||
"ombiUserDefaultsDescription": "Vytvořte uživatele Ombi a nakonfigurujte jej, poté jej vyberte níže. Když je tento profil vybrán, jeho nastavení/oprávnění budou uložena a použita pro nové uživatele Ombi vytvořené jfa-go.",
|
||||
"userProfiles": "Uživatelské profily",
|
||||
"userProfilesDescription": "Profily se použijí pro uživatele, když si vytvoří účet. Profil zahrnuje přístupová práva ke knihovně a rozvržení domovské obrazovky.",
|
||||
"userProfilesIsDefault": "Výchozí",
|
||||
"userProfilesLibraries": "Knihovny",
|
||||
"addProfile": "Přidat profil",
|
||||
"addProfileDescription": "Vytvořte uživatele Jellyfin a nakonfigurujte jej, poté jej vyberte níže. Když se tento profil použije na pozvánku, vytvoří se noví uživatelé s nastavením.",
|
||||
"addProfileNameOf": "Jméno profilu",
|
||||
"addProfileStoreHomescreenLayout": "Uložit rozložení domovské obrazovky",
|
||||
"inviteNoUsersCreated": "Ještě žádný!",
|
||||
"inviteUsersCreated": "Vytvoření uživatelé",
|
||||
"inviteNoProfile": "Žádný profil",
|
||||
"inviteDateCreated": "Vytvořeno",
|
||||
"inviteNoInvites": "Žádný",
|
||||
"inviteExpiresInTime": "Platnost vyprší za {n}",
|
||||
"notifyEvent": "Upozornit na:",
|
||||
"notifyInviteExpiry": "Při vypršení platnosti",
|
||||
"notifyUserCreation": "Při vytvoření uživatele",
|
||||
"sendPIN": "Požádejte uživatele, aby robotovi zaslal níže uvedený PIN.",
|
||||
"searchDiscordUser": "Začněte psát uživatelské jméno Discord a vyhledejte uživatele.",
|
||||
"findDiscordUser": "Najít uživatele Discordu",
|
||||
"linkMatrixDescription": "Zadejte uživatelské jméno a heslo uživatele, který chcete použít jako robot. Po odeslání se aplikace restartuje.",
|
||||
"matrixHomeServer": "Adresa domovského serveru",
|
||||
"saveAsTemplate": "Uložit jako šablonu",
|
||||
"deleteTemplate": "Smazat šablonu",
|
||||
"templateEnterName": "Zadejte název pro uložení této šablony.",
|
||||
"accessJFA": "Přístup k jfa-go",
|
||||
"accessJFASettings": "Nelze změnit, protože v Nastavení > Obecné bylo nastaveno \"Pouze správce\" nebo \"Povolit vše\".",
|
||||
"sortingBy": "Řazení podle",
|
||||
"filters": "Filtry",
|
||||
"clickToRemoveFilter": "Kliknutím tento filtr odstraníte.",
|
||||
"clearSearch": "Vymazat vyhledávání",
|
||||
"actions": "Akce",
|
||||
"searchOptions": "Možnosti hledání",
|
||||
"matchText": "Shoda textu",
|
||||
"jellyfinID": "Jellyfin ID",
|
||||
"userPageLogin": "Uživatelská stránka: Přihlášení",
|
||||
"userPagePage": "Uživatelská stránka: Stránka",
|
||||
"buildTime": "Čas sestavení",
|
||||
"builtBy": "Postaven",
|
||||
"loginNotAdmin": "Nejste správce?"
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "Změněna e-mailová adresa uživatele {n}.",
|
||||
"userCreated": "Uživatel {n} byl vytvořen.",
|
||||
"createProfile": "Vytvořen profil {n}.",
|
||||
"saveSettings": "Nastavení byla uložena",
|
||||
"saveEmail": "Email byl uložen.",
|
||||
"sentAnnouncement": "Oznámení odesláno.",
|
||||
"savedAnnouncement": "Oznámení uloženo.",
|
||||
"setOmbiProfile": "Uložený ombi profil.",
|
||||
"updateApplied": "Aktualizace byla použita, restartujte prosím.",
|
||||
"updateAppliedRefresh": "Aktualizace byla použita, obnovte ji.",
|
||||
"telegramVerified": "Účet telegramu ověřen.",
|
||||
"accountConnected": "Účet připojen.",
|
||||
"referralsEnabled": "Doporučení povolena.",
|
||||
"errorSettingsAppliedNoHomescreenLayout": "Nastavení byla použita, ale použití rozvržení domovské obrazovky mohlo selhat.",
|
||||
"errorHomescreenAppliedNoSettings": "Bylo použito rozvržení domovské obrazovky, ale použití nastavení mohlo selhat.",
|
||||
"errorSettingsFailed": "Aplikace se nezdařila.",
|
||||
"errorSaveEmail": "Uložení e-mailu se nezdařilo.",
|
||||
"errorBlankFields": "Pole zůstala prázdná",
|
||||
"errorDeleteProfile": "Smazání profilu {n} se nezdařilo",
|
||||
"errorLoadProfiles": "Načtení profilů se nezdařilo.",
|
||||
"errorCreateProfile": "Nepodařilo se vytvořit profil {n}",
|
||||
"errorSetDefaultProfile": "Nepodařilo se nastavit výchozí profil.",
|
||||
"errorLoadUsers": "Uživatele se nepodařilo načíst.",
|
||||
"errorLoadSettings": "Nastavení se nepodařilo načíst.",
|
||||
"errorSetOmbiProfile": "Uložení profilu ombi se nezdařilo.",
|
||||
"errorLoadOmbiUsers": "Uživatele ombi se nepodařilo načíst.",
|
||||
"errorChangedEmailAddress": "E-mailovou adresu uživatele {n} se nepodařilo změnit.",
|
||||
"errorFailureCheckLogs": "Selhalo (zkontrolujte konzolu/protokoly)",
|
||||
"errorPartialFailureCheckLogs": "Částečná chyba (zkontrolujte konzolu/protokoly)",
|
||||
"errorUserCreated": "Nepodařilo se vytvořit uživatele {n}.",
|
||||
"errorSendWelcomeEmail": "Nepodařilo se odeslat uvítací zprávu (zkontrolujte konzolu/protokoly)",
|
||||
"errorApplyUpdate": "Aktualizaci se nepodařilo použít, zkuste to ručně.",
|
||||
"errorCheckUpdate": "Kontrola aktualizace se nezdařila.",
|
||||
"errorNoReferralTemplate": "Profil neobsahuje šablonu doporučení, přidejte si ji v nastavení.",
|
||||
"updateAvailable": "Je k dispozici nová aktualizace, zkontrolujte nastavení.",
|
||||
"noUpdatesAvailable": "Nejsou k dispozici žádné nové aktualizace."
|
||||
},
|
||||
"quantityStrings": {
|
||||
"modifySettingsFor": {
|
||||
"singular": "Upravit nastavení pro {n} uživatele",
|
||||
"plural": "Upravit nastavení pro {n} uživatelů"
|
||||
},
|
||||
"enableReferralsFor": {
|
||||
"singular": "Povolit doporučení pro {n} uživatele",
|
||||
"plural": "Povolit doporučení pro {n} uživatelů"
|
||||
},
|
||||
"deleteNUsers": {
|
||||
"singular": "Smazat {n} uživatele",
|
||||
"plural": "Smazat {n} uživatelů"
|
||||
},
|
||||
"disableUsers": {
|
||||
"singular": "Zakázat {n} uživatele",
|
||||
"plural": "Zakázat {n} uživatelů"
|
||||
},
|
||||
"reEnableUsers": {
|
||||
"singular": "Znovu povolte {n} uživatele",
|
||||
"plural": "Znovu povolit {n} uživatelů"
|
||||
},
|
||||
"addUser": {
|
||||
"singular": "Přidat uživatele",
|
||||
"plural": "Přidat uživatele"
|
||||
},
|
||||
"deleteUser": {
|
||||
"singular": "Smazat uživatele",
|
||||
"plural": "Smazat uživatele"
|
||||
},
|
||||
"deletedUser": {
|
||||
"singular": "Smazán {n} uživatel.",
|
||||
"plural": "Smazaní {n} uživatelé."
|
||||
},
|
||||
"disabledUser": {
|
||||
"singular": "Deaktivován {n} uživatel.",
|
||||
"plural": "Zakázaných {n} uživatelů."
|
||||
},
|
||||
"enabledUser": {
|
||||
"singular": "Povoleno {n} uživatele.",
|
||||
"plural": "Povolených {n} uživatelů."
|
||||
},
|
||||
"announceTo": {
|
||||
"singular": "Oznámeno {n} uživateli",
|
||||
"plural": "Oznámit {n} uživatelům"
|
||||
},
|
||||
"appliedSettings": {
|
||||
"singular": "Nastavení byla použita na {n} uživatele.",
|
||||
"plural": "Nastavení byla použita na {n} uživatelů."
|
||||
},
|
||||
"extendExpiry": {
|
||||
"singular": "Prodloužit platnost pro {n} uživatele",
|
||||
"plural": "Prodloužit platnost pro {n} uživatelů"
|
||||
},
|
||||
"setExpiry": {
|
||||
"singular": "Nastavit vypršení platnosti pro {n} uživatele",
|
||||
"plural": "Nastavit vypršení platnosti pro {n} uživatelů"
|
||||
},
|
||||
"extendedExpiry": {
|
||||
"singular": "Prodloužená platnost pro {n} uživatele.",
|
||||
"plural": "Prodloužená platnost pro {n} uživatelů."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,6 @@
|
||||
"inviteUsersCreated": "Oprettet brugere",
|
||||
"inviteNoProfile": "Ingen Profil",
|
||||
"inviteDateCreated": "Oprettet",
|
||||
"inviteRemainingUses": "Resterende anvendelser",
|
||||
"inviteNoInvites": "Ingen",
|
||||
"inviteExpiresInTime": "Udløber om {n}",
|
||||
"notifyEvent": "Meddel den:",
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
"inviteUsersCreated": "Erstellte Benutzer",
|
||||
"inviteNoProfile": "Kein Profil",
|
||||
"inviteDateCreated": "Erstellt",
|
||||
"inviteRemainingUses": "Verbleibende Verwendungen",
|
||||
"inviteNoInvites": "Keine",
|
||||
"inviteExpiresInTime": "Läuft in {n} ab",
|
||||
"notifyEvent": "Benachrichtigen bei:",
|
||||
@@ -103,7 +102,20 @@
|
||||
"accessJFASettings": "Kann nicht geändert werden, da entweder \"Nur Admin-Benutzer\" oder \"Erlaube allen Jellyfin-Nutzern sich anzumelden\" in Einstellungen > Allgemein aktiviert ist.",
|
||||
"saveAsTemplate": "Als Vorlage speichern",
|
||||
"deleteTemplate": "Vorlage löschen",
|
||||
"templateEnterName": "Gebe einen Namen ein, um diese Vorlage zu speichern."
|
||||
"templateEnterName": "Gebe einen Namen ein, um diese Vorlage zu speichern.",
|
||||
"filters": "Filter",
|
||||
"clickToRemoveFilter": "zum Entfernen des Filters klicken.",
|
||||
"clearSearch": "Suche löschen",
|
||||
"actions": "Aktionen",
|
||||
"searchOptions": "Suchoptionen",
|
||||
"matchText": "Textübereinstummung",
|
||||
"jellyfinID": "Jellyfin ID",
|
||||
"userPageLogin": "Benutzer Seite: Login",
|
||||
"userPagePage": "Benutzer Seite: Seite",
|
||||
"after": "nach",
|
||||
"before": "vor",
|
||||
"unlink": "Account trennen",
|
||||
"sortingBy": "Sortieren nach"
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "E-Mail-Adresse von {n} geändert.",
|
||||
@@ -201,4 +213,4 @@
|
||||
"plural": "Ablauf für {n} Benutzer setzen"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,6 @@
|
||||
"inviteUsersCreated": "Δημιουργηθέντες χρήστες",
|
||||
"inviteNoProfile": "Κανένα Προφίλ",
|
||||
"inviteDateCreated": "Δημιουργηθέντα",
|
||||
"inviteRemainingUses": "Εναπομείναντες χρήσεις",
|
||||
"inviteNoInvites": "Καμία",
|
||||
"inviteExpiresInTime": "Λήγει σε {n}",
|
||||
"notifyEvent": "Ενημέρωση όταν:",
|
||||
|
||||
@@ -124,7 +124,6 @@
|
||||
"addProfileStoreHomescreenLayout": "Store homescreen layout",
|
||||
"inviteNoUsersCreated": "None yet!",
|
||||
"inviteUsersCreated": "Created users",
|
||||
"inviteRemainingUses": "Remaining uses",
|
||||
"inviteNoInvites": "None",
|
||||
"inviteExpiresInTime": "Expires in {n}",
|
||||
"notifyEvent": "Notify on:",
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
},
|
||||
"strings": {
|
||||
"invites": "Invites",
|
||||
"invite": "Invite",
|
||||
"accounts": "Accounts",
|
||||
"activity": "Activity",
|
||||
"settings": "Settings",
|
||||
"inviteMonths": "Months",
|
||||
"inviteDays": "Days",
|
||||
@@ -40,6 +42,8 @@
|
||||
"profile": "Profile",
|
||||
"unknown": "Unknown",
|
||||
"label": "Label",
|
||||
"userLabel": "User Label",
|
||||
"userLabelDescription": "Label to apply to users created with this invite.",
|
||||
"logs": "Logs",
|
||||
"announce": "Announce",
|
||||
"templates": "Templates",
|
||||
@@ -51,7 +55,12 @@
|
||||
"reset": "Reset",
|
||||
"donate": "Donate",
|
||||
"unlink": "Unlink Account",
|
||||
"deleted": "Deleted",
|
||||
"disabled": "Disabled",
|
||||
"sendPWR": "Send Password Reset",
|
||||
"noResultsFound": "No Results Found",
|
||||
"keepSearching": "Keep Searching",
|
||||
"keepSearchingDescription": "Only the current loaded activities were searched. Click below if you wish to search all activities.",
|
||||
"contactThrough": "Contact through:",
|
||||
"extendExpiry": "Extend expiry",
|
||||
"sendPWRManual": "User {n} has no method of contact, press copy to get a link to send to them.",
|
||||
@@ -63,6 +72,10 @@
|
||||
"markdownSupported": "Markdown is supported.",
|
||||
"modifySettings": "Modify Settings",
|
||||
"modifySettingsDescription": "Apply settings from an existing profile, or source them directly from a user.",
|
||||
"enableReferrals": "Enable Referrals",
|
||||
"disableReferrals": "Disable Referrals",
|
||||
"enableReferralsDescription": "Give users a personal referral link similiar to an invite, to send to friends/family. Can be sourced from a referral template in a profile, or from an existing invite.",
|
||||
"enableReferralsProfileDescription": "Give users created with this profile a personal referral link similiar to an invite, to send to friends/family. Create an invite with the desired settings, then select it here. Each referral will then be based on this invite. You can delete the invite once complete.",
|
||||
"applyHomescreenLayout": "Apply homescreen layout",
|
||||
"sendDeleteNotificationEmail": "Send notification message",
|
||||
"sendDeleteNotifiationExample": "Your account has been deleted.",
|
||||
@@ -76,6 +89,10 @@
|
||||
"settingsRefreshPage": "Refresh the page in a few seconds.",
|
||||
"settingsRequiredOrRestartMessage": "Note: {n} indicates a required field, {n} indicates changes require a restart.",
|
||||
"settingsSave": "Save",
|
||||
"settingsHiddenDependency": "Matching settings are hidden because they depend on the value of another setting:",
|
||||
"settingsDependsOn": "{setting}: Depends on {dependency}",
|
||||
"settingsAdvancedMode": "{setting}: Advanced Settings must be enabled",
|
||||
"settingsMaybeUnderAdvanced": "Tip: You might find what you're looking for by enabling Advanced Settings.",
|
||||
"ombiProfile": "Ombi user profile",
|
||||
"ombiUserDefaultsDescription": "Create an Ombi user and configure it, then select it below. It's settings/permissions will be stored and applied to new Ombi users created by jfa-go when this profile is selected.",
|
||||
"userProfiles": "User Profiles",
|
||||
@@ -90,7 +107,6 @@
|
||||
"inviteUsersCreated": "Created users",
|
||||
"inviteNoProfile": "No Profile",
|
||||
"inviteDateCreated": "Created",
|
||||
"inviteRemainingUses": "Remaining uses",
|
||||
"inviteNoInvites": "None",
|
||||
"inviteExpiresInTime": "Expires in {n}",
|
||||
"notifyEvent": "Notify on:",
|
||||
@@ -107,6 +123,7 @@
|
||||
"accessJFA": "Access jfa-go",
|
||||
"accessJFASettings": "Cannot be changed as either \"Admin Only\" or \"Allow All\" has been set in Settings > General.",
|
||||
"sortingBy": "Sorting By",
|
||||
"sortDirection": "Sort Direction",
|
||||
"filters": "Filters",
|
||||
"clickToRemoveFilter": "Click to remove this filter.",
|
||||
"clearSearch": "Clear search",
|
||||
@@ -116,8 +133,49 @@
|
||||
"jellyfinID": "Jellyfin ID",
|
||||
"userPageLogin": "User Page: Login",
|
||||
"userPagePage": "User Page: Page",
|
||||
"buildTime": "Build Time",
|
||||
"builtBy": "Built By"
|
||||
"buildTime": "Build Time",
|
||||
"builtBy": "Built By",
|
||||
"loginNotAdmin": "Not an Admin?",
|
||||
"referrer": "Referrer",
|
||||
"accountLinked": "{contactMethod} linked: {user}",
|
||||
"accountUnlinked": "{contactMethod} removed: {user}",
|
||||
"accountResetPassword": "{user} reset their password",
|
||||
"accountChangedPassword": "{user} changed their password",
|
||||
"accountCreated": "Account created: {user}",
|
||||
"accountDeleted": "Account deleted: {user}",
|
||||
"accountDisabled": "Account disabled: {user}",
|
||||
"accountReEnabled": "Account re-enabled: {user}",
|
||||
"accountExpired": "Account expired: {user}",
|
||||
"userDeleted": "User was deleted.",
|
||||
"userDisabled": "User was disabled",
|
||||
"inviteCreated": "Invite created: {invite}",
|
||||
"inviteDeleted": "Invite deleted: {invite}",
|
||||
"inviteExpired": "Invite expired: {invite}",
|
||||
"fromInvite": "From Invite",
|
||||
"byAdmin": "By Admin",
|
||||
"byUser": "By User",
|
||||
"byJfaGo": "By jfa-go",
|
||||
"activityID": "Activity ID",
|
||||
"title": "Title",
|
||||
"usersMentioned": "User mentioned",
|
||||
"actor": "Actor",
|
||||
"actorDescription": "The thing that caused this action. \"user\"/\"admin\"/\"daemon\" or a username.",
|
||||
"accountCreationFilter": "Account Creation",
|
||||
"accountDeletionFilter": "Account Deletion",
|
||||
"accountDisabledFilter": "Account Disabled",
|
||||
"accountEnabledFilter": "Account Enabled",
|
||||
"contactLinkedFilter": "Contact Linked",
|
||||
"contactUnlinkedFilter": "Contact Unlinked",
|
||||
"passwordChangeFilter": "Password Changed",
|
||||
"passwordResetFilter": "Password Reset",
|
||||
"inviteCreatedFilter": "Invite Created",
|
||||
"inviteDeletedFilter": "Invite Deleted/Expired",
|
||||
"loadMore": "Load More",
|
||||
"loadAll": "Load All",
|
||||
"noMoreResults": "No more results.",
|
||||
"totalRecords": "{n} Total Records",
|
||||
"loadedRecords": "{n} Loaded",
|
||||
"shownRecords": "{n} Shown"
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "Changed email address of {n}.",
|
||||
@@ -132,6 +190,10 @@
|
||||
"updateAppliedRefresh": "Update applied, please refresh.",
|
||||
"telegramVerified": "Telegram account verified.",
|
||||
"accountConnected": "Account connected.",
|
||||
"referralsEnabled": "Referrals enabled.",
|
||||
"activityDeleted": "Activity Deleted.",
|
||||
"errorInviteNoLongerExists": "Invite no longer exists.",
|
||||
"errorInviteNotFound": "Invite not found.",
|
||||
"errorSettingsAppliedNoHomescreenLayout": "Settings were applied, but applying homescreen layout may have failed.",
|
||||
"errorHomescreenAppliedNoSettings": "Homescreen layout was applied, but applying settings may have failed.",
|
||||
"errorSettingsFailed": "Application failed.",
|
||||
@@ -152,6 +214,8 @@
|
||||
"errorSendWelcomeEmail": "Failed to send welcome message (check console/logs)",
|
||||
"errorApplyUpdate": "Failed to apply update, try manually.",
|
||||
"errorCheckUpdate": "Failed to check for update.",
|
||||
"errorNoReferralTemplate": "Profile doesn't contain referral template, add one in settings.",
|
||||
"errorLoadActivities": "Failed to load activities.",
|
||||
"updateAvailable": "A new update is available, check settings.",
|
||||
"noUpdatesAvailable": "No new updates available."
|
||||
},
|
||||
@@ -160,6 +224,10 @@
|
||||
"singular": "Modify Settings for {n} user",
|
||||
"plural": "Modify Settings for {n} users"
|
||||
},
|
||||
"enableReferralsFor": {
|
||||
"singular": "Enable Referrals for {n} user",
|
||||
"plural": "Enable Referrals for {n} users"
|
||||
},
|
||||
"deleteNUsers": {
|
||||
"singular": "Delete {n} user",
|
||||
"plural": "Delete {n} users"
|
||||
|
||||
@@ -75,7 +75,6 @@
|
||||
"inviteUsersCreated": "Usuarios creados",
|
||||
"inviteNoProfile": "Sin perfil",
|
||||
"inviteDateCreated": "Creado",
|
||||
"inviteRemainingUses": "Usos restantes",
|
||||
"inviteNoInvites": "Ninguno",
|
||||
"inviteExpiresInTime": "Caduca en {n}",
|
||||
"notifyEvent": "Notificar en:",
|
||||
@@ -103,7 +102,21 @@
|
||||
"ombiProfile": "Perfil de usuario de Ombi",
|
||||
"logs": "Registros",
|
||||
"accessJFA": "Acceso",
|
||||
"accessJFASettings": "No se puede cambia, ya que se ha establecido \"Solo administradores\" o \"Permitir a todos\" en Configuración > General."
|
||||
"accessJFASettings": "No se puede cambia, ya que se ha establecido \"Solo administradores\" o \"Permitir a todos\" en Configuración > General.",
|
||||
"buildTime": "Tiempo de construcción",
|
||||
"builtBy": "Construido por",
|
||||
"sortingBy": "Ordenar por",
|
||||
"filters": "Filtros",
|
||||
"clearSearch": "Borrar búsqueda",
|
||||
"searchOptions": "Opciones de búsqueda",
|
||||
"matchText": "Coincidir texto",
|
||||
"jellyfinID": "Jellyfin ID",
|
||||
"userPageLogin": "Página de usuario: Iniciar sesión",
|
||||
"userPagePage": "Página de usuario: Página",
|
||||
"after": "Después",
|
||||
"before": "Antes",
|
||||
"unlink": "Desvincular cuenta",
|
||||
"clickToRemoveFilter": "Haga clic para eliminar el filtro."
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "Se cambió la dirección de correo electrónico de {n}.",
|
||||
@@ -201,4 +214,4 @@
|
||||
"plural": "Establecer la caducidad para {n} usuarios"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"inviteUsersCreated": "Utilisateurs créés",
|
||||
"inviteNoProfile": "Aucun profil",
|
||||
"inviteDateCreated": "Créer",
|
||||
"inviteRemainingUses": "Utilisations restantes",
|
||||
"inviteNoInvites": "Aucune",
|
||||
"inviteExpiresInTime": "Expires dans {n}",
|
||||
"notifyEvent": "Notifier sur :",
|
||||
@@ -104,7 +103,29 @@
|
||||
"ombiProfile": "Profil d'utilisateur Ombi",
|
||||
"logs": "Logs",
|
||||
"accessJFA": "Accès à jfa-go",
|
||||
"accessJFASettings": "Ne peut pas être changé car \"Admin Only\" ou \"Allow All\" a été défini dans Paramètres > Général."
|
||||
"accessJFASettings": "Ne peut pas être changé car \"Admin Only\" ou \"Allow All\" a été défini dans Paramètres > Général.",
|
||||
"buildTime": "Heure de la version",
|
||||
"builtBy": "Version créée par",
|
||||
"sortingBy": "Trier par",
|
||||
"filters": "Filtres",
|
||||
"clickToRemoveFilter": "Cliquer pour supprimer ce filtre.",
|
||||
"clearSearch": "Réinitialiser la recherche",
|
||||
"actions": "Actions",
|
||||
"searchOptions": "Recherche avancée",
|
||||
"matchText": "Texte correspondant",
|
||||
"jellyfinID": "ID Jellyfin",
|
||||
"userPageLogin": "Page utilisateur : Connexion",
|
||||
"userPagePage": "Page utilisateur : Page",
|
||||
"after": "Après",
|
||||
"before": "Avant",
|
||||
"unlink": "Délier le compte",
|
||||
"enableReferrals": "Activer Parrainage",
|
||||
"enableReferralsDescription": "Offrez aux utilisateurs un lien de parrainage personnel semblable à une invitation, à envoyer à vos amis/famille. Peut provenir modèle de profil ou d’une invitation existante.",
|
||||
"invite": "Inviter",
|
||||
"userLabel": "Étiquette",
|
||||
"userLabelDescription": "Étiquette à appliquer aux utilisateurs créés avec cette invitation.",
|
||||
"disableReferrals": "Désactiver Parrainage",
|
||||
"enableReferralsProfileDescription": "Donnez aux utilisateurs créés avec ce profil un lien de parrainage personnel semblable à une invitation, à envoyer à vos amis/famille. Créez une invitation avec les paramètres souhaités, puis sélectionnez-la ici. Chaque référence sera alors basée sur cette invitation. Vous pouvez supprimer l'invitation une fois terminée."
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "Adresse e-mail modifiée de {n}.",
|
||||
@@ -142,7 +163,9 @@
|
||||
"accountConnected": "Compte connecté.",
|
||||
"savedAnnouncement": "Annonce enregistrée.",
|
||||
"setOmbiProfile": "Profil ombi enregistré.",
|
||||
"errorSetOmbiProfile": "Echec de la sauvegarde du profil ombi."
|
||||
"errorSetOmbiProfile": "Echec de la sauvegarde du profil ombi.",
|
||||
"errorNoReferralTemplate": "Le profil ne contient pas de modèle de référence, ajoutez-en un dans les paramètres.",
|
||||
"referralsEnabled": "Parrainage activer."
|
||||
},
|
||||
"quantityStrings": {
|
||||
"modifySettingsFor": {
|
||||
@@ -200,6 +223,10 @@
|
||||
"setExpiry": {
|
||||
"singular": "Définir l'expiration pour {n} utilisateur",
|
||||
"plural": "Définir l'expiration pour {n} utilisateurs"
|
||||
},
|
||||
"enableReferralsFor": {
|
||||
"singular": "Activer les parrainages pour {n} utilisateur",
|
||||
"plural": "Activer les parrainages pour {n} utilisateur"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
"lastActiveTime": "Utoljára aktív",
|
||||
"from": "Feladó",
|
||||
"user": "Felhasználó",
|
||||
"userExpiry": "Felhasználói lejárat",
|
||||
"userExpiryDescription": "Egy meghatározott idő után minden regisztrációt töröl, vagy felfüggeszt a jfa-go. Ezt a működést megváltoztathatod a beállításokban.",
|
||||
"userExpiry": "Felhasználó megszünése",
|
||||
"userExpiryDescription": "Egy meghatározott idő után minden létrehozott felhasználó, vagy felfüggesztésre vagy törlésre kerül rendszer által. A viselkedés a beállításokban módosítható.",
|
||||
"aboutProgram": "Névjegy",
|
||||
"version": "Verzió",
|
||||
"commitNoun": "Elkövet",
|
||||
@@ -49,50 +49,50 @@
|
||||
"reset": "Visszaállítás",
|
||||
"donate": "Támogatás",
|
||||
"sendPWR": "Jelszó visszaállítás küldése",
|
||||
"contactThrough": "",
|
||||
"extendExpiry": "",
|
||||
"sendPWRManual": "",
|
||||
"sendPWRSuccess": "",
|
||||
"sendPWRSuccessManual": "",
|
||||
"sendPWRValidFor": "",
|
||||
"customizeMessages": "",
|
||||
"customizeMessagesDescription": "",
|
||||
"markdownSupported": "",
|
||||
"modifySettings": "",
|
||||
"modifySettingsDescription": "",
|
||||
"applyHomescreenLayout": "",
|
||||
"sendDeleteNotificationEmail": "",
|
||||
"sendDeleteNotifiationExample": "",
|
||||
"settingsRestart": "",
|
||||
"settingsRestarting": "",
|
||||
"settingsRestartRequired": "",
|
||||
"settingsRestartRequiredDescription": "",
|
||||
"settingsApplyRestartLater": "",
|
||||
"settingsApplyRestartNow": "",
|
||||
"settingsApplied": "",
|
||||
"settingsRefreshPage": "",
|
||||
"settingsRequiredOrRestartMessage": "",
|
||||
"settingsSave": "",
|
||||
"ombiProfile": "",
|
||||
"ombiUserDefaultsDescription": "",
|
||||
"userProfiles": "",
|
||||
"userProfilesDescription": "",
|
||||
"userProfilesIsDefault": "",
|
||||
"userProfilesLibraries": "",
|
||||
"addProfile": "",
|
||||
"addProfileDescription": "",
|
||||
"addProfileNameOf": "",
|
||||
"addProfileStoreHomescreenLayout": "",
|
||||
"inviteNoUsersCreated": "",
|
||||
"inviteUsersCreated": "",
|
||||
"inviteNoProfile": "",
|
||||
"inviteDateCreated": "",
|
||||
"inviteRemainingUses": "",
|
||||
"inviteNoInvites": "",
|
||||
"inviteExpiresInTime": "",
|
||||
"notifyEvent": "",
|
||||
"notifyInviteExpiry": "",
|
||||
"notifyUserCreation": "",
|
||||
"contactThrough": "Kapcsolatfelvétel vele:",
|
||||
"extendExpiry": "Lejárat kiterjesztése",
|
||||
"sendPWRManual": "{n} felhasználónak nincs beállítva egy kapcsolati lehetőség sem, kattints a másolásra a linkhez és küld tovább neki.",
|
||||
"sendPWRSuccess": "Jelszó visszaállítási link elküldve.",
|
||||
"sendPWRSuccessManual": "Ha a felhasználó nem kapta meg, nyomj a másolásra és küld el neki manuálisan.",
|
||||
"sendPWRValidFor": "A link érvényessége 30p.",
|
||||
"customizeMessages": "Üzenetek testreszabása",
|
||||
"customizeMessagesDescription": "Hogyha nem akarod a jfa-go üzenet sablonjait használni, létre hozhatsz egy sajátot, akár Markdown segítségével.",
|
||||
"markdownSupported": "Markdown támogatott.",
|
||||
"modifySettings": "Beállítások módosítása",
|
||||
"modifySettingsDescription": "Beállítások másolása egy meglévő profilról, vagy egy konkrét felhasználóról.",
|
||||
"applyHomescreenLayout": "Főképernyő elrendezés alkalmazása",
|
||||
"sendDeleteNotificationEmail": "Értesítések küldése",
|
||||
"sendDeleteNotifiationExample": "A fiókod törlésre került.",
|
||||
"settingsRestart": "Újraindítás",
|
||||
"settingsRestarting": "Újraindítás…",
|
||||
"settingsRestartRequired": "Újraindítás szükséges",
|
||||
"settingsRestartRequiredDescription": "A változtatott beállítások érvénybe léptetéséhez újraindítás szükséges. Most szeretnéd újraindítani vagy később?",
|
||||
"settingsApplyRestartLater": "Alkalmazás, újraindítás később",
|
||||
"settingsApplyRestartNow": "Alkalmazás és újraindítás",
|
||||
"settingsApplied": "Beállítások alkalmazva.",
|
||||
"settingsRefreshPage": "Frissítsd az oldalt egy pár másodperc múlva.",
|
||||
"settingsRequiredOrRestartMessage": "Megjegyzés: {n} jelöli a kötelező mezőket, {n} jelöli hogyha újraindítás szükséges.",
|
||||
"settingsSave": "Mentés",
|
||||
"ombiProfile": "Ombi felhasználói profil",
|
||||
"ombiUserDefaultsDescription": "Hozz létre egy Ombi felhasználót, állítsd be, majd válaszd ki lentebb. A beállításai/jogosultságai el lesznek mentve és alkalmazva lesznek az új Ombi felhasználókra amik ezzel a profillal lesznek létrehozva.",
|
||||
"userProfiles": "Felhasználói profilok",
|
||||
"userProfilesDescription": "A profilok alkalmazva lesznek a felhasználó létrehozáskor. A profil tartalmazza a könyvtár hozzáférést, és a kezdőképernyő elrendezést.",
|
||||
"userProfilesIsDefault": "Alapértelmezett",
|
||||
"userProfilesLibraries": "Könyvtárak",
|
||||
"addProfile": "Profil hozzáadása",
|
||||
"addProfileDescription": "Hozz létre egy Jellyfin felhasználót, állítsd be, majd válaszd ki lentebb. Amikor egy felhasználó létrejön egy meghívásból aminél ez a profil volt alkalmazva, megkapja az összes beállítását.",
|
||||
"addProfileNameOf": "Profil neve",
|
||||
"addProfileStoreHomescreenLayout": "Kezdőképernyő elrendezés elmentése",
|
||||
"inviteNoUsersCreated": "Még nincs!",
|
||||
"inviteUsersCreated": "Létrehozott felhasználók",
|
||||
"inviteNoProfile": "Profil nélkül",
|
||||
"inviteDateCreated": "Létrehozva",
|
||||
"inviteRemainingUses": "Hátralévő felhasználások",
|
||||
"inviteNoInvites": "Nincs meghívó",
|
||||
"inviteExpiresInTime": "Lejárat {n} múlva",
|
||||
"notifyEvent": "Értesítés ekkor:",
|
||||
"notifyInviteExpiry": "Lejáratkor",
|
||||
"notifyUserCreation": "Használatkor",
|
||||
"sendPIN": "",
|
||||
"searchDiscordUser": "",
|
||||
"findDiscordUser": "",
|
||||
@@ -100,7 +100,19 @@
|
||||
"matrixHomeServer": "",
|
||||
"saveAsTemplate": "",
|
||||
"deleteTemplate": "",
|
||||
"templateEnterName": ""
|
||||
"templateEnterName": "",
|
||||
"unlink": "Fiók leválasztása",
|
||||
"after": "Utánna",
|
||||
"before": "Elötte",
|
||||
"sortingBy": "Rendezés",
|
||||
"filters": "Szűrők",
|
||||
"clearSearch": "Keresés törlése",
|
||||
"actions": "Műveletek",
|
||||
"searchOptions": "Kereső paraméterek",
|
||||
"matchText": "Eggyező szöveg",
|
||||
"jellyfinID": "Jellyfin azonosító",
|
||||
"userPageLogin": "Felhasználói oldal: Bejelentkezés",
|
||||
"clickToRemoveFilter": "Szűrő eltávolítása."
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "",
|
||||
@@ -192,4 +204,4 @@
|
||||
"plural": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"modifySettings": "Ganti Pengaturan",
|
||||
"modifySettingsDescription": "Terapkan pengaturan dari profil yang ada, atau dapatkan langsung dari pengguna.",
|
||||
"applyHomescreenLayout": "Terapkan tata letak layar beranda",
|
||||
"sendDeleteNotificationEmail": "Kirim email notifikasi",
|
||||
"sendDeleteNotificationEmail": "Kirim pesan notifikasi",
|
||||
"sendDeleteNotifiationExample": "Akun anda telah dihapus.",
|
||||
"settingsRestart": "Mulai ulang",
|
||||
"settingsRestarting": "Mengulang kembali…",
|
||||
@@ -56,7 +56,6 @@
|
||||
"inviteUsersCreated": "Pengguna yang telah dibuat",
|
||||
"inviteNoProfile": "Tidak ada profil",
|
||||
"inviteDateCreated": "Dibuat",
|
||||
"inviteRemainingUses": "Penggunaan yang tersisa",
|
||||
"inviteNoInvites": "Tidak ada",
|
||||
"inviteExpiresInTime": "Kadaluarsa dalam {n}",
|
||||
"notifyEvent": "Beritahu pada:",
|
||||
@@ -68,9 +67,14 @@
|
||||
"customizeMessages": "Sesuaikan Email",
|
||||
"customizeMessagesDescription": "Jika Anda tidak ingin menggunakan templat email jfa-go, Anda dapat membuatnya sendiri menggunakan Markdown.",
|
||||
"announce": "Mengumumkan",
|
||||
"subject": "Subjek Email",
|
||||
"subject": "Subjek",
|
||||
"message": "Pesan",
|
||||
"markdownSupported": "Markdown didukung."
|
||||
"markdownSupported": "Markdown didukung.",
|
||||
"donate": "Donasi",
|
||||
"select": "Pilih",
|
||||
"search": "Cari",
|
||||
"download": "Unduh",
|
||||
"inviteMonths": "Bulan"
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "Alamat email {n} diubah.",
|
||||
@@ -129,4 +133,4 @@
|
||||
"plural": "Umumkan kepada {n} pengguna"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
216
lang/admin/it-it.json
Normal file
@@ -0,0 +1,216 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Italiano (IT)"
|
||||
},
|
||||
"strings": {
|
||||
"invites": "",
|
||||
"accounts": "",
|
||||
"settings": "",
|
||||
"inviteMonths": "",
|
||||
"inviteDays": "",
|
||||
"inviteHours": "",
|
||||
"inviteMinutes": "",
|
||||
"inviteNumberOfUses": "",
|
||||
"inviteDuration": "",
|
||||
"warning": "",
|
||||
"inviteInfiniteUsesWarning": "",
|
||||
"inviteSendToEmail": "",
|
||||
"create": "",
|
||||
"apply": "",
|
||||
"select": "",
|
||||
"name": "",
|
||||
"date": "",
|
||||
"setExpiry": "",
|
||||
"updates": "",
|
||||
"update": "",
|
||||
"download": "",
|
||||
"search": "",
|
||||
"advancedSettings": "",
|
||||
"lastActiveTime": "",
|
||||
"from": "",
|
||||
"after": "",
|
||||
"before": "",
|
||||
"user": "",
|
||||
"userExpiry": "",
|
||||
"userExpiryDescription": "",
|
||||
"aboutProgram": "",
|
||||
"version": "",
|
||||
"commitNoun": "",
|
||||
"newUser": "",
|
||||
"profile": "",
|
||||
"unknown": "",
|
||||
"label": "",
|
||||
"logs": "",
|
||||
"announce": "",
|
||||
"templates": "",
|
||||
"subject": "",
|
||||
"message": "",
|
||||
"variables": "",
|
||||
"conditionals": "",
|
||||
"preview": "",
|
||||
"reset": "Ripristino",
|
||||
"donate": "",
|
||||
"unlink": "",
|
||||
"sendPWR": "",
|
||||
"contactThrough": "",
|
||||
"extendExpiry": "",
|
||||
"sendPWRManual": "",
|
||||
"sendPWRSuccess": "",
|
||||
"sendPWRSuccessManual": "",
|
||||
"sendPWRValidFor": "",
|
||||
"customizeMessages": "",
|
||||
"customizeMessagesDescription": "",
|
||||
"markdownSupported": "",
|
||||
"modifySettings": "",
|
||||
"modifySettingsDescription": "",
|
||||
"applyHomescreenLayout": "",
|
||||
"sendDeleteNotificationEmail": "",
|
||||
"sendDeleteNotifiationExample": "",
|
||||
"settingsRestart": "",
|
||||
"settingsRestarting": "",
|
||||
"settingsRestartRequired": "",
|
||||
"settingsRestartRequiredDescription": "",
|
||||
"settingsApplyRestartLater": "",
|
||||
"settingsApplyRestartNow": "",
|
||||
"settingsApplied": "",
|
||||
"settingsRefreshPage": "",
|
||||
"settingsRequiredOrRestartMessage": "",
|
||||
"settingsSave": "",
|
||||
"ombiProfile": "",
|
||||
"ombiUserDefaultsDescription": "",
|
||||
"userProfiles": "",
|
||||
"userProfilesDescription": "",
|
||||
"userProfilesIsDefault": "",
|
||||
"userProfilesLibraries": "",
|
||||
"addProfile": "",
|
||||
"addProfileDescription": "",
|
||||
"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": ""
|
||||
},
|
||||
"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": ""
|
||||
},
|
||||
"quantityStrings": {
|
||||
"modifySettingsFor": {
|
||||
"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": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,6 @@
|
||||
"inviteUsersCreated": "Aangemaakte gebruikers",
|
||||
"inviteNoProfile": "Geen profiel",
|
||||
"inviteDateCreated": "Aangemaakt",
|
||||
"inviteRemainingUses": "Resterend aantal keer te gebruiken",
|
||||
"inviteNoInvites": "Geen",
|
||||
"inviteExpiresInTime": "Verloopt over {n}",
|
||||
"notifyEvent": "Meldingen:",
|
||||
|
||||
@@ -87,7 +87,6 @@
|
||||
"inviteUsersCreated": "",
|
||||
"inviteNoProfile": "",
|
||||
"inviteDateCreated": "Utworzone",
|
||||
"inviteRemainingUses": "",
|
||||
"inviteNoInvites": "",
|
||||
"inviteExpiresInTime": "",
|
||||
"notifyEvent": "",
|
||||
|
||||
@@ -54,7 +54,6 @@
|
||||
"inviteUsersCreated": "Usuários criado",
|
||||
"inviteNoProfile": "Sem Perfil",
|
||||
"inviteDateCreated": "Criado",
|
||||
"inviteRemainingUses": "Uso restantes",
|
||||
"inviteNoInvites": "Nenhum",
|
||||
"inviteExpiresInTime": "Expira em {n}",
|
||||
"notifyEvent": "Notificar em:",
|
||||
|
||||
@@ -65,7 +65,6 @@
|
||||
"inviteUsersCreated": "Skapade användare",
|
||||
"inviteNoProfile": "Ingen profil",
|
||||
"inviteDateCreated": "Skapad",
|
||||
"inviteRemainingUses": "Återstående användningar",
|
||||
"inviteNoInvites": "Ingen",
|
||||
"inviteExpiresInTime": "Går ut om {n}",
|
||||
"notifyEvent": "Meddela den:",
|
||||
|
||||
@@ -86,7 +86,6 @@
|
||||
"inviteUsersCreated": "Người dùng đã tạo",
|
||||
"inviteNoProfile": "Không có Tài khoản mẫu",
|
||||
"inviteDateCreated": "Tạo",
|
||||
"inviteRemainingUses": "Số lần sử dụng còn lại",
|
||||
"inviteNoInvites": "Không có",
|
||||
"inviteExpiresInTime": "Hết hạn trong {n}",
|
||||
"notifyEvent": "Thông báo khi:",
|
||||
|
||||
@@ -80,7 +80,6 @@
|
||||
"inviteUsersCreated": "已创建的用户",
|
||||
"inviteNoProfile": "没有个人资料",
|
||||
"inviteDateCreated": "已创建",
|
||||
"inviteRemainingUses": "剩余使用次数",
|
||||
"inviteNoInvites": "无",
|
||||
"inviteExpiresInTime": "在 {n} 到期",
|
||||
"notifyEvent": "通知:",
|
||||
@@ -103,7 +102,22 @@
|
||||
"sendPWRValidFor": "此链接有效30分钟。",
|
||||
"ombiProfile": "Ombi 用户配置文件",
|
||||
"accessJFASettings": "无法更改,因为“仅限管理员”或“允许所有”已在“设置”>“常规”中设置。",
|
||||
"accessJFA": "访问jfa-go"
|
||||
"accessJFA": "访问 jfa-go",
|
||||
"buildTime": "构建时间",
|
||||
"builtBy": "由",
|
||||
"clickToRemoveFilter": "单击此处以取消此筛选器。",
|
||||
"filters": "筛选器",
|
||||
"jellyfinID": "Jellyfin ID",
|
||||
"clearSearch": "清除搜索",
|
||||
"searchOptions": "搜索选项",
|
||||
"matchText": "匹配文本",
|
||||
"userPagePage": "用户页面",
|
||||
"actions": "操作",
|
||||
"after": "之后",
|
||||
"before": "之前",
|
||||
"unlink": "取消关联帐户",
|
||||
"sortingBy": "排序方式",
|
||||
"userPageLogin": "用户页面:登录"
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "更改了 {n} 的电子邮件地址。",
|
||||
@@ -201,4 +215,4 @@
|
||||
"singular": "为{n}用户设置到期时间"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,6 @@
|
||||
"inviteUsersCreated": "創建的帳戶",
|
||||
"inviteNoProfile": "無資料",
|
||||
"inviteDateCreated": "已創建",
|
||||
"inviteRemainingUses": "剩餘使用次數",
|
||||
"inviteNoInvites": "無",
|
||||
"inviteExpiresInTime": "在 {n} 到期",
|
||||
"notifyEvent": "通知:",
|
||||
|
||||
@@ -2,7 +2,64 @@
|
||||
"meta": {
|
||||
"name": "العربية (AR)"
|
||||
},
|
||||
"strings": {},
|
||||
"notifications": {},
|
||||
"quantityStrings": {}
|
||||
}
|
||||
"strings": {
|
||||
"username": "اسم المستخدم",
|
||||
"password": "كلمة المرور",
|
||||
"emailAddress": "البريد الالكتروني",
|
||||
"name": "الاسم",
|
||||
"submit": "ادخال",
|
||||
"success": "نجاح",
|
||||
"continue": "اكمل",
|
||||
"error": "خطأ",
|
||||
"copy": "نسخ",
|
||||
"time24h": "توقيت 24 ساعة",
|
||||
"time12h": "توقيت 12 ساعة",
|
||||
"linkTelegram": "رابط تلغرام",
|
||||
"contactTelegram": "التواصل عبر التلغرام",
|
||||
"linkDiscord": "رابط الدسكورد",
|
||||
"linkMatrix": "ربط Matrix",
|
||||
"contactDiscord": "التواصل عبر الدسكورد",
|
||||
"theme": "القالب",
|
||||
"refresh": "تحديث",
|
||||
"required": "مطلوب",
|
||||
"login": "تسجيل الدخول",
|
||||
"admin": "المسؤول",
|
||||
"reEnable": "اعادة تفعيل",
|
||||
"disable": "تجميد",
|
||||
"accountStatus": "حالة الحساب",
|
||||
"notSet": "لم تحدد",
|
||||
"expiry": "انتهاء الصلاحية",
|
||||
"add": "اضافة",
|
||||
"edit": "تعديل",
|
||||
"delete": "حذف",
|
||||
"myAccount": "حسابي",
|
||||
"disabled": "معطل",
|
||||
"enabled": "مفعل",
|
||||
"send": "ارسال",
|
||||
"copied": "تم النسخ",
|
||||
"contactEmail": "التواصل عبر البريد الالكتروني",
|
||||
"logout": "تسجيل الخروج",
|
||||
"contactMethods": "وسيلة التواصل"
|
||||
},
|
||||
"notifications": {
|
||||
"errorUnknown": "خطأ غير معروف.",
|
||||
"error401Unauthorized": "غير مخول. حاول تحديث الصفحة.",
|
||||
"errorSaveSettings": "لا يمكن حفظ الاعدادات.",
|
||||
"errorLoginBlank": "اسم المستخدم و/أو كلمة المرور لم يتم ادخالها.",
|
||||
"errorConnection": "لا يمكن الاتصال بـالبرنامج."
|
||||
},
|
||||
"quantityStrings": {
|
||||
"year": {
|
||||
"singular": "{n} سنة",
|
||||
"plural": "{n} سنوات"
|
||||
},
|
||||
"month": {
|
||||
"singular": "{n} شهر",
|
||||
"plural": "{n} أشهر"
|
||||
},
|
||||
"day": {
|
||||
"singular": "{n} يوم",
|
||||
"plural": "{n} أيام"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
67
lang/common/cs-cz.json
Normal file
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Čeština (CZ)"
|
||||
},
|
||||
"strings": {
|
||||
"username": "Uživatelské jméno",
|
||||
"password": "Heslo",
|
||||
"emailAddress": "Emailová adresa",
|
||||
"name": "Název",
|
||||
"submit": "Odeslat",
|
||||
"send": "Poslat",
|
||||
"success": "Hotovo",
|
||||
"continue": "Pokračovat",
|
||||
"error": "Chyba",
|
||||
"copy": "Kopírovat",
|
||||
"copied": "Zkopírováno",
|
||||
"time24h": "Čas 24 hodin",
|
||||
"time12h": "Čas 12 hodin",
|
||||
"linkTelegram": "Link Telegram",
|
||||
"contactEmail": "Kontakt přes Email",
|
||||
"contactTelegram": "Kontakt přes Telegram",
|
||||
"linkDiscord": "Link Discord",
|
||||
"linkMatrix": "Link Matrix",
|
||||
"contactDiscord": "Kontakt přes Discord",
|
||||
"theme": "Téma",
|
||||
"refresh": "Obnovit",
|
||||
"required": "Požadované",
|
||||
"login": "Přihlásit se",
|
||||
"logout": "Odhlásit se",
|
||||
"admin": "Admin",
|
||||
"enabled": "Povoleno",
|
||||
"disabled": "Zakázáno",
|
||||
"reEnable": "Znovu povolit",
|
||||
"disable": "Zakázat",
|
||||
"contactMethods": "Kontaktní metody",
|
||||
"accountStatus": "Stav účtu",
|
||||
"notSet": "Nenastaveno",
|
||||
"expiry": "Uplynutí",
|
||||
"add": "Přidat",
|
||||
"edit": "Upravit",
|
||||
"delete": "Vymazat",
|
||||
"myAccount": "Můj účet",
|
||||
"referrals": "Doporučení",
|
||||
"inviteRemainingUses": "Zbývající použití"
|
||||
},
|
||||
"notifications": {
|
||||
"errorLoginBlank": "Uživatelské jméno a/nebo heslo zůstalo prázdné.",
|
||||
"errorConnection": "Nelze se připojit k jfa-go.",
|
||||
"errorUnknown": "Neznámá chyba.",
|
||||
"error401Unauthorized": "Neoprávněný. Zkuste stránku obnovit.",
|
||||
"errorSaveSettings": "Nastavení se nepodařilo uložit."
|
||||
},
|
||||
"quantityStrings": {
|
||||
"year": {
|
||||
"singular": "{n} rok",
|
||||
"plural": "{n} let"
|
||||
},
|
||||
"month": {
|
||||
"singular": "{n} měsíc",
|
||||
"plural": "{n} měsíců"
|
||||
},
|
||||
"day": {
|
||||
"singular": "{n} den",
|
||||
"plural": "{n} dní"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,8 @@
|
||||
"expiry": "Udløb",
|
||||
"add": "Tilføj",
|
||||
"edit": "Rediger",
|
||||
"delete": "Slet"
|
||||
"delete": "Slet",
|
||||
"inviteRemainingUses": "Resterende anvendelser"
|
||||
},
|
||||
"notifications": {
|
||||
"errorLoginBlank": "Brugernavnet og/eller adgangskoden blev efterladt tomme.",
|
||||
|
||||
@@ -35,7 +35,8 @@
|
||||
"expiry": "Ablaufdatum",
|
||||
"add": "Hinzufügen",
|
||||
"edit": "Bearbeiten",
|
||||
"delete": "Löschen"
|
||||
"delete": "Löschen",
|
||||
"inviteRemainingUses": "Verbleibende Verwendungen"
|
||||
},
|
||||
"notifications": {
|
||||
"errorLoginBlank": "Der Benutzername und/oder das Passwort wurden nicht ausgefüllt.",
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
"disable": "Απενεργοποίηση",
|
||||
"expiry": "Λήξη",
|
||||
"edit": "Επεξεργασία",
|
||||
"delete": "Διαγραφή"
|
||||
"delete": "Διαγραφή",
|
||||
"inviteRemainingUses": "Εναπομείναντες χρήσεις"
|
||||
},
|
||||
"notifications": {
|
||||
"errorLoginBlank": "Το όνομα χρήστη και/ή ο κωδικός ήταν κενά.",
|
||||
|
||||
@@ -35,7 +35,8 @@
|
||||
"expiry": "Expiry",
|
||||
"add": "Add",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete"
|
||||
"delete": "Delete",
|
||||
"inviteRemainingUses": "Remaining uses"
|
||||
},
|
||||
"notifications": {
|
||||
"errorLoginBlank": "The username and/or password was left blank.",
|
||||
|
||||
@@ -39,7 +39,9 @@
|
||||
"add": "Add",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"myAccount": "My Account"
|
||||
"myAccount": "My Account",
|
||||
"referrals": "Referrals",
|
||||
"inviteRemainingUses": "Remaining uses"
|
||||
},
|
||||
"notifications": {
|
||||
"errorLoginBlank": "The username and/or password were left blank.",
|
||||
@@ -62,4 +64,4 @@
|
||||
"plural": "{n} Days"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,8 @@
|
||||
"expiry": "Expiración",
|
||||
"add": "Agregar",
|
||||
"edit": "Editar",
|
||||
"delete": "Eliminar"
|
||||
"delete": "Eliminar",
|
||||
"inviteRemainingUses": "Usos restantes"
|
||||
},
|
||||
"notifications": {
|
||||
"errorLoginBlank": "El nombre de usuario y/o la contraseña se dejaron en blanco.",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"strings": {
|
||||
"username": "Nom d'utilisateur",
|
||||
"password": "Mot de passe",
|
||||
"emailAddress": "Adresse e-mail",
|
||||
"emailAddress": "Adresse Email",
|
||||
"name": "Nom",
|
||||
"submit": "Soumettre",
|
||||
"send": "Envoyer",
|
||||
@@ -29,13 +29,19 @@
|
||||
"logout": "Se déconnecter",
|
||||
"admin": "Administrateur",
|
||||
"enabled": "Activé",
|
||||
"disabled": "Désactivé",
|
||||
"disabled": "Désactiver",
|
||||
"reEnable": "Ré-activé",
|
||||
"disable": "Désactivé",
|
||||
"expiry": "Expiration",
|
||||
"add": "Ajouter",
|
||||
"edit": "Éditer",
|
||||
"delete": "Effacer"
|
||||
"delete": "Effacer",
|
||||
"inviteRemainingUses": "Utilisations restantes",
|
||||
"accountStatus": "Statut du compte",
|
||||
"notSet": "Non défini",
|
||||
"myAccount": "Mon compte",
|
||||
"contactMethods": "Moyens de contact",
|
||||
"referrals": "Programme de parrainage"
|
||||
},
|
||||
"notifications": {
|
||||
"errorLoginBlank": "Le nom d'utilisateur et/ou le mot de passe sont vides.",
|
||||
@@ -44,5 +50,18 @@
|
||||
"error401Unauthorized": "Non autorisé. Essayez d'actualiser la page.",
|
||||
"errorSaveSettings": "Impossible d'enregistrer les paramètres."
|
||||
},
|
||||
"quantityStrings": {}
|
||||
}
|
||||
"quantityStrings": {
|
||||
"year": {
|
||||
"plural": "{n} années",
|
||||
"singular": "{n} année"
|
||||
},
|
||||
"day": {
|
||||
"singular": "{n} jour",
|
||||
"plural": "{n} jours"
|
||||
},
|
||||
"month": {
|
||||
"singular": "{n} mois",
|
||||
"plural": "{n} mois"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "English (US)"
|
||||
"name": "Magyar (HU)"
|
||||
},
|
||||
"strings": {
|
||||
"login": "Belépés",
|
||||
@@ -13,8 +13,53 @@
|
||||
"expiry": "Lejárat",
|
||||
"add": "Hozzáadás",
|
||||
"edit": "Szerkesztés",
|
||||
"delete": "Törlés"
|
||||
"delete": "Törlés",
|
||||
"password": "Jelszó",
|
||||
"username": "Felhasználónév",
|
||||
"emailAddress": "E-mail cím",
|
||||
"name": "Név",
|
||||
"submit": "Mentés",
|
||||
"send": "Küldés",
|
||||
"success": "Siker",
|
||||
"continue": "Folytatás",
|
||||
"error": "Hiba",
|
||||
"copy": "Másolás",
|
||||
"copied": "Másolva",
|
||||
"time24h": "24 órás idő",
|
||||
"time12h": "12 órás idő",
|
||||
"linkTelegram": "Telegram összekötése",
|
||||
"contactEmail": "Kapcsolat e-mailen keresztül",
|
||||
"contactTelegram": "Kapcsolat telegramon keresztül",
|
||||
"linkDiscord": "Discord összekötése",
|
||||
"linkMatrix": "Matrix összekötése",
|
||||
"contactDiscord": "Kapcsolat discordon keresztül",
|
||||
"theme": "Téma",
|
||||
"refresh": "Frissítés",
|
||||
"required": "Kötelező",
|
||||
"contactMethods": "Kapcsolati lehetőségek",
|
||||
"accountStatus": "Fiók státusz",
|
||||
"notSet": "Nincs beállítva",
|
||||
"myAccount": "Saját fiókom"
|
||||
},
|
||||
"notifications": {},
|
||||
"quantityStrings": {}
|
||||
}
|
||||
"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."
|
||||
},
|
||||
"quantityStrings": {
|
||||
"year": {
|
||||
"singular": "{n} Év",
|
||||
"plural": "{n} Évek"
|
||||
},
|
||||
"month": {
|
||||
"singular": "{n} Hónap",
|
||||
"plural": "{n} Hónapok"
|
||||
},
|
||||
"day": {
|
||||
"singular": "{n} Nap",
|
||||
"plural": "{n} Napok"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,14 +19,14 @@
|
||||
"login": "Masuk",
|
||||
"logout": "Keluar",
|
||||
"edit": "Edit",
|
||||
"delete": "Hapus"
|
||||
"delete": "Hapus",
|
||||
"inviteRemainingUses": "Penggunaan yang tersisa"
|
||||
},
|
||||
"notifications": {
|
||||
"errorLoginBlank": "Nama pengguna dan / atau sandi kosong.",
|
||||
"errorConnection": "Tidak dapat terhubung ke jfa-go.",
|
||||
"errorUnknown": "Kesalahan yang tidak diketahui.",
|
||||
"errorUnknown": "Terjadi kesalahan yang tidak diketahui.",
|
||||
"error401Unauthorized": "Tidak ter-otorisasi. Coba segarkan halaman.",
|
||||
"errorSaveSettings": "Tidak dapat menyimpan pengaturan."
|
||||
},
|
||||
"quantityStrings": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,13 +19,47 @@
|
||||
"linkTelegram": "Link Telegram",
|
||||
"contactEmail": "Contatta tramite Email",
|
||||
"contactTelegram": "Contatta tramite Telegram",
|
||||
"linkDiscord": "Link Discord",
|
||||
"linkDiscord": "Collega Discord",
|
||||
"linkMatrix": "Link Matrix",
|
||||
"contactDiscord": "Contatta tramite Discord",
|
||||
"theme": "Tema",
|
||||
"refresh": "Aggiorna",
|
||||
"required": "Richiesto"
|
||||
"required": "Richiesto",
|
||||
"accountStatus": "Stato Account",
|
||||
"login": "Login",
|
||||
"admin": "Admin",
|
||||
"enabled": "Abilitato",
|
||||
"disabled": "Disabilitato",
|
||||
"reEnable": "Riabilita",
|
||||
"disable": "Disattiva",
|
||||
"contactMethods": "Metodo di contatto",
|
||||
"notSet": "Non impostato",
|
||||
"expiry": "Scadenza",
|
||||
"add": "Aggiungi",
|
||||
"edit": "Modifica",
|
||||
"delete": "Elimina",
|
||||
"myAccount": "Il mio Account",
|
||||
"logout": "Esci"
|
||||
},
|
||||
"notifications": {},
|
||||
"quantityStrings": {}
|
||||
"notifications": {
|
||||
"errorUnknown": "Errore sconosciuto.",
|
||||
"errorLoginBlank": "L'username o password sono stati lasciati vuoti.",
|
||||
"errorConnection": "Non riesco a connettermi a jfa-go.",
|
||||
"error401Unauthorized": "Non autorizzato. Prova a ricaricare la pagina.",
|
||||
"errorSaveSettings": "Impossibile salvare impostazione."
|
||||
},
|
||||
"quantityStrings": {
|
||||
"year": {
|
||||
"singular": "{n} Anno",
|
||||
"plural": "{n} Anni"
|
||||
},
|
||||
"month": {
|
||||
"singular": "{n} Mese",
|
||||
"plural": "{n} Mesi"
|
||||
},
|
||||
"day": {
|
||||
"singular": "{n} Giorno",
|
||||
"plural": "{n} Giorni"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
8
lang/common/nds.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Nedderdütsch (NDS)"
|
||||
},
|
||||
"strings": {},
|
||||
"notifications": {},
|
||||
"quantityStrings": {}
|
||||
}
|
||||
@@ -35,7 +35,8 @@
|
||||
"expiry": "Verloop",
|
||||
"add": "Voeg toe",
|
||||
"edit": "Bewerken",
|
||||
"delete": "Verwijderen"
|
||||
"delete": "Verwijderen",
|
||||
"inviteRemainingUses": "Resterend aantal keer te gebruiken"
|
||||
},
|
||||
"notifications": {
|
||||
"errorLoginBlank": "De gebruikersnaam en/of wachtwoord is leeg.",
|
||||
|
||||
@@ -35,7 +35,8 @@
|
||||
"expiry": "Expira",
|
||||
"add": "Adicionar",
|
||||
"edit": "Editar",
|
||||
"delete": "Deletar"
|
||||
"delete": "Deletar",
|
||||
"inviteRemainingUses": "Uso restantes"
|
||||
},
|
||||
"notifications": {
|
||||
"errorLoginBlank": "O nome de usuário e/ou senha foram deixados em branco.",
|
||||
|
||||
@@ -28,4 +28,4 @@
|
||||
},
|
||||
"notifications": {},
|
||||
"quantityStrings": {}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,23 @@
|
||||
"disabled": "Inaktiverad",
|
||||
"expiry": "Löper ut",
|
||||
"edit": "Redigera",
|
||||
"delete": "Radera"
|
||||
"delete": "Radera",
|
||||
"inviteRemainingUses": "Återstående användningar",
|
||||
"send": "Skicka",
|
||||
"linkDiscord": "Länka Discord",
|
||||
"copied": "Kopierat",
|
||||
"linkTelegram": "Länka Telegram",
|
||||
"contactEmail": "Kontakta via e-post",
|
||||
"contactTelegram": "Kontakta via Telegram",
|
||||
"refresh": "Uppdatera",
|
||||
"required": "Obligatoriskt",
|
||||
"contactDiscord": "Kontakt via Discord",
|
||||
"linkMatrix": "Länka Matrix",
|
||||
"reEnable": "Återaktivera",
|
||||
"disable": "Inaktivera",
|
||||
"contactMethods": "Kontaktmetoder",
|
||||
"accountStatus": "Kontostatus",
|
||||
"notSet": "Inte inställt"
|
||||
},
|
||||
"notifications": {
|
||||
"errorLoginBlank": "Användarnamnet och/eller lösenordet lämnades tomt.",
|
||||
@@ -30,6 +46,5 @@
|
||||
"errorUnknown": "Okänt fel.",
|
||||
"error401Unauthorized": "Obehörig. Prova att uppdatera sidan.",
|
||||
"errorSaveSettings": "Det gick inte att spara inställningarna."
|
||||
},
|
||||
"quantityStrings": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
"expiry": "Hết hạn",
|
||||
"add": "Thêm",
|
||||
"edit": "Chỉnh sửa",
|
||||
"delete": "Xóa"
|
||||
"delete": "Xóa",
|
||||
"inviteRemainingUses": "Số lần sử dụng còn lại"
|
||||
},
|
||||
"notifications": {
|
||||
"errorConnection": "Không thể kết nối với jfa-go.",
|
||||
|
||||
@@ -35,7 +35,12 @@
|
||||
"expiry": "到期",
|
||||
"add": "添加",
|
||||
"edit": "编辑",
|
||||
"delete": "删除"
|
||||
"delete": "删除",
|
||||
"inviteRemainingUses": "剩余使用次数",
|
||||
"contactMethods": "联系方式",
|
||||
"accountStatus": "帐户状态",
|
||||
"notSet": "未设置",
|
||||
"myAccount": "我的帐户"
|
||||
},
|
||||
"notifications": {
|
||||
"errorLoginBlank": "用户名/密码留空。",
|
||||
@@ -44,5 +49,18 @@
|
||||
"error401Unauthorized": "无授权。尝试刷新页面。",
|
||||
"errorSaveSettings": "无法保存设置。"
|
||||
},
|
||||
"quantityStrings": {}
|
||||
}
|
||||
"quantityStrings": {
|
||||
"day": {
|
||||
"plural": "{n} 天",
|
||||
"singular": "{n} 天"
|
||||
},
|
||||
"month": {
|
||||
"singular": "{n} 月",
|
||||
"plural": "{n} 月"
|
||||
},
|
||||
"year": {
|
||||
"singular": "{n} 年",
|
||||
"plural": "{n} 年"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,8 @@
|
||||
"expiry": "到期",
|
||||
"add": "添加",
|
||||
"edit": "編輯",
|
||||
"delete": "刪除"
|
||||
"delete": "刪除",
|
||||
"inviteRemainingUses": "剩餘使用次數"
|
||||
},
|
||||
"notifications": {
|
||||
"errorLoginBlank": "帳戶名稱和/或密碼留空。",
|
||||
|
||||
77
lang/email/ar-aa.json
Normal file
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "العربية (AR)"
|
||||
},
|
||||
"strings": {
|
||||
"ifItWasNotYou": "اذا لم يكن هذا انت، الرجاء تجاهل هذا.",
|
||||
"helloUser": "مرحباً {username}،",
|
||||
"reason": "السبب"
|
||||
},
|
||||
"userCreated": {
|
||||
"name": "إنشاء حساب",
|
||||
"title": "ملاحظة: تم إنشاء الحساب",
|
||||
"aUserWasCreated": "تم إنشاء الحساب بواسطة الرمز {code}.",
|
||||
"time": "الوقت",
|
||||
"notificationNotice": "ملاحظة: الرسائل التذكيرية يمكن تعديلها في لوحة التحكم."
|
||||
},
|
||||
"inviteExpiry": {
|
||||
"name": "انتهاء صلاحية الدعوة",
|
||||
"title": "ملاحظة: انتهت صلاحية الدعوة",
|
||||
"inviteExpired": "انتهت صلاحية الدعوة.",
|
||||
"expiredAt": "انتهت صلاحية الرمز {code} في {time} .",
|
||||
"notificationNotice": "ملاحظة: الرسائل التذكيرية يمكن تعديلها في لوحة التحكم."
|
||||
},
|
||||
"passwordReset": {
|
||||
"name": "إعادة تعيين كلمة المرور",
|
||||
"title": "تم طلب إعادة تعيين كلمة المرور - Jellyfin",
|
||||
"someoneHasRequestedReset": "قام شخص ما بطلب إعادة تعيين كلمة المرور مؤخرا.",
|
||||
"ifItWasYou": "إذا كان هذا انت، أدخل رمز التعريف الشخصي أدناه في الخانة.",
|
||||
"ifItWasYouLink": "إذا كان هذا انت، اضغط على الرابط أدناه.",
|
||||
"codeExpiry": "ستنتهي صلاحية الرمز في {date}، {time} UTC، خلال {expiresInMinutes}.",
|
||||
"pin": "رمز التعريف الشخصي"
|
||||
},
|
||||
"userDeleted": {
|
||||
"name": "حذف المستخدم",
|
||||
"title": "لقد تم حذف حسابك - Jellyfin",
|
||||
"yourAccountWasDeleted": "لقد تم حذف حسابك في Jellyfin."
|
||||
},
|
||||
"userDisabled": {
|
||||
"name": "تعطيل المستخدم",
|
||||
"title": "لقد تم تعطيل حسابك - Jellyfin",
|
||||
"yourAccountWasDisabled": "لقد تم تعطيل حسابك."
|
||||
},
|
||||
"userEnabled": {
|
||||
"name": "تفعيل المستخدم",
|
||||
"title": "لقد تم تفعيل حسابك - Jellyfin",
|
||||
"yourAccountWasEnabled": "لقد تم تفعيل حسابك."
|
||||
},
|
||||
"inviteEmail": {
|
||||
"name": "دعوة البريد الإلكتروني",
|
||||
"title": "دعوة - Jellyfin",
|
||||
"hello": "مرحباً",
|
||||
"youHaveBeenInvited": "تمت دعوتك إلى Jellyfin.",
|
||||
"toJoin": "للإنضمام، اتبع الرابط أدناه.",
|
||||
"inviteExpiry": "ستنتهي صلاحية الدعوة في {date} {time}، خلال {expiresInMinutes}، اتخذ اجراءاً.",
|
||||
"linkButton": "قم بإعداد حسابك"
|
||||
},
|
||||
"welcomeEmail": {
|
||||
"name": "مرحباً",
|
||||
"title": "مرحباً في Jellyfin",
|
||||
"welcome": "مرحباً في Jellyfin!",
|
||||
"youCanLoginWith": "يمكنك تسجيل الدخول بإستخدام المعلومات أدناه",
|
||||
"yourAccountWillExpire": "ستنتهي صلاحية حسابك في {date}.",
|
||||
"jellyfinURL": "رابط"
|
||||
},
|
||||
"emailConfirmation": {
|
||||
"name": "بريد التحقق",
|
||||
"title": "قم بتأكيد حسابك - Jellyfin",
|
||||
"clickBelow": "اضغط الرابط ادناه لتأكيد حسابك والبدء في استخدام Jellyfin.",
|
||||
"confirmEmail": "تأكيد البريد الإلكتروني"
|
||||
},
|
||||
"userExpired": {
|
||||
"name": "انتهاء صلاحية المستخدم",
|
||||
"title": "انتهت صلاحية حسابك - Jellyfin",
|
||||
"yourAccountHasExpired": "انتهت صلاحية حسابك.",
|
||||
"contactTheAdmin": "تواصل مع المشرفين للمزيد من المعلومات."
|
||||
}
|
||||
}
|
||||
77
lang/email/cs-cz.json
Normal file
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Čeština (CZ)"
|
||||
},
|
||||
"strings": {
|
||||
"ifItWasNotYou": "Pokud jste to nebyl vy, ignorujte to.",
|
||||
"helloUser": "Ahoj {username},",
|
||||
"reason": "Důvod"
|
||||
},
|
||||
"userCreated": {
|
||||
"name": "Vytvoření uživatele",
|
||||
"title": "Upozornění: Uživatel vytvořen",
|
||||
"aUserWasCreated": "Uživatel byl vytvořen pomocí kódu {code}.",
|
||||
"time": "Čas",
|
||||
"notificationNotice": "Poznámka: Zprávy s upozorněním lze přepínat na řídicím panelu správce."
|
||||
},
|
||||
"inviteExpiry": {
|
||||
"name": "Platnost pozvánky",
|
||||
"title": "Upozornění: Platnost pozvánky vypršela",
|
||||
"inviteExpired": "Platnost pozvánky vypršela.",
|
||||
"expiredAt": "Platnost kódu {code} vypršela v {time}.",
|
||||
"notificationNotice": "Poznámka: Zprávy s upozorněním lze přepínat na řídicím panelu správce."
|
||||
},
|
||||
"passwordReset": {
|
||||
"name": "Resetovat heslo",
|
||||
"title": "Požadováno resetování hesla - Jellyfin",
|
||||
"someoneHasRequestedReset": "Někdo nedávno požádal o reset hesla na Jellyfin.",
|
||||
"ifItWasYou": "Pokud jste to byli vy, zadejte do výzvy níže uvedený kód PIN.",
|
||||
"ifItWasYouLink": "Pokud jste to byli vy, klikněte na odkaz níže.",
|
||||
"codeExpiry": "Platnost kódu vyprší {date} v {time} UTC, což je za {expiresInMinutes}.",
|
||||
"pin": "PIN"
|
||||
},
|
||||
"userDeleted": {
|
||||
"name": "Smazání uživatele",
|
||||
"title": "Váš účet byl smazán - Jellyfin",
|
||||
"yourAccountWasDeleted": "Váš účet Jellyfin byl smazán."
|
||||
},
|
||||
"userDisabled": {
|
||||
"name": "Uživatel zakázán",
|
||||
"title": "Váš účet byl deaktivován - Jellyfin",
|
||||
"yourAccountWasDisabled": "Váš účet byl deaktivován."
|
||||
},
|
||||
"userEnabled": {
|
||||
"name": "Uživatel povolen",
|
||||
"title": "Váš účet byl znovu aktivován - Jellyfin",
|
||||
"yourAccountWasEnabled": "Váš účet byl znovu aktivován."
|
||||
},
|
||||
"inviteEmail": {
|
||||
"name": "Pozvací e-mail",
|
||||
"title": "Pozvat - Jellyfin",
|
||||
"hello": "Ahoj",
|
||||
"youHaveBeenInvited": "Byli jste pozváni do Jellyfinu.",
|
||||
"toJoin": "Chcete-li se připojit, postupujte podle níže uvedeného odkazu.",
|
||||
"inviteExpiry": "Platnost této pozvánky vyprší {date} v {time}, což je za {expiresInMinutes}, proto jednejte rychle.",
|
||||
"linkButton": "Nastavte si účet"
|
||||
},
|
||||
"welcomeEmail": {
|
||||
"name": "Vítejte",
|
||||
"title": "Vítejte v Jellyfin",
|
||||
"welcome": "Vítejte v Jellyfin!",
|
||||
"youCanLoginWith": "Přihlásit se můžete pomocí níže uvedených údajů",
|
||||
"yourAccountWillExpire": "Platnost vašeho účtu vyprší dne {date}.",
|
||||
"jellyfinURL": "URL"
|
||||
},
|
||||
"emailConfirmation": {
|
||||
"name": "Potvrzující email",
|
||||
"title": "Potvrďte svůj email - Jellyfin",
|
||||
"clickBelow": "Kliknutím na odkaz níže potvrďte svou e-mailovou adresu a začněte používat Jellyfin.",
|
||||
"confirmEmail": "Potvrdit email"
|
||||
},
|
||||
"userExpired": {
|
||||
"name": "Vypršení platnosti uživatele",
|
||||
"title": "Platnost vašeho účtu vypršela – Jellyfin",
|
||||
"yourAccountHasExpired": "Platnost vašeho účtu vypršela.",
|
||||
"contactTheAdmin": "Pro více informací kontaktujte administrátora."
|
||||
}
|
||||
}
|
||||
77
lang/email/hu-hu.json
Normal file
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Magyar (HU)"
|
||||
},
|
||||
"strings": {
|
||||
"ifItWasNotYou": "",
|
||||
"helloUser": "",
|
||||
"reason": ""
|
||||
},
|
||||
"userCreated": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"aUserWasCreated": "",
|
||||
"time": "",
|
||||
"notificationNotice": ""
|
||||
},
|
||||
"inviteExpiry": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"inviteExpired": "",
|
||||
"expiredAt": "",
|
||||
"notificationNotice": ""
|
||||
},
|
||||
"passwordReset": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"someoneHasRequestedReset": "",
|
||||
"ifItWasYou": "",
|
||||
"ifItWasYouLink": "",
|
||||
"codeExpiry": "",
|
||||
"pin": ""
|
||||
},
|
||||
"userDeleted": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"yourAccountWasDeleted": ""
|
||||
},
|
||||
"userDisabled": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"yourAccountWasDisabled": ""
|
||||
},
|
||||
"userEnabled": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"yourAccountWasEnabled": ""
|
||||
},
|
||||
"inviteEmail": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"hello": "",
|
||||
"youHaveBeenInvited": "",
|
||||
"toJoin": "",
|
||||
"inviteExpiry": "",
|
||||
"linkButton": ""
|
||||
},
|
||||
"welcomeEmail": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"welcome": "",
|
||||
"youCanLoginWith": "",
|
||||
"yourAccountWillExpire": "",
|
||||
"jellyfinURL": ""
|
||||
},
|
||||
"emailConfirmation": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"clickBelow": "",
|
||||
"confirmEmail": ""
|
||||
},
|
||||
"userExpired": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"yourAccountHasExpired": "",
|
||||
"contactTheAdmin": ""
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Italiano (IT)"
|
||||
},
|
||||
"strings": {
|
||||
"ifItWasNotYou": "Se non sei stato tu, puoi ignorare questa email.",
|
||||
"ifItWasNotYou": "Se non sei stato tu, puoi ignorare questo.",
|
||||
"helloUser": "Ciao {username},",
|
||||
"reason": "Motivo"
|
||||
},
|
||||
@@ -11,42 +11,67 @@
|
||||
"title": "Nota: Utente creato",
|
||||
"aUserWasCreated": "Un utente è stato creato usando il codice {code}.",
|
||||
"time": "Tempo",
|
||||
"notificationNotice": "Nota: Le notifiche via email possono essere attivate nel pannello di admin."
|
||||
"notificationNotice": "Nota: I messaggi di notifica possono essere attivati nel pannello admin.",
|
||||
"name": "Creazione utente"
|
||||
},
|
||||
"inviteExpiry": {
|
||||
"title": "Nota: Invito scaduto",
|
||||
"inviteExpired": "Invito scaduto.",
|
||||
"expiredAt": "Il codice {code} è scaduto il {time}.",
|
||||
"notificationNotice": "Nota: le e-mail di notifica possono essere attivate dal pannello di admin."
|
||||
"notificationNotice": "Nota: I messaggi di notifica possono essere attivati dal pannello admin.",
|
||||
"name": "Scadenza invito"
|
||||
},
|
||||
"passwordReset": {
|
||||
"title": "Richiesto un reset della password - Jellyfin",
|
||||
"someoneHasRequestedReset": "Qualcuno ha recentemente richiesto un reset della password su Jellyfin.",
|
||||
"ifItWasYou": "Se sei stato tu, scrivi il PIN sotto alla richiesta.",
|
||||
"codeExpiry": "Il codice scadrà in {date}, alle {time} UTC, che è alle {expiresInMinutes}.",
|
||||
"pin": "PIN"
|
||||
"pin": "PIN",
|
||||
"name": "Ripristino password",
|
||||
"ifItWasYouLink": "Se sei stato te, clicca il link sottostante."
|
||||
},
|
||||
"userDeleted": {
|
||||
"title": "Il tuo account è stato eliminato - Jellyfin",
|
||||
"yourAccountWasDeleted": "Il tuo account di Jellyfin è stato eliminato."
|
||||
"yourAccountWasDeleted": "Il tuo account di Jellyfin è stato eliminato.",
|
||||
"name": "Eliminazione utente"
|
||||
},
|
||||
"inviteEmail": {
|
||||
"title": "Invita - Jellyfin",
|
||||
"hello": "Salve",
|
||||
"youHaveBeenInvited": "Sei stato inviatato su Jellyfin.",
|
||||
"toJoin": "Per entrare, segui il link sotto.",
|
||||
"inviteExpiry": "",
|
||||
"linkButton": ""
|
||||
"inviteExpiry": "Questo invito scadrà il {date} alle {time}, quindi in {expiresInMinutes}, agisci in fretta.",
|
||||
"linkButton": "Imposta il tuo account",
|
||||
"name": "Email di invito"
|
||||
},
|
||||
"welcomeEmail": {
|
||||
"title": "",
|
||||
"welcome": "",
|
||||
"youCanLoginWith": "",
|
||||
"jellyfinURL": ""
|
||||
"title": "Benvenuto su Jellyfin",
|
||||
"welcome": "Benvenuto su Jellyfin!",
|
||||
"youCanLoginWith": "Puoi effettuare il login con i dettagli sottostanti",
|
||||
"jellyfinURL": "URL",
|
||||
"yourAccountWillExpire": "Il tuo account scadrà il {date}.",
|
||||
"name": "Benvenuto"
|
||||
},
|
||||
"emailConfirmation": {
|
||||
"title": "",
|
||||
"clickBelow": "",
|
||||
"confirmEmail": ""
|
||||
"title": "Conferma la tua email - Jellyfin",
|
||||
"clickBelow": "Clicca il link qui sotto per confermare il tuo indirizzo email e iniziare ad usare Jellyfin.",
|
||||
"confirmEmail": "Conferma Email",
|
||||
"name": "Email di conferma"
|
||||
},
|
||||
"userExpired": {
|
||||
"name": "Scadenza utente",
|
||||
"title": "Il tuo account è scaduto - Jellyfin",
|
||||
"yourAccountHasExpired": "Il tuo account è scaduto.",
|
||||
"contactTheAdmin": "Contatta l’amministratore per più info."
|
||||
},
|
||||
"userDisabled": {
|
||||
"yourAccountWasDisabled": "Il tuo account è stato disabilitato.",
|
||||
"name": "Utente disabilitato",
|
||||
"title": "Il tuo account è stato disabilitato - Jellyfin"
|
||||
},
|
||||
"userEnabled": {
|
||||
"name": "Utente abilitato",
|
||||
"title": "Il tuo account è stato riabilitato - Jellyfin",
|
||||
"yourAccountWasEnabled": "Il tuo account è stato riabilitato."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,30 +3,45 @@
|
||||
"name": "العربية (AR)"
|
||||
},
|
||||
"strings": {
|
||||
"pageTitle": "أنشاء حساب",
|
||||
"createAccountHeader": "أنشاء حساب",
|
||||
"pageTitle": "إنشاء حساب Jellyfin",
|
||||
"createAccountHeader": "إنشاء حساب",
|
||||
"accountDetails": "التفاصيل",
|
||||
"emailAddress": "البريد الالكتروني",
|
||||
"emailAddress": "البريد الإلكتروني",
|
||||
"username": "اسم المستخدم",
|
||||
"password": "كلمة المرور",
|
||||
"reEnterPassword": "تأكيد كلمة المرور",
|
||||
"reEnterPasswordInvalid": "كلمة المرور غير مطابقة.",
|
||||
"createAccountButton": "أنشاء حساب",
|
||||
"createAccountButton": "إنشاء الحساب",
|
||||
"passwordRequirementsHeader": "متطلبات كلمة المرور",
|
||||
"successHeader": "تم!",
|
||||
"confirmationRequired": "مطلوب تأكيد البريد الإلكتروني",
|
||||
"confirmationRequiredMessage": "يرجى التحقق من صندوق البريد الإلكتروني الخاص بك للتحقق من عنوانك.",
|
||||
"yourAccountIsValidUntil": "سيكون حسابك ساري المفعول حتى {date}.",
|
||||
"sendPIN": "أرسل رقم التعريف الشخصي أدناه إلى الروبوت ، ثم ارجع إلى هنا لربط حسابك.",
|
||||
"sendPIN": "أرسل رقم التعريف الشخصي أدناه إلى البوت ، ثم عُد إلى هنا لربط حسابك.",
|
||||
"sendPINDiscord": "اكتب {command} في {server_channel} على Discord ، ثم أرسل رقم التعريف الشخصي أدناه.",
|
||||
"matrixEnterUser": "أدخل معرف المستخدم الخاص بك ، واضغط على إرسال ، وسيتم إرسال رمز PIN إليك. أدخله هنا للمتابعة."
|
||||
"matrixEnterUser": "أدخل معرف المستخدم الخاص بك، واضغط على إرسال، وسيتم إرسال رمز PIN إليك. أدخله هنا للمتابعة.",
|
||||
"oldPassword": "كلمة المرور السابقة",
|
||||
"newPassword": "كلمة المرور الجديدة",
|
||||
"joinTheServer": "انضم إلى السيرفر:",
|
||||
"editContactMethod": "تحرير طريقة الاتصال",
|
||||
"customMessagePlaceholderHeader": "تحرير هذه البطاقة",
|
||||
"resetPassword": "إعادة تعيين كلمة المرور",
|
||||
"resetSent": "تم إرسال إعادة التعيين.",
|
||||
"resetSentDescription": "في حال وجود حساب يملك اسم المستخدم/طريقة الاتصال المحددة، فسيتم إرسال رابط إعادة تعيين كلمة المرور عبر جميع طرق الاتصال المتاحة.ستنتهي صلاحية الرمز خلال 30 دقيقة.",
|
||||
"changePassword": "تغيير كلمة المرور",
|
||||
"welcomeUser": "مرحباً، {user}!",
|
||||
"addContactMethod": "إضافة طريقة اتصال",
|
||||
"customMessagePlaceholderContent": "اضغط على زر تحرير صفحة المستخدم في الإعدادات لتخصيص هذه البطاقة، أو أظهر واحدة على صفحة تسجيل الدخول، ولا تقلق، لا يستطيع المستخدم رؤيتها.",
|
||||
"userPageSuccessMessage": "تستطيع رؤية وتحرير التفاصيل حول حسابك لاحقاً في صفحة {myAccount}.",
|
||||
"resetPasswordThroughJellyfin": "لإعادة تعيين كلمة المرور، قم بزيارة {jfLink} واضغط على زر \"نسيت كلمة المرور\".",
|
||||
"resetPasswordThroughLink": "لإعادة تعيين كلمة المرور، أدخل اسم المستخدم، البريد الإلكتروني، أو اسم مستخدم لطريقة اتصال مرتبطة، ثم أرسل. سيتم إرسال رابط لإعادة تعيين كلمة المرور."
|
||||
},
|
||||
"notifications": {
|
||||
"errorUserExists": "المستخدم موجود مسبقا.",
|
||||
"errorInvalidCode": "رمز دعوة غير صالح.",
|
||||
"errorTelegramVerification": "مطلوب التحقق من Telegram.",
|
||||
"errorDiscordVerification": "مطلوب التحقق من الخلاف.",
|
||||
"errorMatrixVerification": "مطلوب التحقق من المصفوفة.",
|
||||
"errorTelegramVerification": "مطلوب التحقق من تيليجرام.",
|
||||
"errorDiscordVerification": "مطلوب التحقق من الدسكورد.",
|
||||
"errorMatrixVerification": "مطلوب التحقق من Matrix.",
|
||||
"errorInvalidPIN": "رقم التعريف الشخصي غير صالح.",
|
||||
"errorUnknown": "خطأ غير معروف.",
|
||||
"errorNoEmail": "البريد الإلكتروني مطلوب.",
|
||||
@@ -34,8 +49,10 @@
|
||||
"errorPassword": "تحقق من متطلبات كلمة المرور.",
|
||||
"errorNoMatch": "كلمات المرور غير متطابقة.",
|
||||
"verified": "تم التحقق من الحساب.",
|
||||
"errorAccountLinked": "الحساب موجود مسبقا.",
|
||||
"errorEmailLinked": "الايميل مستعمل مسبقا."
|
||||
"errorAccountLinked": "الحساب قيد الاستخدام.",
|
||||
"errorEmailLinked": "البريد الإلكتروني قيد الاستخدام.",
|
||||
"errorOldPassword": "كلمة المرور القديمة غير صحيحة.",
|
||||
"passwordChanged": "تم تغيير كلمة المرور."
|
||||
},
|
||||
"validationStrings": {
|
||||
"length": {
|
||||
@@ -59,4 +76,4 @@
|
||||
"plural": "يجب أن يتألف من {n} حرف خاص على الأقل"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
82
lang/form/cs-cz.json
Normal file
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Čeština (CZ)"
|
||||
},
|
||||
"strings": {
|
||||
"pageTitle": "Vytvořte účet Jellyfin",
|
||||
"createAccountHeader": "Vytvořit účet",
|
||||
"accountDetails": "Podrobnosti",
|
||||
"emailAddress": "Email",
|
||||
"username": "Uživatelské jméno",
|
||||
"oldPassword": "Staré heslo",
|
||||
"newPassword": "Nové heslo",
|
||||
"password": "Heslo",
|
||||
"reEnterPassword": "Znovu zadejte heslo",
|
||||
"reEnterPasswordInvalid": "Hesla nejsou stejná.",
|
||||
"createAccountButton": "Vytvořit účet",
|
||||
"passwordRequirementsHeader": "Požadavky na heslo",
|
||||
"successHeader": "Hotovo!",
|
||||
"confirmationRequired": "Vyžaduje se potvrzení e-mailem",
|
||||
"confirmationRequiredMessage": "Zkontrolujte prosím svou e-mailovou schránku a ověřte svou adresu.",
|
||||
"yourAccountIsValidUntil": "Váš účet bude platný do {date}.",
|
||||
"sendPIN": "Odešlete robotovi níže uvedený PIN a poté se sem vraťte a propojte svůj účet.",
|
||||
"sendPINDiscord": "Napište {command} do {server_channel} na Discordu a poté odešlete PIN níže.",
|
||||
"matrixEnterUser": "Zadejte své uživatelské ID, stiskněte Odeslat a bude vám zaslán PIN. Chcete-li pokračovat, zadejte jej zde.",
|
||||
"welcomeUser": "Vítejte, {user}!",
|
||||
"addContactMethod": "Přidat metodu kontaktu",
|
||||
"editContactMethod": "Upravit metodu kontaktu",
|
||||
"joinTheServer": "Připojte se na server:",
|
||||
"customMessagePlaceholderHeader": "Přizpůsobte si tuto kartu",
|
||||
"customMessagePlaceholderContent": "Kliknutím na tlačítko upravit stránku uživatele v nastavení můžete přizpůsobit tuto kartu nebo ji zobrazit na přihlašovací obrazovce a nebojte se, uživatel to nevidí.",
|
||||
"userPageSuccessMessage": "Podrobnosti o svém účtu můžete později zobrazit a změnit na stránce {myAccount}.",
|
||||
"resetPassword": "Obnovit heslo",
|
||||
"resetPasswordThroughJellyfin": "Chcete-li obnovit heslo, navštivte {jfLink} a stiskněte tlačítko \"Zapomenuté heslo\".",
|
||||
"resetPasswordThroughLink": "Chcete-li obnovit heslo, zadejte své uživatelské jméno, e-mailovou adresu nebo uživatelské jméno propojené kontaktní metody a odešlete. Bude odeslán odkaz pro resetování hesla.",
|
||||
"resetSent": "Resetování odesláno.",
|
||||
"resetSentDescription": "Pokud existuje účet s daným uživatelským jménem/způsobem kontaktu, byl prostřednictvím všech dostupných způsobů kontaktu odeslán odkaz pro resetování hesla. Platnost kódu vyprší za 30 minut.",
|
||||
"changePassword": "Změnit heslo",
|
||||
"referralsDescription": "Pozvěte přátele a rodinu do Jellyfin pomocí tohoto odkazu. Vraťte se sem pro nový, pokud vyprší.",
|
||||
"copyReferral": "Kopírovat odkaz",
|
||||
"invitedBy": "Pozval vás uživatel {user}."
|
||||
},
|
||||
"notifications": {
|
||||
"errorUserExists": "Uživatel již existuje.",
|
||||
"errorInvalidCode": "Neplatný zvací kód.",
|
||||
"errorAccountLinked": "Účet se již používá.",
|
||||
"errorEmailLinked": "Email je již používán.",
|
||||
"errorTelegramVerification": "Je vyžadováno ověření telegramem.",
|
||||
"errorDiscordVerification": "Vyžaduje se ověření neshody.",
|
||||
"errorMatrixVerification": "Vyžaduje se ověření matice.",
|
||||
"errorInvalidPIN": "PIN je neplatný.",
|
||||
"errorUnknown": "Neznámá chyba.",
|
||||
"errorNoEmail": "Email je vyžadován.",
|
||||
"errorCaptcha": "Captcha je nesprávná.",
|
||||
"errorPassword": "Zkontrolujte požadavky na heslo.",
|
||||
"errorNoMatch": "Hesla se neshodují.",
|
||||
"errorOldPassword": "Staré heslo je nesprávné.",
|
||||
"passwordChanged": "Heslo změněno.",
|
||||
"verified": "Účet ověřen."
|
||||
},
|
||||
"validationStrings": {
|
||||
"length": {
|
||||
"singular": "Musí mít alespoň {n} znak",
|
||||
"plural": "Musí mít nejméně {n} znaků"
|
||||
},
|
||||
"uppercase": {
|
||||
"singular": "Musí mít alespoň {n} velkých písmen",
|
||||
"plural": "Musí obsahovat alespoň {n} velkých písmen"
|
||||
},
|
||||
"lowercase": {
|
||||
"singular": "Musí mít alespoň {n} malých písmen",
|
||||
"plural": "Musí obsahovat alespoň {n} malých písmen"
|
||||
},
|
||||
"number": {
|
||||
"singular": "Musí mít alespoň {n} číslo",
|
||||
"plural": "Musí mít alespoň {n} čísel"
|
||||
},
|
||||
"special": {
|
||||
"singular": "Musí mít alespoň {n} speciálních znaků",
|
||||
"plural": "Musí obsahovat alespoň {n} speciálních znaků"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,18 @@
|
||||
"yourAccountIsValidUntil": "Dein Konto wird bis zum {date} gültig sein.",
|
||||
"sendPIN": "Sende die untenstehende PIN an den Bot und komm dann hierher zurück, um dein Konto zu verbinden.",
|
||||
"sendPINDiscord": "Gib auf Discord {command} in {server_channel} ein und sende die untenstehende PIN.",
|
||||
"matrixEnterUser": "Gib deine Benutzer-ID ein und drücke auf Absenden. Anschließend erhälst du ein PIN, die hier eingegeben wird um fortzufahren."
|
||||
"matrixEnterUser": "Gib deine Benutzer-ID ein und drücke auf Absenden. Anschließend erhälst du ein PIN, die hier eingegeben wird um fortzufahren.",
|
||||
"oldPassword": "Altes Passwort",
|
||||
"customMessagePlaceholderHeader": "Karte anpassen",
|
||||
"customMessagePlaceholderContent": "Klicke auf die Benutzerseite bearbeiten Schaltfläche in den Einstellungen, um diese Karte anzupassen, oder auf dem Login Screen anzeigen zu lassen. Keine Angst: Der Benutzer kann dies nicht sehen.",
|
||||
"newPassword": "Neues Passwort",
|
||||
"welcomeUser": "Willkommen, {user}!",
|
||||
"addContactMethod": "Kontaktmethode hinzufügen",
|
||||
"editContactMethod": "Kontaktmethode bearbeiten",
|
||||
"joinTheServer": "Server beitreten:",
|
||||
"userPageSuccessMessage": "Du kannst die Details deines Accounts später in {myAccount} Seite einsehen und ändern.",
|
||||
"resetPassword": "Passwort zurücksetzen",
|
||||
"resetPasswordThroughJellyfin": "Um das Passwort zurückzusetzen, besuche {jfLink} und drücke auf die Schaltfläche \"Passwort vergessen\"."
|
||||
},
|
||||
"validationStrings": {
|
||||
"length": {
|
||||
@@ -58,4 +69,4 @@
|
||||
"errorPassword": "Prüfe die Passwortanforderungen.",
|
||||
"errorNoMatch": "Passwörter stimmen nicht überein."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,10 @@
|
||||
"resetPasswordThroughLink": "To reset your password, enter your username, email address or a linked contact method username, and submit. A link will be sent to reset your password.",
|
||||
"resetSent": "Reset Sent.",
|
||||
"resetSentDescription": "If an account with the given username/contact method exists, a password reset link has been sent via all contact methods available. The code will expire in 30 minutes.",
|
||||
"changePassword": "Change Password"
|
||||
"changePassword": "Change Password",
|
||||
"referralsDescription": "Invite friends & family to Jellyfin with this link. Come back here for a new one if it expires.",
|
||||
"copyReferral": "Copy Link",
|
||||
"invitedBy": "You were invited by user {user}."
|
||||
},
|
||||
"notifications": {
|
||||
"errorUserExists": "User already exists.",
|
||||
|
||||
@@ -7,20 +7,38 @@
|
||||
"pageTitle": "Créer un compte Jellyfin",
|
||||
"createAccountHeader": "Création du compte",
|
||||
"accountDetails": "Détails",
|
||||
"emailAddress": "E-mail",
|
||||
"emailAddress": "Email",
|
||||
"username": "Nom d'utilisateur",
|
||||
"password": "Mot de passe",
|
||||
"reEnterPassword": "Confirmez mot de passe",
|
||||
"reEnterPasswordInvalid": "Les mots de passe ne correspondent pas.",
|
||||
"reEnterPasswordInvalid": "Les mots de passe ne sont pas pareils.",
|
||||
"createAccountButton": "Créer le compte",
|
||||
"passwordRequirementsHeader": "Mot de passe requis",
|
||||
"successHeader": "Succès !",
|
||||
"successHeader": "Succès !",
|
||||
"confirmationRequired": "Confirmation de l'adresse e-mail requise",
|
||||
"confirmationRequiredMessage": "Veuillez vérifier votre boite de réception pour confirmer votre adresse e-mail.",
|
||||
"yourAccountIsValidUntil": "Votre compte sera valide jusqu'au {date}.",
|
||||
"sendPIN": "Envoyez le code PIN ci-dessous au bot, puis revenez ici pour lier votre compte.",
|
||||
"sendPINDiscord": "Écrivez {command} dans le salon {server_channel} sur Discord puis envoyez le PIN en message privé au bot.",
|
||||
"matrixEnterUser": "Entrez votre nom d'utilisateur, appuyez sur soumettre et un code PIN vous sera envoyé. Cliquez ici pour continuez."
|
||||
"sendPINDiscord": "Écrivez {command} dans le salon {server_channel} sur Discord puis envoyez le PIN.",
|
||||
"matrixEnterUser": "Entrez votre nom d'utilisateur, appuyez sur soumettre et un code PIN vous sera envoyé. Cliquez ici pour continuez.",
|
||||
"resetPasswordThroughJellyfin": "Pour réinitialiser votre mot de passe, aller sur {jfLink} et cliquer sur \"Mot de passe oublié\".",
|
||||
"resetPasswordThroughLink": "Pour réinitialiser votre mot de passe, saisissez votre nom d'utilisateur ou adresse courriel. Un lien vous sera envoyé.",
|
||||
"resetSent": "Lien de réinitialisation envoyé.",
|
||||
"resetSentDescription": "Si un compte avec ce nom d'utilisateur ou cette adresse courriel existe, un lien de réinitialisation de mot de passe vous sera envoyé. Le lien expire dans 30 minutes.",
|
||||
"changePassword": "Modifier le mot de passe",
|
||||
"oldPassword": "Ancien mot de passe",
|
||||
"addContactMethod": "Ajouter un moyen de contact",
|
||||
"userPageSuccessMessage": "Vous pouvez voir et modifier les détails de votre compte plus tard sur la page {myAccount}.",
|
||||
"customMessagePlaceholderContent": "Cliquez sur le bouton de modification de la page utilisateur dans les paramètres pour personnaliser cette carte, ou en afficher une sur l'écran de connexion, et ne vous inquiétez pas, l'utilisateur ne peut pas voir ceci.",
|
||||
"newPassword": "Nouveau mot de passe",
|
||||
"welcomeUser": "Bienvenue, {user} !",
|
||||
"editContactMethod": "Modifier le moyen de contact",
|
||||
"joinTheServer": "Rejoindre le serveur :",
|
||||
"customMessagePlaceholderHeader": "Personnaliser cette carte",
|
||||
"resetPassword": "Réinitialisation mot de passe",
|
||||
"referralsDescription": "Invitez vos amis et votre famille à Jellyfin avec ce lien. Revenez ici pour en obtenir un nouveau s'il expire.",
|
||||
"copyReferral": "Copier le lien",
|
||||
"invitedBy": "Vous avez été invité par l'utilisateur {user}."
|
||||
},
|
||||
"validationStrings": {
|
||||
"length": {
|
||||
@@ -59,6 +77,8 @@
|
||||
"errorCaptcha": "Captcha incorrect.",
|
||||
"errorPassword": "Vérifiez les exigences relatives au mot de passe.",
|
||||
"errorNoMatch": "Les mots de passe ne correspondent pas.",
|
||||
"errorAccountLinked": "Compte déjà utilisé."
|
||||
"errorAccountLinked": "Compte déjà utilisé.",
|
||||
"errorOldPassword": "Ancien mot de passe erroné.",
|
||||
"passwordChanged": "Mot de passe modifié."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "English (US)"
|
||||
"name": "Magyar (HU)"
|
||||
},
|
||||
"strings": {
|
||||
"pageTitle": "Jellyfin fiók létrehozása",
|
||||
@@ -10,7 +10,7 @@
|
||||
"username": "Felhasználónév",
|
||||
"password": "Jelszó",
|
||||
"reEnterPassword": "Jelszó megerősítése",
|
||||
"reEnterPasswordInvalid": "A jelszavak nem egyeznek",
|
||||
"reEnterPasswordInvalid": "A jelszavak nem egyeznek.",
|
||||
"createAccountButton": "Fiók létrehozása",
|
||||
"passwordRequirementsHeader": "Jelszó követelmények",
|
||||
"successHeader": "Siker!",
|
||||
@@ -19,7 +19,22 @@
|
||||
"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.",
|
||||
"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."
|
||||
"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.",
|
||||
"resetSentDescription": "Ha létezik fiók a megadott felhasználónévvel/kapcsolattartási móddal, akkor a jelszó visszaállító link minden megadott módon el lesz küldve. A kód 30 percen belül lejár.",
|
||||
"oldPassword": "Régi jelszó",
|
||||
"newPassword": "Új jelszó",
|
||||
"welcomeUser": "Üdvözlet, {user}!",
|
||||
"addContactMethod": "Kapcsolattartási opció hozzáadása",
|
||||
"editContactMethod": "Kapcsolattartási opció szerkesztése",
|
||||
"joinTheServer": "Csatlakozz a szerverhez:",
|
||||
"customMessagePlaceholderHeader": "Kártya személyreszabása",
|
||||
"userPageSuccessMessage": "Meg tudod nézni és szerkeszteni a részleteket a fiókodról késöbb a {myAccount} oldalon.",
|
||||
"resetPassword": "Jelszó visszaállítása",
|
||||
"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"
|
||||
},
|
||||
"notifications": {
|
||||
"errorUserExists": "A felhasználó már létezik.",
|
||||
@@ -35,12 +50,14 @@
|
||||
"errorCaptcha": "Hibás Captcha.",
|
||||
"errorNoMatch": "A jelszavak nem egyeznek.",
|
||||
"errorEmailLinked": "Az e-mail cím már használatban van.",
|
||||
"errorAccountLinked": "A fiók már használatban van."
|
||||
"errorAccountLinked": "A fiók már használatban van.",
|
||||
"errorOldPassword": "Helytelen a régi jelszó.",
|
||||
"passwordChanged": "Jelszó megváltoztatva."
|
||||
},
|
||||
"validationStrings": {
|
||||
"length": {
|
||||
"singular": "Legalább {n} karakter",
|
||||
"plural": "Legalább {n} karakter szükséges."
|
||||
"plural": "Legalább {n} karakter szükséges"
|
||||
},
|
||||
"uppercase": {
|
||||
"singular": "Legalább {n} nagybetűs karakter",
|
||||
@@ -59,4 +76,4 @@
|
||||
"plural": "Legalább {n} speciális karaktert kell tartalmazzon"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,31 +9,42 @@
|
||||
"emailAddress": "Email",
|
||||
"username": "Nama pengguna",
|
||||
"password": "Sandi",
|
||||
"reEnterPassword": "Masukkan kembali sandi",
|
||||
"reEnterPassword": "Masukkan Ulang Sandi",
|
||||
"reEnterPasswordInvalid": "Kata sandi tidak sama.",
|
||||
"createAccountButton": "Buat Akun",
|
||||
"passwordRequirementsHeader": "Persyaratan Kata Sandi",
|
||||
"successHeader": "Sukses!",
|
||||
"successHeader": "Berhasil!",
|
||||
"confirmationRequired": "Konfirmasi email diperlukan",
|
||||
"confirmationRequiredMessage": "Silakan periksa kotak masuk email Anda untuk memverifikasi alamat Anda.",
|
||||
"sendPINDiscord": "Ketik {command} di {server_channel} pada Discord, lalu kirim PIN di bawah ini.",
|
||||
"yourAccountIsValidUntil": "Akun kamu berlaku hingga {date}.",
|
||||
"sendPIN": "Kirim PIN di bawah ini ke bot, lalu kembali kesini untuk menerima link ke akun kamu.",
|
||||
"matrixEnterUser": "Masukkan User ID kamu, tekan submit, nomor PIN akan dikirimkan ke kamu. Masukkan untuk melanjutkan."
|
||||
"yourAccountIsValidUntil": "Akun kamu akan berlaku hingga {date}.",
|
||||
"sendPIN": "Kirim PIN di bawah ini ke bot, lalu kembali kesini untuk menerima tautan ke akun kamu.",
|
||||
"matrixEnterUser": "Masukkan User ID kamu, tekan submit, nomor PIN akan dikirimkan ke kamu. Masukkan untuk melanjutkan.",
|
||||
"oldPassword": "Sandi Lama",
|
||||
"userPageSuccessMessage": "Kamu dapat melihat serta mengubah detail akunmu nanti di halaman {myAccount}.",
|
||||
"newPassword": "Sandi Baru",
|
||||
"welcomeUser": "Selamat datang, {user}!",
|
||||
"joinTheServer": "Bergabung ke server:",
|
||||
"changePassword": "Ubah Sandi",
|
||||
"resetPassword": "Atur Ulang Sandi"
|
||||
},
|
||||
"notifications": {
|
||||
"errorUserExists": "Pengguna sudah ada.",
|
||||
"errorInvalidCode": "Kode undangan tidak valid.",
|
||||
"errorInvalidCode": "Kode undangan tidak sah.",
|
||||
"errorDiscordVerification": "Butuh verifikasi pada Discord.",
|
||||
"errorUnknown": "Terjadi kesalahan.",
|
||||
"errorUnknown": "Terjadi kesalahan yang tidak diketahui.",
|
||||
"errorTelegramVerification": "Butuh verifikasi pada Telegram.",
|
||||
"errorInvalidPIN": "Kode PIN salah.",
|
||||
"errorInvalidPIN": "PIN salah.",
|
||||
"errorCaptcha": "Captcha salah.",
|
||||
"errorPassword": "Perhatikan persyaratan password.",
|
||||
"errorNoMatch": "Password tidak sama.",
|
||||
"errorPassword": "Perhatikan persyaratan sandi.",
|
||||
"errorNoMatch": "Sandi tidak sama.",
|
||||
"verified": "Akun terverifikasi.",
|
||||
"errorNoEmail": "Butuh Email.",
|
||||
"errorMatrixVerification": "Butuh verifikasi pada Matrix."
|
||||
"errorNoEmail": "Butuh email.",
|
||||
"errorMatrixVerification": "Butuh verifikasi pada Matrix.",
|
||||
"passwordChanged": "Sandi Diubah.",
|
||||
"errorEmailLinked": "Email sudah digunakan.",
|
||||
"errorAccountLinked": "Akun telah digunakan.",
|
||||
"errorOldPassword": "Sandi lama salah."
|
||||
},
|
||||
"validationStrings": {
|
||||
"length": {
|
||||
@@ -45,16 +56,16 @@
|
||||
"plural": "Harus memiliki setidaknya {n} karakter huruf besar"
|
||||
},
|
||||
"lowercase": {
|
||||
"singular": "Harus memiliki setidaknya {n} karakter huruf kecil",
|
||||
"plural": "Harus memiliki setidaknya {n} karakter huruf kecil"
|
||||
"singular": "Harus memiliki setidaknya {n} huruf kecil",
|
||||
"plural": "Harus memiliki setidaknya {n} huruf kecil"
|
||||
},
|
||||
"number": {
|
||||
"singular": "Harus memiliki setidaknya {n} nomor",
|
||||
"plural": "Harus memiliki setidaknya {n} nomor"
|
||||
"singular": "Harus memiliki setidaknya {n} angka",
|
||||
"plural": "Harus memiliki setidaknya {n} angka"
|
||||
},
|
||||
"special": {
|
||||
"singular": "Harus memiliki setidaknya {n} karakter khusus",
|
||||
"plural": "Harus memiliki setidaknya {n} karakter khusus"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,40 @@
|
||||
"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.",
|
||||
"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."
|
||||
"matrixEnterUser": "Inserisci il tuo ID utente, premi invia e ti verrò inviato un PIN. Inseriscilo qui per continuare.",
|
||||
"customMessagePlaceholderHeader": "Personalizza questa scheda",
|
||||
"oldPassword": "Vecchia Password",
|
||||
"newPassword": "Nuova Password",
|
||||
"userPageSuccessMessage": "Potrai vedere e modificare i dettagli del tuo account sulla pagina {myAccount}.",
|
||||
"resetPasswordThroughJellyfin": "Per ripristinare la tua password, visita {jfLink} e premi \"Password Dimenticata\".",
|
||||
"welcomeUser": "Benvenuto, {user}!",
|
||||
"addContactMethod": "Aggiungi metodo di contatto",
|
||||
"editContactMethod": "Modifica metodo di contatto",
|
||||
"resetPasswordThroughLink": "Per ripristinare la password, inserisci il tuo username, indirizzo email o un username di contatto collegato, e invia. Verrà inviato un link per ripristinare la password.",
|
||||
"joinTheServer": "Entra nel server:",
|
||||
"customMessagePlaceholderContent": "Fare clic sul pulsante di modifica della pagina utente nelle impostazioni per personalizzare questa scheda o visualizzarne una nella schermata di accesso e non preoccuparti, l'utente non può veder questo.",
|
||||
"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"
|
||||
},
|
||||
"notifications": {
|
||||
"errorUserExists": "L'utente è già esistente.",
|
||||
"errorInvalidCode": "Codice di invito non valido.",
|
||||
"errorTelegramVerification": "Verifica Telegram richiesta."
|
||||
"errorTelegramVerification": "Verifica Telegram richiesta.",
|
||||
"errorPassword": "Verifica i requisiti password.",
|
||||
"errorNoMatch": "Le password non corrispondono.",
|
||||
"errorNoEmail": "Email richiesta.",
|
||||
"errorCaptcha": "Captcha errato.",
|
||||
"errorUnknown": "Errore sconosciuto.",
|
||||
"verified": "Account verificato.",
|
||||
"errorAccountLinked": "Account già in uso.",
|
||||
"errorEmailLinked": "Email già in uso.",
|
||||
"errorInvalidPIN": "PIN non valido.",
|
||||
"errorDiscordVerification": "Verifica Discord richiesta.",
|
||||
"errorMatrixVerification": "Verifica Matrix richiesta.",
|
||||
"errorOldPassword": "Vecchia password non corretta.",
|
||||
"passwordChanged": "Password Cambiata."
|
||||
},
|
||||
"validationStrings": {
|
||||
"length": {
|
||||
@@ -48,4 +76,4 @@
|
||||
"plural": "Deve avere almeno {n} caratteri speciali"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,15 @@
|
||||
"yourAccountIsValidUntil": "Je account zal geldig zijn tot {date}.",
|
||||
"sendPIN": "Stuur onderstaande pincode naar de bot, en kom daarna hier terug om je account te koppelen.",
|
||||
"matrixEnterUser": "Voer je gebruikers ID in, druk op versturen, en er wordt je een pincode toegestuurd. Vul die hier in om door te gaan.",
|
||||
"sendPINDiscord": "Typ {command} in {server_channel} op Discord, stuur daarna onderstaande pincode."
|
||||
"sendPINDiscord": "Typ {command} in {server_channel} op Discord, stuur daarna onderstaande pincode.",
|
||||
"oldPassword": "Huidig wachtwoord",
|
||||
"newPassword": "Nieuw wachtwoord",
|
||||
"welcomeUser": "Welkom, {user}!",
|
||||
"addContactMethod": "Voeg contact methode toe",
|
||||
"editContactMethod": "Wijzig contact methode",
|
||||
"joinTheServer": "Word lid van de server:",
|
||||
"resetPassword": "Wachtwoord opnieuw instellen",
|
||||
"changePassword": "Wachtwoord wijzigen"
|
||||
},
|
||||
"validationStrings": {
|
||||
"length": {
|
||||
@@ -56,6 +64,10 @@
|
||||
"errorNoEmail": "E-mail vereist.",
|
||||
"errorPassword": "Controleer wachtwoordeisen.",
|
||||
"errorNoMatch": "Wachtwoorden komen niet overeen.",
|
||||
"errorCaptcha": "Captcha incorrect."
|
||||
"errorCaptcha": "Captcha incorrect.",
|
||||
"errorAccountLinked": "Account is al in gebruik.",
|
||||
"errorEmailLinked": "Emailadres is al in gebruik.",
|
||||
"errorOldPassword": "Huidig wachtwoord is niet correct.",
|
||||
"passwordChanged": "Wachtwoord gewijzigd."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,4 +57,4 @@
|
||||
"plural": "Potrebnih je vsaj {n} posebnih znakov"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,22 @@
|
||||
"yourAccountIsValidUntil": "您的账户将在 {date} 之前有效。",
|
||||
"sendPIN": "把下面的PIN码发给机器人,然后回到这页绑定您的账户。",
|
||||
"sendPINDiscord": "在Discord {server_channel} 频道中,输入 {command} 并将下面的PIN码。",
|
||||
"matrixEnterUser": "输入您的用户ID并点击提交,将发送给您的PIN码输入到这里并继续。"
|
||||
"matrixEnterUser": "输入您的用户ID并点击提交,将发送给您的PIN码输入到这里并继续。",
|
||||
"addContactMethod": "添加联系方式",
|
||||
"resetPassword": "重置密码",
|
||||
"resetPasswordThroughJellyfin": "要重置您的密码,请访问 {jfLink} 并点击“忘记密码”。",
|
||||
"resetPasswordThroughLink": "要重置您的密码,请输入您的用户名、电子邮件地址或绑定的联系方式用户名,然后点击提交。系统将发送重置密码的链接。",
|
||||
"changePassword": "更改密码",
|
||||
"resetSentDescription": "如果用户名/联系方式存在,则会通过所有可用的联系方式发送密码重置链接。重置代码将在30分钟后过期。",
|
||||
"resetSent": "密码重置链接已发送。",
|
||||
"userPageSuccessMessage": "您可以稍后在 {myAccount} 页面上查看和更改您帐户的详细信息。",
|
||||
"customMessagePlaceholderContent": "点击设置中的用户页面编辑按钮以自定义这张卡片,以在登录页面上显示,不用担心,用户看不到条消息。",
|
||||
"oldPassword": "当前密码",
|
||||
"newPassword": "新密码",
|
||||
"welcomeUser": "欢迎,{user}!",
|
||||
"editContactMethod": "修改联系方式",
|
||||
"joinTheServer": "加入服务器:",
|
||||
"customMessagePlaceholderHeader": "自定义此卡"
|
||||
},
|
||||
"notifications": {
|
||||
"errorUserExists": "用户已经存在。",
|
||||
@@ -33,7 +48,11 @@
|
||||
"errorNoEmail": "必要输入电邮地址。",
|
||||
"errorCaptcha": "验证码不对。",
|
||||
"errorPassword": "请您检查密码要求再试。",
|
||||
"errorNoMatch": "密码不匹配。"
|
||||
"errorNoMatch": "密码不匹配。",
|
||||
"errorAccountLinked": "账号已存在。",
|
||||
"errorOldPassword": "旧密码不正确。",
|
||||
"errorEmailLinked": "邮箱地址已经存在。",
|
||||
"passwordChanged": "密码已更改。"
|
||||
},
|
||||
"validationStrings": {
|
||||
"length": {
|
||||
@@ -57,4 +76,4 @@
|
||||
"plural": "必须至少包含 {n} 个特殊字符"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,22 @@
|
||||
"yourAccountIsValidUntil": "您的帳戶有效期至 {date}。",
|
||||
"sendPIN": "將下面的 PIN 碼發送給機器人,然後返回此處連結您的帳戶。",
|
||||
"sendPINDiscord": "在 Discord 上的 {server_channel} 中輸入 {command},然後發送下方的 PIN 碼。",
|
||||
"matrixEnterUser": "輸入您的帳戶 ID 並點擊提交。在此處輸入發送給您的PIN 碼以繼續。"
|
||||
"matrixEnterUser": "輸入您的帳戶 ID 並點擊提交。在此處輸入發送給您的PIN 碼以繼續。",
|
||||
"oldPassword": "舊密碼",
|
||||
"newPassword": "新密碼",
|
||||
"welcomeUser": "歡迎,{user}!",
|
||||
"addContactMethod": "添加聯繫方式",
|
||||
"editContactMethod": "編輯聯繫方式",
|
||||
"joinTheServer": "加入伺服器:",
|
||||
"customMessagePlaceholderHeader": "自定義此卡片",
|
||||
"customMessagePlaceholderContent": "單擊設置中的用戶頁面編輯按鈕可以自定義此卡片,或在登錄屏幕上顯示一張卡片,不用擔心,用戶看不到它。",
|
||||
"userPageSuccessMessage": "您稍後可以在 {myAccount} 頁面上查看和更改有關您帳戶的詳細信息。",
|
||||
"resetPassword": "重設密碼",
|
||||
"resetPasswordThroughJellyfin": "要重置您的密碼,請訪問 {jfLink} 並按“忘記密碼”按鈕。",
|
||||
"resetPasswordThroughLink": "要重置密碼,請輸入您的用戶名、電子郵件地址或鏈接的聯繫方式用戶名,然後提交。 將發送一個鏈接以重置您的密碼。",
|
||||
"resetSent": "重置已發送。",
|
||||
"resetSentDescription": "如果存在具有給定用戶名/聯繫方式的帳戶,則已通過所有可用的聯繫方式發送密碼重置鏈接。 該代碼將在 30 分鐘後過期。",
|
||||
"changePassword": "更改密碼"
|
||||
},
|
||||
"notifications": {
|
||||
"errorUserExists": "帳戶已存在。",
|
||||
@@ -33,7 +48,11 @@
|
||||
"errorCaptcha": "CAPTCHA 驗證碼不正確。",
|
||||
"errorPassword": "請您檢查密碼要求後再試。",
|
||||
"errorNoMatch": "密碼不匹配。",
|
||||
"verified": "帳戶已驗證。"
|
||||
"verified": "帳戶已驗證。",
|
||||
"errorEmailLinked": "電子郵件已被使用。",
|
||||
"errorAccountLinked": "帳戶已在使用。",
|
||||
"errorOldPassword": "舊密碼不正確。",
|
||||
"passwordChanged": "密碼已更改。"
|
||||
},
|
||||
"validationStrings": {
|
||||
"length": {
|
||||
@@ -57,4 +76,4 @@
|
||||
"plural": "必須至少包含 {n} 個特殊字元"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||