mirror of
https://github.com/hrfee/jfa-go.git
synced 2026-01-19 00:57:37 +01:00
Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96983d70c8 | ||
|
|
9400a5bc66 | ||
|
|
033319af29 | ||
|
|
787d0e7b4c | ||
|
|
d90617c027 | ||
|
|
98303a286a | ||
|
|
aa791f1948 | ||
|
|
e46466180d | ||
|
|
3b956ca82e | ||
|
|
a0e69009f0 | ||
|
|
59400dbc61 | ||
|
|
0b06dd29c4 | ||
|
|
0152acde9a | ||
|
|
273e5caa6b | ||
|
|
8d5aa0d0ae | ||
|
|
e75c71e0a2 | ||
|
|
f423b221e6 | ||
|
|
702e42b8b3 | ||
|
|
bbc99bbeaa | ||
|
|
e2543bda67 | ||
|
|
442ce1fac1 | ||
|
|
03367b2cac | ||
|
|
a2d212e396 | ||
|
|
cecf9ba0d4 | ||
|
|
5aebc323d5 | ||
|
|
2543cd08c2 | ||
|
|
9c353f2a91 | ||
|
|
722e7e66c2 | ||
|
|
1296992752 | ||
|
|
ab5a82858e | ||
|
|
073772ad60 | ||
|
|
d7e4431bd8 | ||
|
|
96ec12f2bd | ||
|
|
85eea23d98 | ||
|
|
51961d16ba | ||
|
|
d6d73e81d6 | ||
|
|
27a80734f9 | ||
|
|
1d4ea7d0a0 | ||
|
|
6d2e517e82 | ||
|
|
982d3ec4c9 | ||
|
|
5e653c51f3 | ||
|
|
875387166e | ||
|
|
909614c3e7 | ||
|
|
3178ca7572 | ||
|
|
442bdd2220 | ||
|
|
a680db92a7 | ||
|
|
08c350d50b | ||
|
|
fe20187b0c | ||
|
|
65a25a7e66 | ||
|
|
607d8e9566 | ||
|
|
8f3b860cc7 | ||
|
|
a3dc8b7e07 | ||
|
|
6bfb345169 | ||
|
|
704157be00 | ||
|
|
b1c578ccf4 | ||
|
|
7c9f917114 |
@@ -11,11 +11,31 @@ clone:
|
||||
depth: 0
|
||||
|
||||
steps:
|
||||
- name: precompile
|
||||
image: docker.io/hrfee/jfa-go-build-docker:latest
|
||||
environment:
|
||||
JFA_GO_SNAPSHOT: y
|
||||
JFA_GO_BUILT_BY:
|
||||
from_secret: BUILT_BY
|
||||
commands:
|
||||
- npm i
|
||||
- make precompile
|
||||
- go mod download
|
||||
- name: test
|
||||
image: docker.io/hrfee/jfa-go-build-docker:latest
|
||||
environment:
|
||||
JFA_GO_SNAPSHOT: y
|
||||
JFA_GO_BUILT_BY:
|
||||
from_secret: BUILT_BY
|
||||
commands:
|
||||
- make test
|
||||
- name: build
|
||||
image: docker.io/hrfee/jfa-go-build-docker:latest
|
||||
environment:
|
||||
JFA_GO_BUILT_BY:
|
||||
from_secret: BUILT_BY
|
||||
GITHUB_TOKEN:
|
||||
from_secret: GITHUB_TOKEN
|
||||
commands:
|
||||
- curl -sfL https://goreleaser.com/static/run > ../goreleaser
|
||||
- chmod +x ../goreleaser
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -2,7 +2,7 @@
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Harvey Tindall
|
||||
Copyright (c) 2025 Harvey Tindall
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
4
Makefile
4
Makefile
@@ -9,7 +9,7 @@ else
|
||||
endif
|
||||
GOBINARY ?= go
|
||||
|
||||
CSSVERSION ?= v3
|
||||
CSSVERSION ?= v0.6.0
|
||||
CSS_BUNDLE = $(DATA)/web/css/$(CSSVERSION)bundle.css
|
||||
|
||||
VERSION ?= $(shell git describe --exact-match HEAD 2> /dev/null || echo vgit)
|
||||
@@ -195,7 +195,7 @@ COPY_TARGET = $(DATA)/jfa-go.service
|
||||
# $(DATA)/LICENSE $(LANG_TARGET) $(STATIC_TARGET) $(DATA)/web/css/$(CSSVERSION)bundle.css
|
||||
$(COPY_TARGET): $(INLINE_TARGET) $(STATIC_SRC) $(LANG_SRC) $(CONFIG_BASE)
|
||||
$(info copying $(CONFIG_BASE))
|
||||
cp $(CONFIG_BASE) $(DATA)/
|
||||
go run scripts/yaml/main.go -in $(CONFIG_BASE) -out $(DATA)/$(shell basename $(CONFIG_BASE))
|
||||
$(info copying crash page)
|
||||
cp $(DATA)/crash.html $(DATA)/html/
|
||||
$(info copying static data)
|
||||
|
||||
@@ -116,7 +116,7 @@ func (app *appContext) generateActivitiesQuery(req ServerFilterReqDTO) *badgerho
|
||||
// @Success 200 {object} GetActivitiesRespDTO
|
||||
// @Router /activity [post]
|
||||
// @Security Bearer
|
||||
// @tags Activity
|
||||
// @tags Activity,Statistics
|
||||
func (app *appContext) GetActivities(gc *gin.Context) {
|
||||
req := ServerSearchReqDTO{}
|
||||
gc.BindJSON(&req)
|
||||
@@ -185,7 +185,7 @@ func (app *appContext) DeleteActivity(gc *gin.Context) {
|
||||
// @Success 200 {object} PageCountDTO
|
||||
// @Router /activity/count [get]
|
||||
// @Security Bearer
|
||||
// @tags Activity
|
||||
// @tags Activity,Statistics
|
||||
func (app *appContext) GetActivityCount(gc *gin.Context) {
|
||||
resp := PageCountDTO{}
|
||||
var err error
|
||||
@@ -202,7 +202,7 @@ func (app *appContext) GetActivityCount(gc *gin.Context) {
|
||||
// @Success 200 {object} PageCountDTO
|
||||
// @Router /activity/count [post]
|
||||
// @Security Bearer
|
||||
// @tags Activity
|
||||
// @tags Activity,Statistics
|
||||
func (app *appContext) GetFilteredActivityCount(gc *gin.Context) {
|
||||
resp := PageCountDTO{}
|
||||
req := ServerFilterReqDTO{}
|
||||
|
||||
@@ -270,7 +270,7 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
|
||||
// @Success 200 {object} PageCountDTO
|
||||
// @Router /invites/count [get]
|
||||
// @Security Bearer
|
||||
// @tags Invites
|
||||
// @tags Invites,Statistics
|
||||
func (app *appContext) GetInviteCount(gc *gin.Context) {
|
||||
resp := PageCountDTO{}
|
||||
var err error
|
||||
@@ -286,7 +286,7 @@ func (app *appContext) GetInviteCount(gc *gin.Context) {
|
||||
// @Success 200 {object} PageCountDTO
|
||||
// @Router /invites/count/used [get]
|
||||
// @Security Bearer
|
||||
// @tags Invites
|
||||
// @tags Invites,Statistics
|
||||
func (app *appContext) GetInviteUsedCount(gc *gin.Context) {
|
||||
resp := PageCountDTO{}
|
||||
var err error
|
||||
@@ -310,7 +310,7 @@ func (app *appContext) GetInviteUsedCount(gc *gin.Context) {
|
||||
// @Success 200 {object} getInvitesDTO
|
||||
// @Router /invites [get]
|
||||
// @Security Bearer
|
||||
// @tags Invites
|
||||
// @tags Invites,Statistics
|
||||
func (app *appContext) GetInvites(gc *gin.Context) {
|
||||
currentTime := time.Now()
|
||||
app.checkInvites()
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/hrfee/jfa-go/common"
|
||||
"github.com/hrfee/jfa-go/jellyseerr"
|
||||
lm "github.com/hrfee/jfa-go/logmessages"
|
||||
)
|
||||
@@ -124,29 +125,62 @@ func (js *JellyseerrWrapper) ImportUser(jellyfinID string, req newUserDTO, profi
|
||||
return
|
||||
}
|
||||
|
||||
func (js *JellyseerrWrapper) AddContactMethods(jellyfinID string, req newUserDTO, discord *DiscordUser, telegram *TelegramUser) (err error) {
|
||||
func (js *JellyseerrWrapper) SetContactMethods(jellyfinID string, email *string, discord *DiscordUser, telegram *TelegramUser, contactPrefs *common.ContactPreferences) (err error) {
|
||||
_, err = js.MustGetUser(jellyfinID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
contactMethods := map[jellyseerr.NotificationsField]any{}
|
||||
if emailEnabled {
|
||||
err = js.ModifyMainUserSettings(jellyfinID, jellyseerr.MainUserSettings{Email: req.Email})
|
||||
if err != nil {
|
||||
// FIXME: This is a little ugly, considering all other errors are unformatted
|
||||
err = fmt.Errorf(lm.FailedSetEmailAddress, lm.Jellyseerr, jellyfinID, err)
|
||||
return
|
||||
} else {
|
||||
contactMethods[jellyseerr.FieldEmailEnabled] = req.EmailContact
|
||||
if contactPrefs == nil {
|
||||
contactPrefs = &common.ContactPreferences{
|
||||
Email: nil,
|
||||
Discord: nil,
|
||||
Telegram: nil,
|
||||
Matrix: nil,
|
||||
}
|
||||
}
|
||||
if discordEnabled && discord != nil {
|
||||
contactMethods[jellyseerr.FieldDiscord] = discord.ID
|
||||
contactMethods[jellyseerr.FieldDiscordEnabled] = req.DiscordContact
|
||||
contactMethods := map[jellyseerr.NotificationsField]any{}
|
||||
if emailEnabled {
|
||||
if contactPrefs.Email != nil {
|
||||
contactMethods[jellyseerr.FieldEmailEnabled] = *(contactPrefs.Email)
|
||||
} else if email != nil && *email != "" {
|
||||
contactMethods[jellyseerr.FieldEmailEnabled] = true
|
||||
}
|
||||
if email != nil {
|
||||
err = js.ModifyMainUserSettings(jellyfinID, jellyseerr.MainUserSettings{Email: *email})
|
||||
if err != nil {
|
||||
// FIXME: This is a little ugly, considering all other errors are unformatted
|
||||
err = fmt.Errorf(lm.FailedSetEmailAddress, lm.Jellyseerr, jellyfinID, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if telegramEnabled && discord != nil {
|
||||
contactMethods[jellyseerr.FieldTelegram] = telegram.ChatID
|
||||
contactMethods[jellyseerr.FieldTelegramEnabled] = req.TelegramContact
|
||||
if discordEnabled {
|
||||
if contactPrefs.Discord != nil {
|
||||
contactMethods[jellyseerr.FieldDiscordEnabled] = *(contactPrefs.Discord)
|
||||
} else if discord != nil && discord.ID != "" {
|
||||
contactMethods[jellyseerr.FieldDiscordEnabled] = true
|
||||
}
|
||||
if discord != nil {
|
||||
contactMethods[jellyseerr.FieldDiscord] = discord.ID
|
||||
// Whether this is still necessary or not, i don't know.
|
||||
if discord.ID == "" {
|
||||
contactMethods[jellyseerr.FieldDiscord] = jellyseerr.BogusIdentifier
|
||||
}
|
||||
}
|
||||
}
|
||||
if telegramEnabled {
|
||||
if contactPrefs.Telegram != nil {
|
||||
contactMethods[jellyseerr.FieldTelegramEnabled] = *(contactPrefs.Telegram)
|
||||
} else if telegram != nil && telegram.ChatID != 0 {
|
||||
contactMethods[jellyseerr.FieldTelegramEnabled] = true
|
||||
}
|
||||
if telegram != nil {
|
||||
contactMethods[jellyseerr.FieldTelegram] = strconv.FormatInt(telegram.ChatID, 10)
|
||||
// Whether this is still necessary or not, i don't know.
|
||||
if telegram.ChatID == 0 {
|
||||
contactMethods[jellyseerr.FieldTelegram] = jellyseerr.BogusIdentifier
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(contactMethods) > 0 {
|
||||
err = js.ModifyNotifications(jellyfinID, contactMethods)
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/hrfee/jfa-go/jellyseerr"
|
||||
"github.com/hrfee/jfa-go/common"
|
||||
lm "github.com/hrfee/jfa-go/logmessages"
|
||||
"github.com/lithammer/shortuuid/v3"
|
||||
"gopkg.in/ini.v1"
|
||||
@@ -263,21 +263,21 @@ func (app *appContext) TelegramAddUser(gc *gin.Context) {
|
||||
}
|
||||
app.storage.SetTelegramKey(req.ID, tgUser)
|
||||
|
||||
if err := app.js.ModifyNotifications(gc.GetString("jfId"), map[jellyseerr.NotificationsField]any{
|
||||
jellyseerr.FieldTelegram: tgUser.ChatID,
|
||||
jellyseerr.FieldTelegramEnabled: tgUser.Contact,
|
||||
}); err != nil {
|
||||
app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err)
|
||||
for _, tps := range app.thirdPartyServices {
|
||||
if err := tps.SetContactMethods(req.ID, nil, nil, &tgUser, &common.ContactPreferences{
|
||||
Telegram: &tgUser.Contact,
|
||||
}); err != nil {
|
||||
app.err.Printf(lm.FailedSyncContactMethods, tps.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
linkExistingOmbiDiscordTelegram(app)
|
||||
app.InvalidateWebUserCache()
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Sets whether to notify a user through telegram/discord/matrix/email or not.
|
||||
// @Produce json
|
||||
// @Param SetContactMethodsDTO body SetContactMethodsDTO true "User's Jellyfin ID and whether or not to notify then through Telegram."
|
||||
// @Param SetContactPreferencesDTO body SetContactPreferencesDTO true "User's Jellyfin ID and whether or not to notify then through Telegram."
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Success 400 {object} boolResponse
|
||||
// @Success 500 {object} boolResponse
|
||||
@@ -285,24 +285,24 @@ func (app *appContext) TelegramAddUser(gc *gin.Context) {
|
||||
// @Security Bearer
|
||||
// @tags Other
|
||||
func (app *appContext) SetContactMethods(gc *gin.Context) {
|
||||
var req SetContactMethodsDTO
|
||||
var req SetContactPreferencesDTO
|
||||
gc.BindJSON(&req)
|
||||
if req.ID == "" {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
app.setContactMethods(req, gc)
|
||||
app.setContactPreferences(req, gc)
|
||||
}
|
||||
|
||||
func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Context) {
|
||||
jsPrefs := map[jellyseerr.NotificationsField]any{}
|
||||
func (app *appContext) setContactPreferences(req SetContactPreferencesDTO, gc *gin.Context) {
|
||||
contactPrefs := common.ContactPreferences{}
|
||||
if tgUser, ok := app.storage.GetTelegramKey(req.ID); ok {
|
||||
change := tgUser.Contact != req.Telegram
|
||||
tgUser.Contact = req.Telegram
|
||||
app.storage.SetTelegramKey(req.ID, tgUser)
|
||||
if change {
|
||||
app.debug.Printf(lm.SetContactPrefForService, lm.Telegram, tgUser.Username, req.Telegram)
|
||||
jsPrefs[jellyseerr.FieldTelegramEnabled] = req.Telegram
|
||||
contactPrefs.Telegram = &req.Telegram
|
||||
}
|
||||
}
|
||||
if dcUser, ok := app.storage.GetDiscordKey(req.ID); ok {
|
||||
@@ -311,7 +311,7 @@ func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Conte
|
||||
app.storage.SetDiscordKey(req.ID, dcUser)
|
||||
if change {
|
||||
app.debug.Printf(lm.SetContactPrefForService, lm.Discord, dcUser.Username, req.Discord)
|
||||
jsPrefs[jellyseerr.FieldDiscordEnabled] = req.Discord
|
||||
contactPrefs.Discord = &req.Discord
|
||||
}
|
||||
}
|
||||
if mxUser, ok := app.storage.GetMatrixKey(req.ID); ok {
|
||||
@@ -320,6 +320,7 @@ func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Conte
|
||||
app.storage.SetMatrixKey(req.ID, mxUser)
|
||||
if change {
|
||||
app.debug.Printf(lm.SetContactPrefForService, lm.Matrix, mxUser.UserID, req.Matrix)
|
||||
contactPrefs.Matrix = &req.Matrix
|
||||
}
|
||||
}
|
||||
if email, ok := app.storage.GetEmailsKey(req.ID); ok {
|
||||
@@ -328,13 +329,13 @@ func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Conte
|
||||
app.storage.SetEmailsKey(req.ID, email)
|
||||
if change {
|
||||
app.debug.Printf(lm.SetContactPrefForService, lm.Email, email.Addr, req.Email)
|
||||
jsPrefs[jellyseerr.FieldEmailEnabled] = req.Email
|
||||
contactPrefs.Email = &req.Email
|
||||
}
|
||||
}
|
||||
if app.config.Section("jellyseerr").Key("enabled").MustBool(false) {
|
||||
err := app.js.ModifyNotifications(req.ID, jsPrefs)
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err)
|
||||
|
||||
for _, tps := range app.thirdPartyServices {
|
||||
if err := tps.SetContactMethods(req.ID, nil, nil, nil, &contactPrefs); err != nil {
|
||||
app.err.Printf(lm.FailedSyncContactMethods, tps.Name(), err)
|
||||
}
|
||||
}
|
||||
app.InvalidateWebUserCache()
|
||||
@@ -621,11 +622,12 @@ func (app *appContext) DiscordConnect(gc *gin.Context) {
|
||||
|
||||
app.storage.SetDiscordKey(req.JellyfinID, user)
|
||||
|
||||
if err := app.js.ModifyNotifications(req.JellyfinID, map[jellyseerr.NotificationsField]any{
|
||||
jellyseerr.FieldDiscord: req.DiscordID,
|
||||
jellyseerr.FieldDiscordEnabled: true,
|
||||
}); err != nil {
|
||||
app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err)
|
||||
for _, tps := range app.thirdPartyServices {
|
||||
if err := tps.SetContactMethods(req.JellyfinID, nil, &user, nil, &common.ContactPreferences{
|
||||
Discord: &user.Contact,
|
||||
}); err != nil {
|
||||
app.err.Printf(lm.FailedSyncContactMethods, tps.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
@@ -659,12 +661,14 @@ func (app *appContext) UnlinkDiscord(gc *gin.Context) {
|
||||
} */
|
||||
app.storage.DeleteDiscordKey(req.ID)
|
||||
|
||||
// May not actually remove Discord ID, but should disable interaction.
|
||||
if err := app.js.ModifyNotifications(gc.GetString("jfId"), map[jellyseerr.NotificationsField]any{
|
||||
jellyseerr.FieldDiscord: jellyseerr.BogusIdentifier,
|
||||
jellyseerr.FieldDiscordEnabled: false,
|
||||
}); err != nil {
|
||||
app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err)
|
||||
contact := false
|
||||
|
||||
for _, tps := range app.thirdPartyServices {
|
||||
if err := tps.SetContactMethods(req.ID, nil, EmptyDiscordUser(), nil, &common.ContactPreferences{
|
||||
Discord: &contact,
|
||||
}); err != nil {
|
||||
app.err.Printf(lm.FailedSyncContactMethods, tps.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
@@ -697,11 +701,14 @@ func (app *appContext) UnlinkTelegram(gc *gin.Context) {
|
||||
} */
|
||||
app.storage.DeleteTelegramKey(req.ID)
|
||||
|
||||
if err := app.js.ModifyNotifications(gc.GetString("jfId"), map[jellyseerr.NotificationsField]any{
|
||||
jellyseerr.FieldTelegram: jellyseerr.BogusIdentifier,
|
||||
jellyseerr.FieldTelegramEnabled: false,
|
||||
}); err != nil {
|
||||
app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err)
|
||||
contact := false
|
||||
|
||||
for _, tps := range app.thirdPartyServices {
|
||||
if err := tps.SetContactMethods(req.ID, nil, nil, EmptyTelegramUser(), &common.ContactPreferences{
|
||||
Telegram: &contact,
|
||||
}); err != nil {
|
||||
app.err.Printf(lm.FailedSyncContactMethods, tps.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
||||
|
||||
69
api-ombi.go
69
api-ombi.go
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/hrfee/jfa-go/common"
|
||||
lm "github.com/hrfee/jfa-go/logmessages"
|
||||
"github.com/hrfee/jfa-go/ombi"
|
||||
ombiLib "github.com/hrfee/jfa-go/ombi"
|
||||
"github.com/hrfee/mediabrowser"
|
||||
)
|
||||
|
||||
@@ -147,7 +147,8 @@ func (app *appContext) DeleteOmbiProfile(gc *gin.Context) {
|
||||
}
|
||||
|
||||
type OmbiWrapper struct {
|
||||
*ombi.Ombi
|
||||
OmbiUserByJfID func(jfID string) (map[string]interface{}, error)
|
||||
*ombiLib.Ombi
|
||||
}
|
||||
|
||||
func (ombi *OmbiWrapper) applyProfile(user map[string]interface{}, profile map[string]interface{}) (err error) {
|
||||
@@ -189,23 +190,69 @@ func (ombi *OmbiWrapper) ImportUser(jellyfinID string, req newUserDTO, profile P
|
||||
return
|
||||
}
|
||||
|
||||
func (ombi *OmbiWrapper) AddContactMethods(jellyfinID string, req newUserDTO, discord *DiscordUser, telegram *TelegramUser) (err error) {
|
||||
var ombiUser map[string]interface{}
|
||||
ombiUser, err = ombi.getUser(req.Username, req.Email)
|
||||
func (ombi *OmbiWrapper) SetContactMethods(jellyfinID string, email *string, discord *DiscordUser, telegram *TelegramUser, contactPrefs *common.ContactPreferences) (err error) {
|
||||
ombiUser, err := ombi.OmbiUserByJfID(jellyfinID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if discordEnabled || telegramEnabled {
|
||||
dID := ""
|
||||
tUser := ""
|
||||
if contactPrefs == nil {
|
||||
contactPrefs = &common.ContactPreferences{
|
||||
Email: nil,
|
||||
Discord: nil,
|
||||
Telegram: nil,
|
||||
Matrix: nil,
|
||||
}
|
||||
}
|
||||
if emailEnabled && email != nil {
|
||||
ombiUser["emailAddress"] = *email
|
||||
err = ombi.ModifyUser(ombiUser)
|
||||
if err != nil {
|
||||
// FIXME: This is a little ugly, considering all other errors are unformatted
|
||||
err = fmt.Errorf(lm.FailedSetEmailAddress, lm.Ombi, jellyfinID, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
data := make([]ombiLib.NotificationPref, 0, 2)
|
||||
if discordEnabled {
|
||||
pref := ombiLib.NotificationPref{
|
||||
Agent: ombiLib.NotifAgentDiscord,
|
||||
UserID: ombiUser["id"].(string),
|
||||
}
|
||||
valid := false
|
||||
if contactPrefs.Discord != nil {
|
||||
pref.Enabled = *(contactPrefs.Discord)
|
||||
valid = true
|
||||
} else if discord != nil && discord.ID != "" {
|
||||
pref.Enabled = true
|
||||
valid = true
|
||||
}
|
||||
if discord != nil {
|
||||
dID = discord.ID
|
||||
pref.Value = discord.ID
|
||||
valid = true
|
||||
}
|
||||
if valid {
|
||||
data = append(data, pref)
|
||||
}
|
||||
}
|
||||
if telegramEnabled && telegram != nil {
|
||||
pref := ombiLib.NotificationPref{
|
||||
Agent: ombiLib.NotifAgentTelegram,
|
||||
UserID: ombiUser["id"].(string),
|
||||
}
|
||||
if contactPrefs.Telegram != nil {
|
||||
pref.Enabled = *(contactPrefs.Telegram)
|
||||
} else if telegram != nil && telegram.Username != "" {
|
||||
pref.Enabled = true
|
||||
}
|
||||
if telegram != nil {
|
||||
tUser = telegram.Username
|
||||
pref.Value = telegram.Username
|
||||
}
|
||||
data = append(data, pref)
|
||||
}
|
||||
if len(data) > 0 {
|
||||
var resp string
|
||||
resp, err = ombi.SetNotificationPrefs(ombiUser, dID, tUser)
|
||||
resp, err = ombi.SetNotificationPrefs(ombiUser, data)
|
||||
if err != nil {
|
||||
if resp != "" {
|
||||
err = fmt.Errorf("%v, %s", err, resp)
|
||||
|
||||
@@ -107,7 +107,7 @@ func (app *appContext) MyDetails(gc *gin.Context) {
|
||||
|
||||
// @Summary Sets whether to notify yourself through telegram/discord/matrix/email or not.
|
||||
// @Produce json
|
||||
// @Param SetContactMethodsDTO body SetContactMethodsDTO true "User's Jellyfin ID and whether or not to notify then through Telegram."
|
||||
// @Param SetContactPreferencesDTO body SetContactPreferencesDTO true "User's Jellyfin ID and whether or not to notify then through Telegram."
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Success 400 {object} boolResponse
|
||||
// @Success 500 {object} boolResponse
|
||||
@@ -115,14 +115,14 @@ func (app *appContext) MyDetails(gc *gin.Context) {
|
||||
// @Security Bearer
|
||||
// @tags User Page
|
||||
func (app *appContext) SetMyContactMethods(gc *gin.Context) {
|
||||
var req SetContactMethodsDTO
|
||||
var req SetContactPreferencesDTO
|
||||
gc.BindJSON(&req)
|
||||
req.ID = gc.GetString("jfId")
|
||||
if req.ID == "" {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
app.setContactMethods(req, gc)
|
||||
app.setContactPreferences(req, gc)
|
||||
}
|
||||
|
||||
// @Summary Logout by deleting refresh token from cookies.
|
||||
|
||||
115
api-users.go
115
api-users.go
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/hrfee/jfa-go/jellyseerr"
|
||||
"github.com/hrfee/jfa-go/common"
|
||||
lm "github.com/hrfee/jfa-go/logmessages"
|
||||
"github.com/hrfee/mediabrowser"
|
||||
"github.com/lithammer/shortuuid/v3"
|
||||
@@ -54,12 +54,29 @@ func (app *appContext) NewUserFromAdmin(gc *gin.Context) {
|
||||
nu.Log()
|
||||
}
|
||||
|
||||
var emailStore *EmailAddress = nil
|
||||
if emailEnabled && req.Email != "" {
|
||||
emailStore := EmailAddress{
|
||||
emailStore = &EmailAddress{
|
||||
Addr: req.Email,
|
||||
Contact: true,
|
||||
}
|
||||
app.storage.SetEmailsKey(nu.User.ID, emailStore)
|
||||
app.storage.SetEmailsKey(nu.User.ID, *emailStore)
|
||||
}
|
||||
|
||||
for _, tps := range app.thirdPartyServices {
|
||||
if !tps.Enabled(app, &profile) {
|
||||
continue
|
||||
}
|
||||
// We only have email
|
||||
if emailStore == nil {
|
||||
continue
|
||||
}
|
||||
err := tps.SetContactMethods(nu.User.ID, &req.Email, nil, nil, &common.ContactPreferences{
|
||||
Email: &(emailStore.Contact),
|
||||
})
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedSyncContactMethods, tps.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
welcomeMessageSentIfNecessary := true
|
||||
@@ -268,12 +285,14 @@ func (app *appContext) PostNewUserFromInvite(nu NewUserData, req ConfirmationKey
|
||||
|
||||
referralsEnabled := profile != nil && profile.ReferralTemplateKey != "" && app.config.Section("user_page").Key("enabled").MustBool(false) && app.config.Section("user_page").Key("referrals").MustBool(false)
|
||||
|
||||
contactPrefs := common.ContactPreferences{}
|
||||
if (emailEnabled && req.Email != "") || invite.UserLabel != "" || referralsEnabled {
|
||||
emailStore := EmailAddress{
|
||||
Addr: req.Email,
|
||||
Contact: (req.Email != ""),
|
||||
Label: invite.UserLabel,
|
||||
}
|
||||
contactPrefs.Email = &(emailStore.Contact)
|
||||
if profile != nil {
|
||||
profile.ReferralTemplateKey = profile.ReferralTemplateKey
|
||||
}
|
||||
@@ -334,18 +353,22 @@ func (app *appContext) PostNewUserFromInvite(nu NewUserData, req ConfirmationKey
|
||||
|
||||
var discordUser *DiscordUser = nil
|
||||
var telegramUser *TelegramUser = nil
|
||||
// FIXME: Make sure its okay to, then change this check to len(app.tps) != 0 && (for loop of tps.Enabled )
|
||||
if app.ombi.Enabled(app, profile) || app.js.Enabled(app, profile) {
|
||||
// FIXME: figure these out in a nicer way? this relies on the current ordering,
|
||||
// which may not be fixed.
|
||||
if discordEnabled {
|
||||
if req.completeContactMethods[0].User != nil {
|
||||
discordUser = req.completeContactMethods[0].User.(*DiscordUser)
|
||||
contactPrefs.Discord = &discordUser.Contact
|
||||
}
|
||||
if telegramEnabled && req.completeContactMethods[1].User != nil {
|
||||
telegramUser = req.completeContactMethods[1].User.(*TelegramUser)
|
||||
contactPrefs.Telegram = &telegramUser.Contact
|
||||
}
|
||||
} else if telegramEnabled && req.completeContactMethods[0].User != nil {
|
||||
telegramUser = req.completeContactMethods[0].User.(*TelegramUser)
|
||||
contactPrefs.Telegram = &telegramUser.Contact
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,7 +377,7 @@ func (app *appContext) PostNewUserFromInvite(nu NewUserData, req ConfirmationKey
|
||||
continue
|
||||
}
|
||||
// User already created, now we can link contact methods
|
||||
err := tps.AddContactMethods(nu.User.ID, req.newUserDTO, discordUser, telegramUser)
|
||||
err := tps.SetContactMethods(nu.User.ID, &(req.Email), discordUser, telegramUser, &contactPrefs)
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedSyncContactMethods, tps.Name(), err)
|
||||
}
|
||||
@@ -525,6 +548,24 @@ func (app *appContext) ExtendExpiry(gc *gin.Context) {
|
||||
base := time.Now()
|
||||
if expiry, ok := app.storage.GetUserExpiryKey(id); ok {
|
||||
base = expiry.Expiry
|
||||
app.debug.Printf(lm.FoundExistingExpiry)
|
||||
} else if req.TryExtendFromPreviousExpiry {
|
||||
var acts []Activity
|
||||
app.storage.db.Find(&acts, badgerhold.Where("Type").Eq(ActivityDisabled).And("UserID").Eq(id).SortBy("Time").Reverse().Limit(1))
|
||||
if len(acts) != 0 {
|
||||
// Only do it if the most recent reason for disabling was expiry
|
||||
if acts[0].SourceType == ActivityDaemon {
|
||||
app.debug.Printf(lm.FoundPreviousExpiryLog, acts[0].Time)
|
||||
newExpiry := acts[0].Time.AddDate(0, req.Months, req.Days).Add(time.Duration(((60 * req.Hours) + req.Minutes)) * time.Minute)
|
||||
if newExpiry.After(base) {
|
||||
base = acts[0].Time
|
||||
} else {
|
||||
app.debug.Printf(lm.ExpiryWouldBeInPast)
|
||||
}
|
||||
} else {
|
||||
app.debug.Printf(lm.PreviousExpiryNotExpiry)
|
||||
}
|
||||
}
|
||||
}
|
||||
app.debug.Printf(lm.ExtendCreateExpiry, id)
|
||||
expiry := UserExpiry{}
|
||||
@@ -911,7 +952,7 @@ func (app *appContext) userSummary(jfUser mediabrowser.User) respUser {
|
||||
// @Success 200 {object} PageCountDTO
|
||||
// @Router /users/count [get]
|
||||
// @Security Bearer
|
||||
// @tags Activity
|
||||
// @tags Activity,Statistics
|
||||
func (app *appContext) GetUserCount(gc *gin.Context) {
|
||||
resp := PageCountDTO{}
|
||||
users, err := app.jf.GetUsers(false)
|
||||
@@ -952,7 +993,7 @@ func (app *appContext) GetUsers(gc *gin.Context) {
|
||||
// @Failure 500 {object} stringResponse
|
||||
// @Router /users [post]
|
||||
// @Security Bearer
|
||||
// @tags Users
|
||||
// @tags Users,Statistics
|
||||
func (app *appContext) SearchUsers(gc *gin.Context) {
|
||||
req := ServerSearchReqDTO{}
|
||||
gc.BindJSON(&req)
|
||||
@@ -991,6 +1032,38 @@ func (app *appContext) SearchUsers(gc *gin.Context) {
|
||||
gc.JSON(200, resp)
|
||||
}
|
||||
|
||||
// @Summary Get a count of users matching the search provided
|
||||
// @Produce json
|
||||
// @Param ServerSearchReqDTO body ServerSearchReqDTO true "search / pagination parameters"
|
||||
// @Success 200 {object} PageCountDTO
|
||||
// @Failure 500 {object} stringResponse
|
||||
// @Router /users/count [post]
|
||||
// @Security Bearer
|
||||
// @tags Users,Statistics
|
||||
func (app *appContext) GetFilteredUserCount(gc *gin.Context) {
|
||||
req := ServerSearchReqDTO{}
|
||||
gc.BindJSON(&req)
|
||||
if req.SortByField == "" {
|
||||
req.SortByField = USER_DEFAULT_SORT_FIELD
|
||||
}
|
||||
|
||||
var resp PageCountDTO
|
||||
// No need to sort
|
||||
userList, err := app.userCache.GetUserDTOs(app, false)
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err)
|
||||
respond(500, "Couldn't get users", gc)
|
||||
return
|
||||
}
|
||||
if len(req.SearchTerms) != 0 || len(req.Queries) != 0 {
|
||||
resp.Count = uint64(len(app.userCache.Filter(userList, req.SearchTerms, req.Queries)))
|
||||
} else {
|
||||
resp.Count = uint64(len(userList))
|
||||
}
|
||||
|
||||
gc.JSON(200, resp)
|
||||
}
|
||||
|
||||
// @Summary Set whether or not a user can access jfa-go. Redundant if the user is a Jellyfin admin.
|
||||
// @Produce json
|
||||
// @Param setAccountsAdminDTO body setAccountsAdminDTO true "Map of userIDs to whether or not they have access."
|
||||
@@ -1058,39 +1131,21 @@ func (app *appContext) ModifyLabels(gc *gin.Context) {
|
||||
}
|
||||
|
||||
func (app *appContext) modifyEmail(jfID string, addr string) {
|
||||
contactPrefChanged := false
|
||||
emailStore, ok := app.storage.GetEmailsKey(jfID)
|
||||
// Auto enable contact by email for newly added addresses
|
||||
if !ok || emailStore.Addr == "" {
|
||||
emailStore = EmailAddress{
|
||||
Contact: true,
|
||||
}
|
||||
contactPrefChanged = true
|
||||
}
|
||||
emailStore.Addr = addr
|
||||
app.storage.SetEmailsKey(jfID, emailStore)
|
||||
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
||||
ombiUser, err := app.getOmbiUser(jfID)
|
||||
if err == nil {
|
||||
ombiUser["emailAddress"] = addr
|
||||
err = app.ombi.ModifyUser(ombiUser)
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedSetEmailAddress, lm.Ombi, jfID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if app.config.Section("jellyseerr").Key("enabled").MustBool(false) {
|
||||
err := app.js.ModifyMainUserSettings(jfID, jellyseerr.MainUserSettings{Email: addr})
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedSetEmailAddress, lm.Jellyseerr, jfID, err)
|
||||
} else if contactPrefChanged {
|
||||
contactMethods := map[jellyseerr.NotificationsField]any{
|
||||
jellyseerr.FieldEmailEnabled: true,
|
||||
}
|
||||
err := app.js.ModifyNotifications(jfID, contactMethods)
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err)
|
||||
}
|
||||
|
||||
for _, tps := range app.thirdPartyServices {
|
||||
if err := tps.SetContactMethods(jfID, &addr, nil, nil, &common.ContactPreferences{
|
||||
Email: &(emailStore.Contact),
|
||||
}); err != nil {
|
||||
app.err.Printf(lm.FailedSetEmailAddress, tps.Name(), jfID, err)
|
||||
}
|
||||
}
|
||||
app.InvalidateWebUserCache()
|
||||
|
||||
@@ -16,6 +16,16 @@ import (
|
||||
lm "github.com/hrfee/jfa-go/logmessages"
|
||||
)
|
||||
|
||||
const (
|
||||
BogusIdentifier = "123412341234123456"
|
||||
)
|
||||
|
||||
// ContactPreferences holds whether or not a user should be contacted through each of the available
|
||||
// methods. If nil, leave setting alone.
|
||||
type ContactPreferences struct {
|
||||
Email, Discord, Telegram, Matrix *bool
|
||||
}
|
||||
|
||||
// TimeoutHandler recovers from an http timeout or panic.
|
||||
type TimeoutHandler func()
|
||||
|
||||
|
||||
@@ -48,8 +48,25 @@ type Section struct {
|
||||
Settings []Setting `json:"settings" yaml:"settings"`
|
||||
}
|
||||
|
||||
// Member is a member of a group, and can either reference a Section or another Group, hence the two fields.
|
||||
type Member struct {
|
||||
Group string `json:"group,omitempty", yaml:"group,omitempty"`
|
||||
Section string `json:"section,omitempty", yaml:"section,omitempty"`
|
||||
}
|
||||
|
||||
type Group struct {
|
||||
Group string `json:"group" yaml:"group" example:"messaging_providers"`
|
||||
Name string `json:"name" yaml:"name" example:"Messaging Providers"`
|
||||
Description string `json:"description" yaml:"description" example:"Options for setting up messaging providers."`
|
||||
Members []Member `json:"members" yaml:"members"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Sections []Section `json:"sections" yaml:"sections"`
|
||||
Groups []Group `json:"groups" yaml:"groups"`
|
||||
// Optional order, which can interleave sections and groups.
|
||||
// If unset, falls back to sections in order, then groups in order.
|
||||
Order []Member `json:"order,omitempty" yaml:"order,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Config) removeSection(section string) {
|
||||
|
||||
@@ -266,6 +266,11 @@ func NewConfig(configPathOrContents any, dataPath string, logs LoggerSet) (*Conf
|
||||
config.Section("discord").Key("start_command").SetValue(strings.TrimPrefix(strings.TrimPrefix(sc, "/"), "!"))
|
||||
|
||||
config.MustSetValue("email", "collect", "true")
|
||||
collect := config.Section("email").Key("collect").MustBool(true)
|
||||
required := config.Section("email").Key("required").MustBool(false) && collect
|
||||
config.Section("email").Key("required").SetValue(strconv.FormatBool(required))
|
||||
unique := config.Section("email").Key("require_unique").MustBool(false) && collect
|
||||
config.Section("email").Key("require_unique").SetValue(strconv.FormatBool(unique))
|
||||
|
||||
config.MustSetValue("matrix", "topic", "Jellyfin notifications")
|
||||
config.MustSetValue("matrix", "show_on_reg", "true")
|
||||
|
||||
@@ -1,3 +1,60 @@
|
||||
order:
|
||||
- section: ui
|
||||
- section: advanced
|
||||
- section: jellyfin
|
||||
- group: sign_up
|
||||
- group: accounts
|
||||
- section: messages
|
||||
- group: external_services
|
||||
- section: activity_log
|
||||
- section: backups
|
||||
- section: updates
|
||||
- section: url_paths
|
||||
- section: template_email
|
||||
- section: files
|
||||
groups:
|
||||
- group: external_services
|
||||
name: "Integrations"
|
||||
description: "Integrations with external services."
|
||||
members:
|
||||
- group: email
|
||||
- group: chatbots
|
||||
- section: ombi
|
||||
- section: jellyseerr
|
||||
- section: webhooks
|
||||
- group: email
|
||||
name: "Email"
|
||||
description: "Options for sending emails through jfa-go."
|
||||
members:
|
||||
- section: email
|
||||
- section: smtp
|
||||
- section: mailgun
|
||||
- section: email_confirmation
|
||||
- group: chatbots
|
||||
name: "Chatbots"
|
||||
description: "Options for messaging through chat services."
|
||||
members:
|
||||
- section: discord
|
||||
- section: telegram
|
||||
- section: matrix
|
||||
- group: sign_up
|
||||
name: "Invites & Referrals"
|
||||
description: "Settings relating to invites, the sign up page and referrals."
|
||||
members:
|
||||
- section: captcha
|
||||
- section: password_validation
|
||||
- section: invite_emails
|
||||
- section: notifications
|
||||
- section: welcome_email
|
||||
- group: accounts
|
||||
name: "Accounts"
|
||||
description: "Settings relating to account management."
|
||||
members:
|
||||
- section: user_page
|
||||
- section: password_resets
|
||||
- section: user_expiry
|
||||
- section: disable_enable
|
||||
- section: deletion
|
||||
sections:
|
||||
- section: updates
|
||||
meta:
|
||||
@@ -516,7 +573,7 @@ sections:
|
||||
meta:
|
||||
name: Captcha
|
||||
description: Settings related to user creation CAPTCHAs.
|
||||
wiki_link: https://wiki.jfa-go.com/docs/captcha/
|
||||
wiki_link: https://wiki.jfa-go.com/docs/external-services/captcha/
|
||||
settings:
|
||||
- setting: enabled
|
||||
name: Enabled
|
||||
@@ -670,7 +727,7 @@ sections:
|
||||
meta:
|
||||
name: Messages/Notifications
|
||||
description: General settings for emails/messages.
|
||||
wiki_link: https://wiki.jfa-go.com/docs/emails/
|
||||
wiki_link: https://wiki.jfa-go.com/docs/customization/emails/
|
||||
settings:
|
||||
- setting: enabled
|
||||
name: Enabled
|
||||
@@ -719,9 +776,27 @@ sections:
|
||||
- ["en-us", "English (US)"]
|
||||
value: en-us
|
||||
description: Default email language. Submit a PR on github if you'd like to translate.
|
||||
- setting: collect
|
||||
name: Collect on sign-up
|
||||
type: bool
|
||||
value: true
|
||||
description: Ask for an email address on the sign-up form.
|
||||
- setting: required
|
||||
name: Require on sign-up
|
||||
depends_true: collect
|
||||
type: bool
|
||||
value: false
|
||||
description: Require an email address on sign-up.
|
||||
- setting: require_unique
|
||||
name: Require unique address
|
||||
requires_restart: true
|
||||
depends_true: method
|
||||
type: bool
|
||||
value: false
|
||||
description: Disables using the same address on multiple accounts.
|
||||
- setting: no_username
|
||||
name: Use email addresses as username
|
||||
depends_true: method
|
||||
depends_true: collect
|
||||
type: bool
|
||||
value: false
|
||||
description: Use email address from invite form as username on Jellyfin.
|
||||
@@ -733,6 +808,7 @@ sections:
|
||||
- ["smtp", "SMTP"]
|
||||
- ["mailgun", "Mailgun"]
|
||||
value: smtp
|
||||
depends_true: messages|enabled
|
||||
description: Method of sending email to use.
|
||||
- setting: address
|
||||
name: Sent from (address)
|
||||
@@ -753,25 +829,6 @@ sections:
|
||||
type: bool
|
||||
value: false
|
||||
description: Send emails as plain text instead of HTML.
|
||||
- setting: collect
|
||||
name: Collect on sign-up
|
||||
depends_true: method
|
||||
type: bool
|
||||
value: true
|
||||
description: Ask for an email address on the sign-up form.
|
||||
- setting: required
|
||||
name: Require on sign-up
|
||||
depends_true: collect
|
||||
type: bool
|
||||
value: false
|
||||
description: Require an email address on sign-up.
|
||||
- setting: require_unique
|
||||
name: Require unique address
|
||||
requires_restart: true
|
||||
depends_true: method
|
||||
type: bool
|
||||
value: false
|
||||
description: Disables using the same address on multiple accounts.
|
||||
- setting: test_note
|
||||
name: 'Test your settings:'
|
||||
type: note
|
||||
@@ -780,7 +837,7 @@ sections:
|
||||
description: Go over to the accounts tab, select your user (ensuring you've assigned it an email address) and send yourself an announcement.
|
||||
- section: mailgun
|
||||
meta:
|
||||
name: Mailgun (Email)
|
||||
name: Mailgun
|
||||
description: Mailgun API connection settings
|
||||
depends_true: email|method
|
||||
settings:
|
||||
@@ -794,7 +851,7 @@ sections:
|
||||
value: your api key
|
||||
- section: smtp
|
||||
meta:
|
||||
name: SMTP (Email)
|
||||
name: SMTP
|
||||
description: SMTP Server connection settings.
|
||||
depends_true: email|method
|
||||
settings:
|
||||
@@ -860,7 +917,7 @@ sections:
|
||||
meta:
|
||||
name: Discord
|
||||
description: Settings for Discord invites/signup/notifications
|
||||
wiki_link: https://wiki.jfa-go.com/docs/bots/discord/
|
||||
wiki_link: https://wiki.jfa-go.com/docs/external-services/bots/discord/
|
||||
settings:
|
||||
- setting: enabled
|
||||
name: Enabled
|
||||
@@ -955,7 +1012,7 @@ sections:
|
||||
name: Telegram
|
||||
description: Settings for Telegram signup/notifications. See the jfa-go wiki for
|
||||
info on setting this up.
|
||||
wiki_link: https://wiki.jfa-go.com/docs/bots/telegram/
|
||||
wiki_link: https://wiki.jfa-go.com/docs/external-services/bots/telegram/
|
||||
settings:
|
||||
- setting: enabled
|
||||
name: Enabled
|
||||
@@ -1004,7 +1061,7 @@ sections:
|
||||
name: Matrix
|
||||
description: Settings for Matrix invites/signup/notifications. See the jfa-go
|
||||
wiki for info on setting this up.
|
||||
wiki_link: https://wiki.jfa-go.com/docs/bots/matrix/
|
||||
wiki_link: https://wiki.jfa-go.com/docs/external-services/bots/matrix/
|
||||
settings:
|
||||
- setting: enabled
|
||||
name: Enabled
|
||||
@@ -1236,7 +1293,7 @@ sections:
|
||||
description: Path to custom email text template for announcements/custom messages.
|
||||
- section: notifications
|
||||
meta:
|
||||
name: Admin invite notifications
|
||||
name: Admin notifications
|
||||
description: Allows toggling "user created" and "invite expired" notifications
|
||||
to be sent to the admin per-invite.
|
||||
depends_true: messages|enabled
|
||||
@@ -1276,13 +1333,13 @@ sections:
|
||||
description: Path to user creation notification email in plaintext.
|
||||
- section: ombi
|
||||
meta:
|
||||
name: Ombi Integration
|
||||
name: Ombi
|
||||
description: Connect to Ombi to automatically create both Ombi and Jellyfin accounts
|
||||
for new users. You'll need to add a ombi template to an existing User Profile
|
||||
for accounts to be created, which you can do by refreshing then checking Settings
|
||||
> User Profiles. To handle password resets for Ombi & Jellyfin, enable "Use
|
||||
reset link instead of PIN".
|
||||
wiki_link: https://wiki.jfa-go.com/docs/ombi/
|
||||
wiki_link: https://wiki.jfa-go.com/docs/external-services/ombi/
|
||||
settings:
|
||||
- setting: enabled
|
||||
name: Enabled
|
||||
@@ -1305,7 +1362,7 @@ sections:
|
||||
description: API Key. Get this from the first tab in Ombi settings.
|
||||
- section: jellyseerr
|
||||
meta:
|
||||
name: Jellyseerr Integration
|
||||
name: Jellyseerr
|
||||
description: Connect to Jellyseerr to automatically trigger the import of users
|
||||
on account creation, and to automatically link contact methods (email, discord
|
||||
and telegram). A template must be added to a User Profile for accounts to be
|
||||
@@ -1425,7 +1482,7 @@ sections:
|
||||
name: Email confirmation
|
||||
description: If enabled, a user will be sent an email confirmation link to ensure
|
||||
their password is right before they can make an account.
|
||||
depends_true: email|method
|
||||
depends_true: email|collect
|
||||
settings:
|
||||
- setting: enabled
|
||||
name: Enabled
|
||||
@@ -1448,7 +1505,7 @@ sections:
|
||||
description: Path to custom email in plain text
|
||||
- section: user_expiry
|
||||
meta:
|
||||
name: User Expiry
|
||||
name: Account Expiry
|
||||
description: When set on an invite, users will be deleted or disabled a specified
|
||||
amount of time after they create their account. Expiries can also be set and
|
||||
extended for invididual users, optionally with a message why.
|
||||
@@ -1590,7 +1647,7 @@ sections:
|
||||
description: jfa-go will send a POST request to these URLs when an event occurs,
|
||||
with relevant information. Request information is logged when debug logging
|
||||
is enabled.
|
||||
wiki_link: https://wiki.jfa-go.com/docs/webhooks/
|
||||
wiki_link: https://wiki.jfa-go.com/docs/dev/webhooks/
|
||||
settings:
|
||||
- setting: created
|
||||
name: User Created
|
||||
|
||||
@@ -221,15 +221,8 @@ sup.\~critical, .text-critical {
|
||||
padding-bottom: 0.1rem;
|
||||
}
|
||||
|
||||
.settings-section-button {
|
||||
width: 100%;
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
.settings-section-button:hover, .settings-section-button:focus {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 2.5rem;
|
||||
background-color: var(--color-neutral-normal-fill);
|
||||
filter: brightness(var(--settings-section-button-filter)) !important;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
.tooltip .content {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
max-width: 10rem;
|
||||
max-width: 16rem;
|
||||
min-width: 6rem;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
color: #fff;
|
||||
@@ -22,15 +22,18 @@
|
||||
}
|
||||
|
||||
.tooltip.below .content {
|
||||
top: 2.5rem;
|
||||
left: 0;
|
||||
top: calc(100% + 0.125rem);
|
||||
left: 50%;
|
||||
right: 0;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.tooltip.above .content {
|
||||
bottom: 2.5rem;
|
||||
left: 0;
|
||||
top: unset;
|
||||
bottom: calc(100% + 0.125rem);
|
||||
left: 50%;
|
||||
right: 0;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.tooltip.darker .content {
|
||||
|
||||
11
discord.go
11
discord.go
@@ -32,6 +32,17 @@ type DiscordDaemon struct {
|
||||
retryOpts *common.MustAuthenticateOptions
|
||||
}
|
||||
|
||||
func EmptyDiscordUser() *DiscordUser {
|
||||
return &DiscordUser{
|
||||
ID: "",
|
||||
Username: "",
|
||||
Discriminator: "",
|
||||
Lang: "",
|
||||
Contact: false,
|
||||
JellyfinID: "",
|
||||
}
|
||||
}
|
||||
|
||||
func newDiscordDaemon(app *appContext) (*DiscordDaemon, error) {
|
||||
token := app.config.Section("discord").Key("token").String()
|
||||
if token == "" {
|
||||
|
||||
115
go.mod
115
go.mod
@@ -1,8 +1,6 @@
|
||||
module github.com/hrfee/jfa-go
|
||||
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.24.0
|
||||
go 1.24.0
|
||||
|
||||
replace github.com/hrfee/jfa-go/docs => ./docs
|
||||
|
||||
@@ -30,47 +28,48 @@ require (
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/getlantern/systray v1.2.2
|
||||
github.com/gin-contrib/pprof v1.5.3
|
||||
github.com/gin-gonic/gin v1.10.1
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b
|
||||
github.com/hrfee/jfa-go/common v0.0.0-20250716174732-bcb6346f8115
|
||||
github.com/hrfee/jfa-go/docs v0.0.0-20250716174732-bcb6346f8115
|
||||
github.com/hrfee/jfa-go/easyproxy v0.0.0-20250716174732-bcb6346f8115
|
||||
github.com/hrfee/jfa-go/jellyseerr v0.0.0-20250716174732-bcb6346f8115
|
||||
github.com/hrfee/jfa-go/linecache v0.0.0-20250716174732-bcb6346f8115
|
||||
github.com/hrfee/jfa-go/logger v0.0.0-20250716174732-bcb6346f8115
|
||||
github.com/hrfee/jfa-go/logmessages v0.0.0-20250716174732-bcb6346f8115
|
||||
github.com/hrfee/jfa-go/ombi v0.0.0-20250716174732-bcb6346f8115
|
||||
github.com/hrfee/mediabrowser v0.3.29
|
||||
github.com/itchyny/timefmt-go v0.1.6
|
||||
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a
|
||||
github.com/hrfee/jfa-go/common v0.0.0-20251123165523-7c9f91711460
|
||||
github.com/hrfee/jfa-go/docs v0.0.0-20251123165523-7c9f91711460
|
||||
github.com/hrfee/jfa-go/easyproxy v0.0.0-20251123165523-7c9f91711460
|
||||
github.com/hrfee/jfa-go/jellyseerr v0.0.0-20251123165523-7c9f91711460
|
||||
github.com/hrfee/jfa-go/linecache v0.0.0-20251123165523-7c9f91711460
|
||||
github.com/hrfee/jfa-go/logger v0.0.0-20251123165523-7c9f91711460
|
||||
github.com/hrfee/jfa-go/logmessages v0.0.0-20251123165523-7c9f91711460
|
||||
github.com/hrfee/jfa-go/ombi v0.0.0-20251123165523-7c9f91711460
|
||||
github.com/hrfee/mediabrowser v0.3.30
|
||||
github.com/itchyny/timefmt-go v0.1.7
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7
|
||||
github.com/mailgun/mailgun-go/v4 v4.23.0
|
||||
github.com/mattn/go-sqlite3 v1.14.28
|
||||
github.com/mattn/go-sqlite3 v1.14.32
|
||||
github.com/robert-nix/ansihtml v1.0.1
|
||||
github.com/steambap/captcha v1.4.1
|
||||
github.com/swaggo/files v1.0.1
|
||||
github.com/swaggo/gin-swagger v1.6.0
|
||||
github.com/swaggo/gin-swagger v1.6.1
|
||||
github.com/timshannon/badgerhold/v4 v4.0.3
|
||||
github.com/writeas/go-strip-markdown v2.0.1+incompatible
|
||||
github.com/xhit/go-simple-mail/v2 v2.16.0
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
maunium.net/go/mautrix v0.24.2
|
||||
maunium.net/go/mautrix v0.26.0
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/bytedance/sonic v1.13.3 // indirect
|
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic v1.14.2 // indirect
|
||||
github.com/bytedance/sonic/loader v0.4.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/dgraph-io/ristretto v1.0.0 // indirect
|
||||
github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect
|
||||
github.com/dgraph-io/ristretto/v2 v2.3.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.11 // indirect
|
||||
github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201 // indirect
|
||||
github.com/getlantern/errors v1.0.4 // indirect
|
||||
github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65 // indirect
|
||||
@@ -78,68 +77,82 @@ require (
|
||||
github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770 // indirect
|
||||
github.com/getlantern/ops v0.0.0-20231025133620-f368ab734534 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-chi/chi/v5 v5.2.2 // indirect
|
||||
github.com/go-chi/chi/v5 v5.2.3 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.22.3 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.3 // indirect
|
||||
github.com/go-openapi/spec v0.22.1 // indirect
|
||||
github.com/go-openapi/swag v0.25.3 // indirect
|
||||
github.com/go-openapi/swag/conv v0.25.3 // indirect
|
||||
github.com/go-openapi/swag/jsonname v0.25.3 // indirect
|
||||
github.com/go-openapi/swag/jsonutils v0.25.3 // indirect
|
||||
github.com/go-openapi/swag/loading v0.25.3 // indirect
|
||||
github.com/go-openapi/swag/stringutils v0.25.3 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.25.3 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.25.3 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.28.0 // indirect
|
||||
github.com/go-stack/stack v1.8.1 // indirect
|
||||
github.com/go-test/deep v1.1.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/flatbuffers v25.2.10+incompatible // indirect
|
||||
github.com/google/flatbuffers v25.9.23+incompatible // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/compress v1.18.1 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b // indirect
|
||||
github.com/mailgun/errors v0.4.0 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/mailru/easyjson v0.9.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20250508124226-395b08cebbdb // indirect
|
||||
github.com/petermattis/goid v0.0.0-20251121121749-a11dd1a45f9a // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.57.0 // indirect
|
||||
github.com/rs/zerolog v1.34.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/swaggo/swag v1.16.4 // indirect
|
||||
github.com/swaggo/swag v1.16.6 // indirect
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/match v1.2.0 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/toorop/go-dkim v0.0.0-20250226130143-9025cce95817 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||
go.mau.fi/util v0.8.8 // indirect
|
||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||
go.mau.fi/util v0.9.3 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/otel v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||
go.uber.org/mock v0.6.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/arch v0.19.0 // indirect
|
||||
golang.org/x/crypto v0.40.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect
|
||||
golang.org/x/image v0.29.0 // indirect
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
golang.org/x/tools v0.35.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
go.uber.org/zap v1.27.1 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/arch v0.23.0 // indirect
|
||||
golang.org/x/crypto v0.45.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 // indirect
|
||||
golang.org/x/image v0.33.0 // indirect
|
||||
golang.org/x/mod v0.30.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
golang.org/x/tools v0.39.0 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
)
|
||||
|
||||
113
go.sum
113
go.sum
@@ -16,15 +16,21 @@ github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd
|
||||
github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
||||
github.com/bwmarrin/discordgo v0.29.0 h1:FmWeXFaKUwrcL3Cx65c20bTRW+vOb6k8AnaP+EgjDno=
|
||||
github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
||||
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||
github.com/bytedance/sonic v1.12.4 h1:9Csb3c9ZJhfUWeMtpCDCq6BUoH5ogfDFLUgQ/jG+R0k=
|
||||
github.com/bytedance/sonic v1.12.4/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
||||
github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
|
||||
github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||
github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE=
|
||||
github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
|
||||
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o=
|
||||
github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
@@ -36,6 +42,8 @@ github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
@@ -58,6 +66,8 @@ github.com/dgraph-io/ristretto v1.0.0 h1:SYG07bONKMlFDUYu5pEu3DGAh8c2OFNzKm6G9J4
|
||||
github.com/dgraph-io/ristretto v1.0.0/go.mod h1:jTi2FiYEhQ1NsMmA7DeBykizjOuY88NhKBkepyu1jPc=
|
||||
github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM=
|
||||
github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI=
|
||||
github.com/dgraph-io/ristretto/v2 v2.3.0 h1:qTQ38m7oIyd4GAed/QkUZyPFNMnvVWyazGXRwvOt5zk=
|
||||
github.com/dgraph-io/ristretto/v2 v2.3.0/go.mod h1:gpoRV3VzrEY1a9dWAYV6T1U7YzfgttXdd/ZzL1s9OZM=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
@@ -84,6 +94,8 @@ github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf
|
||||
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||
github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik=
|
||||
github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
|
||||
github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201 h1:oEZYEpZo28Wdx+5FZo4aU7JFXu0WG/4wJWese5reQSA=
|
||||
github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201/go.mod h1:Y9WZUHEb+mpra02CbQ/QczLUe6f0Dezxaw5DCJlJQGo=
|
||||
@@ -126,10 +138,14 @@ github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
||||
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
@@ -145,16 +161,22 @@ github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
|
||||
github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
|
||||
github.com/go-openapi/jsonpointer v0.22.3 h1:dKMwfV4fmt6Ah90zloTbUKWMD+0he+12XYAsPotrkn8=
|
||||
github.com/go-openapi/jsonpointer v0.22.3/go.mod h1:0lBbqeRsQ5lIanv3LHZBrmRGHLHcQoOXQnf88fHlGWo=
|
||||
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
|
||||
github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
|
||||
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
|
||||
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/jsonreference v0.21.3 h1:96Dn+MRPa0nYAR8DR1E03SblB5FJvh7W6krPI0Z7qMc=
|
||||
github.com/go-openapi/jsonreference v0.21.3/go.mod h1:RqkUP0MrLf37HqxZxrIAtTWW4ZJIK1VzduhXYBEeGc4=
|
||||
github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||
github.com/go-openapi/spec v0.22.1 h1:beZMa5AVQzRspNjvhe5aG1/XyBSMeX1eEOs7dMoXh/k=
|
||||
github.com/go-openapi/spec v0.22.1/go.mod h1:c7aeIQT175dVowfp7FeCvXXnjN/MrpaONStibD2WtDA=
|
||||
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
@@ -162,6 +184,22 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
|
||||
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
|
||||
github.com/go-openapi/swag v0.25.3 h1:FAa5wJXyDtI7yUztKDfZxDrSx+8WTg31MfCQ9s3PV+s=
|
||||
github.com/go-openapi/swag v0.25.3/go.mod h1:tX9vI8Mj8Ny+uCEk39I1QADvIPI7lkndX4qCsEqhkS8=
|
||||
github.com/go-openapi/swag/conv v0.25.3 h1:PcB18wwfba7MN5BVlBIV+VxvUUeC2kEuCEyJ2/t2X7E=
|
||||
github.com/go-openapi/swag/conv v0.25.3/go.mod h1:n4Ibfwhn8NJnPXNRhBO5Cqb9ez7alBR40JS4rbASUPU=
|
||||
github.com/go-openapi/swag/jsonname v0.25.3 h1:U20VKDS74HiPaLV7UZkztpyVOw3JNVsit+w+gTXRj0A=
|
||||
github.com/go-openapi/swag/jsonname v0.25.3/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag=
|
||||
github.com/go-openapi/swag/jsonutils v0.25.3 h1:kV7wer79KXUM4Ea4tBdAVTU842Rg6tWstX3QbM4fGdw=
|
||||
github.com/go-openapi/swag/jsonutils v0.25.3/go.mod h1:ILcKqe4HC1VEZmJx51cVuZQ6MF8QvdfXsQfiaCs0z9o=
|
||||
github.com/go-openapi/swag/loading v0.25.3 h1:Nn65Zlzf4854MY6Ft0JdNrtnHh2bdcS/tXckpSnOb2Y=
|
||||
github.com/go-openapi/swag/loading v0.25.3/go.mod h1:xajJ5P4Ang+cwM5gKFrHBgkEDWfLcsAKepIuzTmOb/c=
|
||||
github.com/go-openapi/swag/stringutils v0.25.3 h1:nAmWq1fUTWl/XiaEPwALjp/8BPZJun70iDHRNq/sH6w=
|
||||
github.com/go-openapi/swag/stringutils v0.25.3/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0=
|
||||
github.com/go-openapi/swag/typeutils v0.25.3 h1:2w4mEEo7DQt3V4veWMZw0yTPQibiL3ri2fdDV4t2TQc=
|
||||
github.com/go-openapi/swag/typeutils v0.25.3/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.3 h1:LKTJjCn/W1ZfMec0XDL4Vxh8kyAnv1orH5F2OREDUrg=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.3/go.mod h1:Y7QN6Wc5DOBXK14/xeo1cQlq0EA0wvLoSv13gDQoCao=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
@@ -172,6 +210,8 @@ github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27
|
||||
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
|
||||
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
|
||||
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
|
||||
@@ -183,6 +223,8 @@ github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
@@ -219,12 +261,16 @@ github.com/gomarkdown/markdown v0.0.0-20241105142532-d03b89096d81 h1:5lyLWsV+qCk
|
||||
github.com/gomarkdown/markdown v0.0.0-20241105142532-d03b89096d81/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b h1:EY/KpStFl60qA17CptGXhwfZ+k1sFNJIUNR8DdbcuUk=
|
||||
github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a h1:l7A0loSszR5zHd/qK53ZIHMO8b3bBSmENnQ6eKnUT0A=
|
||||
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/flatbuffers v23.5.9+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI=
|
||||
github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q=
|
||||
github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/flatbuffers v25.9.23+incompatible h1:rGZKv+wOb6QPzIdkM2KxhBZCDrA0DeN6DNmRDrqIsQU=
|
||||
github.com/google/flatbuffers v25.9.23+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
@@ -249,9 +295,13 @@ github.com/hrfee/mediabrowser v0.3.28 h1:KkSgODXxUnZLrkmjSWpma8mXwEVxlOtI51uS2QP
|
||||
github.com/hrfee/mediabrowser v0.3.28/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U=
|
||||
github.com/hrfee/mediabrowser v0.3.29 h1:xTqGS9u8HuolZAhouYHxutnE0fF/8aVCInbByKZEzIo=
|
||||
github.com/hrfee/mediabrowser v0.3.29/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U=
|
||||
github.com/hrfee/mediabrowser v0.3.30 h1:llJo4hxWchbwROnkfhlYsrvtZ6/8WDTp3QxAvbgjUfI=
|
||||
github.com/hrfee/mediabrowser v0.3.30/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q=
|
||||
github.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg=
|
||||
github.com/itchyny/timefmt-go v0.1.7 h1:xyftit9Tbw+Dc/huSSPJaEmX1TVL8lw5vxjJLK4GMMA=
|
||||
github.com/itchyny/timefmt-go v0.1.7/go.mod h1:5E46Q+zj7vbTgWY8o5YkMeYb4I6GeWLFnetPy5oBrAI=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
@@ -266,6 +316,8 @@ github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IX
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
|
||||
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
@@ -302,6 +354,8 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
|
||||
github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
@@ -319,6 +373,8 @@ github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBW
|
||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
|
||||
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
||||
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -338,12 +394,18 @@ github.com/petermattis/goid v0.0.0-20241025130422-66cb2e6d7274 h1:qli3BGQK0tYDkS
|
||||
github.com/petermattis/goid v0.0.0-20241025130422-66cb2e6d7274/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||
github.com/petermattis/goid v0.0.0-20250508124226-395b08cebbdb h1:3PrKuO92dUTMrQ9dx0YNejC6U/Si6jqKmyQ9vWjwqR4=
|
||||
github.com/petermattis/goid v0.0.0-20250508124226-395b08cebbdb/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||
github.com/petermattis/goid v0.0.0-20251121121749-a11dd1a45f9a h1:VweslR2akb/ARhXfqSfRbj1vpWwYXf3eeAUyw/ndms0=
|
||||
github.com/petermattis/goid v0.0.0-20251121121749-a11dd1a45f9a/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||
github.com/quic-go/quic-go v0.57.0 h1:AsSSrrMs4qI/hLrKlTH/TGQeTMY0ib1pAOX7vA3AdqE=
|
||||
github.com/quic-go/quic-go v0.57.0/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s=
|
||||
github.com/robert-nix/ansihtml v1.0.1 h1:VTiyQ6/+AxSJoSSLsMecnkh8i0ZqOEdiRl/odOc64fc=
|
||||
github.com/robert-nix/ansihtml v1.0.1/go.mod h1:CJwclxYaTPc2RfcxtanEACsYuTksh4yDXcNeHHKZINE=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
@@ -373,6 +435,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
@@ -381,18 +444,24 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E=
|
||||
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
|
||||
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
|
||||
github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI=
|
||||
github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M=
|
||||
github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
|
||||
github.com/swaggo/gin-swagger v1.6.1 h1:Ri06G4gc9N4t4k8hekMigJ9zKTFSlqj/9paAQCQs7cY=
|
||||
github.com/swaggo/gin-swagger v1.6.1/go.mod h1:LQ+hJStHakCWRiK/YNYtJOu4mR2FP+pxLnILT/qNiTw=
|
||||
github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y=
|
||||
github.com/swaggo/swag v1.6.7/go.mod h1:xDhTyuFIujYiN3DKWC/H/83xcfHp+UE/IzWWampG7Zc=
|
||||
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
|
||||
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
|
||||
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
|
||||
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
|
||||
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
@@ -400,6 +469,8 @@ github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
|
||||
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
@@ -423,6 +494,8 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
||||
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
||||
github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6FkrCNfXkvbR+Nbu1ls48pXYcw=
|
||||
@@ -438,39 +511,57 @@ go.mau.fi/util v0.8.1 h1:Ga43cz6esQBYqcjZ/onRoVnYWoUwjWbsxVeJg2jOTSo=
|
||||
go.mau.fi/util v0.8.1/go.mod h1:T1u/rD2rzidVrBLyaUdPpZiJdP/rsyi+aTzn0D+Q6wc=
|
||||
go.mau.fi/util v0.8.8 h1:OnuEEc/sIJFhnq4kFggiImUpcmnmL/xpvQMRu5Fiy5c=
|
||||
go.mau.fi/util v0.8.8/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c=
|
||||
go.mau.fi/util v0.9.3 h1:aqNF8KDIN8bFpFbybSk+mEBil7IHeBwlujfyTnvP0uU=
|
||||
go.mau.fi/util v0.9.3/go.mod h1:krWWfBM1jWTb5f8NCa2TLqWMQuM81X7TGQjhMjBeXmQ=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.9.0/go.mod h1:np4EoPGzoPs3O67xUVNoPPcmSvsfOxNlNA4F4AC+0Eo=
|
||||
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
|
||||
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
|
||||
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/trace v1.9.0/go.mod h1:2737Q0MuG8q1uILYm2YYVkAyLtOofiTNGg6VODnOiPo=
|
||||
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
|
||||
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4=
|
||||
golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/arch v0.19.0 h1:LmbDQUodHThXE+htjrnmVD73M//D9GTH6wFZjyDkjyU=
|
||||
golang.org/x/arch v0.19.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||
golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg=
|
||||
golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@@ -482,16 +573,22 @@ golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
|
||||
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc h1:TS73t7x3KarrNd5qAipmspBDS1rkMcgVG/fS1aRb4Rc=
|
||||
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
|
||||
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0=
|
||||
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
|
||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||
golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
|
||||
golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78=
|
||||
golang.org/x/image v0.29.0 h1:HcdsyR4Gsuys/Axh0rDEmlBmB68rW1U9BUdB3UVHsas=
|
||||
golang.org/x/image v0.29.0/go.mod h1:RVJROnf3SLK8d26OW91j4FrIHGbsJ8QnbEocVTOWQDA=
|
||||
golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ=
|
||||
golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
@@ -504,6 +601,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
||||
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -529,6 +628,8 @@ golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
|
||||
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -543,6 +644,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -574,6 +677,8 @@ golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@@ -589,6 +694,8 @@ golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/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=
|
||||
@@ -607,6 +714,8 @@ golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
|
||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
||||
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -639,6 +748,8 @@ google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFyt
|
||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/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=
|
||||
@@ -662,4 +773,6 @@ maunium.net/go/mautrix v0.21.1 h1:Z+e448jtlY977iC1kokNJTH5kg2WmDpcQCqn+v9oZOA=
|
||||
maunium.net/go/mautrix v0.21.1/go.mod h1:7F/S6XAdyc/6DW+Q7xyFXRSPb6IjfqMb1OMepQ8C8OE=
|
||||
maunium.net/go/mautrix v0.24.2 h1:+AVT5kbcA/QuT5svrJKp4ivwoUmz+RRplMp3DnfpheI=
|
||||
maunium.net/go/mautrix v0.24.2/go.mod h1:1ut900w++eE9by9yqCR2dQdMqwsHwZG5L+1bKB1EvSA=
|
||||
maunium.net/go/mautrix v0.26.0 h1:valc2VmZF+oIY4bMq4Cd5H9cEKMRe8eP4FM7iiaYLxI=
|
||||
maunium.net/go/mautrix v0.26.0/go.mod h1:NWMv+243NX/gDrLofJ2nNXJPrG8vzoM+WUCWph85S6Q=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
|
||||
16
html/account-linking-discord.html
Normal file
16
html/account-linking-discord.html
Normal file
@@ -0,0 +1,16 @@
|
||||
{{ if .discordEnabled }}
|
||||
<div id="modal-discord" class="modal">
|
||||
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3">
|
||||
<span class="heading mb-4">{{ .strings.linkDiscord }}</span>
|
||||
<p class="content mb-4"> {{ .discordSendPINMessage }}</p>
|
||||
<h1 class="text-center text-2xl mb-2 pin"></h1>
|
||||
<div class="row center">
|
||||
<a class="my-5 hover:underline">
|
||||
<span class="mr-2">{{ .strings.joinTheServer }}</span>
|
||||
<span id="discord-invite"></span>
|
||||
</a>
|
||||
</div>
|
||||
<span class="button ~info @low full-width center mt-4" id="discord-waiting">{{ .strings.success }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
18
html/account-linking-matrix.html
Normal file
18
html/account-linking-matrix.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{{ if .matrixEnabled }}
|
||||
<div id="modal-matrix" class="modal">
|
||||
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3">
|
||||
<span class="heading mb-4">{{ .strings.linkMatrix }}</span>
|
||||
<p class="content mb-4"> {{ .strings.matrixEnterUser }}</p>
|
||||
<input type="text" class="input ~neutral @high" placeholder="@user:riot.im" id="matrix-userid">
|
||||
<div class="subheading link-center mt-4">
|
||||
<span class="shield ~info mr-4">
|
||||
<span class="icon">
|
||||
<i class="ri-chat-3-line"></i>
|
||||
</span>
|
||||
</span>
|
||||
{{ .matrixUser }}
|
||||
</div>
|
||||
<span class="button ~info @low full-width center mt-4" id="matrix-send">{{ .strings.submit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
18
html/account-linking-telegram.html
Normal file
18
html/account-linking-telegram.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{{ if .telegramEnabled }}
|
||||
<div id="modal-telegram" class="modal">
|
||||
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3">
|
||||
<span class="heading mb-4">{{ .strings.linkTelegram }}</span>
|
||||
<p class="content mb-4">{{ .strings.sendPIN }}</p>
|
||||
<p class="text-center text-2xl mb-2 pin"></p>
|
||||
<a class="subheading link link-center" href="{{ .telegramURL }}" target="_blank">
|
||||
<span class="shield ~info mr-4">
|
||||
<span class="icon">
|
||||
<i class="ri-telegram-line"></i>
|
||||
</span>
|
||||
</span>
|
||||
@<span class="username">{{ .telegramUsername }}</span>
|
||||
</a>
|
||||
<span class="button ~info @low full-width center mt-4" id="telegram-waiting">{{ .strings.success }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
@@ -1,52 +1,3 @@
|
||||
{{ if .discordEnabled }}
|
||||
<div id="modal-discord" class="modal">
|
||||
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3">
|
||||
<span class="heading mb-4">{{ .strings.linkDiscord }}</span>
|
||||
<p class="content mb-4"> {{ .discordSendPINMessage }}</p>
|
||||
<h1 class="text-center text-2xl mb-2 pin"></h1>
|
||||
<div class="row center">
|
||||
<a class="my-5 hover:underline">
|
||||
<span class="mr-2">{{ .strings.joinTheServer }}</span>
|
||||
<span id="discord-invite"></span>
|
||||
</a>
|
||||
</div>
|
||||
<span class="button ~info @low full-width center mt-4" id="discord-waiting">{{ .strings.success }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ if .telegramEnabled }}
|
||||
<div id="modal-telegram" class="modal">
|
||||
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3">
|
||||
<span class="heading mb-4">{{ .strings.linkTelegram }}</span>
|
||||
<p class="content mb-4">{{ .strings.sendPIN }}</p>
|
||||
<p class="text-center text-2xl mb-2 pin"></p>
|
||||
<a class="subheading link-center" href="{{ .telegramURL }}" target="_blank">
|
||||
<span class="shield ~info mr-4">
|
||||
<span class="icon">
|
||||
<i class="ri-telegram-line"></i>
|
||||
</span>
|
||||
</span>
|
||||
@{{ .telegramUsername }}
|
||||
</a>
|
||||
<span class="button ~info @low full-width center mt-4" id="telegram-waiting">{{ .strings.success }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ if .matrixEnabled }}
|
||||
<div id="modal-matrix" class="modal">
|
||||
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3">
|
||||
<span class="heading mb-4">{{ .strings.linkMatrix }}</span>
|
||||
<p class="content mb-4"> {{ .strings.matrixEnterUser }}</p>
|
||||
<input type="text" class="input ~neutral @high" placeholder="@user:riot.im" id="matrix-userid">
|
||||
<div class="subheading link-center mt-4">
|
||||
<span class="shield ~info mr-4">
|
||||
<span class="icon">
|
||||
<i class="ri-chat-3-line"></i>
|
||||
</span>
|
||||
</span>
|
||||
{{ .matrixUser }}
|
||||
</div>
|
||||
<span class="button ~info @low full-width center mt-4" id="matrix-send">{{ .strings.submit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ template "account-linking-discord.html" . }}
|
||||
{{ template "account-linking-telegram.html" . }}
|
||||
{{ template "account-linking-matrix.html" . }}
|
||||
|
||||
@@ -186,56 +186,60 @@
|
||||
</form>
|
||||
</div>
|
||||
<div id="modal-extend-expiry" class="modal">
|
||||
<form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3" id="form-extend-expiry" href="">
|
||||
<form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3 flex flex-col gap-2" id="form-extend-expiry" href="">
|
||||
<span class="heading"><span id="header-extend-expiry"></span> <span class="modal-close">×</span></span>
|
||||
<div class="content mt-8">
|
||||
<aside class="aside sm ~urge dark:~d_info mb-2 @low row unfocused" id="extend-expiry-date"></aside>
|
||||
<div>
|
||||
<span class="text-xl supra row py-1">{{ .strings.setExpiry }}</span>
|
||||
<div class="row">
|
||||
<input type="text" id="extend-expiry-text" class="input ~neutral @low mb-2 mt-4" placeholder="{{ .strings.enterExpiry }}">
|
||||
</div>
|
||||
<div class="flex flex-col gap-3">
|
||||
<aside class="aside sm ~urge dark:~d_info @low unfocused" id="extend-expiry-date"></aside>
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="text-xl supra">{{ .strings.setExpiry }}</span>
|
||||
<input type="text" id="extend-expiry-text" class="input ~neutral @low" placeholder="{{ .strings.enterExpiry }}">
|
||||
</div>
|
||||
<div id="extend-expiry-field-inputs">
|
||||
<span class="text-xl supra row py-1">{{ .strings.extendExpiry }}</span>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div id="extend-expiry-field-inputs" class="flex flex-col gap-2">
|
||||
<span class="text-xl supra">{{ .strings.extendExpiry }}</span>
|
||||
<div class="grid grid-cols-2 grid-rows-2 gap-2">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="label supra" for="extend-expiry-months">{{ .strings.inviteMonths }}</label>
|
||||
<div class="select ~neutral @low mb-2 mt-4">
|
||||
<div class="select ~neutral @low">
|
||||
<select id="extend-expiry-months">
|
||||
<option>0</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="label supra" for="extend-expiry-days">{{ .strings.inviteDays }}</label>
|
||||
<div class="select ~neutral @low mb-2 mt-4">
|
||||
<div class="select ~neutral @low">
|
||||
<select id="extend-expiry-days">
|
||||
<option>0</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="label supra" for="extend-expiry-hours">{{ .strings.inviteHours }}</label>
|
||||
<div class="select ~neutral @low mb-2 mt-4">
|
||||
<div class="select ~neutral @low">
|
||||
<select id="extend-expiry-hours">
|
||||
<option>0</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="label supra" for="extend-expiry-minutes">{{ .strings.inviteMinutes }}</label>
|
||||
<div class="select ~neutral @low mb-2 mt-4">
|
||||
<div class="select ~neutral @low">
|
||||
<select id="extend-expiry-minutes">
|
||||
<option>0</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="expiry-use-previous">
|
||||
<span>{{ .strings.extendFromPreviousExpiry }}</span>
|
||||
<div class="tooltip left">
|
||||
<i class="icon ri-information-line align-middle"></i>
|
||||
<div class="content sm w-max">{{ .strings.extendFromPreviousExpiryDescription }}</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<label class="switch mb-4">
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="expiry-extend-enable" checked>
|
||||
<span>{{ .strings.sendDeleteNotificationEmail }}</span>
|
||||
</label>
|
||||
@@ -487,24 +491,7 @@
|
||||
<span class="button ~urge @low full-width center mt-2" id="update-update">{{ .strings.update }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ if .telegramEnabled }}
|
||||
<div id="modal-telegram" class="modal">
|
||||
<div class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3">
|
||||
<span class="heading mb-4">{{ .strings.linkTelegram }}</span>
|
||||
<p class="content mb-4">{{ .strings.sendPIN }}</p>
|
||||
<h1 class="ac" id="telegram-pin"></h1>
|
||||
<a class="subheading link-center" id="telegram-link" target="_blank">
|
||||
<span class="shield ~info mr-2">
|
||||
<span class="icon">
|
||||
<i class="ri-telegram-line"></i>
|
||||
</span>
|
||||
</span>
|
||||
@<span id="telegram-username">
|
||||
</a>
|
||||
<span class="button ~info @low full-width center mt-4" id="telegram-waiting">{{ .strings.success }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ template "account-linking-telegram.html" . }}
|
||||
{{ if .discordEnabled }}
|
||||
<div id="modal-discord" class="modal">
|
||||
<div class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3">
|
||||
@@ -557,7 +544,7 @@
|
||||
<div id="tab-invites" class="flex flex-col gap-4">
|
||||
<div class="card @low dark:~d_neutral flex flex-col gap-2 overflow-visible invites">
|
||||
<span class="heading">{{ .strings.invites }}</span>
|
||||
<div id="invites"></div>
|
||||
<div id="invites" class="flex flex-col gap-2"></div>
|
||||
</div>
|
||||
<div class="card @low dark:~d_neutral flex flex-col gap-2">
|
||||
<span class="heading">{{ .strings.create }}</span>
|
||||
@@ -916,19 +903,19 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col md:flex-row gap-3">
|
||||
<div class="md:card @low dark:~d_neutral flex md:flex flex-col gap-2 flex-1" id="settings-sidebar">
|
||||
<div class="@low dark:~d_neutral flex md:flex flex-col gap-2" id="settings-sidebar">
|
||||
<div class="flex flex-row justify-between">
|
||||
<input type="search" class="field ~neutral @low input settings-section-button justify-between" id="settings-search" placeholder="{{ .strings.search }}">
|
||||
<button class="button ~neutral @low center -ml-10 rounded-s-none settings-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></button>
|
||||
</div>
|
||||
<aside class="aside sm ~urge dark:~d_info @low" id="settings-message">Note: <span class="badge ~critical">*</span> indicates a required field, <span class="badge ~info dark:~d_warning">R</span> indicates changes require a restart.</aside>
|
||||
<div id="settings-loader" class="flex flex-row flex-wrap gap-2">
|
||||
<span class="button ~neutral @low justify-center grow" id="setting-about"><span class="flex">{{ .strings.aboutProgram }} <i class="ri-information-line ml-2"></i></span></span>
|
||||
<a class="button ~urge dark:~d_info @low justify-center grow" target="_blank" href="https://wiki.jfa-go.com"><span class="flex">{{ .strings.wiki }} <i class="ri-book-shelf-line ml-2"></i></a>
|
||||
<span class="button ~neutral @low justify-center grow" id="setting-profiles"><span class="flex">{{ .strings.userProfiles }} <i class="ri-user-line ml-2"></i></span></span>
|
||||
</div>
|
||||
<div class="flex md:flex flex-col gap-2 overflow-y-scroll" id="settings-sidebar-items"></div>
|
||||
</div>
|
||||
<div class="card ~neutral @low overflow flex-1" id="settings-panel">
|
||||
<div class="card ~neutral @low overflow flex-1 grow" id="settings-panel">
|
||||
<div class="settings-section unfocused h-[100%]" id="settings-not-found">
|
||||
<div class="flex flex-col h-[100%] justify-center items-center">
|
||||
<span class="text-2xl font-medium italic mb-2">{{ .strings.noResultsFound }}</span>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!--- This CSS is inlined so we should keep this here! -->
|
||||
<link inline rel="stylesheet" type="text/css" href="web/css/v3bundle.css">
|
||||
<link inline rel="stylesheet" type="text/css" href="web/css/v0.6.0bundle.css">
|
||||
{{ template "header.html" . }}
|
||||
<title>Crash report</title>
|
||||
</head>
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
window.reCAPTCHASiteKey = "{{ .reCAPTCHASiteKey }}";
|
||||
window.userPageEnabled = {{ .userPageEnabled }};
|
||||
window.userPageAddress = "{{ .userPageAddress }}";
|
||||
window.collectEmail = {{ .collectEmail }};
|
||||
{{ if index . "customSuccessCard" }}
|
||||
window.customSuccessCard = {{ .customSuccessCard }};
|
||||
{{ else }}
|
||||
|
||||
@@ -68,8 +68,10 @@
|
||||
<input type="text" class="input ~neutral @high mt-2 mb-4" placeholder="{{ .strings.username }}" id="create-username" aria-label="{{ .strings.username }}">
|
||||
</label>
|
||||
|
||||
<label class="label supra" for="create-email">{{ .strings.emailAddress }}</label>
|
||||
<input type="email" class="input ~neutral @high mt-2 mb-4" placeholder="{{ .strings.emailAddress }}" id="create-email" aria-label="{{ .strings.emailAddress }}" value="{{ .email }}">
|
||||
<div>
|
||||
<label class="label supra" for="create-email">{{ .strings.emailAddress }}</label>
|
||||
<input type="email" class="input ~neutral @high mt-2 mb-4" placeholder="{{ .strings.emailAddress }}" id="create-email" aria-label="{{ .strings.emailAddress }}" value="{{ .email }}">
|
||||
</div>
|
||||
{{ if .telegramEnabled }}
|
||||
<span class="button ~info @low full-width center mb-4" id="link-telegram">{{ .strings.linkTelegram }} {{ if .telegramRequired }}({{ .strings.required }}){{ end }}</span>
|
||||
{{ end }}
|
||||
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hrfee/jfa-go/jellyseerr"
|
||||
@@ -28,7 +29,12 @@ func (app *appContext) SynchronizeJellyseerrUser(jfID string) {
|
||||
if ok && email.Addr != "" && user.Email != email.Addr {
|
||||
err = app.js.ModifyMainUserSettings(jfID, jellyseerr.MainUserSettings{Email: email.Addr})
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedSetEmailAddress, lm.Jellyseerr, jfID, err)
|
||||
if strings.Contains(err.Error(), "INVALID_EMAIL") {
|
||||
app.err.Printf(lm.FailedSetEmailAddress, lm.Jellyseerr, jfID, err.Error()+"\""+email.Addr+"\"")
|
||||
} else {
|
||||
|
||||
app.err.Printf(lm.FailedSetEmailAddress, lm.Jellyseerr, jfID, err)
|
||||
}
|
||||
} else {
|
||||
contactMethods[jellyseerr.FieldEmailEnabled] = email.Contact
|
||||
}
|
||||
|
||||
@@ -92,8 +92,9 @@ func (js *Jellyseerr) req(mode string, uri string, data any, queryParams url.Val
|
||||
var responseText string
|
||||
defer resp.Body.Close()
|
||||
if response || err != nil {
|
||||
responseText, err = js.decodeResp(resp)
|
||||
if err != nil {
|
||||
var decodeErr error
|
||||
responseText, decodeErr = js.decodeResp(resp)
|
||||
if decodeErr != nil {
|
||||
return responseText, resp.StatusCode, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,37 @@
|
||||
"enableReferrals": "Empfehlungen aktivieren",
|
||||
"disableReferrals": "Empfehlungen deaktivieren",
|
||||
"userLabel": "Benutzer Label",
|
||||
"noResultsFound": "Keine Resultate gefunden"
|
||||
"noResultsFound": "Keine Resultate gefunden",
|
||||
"buildTime": "Erstellungszeit",
|
||||
"accountDisabled": "Konto deaktiviert: {user}",
|
||||
"accountReEnabled": "Konto reaktiviert: {user}",
|
||||
"accountExpired": "Konto abgelaufen: {user}",
|
||||
"accountWillExpire": "Konto läuft ab am {date}.",
|
||||
"expirationBasedOn": "Angegebenes Datum basiert auf dem ersten Benutzer.",
|
||||
"userDeleted": "Benutzer wurde gelöscht.",
|
||||
"userDisabled": "Benutzer wurde deaktiviert",
|
||||
"inviteCreated": "Einladung erstellt: {invite}",
|
||||
"inviteDeleted": "Einladung gelöscht: {invite}",
|
||||
"builtBy": "Erstellt von",
|
||||
"accountLinked": "{contactMethod} verknüpft: {user}",
|
||||
"referrer": "Empfehlungsgeber",
|
||||
"loginNotAdmin": "Kein Administrator?",
|
||||
"jellyseerrProfile": "Jellyseerr-Benutzerprofil",
|
||||
"jellyseerrUserDefaultsDescription": "Erstellen Sie einen Jellyseerr-Benutzer und konfigurieren Sie ihn. Wählen Sie ihn anschließend unten aus. Seine Einstellungen/Berechtigungen werden gespeichert und auf neue Jellyseerr-Benutzer angewendet, die von jfa-go erstellt werden, wenn dieses Profil ausgewählt ist.",
|
||||
"sortDirection": "Sortierreihenfolge",
|
||||
"searchAll": "Alle suchen/sortieren",
|
||||
"searchAllRecords": "Alle Datensätze suchen/sortieren (auf dem Server)",
|
||||
"postSignupCard": "Hilfekarte nach der Anmeldung",
|
||||
"postSignupCardDescription": "Karte, die dem Benutzer nach der Anmeldung angezeigt wird. Überschreibt die „Erfolgsmeldung“. Wird durch die Einstellung „Automatische Weiterleitung bei Erfolg“ überschrieben.",
|
||||
"buildTags": "Build Tags",
|
||||
"accountUnlinked": "{contactMethod} entfernt: {user}",
|
||||
"accountResetPassword": "{user} hat sein Passwort zurückgesetzt",
|
||||
"accountChangedPassword": "{user} hat sein Passwort geändert",
|
||||
"accountCreated": "Konto erstellt: {user}",
|
||||
"accountDeleted": "Konto gelöscht: {user}",
|
||||
"applyConfigurationAndPolicy": "Jellyfin Konfiguration/Richtlinie anwenden",
|
||||
"applyOmbi": "Ombi -Profil anwenden (falls verfügbar)",
|
||||
"applyJellyseerr": "Jellyseerr-Profil anwenden (falls verfügbar)"
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "E-Mail-Adresse von {n} geändert.",
|
||||
|
||||
@@ -66,6 +66,8 @@
|
||||
"setExpiry": "Set expiry",
|
||||
"removeExpiry": "Remove expiry",
|
||||
"enterExpiry": "Enter an expiry",
|
||||
"extendFromPreviousExpiry": "Extend from previous expiry date (if possible)",
|
||||
"extendFromPreviousExpiryDescription": "If a record of an expired user's expiry time is found in the activity log, expiry will be extended from then, rather than the current time, unless the new expiry date would have already passed.",
|
||||
"sendPWRManual": "User {n} has no method of contact, press copy to get a link to send to them.",
|
||||
"sendPWRSuccess": "Password reset link sent.",
|
||||
"sendPWRSuccessManual": "If the user hasn't received it, press copy to get a link to manually send to them.",
|
||||
@@ -209,7 +211,9 @@
|
||||
"backupCanBeFound": "The backup can be found on the server at {filepath}.",
|
||||
"backupCanDownload": "Alternatively, click below to download the backup.",
|
||||
"wikiPage": "Wiki Page",
|
||||
"wiki": "Wiki"
|
||||
"wiki": "Wiki",
|
||||
"restartRequired": "Restart required",
|
||||
"required": "Required"
|
||||
},
|
||||
"notifications": {
|
||||
"pathCopied": "Full path copied to clipboard.",
|
||||
|
||||
@@ -93,14 +93,14 @@
|
||||
"notifyEvent": "Értesítés ekkor:",
|
||||
"notifyInviteExpiry": "Lejáratkor",
|
||||
"notifyUserCreation": "Használatkor",
|
||||
"sendPIN": "",
|
||||
"searchDiscordUser": "",
|
||||
"findDiscordUser": "",
|
||||
"linkMatrixDescription": "",
|
||||
"matrixHomeServer": "",
|
||||
"saveAsTemplate": "",
|
||||
"deleteTemplate": "",
|
||||
"templateEnterName": "",
|
||||
"sendPIN": "Kérd meg a felhasználókat, hogy küldjék el a PIN-t a botnak.",
|
||||
"searchDiscordUser": "Kezd el írni adiscord felhasználó nevet a keresés indításához.",
|
||||
"findDiscordUser": "Discord felhasználó keresése",
|
||||
"linkMatrixDescription": "Add meg a felhasználó nevét és jelszavát hogy botként tudd használni. A beküldés után az alkalmazás újra fog indulni.",
|
||||
"matrixHomeServer": "Otthoni szerver címe",
|
||||
"saveAsTemplate": "Mentés sablonként",
|
||||
"deleteTemplate": "Sablon törlése",
|
||||
"templateEnterName": "Adj meg egy nevet a sablon mentéséhez.",
|
||||
"unlink": "Fiók leválasztása",
|
||||
"after": "Utánna",
|
||||
"before": "Elötte",
|
||||
@@ -117,7 +117,35 @@
|
||||
"invite": "Meghívás",
|
||||
"activity": "Aktivitás",
|
||||
"userLabel": "Felhasználói címke",
|
||||
"userLabelDescription": "Ezzel a meghívóval létrehozott felhasználókra alkalmazandó címke."
|
||||
"userLabelDescription": "Ezzel a meghívóval létrehozott felhasználókra alkalmazandó címke.",
|
||||
"noResultsFoundLocally": "A keresés csak a betöltött adatokon meg végbe. Betölthetsz több adatot is vagy kereshetsz az összes adaton.",
|
||||
"keepSearchingDescription": "Csak a betöltött tevékenységek között futott le a keresés. Kattints ide ha az összes tevékenység között szeretnél keresni.",
|
||||
"enableReferralsDescription": "Adjon a felhasználóknak egy meghívóhoz hasonló személyes hivatkozási linket, amelyet elküldhet barátainak/családjának. Ez származhat a profiljukban található ajánlói sablonból vagy egy meglévő meghívóból.",
|
||||
"enableReferralsProfileDescription": "Adj az ezzel a profillal létrehozott felhasználóknak egy személyre szabott ajánlói linket, hasonlóan egy meghívóhoz, amelyet elküldhetnek barátaiknak és családtagjaiknak. Hozz létre egy meghívót a kívánt beállításokkal, majd válaszd ki itt. Minden ajánlás ezután ezen a meghívón alapul majd. A meghívót törölheted, ha kész vagy.",
|
||||
"postSignupCardDescription": "A felhasználónak a regisztráció után megjelenő kártya. Felülírja a „Sikerüzenet” beállítást. Felülírja az „Automatikus átirányítás siker esetén” beállítás.",
|
||||
"buildTime": "Készítési idő",
|
||||
"accessJFA": "jfa-go hozzáférés",
|
||||
"accessJFASettings": "Nem módosítható, mert a Beállítások > Általános menüpontban engedélyezve van a „Csak rendszergazdai felhasználók” vagy az „Összes Jellyfin felhasználó bejelentkezése” lehetőség.",
|
||||
"disabled": "Tiltva",
|
||||
"userPagePage": "Felhasználói oldal: oldal",
|
||||
"noResultsFound": "Nincs megjeleníthető adat",
|
||||
"settingsHiddenDependency": "Az egyező beállítások rejtve vannak, mert egy másik beállítás értékétől függenek:",
|
||||
"settingsDependsOn": "{setting}: ettől függ: {dependency}",
|
||||
"settingsAdvancedMode": "{setting}: Haladó beállítások engedélyezése szükséges",
|
||||
"keepSearching": "Keresés folytatása",
|
||||
"removeExpiry": "Lejárat eltávolítása",
|
||||
"enterExpiry": "Lejárati dátum megadása",
|
||||
"enableReferrals": "Hivatkozások engedélyezése",
|
||||
"disableReferrals": "Hivatkozások tiltása",
|
||||
"useInviteExpiry": "Lejárat beállítása profilból vagy meghívóból",
|
||||
"useInviteExpiryNote": "Alapértelmezés szerint a meghívók 90 nap után lejárnak, de a felhasználó megújíthatja őket. Engedélyezze, ha azt szeretné, hogy a megadott idő lejárta után a meghívás letiltásra kerüljön.",
|
||||
"settingsMaybeUnderAdvanced": "Tipp: Lehet hogy megtalálod amit keresel ha bekapcsolod a haladó beállíításokat.",
|
||||
"jellyseerrProfile": "Jellyseer felhasználói profil",
|
||||
"jellyseerrUserDefaultsDescription": "Hozz létre egy Jellyseerr felhasználót, állítsd be, majd válaszd ki lent. A beállításait/engedélyeit a rendszer tárolja és alkalmazza a jfa-go által létrehozott új Jellyseerr felhasználókra, amikor ezt a profilt kiválasztod.",
|
||||
"sortDirection": "Rendezés iránya",
|
||||
"searchAll": "Összes keresés/rendezés",
|
||||
"searchAllRecords": "Keresés/rendezés az összes adaton(a szerveren lévő)",
|
||||
"builtBy": "Készítette"
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"create": "",
|
||||
"apply": "",
|
||||
"select": "",
|
||||
"name": "",
|
||||
"name": "Nome",
|
||||
"date": "",
|
||||
"setExpiry": "",
|
||||
"updates": "",
|
||||
@@ -117,7 +117,8 @@
|
||||
"userPageLogin": "",
|
||||
"userPagePage": "",
|
||||
"buildTime": "",
|
||||
"builtBy": ""
|
||||
"builtBy": "",
|
||||
"disabled": "Disabilitato"
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "",
|
||||
|
||||
322
lang/admin/tr-TR.json
Normal file
322
lang/admin/tr-TR.json
Normal file
@@ -0,0 +1,322 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "İngilizce (ABD)"
|
||||
},
|
||||
"strings": {
|
||||
"invites": "Davetler",
|
||||
"invite": "Davet",
|
||||
"accounts": "Hesaplar",
|
||||
"activity": "Aktivite",
|
||||
"settings": "Ayarlar",
|
||||
"inviteMonths": "Ay",
|
||||
"inviteDays": "Gün",
|
||||
"inviteHours": "Saat",
|
||||
"inviteMinutes": "Dakika",
|
||||
"inviteNumberOfUses": "",
|
||||
"inviteDuration": "",
|
||||
"warning": "",
|
||||
"inviteInfiniteUsesWarning": "",
|
||||
"inviteSendToEmail": "",
|
||||
"create": "",
|
||||
"apply": "",
|
||||
"select": "",
|
||||
"name": "",
|
||||
"date": "",
|
||||
"updates": "",
|
||||
"update": "",
|
||||
"download": "",
|
||||
"search": "",
|
||||
"advancedSettings": "",
|
||||
"lastActiveTime": "",
|
||||
"from": "",
|
||||
"after": "",
|
||||
"before": "",
|
||||
"user": "",
|
||||
"userExpiry": "",
|
||||
"userExpiryDescription": "",
|
||||
"aboutProgram": "",
|
||||
"version": "",
|
||||
"commitNoun": "",
|
||||
"newUser": "",
|
||||
"profile": "",
|
||||
"unknown": "",
|
||||
"label": "",
|
||||
"userLabel": "",
|
||||
"userLabelDescription": "",
|
||||
"logs": "",
|
||||
"announce": "",
|
||||
"templates": "",
|
||||
"subject": "",
|
||||
"message": "Mesaj",
|
||||
"variables": "",
|
||||
"conditionals": "",
|
||||
"preview": "",
|
||||
"reset": "",
|
||||
"donate": "",
|
||||
"unlink": "",
|
||||
"deleted": "",
|
||||
"disabled": "Devre Dışı",
|
||||
"sendPWR": "",
|
||||
"noResultsFound": "",
|
||||
"noResultsFoundLocally": "",
|
||||
"keepSearching": "",
|
||||
"keepSearchingDescription": "",
|
||||
"contactThrough": "",
|
||||
"extendExpiry": "",
|
||||
"setExpiry": "",
|
||||
"removeExpiry": "",
|
||||
"enterExpiry": "",
|
||||
"sendPWRManual": "",
|
||||
"sendPWRSuccess": "",
|
||||
"sendPWRSuccessManual": "",
|
||||
"sendPWRValidFor": "",
|
||||
"customizeMessages": "",
|
||||
"customizeMessagesDescription": "",
|
||||
"markdownSupported": "",
|
||||
"modifySettings": "",
|
||||
"modifySettingsDescription": "",
|
||||
"enableReferrals": "",
|
||||
"disableReferrals": "",
|
||||
"enableReferralsDescription": "",
|
||||
"enableReferralsProfileDescription": "",
|
||||
"useInviteExpiry": "",
|
||||
"useInviteExpiryNote": "",
|
||||
"applyHomescreenLayout": "",
|
||||
"applyConfigurationAndPolicy": "",
|
||||
"applyOmbi": "",
|
||||
"applyJellyseerr": "",
|
||||
"sendDeleteNotificationEmail": "",
|
||||
"sendDeleteNotifiationExample": "",
|
||||
"settingsRestart": "",
|
||||
"settingsRestarting": "",
|
||||
"settingsRestartRequired": "",
|
||||
"settingsRestartRequiredDescription": "",
|
||||
"settingsApplyRestartLater": "",
|
||||
"settingsApplyRestartNow": "",
|
||||
"settingsApplied": "",
|
||||
"settingsRefreshPage": "",
|
||||
"settingsRequiredOrRestartMessage": "",
|
||||
"settingsSave": "",
|
||||
"settingsHiddenDependency": "",
|
||||
"settingsDependsOn": "",
|
||||
"settingsAdvancedMode": "",
|
||||
"settingsMaybeUnderAdvanced": "",
|
||||
"ombiProfile": "",
|
||||
"ombiUserDefaultsDescription": "",
|
||||
"jellyseerrProfile": "",
|
||||
"jellyseerrUserDefaultsDescription": "",
|
||||
"userProfiles": "",
|
||||
"userProfilesDescription": "",
|
||||
"userProfilesIsDefault": "",
|
||||
"userProfilesLibraries": "",
|
||||
"addProfile": "",
|
||||
"addProfileDescription": "",
|
||||
"addProfileNameOf": "",
|
||||
"addProfileStoreHomescreenLayout": "",
|
||||
"inviteNoUsersCreated": "",
|
||||
"inviteUsersCreated": "",
|
||||
"inviteNoProfile": "",
|
||||
"inviteDateCreated": "",
|
||||
"inviteNoInvites": "",
|
||||
"inviteExpiresInTime": "",
|
||||
"notifyEvent": "",
|
||||
"notifyInviteExpiry": "",
|
||||
"notifyUserCreation": "",
|
||||
"sendPIN": "",
|
||||
"searchDiscordUser": "",
|
||||
"findDiscordUser": "",
|
||||
"linkMatrixDescription": "",
|
||||
"matrixHomeServer": "",
|
||||
"saveAsTemplate": "",
|
||||
"deleteTemplate": "",
|
||||
"templateEnterName": "",
|
||||
"accessJFA": "",
|
||||
"accessJFASettings": "",
|
||||
"sortingBy": "",
|
||||
"sortDirection": "",
|
||||
"filters": "",
|
||||
"clickToRemoveFilter": "",
|
||||
"clearSearch": "",
|
||||
"searchAll": "",
|
||||
"searchAllRecords": "",
|
||||
"actions": "",
|
||||
"searchOptions": "",
|
||||
"matchText": "",
|
||||
"jellyfinID": "",
|
||||
"userPageLogin": "",
|
||||
"userPagePage": "",
|
||||
"postSignupCard": "",
|
||||
"postSignupCardDescription": "",
|
||||
"buildTime": "",
|
||||
"builtBy": "",
|
||||
"buildTags": "",
|
||||
"loginNotAdmin": "",
|
||||
"referrer": "",
|
||||
"accountLinked": "",
|
||||
"accountUnlinked": "",
|
||||
"accountResetPassword": "",
|
||||
"accountChangedPassword": "",
|
||||
"accountCreated": "",
|
||||
"accountDeleted": "",
|
||||
"accountDisabled": "",
|
||||
"accountReEnabled": "",
|
||||
"accountExpired": "",
|
||||
"accountWillExpire": "",
|
||||
"expirationBasedOn": "",
|
||||
"userDeleted": "",
|
||||
"userDisabled": "",
|
||||
"inviteCreated": "",
|
||||
"inviteDeleted": "",
|
||||
"inviteExpired": "",
|
||||
"fromInvite": "",
|
||||
"byAdmin": "",
|
||||
"byUser": "",
|
||||
"byJfaGo": "",
|
||||
"activityID": "",
|
||||
"title": "",
|
||||
"usersMentioned": "",
|
||||
"actor": "",
|
||||
"actorDescription": "",
|
||||
"accountCreationFilter": "",
|
||||
"accountDeletionFilter": "",
|
||||
"accountDisabledFilter": "",
|
||||
"accountEnabledFilter": "",
|
||||
"contactLinkedFilter": "",
|
||||
"contactUnlinkedFilter": "",
|
||||
"passwordChangeFilter": "",
|
||||
"passwordResetFilter": "",
|
||||
"inviteCreatedFilter": "",
|
||||
"inviteDeletedFilter": "",
|
||||
"loadMore": "",
|
||||
"loadAll": "",
|
||||
"noMoreResults": "",
|
||||
"totalRecords": "",
|
||||
"loadedRecords": "",
|
||||
"shownRecords": "",
|
||||
"selectedRecords": "",
|
||||
"allMatchingSelected": "",
|
||||
"allLoadedSelected": "",
|
||||
"backups": "",
|
||||
"backupsDescription": "",
|
||||
"backupsFormatNote": "",
|
||||
"backupsCopy": "",
|
||||
"backupDownloadRestore": "",
|
||||
"backupUpload": "",
|
||||
"backupDownload": "",
|
||||
"backupRestore": "",
|
||||
"backupNow": "",
|
||||
"backupCreated": "",
|
||||
"backupCanBeFound": "",
|
||||
"backupCanDownload": "",
|
||||
"wikiPage": "",
|
||||
"wiki": ""
|
||||
},
|
||||
"notifications": {
|
||||
"pathCopied": "",
|
||||
"changedEmailAddress": "",
|
||||
"userCreated": "",
|
||||
"createProfile": "",
|
||||
"saveSettings": "",
|
||||
"saveEmail": "",
|
||||
"sentAnnouncement": "",
|
||||
"savedAnnouncement": "",
|
||||
"setOmbiProfile": "",
|
||||
"savedProfile": "",
|
||||
"updateApplied": "",
|
||||
"updateAppliedRefresh": "",
|
||||
"telegramVerified": "",
|
||||
"accountConnected": "",
|
||||
"referralsEnabled": "",
|
||||
"activityDeleted": "",
|
||||
"errorInviteNoLongerExists": "",
|
||||
"errorInviteNotFound": "",
|
||||
"errorSettingsAppliedNoHomescreenLayout": "",
|
||||
"errorHomescreenAppliedNoSettings": "",
|
||||
"errorSettingsFailed": "",
|
||||
"errorSaveEmail": "",
|
||||
"errorBlankFields": "",
|
||||
"errorDeleteProfile": "",
|
||||
"errorLoadProfiles": "",
|
||||
"errorCreateProfile": "",
|
||||
"errorSavedProfile": "",
|
||||
"errorSetDefaultProfile": "",
|
||||
"errorLoadUsers": "",
|
||||
"errorLoadSettings": "",
|
||||
"errorSetOmbiProfile": "",
|
||||
"errorLoadOmbiUsers": "",
|
||||
"errorChangedEmailAddress": "",
|
||||
"errorFailureCheckLogs": "",
|
||||
"errorPartialFailureCheckLogs": "",
|
||||
"errorUserCreated": "",
|
||||
"errorSendWelcomeEmail": "",
|
||||
"errorApplyUpdate": "",
|
||||
"errorCheckUpdate": "",
|
||||
"errorNoReferralTemplate": "",
|
||||
"errorLoadActivities": "",
|
||||
"errorInvalidDate": "",
|
||||
"updateAvailable": "",
|
||||
"noUpdatesAvailable": ""
|
||||
},
|
||||
"quantityStrings": {
|
||||
"modifySettingsFor": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"enableReferralsFor": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"deleteNUsers": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"disableUsers": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"reEnableUsers": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"addUser": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"deleteUser": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"deletedUser": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"disabledUser": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"enabledUser": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"announceTo": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"appliedSettings": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"extendExpiry": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"setExpiry": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"extendedExpiry": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,14 +39,19 @@
|
||||
"contactMethods": "Kapcsolati lehetőségek",
|
||||
"accountStatus": "Fiók státusz",
|
||||
"notSet": "Nincs beállítva",
|
||||
"myAccount": "Saját fiókom"
|
||||
"myAccount": "Saját fiókom",
|
||||
"internal": "Belső",
|
||||
"referrals": "Hivatkozások",
|
||||
"inviteRemainingUses": "Fennmaradó felhasználások",
|
||||
"external": "Külső"
|
||||
},
|
||||
"notifications": {
|
||||
"errorLoginBlank": "A felhasználónév és/vagy a jelszó üresen lett hagyva.",
|
||||
"errorConnection": "Nem lehet csatlakozni a jfa-go-hoz.",
|
||||
"errorUnknown": "Ismeretlen hiba.",
|
||||
"error401Unauthorized": "Nincs jogosultság. Próbáld frissíteni az oldalt.",
|
||||
"errorSaveSettings": "Nem lehet menteni a beállításokat."
|
||||
"errorSaveSettings": "Nem lehet menteni a beállításokat.",
|
||||
"errorSpecialSymbols": "Ez a mező nem tartalmazhat speciális karaktereket."
|
||||
},
|
||||
"quantityStrings": {
|
||||
"year": {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Italiano (IT)"
|
||||
},
|
||||
"strings": {
|
||||
"username": "Username",
|
||||
"username": "Nome Utente",
|
||||
"password": "Password",
|
||||
"emailAddress": "Indirizzo Email",
|
||||
"name": "Nome",
|
||||
|
||||
70
lang/common/tr-TR.json
Normal file
70
lang/common/tr-TR.json
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "İngilizce (ABD)"
|
||||
},
|
||||
"strings": {
|
||||
"username": "Kullanıcı Adı",
|
||||
"password": "Şifre",
|
||||
"emailAddress": "E-posta Adresi",
|
||||
"name": "İsim",
|
||||
"submit": "Kaydet",
|
||||
"send": "Gönder",
|
||||
"success": "Başarılı",
|
||||
"continue": "Devam Et",
|
||||
"error": "Hata",
|
||||
"copy": "Kopyala",
|
||||
"copied": "Kopyalandı",
|
||||
"time24h": "24 Saat",
|
||||
"time12h": "12 Saat",
|
||||
"linkTelegram": "Telegram Bağla",
|
||||
"contactEmail": "E-posta ile İletişim",
|
||||
"contactTelegram": "Telegram ile İletişim",
|
||||
"linkDiscord": "Discord Bağla",
|
||||
"linkMatrix": "Matrix Bağla",
|
||||
"contactDiscord": "Discord ile İletişim",
|
||||
"theme": "Tema",
|
||||
"refresh": "Yenile",
|
||||
"required": "Gerekli",
|
||||
"login": "Oturum Aç",
|
||||
"logout": "Oturumu Kapat",
|
||||
"admin": "Yönetici",
|
||||
"enabled": "Etkin",
|
||||
"disabled": "Devre Dışı",
|
||||
"reEnable": "Yeniden Etkinleştir",
|
||||
"disable": "Devre Dışı Bırak",
|
||||
"contactMethods": "İletişim Yöntemleri",
|
||||
"accountStatus": "Hesap Durumu",
|
||||
"notSet": "Ayarlanmadı",
|
||||
"expiry": "Son Kullanma Tarihi",
|
||||
"add": "Ekle",
|
||||
"edit": "Düzenle",
|
||||
"delete": "Sil",
|
||||
"myAccount": "Hesabım",
|
||||
"referrals": "Referanslar",
|
||||
"inviteRemainingUses": "Kalan Kullanım",
|
||||
"internal": "Dahili",
|
||||
"external": "Harici"
|
||||
},
|
||||
"notifications": {
|
||||
"errorLoginBlank": "Kullanıcı adı ve/veya şifre boş bırakıldı.",
|
||||
"errorConnection": "jfa-go'ya bağlanılamadı.",
|
||||
"errorUnknown": "Bilinmeyen hata.",
|
||||
"error401Unauthorized": "Yetkisiz İşlem. Sayfayı yenilemeyi deneyin.",
|
||||
"errorSaveSettings": "Ayarlar kaydedilemedi.",
|
||||
"errorSpecialSymbols": "Alan özel semboller içeremez."
|
||||
},
|
||||
"quantityStrings": {
|
||||
"year": {
|
||||
"singular": "{n} Yıl",
|
||||
"plural": "{n} Yıl"
|
||||
},
|
||||
"month": {
|
||||
"singular": "{n} Ay",
|
||||
"plural": "{n} Ay"
|
||||
},
|
||||
"day": {
|
||||
"singular": "{n} Gün",
|
||||
"plural": "{n} Gün"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,75 +3,82 @@
|
||||
"name": "Magyar (HU)"
|
||||
},
|
||||
"strings": {
|
||||
"ifItWasNotYou": "",
|
||||
"helloUser": "",
|
||||
"reason": ""
|
||||
"ifItWasNotYou": "Ha nem Te voltál, akkor hagyd figyelmen kívül.",
|
||||
"helloUser": "Szia {username},",
|
||||
"reason": "Ok"
|
||||
},
|
||||
"userCreated": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"aUserWasCreated": "",
|
||||
"time": "",
|
||||
"notificationNotice": ""
|
||||
"name": "Felhasználó létrehozása",
|
||||
"title": "Értesítés: Felhasználó létrehozva",
|
||||
"aUserWasCreated": "Felhasználó létrehozva {code} kóddal.",
|
||||
"time": "Idő",
|
||||
"notificationNotice": "Megjegyzés: A figyelmeztető üzenetek ki- be kapcsolhatók az admin felületen."
|
||||
},
|
||||
"inviteExpiry": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"inviteExpired": "",
|
||||
"expiredAt": "",
|
||||
"notificationNotice": ""
|
||||
"name": "Meghívó lejárata",
|
||||
"title": "Értesítés: A meghívó lejárt",
|
||||
"inviteExpired": "A meghívó lejárt.",
|
||||
"expiredAt": "A {code} kód lejárt ekkor {time}.",
|
||||
"notificationNotice": "Megjegyzés: A figyelmeztető üzenetek ki- be kapcsolhatók az admin felületen."
|
||||
},
|
||||
"passwordReset": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"someoneHasRequestedReset": "",
|
||||
"ifItWasYou": "",
|
||||
"ifItWasYouLink": "",
|
||||
"codeExpiry": "",
|
||||
"pin": ""
|
||||
"name": "Jelszó visszaállítás",
|
||||
"title": "Jelszó visszaállítási kérelem - Jellyfin",
|
||||
"someoneHasRequestedReset": "Valaki mostanában jelszó visszaállítást kért.",
|
||||
"ifItWasYou": "Ha Te voltál, írd be a kódot ide.",
|
||||
"ifItWasYouLink": "Ha Te voltál, kattints a linkre.",
|
||||
"codeExpiry": "A kód lejárt {expiresInMinutes} perce. ({date} {time} UTC).",
|
||||
"pin": "PIN"
|
||||
},
|
||||
"userDeleted": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"yourAccountWasDeleted": ""
|
||||
"name": "Felhasználó törlése",
|
||||
"title": "A fiókod törölve lett - Jellyfin",
|
||||
"yourAccountWasDeleted": "A jellyfin fiókod törölve lett."
|
||||
},
|
||||
"userDisabled": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"yourAccountWasDisabled": ""
|
||||
"name": "Felhsználó letiltva",
|
||||
"title": "A felhasználód le lett tiltva - Jellyfin",
|
||||
"yourAccountWasDisabled": "A fiókod le lett tiltva."
|
||||
},
|
||||
"userEnabled": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"yourAccountWasEnabled": ""
|
||||
"name": "Felhasználó engedélyezve",
|
||||
"title": "A fiókod fel lett oldva - Jellyfin",
|
||||
"yourAccountWasEnabled": "A fiókod fel lett oldva."
|
||||
},
|
||||
"inviteEmail": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"hello": "",
|
||||
"youHaveBeenInvited": "",
|
||||
"toJoin": "",
|
||||
"inviteExpiry": "",
|
||||
"linkButton": ""
|
||||
"name": "Meghívó email",
|
||||
"title": "Meghívó - Jellyfin",
|
||||
"hello": "Szia",
|
||||
"youHaveBeenInvited": "Meghívtak a jellyfin alkalmazásba.",
|
||||
"toJoin": "Csatlakozáshoz kattints a linkre.",
|
||||
"inviteExpiry": "A meghívó {date} {time}-kor lejár, ami {expiresInMinutes} perc múlva lesz, szóval gyorsan cselekedj.",
|
||||
"linkButton": "Fiók beállítása"
|
||||
},
|
||||
"welcomeEmail": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"welcome": "",
|
||||
"youCanLoginWith": "",
|
||||
"yourAccountWillExpire": "",
|
||||
"jellyfinURL": ""
|
||||
"name": "Üdvözöllek",
|
||||
"title": "Üdvözöllek a Jellyfin-ben",
|
||||
"welcome": "Üdvözöllek a Jellyfin-ben!",
|
||||
"youCanLoginWith": "Be tudsz lépni az alábbi adatokkal",
|
||||
"yourAccountWillExpire": "A fiókod {date} dátummal lejár.",
|
||||
"jellyfinURL": "URL"
|
||||
},
|
||||
"emailConfirmation": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"clickBelow": "",
|
||||
"confirmEmail": ""
|
||||
"name": "Megerősítő email cím",
|
||||
"title": "Erősítsd meg az email címed- Jellyfin",
|
||||
"clickBelow": "Kattints az alábbi linkre, hogy megerősítsd az email címed és elkezd használni a jellyfin-t.",
|
||||
"confirmEmail": "Email megerősítése"
|
||||
},
|
||||
"userExpired": {
|
||||
"name": "",
|
||||
"title": "",
|
||||
"yourAccountHasExpired": "",
|
||||
"contactTheAdmin": ""
|
||||
"name": "Felhasználó lejárata",
|
||||
"title": "A fiókod lejárt - Jellyfin",
|
||||
"yourAccountHasExpired": "A fiókod lejárt.",
|
||||
"contactTheAdmin": "Lépj kapcsolatba az rendszergazdával további információkért."
|
||||
},
|
||||
"userExpiryAdjusted": {
|
||||
"name": "Lejárat módosítva",
|
||||
"title": "Fiók lejárat módosítva - Jellyfin",
|
||||
"yourExpiryWasAdjusted": "A fiókod lejárata módosult.",
|
||||
"ifPreviouslyDisabled": "Ha fiókod korábban letiltották, előfordulhat, hogy újra engedélyezték.",
|
||||
"newExpiry": "A fiókod {date} napon lejár."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"confirmationRequired": "E-mail megerősítés szükséges",
|
||||
"confirmationRequiredMessage": "Kérjük ellenőrizze az e-mail címére küldött üzenetet, a fiók ellenőrzéséhez.",
|
||||
"yourAccountIsValidUntil": "A fiókja eddig lesz érvényes: {date}.",
|
||||
"sendPIN": "Az alábbi PIN-t küldje el a botnak, majd itt csatolja össze a fiókját.",
|
||||
"sendPIN": "Az alábbi PIN-t küld el a botnak, majd itt csatold össze a fiókoddal.",
|
||||
"sendPINDiscord": "Írja be a {command} parancsot a {server_channel} Discord csatornába, adja meg a PIN-t.",
|
||||
"matrixEnterUser": "Írja be a felhasználója azonosítóját majd nyomja meg a beküldés gombot. A kapott kódot ide írja be.",
|
||||
"customMessagePlaceholderContent": "Kattints a felhasználói oldal szerkesztés gombjára a beállításokban a kártya testreszabásához, vagy jeleníts meg egyet a bejelentkezési képernyőn, ne aggódj, a felhasználó ezt nem láthatja.",
|
||||
@@ -34,7 +34,16 @@
|
||||
"resetPasswordThroughJellyfin": "A jelszavad visszaállításához látogass el a {jfLink} oldalra, és nyomj rá az \"Elfelejtett jelszó\" gombra.",
|
||||
"resetPasswordThroughLink": "A jelszavad visszaállításához, add meg a felhasználóneved, e-mail címed vagy a hozzákötött kapcsolattartási felhasználónevet, és nyomj a gombra. A linket levélben fogod kapni.",
|
||||
"resetSent": "Visszaállítás elküldve.",
|
||||
"changePassword": "Jelszó megváltoztatása"
|
||||
"changePassword": "Jelszó megváltoztatása",
|
||||
"referralsWithExpiryDescription": "Hívd meg barátaidat és családtagjaidat a Jellyfinre ezzel a linkkel. A link nem lesz elérhető, ha lejár.",
|
||||
"referralsDescription": "Hívd meg barátaidat és családtagjaidat a Jellyfinre ezzel a linkkel. Gyere vissza ide egy újért, ha lejár.",
|
||||
"copyReferral": "Link másolása",
|
||||
"invitedBy": "Meghívást kaptál {user} által.",
|
||||
"resetPasswordThroughLinkStart": "Jelszava visszaállításához adja meg az alábbiak egyikét:",
|
||||
"resetPasswordThroughLinkEnd": "Ezután kattints az elküldésre. Egy linket fogsz kapni a jelszó visszaállításához.",
|
||||
"resetPasswordUsername": "Jellyfin felhasználónév",
|
||||
"resetPasswordEmail": "Email cím",
|
||||
"resetPasswordContactMethod": "A fiókodhoz kapcsolt kapcsolatfelvételi mód felhasználóneve"
|
||||
},
|
||||
"notifications": {
|
||||
"errorUserExists": "A felhasználó már létezik.",
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
"name": "Italiano (IT)"
|
||||
},
|
||||
"strings": {
|
||||
"pageTitle": "Crea Un Account Jellyfin",
|
||||
"pageTitle": "Crea Account Jellyfin",
|
||||
"createAccountHeader": "Crea Un Account",
|
||||
"accountDetails": "Dettagli",
|
||||
"emailAddress": "Email",
|
||||
"username": "Username",
|
||||
"username": "Nome Utente",
|
||||
"password": "Password",
|
||||
"reEnterPassword": "Riscrivi La Password",
|
||||
"reEnterPasswordInvalid": "Le password non sono uguali.",
|
||||
@@ -17,7 +17,7 @@
|
||||
"confirmationRequired": "Richiesta la conferma Email",
|
||||
"confirmationRequiredMessage": "Controlla la tua casella email per verificare il tuo indirizzo.",
|
||||
"yourAccountIsValidUntil": "Il tuo account sarà valido fino al {date}.",
|
||||
"sendPIN": "Scrivi il PIN qui sotto al bot, poi torna qui per connettere il tuo account.",
|
||||
"sendPIN": "Invia il PIN riportato sotto al bot, poi torna qui per associare il tuo account.",
|
||||
"sendPINDiscord": "Scrivi {command} in {server_channel} su Discord, poi invia il PIN qui sotto.",
|
||||
"matrixEnterUser": "Inserisci il tuo ID utente, premi invia e ti verrò inviato un PIN. Inseriscilo qui per continuare.",
|
||||
"customMessagePlaceholderHeader": "Personalizza questa scheda",
|
||||
@@ -34,7 +34,15 @@
|
||||
"resetPassword": "Ripristina Password",
|
||||
"resetSent": "Richiesta di ripristino inviata.",
|
||||
"resetSentDescription": "Se l'username/metodo di contatto corrisponde ad un account esistente, verrà inviato un link di reset a tutti i metodi di contatto disponibili. Il codice scadrà tra 30 minuti.",
|
||||
"changePassword": "Cambia Password"
|
||||
"changePassword": "Cambia Password",
|
||||
"resetPasswordThroughLinkStart": "Per reimpostare la password, inserisci uno dei seguenti:",
|
||||
"resetPasswordThroughLinkEnd": "Successivamente premi Invia. Un link verra' inviato per resettare la tua password.",
|
||||
"resetPasswordUsername": "Il tuo nome utente Jellyfin",
|
||||
"resetPasswordEmail": "Il tuo indirizzo email",
|
||||
"referralsWithExpiryDescription": "Invita amici e famigliari su Jellyfin con questo link. Il link verra' disabilitato una volta scaduto.",
|
||||
"referralsDescription": "Invita amici e famigliari su Jellyfin usando questo link. Ritorna su questa pagina per ottenerne uno nuovo.",
|
||||
"copyReferral": "Copia Link",
|
||||
"invitedBy": "Sei stato invitato dall'utente {user}."
|
||||
},
|
||||
"notifications": {
|
||||
"errorUserExists": "L'utente è già esistente.",
|
||||
@@ -76,4 +84,4 @@
|
||||
"plural": "Deve avere almeno {n} caratteri speciali"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
88
lang/form/tr-TR.json
Normal file
88
lang/form/tr-TR.json
Normal file
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "İngilizce (ABD)"
|
||||
},
|
||||
"strings": {
|
||||
"pageTitle": "Jellyfin Hesabı Oluştur",
|
||||
"createAccountHeader": "Hesap Oluştur",
|
||||
"accountDetails": "Ayrıntılar",
|
||||
"emailAddress": "E-posta",
|
||||
"username": "Kullanıcı Adı",
|
||||
"oldPassword": "Eski Şifre",
|
||||
"newPassword": "Yeni Şifre",
|
||||
"password": "Şifre",
|
||||
"reEnterPassword": "Şifreyi Tekrar Girin",
|
||||
"reEnterPasswordInvalid": "Şifreler aynı değil.",
|
||||
"createAccountButton": "Hesap Oluştur",
|
||||
"passwordRequirementsHeader": "Şifre Gereksinimleri",
|
||||
"successHeader": "Başarılı!",
|
||||
"confirmationRequired": "E-posta onayı gerekli",
|
||||
"confirmationRequiredMessage": "Lütfen adresinizi doğrulamak için e-posta gelen kutunuzu kontrol edin.",
|
||||
"yourAccountIsValidUntil": "Hesabınız {date} tarihine kadar geçerli olacaktır.",
|
||||
"sendPIN": "Aşağıdaki **PIN'i** bota gönderin, ardından hesabınızı bağlamak için buraya geri gelin.",
|
||||
"sendPINDiscord": "Discord'da {server_channel} {command} yazın, ardından aşağıdaki PIN'i gönderin.",
|
||||
"matrixEnterUser": "Kullanıcı Kimliğinizi girin, gönderin ve size bir PIN gönderilecektir. Devam etmek için buraya girin.",
|
||||
"welcomeUser": "Hoşgeldin, {user}!",
|
||||
"addContactMethod": "İletişim Yöntemi Ekle",
|
||||
"editContactMethod": "İletişim Yöntemini Düzenle",
|
||||
"joinTheServer": "Sunucuya katıl:",
|
||||
"customMessagePlaceholderHeader": "Bu kartı özelleştir",
|
||||
"customMessagePlaceholderContent": "Bu kartı özelleştirmek için ayarlarda kullanıcı sayfası düzenleme düğmesine tıklayın ya da oturum açma ekranında bir tane gösterin ve endişelenmeyin, kullanıcı bunu göremez.",
|
||||
"userPageSuccessMessage": "Hesabınızla ilgili ayrıntıları daha sonra {myAccount} sayfasında görebilir ve değiştirebilirsiniz.",
|
||||
"resetPassword": "Şifreyi Sıfırla",
|
||||
"resetPasswordThroughJellyfin": "Şifrenizi sıfırlamak için {jfLink} adresini ziyaret edin ve \"Şifremi Unuttum\" düğmesine basın.",
|
||||
"resetPasswordThroughLink": "Şifrenizi sıfırlamak için kullanıcı adınızı, e-posta adresinizi veya bağlı bir iletişim yöntemi kullanıcı adınızı girin ve gönderin. Şifrenizi sıfırlamanız için bir bağlantı gönderilecektir.",
|
||||
"resetPasswordThroughLinkStart": "Şifrenizi sıfırlamak için aşağıdakilerden birini girin:",
|
||||
"resetPasswordThroughLinkEnd": "Şifrenizi sıfırlamanız için bir bağlantı gönderilecektir. Ardından gönder'e basın.",
|
||||
"resetPasswordUsername": "Jellyfin kullanıcı adınız",
|
||||
"resetPasswordEmail": "E-posta adresiniz",
|
||||
"resetPasswordContactMethod": "Hesabınıza bağlı herhangi bir iletişim yönteminin kullanıcı adı",
|
||||
"resetSent": "Sıfırlama Gönderildi.",
|
||||
"resetSentDescription": "Verilen kullanıcı adı/iletişim yöntemine sahip bir hesap varsa, mevcut tüm iletişim yöntemleri aracılığıyla bir şifre sıfırlama bağlantısı gönderilmiştir. Kodun süresi **30 dakika** içinde dolacaktır.",
|
||||
"changePassword": "Şifreyi Değiştir",
|
||||
"referralsDescription": "Bu bağlantı ile arkadaşlarınızı ve ailenizi Jellyfin'e davet edin. Süresi dolarsa yeni bir tane almak için buraya geri gelin.",
|
||||
"referralsWithExpiryDescription": "Bu bağlantı ile arkadaşlarınızı ve ailenizi Jellyfin'e davet edin. Bağlantının süresi dolduğunda devre dışı bırakılacaktır.",
|
||||
"copyReferral": "Linki Kopyala",
|
||||
"invitedBy": "Sizi {user} adlı kullanıcı davet etti."
|
||||
},
|
||||
"notifications": {
|
||||
"errorUserExists": "Kullanıcı zaten mevcut.",
|
||||
"errorInvalidCode": "Geçersiz davet kodu.",
|
||||
"errorAccountLinked": "Hesap zaten kullanımda.",
|
||||
"errorEmailLinked": "E-posta zaten kullanımda.",
|
||||
"errorTelegramVerification": "Telegram doğrulama gerekli.",
|
||||
"errorDiscordVerification": "Discord doğrulama gerekli.",
|
||||
"errorMatrixVerification": "Matrix doğrulama gerekli.",
|
||||
"errorInvalidPIN": "PIN geçersiz.",
|
||||
"errorUnknown": "Bilinmeyen hata.",
|
||||
"errorNoEmail": "E-posta gerekli.",
|
||||
"errorCaptcha": "Captcha yanlış.",
|
||||
"errorPassword": "Şifre gereksinimlerini kontrol edin.",
|
||||
"errorNoMatch": "Şifreler eşleşmiyor.",
|
||||
"errorOldPassword": "Eski şifre yanlış.",
|
||||
"passwordChanged": "Şifre Değiştirildi.",
|
||||
"verified": "Hesap doğrulandı."
|
||||
},
|
||||
"validationStrings": {
|
||||
"length": {
|
||||
"singular": "En az {n} karakter içermeli",
|
||||
"plural": "En az {n} karakter içermeli"
|
||||
},
|
||||
"uppercase": {
|
||||
"singular": "En az {n} büyük harf içermeli",
|
||||
"plural": "En az {n} büyük harf içermeli"
|
||||
},
|
||||
"lowercase": {
|
||||
"singular": "En az {n} küçük harf içermeli",
|
||||
"plural": "En az {n} küçük harf içermeli"
|
||||
},
|
||||
"number": {
|
||||
"singular": "En az {n} küçük harf içermeli",
|
||||
"plural": "En az {n} küçük harf içermeli"
|
||||
},
|
||||
"special": {
|
||||
"singular": "En az {n} özel karakter içermeli",
|
||||
"plural": "En az {n} özel karakter içermeli"
|
||||
}
|
||||
}
|
||||
}
|
||||
16
lang/pwreset/tr-TR.json
Normal file
16
lang/pwreset/tr-TR.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "İngilizce (ABD)"
|
||||
},
|
||||
"strings": {
|
||||
"passwordReset": "Şifre sıfırlama",
|
||||
"reset": "Sıfırla",
|
||||
"resetFailed": "Şifre sıfırlama başarısız oldu",
|
||||
"tryAgain": "Lütfen tekrar deneyin.",
|
||||
"youCanLogin": "Artık aşağıdaki kodla şifreniz olarak oturum açabilirsiniz.",
|
||||
"youCanLoginOmbi": "Artık aşağıdaki kodu şifreniz olarak kullanarak Jellyfin & Ombi'ye oturum açabilirsiniz.",
|
||||
"youCanLoginPassword": "Artık yeni şifrenizle oturum açabilirsiniz. Jellyfin'e devam etmek için aşağıya basın.",
|
||||
"changeYourPassword": "Oturum açtıktan sonra şifrenizi değiştirdiğinizden emin olun.",
|
||||
"enterYourPassword": "Yeni şifrenizi aşağıya girin."
|
||||
}
|
||||
}
|
||||
@@ -141,7 +141,7 @@
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Admin Notifications",
|
||||
"description": "If enabled, you can choose (per invite) to receive an message when an invite expires, or a user is created. If you didn't choose the Jellyfin login method, make sure you provided your email address, or add another contact method later."
|
||||
"description": "If enabled, you can choose (per invite) to receive a message when an invite expires, or a user is created. If you didn't choose the Jellyfin login method, make sure you provided your email address, or add another contact method later."
|
||||
},
|
||||
"inviteEmails": {
|
||||
"title": "Invite Messages",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"apiKey": "API Key",
|
||||
"error": "Error",
|
||||
"errorInvalidUserPass": "Invalid username/password.",
|
||||
"errorNotAdmin": "User is not aEnabledllowed to manage server.",
|
||||
"errorNotAdmin": "User is not allowed to manage server.",
|
||||
"errorUserDisabled": "User may be disabled.",
|
||||
"error404": "404, check the internal URL.",
|
||||
"errorConnectionRefused": "Connection refused.",
|
||||
@@ -126,7 +126,7 @@
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Admin Notifications",
|
||||
"description": "If enabled, you can choose (per invite) to receive an message when an invite expires, or a user is created. If you didn't choose the Jellyfin login method, make sure you provided your email address, or add another contact method later."
|
||||
"description": "If enabled, you can choose (per invite) to receive a message when an invite expires, or a user is created. If you didn't choose the Jellyfin login method, make sure you provided your email address, or add another contact method later."
|
||||
},
|
||||
"userPage": {
|
||||
"title": "User Page",
|
||||
@@ -136,7 +136,7 @@
|
||||
},
|
||||
"welcomeEmails": {
|
||||
"title": "Welcome messages",
|
||||
"description": "If enabled, an message will be sent to new users with the Jellyfin/Emby URL and their username."
|
||||
"description": "If enabled, a message will be sent to new users with the Jellyfin/Emby URL and their username."
|
||||
},
|
||||
"inviteEmails": {
|
||||
"title": "Invite Messages",
|
||||
|
||||
@@ -20,132 +20,162 @@
|
||||
"errorNotAdmin": "A felhasználó számára nincs engedélyezve a szerver kezelése.",
|
||||
"errorUserDisabled": "Lehetséges, hogy a felhasználó le lett tiltva.",
|
||||
"error404": "404, ellenőrizze a belső URL-t.",
|
||||
"errorConnectionRefused": "",
|
||||
"error": "Hiba"
|
||||
"errorConnectionRefused": "Csatlakozás visszautasítva.",
|
||||
"error": "Hiba",
|
||||
"errorUnknown": "Váratlan hiba, ellenőrizd a napló fájlt.",
|
||||
"errorProxy": "Proxy beállítás érvénytelen."
|
||||
},
|
||||
"startPage": {
|
||||
"welcome": "Üdv!",
|
||||
"pressStart": "",
|
||||
"httpsNotice": "",
|
||||
"start": ""
|
||||
"pressStart": "A jfa-go beállításához néhány dolgot el kell végezned. A folytatáshoz nyomd meg a kezdés gombot.",
|
||||
"httpsNotice": "Győződjön meg róla, hogy HTTPS-en vagy privát hálózaton keresztül éri el ezt az oldalt.",
|
||||
"start": "Kezdés"
|
||||
},
|
||||
"endPage": {
|
||||
"finished": "",
|
||||
"finished": "Kész!",
|
||||
"restartMessage": "",
|
||||
"refreshPage": ""
|
||||
"refreshPage": "Újratöltés",
|
||||
"moreFeatures": "Rengeteg további funkció, mint például a Discord/Telegram/Matrix botok és az egyéni Markdown üzenetek, megtalálható a Beállításokban, ezért mindenképpen böngészd át őket.",
|
||||
"restartReload": "Kattints ide az újraindításhoz, majd a megadott belső/külső URL-címek egyikén nyisd meg a jfa-go alkalmazást.",
|
||||
"ifFailedLoad": "Ha nem töltődik be, ellenőrizd az alkalmazás naplóit, hogy miért."
|
||||
},
|
||||
"language": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"defaultAdminLang": "",
|
||||
"defaultFormLang": "",
|
||||
"defaultEmailLang": ""
|
||||
"title": "Nyelv",
|
||||
"description": "A jfa-go legtöbb részéhez elérhetők közösségi fordítások. Az alábbiakban kiválaszthatod az alapértelmezett nyelveket, de a felhasználók továbbra is módosíthatják azokat, ha akarják. Ha szeretnél segíteni a fordításban, regisztrálj a {n}-re, hogy elkezdhesd a közreműködést!",
|
||||
"defaultAdminLang": "Alapártelmezett rendszergazda nyelv",
|
||||
"defaultFormLang": "Alapértelmezett fiók nyelv",
|
||||
"defaultEmailLang": "Alapértelmezett email nyelv"
|
||||
},
|
||||
"general": {
|
||||
"title": "",
|
||||
"listenAddress": "",
|
||||
"urlBase": "",
|
||||
"urlBaseNotice": "",
|
||||
"lightTheme": "",
|
||||
"darkTheme": "",
|
||||
"useHTTPS": "",
|
||||
"httpsPort": "",
|
||||
"useHTTPSNotice": "",
|
||||
"pathToCertificate": "",
|
||||
"pathToKeyFile": ""
|
||||
"title": "Alap",
|
||||
"listenAddress": "Figyelő címe",
|
||||
"urlBase": "Alap URL",
|
||||
"urlBaseNotice": "Csak akkor szükséges, ha fordított proxyt használsz egy almappán (pl. 'jellyf.in/accounts').",
|
||||
"lightTheme": "Fényes",
|
||||
"darkTheme": "Sötét",
|
||||
"useHTTPS": "HTTPS használata",
|
||||
"httpsPort": "HTTPS Port",
|
||||
"useHTTPSNotice": "Csak akkor aljánlott ha fordított proxy-t használsz.",
|
||||
"pathToCertificate": "Tanúsítvány elérési útja",
|
||||
"pathToKeyFile": "Kulcs fájl elérési útja",
|
||||
"externalURLNotice": "Az URL, amelyről a jfa-go címhez fogsz hozzáférni. Linkek generálására szolgál, például jelszó-visszaállításhoz. Ha beállítottál egyet, feltétlenül add meg a fenti alap URL-t is.",
|
||||
"externalURL": "Külső jfa-go URL"
|
||||
},
|
||||
"updates": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"updateChannel": "",
|
||||
"stable": "",
|
||||
"unstable": ""
|
||||
"title": "Frissítések",
|
||||
"description": "Engedélyezd ha szeretnél értesítést az új frissítésekről. A jfa-go 30 percenként ellenőrzi a(z) {n} címet. Nem gyűjt IP-címeket vagy személyes adatokat.",
|
||||
"updateChannel": "Csatorna frissítése",
|
||||
"stable": "Stabil",
|
||||
"unstable": "Instabil"
|
||||
},
|
||||
"login": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"authorizeWithJellyfin": "",
|
||||
"authorizeManual": "",
|
||||
"adminOnly": "",
|
||||
"allowAll": "",
|
||||
"allowAllDescription": "",
|
||||
"emailNotice": ""
|
||||
"title": "Belépés",
|
||||
"description": "Az admin oldal eléréséhez az alábbi módszerrel kell bejelentkezned:",
|
||||
"authorizeWithJellyfin": "Bejelentkezés Jellyfin/Emby segítségével: A bejelentkezési adatok meg vannak osztva a Jellyfin-nel, ami több felhasználó létrehozását teszi lehetővé.",
|
||||
"authorizeManual": "Felhasználónév és Jelszó: Felhasználónév és jelszó manuális beállítása.",
|
||||
"adminOnly": "Csak rendszergazda felhasználók (ajánlott)",
|
||||
"allowAll": "Összes Jellyfin felhasználó belépéssének engedélyezése",
|
||||
"allowAllDescription": "Nem ajánlott, a beállítás után engedélyezni kell az egyes felhasználók bejelentkezését.",
|
||||
"emailNotice": "Az email címed értesítések fogadására lesz használva.",
|
||||
"authorizeManualUserPageNotice": "Ennek használata letiltja a „Felhasználói oldal” funkciót."
|
||||
},
|
||||
"jellyfinEmby": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"embyNotice": "",
|
||||
"internal": "",
|
||||
"external": "",
|
||||
"replaceJellyfin": "",
|
||||
"replaceJellyfinNotice": "",
|
||||
"addressExternalNotice": "",
|
||||
"testConnection": ""
|
||||
"title": "Jellyfin/Emby",
|
||||
"description": "Egy adminisztrátori fiók szükséges, mivel az API nem engedélyezi a felhasználók létrehozását API-kulcs használatával. Létre kell hoznia egy külön fiókot, és engedélyeznie kell az „Ez a felhasználó kezelheti a szervert” beállítást. Minden mást letilthat. Ha ezt megtette, adja meg itt a hitelesítő adatait.",
|
||||
"embyNotice": "Az Emby támogatása korlátozott, és nem támogatja a jelszó-visszaállítást.",
|
||||
"internal": "Belső",
|
||||
"external": "Külső",
|
||||
"replaceJellyfin": "Szerver neve",
|
||||
"replaceJellyfinNotice": "Ha meg van adva, ez felülírja a 'Jellyfin' minden előfordulását az alkalmazásban.",
|
||||
"addressExternalNotice": "Hagyja üresen, ha ugyanazt a címet szeretnéd használni.",
|
||||
"testConnection": "Kapcsolat tesztelése"
|
||||
},
|
||||
"ombi": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"apiKeyNotice": ""
|
||||
"title": "Ombi",
|
||||
"description": "Az Ombihoz való csatlakozással Jellyfin és Ombi fiók is létrejön, amikor a felhasználó a jfa-go-n keresztül csatlakozik. A beállítás befejezése után lépjen a Beállítások menüpontra, hogy alapértelmezett profilt állítson be az új ombi-felhasználók számára.",
|
||||
"apiKeyNotice": "Ezt az Ombi beállítások első lapján találod.",
|
||||
"stabilityWarning": "Figyelmeztetés: Az Ombi integráció instabil, és problémákat okozhat. Helyette a Jellyseerr használata ajánlott. További információkért lásd: {n}."
|
||||
},
|
||||
"messages": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "Üzenetek",
|
||||
"description": "A jfa-go jelszó-visszaállítási információkat és különféle üzeneteket tud küldeni e-mailben, Discordon, Telegramon és/vagy Matrixon keresztül. Az e-mailt alább állíthatod be, a többit pedig később a Beállításokban konfigurálhatod. Az utasításokat a {n} oldalon találod. Ha erre nincs szükséged, itt letilthatod ezeket a funkciókat."
|
||||
},
|
||||
"email": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"method": "",
|
||||
"useEmailAsUsername": "",
|
||||
"useEmailAsUsernameNotice": "",
|
||||
"fromAddress": "",
|
||||
"senderName": "",
|
||||
"dateFormat": "",
|
||||
"dateFormatNotice": "",
|
||||
"encryption": "",
|
||||
"mailgunApiURL": ""
|
||||
"title": "Email",
|
||||
"description": "A jfa-go jelszó-visszaállító PIN-kódokat és különféle értesítéseket tud küldeni e-mailben. Csatlakozhatsz egy SMTP-kiszolgálóhoz, vagy használhatod az {n} API-t.",
|
||||
"method": "Küldési mód",
|
||||
"useEmailAsUsername": "Email cím használata fehasználónévnek",
|
||||
"useEmailAsUsernameNotice": "Ha engedélyezve van, az új felhasználók a Jellyfin/Emby rendszerbe felhasználónév helyett az e-mail címükkel jelentkeznek be.",
|
||||
"fromAddress": "Feladó címe",
|
||||
"senderName": "Küldő címe",
|
||||
"dateFormat": "Dátum formátuma",
|
||||
"dateFormatNotice": "A dátum az strftime formátumot követi. További információkért látogasson el a {n} oldalra.",
|
||||
"encryption": "Titkosítás",
|
||||
"mailgunApiURL": "API URL"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "Rendszergazda értesítések",
|
||||
"description": "Ha engedélyezve van, meghívónként kiválaszthatod, hogy üzenetet kapj-e, amikor egy meghívó lejár, vagy amikor létrejön egy felhasználó. Ha nem a Jellyfin bejelentkezési módot választottad, győződj meg róla, hogy megadtad az e-mail címedet, vagy adj hozzá később egy másik kapcsolatfelvételi módot."
|
||||
},
|
||||
"welcomeEmails": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "Üdvözlő üzenetek",
|
||||
"description": "Ha engedélyezve van, az új felhasználók üzenetben kapják meg a Jellyfin/Emby URL-címet és a felhasználónevüket."
|
||||
},
|
||||
"inviteEmails": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "Meghívó üzenetek",
|
||||
"description": "Ha engedélyezve van, közvetlenül a felhasználó e-mail címére, Discord vagy Matrix felhasználóra küldhet meghívókat. Mivel fordított proxyt használhat, meg kell adnia azt az URL-címet, ahonnan a meghívók elérhetők. Írja be az URL-alapját, és fűzze hozzá a '/invite' részt."
|
||||
},
|
||||
"passwordResets": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"pathToJellyfin": "",
|
||||
"pathToJellyfinNotice": "",
|
||||
"resetLinks": "",
|
||||
"resetLinksNotice": "",
|
||||
"resetLinksLanguage": "",
|
||||
"setPassword": "",
|
||||
"setPasswordNotice": ""
|
||||
"title": "Jelszó visszaállítás",
|
||||
"description": "Amikor egy felhasználó megpróbálja visszaállítani a jelszavát, a Jellyfin létrehoz egy „passwordreset-*.json” nevű fájlt, amely egy PIN-kódot tartalmaz. A jfa-go beolvassa a fájlt, és elküldi a PIN-kódot a felhasználónak. Ha engedélyezte a „Felhasználói oldal” funkciót, a visszaállítás ott is elvégezhető felhasználónév, e-mail cím vagy kapcsolatfelvételi mód megadásával.",
|
||||
"pathToJellyfin": "Jellyfin konfigurációs könyvtár elérési útja",
|
||||
"pathToJellyfinNotice": "Ha nem tudod, hol van ez, próbáld meg visszaállítani a jelszavadat a Jellyfinben. Megjelenik egy felugró ablak a következővel: '<jellyfin elérési útja>/passwordreset-*.json'. Ez nem szükséges, ha csak az önkiszolgáló jelszó-visszaállítást szeretnéd használni a \"Felhasználói oldalon\".",
|
||||
"resetLinks": "Link küldése PIN kód helyett",
|
||||
"resetLinksNotice": "Ha az Ombi integráció engedélyezve van, használja ezt a Jellyfin jelszó-visszaállítások Ombival való szinkronizálásához.",
|
||||
"resetLinksLanguage": "Alapértelmezett jelszó-visszaállítási nyelv",
|
||||
"setPassword": "Jelszó beállítás linken keresztül",
|
||||
"setPasswordNotice": "Ha engedélyezve van, a felhasználónak nem kell PIN-kóddal módosítania a jelszavát. Ez a jelszó-ellenőrzést is kikényszeríti.",
|
||||
"moreInfo": "A jelszavak visszaállításának különböző módjairól további információt a {n} oldalon talál.",
|
||||
"resetLinksRequiredForUserPage": "Szükséges az önkiszolgáló jelszó-visszaállításhoz a felhasználói oldalon."
|
||||
},
|
||||
"passwordValidation": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"length": "",
|
||||
"uppercase": "",
|
||||
"lowercase": "",
|
||||
"numbers": "",
|
||||
"special": ""
|
||||
"title": "Jelszóérvényesítés",
|
||||
"description": "Ha engedélyezve van, a fiók létrehozási oldalán megjelennek a jelszóra vonatkozó követelmények, például a minimális hossz, a nagy- és kisbetűk stb.",
|
||||
"length": "Hossz",
|
||||
"uppercase": "Nagybetűs karakterek",
|
||||
"lowercase": "Kisbetűs karakterek",
|
||||
"numbers": "Számok",
|
||||
"special": "Speciális karakterek"
|
||||
},
|
||||
"helpMessages": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"contactMessage": "",
|
||||
"contactMessageNotice": "",
|
||||
"helpMessage": "",
|
||||
"helpMessageNotice": "",
|
||||
"successMessage": "",
|
||||
"successMessageNotice": "",
|
||||
"emailMessage": "",
|
||||
"emailMessageNotice": ""
|
||||
"title": "Súgóüzenetek",
|
||||
"description": "Ezek az üzenetek a fiók létrehozási oldalán és néhány e-mailben jelennek meg.",
|
||||
"contactMessage": "Kapcsolatfelvételi üzenet",
|
||||
"contactMessageNotice": "Az adminisztrációs oldal kivételével az összes oldal alján megjelenik.",
|
||||
"helpMessage": "Súgóüzenet",
|
||||
"helpMessageNotice": "A fiók létrehozási oldalán jelenik meg.",
|
||||
"successMessage": "Sikeres üzenet",
|
||||
"successMessageNotice": "Akkor jelenik meg, amikor a felhasználó létrehozza a fiókját.",
|
||||
"emailMessage": "Email üzenet",
|
||||
"emailMessageNotice": "Az e-mailek alján jelenik meg.",
|
||||
"markdownMessageNotice": "Egyes e-mailek, oldalak és üzenetek tartalma testreszabható a Markdown segítségével a beállításokban."
|
||||
},
|
||||
"jellyseerr": {
|
||||
"description": "A Jellyseerr az Ombi alternatívája, és jobban integrálódik a jfa-go-val. A beállítás befejezése után a Beállítások menüpontban hozz létre egy profilt, és adj hozzá egy sablont az új Jellyseerr fiókokhoz.",
|
||||
"title": "Jellyseerr",
|
||||
"importExisting": "Meglévő fiókok importálása",
|
||||
"importExistingDescription": "Ha engedélyezve van, a meglévő felhasználók elérhetőségi adatai és beállításai szinkronizálva lesznek a jfa-go rendszerből."
|
||||
},
|
||||
"userPage": {
|
||||
"description": "A felhasználói oldal („Fiókom” néven látható) lehetővé teszi a felhasználók számára, hogy hozzáférjenek a fiókjukkal kapcsolatos információkhoz, például a kapcsolatfelvételi módokhoz és a fiók lejáratához. Megváltoztathatják jelszavukat, jelszó-visszaállítást kezdeményezhetnek, és összekapcsolhatják/módosíthatják a kapcsolatfelvételi módokat anélkül, hogy megkérdeznék Önt. Ezenkívül személyre szabott Markdown-üzenetek jeleníthetők meg a felhasználóknak a bejelentkezés előtt és után.",
|
||||
"title": "Felhasználói oldal",
|
||||
"customizeMessages": "Kattintson a beállításokban a „Felhasználói oldal” melletti szerkesztés gombra a későbbi módosításhoz.",
|
||||
"requiredSettings": "A jfa-go-ba Jellyfinen keresztül történő bejelentkezést be kell állítani. Győződjön meg róla, hogy a „jelszó visszaállítása linken keresztül” lehetőség van kiválasztva később az önkiszolgáló jelszó-visszaállításhoz."
|
||||
},
|
||||
"proxy": {
|
||||
"title": "Proxy",
|
||||
"description": "A jfa-go minden kapcsolatot HTTP/SOCKS5 proxyn keresztül hozzon létre. A Jellyfinhez való csatlakozást ezen a proxyn keresztül fogja tesztelni.",
|
||||
"protocol": "Protokoll",
|
||||
"address": "Cím (Port-al együtt)"
|
||||
}
|
||||
}
|
||||
|
||||
180
lang/setup/tr-TR.json
Normal file
180
lang/setup/tr-TR.json
Normal file
@@ -0,0 +1,180 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "İngilizce (ABD)"
|
||||
},
|
||||
"strings": {
|
||||
"pageTitle": "Kurulum - jfa-go",
|
||||
"next": "İleri",
|
||||
"back": "Geri",
|
||||
"optional": "İsteğe Bağlı",
|
||||
"serverType": "Sunucu Türü",
|
||||
"disabled": "Devre Dışı",
|
||||
"enabled": "Etkin",
|
||||
"port": "Bağlantı Noktası",
|
||||
"message": "Mesaj",
|
||||
"serverAddress": "Sunucu Adresi",
|
||||
"emailSubject": "E-posta Konusu",
|
||||
"URL": "URL",
|
||||
"apiKey": "API Anahtarı",
|
||||
"error": "Hata",
|
||||
"errorInvalidUserPass": "Geçersiz kullanıcı adı/şifre.",
|
||||
"errorNotAdmin": "Kullanıcının sunucuyu yönetmesine izin verilmiyor.",
|
||||
"errorUserDisabled": "Kullanıcı devre dışı bırakılmış olabilir.",
|
||||
"error404": "404, dahili URL'yi kontrol edin.",
|
||||
"errorConnectionRefused": "Bağlantı reddedildi.",
|
||||
"errorUnknown": "Bilinmeyen hata, uygulama günlüklerini kontrol edin.",
|
||||
"errorProxy": ""
|
||||
},
|
||||
"startPage": {
|
||||
"welcome": "",
|
||||
"pressStart": "",
|
||||
"httpsNotice": "",
|
||||
"start": ""
|
||||
},
|
||||
"endPage": {
|
||||
"finished": "",
|
||||
"moreFeatures": "",
|
||||
"restartReload": "",
|
||||
"ifFailedLoad": "",
|
||||
"refreshPage": ""
|
||||
},
|
||||
"language": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"defaultAdminLang": "",
|
||||
"defaultFormLang": "",
|
||||
"defaultEmailLang": ""
|
||||
},
|
||||
"general": {
|
||||
"title": "",
|
||||
"listenAddress": "",
|
||||
"urlBase": "",
|
||||
"urlBaseNotice": "",
|
||||
"externalURL": "",
|
||||
"externalURLNotice": "",
|
||||
"lightTheme": "",
|
||||
"darkTheme": "",
|
||||
"useHTTPS": "",
|
||||
"httpsPort": "",
|
||||
"useHTTPSNotice": "",
|
||||
"pathToCertificate": "",
|
||||
"pathToKeyFile": ""
|
||||
},
|
||||
"updates": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"updateChannel": "",
|
||||
"stable": "",
|
||||
"unstable": ""
|
||||
},
|
||||
"proxy": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"protocol": "",
|
||||
"address": ""
|
||||
},
|
||||
"login": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"authorizeWithJellyfin": "",
|
||||
"authorizeManual": "",
|
||||
"adminOnly": "",
|
||||
"allowAll": "",
|
||||
"allowAllDescription": "",
|
||||
"authorizeManualUserPageNotice": "",
|
||||
"emailNotice": ""
|
||||
},
|
||||
"jellyfinEmby": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"embyNotice": "",
|
||||
"internal": "",
|
||||
"external": "",
|
||||
"replaceJellyfin": "",
|
||||
"replaceJellyfinNotice": "",
|
||||
"addressExternalNotice": "",
|
||||
"testConnection": ""
|
||||
},
|
||||
"ombi": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"apiKeyNotice": "",
|
||||
"stabilityWarning": ""
|
||||
},
|
||||
"jellyseerr": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"importExisting": "",
|
||||
"importExistingDescription": ""
|
||||
},
|
||||
"messages": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
},
|
||||
"email": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"method": "",
|
||||
"useEmailAsUsername": "",
|
||||
"useEmailAsUsernameNotice": "",
|
||||
"fromAddress": "",
|
||||
"senderName": "",
|
||||
"dateFormat": "",
|
||||
"dateFormatNotice": "",
|
||||
"encryption": "",
|
||||
"mailgunApiURL": ""
|
||||
},
|
||||
"notifications": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
},
|
||||
"userPage": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"customizeMessages": "",
|
||||
"requiredSettings": ""
|
||||
},
|
||||
"welcomeEmails": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
},
|
||||
"inviteEmails": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
},
|
||||
"passwordResets": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"moreInfo": "",
|
||||
"pathToJellyfin": "",
|
||||
"pathToJellyfinNotice": "",
|
||||
"resetLinks": "",
|
||||
"resetLinksRequiredForUserPage": "",
|
||||
"resetLinksNotice": "",
|
||||
"resetLinksLanguage": "",
|
||||
"setPassword": "",
|
||||
"setPasswordNotice": ""
|
||||
},
|
||||
"passwordValidation": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"length": "",
|
||||
"uppercase": "",
|
||||
"lowercase": "",
|
||||
"numbers": "",
|
||||
"special": ""
|
||||
},
|
||||
"helpMessages": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"markdownMessageNotice": "",
|
||||
"contactMessage": "",
|
||||
"contactMessageNotice": "",
|
||||
"helpMessage": "",
|
||||
"helpMessageNotice": "",
|
||||
"successMessage": "",
|
||||
"successMessageNotice": "",
|
||||
"emailMessage": "",
|
||||
"emailMessageNotice": ""
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
"languageSet": "El idioma esta configurado como {language}.",
|
||||
"discordDMs": "Por favor, compruebe sus DMs para una respuesta.",
|
||||
"sentInvite": "Enviar invitación.",
|
||||
"sentInviteFailure": "Error al enviar la invitación, compruebe los logs."
|
||||
"sentInviteFailure": "Error al enviar la invitación, compruebe los logs.",
|
||||
"noPermission": "No tienes permisos para esta acción."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
"languageMessage": "Megjegyzés: Az elérhető nyelveket a {command} parancsal láthatod, és a {command} <nyelv kód> parancsal szerkesztheted.",
|
||||
"languageMessageDiscord": "Megjegyzés: a saját nyelvet a /lang <nyelv neve> parancsal tudod beállítani.",
|
||||
"languageSet": "Nyelv {language}-ra/re állítva.",
|
||||
"discordDMs": "Ellenőrizd az üzeneteidet."
|
||||
"discordDMs": "Ellenőrizd az üzeneteidet.",
|
||||
"sentInvite": "Meghívó elküldve.",
|
||||
"sentInviteFailure": "Meghívó elküldése sikertelen, ellenőrizd a napló fájlt.",
|
||||
"noPermission": "Nincs jogosultságod erre a műveletre."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +186,11 @@ const (
|
||||
|
||||
IncorrectCaptcha = "captcha incorrect"
|
||||
|
||||
ExtendCreateExpiry = "Extended or created expiry for user \"%s\""
|
||||
ExtendCreateExpiry = "Extended or created expiry for user \"%s\""
|
||||
FoundExistingExpiry = "Found existing expiry key"
|
||||
FoundPreviousExpiryLog = "Found most recent previous expiry in activity log @ %v"
|
||||
ExpiryWouldBeInPast = "Expiry would've been in the past, using current time base"
|
||||
PreviousExpiryNotExpiry = "Last user disable was not an expiry, using current time base"
|
||||
|
||||
UserEmailAdjusted = "Email for user \"%s\" adjusted"
|
||||
UserAdminAdjusted = "Admin state for user \"%s\" set to %t"
|
||||
|
||||
9
main.go
9
main.go
@@ -369,7 +369,9 @@ func start(asDaemon, firstCall bool) {
|
||||
// NOTE: As of writing this, the order in app.thirdPartyServices doesn't matter,
|
||||
// but in future it might (like app.contactMethods does), so append to the end!
|
||||
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
||||
app.ombi = &OmbiWrapper{}
|
||||
app.ombi = &OmbiWrapper{
|
||||
OmbiUserByJfID: app.getOmbiUser,
|
||||
}
|
||||
app.debug.Printf(lm.UsingOmbi)
|
||||
ombiServer := app.config.Section("ombi").Key("server").String()
|
||||
app.ombi.Ombi = ombi.NewOmbi(
|
||||
@@ -709,7 +711,7 @@ func flagPassed(name string) (found bool) {
|
||||
}
|
||||
|
||||
// @title jfa-go internal API
|
||||
// @version 0.5.2
|
||||
// @version 0.6.0
|
||||
// @description API for the jfa-go frontend
|
||||
// @contact.name Harvey Tindall
|
||||
// @contact.email hrfee@hrfee.dev
|
||||
@@ -757,6 +759,9 @@ func flagPassed(name string) (found bool) {
|
||||
// @tag.name Other
|
||||
// @tag.description Things that dont fit elsewhere.
|
||||
|
||||
// @tag.name Statistics
|
||||
// @tag.description Routes that expose useful info/stats.
|
||||
|
||||
func printVersion() {
|
||||
tray := ""
|
||||
if TRAY {
|
||||
|
||||
10
matrix.go
10
matrix.go
@@ -64,6 +64,16 @@ var matrixFilter = mautrix.Filter{
|
||||
},
|
||||
}
|
||||
|
||||
func EmptyMatrixUser() *MatrixUser {
|
||||
return &MatrixUser{
|
||||
RoomID: "",
|
||||
UserID: "",
|
||||
Lang: "",
|
||||
Contact: false,
|
||||
JellyfinID: "",
|
||||
}
|
||||
}
|
||||
|
||||
func (d *MatrixDaemon) renderUserID(uid id.UserID) id.UserID {
|
||||
if uid[0] != '@' {
|
||||
uid = "@" + uid
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/hrfee/jfa-go/ombi"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
@@ -191,7 +192,10 @@ func linkExistingOmbiDiscordTelegram(app *appContext) error {
|
||||
app.debug.Printf("Failed to get Ombi user with Discord/Telegram \"%s\"/\"%s\": %v", ids[0], ids[1], err)
|
||||
continue
|
||||
}
|
||||
_, err = app.ombi.SetNotificationPrefs(ombiUser, ids[0], ids[1])
|
||||
_, err = app.ombi.SetNotificationPrefs(ombiUser, []ombi.NotificationPref{
|
||||
{ombi.NotifAgentDiscord, ombiUser["id"].(string), ids[0], true},
|
||||
{ombi.NotifAgentTelegram, ombiUser["id"].(string), ids[1], true},
|
||||
})
|
||||
if err != nil {
|
||||
app.debug.Printf("Failed to set prefs for Ombi user \"%s\": %v", ombiUser["userName"].(string), err)
|
||||
continue
|
||||
|
||||
19
models.go
19
models.go
@@ -252,14 +252,15 @@ type customEmailDTO struct {
|
||||
}
|
||||
|
||||
type extendExpiryDTO struct {
|
||||
Users []string `json:"users"` // List of user IDs to apply to.
|
||||
Months int `json:"months" example:"1"` // Number of months to add.
|
||||
Days int `json:"days" example:"1"` // Number of days to add.
|
||||
Hours int `json:"hours" example:"2"` // Number of hours to add.
|
||||
Minutes int `json:"minutes" example:"3"` // Number of minutes to add.
|
||||
Timestamp int64 `json:"timestamp"` // Optional, exact time to expire at. Overrides other fields.
|
||||
Notify bool `json:"notify"` // Whether to message the user(s) about the change.
|
||||
Reason string `json:"reason" example:"i felt like it"` // Reason for adjustment.
|
||||
Users []string `json:"users"` // List of user IDs to apply to.
|
||||
Months int `json:"months,omitempty" example:"1"` // Number of months to add.
|
||||
Days int `json:"days,omityempty" example:"1"` // Number of days to add.
|
||||
Hours int `json:"hours,omitempty" example:"2"` // Number of hours to add.
|
||||
Minutes int `json:"minutes,omitempty" example:"3"` // Number of minutes to add.
|
||||
Timestamp int64 `json:"timestamp,omitempty"` // Optional, exact time to expire at. Overrides other fields.
|
||||
Notify bool `json:"notify"` // Whether to message the user(s) about the change.
|
||||
Reason string `json:"reason,omitempty" example:"i felt like it"` // Optional, reason for adjustment.
|
||||
TryExtendFromPreviousExpiry bool `json:"try_extend_from_previous_expiry,omitempty"` // If an activity log of the expiry of a disabled user is available, extend the expiry from that instead of the current time.
|
||||
}
|
||||
|
||||
type checkUpdateDTO struct {
|
||||
@@ -277,7 +278,7 @@ type telegramSetDTO struct {
|
||||
ID string `json:"id"` // Jellyfin ID of user.
|
||||
}
|
||||
|
||||
type SetContactMethodsDTO struct {
|
||||
type SetContactPreferencesDTO struct {
|
||||
ID string `json:"id"`
|
||||
Email bool `json:"email"`
|
||||
Discord bool `json:"discord"`
|
||||
|
||||
10
ombi/ombi.go
10
ombi/ombi.go
@@ -246,16 +246,8 @@ type NotificationPref struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
func (ombi *Ombi) SetNotificationPrefs(user map[string]interface{}, discordID, telegramUser string) (result string, err error) {
|
||||
id := user["id"].(string)
|
||||
func (ombi *Ombi) SetNotificationPrefs(user map[string]interface{}, data []NotificationPref) (result string, err error) {
|
||||
url := fmt.Sprintf("%s/api/v1/Identity/NotificationPreferences", ombi.server)
|
||||
data := []NotificationPref{}
|
||||
if discordID != "" {
|
||||
data = append(data, NotificationPref{NotifAgentDiscord, id, discordID, true})
|
||||
}
|
||||
if telegramUser != "" {
|
||||
data = append(data, NotificationPref{NotifAgentTelegram, id, telegramUser, true})
|
||||
}
|
||||
var code int
|
||||
result, code, err = ombi.send("POST", url, data, true, map[string]string{"UserName": user["userName"].(string)})
|
||||
err = co.GenericErr(code, err)
|
||||
|
||||
@@ -201,6 +201,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
|
||||
api.GET(p+"/users", app.GetUsers)
|
||||
api.GET(p+"/users/count", app.GetUserCount)
|
||||
api.POST(p+"/users", app.SearchUsers)
|
||||
api.POST(p+"/users/count", app.GetFilteredUserCount)
|
||||
api.POST(p+"/user", app.NewUserFromAdmin)
|
||||
api.POST(p+"/users/extend", app.ExtendExpiry)
|
||||
api.DELETE(p+"/users/:id/expiry", app.RemoveExpiry)
|
||||
|
||||
@@ -2,11 +2,21 @@ module github.com/hrfee/jfa-go/scripts/ini
|
||||
|
||||
replace github.com/hrfee/jfa-go/common => ../../common
|
||||
|
||||
go 1.18
|
||||
replace github.com/hrfee/jfa-go/logmessages => ../../logmessages
|
||||
|
||||
go 1.22.4
|
||||
|
||||
require (
|
||||
github.com/hrfee/jfa-go/common v0.0.0-20240824141650-fcdd4e451882 // indirect
|
||||
github.com/hrfee/jfa-go/logmessages v0.0.0-20240806200606-6308db495a0a // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/hrfee/jfa-go/common v0.0.0-00010101000000-000000000000
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/hrfee/jfa-go/logmessages v0.0.0-20240806200606-6308db495a0a // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
github.com/hrfee/jfa-go/logmessages v0.0.0-20240806200606-6308db495a0a h1:qbXZgCqb9eaPSJfLEXczQD2lxTv6jb6silMPIWW9j6o=
|
||||
github.com/hrfee/jfa-go/logmessages v0.0.0-20240806200606-6308db495a0a/go.mod h1:c5HKkLayo0GrEUDlJwT12b67BL9cdPjP271Xlv/KDRQ=
|
||||
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/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
|
||||
@@ -26,6 +26,7 @@ func generateIni(yamlPath string, iniPath string) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
conf := ini.Empty()
|
||||
|
||||
for _, section := range configBase.Sections {
|
||||
|
||||
@@ -34,4 +34,4 @@ fi
|
||||
JFA_GO_VERSION=$(git describe --exact-match HEAD 2> /dev/null || echo 'vgit')
|
||||
TIMEOUT=60m
|
||||
|
||||
JFA_GO_CSS_VERSION="v3" JFA_GO_NFPM_EPOCH=$(git rev-list --all --count) JFA_GO_BUILD_TIME=$(date +%s) JFA_GO_BUILT_BY=${JFA_GO_BUILT_BY:-"???"} JFA_GO_VERSION="$(echo $JFA_GO_VERSION | sed 's/v//g')" $@ --timeout $TIMEOUT
|
||||
JFA_GO_CSS_VERSION="v0.6.0" JFA_GO_NFPM_EPOCH=$(git rev-list --all --count) JFA_GO_BUILD_TIME=$(date +%s) JFA_GO_BUILT_BY=${JFA_GO_BUILT_BY:-"???"} JFA_GO_VERSION="$(echo $JFA_GO_VERSION | sed 's/v//g')" $@ --timeout $TIMEOUT
|
||||
|
||||
18
scripts/yaml/go.mod
Normal file
18
scripts/yaml/go.mod
Normal file
@@ -0,0 +1,18 @@
|
||||
module github.com/hrfee/jfa-go/scripts/yaml
|
||||
|
||||
replace github.com/hrfee/jfa-go/common => ../../common
|
||||
|
||||
replace github.com/hrfee/jfa-go/logmessages => ../../logmessages
|
||||
|
||||
go 1.22.4
|
||||
|
||||
require (
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
github.com/hrfee/jfa-go/common v0.0.0-20251123201034-b1c578ccf49f // indirect
|
||||
github.com/hrfee/jfa-go/logmessages v0.0.0-20240806200606-6308db495a0a // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
16
scripts/yaml/go.sum
Normal file
16
scripts/yaml/go.sum
Normal file
@@ -0,0 +1,16 @@
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
121
scripts/yaml/main.go
Normal file
121
scripts/yaml/main.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/hrfee/jfa-go/common"
|
||||
)
|
||||
|
||||
func flattenOrder(c common.Config) (sections []string) {
|
||||
var traverseGroup func(groupName string) []string
|
||||
traverseGroup = func(groupName string) []string {
|
||||
out := []string{}
|
||||
for _, group := range c.Groups {
|
||||
if group.Group == groupName {
|
||||
for _, groupMember := range group.Members {
|
||||
if groupMember.Group != "" {
|
||||
out = append(out, traverseGroup(groupMember.Group)...)
|
||||
} else if groupMember.Section != "" {
|
||||
out = append(out, groupMember.Section)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
sections = make([]string, 0, len(c.Sections))
|
||||
for _, member := range c.Order {
|
||||
if member.Group != "" {
|
||||
sections = append(sections, traverseGroup(member.Group)...)
|
||||
} else if member.Section != "" {
|
||||
sections = append(sections, member.Section)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func validateOrderCompleteness(c common.Config, sectOrder []string) (missing []string) {
|
||||
listedSects := map[string]bool{}
|
||||
for _, sect := range sectOrder {
|
||||
listedSects[sect] = true
|
||||
}
|
||||
|
||||
for _, section := range c.Sections {
|
||||
if _, ok := listedSects[section.Section]; !ok {
|
||||
missing = append(missing, section.Section)
|
||||
}
|
||||
}
|
||||
return missing
|
||||
}
|
||||
|
||||
func main() {
|
||||
var inPath string
|
||||
var outPath string
|
||||
flag.StringVar(&inPath, "in", "", "Input of the config base in yaml.")
|
||||
flag.StringVar(&outPath, "out", "", "Output of the checked and processed")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if inPath == "" {
|
||||
panic(errors.New("invalid input path"))
|
||||
}
|
||||
if outPath == "" {
|
||||
panic(errors.New("invalid output path"))
|
||||
}
|
||||
|
||||
yamlFile, err := os.ReadFile(inPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
info, err := os.Stat(inPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
configBase := common.Config{}
|
||||
err = yaml.Unmarshal(yamlFile, &configBase)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
red := color.New(color.FgRed)
|
||||
|
||||
if len(configBase.Order) > 0 {
|
||||
sectOrder := flattenOrder(configBase)
|
||||
missing := validateOrderCompleteness(configBase, sectOrder)
|
||||
if len(missing) > 0 {
|
||||
red.Fprintln(os.Stderr, "ERROR: Root order specified but the following sections were not listed, directly or indirectly:")
|
||||
for _, section := range missing {
|
||||
red.Fprintln(os.Stderr, "\t"+section)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
sectionMap := map[string]common.Section{}
|
||||
for _, sect := range configBase.Sections {
|
||||
sectionMap[sect.Section] = sect
|
||||
}
|
||||
|
||||
for i, sect := range sectOrder {
|
||||
configBase.Sections[i] = sectionMap[sect]
|
||||
}
|
||||
|
||||
fmt.Println("Re-ordered sections to follow root order.")
|
||||
}
|
||||
|
||||
bytes, err := yaml.Marshal(&configBase)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(outPath, bytes, info.Mode())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -625,7 +625,11 @@ type ThirdPartyService interface {
|
||||
common.ConfigurableTransport
|
||||
// ok implies user imported, err can be any issue that occurs during
|
||||
ImportUser(jellyfinID string, req newUserDTO, profile Profile) (err error, ok bool)
|
||||
AddContactMethods(jellyfinID string, req newUserDTO, discord *DiscordUser, telegram *TelegramUser) (err error)
|
||||
// SetContactMethods allows setting any combination of contact method address/username/id and contact preference. To leave fields alone, pass nil pointers, to set them to a blank value, use "" or Empty(Discord|Telegram|Matrix)User(). Ignores the "Contact" field in some xyzUser structs.
|
||||
SetContactMethods(jellyfinID string, email *string, discord *DiscordUser, telegram *TelegramUser, contactPrefs *common.ContactPreferences) (err error)
|
||||
// Enabled returns whether this service is enabled in the given profile.
|
||||
// Not for checking if the service is enabled in general!
|
||||
// If it wasn't, it wouldn't be in app.thirdPartyServices.
|
||||
Enabled(app *appContext, profile *Profile) bool
|
||||
Name() string
|
||||
}
|
||||
|
||||
10
telegram.go
10
telegram.go
@@ -28,6 +28,16 @@ func (tv TelegramVerifiedToken) ToUser() *TelegramUser {
|
||||
}
|
||||
}
|
||||
|
||||
func EmptyTelegramUser() *TelegramUser {
|
||||
return &TelegramUser{
|
||||
JellyfinID: "",
|
||||
ChatID: 0,
|
||||
Username: "",
|
||||
Lang: "",
|
||||
Contact: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TelegramVerifiedToken) Name() string { return t.Username }
|
||||
func (t *TelegramVerifiedToken) SetMethodID(id any) { t.ChatID = id.(int64) }
|
||||
func (t *TelegramVerifiedToken) MethodID() any { return t.ChatID }
|
||||
|
||||
@@ -39,6 +39,7 @@ interface formWindow extends GlobalWindow {
|
||||
userPageEnabled: boolean;
|
||||
userPageAddress: string;
|
||||
customSuccessCard: boolean;
|
||||
collectEmail: boolean;
|
||||
}
|
||||
|
||||
loadLangSelector("form");
|
||||
@@ -171,7 +172,13 @@ const submitSpan = form.querySelector("span.submit") as HTMLSpanElement;
|
||||
const submitText = submitSpan.textContent;
|
||||
let usernameField = document.getElementById("create-username") as HTMLInputElement;
|
||||
const emailField = document.getElementById("create-email") as HTMLInputElement;
|
||||
if (!window.usernameEnabled) { usernameField.parentElement.remove(); usernameField = emailField; }
|
||||
window.emailRequired &&= window.collectEmail;
|
||||
if (!window.usernameEnabled) {
|
||||
usernameField.parentElement.remove(); usernameField = emailField;
|
||||
} else if (!window.collectEmail) {
|
||||
emailField.parentElement.classList.add("unfocused");
|
||||
emailField.value = "";
|
||||
}
|
||||
const passwordField = document.getElementById("create-password") as HTMLInputElement;
|
||||
const rePasswordField = document.getElementById("create-reenter-password") as HTMLInputElement;
|
||||
|
||||
|
||||
@@ -750,9 +750,10 @@ class user implements User, SearchableItem {
|
||||
|
||||
private _addTelegram = () => _get("/telegram/pin", null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4 && req.status == 200) {
|
||||
const pin = document.getElementById("telegram-pin");
|
||||
const link = document.getElementById("telegram-link") as HTMLAnchorElement;
|
||||
const username = document.getElementById("telegram-username") as HTMLSpanElement;
|
||||
const modal = window.modals.telegram.modal;
|
||||
const pin = modal.getElementsByClassName("pin")[0] as HTMLElement;
|
||||
const link = modal.getElementsByClassName("link")[0] as HTMLAnchorElement;
|
||||
const username = modal.getElementsByClassName("username")[0] as HTMLElement;
|
||||
const waiting = document.getElementById("telegram-waiting") as HTMLSpanElement;
|
||||
let resp = req.response as getPinResponse;
|
||||
pin.textContent = resp.token;
|
||||
@@ -836,6 +837,18 @@ interface UsersDTO extends paginatedDTO {
|
||||
users: User[];
|
||||
}
|
||||
|
||||
declare interface ExtendExpiryDTO {
|
||||
users: string[];
|
||||
months?: number;
|
||||
days?: number;
|
||||
hours?: number;
|
||||
minutes?: number;
|
||||
timestamp?: number;
|
||||
notify: boolean;
|
||||
reason?: string;
|
||||
try_extend_from_previous_expiry?: boolean;
|
||||
}
|
||||
|
||||
export class accountsList extends PaginatedList {
|
||||
protected _container = document.getElementById("accounts-list") as HTMLTableSectionElement;
|
||||
|
||||
@@ -856,6 +869,7 @@ export class accountsList extends PaginatedList {
|
||||
private _extendExpiryForm = document.getElementById("form-extend-expiry") as HTMLFormElement;
|
||||
private _extendExpiryTextInput = document.getElementById("extend-expiry-text") as HTMLInputElement;
|
||||
private _extendExpiryFieldInputs = document.getElementById("extend-expiry-field-inputs") as HTMLElement;
|
||||
private _extendExpiryFromPreviousExpiry = document.getElementById("expiry-use-previous") as HTMLInputElement;
|
||||
private _usingExtendExpiryTextInput = true;
|
||||
|
||||
private _extendExpiryDate = document.getElementById("extend-expiry-date") as HTMLElement;
|
||||
@@ -1117,14 +1131,14 @@ export class accountsList extends PaginatedList {
|
||||
this._extendExpiryDate.classList.add("unfocused");
|
||||
|
||||
this._extendExpiryTextInput.onkeyup = () => {
|
||||
this._extendExpiryTextInput.parentElement.parentElement.classList.remove("opacity-60");
|
||||
this._extendExpiryTextInput.parentElement.classList.remove("opacity-60");
|
||||
this._extendExpiryFieldInputs.classList.add("opacity-60");
|
||||
this._usingExtendExpiryTextInput = true;
|
||||
this._displayExpiryDate();
|
||||
}
|
||||
|
||||
this._extendExpiryTextInput.onclick = () => {
|
||||
this._extendExpiryTextInput.parentElement.parentElement.classList.remove("opacity-60");
|
||||
this._extendExpiryTextInput.parentElement.classList.remove("opacity-60");
|
||||
this._extendExpiryFieldInputs.classList.add("opacity-60");
|
||||
this._usingExtendExpiryTextInput = true;
|
||||
this._displayExpiryDate();
|
||||
@@ -1132,15 +1146,17 @@ export class accountsList extends PaginatedList {
|
||||
|
||||
this._extendExpiryFieldInputs.onclick = () => {
|
||||
this._extendExpiryFieldInputs.classList.remove("opacity-60");
|
||||
this._extendExpiryTextInput.parentElement.parentElement.classList.add("opacity-60");
|
||||
this._extendExpiryTextInput.parentElement.classList.add("opacity-60");
|
||||
this._usingExtendExpiryTextInput = false;
|
||||
this._displayExpiryDate();
|
||||
};
|
||||
|
||||
this._extendExpiryFromPreviousExpiry.onclick = this._displayExpiryDate;
|
||||
|
||||
for (let field of ["months", "days", "hours", "minutes"]) {
|
||||
(document.getElementById("extend-expiry-"+field) as HTMLSelectElement).onchange = () => {
|
||||
this._extendExpiryFieldInputs.classList.remove("opacity-60");
|
||||
this._extendExpiryTextInput.parentElement.parentElement.classList.add("opacity-60");
|
||||
this._extendExpiryTextInput.parentElement.classList.add("opacity-60");
|
||||
this._usingExtendExpiryTextInput = false;
|
||||
this._displayExpiryDate();
|
||||
};
|
||||
@@ -2008,45 +2024,54 @@ export class accountsList extends PaginatedList {
|
||||
_displayExpiryDate = () => {
|
||||
let date: Date;
|
||||
let invalid = false;
|
||||
let cantShow = false;
|
||||
let users = this._collectUsers();
|
||||
if (this._usingExtendExpiryTextInput) {
|
||||
date = (Date as any).fromString(this._extendExpiryTextInput.value) as Date;
|
||||
invalid = "invalid" in (date as any);
|
||||
} else {
|
||||
let fields: Array<HTMLSelectElement> = [
|
||||
document.getElementById("extend-expiry-months") as HTMLSelectElement,
|
||||
document.getElementById("extend-expiry-days") as HTMLSelectElement,
|
||||
document.getElementById("extend-expiry-hours") as HTMLSelectElement,
|
||||
document.getElementById("extend-expiry-minutes") as HTMLSelectElement
|
||||
];
|
||||
invalid = fields[0].value == "0" && fields[1].value == "0" && fields[2].value == "0" && fields[3].value == "0";
|
||||
let id = users.length > 0 ? users[0] : "";
|
||||
if (!id) invalid = true;
|
||||
else {
|
||||
date = new Date(this.users[id].expiry*1000);
|
||||
if (this.users[id].expiry == 0) date = new Date();
|
||||
date.setMonth(date.getMonth() + (+fields[0].value))
|
||||
date.setDate(date.getDate() + (+fields[1].value));
|
||||
date.setHours(date.getHours() + (+fields[2].value));
|
||||
date.setMinutes(date.getMinutes() + (+fields[3].value));
|
||||
if (this._extendExpiryFromPreviousExpiry.checked) {
|
||||
cantShow = true;
|
||||
} else {
|
||||
let fields: Array<HTMLSelectElement> = [
|
||||
document.getElementById("extend-expiry-months") as HTMLSelectElement,
|
||||
document.getElementById("extend-expiry-days") as HTMLSelectElement,
|
||||
document.getElementById("extend-expiry-hours") as HTMLSelectElement,
|
||||
document.getElementById("extend-expiry-minutes") as HTMLSelectElement
|
||||
];
|
||||
invalid = fields[0].value == "0" && fields[1].value == "0" && fields[2].value == "0" && fields[3].value == "0";
|
||||
let id = users.length > 0 ? users[0] : "";
|
||||
if (!id) invalid = true;
|
||||
else {
|
||||
date = new Date(this.users[id].expiry*1000);
|
||||
if (this.users[id].expiry == 0) date = new Date();
|
||||
date.setMonth(date.getMonth() + (+fields[0].value))
|
||||
date.setDate(date.getDate() + (+fields[1].value));
|
||||
date.setHours(date.getHours() + (+fields[2].value));
|
||||
date.setMinutes(date.getMinutes() + (+fields[3].value));
|
||||
}
|
||||
}
|
||||
}
|
||||
const submit = this._extendExpiryForm.querySelector(`input[type="submit"]`) as HTMLInputElement;
|
||||
const submitSpan = submit.nextElementSibling;
|
||||
if (invalid || cantShow) {
|
||||
this._extendExpiryDate.classList.add("unfocused");
|
||||
}
|
||||
if (invalid) {
|
||||
submit.disabled = true;
|
||||
submitSpan.classList.add("opacity-60");
|
||||
this._extendExpiryDate.classList.add("unfocused");
|
||||
} else {
|
||||
submit.disabled = false;
|
||||
submitSpan.classList.remove("opacity-60");
|
||||
this._extendExpiryDate.innerHTML = `
|
||||
<div class="flex flex-col">
|
||||
<span>${window.lang.strings("accountWillExpire").replace("{date}", toDateString(date))}</span>
|
||||
${users.length > 1 ? "<span>"+window.lang.strings("expirationBasedOn")+"</span>" : ""}
|
||||
</div>
|
||||
`;
|
||||
this._extendExpiryDate.classList.remove("unfocused");
|
||||
if (!cantShow) {
|
||||
this._extendExpiryDate.innerHTML = `
|
||||
<div class="flex flex-col">
|
||||
<span>${window.lang.strings("accountWillExpire").replace("{date}", toDateString(date))}</span>
|
||||
${users.length > 1 ? "<span>"+window.lang.strings("expirationBasedOn")+"</span>" : ""}
|
||||
</div>
|
||||
`;
|
||||
this._extendExpiryDate.classList.remove("unfocused");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2072,18 +2097,25 @@ export class accountsList extends PaginatedList {
|
||||
}
|
||||
document.getElementById("header-extend-expiry").textContent = header;
|
||||
const extend = () => {
|
||||
let send = { "users": applyList, "timestamp": 0, "notify": this._enableExpiryNotify.checked }
|
||||
let send: ExtendExpiryDTO = {
|
||||
users: applyList,
|
||||
timestamp: 0,
|
||||
notify: this._enableExpiryNotify.checked
|
||||
}
|
||||
if (this._enableExpiryNotify.checked) {
|
||||
send["reason"] = this._enableExpiryReason.value;
|
||||
send.reason = this._enableExpiryReason.value;
|
||||
}
|
||||
if (this._usingExtendExpiryTextInput) {
|
||||
let date = (Date as any).fromString(this._extendExpiryTextInput.value) as Date;
|
||||
send["timestamp"] = Math.floor(date.getTime() / 1000);
|
||||
send.timestamp = Math.floor(date.getTime() / 1000);
|
||||
if ("invalid" in (date as any)) {
|
||||
window.notifications.customError("extendExpiryError", window.lang.notif("errorInvalidDate"));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (this._extendExpiryFromPreviousExpiry.checked) {
|
||||
send.try_extend_from_previous_expiry = true;
|
||||
}
|
||||
for (let field of ["months", "days", "hours", "minutes"]) {
|
||||
send[field] = +(document.getElementById("extend-expiry-"+field) as HTMLSelectElement).value;
|
||||
}
|
||||
|
||||
@@ -248,22 +248,65 @@ class DOMInvite implements Invite {
|
||||
private _right: HTMLDivElement;
|
||||
private _userTable: HTMLDivElement;
|
||||
|
||||
private _detailsToggle: HTMLInputElement;
|
||||
|
||||
// whether the details card is expanded.
|
||||
get expanded(): boolean {
|
||||
return this._details.classList.contains("focused");
|
||||
return this._detailsToggle.checked;
|
||||
}
|
||||
set expanded(state: boolean) {
|
||||
const toggle = (this._infoArea.querySelector("input.inv-toggle-details") as HTMLInputElement);
|
||||
this._detailsToggle.checked = state;
|
||||
if (state) {
|
||||
this._detailsToggle.previousElementSibling.classList.add("rotated");
|
||||
this._detailsToggle.previousElementSibling.classList.remove("not-rotated");
|
||||
|
||||
this._details.classList.remove("unfocused");
|
||||
this._details.classList.add("focused");
|
||||
toggle.previousElementSibling.classList.add("rotated");
|
||||
toggle.previousElementSibling.classList.remove("not-rotated");
|
||||
const fullHeight = () => {
|
||||
this._details.removeEventListener("transitionend", fullHeight);
|
||||
this._details.style.maxHeight = "9999px";
|
||||
};
|
||||
this._details.addEventListener("transitionend", fullHeight);
|
||||
this._details.style.maxHeight = (1*this._details.scrollHeight)+"px";
|
||||
this._details.style.opacity = "100%";
|
||||
} else {
|
||||
this._detailsToggle.previousElementSibling.classList.remove("rotated");
|
||||
this._detailsToggle.previousElementSibling.classList.add("not-rotated");
|
||||
const mainTransitionEnd = () => {
|
||||
this._details.removeEventListener("transitionend", mainTransitionEnd);
|
||||
this._details.classList.add("unfocused");
|
||||
this._details.classList.remove("focused");
|
||||
};
|
||||
const mainTransitionStart = () => {
|
||||
this._details.removeEventListener("transitionend", mainTransitionStart);
|
||||
this._details.style.transitionDuration = "";
|
||||
this._details.addEventListener("transitionend", mainTransitionEnd);
|
||||
this._details.style.maxHeight = "0";
|
||||
this._details.style.opacity = "0";
|
||||
};
|
||||
this._details.style.transitionDuration = "1ms";
|
||||
this._details.addEventListener("transitionend", mainTransitionStart);
|
||||
this._details.style.maxHeight = (1*this._details.scrollHeight)+"px";
|
||||
}
|
||||
}
|
||||
|
||||
setExpandedWithoutAnimation(state: boolean) {
|
||||
this._detailsToggle.checked = state;
|
||||
if (state) {
|
||||
this._detailsToggle.previousElementSibling.classList.add("rotated");
|
||||
this._detailsToggle.previousElementSibling.classList.remove("not-rotated");
|
||||
|
||||
this._details.classList.remove("unfocused");
|
||||
this._details.classList.add("focused");
|
||||
this._details.style.maxHeight = "9999px";
|
||||
this._details.style.opacity = "100%";
|
||||
} else {
|
||||
this._detailsToggle.previousElementSibling.classList.remove("rotated");
|
||||
this._detailsToggle.previousElementSibling.classList.add("not-rotated");
|
||||
this._details.classList.add("unfocused");
|
||||
this._details.classList.remove("focused");
|
||||
toggle.previousElementSibling.classList.remove("rotated");
|
||||
toggle.previousElementSibling.classList.add("not-rotated");
|
||||
this._details.style.maxHeight = "0";
|
||||
this._details.style.opacity = "0";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,11 +315,11 @@ class DOMInvite implements Invite {
|
||||
constructor(invite: Invite) {
|
||||
// first create the invite structure, then use our setter methods to fill in the data.
|
||||
this._container = document.createElement('div') as HTMLDivElement;
|
||||
this._container.classList.add("inv", "overflow-visible");
|
||||
this._container.classList.add("inv", "overflow-visible", "flex", "flex-col", "gap-2");
|
||||
|
||||
this._header = document.createElement('div') as HTMLDivElement;
|
||||
this._container.appendChild(this._header);
|
||||
this._header.classList.add("card", "dark:~d_neutral", "@low", "inv-header", "flex", "flex-row", "justify-between", "mt-2", "overflow-visible", "gap-2");
|
||||
this._header.classList.add("card", "dark:~d_neutral", "@low", "inv-header", "flex", "flex-row", "justify-between", "overflow-visible", "gap-2");
|
||||
|
||||
this._codeArea = document.createElement('div') as HTMLDivElement;
|
||||
this._header.appendChild(this._codeArea);
|
||||
@@ -314,15 +357,17 @@ class DOMInvite implements Invite {
|
||||
</div>
|
||||
<span class="button ~critical @low inv-delete h-full">${window.lang.strings("delete")}</span>
|
||||
<label>
|
||||
<i class="icon px-2.5 py-2 ri-arrow-down-s-line not-rotated"></i>
|
||||
<i class="icon px-2.5 py-2 ri-arrow-down-s-line text-xl not-rotated"></i>
|
||||
<input class="inv-toggle-details unfocused" type="checkbox">
|
||||
</label>
|
||||
`;
|
||||
|
||||
(this._infoArea.querySelector(".inv-delete") as HTMLSpanElement).onclick = this.delete;
|
||||
|
||||
const toggle = (this._infoArea.querySelector("input.inv-toggle-details") as HTMLInputElement);
|
||||
toggle.onchange = () => { this.expanded = !this.expanded; };
|
||||
this._detailsToggle = (this._infoArea.querySelector("input.inv-toggle-details") as HTMLInputElement);
|
||||
this._detailsToggle.onclick = () => {
|
||||
this.expanded = this.expanded;
|
||||
};
|
||||
const toggleDetails = (event: Event) => {
|
||||
if (event.target == this._header || event.target == this._codeArea || event.target == this._infoArea) {
|
||||
this.expanded = !this.expanded;
|
||||
@@ -333,7 +378,9 @@ class DOMInvite implements Invite {
|
||||
|
||||
this._details = document.createElement('div') as HTMLDivElement;
|
||||
this._container.appendChild(this._details);
|
||||
this._details.classList.add("card", "~neutral", "@low", "mt-2", "inv-details");
|
||||
this._details.classList.add("card", "~neutral", "@low", "inv-details", "transition-all", "unfocused");
|
||||
this._details.style.maxHeight = "0";
|
||||
this._details.style.opacity = "0";
|
||||
const detailsInner = document.createElement('div') as HTMLDivElement;
|
||||
this._details.appendChild(detailsInner);
|
||||
detailsInner.classList.add("inv-row", "flex", "flex-row", "flex-wrap", "justify-between", "gap-4");
|
||||
@@ -394,8 +441,7 @@ class DOMInvite implements Invite {
|
||||
this._userTable.classList.add("text-sm", "mt-1", );
|
||||
this._right.appendChild(this._userTable);
|
||||
|
||||
|
||||
this.expanded = false;
|
||||
this.setExpandedWithoutAnimation(false);
|
||||
this.update(invite);
|
||||
|
||||
document.addEventListener("profileLoadEvent", () => { this.loadProfiles(); }, false);
|
||||
@@ -440,7 +486,7 @@ export class inviteList implements inviteList {
|
||||
|
||||
focusInvite = (inviteCode: string, errorMsg: string = window.lang.notif("errorInviteNoLongerExists")) => {
|
||||
for (let code of Object.keys(this.invites)) {
|
||||
this.invites[code].expanded = code == inviteCode;
|
||||
this.invites[code].setExpandedWithoutAnimation(code == inviteCode);
|
||||
}
|
||||
if (inviteCode in this.invites) this.invites[inviteCode].focus();
|
||||
else window.notifications.customError("inviteDoesntExistError", errorMsg);
|
||||
@@ -488,7 +534,7 @@ export class inviteList implements inviteList {
|
||||
this._list.classList.add("empty");
|
||||
this._list.innerHTML = `
|
||||
<div class="inv inv-empty">
|
||||
<div class="card dark:~d_neutral @low inv-header mt-2">
|
||||
<div class="card dark:~d_neutral @low inv-header">
|
||||
<div class="justify-start">
|
||||
<span class="text-black dark:text-white font-mono bg-inherit">${window.lang.strings("inviteNoInvites")}</span>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { _get, _post, _delete, _download, _upload, toggleLoader, addLoader, removeLoader, insertText, toClipboard, toDateString } from "../modules/common.js";
|
||||
import { Marked } from "@ts-stack/markdown";
|
||||
import { stripMarkdown } from "../modules/stripmd.js";
|
||||
import { PDT } from "src/data/timezoneNames";
|
||||
|
||||
declare var window: GlobalWindow;
|
||||
|
||||
const toBool = (s: string): boolean => {
|
||||
let b = Boolean(s);
|
||||
if (s == "false") b = false;
|
||||
return b;
|
||||
return s == "false" ? false : Boolean(s);
|
||||
}
|
||||
|
||||
interface BackupDTO {
|
||||
@@ -19,9 +18,19 @@ interface BackupDTO {
|
||||
}
|
||||
|
||||
interface settingsChangedEvent extends Event {
|
||||
detail: string;
|
||||
detail: {
|
||||
value: string;
|
||||
hidden: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
const changedEvent = (section: string, setting: string, value: string, hidden: boolean = false) => {
|
||||
return new CustomEvent(`settings-${section}-${setting}`, { detail: {
|
||||
value: value,
|
||||
hidden: hidden
|
||||
}});
|
||||
};
|
||||
|
||||
type SettingType = string;
|
||||
|
||||
const BoolType: SettingType = "bool";
|
||||
@@ -60,8 +69,7 @@ interface Setting {
|
||||
asElement: () => HTMLElement;
|
||||
update: (s: Setting) => void;
|
||||
|
||||
hide: () => void;
|
||||
show: () => void;
|
||||
hidden: boolean;
|
||||
|
||||
valueAsString: () => string;
|
||||
}
|
||||
@@ -74,6 +82,9 @@ const splitDependant = (section: string, dep: string): string[] => {
|
||||
return parts
|
||||
};
|
||||
|
||||
let RestartRequiredBadge: HTMLElement;
|
||||
let RequiredBadge: HTMLElement;
|
||||
|
||||
class DOMSetting {
|
||||
protected _hideEl: HTMLElement;
|
||||
protected _input: HTMLInputElement;
|
||||
@@ -83,26 +94,22 @@ class DOMSetting {
|
||||
protected _restart: HTMLSpanElement;
|
||||
protected _advanced: boolean;
|
||||
protected _section: string;
|
||||
protected _s: Setting;
|
||||
setting: string;
|
||||
|
||||
hide = () => {
|
||||
this._hideEl.classList.add("unfocused");
|
||||
const event = new CustomEvent(`settings-${this._section}-${this.setting}`, { "detail": false })
|
||||
document.dispatchEvent(event);
|
||||
|
||||
};
|
||||
show = () => {
|
||||
this._hideEl.classList.remove("unfocused");
|
||||
const event = new CustomEvent(`settings-${this._section}-${this.setting}`, { "detail": this.valueAsString() })
|
||||
document.dispatchEvent(event);
|
||||
};
|
||||
get hidden(): boolean { return this._hideEl.classList.contains("unfocused"); }
|
||||
set hidden(v: boolean) {
|
||||
if (v) {
|
||||
this._hideEl.classList.add("unfocused");
|
||||
} else {
|
||||
this._hideEl.classList.remove("unfocused");
|
||||
}
|
||||
document.dispatchEvent(changedEvent(this._section, this.setting, this.valueAsString(), v));
|
||||
console.log(`dispatched settings-${this._section}-${this.setting} = ${this.valueAsString()}/${v}`);
|
||||
}
|
||||
|
||||
private _advancedListener = (event: settingsChangedEvent) => {
|
||||
if (!toBool(event.detail)) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.show();
|
||||
}
|
||||
this.hidden = !toBool(event.detail.value);
|
||||
}
|
||||
|
||||
get advanced(): boolean { return this._advanced; }
|
||||
@@ -129,38 +136,55 @@ class DOMSetting {
|
||||
}
|
||||
}
|
||||
|
||||
get required(): boolean { return this._required.classList.contains("badge"); }
|
||||
get required(): boolean { return !(this._required.classList.contains("unfocused")); }
|
||||
set required(state: boolean) {
|
||||
if (state) {
|
||||
this._required.classList.remove("unfocused");
|
||||
this._required.classList.add("badge", "~critical");
|
||||
this._required.textContent = "*";
|
||||
this._required.innerHTML = RequiredBadge.outerHTML;
|
||||
} else {
|
||||
this._required.classList.add("unfocused");
|
||||
this._required.classList.remove("badge", "~critical");
|
||||
this._required.textContent = "";
|
||||
this._required.textContent = ``;
|
||||
}
|
||||
}
|
||||
|
||||
get requires_restart(): boolean { return this._restart.classList.contains("badge"); }
|
||||
get requires_restart(): boolean { return !(this._restart.classList.contains("unfocused")); }
|
||||
set requires_restart(state: boolean) {
|
||||
if (state) {
|
||||
this._restart.classList.remove("unfocused");
|
||||
this._restart.classList.add("badge", "~info", "dark:~d_warning");
|
||||
this._restart.textContent = "R";
|
||||
this._restart.innerHTML = RestartRequiredBadge.outerHTML;
|
||||
} else {
|
||||
this._restart.classList.add("unfocused");
|
||||
this._restart.classList.remove("badge", "~info", "dark:~d_warning");
|
||||
this._restart.textContent = "";
|
||||
this._restart.textContent = ``;
|
||||
}
|
||||
}
|
||||
|
||||
get depends_true(): string { return this._s.depends_true; }
|
||||
set depends_true(v: string) {
|
||||
this._s.depends_true = v;
|
||||
this._registerDependencies();
|
||||
}
|
||||
|
||||
get depends_false(): string { return this._s.depends_false; }
|
||||
set depends_false(v: string) {
|
||||
this._s.depends_false = v;
|
||||
this._registerDependencies();
|
||||
}
|
||||
|
||||
protected _registerDependencies() {
|
||||
// Doesn't re-register dependencies, but that isn't important in this application
|
||||
if (!(this._s.depends_true || this._s.depends_false)) return;
|
||||
let [sect, dependant] = splitDependant(this._section, this._s.depends_true || this._s.depends_false);
|
||||
let state = !(Boolean(this._s.depends_false));
|
||||
document.addEventListener(`settings-${sect}-${dependant}`, (event: settingsChangedEvent) => {
|
||||
this.hidden = event.detail.hidden || (toBool(event.detail.value) !== state);
|
||||
});
|
||||
}
|
||||
|
||||
valueAsString = (): string => { return ""+this.value; };
|
||||
|
||||
onValueChange = () => {
|
||||
const event = new CustomEvent(`settings-${this._section}-${this.setting}`, { "detail": this.valueAsString() })
|
||||
document.dispatchEvent(changedEvent(this._section, this.setting, this.valueAsString(), this.hidden));
|
||||
const setEvent = new CustomEvent(`settings-set-${this._section}-${this.setting}`, { "detail": this.valueAsString() })
|
||||
document.dispatchEvent(event);
|
||||
document.dispatchEvent(setEvent);
|
||||
if (this.requires_restart) { document.dispatchEvent(new CustomEvent("settings-requires-restart")); }
|
||||
};
|
||||
@@ -177,7 +201,7 @@ class DOMSetting {
|
||||
<div class="flex flex-row gap-2 items-baseline">
|
||||
<span class="setting-label"></span>
|
||||
<div class="setting-tooltip tooltip right unfocused">
|
||||
<i class="icon ri-information-line align-baseline"></i>
|
||||
<i class="icon ri-information-line align-[-0.05rem]"></i>
|
||||
<span class="content sm"></span>
|
||||
</div>
|
||||
<span class="setting-required unfocused"></span>
|
||||
@@ -191,18 +215,6 @@ class DOMSetting {
|
||||
this._restart = this._container.querySelector("span.setting-restart") as HTMLSpanElement;
|
||||
// "input" variable should supply the HTML of an element with class "setting-input"
|
||||
this._input = this._container.querySelector(".setting-input") as HTMLInputElement;
|
||||
if (setting.depends_false || setting.depends_true) {
|
||||
let dependant = splitDependant(section, setting.depends_true || setting.depends_false);
|
||||
let state = true;
|
||||
if (setting.depends_false) { state = false; }
|
||||
document.addEventListener(`settings-${dependant[0]}-${dependant[1]}`, (event: settingsChangedEvent) => {
|
||||
if (toBool(event.detail) !== state) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
this._input.onchange = this.onValueChange;
|
||||
document.addEventListener(`settings-loaded`, this.onValueChange);
|
||||
this._hideEl = this._container;
|
||||
@@ -218,6 +230,11 @@ class DOMSetting {
|
||||
this.requires_restart = s.requires_restart;
|
||||
this.value = s.value;
|
||||
this.advanced = s.advanced;
|
||||
if (!(this._s) || s.depends_true != this._s.depends_true || s.depends_false != this._s.depends_false) {
|
||||
this._s = s;
|
||||
this._registerDependencies();
|
||||
}
|
||||
this._s = s;
|
||||
}
|
||||
|
||||
asElement = (): HTMLDivElement => { return this._container; }
|
||||
@@ -415,12 +432,14 @@ class DOMNote extends DOMSetting implements SNote {
|
||||
private _style: string;
|
||||
|
||||
// We're a note, no one depends on us so we don't need to broadcast a state change.
|
||||
hide = () => {
|
||||
this._container.classList.add("unfocused");
|
||||
};
|
||||
show = () => {
|
||||
this._container.classList.remove("unfocused");
|
||||
};
|
||||
get hidden(): boolean { return this._container.classList.contains("unfocused"); }
|
||||
set hidden(v: boolean) {
|
||||
if (v) {
|
||||
this._container.classList.add("unfocused");
|
||||
} else {
|
||||
this._container.classList.remove("unfocused");
|
||||
}
|
||||
}
|
||||
|
||||
get name(): string { return this._nameEl.textContent; }
|
||||
set name(n: string) { this._nameEl.textContent = n; }
|
||||
@@ -472,6 +491,226 @@ class DOMNote extends DOMSetting implements SNote {
|
||||
asElement = (): HTMLDivElement => { return this._container; }
|
||||
}
|
||||
|
||||
interface Group {
|
||||
group: string;
|
||||
name: string;
|
||||
description: string;
|
||||
members: Member[];
|
||||
}
|
||||
|
||||
abstract class groupableItem {
|
||||
protected _el: HTMLElement;
|
||||
asElement = () => { return this._el; }
|
||||
remove = () => { this._el.remove(); };
|
||||
inGroup = (): string|null => { return this._el.parentElement.getAttribute("data-group"); }
|
||||
get hidden(): boolean { return this._el.classList.contains("unfocused"); }
|
||||
set hidden(v: boolean) {
|
||||
if (v) {
|
||||
this._el.classList.add("unfocused");
|
||||
if (this.inGroup()) {
|
||||
document.dispatchEvent(new CustomEvent(`settings-group-${this.inGroup()}-child-hidden`));
|
||||
}
|
||||
} else {
|
||||
this._el.classList.remove("unfocused");
|
||||
if (this.inGroup()) {
|
||||
document.dispatchEvent(new CustomEvent(`settings-group-${this.inGroup()}-child-visible`));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class groupButton extends groupableItem {
|
||||
button: HTMLElement;
|
||||
private _dropdown: HTMLElement;
|
||||
private _icon: HTMLElement;
|
||||
private _check: HTMLInputElement;
|
||||
private _group: Group;
|
||||
private _indent: number;
|
||||
private _parentSidebar: HTMLElement;
|
||||
|
||||
private static readonly _margin = "ml-6";
|
||||
private _indentClasses = ["h-11", "h-10", "h-9"];
|
||||
private _indentClass = () => {
|
||||
const classes = [["h-10"], ["h-9"]];
|
||||
return classes[Math.min(this.indent, classes.length-1)];
|
||||
};
|
||||
|
||||
asElement = () => { return this._el; };
|
||||
|
||||
remove = () => { this._el.remove(); };
|
||||
|
||||
update = (g: Group) => {
|
||||
this._group = g;
|
||||
this.group = g.group;
|
||||
this.name = g.name;
|
||||
this.description = g.description;
|
||||
};
|
||||
|
||||
append(item: HTMLElement|groupButton) {
|
||||
if (item instanceof groupButton) {
|
||||
item.button.classList.remove(...this._indentClasses);
|
||||
item.button.classList.add(...this._indentClass());
|
||||
this._dropdown.appendChild(item.asElement());
|
||||
} else {
|
||||
item.classList.remove(...this._indentClasses);
|
||||
item.classList.add(...this._indentClass());
|
||||
this._dropdown.appendChild(item);
|
||||
}
|
||||
}
|
||||
|
||||
get name(): string { return this._group.name; }
|
||||
set name(v: string) {
|
||||
this._group.name = v;
|
||||
this.button.querySelector(".group-button-name").textContent = v;
|
||||
}
|
||||
|
||||
get group(): string { return this._group.group; }
|
||||
set group(v: string) {
|
||||
document.removeEventListener(`settings-group-${this.group}-child-visible`, this._childVisible);
|
||||
document.removeEventListener(`settings-group-${this.group}-child-hidden`, this._childHidden);
|
||||
this._group.group = v;
|
||||
document.addEventListener(`settings-group-${this.group}-child-visible`, this._childVisible);
|
||||
document.addEventListener(`settings-group-${this.group}-child-hidden`, this._childHidden);
|
||||
this._el.setAttribute("data-group", v);
|
||||
this.button.setAttribute("data-group", v);
|
||||
this._check.setAttribute("data-group", v);
|
||||
this._dropdown.setAttribute("data-group", v);
|
||||
}
|
||||
|
||||
get description(): string { return this._group.description; }
|
||||
set description(v: string) { this._group.description = v; }
|
||||
|
||||
get indent(): number { return this._indent; }
|
||||
set indent(v: number) {
|
||||
this._dropdown.classList.remove(groupButton._margin);
|
||||
this._indent = v;
|
||||
this._dropdown.classList.add(groupButton._margin);
|
||||
for (let child of this._dropdown.children) {
|
||||
child.classList.remove(...this._indentClasses);
|
||||
child.classList.add(...this._indentClass());
|
||||
};
|
||||
}
|
||||
|
||||
get open(): boolean { return this._check.checked; }
|
||||
set open(v: boolean) {
|
||||
this.openCloseWithAnimation(v);
|
||||
}
|
||||
|
||||
openCloseWithAnimation(v: boolean) {
|
||||
this._check.checked = v;
|
||||
// When groups are nested, the outer group's scrollHeight will obviously change when an
|
||||
// inner group is opened/closed. Instead of traversing the tree and adjusting the maxHeight property
|
||||
// each open/close, just set the maxHeight to 9999px once the animation is completed.
|
||||
// On close, quickly set maxHeight back to ~scrollHeight, then animate to 0.
|
||||
if (this._check.checked) {
|
||||
this._icon.classList.add("rotated");
|
||||
this._icon.classList.remove("not-rotated");
|
||||
// Hide the scrollbar while we animate
|
||||
this._parentSidebar.style.overflowY = "hidden";
|
||||
this._dropdown.classList.remove("unfocused");
|
||||
const fullHeight = () => {
|
||||
this._dropdown.removeEventListener("transitionend", fullHeight);
|
||||
this._dropdown.style.maxHeight = "9999px";
|
||||
// Return the scrollbar (or whatever, just don't hide it)
|
||||
this._parentSidebar.style.overflowY = "";
|
||||
};
|
||||
this._dropdown.addEventListener("transitionend", fullHeight);
|
||||
this._dropdown.style.maxHeight = (1.2*this._dropdown.scrollHeight)+"px";
|
||||
this._dropdown.style.opacity = "100%";
|
||||
} else {
|
||||
this._icon.classList.add("not-rotated");
|
||||
this._icon.classList.remove("rotated");
|
||||
const mainTransitionEnd = () => {
|
||||
this._dropdown.removeEventListener("transitionend", mainTransitionEnd);
|
||||
this._dropdown.classList.add("unfocused");
|
||||
// Return the scrollbar (or whatever, just don't hide it)
|
||||
this._parentSidebar.style.overflowY = "";
|
||||
};
|
||||
const mainTransitionStart = () => {
|
||||
this._dropdown.removeEventListener("transitionend", mainTransitionStart)
|
||||
this._dropdown.style.transitionDuration = "";
|
||||
this._dropdown.addEventListener("transitionend", mainTransitionEnd);
|
||||
this._dropdown.style.maxHeight = "0";
|
||||
this._dropdown.style.opacity = "0";
|
||||
};
|
||||
// Hide the scrollbar while we animate
|
||||
this._parentSidebar.style.overflowY = "hidden";
|
||||
// Disabling transitions then going from 9999 - scrollHeight doesn't work in firefox to me,
|
||||
// so instead just make the transition duration really short.
|
||||
this._dropdown.style.transitionDuration = "1ms";
|
||||
this._dropdown.addEventListener("transitionend", mainTransitionStart);
|
||||
this._dropdown.style.maxHeight = (1.2*this._dropdown.scrollHeight)+"px";
|
||||
}
|
||||
}
|
||||
|
||||
openCloseWithoutAnimation(v: boolean) {
|
||||
this._check.checked = v;
|
||||
if (this._check.checked) {
|
||||
this._icon.classList.add("rotated");
|
||||
this._dropdown.style.maxHeight = "9999px";
|
||||
this._dropdown.style.opacity = "100%";
|
||||
this._dropdown.classList.remove("unfocused");
|
||||
} else {
|
||||
this._icon.classList.remove("rotated");
|
||||
this._dropdown.style.maxHeight = "0";
|
||||
this._dropdown.style.opacity = "0";
|
||||
this._dropdown.classList.add("unfocused");
|
||||
}
|
||||
}
|
||||
|
||||
private _childVisible = () => {
|
||||
this.hidden = false;
|
||||
}
|
||||
|
||||
private _childHidden = () => {
|
||||
for (let el of this._dropdown.children) {
|
||||
if (!(el.classList.contains("unfocused"))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// All children are hidden, so hide ourself
|
||||
this.hidden = true;
|
||||
}
|
||||
|
||||
// Takes sidebar as we need to disable scrolling on it when animation starts.
|
||||
constructor(parentSidebar: HTMLElement) {
|
||||
super();
|
||||
this._parentSidebar = parentSidebar;
|
||||
|
||||
this._el = document.createElement("div");
|
||||
this._el.classList.add("flex", "flex-col", "gap-2");
|
||||
|
||||
this.button = document.createElement("span") as HTMLSpanElement;
|
||||
this._el.appendChild(this.button);
|
||||
this.button.classList.add("button", "~neutral", "@low", "settings-section-button", "h-11", "justify-between");
|
||||
this.button.innerHTML = `
|
||||
<span class="group-button-name"></span>
|
||||
<label class="button border-none shadow-none">
|
||||
<i class="icon ri-arrow-down-s-line"></i>
|
||||
<input class="unfocused" type="checkbox">
|
||||
</label>
|
||||
`;
|
||||
|
||||
this._dropdown = document.createElement("div") as HTMLDivElement;
|
||||
this._el.appendChild(this._dropdown);
|
||||
this._dropdown.style.maxHeight = "0";
|
||||
this._dropdown.style.opacity = "0";
|
||||
this._dropdown.classList.add("settings-dropdown", "unfocused", "flex", "flex-col", "gap-2", "transition-all");
|
||||
|
||||
this._icon = this.button.querySelector("i.icon");
|
||||
this._check = this.button.querySelector("input[type=checkbox]") as HTMLInputElement;
|
||||
|
||||
this.button.onclick = (event: Event) => {
|
||||
if (event.target != this._icon && event.target != this._check) this.open = !this.open;
|
||||
};
|
||||
this._check.onclick = () => {
|
||||
this.open = this.open;
|
||||
}
|
||||
|
||||
this.openCloseWithoutAnimation(false);
|
||||
}
|
||||
};
|
||||
|
||||
interface Section {
|
||||
section: string;
|
||||
meta: Meta;
|
||||
@@ -566,8 +805,110 @@ class sectionPanel {
|
||||
asElement = (): HTMLDivElement => { return this._section; }
|
||||
}
|
||||
|
||||
type Member = { group: string } | { section: string };
|
||||
|
||||
class sectionButton extends groupableItem {
|
||||
section: string;
|
||||
private _name: HTMLElement;
|
||||
private _subButton: HTMLElement;
|
||||
private _meta: Meta;
|
||||
|
||||
update = (section: string, sm: Meta) => {
|
||||
this.section = section;
|
||||
this._meta = sm;
|
||||
this.name = sm.name;
|
||||
this.advanced = sm.advanced;
|
||||
this._registerDependencies();
|
||||
};
|
||||
|
||||
get subButton(): HTMLElement { return this._subButton.children[0] as HTMLElement; }
|
||||
set subButton(v: HTMLElement) { this._subButton.replaceChildren(v); }
|
||||
|
||||
get name(): string { return this._meta.name; }
|
||||
set name(v: string) {
|
||||
this._meta.name = v;
|
||||
this._name.textContent = v;
|
||||
};
|
||||
|
||||
get depends_true(): string { return this._meta.depends_true; }
|
||||
set depends_true(v: string) {
|
||||
this._meta.depends_true = v;
|
||||
this._registerDependencies();
|
||||
}
|
||||
|
||||
get depends_false(): string { return this._meta.depends_false; }
|
||||
set depends_false(v: string) {
|
||||
this._meta.depends_false = v;
|
||||
this._registerDependencies();
|
||||
}
|
||||
|
||||
get selected(): boolean { return this._el.classList.contains("selected"); }
|
||||
set selected(v: boolean) {
|
||||
if (v) this._el.classList.add("selected");
|
||||
else this._el.classList.remove("selected");
|
||||
}
|
||||
|
||||
select = () => {
|
||||
document.dispatchEvent(new CustomEvent("settings-show-panel", { detail: this.section }));
|
||||
}
|
||||
|
||||
private _registerDependencies() {
|
||||
// Doesn't re-register dependencies, but that isn't important in this application
|
||||
if (!(this._meta.depends_true || this._meta.depends_false)) return;
|
||||
|
||||
let [sect, dependant] = splitDependant(this.section, this._meta.depends_true || this._meta.depends_false);
|
||||
let state = !(Boolean(this._meta.depends_false));
|
||||
document.addEventListener(`settings-${sect}-${dependant}`, (event: settingsChangedEvent) => {
|
||||
console.log(`recieved settings-${sect}-${dependant} = ${event.detail.value} = ${toBool(event.detail.value)} / ${event.detail.hidden}`);
|
||||
const hide = event.detail.hidden || (toBool(event.detail.value) !== state);
|
||||
this.hidden = hide;
|
||||
document.dispatchEvent(new CustomEvent(`settings-${name}`, { detail: !hide }));
|
||||
});
|
||||
document.addEventListener(`settings-${sect}`, (event: settingsChangedEvent) => {
|
||||
if (event.detail.hidden || toBool(event.detail.value) !== state) {
|
||||
this.hidden = true;
|
||||
document.dispatchEvent(new CustomEvent(`settings-${name}`, { detail: false }));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _advancedListener = (event: settingsChangedEvent) => {
|
||||
if (!toBool(event.detail.value)) {
|
||||
this._el.classList.add("unfocused");
|
||||
} else {
|
||||
this._el.classList.remove("unfocused");
|
||||
}
|
||||
document.dispatchEvent(new CustomEvent("settings-re-search"));
|
||||
}
|
||||
|
||||
get advanced(): boolean { return this._meta.advanced }
|
||||
set advanced(v: boolean) {
|
||||
this._meta.advanced = v;
|
||||
if (v) document.addEventListener("settings-advancedState", this._advancedListener);
|
||||
else document.removeEventListener("settings-advancedState", this._advancedListener);
|
||||
}
|
||||
|
||||
constructor(section?: string, sectionMeta?: Meta) {
|
||||
super();
|
||||
this._el = document.createElement("span") as HTMLSpanElement;
|
||||
this._el.classList.add("button", "~neutral", "@low", "settings-section-button", "h-11", "justify-between");
|
||||
this._el.innerHTML = `
|
||||
<span class="settings-section-button-name"></span>
|
||||
<div class="settings-section-button-sub-button"></div>
|
||||
`;
|
||||
this._name = this._el.getElementsByClassName("settings-section-button-name")[0] as HTMLElement;
|
||||
this._subButton = this._el.getElementsByClassName("settings-section-button-sub-button")[0] as HTMLElement;
|
||||
|
||||
this._el.onclick = this.select;
|
||||
|
||||
if (sectionMeta) this.update(section, sectionMeta);
|
||||
}
|
||||
}
|
||||
|
||||
interface Settings {
|
||||
groups: Group[];
|
||||
sections: Section[];
|
||||
order?: Member[];
|
||||
}
|
||||
|
||||
export class settingsList {
|
||||
@@ -578,10 +919,14 @@ export class settingsList {
|
||||
private _loader = document.getElementById("settings-loader") as HTMLDivElement;
|
||||
|
||||
private _panel = document.getElementById("settings-panel") as HTMLDivElement;
|
||||
private _sidebar = document.getElementById("settings-sidebar") as HTMLDivElement;
|
||||
private _sidebar = document.getElementById("settings-sidebar-items") as HTMLDivElement;
|
||||
private _visibleSection: string;
|
||||
private _sections: { [name: string]: sectionPanel }
|
||||
private _buttons: { [name: string]: HTMLSpanElement }
|
||||
private _sections: { [name: string]: sectionPanel };
|
||||
private _buttons: { [name: string]: sectionButton };
|
||||
|
||||
private _groups: { [name: string]: Group };
|
||||
private _groupButtons: { [name: string]: groupButton };
|
||||
|
||||
private _needsRestart: boolean = false;
|
||||
private _messageEditor = new MessageEditor();
|
||||
private _settings: Settings;
|
||||
@@ -595,59 +940,93 @@ export class settingsList {
|
||||
private _backupSortDirection = document.getElementById("settings-backups-sort-direction") as HTMLButtonElement;
|
||||
private _backupSortAscending = true;
|
||||
|
||||
// Must be called -after- all section have been added.
|
||||
// Takes all groups at once since members might contain each other.
|
||||
addGroups = (groups: Group[]) => {
|
||||
groups.forEach((g) => { this._groups[g.group] = g });
|
||||
const addGroup = (g: Group, indent: number = 0): groupButton => {
|
||||
if (g.group in this._groupButtons) return null;
|
||||
|
||||
const container = new groupButton(this._sidebar);
|
||||
container.update(g);
|
||||
container.indent = indent;
|
||||
|
||||
for (const member of g.members) {
|
||||
if ("group" in member) {
|
||||
let subgroup = addGroup(this._groups[member.group], indent+1);
|
||||
if (!subgroup) {
|
||||
subgroup = this._groupButtons[member.group];
|
||||
// Remove from page
|
||||
subgroup.remove();
|
||||
}
|
||||
container.append(subgroup);
|
||||
} else if ("section" in member) {
|
||||
const subsection = this._buttons[member.section];
|
||||
// Remove from page
|
||||
subsection.remove();
|
||||
container.append(subsection.asElement());
|
||||
}
|
||||
}
|
||||
|
||||
this._groupButtons[g.group] = container;
|
||||
return container;
|
||||
}
|
||||
for (let g of groups) {
|
||||
const container = addGroup(g);
|
||||
if (container) {
|
||||
this._sidebar.appendChild(container.asElement());
|
||||
container.openCloseWithoutAnimation(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addSection = (name: string, s: Section, subButton?: HTMLElement) => {
|
||||
const section = new sectionPanel(s, name);
|
||||
this._sections[name] = section;
|
||||
this._panel.appendChild(this._sections[name].asElement());
|
||||
const button = document.createElement("span") as HTMLSpanElement;
|
||||
button.classList.add("button", "~neutral", "@low", "settings-section-button", "justify-between");
|
||||
button.textContent = s.meta.name;
|
||||
if (subButton) { button.appendChild(subButton); }
|
||||
button.onclick = () => { this._showPanel(name); };
|
||||
if (s.meta.depends_true || s.meta.depends_false) {
|
||||
let dependant = splitDependant(name, s.meta.depends_true || s.meta.depends_false);
|
||||
let state = true;
|
||||
if (s.meta.depends_false) { state = false; }
|
||||
document.addEventListener(`settings-${dependant[0]}-${dependant[1]}`, (event: settingsChangedEvent) => {
|
||||
if (toBool(event.detail) !== state) {
|
||||
button.classList.add("unfocused");
|
||||
document.dispatchEvent(new CustomEvent(`settings-${name}`, { detail: false }));
|
||||
} else {
|
||||
button.classList.remove("unfocused");
|
||||
document.dispatchEvent(new CustomEvent(`settings-${name}`, { detail: true }));
|
||||
}
|
||||
});
|
||||
document.addEventListener(`settings-${dependant[0]}`, (event: settingsChangedEvent) => {
|
||||
if (toBool(event.detail) !== state) {
|
||||
button.classList.add("unfocused");
|
||||
document.dispatchEvent(new CustomEvent(`settings-${name}`, { detail: false }));
|
||||
}
|
||||
});
|
||||
}
|
||||
if (s.meta.advanced) {
|
||||
document.addEventListener("settings-advancedState", (event: settingsChangedEvent) => {
|
||||
if (!toBool(event.detail)) {
|
||||
button.classList.add("unfocused");
|
||||
} else {
|
||||
button.classList.remove("unfocused");
|
||||
}
|
||||
this._searchbox.oninput(null);
|
||||
});
|
||||
}
|
||||
const button = new sectionButton(name, s.meta);
|
||||
if (subButton) button.subButton = subButton;
|
||||
this._buttons[name] = button;
|
||||
this._sidebar.appendChild(this._buttons[name]);
|
||||
this._sidebar.appendChild(button.asElement());
|
||||
}
|
||||
|
||||
private _traverseMemberList = (list: Member[], func: (sect: string) => void) => {
|
||||
for (const member of list) {
|
||||
if ("group" in member) {
|
||||
for (const group of this._settings.groups) {
|
||||
if (group.group == member.group) {
|
||||
this._traverseMemberList(group.members, func);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
func(member.section);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setUIOrder(order: Member[]) {
|
||||
this._sidebar.textContent = ``;
|
||||
for (const member of order) {
|
||||
if ("group" in member) {
|
||||
this._sidebar.appendChild(this._groupButtons[member.group].asElement());
|
||||
this._groupButtons[member.group].openCloseWithoutAnimation(false);
|
||||
} else if ("section" in member) {
|
||||
if (member.section in this._buttons) {
|
||||
this._sidebar.appendChild(this._buttons[member.section].asElement());
|
||||
} else {
|
||||
console.warn("Settings section specified in order but missing:", member.section);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _showPanel = (name: string) => {
|
||||
// console.log("showing", name);
|
||||
for (let n in this._sections) {
|
||||
this._sections[n].visible = n == name;
|
||||
this._buttons[name].selected = n == name;
|
||||
if (n == name) {
|
||||
this._sections[name].visible = true;
|
||||
this._visibleSection = name;
|
||||
this._buttons[name].classList.add("selected");
|
||||
} else {
|
||||
this._sections[n].visible = false;
|
||||
this._buttons[n].classList.remove("selected");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -697,7 +1076,7 @@ export class settingsList {
|
||||
|
||||
setBackupSort = (ascending: boolean) => {
|
||||
this._backupSortAscending = ascending;
|
||||
this._backupSortDirection.innerHTML = `${window.lang.strings("sortDirection")} <i class="ri-arrow-${ascending ? "up" : "down"}-s-line ml-2"></i>`;
|
||||
this._backupSortDirection.innerHTML = `${window.lang.strings("sortDirection")} <i class="${ascending ? "ri-arrow-up-s-line" : "ri-arrow-down-s-line"} ml-2"></i>`;
|
||||
this._getBackups();
|
||||
};
|
||||
|
||||
@@ -759,6 +1138,8 @@ export class settingsList {
|
||||
});
|
||||
|
||||
constructor() {
|
||||
this._groups = {};
|
||||
this._groupButtons = {};
|
||||
this._sections = {};
|
||||
this._buttons = {};
|
||||
document.addEventListener("settings-section-changed", () => this._saveButton.classList.remove("unfocused"));
|
||||
@@ -775,6 +1156,10 @@ export class settingsList {
|
||||
this._backup();
|
||||
};
|
||||
|
||||
document.addEventListener("settings-show-panel", (event: CustomEvent) => {
|
||||
this._showPanel(event.detail as string);
|
||||
});
|
||||
|
||||
document.getElementById("settings-backups").onclick = () => {
|
||||
this.setBackupSort(this._backupSortAscending);
|
||||
window.modals.backups.show();
|
||||
@@ -814,20 +1199,39 @@ export class settingsList {
|
||||
this._searchbox.oninput = () => {
|
||||
this.search(this._searchbox.value);
|
||||
};
|
||||
|
||||
document.addEventListener("settings-re-search", () => {
|
||||
this._searchbox.oninput(null);
|
||||
});
|
||||
|
||||
for (let b of this._clearSearchboxButtons) {
|
||||
b.onclick = () => {
|
||||
this._searchbox.value = "";
|
||||
this._searchbox.oninput(null);
|
||||
};
|
||||
};
|
||||
|
||||
// Create (restart)required badges (can't do on load as window.lang is unset)
|
||||
RestartRequiredBadge = (() => {
|
||||
const rr = document.createElement("span");
|
||||
rr.classList.add("tooltip", "below");
|
||||
rr.innerHTML = `
|
||||
<span class="badge ~info dark:~d_warning align-[0.08rem]"><i class="icon ri-refresh-line h-full"></i></span>
|
||||
<span class="content sm">${window.lang.strings("restartRequired")}</span>
|
||||
`;
|
||||
|
||||
// What possessed me to put this in the DOMSelect constructor originally? like what????????
|
||||
const message = document.getElementById("settings-message") as HTMLElement;
|
||||
message.innerHTML = window.lang.var("strings",
|
||||
"settingsRequiredOrRestartMessage",
|
||||
`<span class="badge ~critical">*</span>`,
|
||||
`<span class="badge ~info dark:~d_warning">R</span>`
|
||||
);
|
||||
return rr;
|
||||
})();
|
||||
RequiredBadge = (() => {
|
||||
const r = document.createElement("span");
|
||||
r.classList.add("tooltip", "below");
|
||||
r.innerHTML = `
|
||||
<span class="badge ~critical align-[0.08rem]"><i class="icon ri-asterisk h-full"></i></span>
|
||||
<span class="content sm">${window.lang.strings("required")}</span>
|
||||
`;
|
||||
|
||||
return r;
|
||||
})();
|
||||
}
|
||||
|
||||
private _addMatrix = () => {
|
||||
@@ -883,9 +1287,9 @@ export class settingsList {
|
||||
} else {
|
||||
if (section.section == "messages" || section.section == "user_page") {
|
||||
const editButton = document.createElement("div");
|
||||
editButton.classList.add("tooltip", "left");
|
||||
editButton.classList.add("tooltip", "left", "h-full");
|
||||
editButton.innerHTML = `
|
||||
<span class="button ~neutral @low">
|
||||
<span class="button ~neutral @low h-full">
|
||||
<i class="icon ri-edit-line"></i>
|
||||
</span>
|
||||
<span class="content sm">
|
||||
@@ -902,13 +1306,28 @@ export class settingsList {
|
||||
icon.classList.add("button", "~urge");
|
||||
icon.innerHTML = `<i class="ri-download-line" title="${window.lang.strings("update")}"></i>`;
|
||||
icon.onclick = () => window.updater.checkForUpdates(window.modals.updateInfo.show);
|
||||
// Put us first
|
||||
if ("order" in this._settings && this._settings.order) {
|
||||
let i = -1;
|
||||
for (let j = 0; j < this._settings.order.length; j++) {
|
||||
const member = this._settings.order[j];
|
||||
if ("section" in member && member.section == "updates") {
|
||||
i = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i != -1) {
|
||||
this._settings.order.splice(i, 1);
|
||||
this._settings.order.unshift({ section: "updates" });
|
||||
}
|
||||
}
|
||||
}
|
||||
this.addSection(section.section, section, icon);
|
||||
} else if (section.section == "matrix" && !window.matrixEnabled) {
|
||||
const addButton = document.createElement("div");
|
||||
addButton.classList.add("tooltip", "left");
|
||||
addButton.classList.add("tooltip", "left", "h-full");
|
||||
addButton.innerHTML = `
|
||||
<span class="button ~neutral @low">+</span>
|
||||
<span class="button ~neutral h-full"><i class="icon ri-links-line"></i></span>
|
||||
<span class="content sm">
|
||||
${window.lang.strings("linkMatrix")}
|
||||
</span>
|
||||
@@ -920,6 +1339,11 @@ export class settingsList {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.addGroups(this._settings.groups);
|
||||
|
||||
if ("order" in this._settings && this._settings.order) this.setUIOrder(this._settings.order);
|
||||
|
||||
removeLoader(this._loader);
|
||||
for (let i = 0; i < this._loader.children.length; i++) {
|
||||
this._loader.children[i].classList.remove("invisible");
|
||||
@@ -936,18 +1360,36 @@ export class settingsList {
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
private _query: string;
|
||||
// FIXME: Fix searching groups
|
||||
// FIXME: Search "About" & "User profiles", pseudo-search "User profiles" for things like "Ombi", "Referrals", etc.
|
||||
search = (query: string) => {
|
||||
query = query.toLowerCase().trim();
|
||||
// Make sure a blank search is detected when there's just whitespace.
|
||||
if (query.replace(/\s+/g, "") == "") query = "";
|
||||
const noChange = query == this._query;
|
||||
|
||||
let firstVisibleSection = "";
|
||||
for (let section of this._settings.sections) {
|
||||
|
||||
// Close and hide all groups to start with
|
||||
for (const groupButton of Object.values(this._groupButtons)) {
|
||||
// Leave these opened/closed if the query didn't change
|
||||
// (this is overridden anyway if an actual search is happening,
|
||||
// so we'll only do it if the search is blank, implying something else
|
||||
// changed like advanced settings being enabled).
|
||||
if (noChange && query == "") continue;
|
||||
groupButton.openCloseWithoutAnimation(false);
|
||||
groupButton.hidden = !(groupButton.group.toLowerCase().includes(query) ||
|
||||
groupButton.name.toLowerCase().includes(query) ||
|
||||
groupButton.description.toLowerCase().includes(query));
|
||||
}
|
||||
|
||||
const searchSection = (section: Section) => {
|
||||
// Section might be disabled at build-time (like Updates), or deprecated and so not appear.
|
||||
if (!(section.section in this._sections)) {
|
||||
// console.log(`Couldn't find section "${section.section}"`);
|
||||
continue
|
||||
return;
|
||||
}
|
||||
const sectionElement = this._sections[section.section].asElement();
|
||||
let dependencyCard = sectionElement.querySelector(".settings-dependency-message");
|
||||
@@ -956,19 +1398,37 @@ export class settingsList {
|
||||
let dependencyList = null;
|
||||
|
||||
// hide button, unhide if matched
|
||||
this._buttons[section.section].classList.add("unfocused");
|
||||
const button = this._buttons[section.section];
|
||||
button.hidden = true;
|
||||
const parentGroup = button.inGroup();
|
||||
let parentGroupButton: groupButton = null;
|
||||
let matchedGroup = false;
|
||||
if (parentGroup) {
|
||||
parentGroupButton = this._groupButtons[parentGroup];
|
||||
matchedGroup = !(parentGroupButton.hidden);
|
||||
}
|
||||
|
||||
let matchedSection = false;
|
||||
|
||||
if (section.section.toLowerCase().includes(query) ||
|
||||
section.meta.name.toLowerCase().includes(query) ||
|
||||
section.meta.description.toLowerCase().includes(query)) {
|
||||
if ((section.meta.advanced && this._advanced) || !(section.meta.advanced)) {
|
||||
this._buttons[section.section].classList.remove("unfocused");
|
||||
firstVisibleSection = firstVisibleSection || section.section;
|
||||
matchedSection = true;
|
||||
const show = () => {
|
||||
button.hidden = false;
|
||||
if (parentGroupButton) {
|
||||
if (query != "") parentGroupButton.openCloseWithoutAnimation(true);
|
||||
}
|
||||
}
|
||||
const hide = () => {
|
||||
button.hidden = true;
|
||||
}
|
||||
|
||||
let matchedSection = matchedGroup ||
|
||||
section.section.toLowerCase().includes(query) ||
|
||||
section.meta.name.toLowerCase().includes(query) ||
|
||||
section.meta.description.toLowerCase().includes(query);
|
||||
matchedSection &&= ((section.meta.advanced && this._advanced) || !(section.meta.advanced));
|
||||
|
||||
if (matchedSection) {
|
||||
show();
|
||||
firstVisibleSection = firstVisibleSection || section.section;
|
||||
}
|
||||
|
||||
for (let setting of section.settings) {
|
||||
if (setting.type == "note") continue;
|
||||
const element = sectionElement.querySelector(`div[data-name="${setting.setting}"]`) as HTMLElement;
|
||||
@@ -992,7 +1452,7 @@ export class settingsList {
|
||||
setting.description.toLowerCase().includes(query) ||
|
||||
String(setting.value).toLowerCase().includes(query)) {
|
||||
if ((section.meta.advanced && this._advanced) || !(section.meta.advanced)) {
|
||||
this._buttons[section.section].classList.remove("unfocused");
|
||||
show();
|
||||
firstVisibleSection = firstVisibleSection || section.section;
|
||||
}
|
||||
const shouldShow = (query != "" &&
|
||||
@@ -1037,18 +1497,26 @@ export class settingsList {
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (let section of this._settings.sections) {
|
||||
searchSection(section);
|
||||
}
|
||||
|
||||
if (firstVisibleSection && (query != "" || this._visibleSection == "")) {
|
||||
this._buttons[firstVisibleSection].onclick(null);
|
||||
this._buttons[firstVisibleSection].select();
|
||||
this._noResultsPanel.classList.add("unfocused");
|
||||
} else if (query != "") {
|
||||
this._noResultsPanel.classList.remove("unfocused");
|
||||
if (this._visibleSection) {
|
||||
this._sections[this._visibleSection].visible = false;
|
||||
this._buttons[this._visibleSection].classList.remove("selected");
|
||||
this._buttons[this._visibleSection].selected = false;
|
||||
this._visibleSection = "";
|
||||
}
|
||||
}
|
||||
|
||||
// We can use this later to tell if we should leave groups expanded/closed as they were.
|
||||
this._query = query;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"outDir": "../js",
|
||||
"target": "es2017",
|
||||
"lib": ["dom", "es2017"],
|
||||
"lib": ["dom", "es2017", "dom.iterable"],
|
||||
"typeRoots": ["./typings", "../node_modules/@types"],
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
|
||||
@@ -25,7 +25,7 @@ interface userWindow extends GlobalWindow {
|
||||
declare var window: userWindow;
|
||||
|
||||
// const basePath = window.location.pathname.replace("/password/reset", "");
|
||||
const basePath = window.pages.MyAccount;
|
||||
const basePath = window.pages.Base + window.pages.MyAccount;
|
||||
|
||||
const theme = new ThemeManager(document.getElementById("button-theme"));
|
||||
|
||||
@@ -659,7 +659,7 @@ document.addEventListener("details-reload", () => {
|
||||
expiryCard.expiry = details.expiry;
|
||||
|
||||
const adminBackButton = document.getElementById("admin-back-button") as HTMLAnchorElement;
|
||||
adminBackButton.href = window.pages.Base + window.pages.Admin;
|
||||
adminBackButton.href = window.pages.Base + window.pages.Admin + "/";
|
||||
|
||||
let messageCard = document.getElementById("card-message");
|
||||
if (details.accounts_admin) {
|
||||
|
||||
@@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
lm "github.com/hrfee/jfa-go/logmessages"
|
||||
@@ -68,9 +67,10 @@ func (app *appContext) getUserTokenLogin(gc *gin.Context) {
|
||||
host := app.ExternalDomainNoPort(gc)
|
||||
uri := "/my"
|
||||
// FIXME: This seems like a bad idea? I think it's to deal with people having Reverse proxy subfolder/URL base set to /accounts.
|
||||
if strings.HasPrefix(gc.Request.RequestURI, PAGES.Base) {
|
||||
uri = "/accounts/my"
|
||||
}
|
||||
// RESPONSE: Not sure when this was added but I think some changes to page stuff make it unnecessary.
|
||||
// if strings.HasPrefix(gc.Request.RequestURI, PAGES.Base) {
|
||||
// uri = "/accounts/my"
|
||||
// }
|
||||
gc.SetCookie("user-refresh", refresh, REFRESH_TOKEN_VALIDITY_SEC, uri, host, true, true)
|
||||
gc.JSON(200, getTokenDTO{token})
|
||||
}
|
||||
|
||||
2
views.go
2
views.go
@@ -299,6 +299,7 @@ func (app *appContext) ResetPassword(gc *gin.Context) {
|
||||
"strings": app.storage.lang.PasswordReset[lang].Strings,
|
||||
"success": false,
|
||||
"customSuccessCard": false,
|
||||
"collectEmail": app.config.Section("email").Key("collect").MustBool(true),
|
||||
}
|
||||
pwr, isInternal := app.internalPWRs[pin]
|
||||
// if isInternal && setPassword {
|
||||
@@ -761,6 +762,7 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
|
||||
"validate": app.config.Section("password_validation").Key("enabled").MustBool(false),
|
||||
"requirements": app.validator.getCriteria(),
|
||||
"email": email,
|
||||
"collectEmail": app.config.Section("email").Key("collect").MustBool(true),
|
||||
"username": !app.config.Section("email").Key("no_username").MustBool(false),
|
||||
"strings": app.storage.lang.User[lang].Strings,
|
||||
"validationStrings": app.storage.lang.User[lang].validationStringsJSON,
|
||||
|
||||
Reference in New Issue
Block a user