2020-07-29 21:11:28 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2021-02-01 17:39:19 +00:00
|
|
|
"io/fs"
|
2020-11-02 23:20:06 +00:00
|
|
|
"log"
|
2021-01-31 23:12:50 +00:00
|
|
|
"os"
|
2021-01-11 19:17:43 +00:00
|
|
|
"path/filepath"
|
2020-09-22 23:01:07 +00:00
|
|
|
"strconv"
|
2020-11-29 18:01:10 +00:00
|
|
|
"strings"
|
2021-03-20 19:23:54 +00:00
|
|
|
"sync"
|
2020-07-29 21:11:28 +00:00
|
|
|
"time"
|
2021-02-19 00:47:01 +00:00
|
|
|
|
2021-03-29 20:49:46 +00:00
|
|
|
"github.com/hrfee/mediabrowser"
|
2022-01-10 01:55:48 +00:00
|
|
|
"github.com/steambap/captcha"
|
2020-07-29 21:11:28 +00:00
|
|
|
)
|
|
|
|
|
2023-06-20 11:19:24 +00:00
|
|
|
type discordStore map[string]DiscordUser
|
|
|
|
type telegramStore map[string]TelegramUser
|
|
|
|
type matrixStore map[string]MatrixUser
|
|
|
|
type emailStore map[string]EmailAddress
|
|
|
|
|
2020-07-29 21:11:28 +00:00
|
|
|
type Storage struct {
|
2023-06-20 20:43:25 +00:00
|
|
|
timePattern string
|
|
|
|
invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path, customEmails_path, users_path, telegram_path, discord_path, matrix_path, announcements_path, matrix_sql_path, userPage_path string
|
|
|
|
users map[string]time.Time // Map of Jellyfin User IDs to their expiry times.
|
|
|
|
invites Invites
|
|
|
|
profiles map[string]Profile
|
|
|
|
defaultProfile string
|
|
|
|
displayprefs, ombi_template map[string]interface{}
|
|
|
|
emails emailStore
|
|
|
|
telegram telegramStore // Map of Jellyfin User IDs to telegram users.
|
|
|
|
discord discordStore // Map of Jellyfin user IDs to discord users.
|
|
|
|
matrix matrixStore // Map of Jellyfin user IDs to Matrix users.
|
|
|
|
customEmails customEmails
|
|
|
|
userPage userPageContent
|
|
|
|
policy mediabrowser.Policy
|
|
|
|
configuration mediabrowser.Configuration
|
|
|
|
lang Lang
|
|
|
|
announcements map[string]announcementTemplate
|
|
|
|
invitesLock, usersLock, discordLock, telegramLock, matrixLock, emailsLock sync.Mutex
|
2023-06-20 11:19:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetEmails returns a copy of the store.
|
|
|
|
func (st *Storage) GetEmails() emailStore {
|
|
|
|
return st.emails
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetEmailsKey returns the value stored in the store's key.
|
|
|
|
func (st *Storage) GetEmailsKey(k string) (EmailAddress, bool) {
|
|
|
|
v, ok := st.emails[k]
|
|
|
|
return v, ok
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetEmailsKey stores value v in key k.
|
|
|
|
func (st *Storage) SetEmailsKey(k string, v EmailAddress) {
|
|
|
|
st.emailsLock.Lock()
|
|
|
|
st.emails[k] = v
|
|
|
|
st.storeEmails()
|
|
|
|
st.emailsLock.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteEmailKey deletes value at key k.
|
|
|
|
func (st *Storage) DeleteEmailsKey(k string) {
|
|
|
|
st.emailsLock.Lock()
|
|
|
|
delete(st.emails, k)
|
2023-06-20 12:28:13 +00:00
|
|
|
st.storeEmails()
|
2023-06-20 11:19:24 +00:00
|
|
|
st.emailsLock.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetDiscord returns a copy of the store.
|
|
|
|
func (st *Storage) GetDiscord() discordStore {
|
2023-06-21 16:59:58 +00:00
|
|
|
if st.discord == nil {
|
|
|
|
st.discord = discordStore{}
|
|
|
|
}
|
2023-06-20 11:19:24 +00:00
|
|
|
return st.discord
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetDiscordKey returns the value stored in the store's key.
|
|
|
|
func (st *Storage) GetDiscordKey(k string) (DiscordUser, bool) {
|
|
|
|
v, ok := st.discord[k]
|
|
|
|
return v, ok
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetDiscordKey stores value v in key k.
|
|
|
|
func (st *Storage) SetDiscordKey(k string, v DiscordUser) {
|
|
|
|
st.discordLock.Lock()
|
2023-06-21 16:59:58 +00:00
|
|
|
if st.discord == nil {
|
|
|
|
st.discord = discordStore{}
|
|
|
|
}
|
2023-06-20 11:19:24 +00:00
|
|
|
st.discord[k] = v
|
|
|
|
st.storeDiscordUsers()
|
|
|
|
st.discordLock.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteDiscordKey deletes value at key k.
|
|
|
|
func (st *Storage) DeleteDiscordKey(k string) {
|
|
|
|
st.discordLock.Lock()
|
|
|
|
delete(st.discord, k)
|
2023-06-20 12:28:13 +00:00
|
|
|
st.storeDiscordUsers()
|
2023-06-20 11:19:24 +00:00
|
|
|
st.discordLock.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetTelegram returns a copy of the store.
|
|
|
|
func (st *Storage) GetTelegram() telegramStore {
|
2023-06-21 16:59:58 +00:00
|
|
|
if st.telegram == nil {
|
|
|
|
st.telegram = telegramStore{}
|
|
|
|
}
|
2023-06-20 11:19:24 +00:00
|
|
|
return st.telegram
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetTelegramKey returns the value stored in the store's key.
|
|
|
|
func (st *Storage) GetTelegramKey(k string) (TelegramUser, bool) {
|
|
|
|
v, ok := st.telegram[k]
|
|
|
|
return v, ok
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetTelegramKey stores value v in key k.
|
|
|
|
func (st *Storage) SetTelegramKey(k string, v TelegramUser) {
|
|
|
|
st.telegramLock.Lock()
|
2023-06-21 16:59:58 +00:00
|
|
|
if st.telegram == nil {
|
|
|
|
st.telegram = telegramStore{}
|
|
|
|
}
|
2023-06-20 11:19:24 +00:00
|
|
|
st.telegram[k] = v
|
|
|
|
st.storeTelegramUsers()
|
|
|
|
st.telegramLock.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteTelegramKey deletes value at key k.
|
|
|
|
func (st *Storage) DeleteTelegramKey(k string) {
|
|
|
|
st.telegramLock.Lock()
|
|
|
|
delete(st.telegram, k)
|
2023-06-20 12:28:13 +00:00
|
|
|
st.storeTelegramUsers()
|
2023-06-20 11:19:24 +00:00
|
|
|
st.telegramLock.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetMatrix returns a copy of the store.
|
|
|
|
func (st *Storage) GetMatrix() matrixStore {
|
2023-06-21 16:59:58 +00:00
|
|
|
if st.matrix == nil {
|
|
|
|
st.matrix = matrixStore{}
|
|
|
|
}
|
2023-06-20 11:19:24 +00:00
|
|
|
return st.matrix
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetMatrixKey returns the value stored in the store's key.
|
|
|
|
func (st *Storage) GetMatrixKey(k string) (MatrixUser, bool) {
|
|
|
|
v, ok := st.matrix[k]
|
|
|
|
return v, ok
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetMatrixKey stores value v in key k.
|
|
|
|
func (st *Storage) SetMatrixKey(k string, v MatrixUser) {
|
|
|
|
st.matrixLock.Lock()
|
2023-06-21 16:59:58 +00:00
|
|
|
if st.matrix == nil {
|
|
|
|
st.matrix = matrixStore{}
|
|
|
|
}
|
2023-06-20 11:19:24 +00:00
|
|
|
st.matrix[k] = v
|
|
|
|
st.storeMatrixUsers()
|
|
|
|
st.matrixLock.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteMatrixKey deletes value at key k.
|
|
|
|
func (st *Storage) DeleteMatrixKey(k string) {
|
|
|
|
st.matrixLock.Lock()
|
|
|
|
delete(st.matrix, k)
|
2023-06-20 12:28:13 +00:00
|
|
|
st.storeMatrixUsers()
|
2023-06-20 11:19:24 +00:00
|
|
|
st.matrixLock.Unlock()
|
2021-05-07 00:08:12 +00:00
|
|
|
}
|
|
|
|
|
2023-06-21 19:39:16 +00:00
|
|
|
// GetInvites returns a copy of the store.
|
|
|
|
func (st *Storage) GetInvites() Invites {
|
|
|
|
if st.invites == nil {
|
|
|
|
st.invites = Invites{}
|
|
|
|
}
|
|
|
|
return st.invites
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetInvitesKey returns the value stored in the store's key.
|
|
|
|
func (st *Storage) GetInvitesKey(k string) (Invite, bool) {
|
|
|
|
v, ok := st.invites[k]
|
|
|
|
return v, ok
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetInvitesKey stores value v in key k.
|
|
|
|
func (st *Storage) SetInvitesKey(k string, v Invite) {
|
|
|
|
st.invitesLock.Lock()
|
|
|
|
if st.invites == nil {
|
|
|
|
st.invites = Invites{}
|
|
|
|
}
|
|
|
|
st.invites[k] = v
|
|
|
|
st.storeInvites()
|
|
|
|
st.invitesLock.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteInvitesKey deletes value at key k.
|
|
|
|
func (st *Storage) DeleteInvitesKey(k string) {
|
|
|
|
st.invitesLock.Lock()
|
|
|
|
delete(st.invites, k)
|
|
|
|
st.storeInvites()
|
|
|
|
st.invitesLock.Unlock()
|
|
|
|
}
|
|
|
|
|
2021-05-07 00:08:12 +00:00
|
|
|
type TelegramUser struct {
|
|
|
|
ChatID int64
|
|
|
|
Username string
|
|
|
|
Lang string
|
2021-05-07 13:32:51 +00:00
|
|
|
Contact bool // Whether to contact through telegram or not
|
2021-02-19 21:38:20 +00:00
|
|
|
}
|
|
|
|
|
2021-05-17 22:42:33 +00:00
|
|
|
type DiscordUser struct {
|
2021-05-21 20:35:25 +00:00
|
|
|
ChannelID string
|
|
|
|
ID string
|
|
|
|
Username string
|
|
|
|
Discriminator string
|
|
|
|
Lang string
|
|
|
|
Contact bool
|
2023-06-21 17:26:08 +00:00
|
|
|
JellyfinID string `json:"-"` // Used internally in discord.go
|
2021-05-17 22:42:33 +00:00
|
|
|
}
|
|
|
|
|
2021-05-21 21:46:46 +00:00
|
|
|
type EmailAddress struct {
|
|
|
|
Addr string
|
2022-01-08 16:20:31 +00:00
|
|
|
Label string // User Label.
|
2021-05-21 21:46:46 +00:00
|
|
|
Contact bool
|
2022-01-09 19:29:17 +00:00
|
|
|
Admin bool // Whether or not user is jfa-go admin.
|
2021-05-21 21:46:46 +00:00
|
|
|
}
|
|
|
|
|
2021-02-19 21:38:20 +00:00
|
|
|
type customEmails struct {
|
2023-06-20 20:43:25 +00:00
|
|
|
UserCreated customContent `json:"userCreated"`
|
|
|
|
InviteExpiry customContent `json:"inviteExpiry"`
|
|
|
|
PasswordReset customContent `json:"passwordReset"`
|
|
|
|
UserDeleted customContent `json:"userDeleted"`
|
|
|
|
UserDisabled customContent `json:"userDisabled"`
|
|
|
|
UserEnabled customContent `json:"userEnabled"`
|
|
|
|
InviteEmail customContent `json:"inviteEmail"`
|
|
|
|
WelcomeEmail customContent `json:"welcomeEmail"`
|
|
|
|
EmailConfirmation customContent `json:"emailConfirmation"`
|
|
|
|
UserExpired customContent `json:"userExpired"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type customContent struct {
|
2021-04-15 14:34:17 +00:00
|
|
|
Enabled bool `json:"enabled,omitempty"`
|
|
|
|
Content string `json:"content"`
|
|
|
|
Variables []string `json:"variables,omitempty"`
|
|
|
|
Conditionals []string `json:"conditionals,omitempty"`
|
2020-10-30 22:51:47 +00:00
|
|
|
}
|
|
|
|
|
2023-06-20 20:43:25 +00:00
|
|
|
type userPageContent struct {
|
|
|
|
Login customContent `json:"login"`
|
|
|
|
Page customContent `json:"page"`
|
|
|
|
}
|
|
|
|
|
2020-07-29 21:11:28 +00:00
|
|
|
// timePattern: %Y-%m-%dT%H:%M:%S.%f
|
|
|
|
|
2020-09-20 10:21:04 +00:00
|
|
|
type Profile struct {
|
2021-02-19 00:47:01 +00:00
|
|
|
Admin bool `json:"admin,omitempty"`
|
|
|
|
LibraryAccess string `json:"libraries,omitempty"`
|
|
|
|
FromUser string `json:"fromUser,omitempty"`
|
|
|
|
Policy mediabrowser.Policy `json:"policy,omitempty"`
|
|
|
|
Configuration mediabrowser.Configuration `json:"configuration,omitempty"`
|
|
|
|
Displayprefs map[string]interface{} `json:"displayprefs,omitempty"`
|
|
|
|
Default bool `json:"default,omitempty"`
|
2021-11-13 18:53:53 +00:00
|
|
|
Ombi map[string]interface{} `json:"ombi,omitempty"`
|
2020-09-20 10:21:04 +00:00
|
|
|
}
|
|
|
|
|
2020-07-29 21:11:28 +00:00
|
|
|
type Invite struct {
|
2021-04-06 20:25:44 +00:00
|
|
|
Created time.Time `json:"created"`
|
|
|
|
NoLimit bool `json:"no-limit"`
|
|
|
|
RemainingUses int `json:"remaining-uses"`
|
|
|
|
ValidTill time.Time `json:"valid_till"`
|
|
|
|
UserExpiry bool `json:"user-duration"`
|
2021-04-08 19:43:01 +00:00
|
|
|
UserMonths int `json:"user-months,omitempty"`
|
2021-04-06 20:25:44 +00:00
|
|
|
UserDays int `json:"user-days,omitempty"`
|
|
|
|
UserHours int `json:"user-hours,omitempty"`
|
|
|
|
UserMinutes int `json:"user-minutes,omitempty"`
|
2021-05-23 15:16:31 +00:00
|
|
|
SendTo string `json:"email"`
|
2021-04-06 20:25:44 +00:00
|
|
|
// Used to be stored as formatted time, now as Unix.
|
2023-06-21 20:14:41 +00:00
|
|
|
UsedBy [][]string `json:"used-by"`
|
|
|
|
Notify map[string]map[string]bool `json:"notify"`
|
|
|
|
Profile string `json:"profile"`
|
|
|
|
Label string `json:"label,omitempty"`
|
|
|
|
Captchas map[string]*captcha.Data // Map of Captcha IDs to answers
|
2020-07-29 21:11:28 +00:00
|
|
|
}
|
|
|
|
|
2021-01-19 00:29:29 +00:00
|
|
|
type Lang struct {
|
2021-05-23 16:31:20 +00:00
|
|
|
AdminPath string
|
|
|
|
chosenAdminLang string
|
|
|
|
Admin adminLangs
|
|
|
|
AdminJSON map[string]string
|
2023-06-16 13:43:37 +00:00
|
|
|
UserPath string
|
|
|
|
chosenUserLang string
|
|
|
|
User userLangs
|
2021-05-23 16:31:20 +00:00
|
|
|
PasswordResetPath string
|
|
|
|
chosenPWRLang string
|
|
|
|
PasswordReset pwrLangs
|
|
|
|
EmailPath string
|
|
|
|
chosenEmailLang string
|
|
|
|
Email emailLangs
|
|
|
|
CommonPath string
|
|
|
|
Common commonLangs
|
|
|
|
SetupPath string
|
|
|
|
Setup setupLangs
|
|
|
|
// Telegram translations are also used for Discord bots (and likely future ones).
|
2021-05-07 00:08:12 +00:00
|
|
|
chosenTelegramLang string
|
|
|
|
TelegramPath string
|
|
|
|
Telegram telegramLangs
|
2021-01-19 00:29:29 +00:00
|
|
|
}
|
2020-07-29 21:11:28 +00:00
|
|
|
|
2021-02-01 17:39:19 +00:00
|
|
|
func (st *Storage) loadLang(filesystems ...fs.FS) (err error) {
|
|
|
|
err = st.loadLangCommon(filesystems...)
|
2021-01-25 21:26:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2021-02-01 17:39:19 +00:00
|
|
|
err = st.loadLangAdmin(filesystems...)
|
2021-01-19 00:29:29 +00:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2023-06-17 16:26:36 +00:00
|
|
|
err = st.loadLangEmail(filesystems...)
|
2021-01-19 00:29:29 +00:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2023-06-17 16:26:36 +00:00
|
|
|
err = st.loadLangUser(filesystems...)
|
2021-03-30 21:41:28 +00:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2023-06-17 16:26:36 +00:00
|
|
|
err = st.loadLangPWR(filesystems...)
|
2021-05-07 00:08:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
err = st.loadLangTelegram(filesystems...)
|
2021-01-19 00:29:29 +00:00
|
|
|
return
|
2020-07-29 21:11:28 +00:00
|
|
|
}
|
|
|
|
|
2021-04-13 17:34:13 +00:00
|
|
|
// The following patch* functions fill in a language with missing values
|
|
|
|
// from a list of other sources in a preferred order.
|
|
|
|
// languages to patch from should be in decreasing priority,
|
|
|
|
// E.g: If to = fr-be, from = [fr-fr, en-us].
|
2023-06-16 17:29:49 +00:00
|
|
|
func (common *commonLangs) patchCommonStrings(to *langSection, from ...string) {
|
2021-04-13 17:34:13 +00:00
|
|
|
if *to == nil {
|
|
|
|
*to = langSection{}
|
|
|
|
}
|
|
|
|
for n, ev := range (*common)[from[len(from)-1]].Strings {
|
|
|
|
if v, ok := (*to)[n]; !ok || v == "" {
|
|
|
|
i := 0
|
|
|
|
for i < len(from)-1 {
|
|
|
|
ev, ok = (*common)[from[i]].Strings[n]
|
|
|
|
if ok && ev != "" {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
(*to)[n] = ev
|
2021-01-25 21:26:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-16 17:29:49 +00:00
|
|
|
func (common *commonLangs) patchCommonNotifications(to *langSection, from ...string) {
|
|
|
|
if *to == nil {
|
|
|
|
*to = langSection{}
|
|
|
|
}
|
|
|
|
for n, ev := range (*common)[from[len(from)-1]].Notifications {
|
|
|
|
if v, ok := (*to)[n]; !ok || v == "" {
|
|
|
|
i := 0
|
|
|
|
for i < len(from)-1 {
|
|
|
|
ev, ok = (*common)[from[i]].Notifications[n]
|
|
|
|
if ok && ev != "" {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
(*to)[n] = ev
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-18 11:27:18 +00:00
|
|
|
func (common *commonLangs) patchCommonQuantityStrings(to *map[string]quantityString, from ...string) {
|
|
|
|
if *to == nil {
|
|
|
|
*to = map[string]quantityString{}
|
|
|
|
}
|
|
|
|
for n, ev := range (*common)[from[len(from)-1]].QuantityStrings {
|
|
|
|
if v, ok := (*to)[n]; !ok || (v.Singular == "" && v.Plural == "") {
|
|
|
|
i := 0
|
|
|
|
for i < len(from)-1 {
|
|
|
|
ev, ok = (*common)[from[i]].QuantityStrings[n]
|
|
|
|
if ok && ev.Singular != "" && ev.Plural != "" {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
(*to)[n] = ev
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-13 17:34:13 +00:00
|
|
|
func patchLang(to *langSection, from ...*langSection) {
|
|
|
|
if *to == nil {
|
|
|
|
*to = langSection{}
|
|
|
|
}
|
|
|
|
for n, ev := range *from[len(from)-1] {
|
|
|
|
if v, ok := (*to)[n]; !ok || v == "" {
|
|
|
|
i := 0
|
|
|
|
for i < len(from)-1 {
|
|
|
|
ev, ok = (*from[i])[n]
|
|
|
|
if ok && ev != "" {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
(*to)[n] = ev
|
2021-01-19 00:29:29 +00:00
|
|
|
}
|
|
|
|
}
|
2020-07-29 21:11:28 +00:00
|
|
|
}
|
|
|
|
|
2021-04-13 17:34:13 +00:00
|
|
|
func patchQuantityStrings(to *map[string]quantityString, from ...*map[string]quantityString) {
|
|
|
|
if *to == nil {
|
|
|
|
*to = map[string]quantityString{}
|
|
|
|
}
|
|
|
|
for n, ev := range *from[len(from)-1] {
|
|
|
|
qs, ok := (*to)[n]
|
|
|
|
if !ok || qs.Singular == "" || qs.Plural == "" {
|
|
|
|
i := 0
|
|
|
|
subOk := false
|
|
|
|
for i < len(from)-1 {
|
|
|
|
ev, subOk = (*from[i])[n]
|
|
|
|
if subOk && ev.Singular != "" && ev.Plural != "" {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
if !ok {
|
|
|
|
(*to)[n] = ev
|
|
|
|
continue
|
|
|
|
} else if qs.Singular == "" {
|
|
|
|
qs.Singular = ev.Singular
|
|
|
|
} else if qs.Plural == "" {
|
|
|
|
qs.Plural = ev.Plural
|
|
|
|
}
|
|
|
|
(*to)[n] = qs
|
2021-01-19 00:29:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-13 17:34:13 +00:00
|
|
|
type loadLangFunc func(fsIndex int, name string) error
|
|
|
|
|
2021-02-01 17:39:19 +00:00
|
|
|
func (st *Storage) loadLangCommon(filesystems ...fs.FS) error {
|
2021-01-25 21:26:54 +00:00
|
|
|
st.lang.Common = map[string]commonLang{}
|
|
|
|
var english commonLang
|
2021-04-13 17:34:13 +00:00
|
|
|
loadedLangs := make([]map[string]bool, len(filesystems))
|
|
|
|
var load loadLangFunc
|
|
|
|
load = func(fsIndex int, fname string) error {
|
|
|
|
filesystem := filesystems[fsIndex]
|
2021-01-25 21:26:54 +00:00
|
|
|
index := strings.TrimSuffix(fname, filepath.Ext(fname))
|
|
|
|
lang := commonLang{}
|
2021-02-08 12:03:22 +00:00
|
|
|
f, err := fs.ReadFile(filesystem, FSJoin(st.lang.CommonPath, fname))
|
2021-01-25 21:26:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if substituteStrings != "" {
|
|
|
|
f = []byte(strings.ReplaceAll(string(f), "Jellyfin", substituteStrings))
|
|
|
|
}
|
|
|
|
err = json.Unmarshal(f, &lang)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if fname != "en-us.json" {
|
2021-04-13 17:34:13 +00:00
|
|
|
if lang.Meta.Fallback != "" {
|
|
|
|
fallback, ok := st.lang.Common[lang.Meta.Fallback]
|
|
|
|
err = nil
|
|
|
|
if !ok {
|
|
|
|
err = load(fsIndex, lang.Meta.Fallback+".json")
|
|
|
|
fallback = st.lang.Common[lang.Meta.Fallback]
|
|
|
|
}
|
|
|
|
if err == nil {
|
|
|
|
loadedLangs[fsIndex][lang.Meta.Fallback+".json"] = true
|
|
|
|
patchLang(&lang.Strings, &fallback.Strings, &english.Strings)
|
2023-06-18 11:27:18 +00:00
|
|
|
patchLang(&lang.Notifications, &fallback.Notifications, &english.Notifications)
|
|
|
|
patchQuantityStrings(&lang.QuantityStrings, &fallback.QuantityStrings, &english.QuantityStrings)
|
2021-04-13 17:34:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (lang.Meta.Fallback != "" && err != nil) || lang.Meta.Fallback == "" {
|
|
|
|
patchLang(&lang.Strings, &english.Strings)
|
2023-06-18 11:27:18 +00:00
|
|
|
patchLang(&lang.Notifications, &english.Notifications)
|
|
|
|
patchQuantityStrings(&lang.QuantityStrings, &english.QuantityStrings)
|
2021-04-13 17:34:13 +00:00
|
|
|
}
|
2021-01-25 21:26:54 +00:00
|
|
|
}
|
|
|
|
st.lang.Common[index] = lang
|
|
|
|
return nil
|
|
|
|
}
|
2021-02-01 17:39:19 +00:00
|
|
|
engFound := false
|
|
|
|
var err error
|
2021-04-13 17:34:13 +00:00
|
|
|
for i := range filesystems {
|
|
|
|
loadedLangs[i] = map[string]bool{}
|
|
|
|
err = load(i, "en-us.json")
|
2021-02-01 17:39:19 +00:00
|
|
|
if err == nil {
|
|
|
|
engFound = true
|
|
|
|
}
|
2021-04-13 17:34:13 +00:00
|
|
|
loadedLangs[i]["en-us.json"] = true
|
2021-01-25 21:26:54 +00:00
|
|
|
}
|
2021-02-01 17:39:19 +00:00
|
|
|
if !engFound {
|
2021-01-25 21:26:54 +00:00
|
|
|
return err
|
|
|
|
}
|
2021-02-01 17:39:19 +00:00
|
|
|
english = st.lang.Common["en-us"]
|
|
|
|
commonLoaded := false
|
2021-04-13 17:34:13 +00:00
|
|
|
for i := range filesystems {
|
|
|
|
files, err := fs.ReadDir(filesystems[i], st.lang.CommonPath)
|
2021-02-01 17:39:19 +00:00
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
for _, f := range files {
|
2021-04-13 17:34:13 +00:00
|
|
|
if !loadedLangs[i][f.Name()] {
|
|
|
|
err = load(i, f.Name())
|
2021-02-01 17:39:19 +00:00
|
|
|
if err == nil {
|
|
|
|
commonLoaded = true
|
2021-04-13 17:34:13 +00:00
|
|
|
loadedLangs[i][f.Name()] = true
|
2021-02-01 17:39:19 +00:00
|
|
|
}
|
2021-01-25 21:26:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-02-01 17:39:19 +00:00
|
|
|
if !commonLoaded {
|
|
|
|
return err
|
|
|
|
}
|
2021-01-25 21:26:54 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-02-01 17:39:19 +00:00
|
|
|
func (st *Storage) loadLangAdmin(filesystems ...fs.FS) error {
|
2021-01-19 00:29:29 +00:00
|
|
|
st.lang.Admin = map[string]adminLang{}
|
|
|
|
var english adminLang
|
2021-04-13 17:34:13 +00:00
|
|
|
loadedLangs := make([]map[string]bool, len(filesystems))
|
|
|
|
var load loadLangFunc
|
|
|
|
load = func(fsIndex int, fname string) error {
|
|
|
|
filesystem := filesystems[fsIndex]
|
2021-01-19 00:29:29 +00:00
|
|
|
index := strings.TrimSuffix(fname, filepath.Ext(fname))
|
|
|
|
lang := adminLang{}
|
2021-02-08 12:03:22 +00:00
|
|
|
f, err := fs.ReadFile(filesystem, FSJoin(st.lang.AdminPath, fname))
|
2021-01-12 00:11:40 +00:00
|
|
|
if err != nil {
|
2021-01-19 00:29:29 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
if substituteStrings != "" {
|
|
|
|
f = []byte(strings.ReplaceAll(string(f), "Jellyfin", substituteStrings))
|
|
|
|
}
|
|
|
|
err = json.Unmarshal(f, &lang)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-06-16 17:29:49 +00:00
|
|
|
st.lang.Common.patchCommonStrings(&lang.Strings, index)
|
|
|
|
st.lang.Common.patchCommonNotifications(&lang.Notifications, index)
|
2021-01-19 00:29:29 +00:00
|
|
|
if fname != "en-us.json" {
|
2021-04-13 17:34:13 +00:00
|
|
|
if lang.Meta.Fallback != "" {
|
|
|
|
fallback, ok := st.lang.Admin[lang.Meta.Fallback]
|
|
|
|
err = nil
|
|
|
|
if !ok {
|
|
|
|
err = load(fsIndex, lang.Meta.Fallback+".json")
|
|
|
|
fallback = st.lang.Admin[lang.Meta.Fallback]
|
|
|
|
}
|
|
|
|
if err == nil {
|
|
|
|
loadedLangs[fsIndex][lang.Meta.Fallback+".json"] = true
|
|
|
|
patchLang(&lang.Strings, &fallback.Strings, &english.Strings)
|
|
|
|
patchLang(&lang.Notifications, &fallback.Notifications, &english.Notifications)
|
|
|
|
patchQuantityStrings(&lang.QuantityStrings, &fallback.QuantityStrings, &english.QuantityStrings)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (lang.Meta.Fallback != "" && err != nil) || lang.Meta.Fallback == "" {
|
|
|
|
patchLang(&lang.Strings, &english.Strings)
|
|
|
|
patchLang(&lang.Notifications, &english.Notifications)
|
|
|
|
patchQuantityStrings(&lang.QuantityStrings, &english.QuantityStrings)
|
|
|
|
}
|
2021-01-19 00:29:29 +00:00
|
|
|
}
|
|
|
|
stringAdmin, err := json.Marshal(lang)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
lang.JSON = string(stringAdmin)
|
|
|
|
st.lang.Admin[index] = lang
|
|
|
|
return nil
|
|
|
|
}
|
2021-02-01 17:39:19 +00:00
|
|
|
engFound := false
|
|
|
|
var err error
|
2021-04-13 17:34:13 +00:00
|
|
|
for i := range filesystems {
|
|
|
|
loadedLangs[i] = map[string]bool{}
|
|
|
|
err = load(i, "en-us.json")
|
2021-02-01 17:39:19 +00:00
|
|
|
if err == nil {
|
|
|
|
engFound = true
|
|
|
|
}
|
2021-04-13 17:34:13 +00:00
|
|
|
loadedLangs[i]["en-us.json"] = true
|
2021-01-19 00:29:29 +00:00
|
|
|
}
|
2021-02-01 17:39:19 +00:00
|
|
|
if !engFound {
|
2021-01-19 00:29:29 +00:00
|
|
|
return err
|
|
|
|
}
|
2021-02-01 17:39:19 +00:00
|
|
|
english = st.lang.Admin["en-us"]
|
|
|
|
adminLoaded := false
|
2021-04-13 17:34:13 +00:00
|
|
|
for i := range filesystems {
|
|
|
|
files, err := fs.ReadDir(filesystems[i], st.lang.AdminPath)
|
2021-02-01 17:39:19 +00:00
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
for _, f := range files {
|
2021-04-13 17:34:13 +00:00
|
|
|
if !loadedLangs[i][f.Name()] {
|
|
|
|
err = load(i, f.Name())
|
2021-02-01 17:39:19 +00:00
|
|
|
if err == nil {
|
|
|
|
adminLoaded = true
|
2021-04-13 17:34:13 +00:00
|
|
|
loadedLangs[i][f.Name()] = true
|
2021-02-01 17:39:19 +00:00
|
|
|
}
|
2021-01-12 00:11:40 +00:00
|
|
|
}
|
2021-01-19 00:29:29 +00:00
|
|
|
}
|
|
|
|
}
|
2021-02-01 17:39:19 +00:00
|
|
|
if !adminLoaded {
|
|
|
|
return err
|
|
|
|
}
|
2021-01-19 00:29:29 +00:00
|
|
|
return nil
|
|
|
|
}
|
2021-01-12 23:15:12 +00:00
|
|
|
|
2023-06-16 13:43:37 +00:00
|
|
|
func (st *Storage) loadLangUser(filesystems ...fs.FS) error {
|
|
|
|
st.lang.User = map[string]userLang{}
|
|
|
|
var english userLang
|
2021-04-13 17:34:13 +00:00
|
|
|
loadedLangs := make([]map[string]bool, len(filesystems))
|
|
|
|
var load loadLangFunc
|
|
|
|
load = func(fsIndex int, fname string) error {
|
|
|
|
filesystem := filesystems[fsIndex]
|
2021-01-19 00:29:29 +00:00
|
|
|
index := strings.TrimSuffix(fname, filepath.Ext(fname))
|
2023-06-16 13:43:37 +00:00
|
|
|
lang := userLang{}
|
|
|
|
f, err := fs.ReadFile(filesystem, FSJoin(st.lang.UserPath, fname))
|
2021-01-19 00:29:29 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if substituteStrings != "" {
|
|
|
|
f = []byte(strings.ReplaceAll(string(f), "Jellyfin", substituteStrings))
|
2021-01-12 00:11:40 +00:00
|
|
|
}
|
2021-01-19 00:29:29 +00:00
|
|
|
err = json.Unmarshal(f, &lang)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-06-16 17:29:49 +00:00
|
|
|
st.lang.Common.patchCommonStrings(&lang.Strings, index)
|
|
|
|
st.lang.Common.patchCommonNotifications(&lang.Notifications, index)
|
2023-06-18 11:27:18 +00:00
|
|
|
st.lang.Common.patchCommonQuantityStrings(&lang.QuantityStrings, index)
|
2023-06-17 16:26:36 +00:00
|
|
|
// turns out, a lot of email strings are useful on the user page.
|
|
|
|
emailLang := []langSection{st.lang.Email[index].WelcomeEmail, st.lang.Email[index].UserDisabled, st.lang.Email[index].UserExpired}
|
|
|
|
for _, v := range emailLang {
|
|
|
|
patchLang(&lang.Strings, &v)
|
|
|
|
}
|
2021-01-19 00:29:29 +00:00
|
|
|
if fname != "en-us.json" {
|
2021-04-13 17:34:13 +00:00
|
|
|
if lang.Meta.Fallback != "" {
|
2023-06-16 13:43:37 +00:00
|
|
|
fallback, ok := st.lang.User[lang.Meta.Fallback]
|
2021-04-13 17:34:13 +00:00
|
|
|
err = nil
|
|
|
|
if !ok {
|
|
|
|
err = load(fsIndex, lang.Meta.Fallback+".json")
|
2023-06-16 13:43:37 +00:00
|
|
|
fallback = st.lang.User[lang.Meta.Fallback]
|
2021-04-13 17:34:13 +00:00
|
|
|
}
|
|
|
|
if err == nil {
|
|
|
|
loadedLangs[fsIndex][lang.Meta.Fallback+".json"] = true
|
|
|
|
patchLang(&lang.Strings, &fallback.Strings, &english.Strings)
|
|
|
|
patchLang(&lang.Notifications, &fallback.Notifications, &english.Notifications)
|
|
|
|
patchQuantityStrings(&lang.ValidationStrings, &fallback.ValidationStrings, &english.ValidationStrings)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (lang.Meta.Fallback != "" && err != nil) || lang.Meta.Fallback == "" {
|
|
|
|
patchLang(&lang.Strings, &english.Strings)
|
|
|
|
patchLang(&lang.Notifications, &english.Notifications)
|
|
|
|
patchQuantityStrings(&lang.ValidationStrings, &english.ValidationStrings)
|
|
|
|
}
|
2021-01-19 00:29:29 +00:00
|
|
|
}
|
2021-01-25 18:01:18 +00:00
|
|
|
notifications, err := json.Marshal(lang.Notifications)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-01-19 00:29:29 +00:00
|
|
|
validationStrings, err := json.Marshal(lang.ValidationStrings)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-06-16 17:29:49 +00:00
|
|
|
userJSON, err := json.Marshal(lang)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-01-25 18:01:18 +00:00
|
|
|
lang.notificationsJSON = string(notifications)
|
2021-01-19 00:29:29 +00:00
|
|
|
lang.validationStringsJSON = string(validationStrings)
|
2023-06-16 17:29:49 +00:00
|
|
|
lang.JSON = string(userJSON)
|
2023-06-16 13:43:37 +00:00
|
|
|
st.lang.User[index] = lang
|
2021-01-19 00:29:29 +00:00
|
|
|
return nil
|
2021-01-12 00:11:40 +00:00
|
|
|
}
|
2021-02-01 17:39:19 +00:00
|
|
|
engFound := false
|
|
|
|
var err error
|
2021-04-13 17:34:13 +00:00
|
|
|
for i := range filesystems {
|
|
|
|
loadedLangs[i] = map[string]bool{}
|
|
|
|
err = load(i, "en-us.json")
|
2021-02-01 17:39:19 +00:00
|
|
|
if err == nil {
|
|
|
|
engFound = true
|
|
|
|
}
|
2021-04-13 17:34:13 +00:00
|
|
|
loadedLangs[i]["en-us.json"] = true
|
2020-10-30 22:51:47 +00:00
|
|
|
}
|
2021-02-01 17:39:19 +00:00
|
|
|
if !engFound {
|
2021-01-19 00:29:29 +00:00
|
|
|
return err
|
|
|
|
}
|
2023-06-16 13:43:37 +00:00
|
|
|
english = st.lang.User["en-us"]
|
|
|
|
userLoaded := false
|
2021-04-13 17:34:13 +00:00
|
|
|
for i := range filesystems {
|
2023-06-16 13:43:37 +00:00
|
|
|
files, err := fs.ReadDir(filesystems[i], st.lang.UserPath)
|
2021-02-01 17:39:19 +00:00
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
for _, f := range files {
|
2021-04-13 17:34:13 +00:00
|
|
|
if !loadedLangs[i][f.Name()] {
|
|
|
|
err = load(i, f.Name())
|
2021-02-01 17:39:19 +00:00
|
|
|
if err == nil {
|
2023-06-16 13:43:37 +00:00
|
|
|
userLoaded = true
|
2021-04-13 17:34:13 +00:00
|
|
|
loadedLangs[i][f.Name()] = true
|
2021-02-01 17:39:19 +00:00
|
|
|
}
|
2021-01-19 00:29:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-06-16 13:43:37 +00:00
|
|
|
if !userLoaded {
|
2021-02-01 17:39:19 +00:00
|
|
|
return err
|
|
|
|
}
|
2021-01-19 00:29:29 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-03-30 21:41:28 +00:00
|
|
|
func (st *Storage) loadLangPWR(filesystems ...fs.FS) error {
|
|
|
|
st.lang.PasswordReset = map[string]pwrLang{}
|
|
|
|
var english pwrLang
|
2021-04-13 17:34:13 +00:00
|
|
|
loadedLangs := make([]map[string]bool, len(filesystems))
|
|
|
|
var load loadLangFunc
|
|
|
|
load = func(fsIndex int, fname string) error {
|
|
|
|
filesystem := filesystems[fsIndex]
|
2021-03-30 21:41:28 +00:00
|
|
|
index := strings.TrimSuffix(fname, filepath.Ext(fname))
|
|
|
|
lang := pwrLang{}
|
|
|
|
f, err := fs.ReadFile(filesystem, FSJoin(st.lang.PasswordResetPath, fname))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if substituteStrings != "" {
|
|
|
|
f = []byte(strings.ReplaceAll(string(f), "Jellyfin", substituteStrings))
|
|
|
|
}
|
|
|
|
err = json.Unmarshal(f, &lang)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-06-16 17:29:49 +00:00
|
|
|
st.lang.Common.patchCommonStrings(&lang.Strings, index)
|
2021-03-30 21:41:28 +00:00
|
|
|
if fname != "en-us.json" {
|
2021-04-13 17:34:13 +00:00
|
|
|
if lang.Meta.Fallback != "" {
|
|
|
|
fallback, ok := st.lang.PasswordReset[lang.Meta.Fallback]
|
|
|
|
err = nil
|
|
|
|
if !ok {
|
|
|
|
err = load(fsIndex, lang.Meta.Fallback+".json")
|
|
|
|
fallback = st.lang.PasswordReset[lang.Meta.Fallback]
|
|
|
|
}
|
|
|
|
if err == nil {
|
|
|
|
patchLang(&lang.Strings, &fallback.Strings, &english.Strings)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (lang.Meta.Fallback != "" && err != nil) || lang.Meta.Fallback == "" {
|
|
|
|
patchLang(&lang.Strings, &english.Strings)
|
|
|
|
}
|
2021-03-30 21:41:28 +00:00
|
|
|
}
|
|
|
|
st.lang.PasswordReset[index] = lang
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
engFound := false
|
|
|
|
var err error
|
2021-04-13 17:34:13 +00:00
|
|
|
for i := range filesystems {
|
|
|
|
loadedLangs[i] = map[string]bool{}
|
|
|
|
err = load(i, "en-us.json")
|
2021-03-30 21:41:28 +00:00
|
|
|
if err == nil {
|
|
|
|
engFound = true
|
|
|
|
}
|
2021-04-13 17:34:13 +00:00
|
|
|
loadedLangs[i]["en-us.json"] = true
|
2021-03-30 21:41:28 +00:00
|
|
|
}
|
|
|
|
if !engFound {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
english = st.lang.PasswordReset["en-us"]
|
2023-06-16 13:43:37 +00:00
|
|
|
userLoaded := false
|
2021-04-13 17:34:13 +00:00
|
|
|
for i := range filesystems {
|
|
|
|
files, err := fs.ReadDir(filesystems[i], st.lang.PasswordResetPath)
|
2021-03-30 21:41:28 +00:00
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
for _, f := range files {
|
2021-04-13 17:34:13 +00:00
|
|
|
if !loadedLangs[i][f.Name()] {
|
|
|
|
err = load(i, f.Name())
|
2021-03-30 21:41:28 +00:00
|
|
|
if err == nil {
|
2023-06-16 13:43:37 +00:00
|
|
|
userLoaded = true
|
2021-04-13 17:34:13 +00:00
|
|
|
loadedLangs[i][f.Name()] = true
|
2021-03-30 21:41:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-06-16 13:43:37 +00:00
|
|
|
if !userLoaded {
|
2021-03-30 21:41:28 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-02-01 17:39:19 +00:00
|
|
|
func (st *Storage) loadLangEmail(filesystems ...fs.FS) error {
|
2021-01-19 00:29:29 +00:00
|
|
|
st.lang.Email = map[string]emailLang{}
|
|
|
|
var english emailLang
|
2021-04-13 17:34:13 +00:00
|
|
|
loadedLangs := make([]map[string]bool, len(filesystems))
|
|
|
|
var load loadLangFunc
|
|
|
|
load = func(fsIndex int, fname string) error {
|
|
|
|
filesystem := filesystems[fsIndex]
|
2021-01-19 00:29:29 +00:00
|
|
|
index := strings.TrimSuffix(fname, filepath.Ext(fname))
|
|
|
|
lang := emailLang{}
|
2021-02-08 12:03:22 +00:00
|
|
|
f, err := fs.ReadFile(filesystem, FSJoin(st.lang.EmailPath, fname))
|
2021-01-11 19:17:43 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-01-19 00:29:29 +00:00
|
|
|
if substituteStrings != "" {
|
|
|
|
f = []byte(strings.ReplaceAll(string(f), "Jellyfin", substituteStrings))
|
|
|
|
}
|
|
|
|
err = json.Unmarshal(f, &lang)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-06-16 17:29:49 +00:00
|
|
|
st.lang.Common.patchCommonStrings(&lang.Strings, index)
|
2021-01-19 00:29:29 +00:00
|
|
|
if fname != "en-us.json" {
|
2021-04-13 17:34:13 +00:00
|
|
|
if lang.Meta.Fallback != "" {
|
|
|
|
fallback, ok := st.lang.Email[lang.Meta.Fallback]
|
|
|
|
err = nil
|
|
|
|
if !ok {
|
|
|
|
err = load(fsIndex, lang.Meta.Fallback+".json")
|
|
|
|
fallback = st.lang.Email[lang.Meta.Fallback]
|
|
|
|
}
|
|
|
|
if err == nil {
|
|
|
|
loadedLangs[fsIndex][lang.Meta.Fallback+".json"] = true
|
|
|
|
patchLang(&lang.UserCreated, &fallback.UserCreated, &english.UserCreated)
|
|
|
|
patchLang(&lang.InviteExpiry, &fallback.InviteExpiry, &english.InviteExpiry)
|
|
|
|
patchLang(&lang.PasswordReset, &fallback.PasswordReset, &english.PasswordReset)
|
|
|
|
patchLang(&lang.UserDeleted, &fallback.UserDeleted, &english.UserDeleted)
|
|
|
|
patchLang(&lang.UserDisabled, &fallback.UserDisabled, &english.UserDisabled)
|
|
|
|
patchLang(&lang.UserEnabled, &fallback.UserEnabled, &english.UserEnabled)
|
|
|
|
patchLang(&lang.InviteEmail, &fallback.InviteEmail, &english.InviteEmail)
|
|
|
|
patchLang(&lang.WelcomeEmail, &fallback.WelcomeEmail, &english.WelcomeEmail)
|
|
|
|
patchLang(&lang.EmailConfirmation, &fallback.EmailConfirmation, &english.EmailConfirmation)
|
|
|
|
patchLang(&lang.UserExpired, &fallback.UserExpired, &english.UserExpired)
|
|
|
|
patchLang(&lang.Strings, &fallback.Strings, &english.Strings)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (lang.Meta.Fallback != "" && err != nil) || lang.Meta.Fallback == "" {
|
|
|
|
patchLang(&lang.UserCreated, &english.UserCreated)
|
|
|
|
patchLang(&lang.InviteExpiry, &english.InviteExpiry)
|
|
|
|
patchLang(&lang.PasswordReset, &english.PasswordReset)
|
|
|
|
patchLang(&lang.UserDeleted, &english.UserDeleted)
|
|
|
|
patchLang(&lang.UserDisabled, &english.UserDisabled)
|
|
|
|
patchLang(&lang.UserEnabled, &english.UserEnabled)
|
|
|
|
patchLang(&lang.InviteEmail, &english.InviteEmail)
|
|
|
|
patchLang(&lang.WelcomeEmail, &english.WelcomeEmail)
|
|
|
|
patchLang(&lang.EmailConfirmation, &english.EmailConfirmation)
|
|
|
|
patchLang(&lang.UserExpired, &english.UserExpired)
|
|
|
|
patchLang(&lang.Strings, &english.Strings)
|
|
|
|
}
|
2021-01-19 00:29:29 +00:00
|
|
|
}
|
|
|
|
st.lang.Email[index] = lang
|
|
|
|
return nil
|
2020-10-30 22:51:47 +00:00
|
|
|
}
|
2021-02-01 17:39:19 +00:00
|
|
|
engFound := false
|
|
|
|
var err error
|
2021-04-13 17:34:13 +00:00
|
|
|
for i := range filesystems {
|
|
|
|
loadedLangs[i] = map[string]bool{}
|
|
|
|
err = load(i, "en-us.json")
|
2021-02-01 17:39:19 +00:00
|
|
|
if err == nil {
|
|
|
|
engFound = true
|
|
|
|
}
|
2021-04-13 17:34:13 +00:00
|
|
|
loadedLangs[i]["en-us.json"] = true
|
2021-01-19 00:29:29 +00:00
|
|
|
}
|
2021-02-01 17:39:19 +00:00
|
|
|
if !engFound {
|
2021-01-19 00:29:29 +00:00
|
|
|
return err
|
|
|
|
}
|
2021-02-01 17:39:19 +00:00
|
|
|
english = st.lang.Email["en-us"]
|
|
|
|
emailLoaded := false
|
2021-04-13 17:34:13 +00:00
|
|
|
for i := range filesystems {
|
|
|
|
files, err := fs.ReadDir(filesystems[i], st.lang.EmailPath)
|
2021-02-01 17:39:19 +00:00
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
for _, f := range files {
|
2021-04-13 17:34:13 +00:00
|
|
|
if !loadedLangs[i][f.Name()] {
|
|
|
|
err = load(i, f.Name())
|
2021-02-01 17:39:19 +00:00
|
|
|
if err == nil {
|
|
|
|
emailLoaded = true
|
2021-04-13 17:34:13 +00:00
|
|
|
loadedLangs[i][f.Name()] = true
|
2021-02-01 17:39:19 +00:00
|
|
|
}
|
2021-01-19 00:29:29 +00:00
|
|
|
}
|
2021-01-14 17:51:12 +00:00
|
|
|
}
|
|
|
|
}
|
2021-02-01 17:39:19 +00:00
|
|
|
if !emailLoaded {
|
|
|
|
return err
|
|
|
|
}
|
2021-01-19 00:29:29 +00:00
|
|
|
return nil
|
2020-10-30 22:51:47 +00:00
|
|
|
}
|
|
|
|
|
2021-05-07 00:08:12 +00:00
|
|
|
func (st *Storage) loadLangTelegram(filesystems ...fs.FS) error {
|
|
|
|
st.lang.Telegram = map[string]telegramLang{}
|
|
|
|
var english telegramLang
|
|
|
|
loadedLangs := make([]map[string]bool, len(filesystems))
|
|
|
|
var load loadLangFunc
|
|
|
|
load = func(fsIndex int, fname string) error {
|
|
|
|
filesystem := filesystems[fsIndex]
|
|
|
|
index := strings.TrimSuffix(fname, filepath.Ext(fname))
|
|
|
|
lang := telegramLang{}
|
|
|
|
f, err := fs.ReadFile(filesystem, FSJoin(st.lang.TelegramPath, fname))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if substituteStrings != "" {
|
|
|
|
f = []byte(strings.ReplaceAll(string(f), "Jellyfin", substituteStrings))
|
|
|
|
}
|
|
|
|
err = json.Unmarshal(f, &lang)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-06-16 17:29:49 +00:00
|
|
|
st.lang.Common.patchCommonStrings(&lang.Strings, index)
|
2021-05-07 00:08:12 +00:00
|
|
|
if fname != "en-us.json" {
|
|
|
|
if lang.Meta.Fallback != "" {
|
|
|
|
fallback, ok := st.lang.Telegram[lang.Meta.Fallback]
|
|
|
|
err = nil
|
|
|
|
if !ok {
|
|
|
|
err = load(fsIndex, lang.Meta.Fallback+".json")
|
|
|
|
fallback = st.lang.Telegram[lang.Meta.Fallback]
|
|
|
|
}
|
|
|
|
if err == nil {
|
|
|
|
loadedLangs[fsIndex][lang.Meta.Fallback+".json"] = true
|
|
|
|
patchLang(&lang.Strings, &fallback.Strings, &english.Strings)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (lang.Meta.Fallback != "" && err != nil) || lang.Meta.Fallback == "" {
|
|
|
|
patchLang(&lang.Strings, &english.Strings)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
st.lang.Telegram[index] = lang
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
engFound := false
|
|
|
|
var err error
|
|
|
|
for i := range filesystems {
|
|
|
|
loadedLangs[i] = map[string]bool{}
|
|
|
|
err = load(i, "en-us.json")
|
|
|
|
if err == nil {
|
|
|
|
engFound = true
|
|
|
|
}
|
|
|
|
loadedLangs[i]["en-us.json"] = true
|
|
|
|
}
|
|
|
|
if !engFound {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
english = st.lang.Telegram["en-us"]
|
|
|
|
telegramLoaded := false
|
|
|
|
for i := range filesystems {
|
|
|
|
files, err := fs.ReadDir(filesystems[i], st.lang.TelegramPath)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
for _, f := range files {
|
|
|
|
if !loadedLangs[i][f.Name()] {
|
|
|
|
err = load(i, f.Name())
|
|
|
|
if err == nil {
|
|
|
|
telegramLoaded = true
|
|
|
|
loadedLangs[i][f.Name()] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !telegramLoaded {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-01-19 00:29:29 +00:00
|
|
|
type Invites map[string]Invite
|
|
|
|
|
|
|
|
func (st *Storage) loadInvites() error {
|
|
|
|
return loadJSON(st.invite_path, &st.invites)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) storeInvites() error {
|
|
|
|
return storeJSON(st.invite_path, st.invites)
|
|
|
|
}
|
|
|
|
|
2021-02-28 15:41:06 +00:00
|
|
|
func (st *Storage) loadUsers() error {
|
2021-04-06 17:02:15 +00:00
|
|
|
st.usersLock.Lock()
|
|
|
|
defer st.usersLock.Unlock()
|
2021-04-06 13:00:32 +00:00
|
|
|
if st.users == nil {
|
|
|
|
st.users = map[string]time.Time{}
|
|
|
|
}
|
2021-04-06 12:53:07 +00:00
|
|
|
temp := map[string]time.Time{}
|
|
|
|
err := loadJSON(st.users_path, &temp)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for id, t1 := range temp {
|
|
|
|
if _, ok := st.users[id]; !ok {
|
|
|
|
st.users[id] = t1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
2021-02-28 15:41:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) storeUsers() error {
|
|
|
|
return storeJSON(st.users_path, st.users)
|
|
|
|
}
|
|
|
|
|
2020-07-29 21:11:28 +00:00
|
|
|
func (st *Storage) loadEmails() error {
|
|
|
|
return loadJSON(st.emails_path, &st.emails)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) storeEmails() error {
|
|
|
|
return storeJSON(st.emails_path, st.emails)
|
|
|
|
}
|
|
|
|
|
2021-05-07 00:08:12 +00:00
|
|
|
func (st *Storage) loadTelegramUsers() error {
|
|
|
|
return loadJSON(st.telegram_path, &st.telegram)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) storeTelegramUsers() error {
|
|
|
|
return storeJSON(st.telegram_path, st.telegram)
|
|
|
|
}
|
|
|
|
|
2021-05-17 22:42:33 +00:00
|
|
|
func (st *Storage) loadDiscordUsers() error {
|
|
|
|
return loadJSON(st.discord_path, &st.discord)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) storeDiscordUsers() error {
|
|
|
|
return storeJSON(st.discord_path, st.discord)
|
|
|
|
}
|
|
|
|
|
2021-05-29 16:43:11 +00:00
|
|
|
func (st *Storage) loadMatrixUsers() error {
|
|
|
|
return loadJSON(st.matrix_path, &st.matrix)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) storeMatrixUsers() error {
|
|
|
|
return storeJSON(st.matrix_path, st.matrix)
|
|
|
|
}
|
|
|
|
|
2021-02-19 21:38:20 +00:00
|
|
|
func (st *Storage) loadCustomEmails() error {
|
|
|
|
return loadJSON(st.customEmails_path, &st.customEmails)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) storeCustomEmails() error {
|
|
|
|
return storeJSON(st.customEmails_path, st.customEmails)
|
|
|
|
}
|
|
|
|
|
2023-06-20 20:43:25 +00:00
|
|
|
func (st *Storage) loadUserPageContent() error {
|
|
|
|
return loadJSON(st.userPage_path, &st.userPage)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) storeUserPageContent() error {
|
|
|
|
return storeJSON(st.userPage_path, st.userPage)
|
|
|
|
}
|
|
|
|
|
2020-07-29 21:11:28 +00:00
|
|
|
func (st *Storage) loadPolicy() error {
|
|
|
|
return loadJSON(st.policy_path, &st.policy)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) storePolicy() error {
|
|
|
|
return storeJSON(st.policy_path, st.policy)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) loadConfiguration() error {
|
|
|
|
return loadJSON(st.configuration_path, &st.configuration)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) storeConfiguration() error {
|
|
|
|
return storeJSON(st.configuration_path, st.configuration)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) loadDisplayprefs() error {
|
|
|
|
return loadJSON(st.displayprefs_path, &st.displayprefs)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) storeDisplayprefs() error {
|
|
|
|
return storeJSON(st.displayprefs_path, st.displayprefs)
|
|
|
|
}
|
|
|
|
|
2020-09-05 16:32:49 +00:00
|
|
|
func (st *Storage) loadOmbiTemplate() error {
|
|
|
|
return loadJSON(st.ombi_path, &st.ombi_template)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) storeOmbiTemplate() error {
|
|
|
|
return storeJSON(st.ombi_path, st.ombi_template)
|
|
|
|
}
|
|
|
|
|
2021-07-10 15:43:27 +00:00
|
|
|
func (st *Storage) loadAnnouncements() error {
|
|
|
|
return loadJSON(st.announcements_path, &st.announcements)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) storeAnnouncements() error {
|
|
|
|
return storeJSON(st.announcements_path, st.announcements)
|
|
|
|
}
|
|
|
|
|
2020-09-20 10:21:04 +00:00
|
|
|
func (st *Storage) loadProfiles() error {
|
2020-09-22 23:01:07 +00:00
|
|
|
err := loadJSON(st.profiles_path, &st.profiles)
|
|
|
|
for name, profile := range st.profiles {
|
2020-09-23 17:48:00 +00:00
|
|
|
if profile.Default {
|
|
|
|
st.defaultProfile = name
|
|
|
|
}
|
2020-09-22 23:01:07 +00:00
|
|
|
change := false
|
2021-02-19 00:47:01 +00:00
|
|
|
if profile.Policy.IsAdministrator != profile.Admin {
|
2020-09-22 23:01:07 +00:00
|
|
|
change = true
|
|
|
|
}
|
2021-02-19 00:47:01 +00:00
|
|
|
profile.Admin = profile.Policy.IsAdministrator
|
|
|
|
if profile.Policy.EnabledFolders != nil {
|
|
|
|
length := len(profile.Policy.EnabledFolders)
|
2020-09-22 23:01:07 +00:00
|
|
|
if length == 0 {
|
|
|
|
profile.LibraryAccess = "All"
|
|
|
|
} else {
|
|
|
|
profile.LibraryAccess = strconv.Itoa(length)
|
|
|
|
}
|
|
|
|
change = true
|
|
|
|
}
|
|
|
|
if profile.FromUser == "" {
|
|
|
|
profile.FromUser = "Unknown"
|
|
|
|
change = true
|
|
|
|
}
|
|
|
|
if change {
|
|
|
|
st.profiles[name] = profile
|
|
|
|
}
|
|
|
|
}
|
2020-09-23 17:48:00 +00:00
|
|
|
if st.defaultProfile == "" {
|
|
|
|
for n := range st.profiles {
|
|
|
|
st.defaultProfile = n
|
|
|
|
}
|
2020-09-22 23:01:07 +00:00
|
|
|
}
|
|
|
|
return err
|
2020-09-20 10:21:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) storeProfiles() error {
|
|
|
|
return storeJSON(st.profiles_path, st.profiles)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) migrateToProfile() error {
|
|
|
|
st.loadPolicy()
|
|
|
|
st.loadConfiguration()
|
|
|
|
st.loadDisplayprefs()
|
|
|
|
st.loadProfiles()
|
|
|
|
st.profiles["Default"] = Profile{
|
|
|
|
Policy: st.policy,
|
|
|
|
Configuration: st.configuration,
|
|
|
|
Displayprefs: st.displayprefs,
|
|
|
|
}
|
|
|
|
return st.storeProfiles()
|
|
|
|
}
|
|
|
|
|
2020-07-29 21:11:28 +00:00
|
|
|
func loadJSON(path string, obj interface{}) error {
|
2020-08-01 20:20:02 +00:00
|
|
|
var file []byte
|
|
|
|
var err error
|
2021-01-31 23:12:50 +00:00
|
|
|
file, err = os.ReadFile(path)
|
2020-07-29 21:11:28 +00:00
|
|
|
if err != nil {
|
2020-08-01 20:20:02 +00:00
|
|
|
file = []byte("{}")
|
2020-07-29 21:11:28 +00:00
|
|
|
}
|
|
|
|
err = json.Unmarshal(file, &obj)
|
2020-11-02 23:20:06 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("ERROR: Failed to read \"%s\": %s", path, err)
|
|
|
|
}
|
2020-07-29 21:11:28 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func storeJSON(path string, obj interface{}) error {
|
|
|
|
data, err := json.Marshal(obj)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-01-31 23:12:50 +00:00
|
|
|
err = os.WriteFile(path, data, 0644)
|
2020-11-02 23:20:06 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("ERROR: Failed to write to \"%s\": %s", path, err)
|
|
|
|
}
|
2020-07-29 21:11:28 +00:00
|
|
|
return err
|
|
|
|
}
|