mirror of
https://github.com/hrfee/jfa-go.git
synced 2025-01-01 05:50:12 +00:00
Compare commits
39 Commits
cebde9d4c0
...
4c3e310634
Author | SHA1 | Date | |
---|---|---|---|
4c3e310634 | |||
8b52330304 | |||
200dc1c91a | |||
|
531d4aaefc | ||
|
f8d98fb66f | ||
|
5b2472853b | ||
|
2dc234a94d | ||
|
fdb43b2c53 | ||
|
2161d1aa2f | ||
|
08c8534d39 | ||
|
9e7914e0b6 | ||
|
daebfec0a2 | ||
|
e81411cb9b | ||
|
6cdae16752 | ||
|
65a0c6cb23 | ||
|
36d0550bf9 | ||
|
92aee997da | ||
|
63e6f3a3fa | ||
|
bfafbad9dc | ||
|
4435fead5a | ||
|
e73715f30e | ||
|
f31e9c0d81 | ||
|
2d1a4737db | ||
|
91f89793da | ||
|
d201644a5b | ||
|
982dd001ef | ||
|
80bd4d134e | ||
|
92fda348cd | ||
|
ef3fdb7555 | ||
|
9eb30ffab3 | ||
|
0bf6c25a14 | ||
|
5bb7a7d30c | ||
|
85c9319dfd | ||
|
3fea051691 | ||
|
7826fdffeb | ||
|
1105605370 | ||
|
3de3dd426b | ||
|
0c7187d53f | ||
12db53d1eb |
@ -39,7 +39,7 @@ before:
|
||||
- rm data/bundle.css
|
||||
- npx tailwindcss -i data/web/css/bundle.css -o data/web/css/bundle.css
|
||||
- mv data/crash.html data/html/
|
||||
- go get -u github.com/swaggo/swag/cmd/swag
|
||||
- go install github.com/swaggo/swag/cmd/swag@latest
|
||||
- swag init -g main.go
|
||||
- mv data/web/css/bundle.css data/web/css/{{.Env.JFA_GO_CSS_VERSION}}bundle.css
|
||||
builds:
|
||||
|
2
Makefile
2
Makefile
@ -111,7 +111,7 @@ typescript:
|
||||
$(COPYTS)
|
||||
|
||||
swagger:
|
||||
$(GOBINARY) install github.com/swaggo/swag/cmd/swag
|
||||
$(GOBINARY) install github.com/swaggo/swag/cmd/swag@latest
|
||||
swag init -g main.go
|
||||
|
||||
compile:
|
||||
|
432
api-invites.go
Normal file
432
api-invites.go
Normal file
@ -0,0 +1,432 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/itchyny/timefmt-go"
|
||||
"github.com/lithammer/shortuuid/v3"
|
||||
)
|
||||
|
||||
func (app *appContext) checkInvites() {
|
||||
currentTime := time.Now()
|
||||
app.storage.loadInvites()
|
||||
changed := false
|
||||
for code, data := range app.storage.invites {
|
||||
expiry := data.ValidTill
|
||||
if !currentTime.After(expiry) {
|
||||
continue
|
||||
}
|
||||
app.debug.Printf("Housekeeping: Deleting old invite %s", code)
|
||||
notify := data.Notify
|
||||
if emailEnabled && app.config.Section("notifications").Key("enabled").MustBool(false) && len(notify) != 0 {
|
||||
app.debug.Printf("%s: Expiry notification", code)
|
||||
var wait sync.WaitGroup
|
||||
for address, settings := range notify {
|
||||
if !settings["notify-expiry"] {
|
||||
continue
|
||||
}
|
||||
wait.Add(1)
|
||||
go func(addr string) {
|
||||
defer wait.Done()
|
||||
msg, err := app.email.constructExpiry(code, data, app, false)
|
||||
if err != nil {
|
||||
app.err.Printf("%s: Failed to construct expiry notification: %v", code, err)
|
||||
} else {
|
||||
// Check whether notify "address" is an email address of Jellyfin ID
|
||||
if strings.Contains(addr, "@") {
|
||||
err = app.email.send(msg, addr)
|
||||
} else {
|
||||
err = app.sendByID(msg, addr)
|
||||
}
|
||||
if err != nil {
|
||||
app.err.Printf("%s: Failed to send expiry notification: %v", code, err)
|
||||
} else {
|
||||
app.info.Printf("Sent expiry notification to %s", addr)
|
||||
}
|
||||
}
|
||||
}(address)
|
||||
}
|
||||
wait.Wait()
|
||||
}
|
||||
changed = true
|
||||
delete(app.storage.invites, code)
|
||||
}
|
||||
if changed {
|
||||
app.storage.storeInvites()
|
||||
}
|
||||
}
|
||||
|
||||
func (app *appContext) checkInvite(code string, used bool, username string) bool {
|
||||
currentTime := time.Now()
|
||||
app.storage.loadInvites()
|
||||
changed := false
|
||||
inv, match := app.storage.invites[code]
|
||||
if !match {
|
||||
return false
|
||||
}
|
||||
expiry := inv.ValidTill
|
||||
if currentTime.After(expiry) {
|
||||
app.debug.Printf("Housekeeping: Deleting old invite %s", code)
|
||||
notify := inv.Notify
|
||||
if emailEnabled && app.config.Section("notifications").Key("enabled").MustBool(false) && len(notify) != 0 {
|
||||
app.debug.Printf("%s: Expiry notification", code)
|
||||
var wait sync.WaitGroup
|
||||
for address, settings := range notify {
|
||||
if !settings["notify-expiry"] {
|
||||
continue
|
||||
}
|
||||
wait.Add(1)
|
||||
go func(addr string) {
|
||||
defer wait.Done()
|
||||
msg, err := app.email.constructExpiry(code, inv, app, false)
|
||||
if err != nil {
|
||||
app.err.Printf("%s: Failed to construct expiry notification: %v", code, err)
|
||||
} else {
|
||||
// Check whether notify "address" is an email address of Jellyfin ID
|
||||
if strings.Contains(addr, "@") {
|
||||
err = app.email.send(msg, addr)
|
||||
} else {
|
||||
err = app.sendByID(msg, addr)
|
||||
}
|
||||
if err != nil {
|
||||
app.err.Printf("%s: Failed to send expiry notification: %v", code, err)
|
||||
} else {
|
||||
app.info.Printf("Sent expiry notification to %s", addr)
|
||||
}
|
||||
}
|
||||
}(address)
|
||||
}
|
||||
wait.Wait()
|
||||
}
|
||||
changed = true
|
||||
match = false
|
||||
delete(app.storage.invites, code)
|
||||
} else if used {
|
||||
changed = true
|
||||
del := false
|
||||
newInv := inv
|
||||
if newInv.RemainingUses == 1 {
|
||||
del = true
|
||||
delete(app.storage.invites, code)
|
||||
} else if newInv.RemainingUses != 0 {
|
||||
// 0 means infinite i guess?
|
||||
newInv.RemainingUses--
|
||||
}
|
||||
newInv.UsedBy = append(newInv.UsedBy, []string{username, strconv.FormatInt(currentTime.Unix(), 10)})
|
||||
if !del {
|
||||
app.storage.invites[code] = newInv
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
app.storage.storeInvites()
|
||||
}
|
||||
return match
|
||||
}
|
||||
|
||||
// @Summary Create a new invite.
|
||||
// @Produce json
|
||||
// @Param generateInviteDTO body generateInviteDTO true "New invite request object"
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Router /invites [post]
|
||||
// @Security Bearer
|
||||
// @tags Invites
|
||||
func (app *appContext) GenerateInvite(gc *gin.Context) {
|
||||
var req generateInviteDTO
|
||||
app.debug.Println("Generating new invite")
|
||||
app.storage.loadInvites()
|
||||
gc.BindJSON(&req)
|
||||
currentTime := time.Now()
|
||||
validTill := currentTime.AddDate(0, req.Months, req.Days)
|
||||
validTill = validTill.Add(time.Hour*time.Duration(req.Hours) + time.Minute*time.Duration(req.Minutes))
|
||||
// make sure code doesn't begin with number
|
||||
inviteCode := shortuuid.New()
|
||||
_, err := strconv.Atoi(string(inviteCode[0]))
|
||||
for err == nil {
|
||||
inviteCode = shortuuid.New()
|
||||
_, err = strconv.Atoi(string(inviteCode[0]))
|
||||
}
|
||||
var invite Invite
|
||||
if req.Label != "" {
|
||||
invite.Label = req.Label
|
||||
}
|
||||
invite.Created = currentTime
|
||||
if req.MultipleUses {
|
||||
if req.NoLimit {
|
||||
invite.NoLimit = true
|
||||
} else {
|
||||
invite.RemainingUses = req.RemainingUses
|
||||
}
|
||||
} else {
|
||||
invite.RemainingUses = 1
|
||||
}
|
||||
invite.UserExpiry = req.UserExpiry
|
||||
if invite.UserExpiry {
|
||||
invite.UserMonths = req.UserMonths
|
||||
invite.UserDays = req.UserDays
|
||||
invite.UserHours = req.UserHours
|
||||
invite.UserMinutes = req.UserMinutes
|
||||
}
|
||||
invite.ValidTill = validTill
|
||||
if req.SendTo != "" && app.config.Section("invite_emails").Key("enabled").MustBool(false) {
|
||||
addressValid := false
|
||||
discord := ""
|
||||
app.debug.Printf("%s: Sending invite message", inviteCode)
|
||||
if discordEnabled && !strings.Contains(req.SendTo, "@") {
|
||||
users := app.discord.GetUsers(req.SendTo)
|
||||
if len(users) == 0 {
|
||||
invite.SendTo = fmt.Sprintf("Failed: User not found: \"%s\"", req.SendTo)
|
||||
} else if len(users) > 1 {
|
||||
invite.SendTo = fmt.Sprintf("Failed: Multiple users found: \"%s\"", req.SendTo)
|
||||
} else {
|
||||
invite.SendTo = req.SendTo
|
||||
addressValid = true
|
||||
discord = users[0].User.ID
|
||||
}
|
||||
} else if emailEnabled {
|
||||
addressValid = true
|
||||
invite.SendTo = req.SendTo
|
||||
}
|
||||
if addressValid {
|
||||
msg, err := app.email.constructInvite(inviteCode, invite, app, false)
|
||||
if err != nil {
|
||||
invite.SendTo = fmt.Sprintf("Failed to send to %s", req.SendTo)
|
||||
app.err.Printf("%s: Failed to construct invite message: %v", inviteCode, err)
|
||||
} else {
|
||||
var err error
|
||||
if discord != "" {
|
||||
err = app.discord.SendDM(msg, discord)
|
||||
} else {
|
||||
err = app.email.send(msg, req.SendTo)
|
||||
}
|
||||
if err != nil {
|
||||
invite.SendTo = fmt.Sprintf("Failed to send to %s", req.SendTo)
|
||||
app.err.Printf("%s: %s: %v", inviteCode, invite.SendTo, err)
|
||||
} else {
|
||||
app.info.Printf("%s: Sent invite email to \"%s\"", inviteCode, req.SendTo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if req.Profile != "" {
|
||||
if _, ok := app.storage.profiles[req.Profile]; ok {
|
||||
invite.Profile = req.Profile
|
||||
} else {
|
||||
invite.Profile = "Default"
|
||||
}
|
||||
}
|
||||
app.storage.invites[inviteCode] = invite
|
||||
app.storage.storeInvites()
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Get invites.
|
||||
// @Produce json
|
||||
// @Success 200 {object} getInvitesDTO
|
||||
// @Router /invites [get]
|
||||
// @Security Bearer
|
||||
// @tags Invites
|
||||
func (app *appContext) GetInvites(gc *gin.Context) {
|
||||
app.debug.Println("Invites requested")
|
||||
currentTime := time.Now()
|
||||
app.storage.loadInvites()
|
||||
app.checkInvites()
|
||||
var invites []inviteDTO
|
||||
for code, inv := range app.storage.invites {
|
||||
_, months, days, hours, minutes, _ := timeDiff(inv.ValidTill, currentTime)
|
||||
invite := inviteDTO{
|
||||
Code: code,
|
||||
Months: months,
|
||||
Days: days,
|
||||
Hours: hours,
|
||||
Minutes: minutes,
|
||||
UserExpiry: inv.UserExpiry,
|
||||
UserMonths: inv.UserMonths,
|
||||
UserDays: inv.UserDays,
|
||||
UserHours: inv.UserHours,
|
||||
UserMinutes: inv.UserMinutes,
|
||||
Created: inv.Created.Unix(),
|
||||
Profile: inv.Profile,
|
||||
NoLimit: inv.NoLimit,
|
||||
Label: inv.Label,
|
||||
}
|
||||
if len(inv.UsedBy) != 0 {
|
||||
invite.UsedBy = map[string]int64{}
|
||||
for _, pair := range inv.UsedBy {
|
||||
// These used to be stored formatted instead of as a unix timestamp.
|
||||
unix, err := strconv.ParseInt(pair[1], 10, 64)
|
||||
if err != nil {
|
||||
date, err := timefmt.Parse(pair[1], app.datePattern+" "+app.timePattern)
|
||||
if err != nil {
|
||||
app.err.Printf("Failed to parse usedBy time: %v", err)
|
||||
}
|
||||
unix = date.Unix()
|
||||
}
|
||||
invite.UsedBy[pair[0]] = unix
|
||||
}
|
||||
}
|
||||
invite.RemainingUses = 1
|
||||
if inv.RemainingUses != 0 {
|
||||
invite.RemainingUses = inv.RemainingUses
|
||||
}
|
||||
if inv.SendTo != "" {
|
||||
invite.SendTo = inv.SendTo
|
||||
}
|
||||
if len(inv.Notify) != 0 {
|
||||
var address string
|
||||
if app.config.Section("ui").Key("jellyfin_login").MustBool(false) {
|
||||
app.storage.loadEmails()
|
||||
if addr, ok := app.storage.emails[gc.GetString("jfId")]; ok && addr.Addr != "" {
|
||||
address = addr.Addr
|
||||
}
|
||||
} else {
|
||||
address = app.config.Section("ui").Key("email").String()
|
||||
}
|
||||
if _, ok := inv.Notify[address]; ok {
|
||||
if _, ok = inv.Notify[address]["notify-expiry"]; ok {
|
||||
invite.NotifyExpiry = inv.Notify[address]["notify-expiry"]
|
||||
}
|
||||
if _, ok = inv.Notify[address]["notify-creation"]; ok {
|
||||
invite.NotifyCreation = inv.Notify[address]["notify-creation"]
|
||||
}
|
||||
}
|
||||
}
|
||||
invites = append(invites, invite)
|
||||
}
|
||||
profiles := make([]string, len(app.storage.profiles))
|
||||
if len(app.storage.profiles) != 0 {
|
||||
profiles[0] = app.storage.defaultProfile
|
||||
i := 1
|
||||
if len(app.storage.profiles) > 1 {
|
||||
for p := range app.storage.profiles {
|
||||
if p != app.storage.defaultProfile {
|
||||
profiles[i] = p
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
resp := getInvitesDTO{
|
||||
Profiles: profiles,
|
||||
Invites: invites,
|
||||
}
|
||||
gc.JSON(200, resp)
|
||||
}
|
||||
|
||||
// @Summary Set profile for an invite
|
||||
// @Produce json
|
||||
// @Param inviteProfileDTO body inviteProfileDTO true "Invite profile object"
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 500 {object} stringResponse
|
||||
// @Router /invites/profile [post]
|
||||
// @Security Bearer
|
||||
// @tags Profiles & Settings
|
||||
func (app *appContext) SetProfile(gc *gin.Context) {
|
||||
var req inviteProfileDTO
|
||||
gc.BindJSON(&req)
|
||||
app.debug.Printf("%s: Setting profile to \"%s\"", req.Invite, req.Profile)
|
||||
// "" means "Don't apply profile"
|
||||
if _, ok := app.storage.profiles[req.Profile]; !ok && req.Profile != "" {
|
||||
app.err.Printf("%s: Profile \"%s\" not found", req.Invite, req.Profile)
|
||||
respond(500, "Profile not found", gc)
|
||||
return
|
||||
}
|
||||
inv := app.storage.invites[req.Invite]
|
||||
inv.Profile = req.Profile
|
||||
app.storage.invites[req.Invite] = inv
|
||||
app.storage.storeInvites()
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Set notification preferences for an invite.
|
||||
// @Produce json
|
||||
// @Param setNotifyDTO body setNotifyDTO true "Map of invite codes to notification settings objects"
|
||||
// @Success 200
|
||||
// @Failure 400 {object} stringResponse
|
||||
// @Failure 500 {object} stringResponse
|
||||
// @Router /invites/notify [post]
|
||||
// @Security Bearer
|
||||
// @tags Other
|
||||
func (app *appContext) SetNotify(gc *gin.Context) {
|
||||
var req map[string]map[string]bool
|
||||
gc.BindJSON(&req)
|
||||
changed := false
|
||||
for code, settings := range req {
|
||||
app.debug.Printf("%s: Notification settings change requested", code)
|
||||
app.storage.loadInvites()
|
||||
app.storage.loadEmails()
|
||||
invite, ok := app.storage.invites[code]
|
||||
if !ok {
|
||||
app.err.Printf("%s Notification setting change failed: Invalid code", code)
|
||||
respond(400, "Invalid invite code", gc)
|
||||
return
|
||||
}
|
||||
var address string
|
||||
jellyfinLogin := app.config.Section("ui").Key("jellyfin_login").MustBool(false)
|
||||
if jellyfinLogin {
|
||||
var addressAvailable bool = app.getAddressOrName(gc.GetString("jfId")) != ""
|
||||
if !addressAvailable {
|
||||
app.err.Printf("%s: Couldn't find contact method for admin. Make sure one is set.", code)
|
||||
app.debug.Printf("%s: User ID \"%s\"", code, gc.GetString("jfId"))
|
||||
respond(500, "Missing user contact method", gc)
|
||||
return
|
||||
}
|
||||
address = gc.GetString("jfId")
|
||||
} else {
|
||||
address = app.config.Section("ui").Key("email").String()
|
||||
}
|
||||
if invite.Notify == nil {
|
||||
invite.Notify = map[string]map[string]bool{}
|
||||
}
|
||||
if _, ok := invite.Notify[address]; !ok {
|
||||
invite.Notify[address] = map[string]bool{}
|
||||
} /*else {
|
||||
if _, ok := invite.Notify[address]["notify-expiry"]; !ok {
|
||||
*/
|
||||
if _, ok := settings["notify-expiry"]; ok && invite.Notify[address]["notify-expiry"] != settings["notify-expiry"] {
|
||||
invite.Notify[address]["notify-expiry"] = settings["notify-expiry"]
|
||||
app.debug.Printf("%s: Set \"notify-expiry\" to %t for %s", code, settings["notify-expiry"], address)
|
||||
changed = true
|
||||
}
|
||||
if _, ok := settings["notify-creation"]; ok && invite.Notify[address]["notify-creation"] != settings["notify-creation"] {
|
||||
invite.Notify[address]["notify-creation"] = settings["notify-creation"]
|
||||
app.debug.Printf("%s: Set \"notify-creation\" to %t for %s", code, settings["notify-creation"], address)
|
||||
changed = true
|
||||
}
|
||||
if changed {
|
||||
app.storage.invites[code] = invite
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
app.storage.storeInvites()
|
||||
}
|
||||
}
|
||||
|
||||
// @Summary Delete an invite.
|
||||
// @Produce json
|
||||
// @Param deleteInviteDTO body deleteInviteDTO true "Delete invite object"
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 400 {object} stringResponse
|
||||
// @Router /invites [delete]
|
||||
// @Security Bearer
|
||||
// @tags Invites
|
||||
func (app *appContext) DeleteInvite(gc *gin.Context) {
|
||||
var req deleteInviteDTO
|
||||
gc.BindJSON(&req)
|
||||
app.debug.Printf("%s: Deletion requested", req.Code)
|
||||
var ok bool
|
||||
_, ok = app.storage.invites[req.Code]
|
||||
if ok {
|
||||
delete(app.storage.invites, req.Code)
|
||||
app.storage.storeInvites()
|
||||
app.info.Printf("%s: Invite deleted", req.Code)
|
||||
respondBool(200, true, gc)
|
||||
return
|
||||
}
|
||||
app.err.Printf("%s: Deletion failed: Invalid code", req.Code)
|
||||
respond(400, "Code doesn't exist", gc)
|
||||
}
|
700
api-messages.go
Normal file
700
api-messages.go
Normal file
@ -0,0 +1,700 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
// @Summary Get a list of email names and IDs.
|
||||
// @Produce json
|
||||
// @Param lang query string false "Language for email titles."
|
||||
// @Success 200 {object} emailListDTO
|
||||
// @Router /config/emails [get]
|
||||
// @Security Bearer
|
||||
// @tags Configuration
|
||||
func (app *appContext) GetCustomEmails(gc *gin.Context) {
|
||||
lang := gc.Query("lang")
|
||||
if _, ok := app.storage.lang.Email[lang]; !ok {
|
||||
lang = app.storage.lang.chosenEmailLang
|
||||
}
|
||||
gc.JSON(200, emailListDTO{
|
||||
"UserCreated": {Name: app.storage.lang.Email[lang].UserCreated["name"], Enabled: app.storage.customEmails.UserCreated.Enabled},
|
||||
"InviteExpiry": {Name: app.storage.lang.Email[lang].InviteExpiry["name"], Enabled: app.storage.customEmails.InviteExpiry.Enabled},
|
||||
"PasswordReset": {Name: app.storage.lang.Email[lang].PasswordReset["name"], Enabled: app.storage.customEmails.PasswordReset.Enabled},
|
||||
"UserDeleted": {Name: app.storage.lang.Email[lang].UserDeleted["name"], Enabled: app.storage.customEmails.UserDeleted.Enabled},
|
||||
"UserDisabled": {Name: app.storage.lang.Email[lang].UserDisabled["name"], Enabled: app.storage.customEmails.UserDisabled.Enabled},
|
||||
"UserEnabled": {Name: app.storage.lang.Email[lang].UserEnabled["name"], Enabled: app.storage.customEmails.UserEnabled.Enabled},
|
||||
"InviteEmail": {Name: app.storage.lang.Email[lang].InviteEmail["name"], Enabled: app.storage.customEmails.InviteEmail.Enabled},
|
||||
"WelcomeEmail": {Name: app.storage.lang.Email[lang].WelcomeEmail["name"], Enabled: app.storage.customEmails.WelcomeEmail.Enabled},
|
||||
"EmailConfirmation": {Name: app.storage.lang.Email[lang].EmailConfirmation["name"], Enabled: app.storage.customEmails.EmailConfirmation.Enabled},
|
||||
"UserExpired": {Name: app.storage.lang.Email[lang].UserExpired["name"], Enabled: app.storage.customEmails.UserExpired.Enabled},
|
||||
})
|
||||
}
|
||||
|
||||
func (app *appContext) getCustomEmail(id string) *customEmail {
|
||||
switch id {
|
||||
case "Announcement":
|
||||
return &customEmail{}
|
||||
case "UserCreated":
|
||||
return &app.storage.customEmails.UserCreated
|
||||
case "InviteExpiry":
|
||||
return &app.storage.customEmails.InviteExpiry
|
||||
case "PasswordReset":
|
||||
return &app.storage.customEmails.PasswordReset
|
||||
case "UserDeleted":
|
||||
return &app.storage.customEmails.UserDeleted
|
||||
case "UserDisabled":
|
||||
return &app.storage.customEmails.UserDisabled
|
||||
case "UserEnabled":
|
||||
return &app.storage.customEmails.UserEnabled
|
||||
case "InviteEmail":
|
||||
return &app.storage.customEmails.InviteEmail
|
||||
case "WelcomeEmail":
|
||||
return &app.storage.customEmails.WelcomeEmail
|
||||
case "EmailConfirmation":
|
||||
return &app.storage.customEmails.EmailConfirmation
|
||||
case "UserExpired":
|
||||
return &app.storage.customEmails.UserExpired
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// @Summary Sets the corresponding custom email.
|
||||
// @Produce json
|
||||
// @Param customEmail body customEmail true "Content = email (in markdown)."
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 400 {object} boolResponse
|
||||
// @Failure 500 {object} boolResponse
|
||||
// @Param id path string true "ID of email"
|
||||
// @Router /config/emails/{id} [post]
|
||||
// @Security Bearer
|
||||
// @tags Configuration
|
||||
func (app *appContext) SetCustomEmail(gc *gin.Context) {
|
||||
var req customEmail
|
||||
gc.BindJSON(&req)
|
||||
id := gc.Param("id")
|
||||
if req.Content == "" {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
email := app.getCustomEmail(id)
|
||||
if email == nil {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
email.Content = req.Content
|
||||
email.Enabled = true
|
||||
if app.storage.storeCustomEmails() != nil {
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Enable/Disable custom email.
|
||||
// @Produce json
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 400 {object} boolResponse
|
||||
// @Failure 500 {object} boolResponse
|
||||
// @Param enable/disable path string true "enable/disable"
|
||||
// @Param id path string true "ID of email"
|
||||
// @Router /config/emails/{id}/state/{enable/disable} [post]
|
||||
// @Security Bearer
|
||||
// @tags Configuration
|
||||
func (app *appContext) SetCustomEmailState(gc *gin.Context) {
|
||||
id := gc.Param("id")
|
||||
s := gc.Param("state")
|
||||
enabled := false
|
||||
if s == "enable" {
|
||||
enabled = true
|
||||
} else if s != "disable" {
|
||||
respondBool(400, false, gc)
|
||||
}
|
||||
email := app.getCustomEmail(id)
|
||||
if email == nil {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
email.Enabled = enabled
|
||||
if app.storage.storeCustomEmails() != nil {
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Returns the custom email (generating it if not set) and list of used variables in it.
|
||||
// @Produce json
|
||||
// @Success 200 {object} customEmailDTO
|
||||
// @Failure 400 {object} boolResponse
|
||||
// @Failure 500 {object} boolResponse
|
||||
// @Param id path string true "ID of email"
|
||||
// @Router /config/emails/{id} [get]
|
||||
// @Security Bearer
|
||||
// @tags Configuration
|
||||
func (app *appContext) GetCustomEmailTemplate(gc *gin.Context) {
|
||||
lang := app.storage.lang.chosenEmailLang
|
||||
id := gc.Param("id")
|
||||
var content string
|
||||
var err error
|
||||
var msg *Message
|
||||
var variables []string
|
||||
var conditionals []string
|
||||
var values map[string]interface{}
|
||||
username := app.storage.lang.Email[lang].Strings.get("username")
|
||||
emailAddress := app.storage.lang.Email[lang].Strings.get("emailAddress")
|
||||
email := app.getCustomEmail(id)
|
||||
if email == nil {
|
||||
app.err.Printf("Failed to get custom email with ID \"%s\"", id)
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
if id == "WelcomeEmail" {
|
||||
conditionals = []string{"{yourAccountWillExpire}"}
|
||||
email.Conditionals = conditionals
|
||||
}
|
||||
content = email.Content
|
||||
noContent := content == ""
|
||||
if !noContent {
|
||||
variables = email.Variables
|
||||
}
|
||||
switch id {
|
||||
case "Announcement":
|
||||
// Just send the email html
|
||||
content = ""
|
||||
case "UserCreated":
|
||||
if noContent {
|
||||
msg, err = app.email.constructCreated("", "", "", Invite{}, app, true)
|
||||
}
|
||||
values = app.email.createdValues("xxxxxx", username, emailAddress, Invite{}, app, false)
|
||||
case "InviteExpiry":
|
||||
if noContent {
|
||||
msg, err = app.email.constructExpiry("", Invite{}, app, true)
|
||||
}
|
||||
values = app.email.expiryValues("xxxxxx", Invite{}, app, false)
|
||||
case "PasswordReset":
|
||||
if noContent {
|
||||
msg, err = app.email.constructReset(PasswordReset{}, app, true)
|
||||
}
|
||||
values = app.email.resetValues(PasswordReset{Pin: "12-34-56", Username: username}, app, false)
|
||||
case "UserDeleted":
|
||||
if noContent {
|
||||
msg, err = app.email.constructDeleted("", app, true)
|
||||
}
|
||||
values = app.email.deletedValues(app.storage.lang.Email[lang].Strings.get("reason"), app, false)
|
||||
case "UserDisabled":
|
||||
if noContent {
|
||||
msg, err = app.email.constructDisabled("", app, true)
|
||||
}
|
||||
values = app.email.deletedValues(app.storage.lang.Email[lang].Strings.get("reason"), app, false)
|
||||
case "UserEnabled":
|
||||
if noContent {
|
||||
msg, err = app.email.constructEnabled("", app, true)
|
||||
}
|
||||
values = app.email.deletedValues(app.storage.lang.Email[lang].Strings.get("reason"), app, false)
|
||||
case "InviteEmail":
|
||||
if noContent {
|
||||
msg, err = app.email.constructInvite("", Invite{}, app, true)
|
||||
}
|
||||
values = app.email.inviteValues("xxxxxx", Invite{}, app, false)
|
||||
case "WelcomeEmail":
|
||||
if noContent {
|
||||
msg, err = app.email.constructWelcome("", time.Time{}, app, true)
|
||||
}
|
||||
values = app.email.welcomeValues(username, time.Now(), app, false, true)
|
||||
case "EmailConfirmation":
|
||||
if noContent {
|
||||
msg, err = app.email.constructConfirmation("", "", "", app, true)
|
||||
}
|
||||
values = app.email.confirmationValues("xxxxxx", username, "xxxxxx", app, false)
|
||||
case "UserExpired":
|
||||
if noContent {
|
||||
msg, err = app.email.constructUserExpired(app, true)
|
||||
}
|
||||
values = app.email.userExpiredValues(app, false)
|
||||
}
|
||||
if err != nil {
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
if noContent && id != "Announcement" {
|
||||
content = msg.Text
|
||||
variables = make([]string, strings.Count(content, "{"))
|
||||
i := 0
|
||||
found := false
|
||||
buf := ""
|
||||
for _, c := range content {
|
||||
if !found && c != '{' && c != '}' {
|
||||
continue
|
||||
}
|
||||
found = true
|
||||
buf += string(c)
|
||||
if c == '}' {
|
||||
found = false
|
||||
variables[i] = buf
|
||||
buf = ""
|
||||
i++
|
||||
}
|
||||
}
|
||||
email.Variables = variables
|
||||
}
|
||||
if variables == nil {
|
||||
variables = []string{}
|
||||
}
|
||||
if app.storage.storeCustomEmails() != nil {
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
mail, err := app.email.constructTemplate("", "<div class=\"preview-content\"></div>", app)
|
||||
if err != nil {
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
gc.JSON(200, customEmailDTO{Content: content, Variables: variables, Conditionals: conditionals, Values: values, HTML: mail.HTML, Plaintext: mail.Text})
|
||||
}
|
||||
|
||||
// @Summary Returns a new Telegram verification PIN, and the bot username.
|
||||
// @Produce json
|
||||
// @Success 200 {object} telegramPinDTO
|
||||
// @Router /telegram/pin [get]
|
||||
// @Security Bearer
|
||||
// @tags Other
|
||||
func (app *appContext) TelegramGetPin(gc *gin.Context) {
|
||||
gc.JSON(200, telegramPinDTO{
|
||||
Token: app.telegram.NewAuthToken(),
|
||||
Username: app.telegram.username,
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary Link a Jellyfin & Telegram user together via a verification PIN.
|
||||
// @Produce json
|
||||
// @Param telegramSetDTO body telegramSetDTO true "Token and user's Jellyfin ID."
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 500 {object} boolResponse
|
||||
// @Failure 400 {object} boolResponse
|
||||
// @Router /users/telegram [post]
|
||||
// @Security Bearer
|
||||
// @tags Other
|
||||
func (app *appContext) TelegramAddUser(gc *gin.Context) {
|
||||
var req telegramSetDTO
|
||||
gc.BindJSON(&req)
|
||||
if req.Token == "" || req.ID == "" {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
tokenIndex := -1
|
||||
for i, v := range app.telegram.verifiedTokens {
|
||||
if v.Token == req.Token {
|
||||
tokenIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if tokenIndex == -1 {
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
tgToken := app.telegram.verifiedTokens[tokenIndex]
|
||||
tgUser := TelegramUser{
|
||||
ChatID: tgToken.ChatID,
|
||||
Username: tgToken.Username,
|
||||
Contact: true,
|
||||
}
|
||||
if lang, ok := app.telegram.languages[tgToken.ChatID]; ok {
|
||||
tgUser.Lang = lang
|
||||
}
|
||||
if app.storage.telegram == nil {
|
||||
app.storage.telegram = map[string]TelegramUser{}
|
||||
}
|
||||
app.storage.telegram[req.ID] = tgUser
|
||||
err := app.storage.storeTelegramUsers()
|
||||
if err != nil {
|
||||
app.err.Printf("Failed to store Telegram users: %v", err)
|
||||
} else {
|
||||
app.telegram.verifiedTokens[len(app.telegram.verifiedTokens)-1], app.telegram.verifiedTokens[tokenIndex] = app.telegram.verifiedTokens[tokenIndex], app.telegram.verifiedTokens[len(app.telegram.verifiedTokens)-1]
|
||||
app.telegram.verifiedTokens = app.telegram.verifiedTokens[:len(app.telegram.verifiedTokens)-1]
|
||||
}
|
||||
linkExistingOmbiDiscordTelegram(app)
|
||||
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."
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Success 400 {object} boolResponse
|
||||
// @Success 500 {object} boolResponse
|
||||
// @Router /users/telegram/notify [post]
|
||||
// @Security Bearer
|
||||
// @tags Other
|
||||
func (app *appContext) SetContactMethods(gc *gin.Context) {
|
||||
var req SetContactMethodsDTO
|
||||
gc.BindJSON(&req)
|
||||
if req.ID == "" {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
if tgUser, ok := app.storage.telegram[req.ID]; ok {
|
||||
change := tgUser.Contact != req.Telegram
|
||||
tgUser.Contact = req.Telegram
|
||||
app.storage.telegram[req.ID] = tgUser
|
||||
if err := app.storage.storeTelegramUsers(); err != nil {
|
||||
respondBool(500, false, gc)
|
||||
app.err.Printf("Telegram: Failed to store users: %v", err)
|
||||
return
|
||||
}
|
||||
if change {
|
||||
msg := ""
|
||||
if !req.Telegram {
|
||||
msg = " not"
|
||||
}
|
||||
app.debug.Printf("Telegram: User \"%s\" will%s be notified through Telegram.", tgUser.Username, msg)
|
||||
}
|
||||
}
|
||||
if dcUser, ok := app.storage.discord[req.ID]; ok {
|
||||
change := dcUser.Contact != req.Discord
|
||||
dcUser.Contact = req.Discord
|
||||
app.storage.discord[req.ID] = dcUser
|
||||
if err := app.storage.storeDiscordUsers(); err != nil {
|
||||
respondBool(500, false, gc)
|
||||
app.err.Printf("Discord: Failed to store users: %v", err)
|
||||
return
|
||||
}
|
||||
if change {
|
||||
msg := ""
|
||||
if !req.Discord {
|
||||
msg = " not"
|
||||
}
|
||||
app.debug.Printf("Discord: User \"%s\" will%s be notified through Discord.", dcUser.Username, msg)
|
||||
}
|
||||
}
|
||||
if mxUser, ok := app.storage.matrix[req.ID]; ok {
|
||||
change := mxUser.Contact != req.Matrix
|
||||
mxUser.Contact = req.Matrix
|
||||
app.storage.matrix[req.ID] = mxUser
|
||||
if err := app.storage.storeMatrixUsers(); err != nil {
|
||||
respondBool(500, false, gc)
|
||||
app.err.Printf("Matrix: Failed to store users: %v", err)
|
||||
return
|
||||
}
|
||||
if change {
|
||||
msg := ""
|
||||
if !req.Matrix {
|
||||
msg = " not"
|
||||
}
|
||||
app.debug.Printf("Matrix: User \"%s\" will%s be notified through Matrix.", mxUser.UserID, msg)
|
||||
}
|
||||
}
|
||||
if email, ok := app.storage.emails[req.ID]; ok {
|
||||
change := email.Contact != req.Email
|
||||
email.Contact = req.Email
|
||||
app.storage.emails[req.ID] = email
|
||||
if err := app.storage.storeEmails(); err != nil {
|
||||
respondBool(500, false, gc)
|
||||
app.err.Printf("Failed to store emails: %v", err)
|
||||
return
|
||||
}
|
||||
if change {
|
||||
msg := ""
|
||||
if !req.Email {
|
||||
msg = " not"
|
||||
}
|
||||
app.debug.Printf("\"%s\" will%s be notified via Email.", email.Addr, msg)
|
||||
}
|
||||
}
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Returns true/false on whether or not a telegram PIN was verified. Requires bearer auth.
|
||||
// @Produce json
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Param pin path string true "PIN code to check"
|
||||
// @Router /telegram/verified/{pin} [get]
|
||||
// @Security Bearer
|
||||
// @tags Other
|
||||
func (app *appContext) TelegramVerified(gc *gin.Context) {
|
||||
pin := gc.Param("pin")
|
||||
tokenIndex := -1
|
||||
for i, v := range app.telegram.verifiedTokens {
|
||||
if v.Token == pin {
|
||||
tokenIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
// if tokenIndex != -1 {
|
||||
// length := len(app.telegram.verifiedTokens)
|
||||
// app.telegram.verifiedTokens[length-1], app.telegram.verifiedTokens[tokenIndex] = app.telegram.verifiedTokens[tokenIndex], app.telegram.verifiedTokens[length-1]
|
||||
// app.telegram.verifiedTokens = app.telegram.verifiedTokens[:length-1]
|
||||
// }
|
||||
respondBool(200, tokenIndex != -1, gc)
|
||||
}
|
||||
|
||||
// @Summary Returns true/false on whether or not a telegram PIN was verified. Requires invite code.
|
||||
// @Produce json
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Success 401 {object} boolResponse
|
||||
// @Param pin path string true "PIN code to check"
|
||||
// @Param invCode path string true "invite Code"
|
||||
// @Router /invite/{invCode}/telegram/verified/{pin} [get]
|
||||
// @tags Other
|
||||
func (app *appContext) TelegramVerifiedInvite(gc *gin.Context) {
|
||||
code := gc.Param("invCode")
|
||||
if _, ok := app.storage.invites[code]; !ok {
|
||||
respondBool(401, false, gc)
|
||||
return
|
||||
}
|
||||
pin := gc.Param("pin")
|
||||
tokenIndex := -1
|
||||
for i, v := range app.telegram.verifiedTokens {
|
||||
if v.Token == pin {
|
||||
tokenIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
// if tokenIndex != -1 {
|
||||
// length := len(app.telegram.verifiedTokens)
|
||||
// app.telegram.verifiedTokens[length-1], app.telegram.verifiedTokens[tokenIndex] = app.telegram.verifiedTokens[tokenIndex], app.telegram.verifiedTokens[length-1]
|
||||
// app.telegram.verifiedTokens = app.telegram.verifiedTokens[:length-1]
|
||||
// }
|
||||
respondBool(200, tokenIndex != -1, gc)
|
||||
}
|
||||
|
||||
// @Summary Returns true/false on whether or not a discord PIN was verified. Requires invite code.
|
||||
// @Produce json
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 401 {object} boolResponse
|
||||
// @Param pin path string true "PIN code to check"
|
||||
// @Param invCode path string true "invite Code"
|
||||
// @Router /invite/{invCode}/discord/verified/{pin} [get]
|
||||
// @tags Other
|
||||
func (app *appContext) DiscordVerifiedInvite(gc *gin.Context) {
|
||||
code := gc.Param("invCode")
|
||||
if _, ok := app.storage.invites[code]; !ok {
|
||||
respondBool(401, false, gc)
|
||||
return
|
||||
}
|
||||
pin := gc.Param("pin")
|
||||
_, ok := app.discord.verifiedTokens[pin]
|
||||
respondBool(200, ok, gc)
|
||||
}
|
||||
|
||||
// @Summary Returns a 10-minute, one-use Discord server invite
|
||||
// @Produce json
|
||||
// @Success 200 {object} DiscordInviteDTO
|
||||
// @Failure 400 {object} boolResponse
|
||||
// @Failure 401 {object} boolResponse
|
||||
// @Failure 500 {object} boolResponse
|
||||
// @Param invCode path string true "invite Code"
|
||||
// @Router /invite/{invCode}/discord/invite [get]
|
||||
// @tags Other
|
||||
func (app *appContext) DiscordServerInvite(gc *gin.Context) {
|
||||
if app.discord.inviteChannelName == "" {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
code := gc.Param("invCode")
|
||||
if _, ok := app.storage.invites[code]; !ok {
|
||||
respondBool(401, false, gc)
|
||||
return
|
||||
}
|
||||
invURL, iconURL := app.discord.NewTempInvite(10*60, 1)
|
||||
if invURL == "" {
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
gc.JSON(200, DiscordInviteDTO{invURL, iconURL})
|
||||
}
|
||||
|
||||
// @Summary Generate and send a new PIN to a specified Matrix user.
|
||||
// @Produce json
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 400 {object} boolResponse
|
||||
// @Failure 401 {object} boolResponse
|
||||
// @Failure 500 {object} boolResponse
|
||||
// @Param invCode path string true "invite Code"
|
||||
// @Param MatrixSendPINDTO body MatrixSendPINDTO true "User's Matrix ID."
|
||||
// @Router /invite/{invCode}/matrix/user [post]
|
||||
// @tags Other
|
||||
func (app *appContext) MatrixSendPIN(gc *gin.Context) {
|
||||
code := gc.Param("invCode")
|
||||
if _, ok := app.storage.invites[code]; !ok {
|
||||
respondBool(401, false, gc)
|
||||
return
|
||||
}
|
||||
var req MatrixSendPINDTO
|
||||
gc.BindJSON(&req)
|
||||
if req.UserID == "" {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
ok := app.matrix.SendStart(req.UserID)
|
||||
if !ok {
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Check whether a matrix PIN is valid, and mark the token as verified if so. Requires invite code.
|
||||
// @Produce json
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 401 {object} boolResponse
|
||||
// @Param pin path string true "PIN code to check"
|
||||
// @Param invCode path string true "invite Code"
|
||||
// @Param userID path string true "Matrix User ID"
|
||||
// @Router /invite/{invCode}/matrix/verified/{userID}/{pin} [get]
|
||||
// @tags Other
|
||||
func (app *appContext) MatrixCheckPIN(gc *gin.Context) {
|
||||
code := gc.Param("invCode")
|
||||
if _, ok := app.storage.invites[code]; !ok {
|
||||
app.debug.Println("Matrix: Invite code was invalid")
|
||||
respondBool(401, false, gc)
|
||||
return
|
||||
}
|
||||
userID := gc.Param("userID")
|
||||
pin := gc.Param("pin")
|
||||
user, ok := app.matrix.tokens[pin]
|
||||
if !ok {
|
||||
app.debug.Println("Matrix: PIN not found")
|
||||
respondBool(200, false, gc)
|
||||
return
|
||||
}
|
||||
if user.User.UserID != userID {
|
||||
app.debug.Println("Matrix: User ID of PIN didn't match")
|
||||
respondBool(200, false, gc)
|
||||
return
|
||||
}
|
||||
user.Verified = true
|
||||
app.matrix.tokens[pin] = user
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Generates a Matrix access token from a username and password.
|
||||
// @Produce json
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 400 {object} stringResponse
|
||||
// @Failure 401 {object} boolResponse
|
||||
// @Failure 500 {object} boolResponse
|
||||
// @Param MatrixLoginDTO body MatrixLoginDTO true "Username & password."
|
||||
// @Router /matrix/login [post]
|
||||
// @tags Other
|
||||
func (app *appContext) MatrixLogin(gc *gin.Context) {
|
||||
var req MatrixLoginDTO
|
||||
gc.BindJSON(&req)
|
||||
if req.Username == "" || req.Password == "" {
|
||||
respond(400, "errorLoginBlank", gc)
|
||||
return
|
||||
}
|
||||
token, err := app.matrix.generateAccessToken(req.Homeserver, req.Username, req.Password)
|
||||
if err != nil {
|
||||
app.err.Printf("Matrix: Failed to generate token: %v", err)
|
||||
respond(401, "Unauthorized", gc)
|
||||
return
|
||||
}
|
||||
tempConfig, _ := ini.Load(app.configPath)
|
||||
matrix := tempConfig.Section("matrix")
|
||||
matrix.Key("enabled").SetValue("true")
|
||||
matrix.Key("homeserver").SetValue(req.Homeserver)
|
||||
matrix.Key("token").SetValue(token)
|
||||
matrix.Key("user_id").SetValue(req.Username)
|
||||
if err := tempConfig.SaveTo(app.configPath); err != nil {
|
||||
app.err.Printf("Failed to save config to \"%s\": %v", app.configPath, err)
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Links a Matrix user to a Jellyfin account via user IDs. Notifications are turned on by default.
|
||||
// @Produce json
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 400 {object} boolResponse
|
||||
// @Failure 500 {object} boolResponse
|
||||
// @Param MatrixConnectUserDTO body MatrixConnectUserDTO true "User's Jellyfin ID & Matrix user ID."
|
||||
// @Router /users/matrix [post]
|
||||
// @tags Other
|
||||
func (app *appContext) MatrixConnect(gc *gin.Context) {
|
||||
var req MatrixConnectUserDTO
|
||||
gc.BindJSON(&req)
|
||||
if app.storage.matrix == nil {
|
||||
app.storage.matrix = map[string]MatrixUser{}
|
||||
}
|
||||
roomID, encrypted, err := app.matrix.CreateRoom(req.UserID)
|
||||
if err != nil {
|
||||
app.err.Printf("Matrix: Failed to create room: %v", err)
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
app.storage.matrix[req.JellyfinID] = MatrixUser{
|
||||
UserID: req.UserID,
|
||||
RoomID: string(roomID),
|
||||
Lang: "en-us",
|
||||
Contact: true,
|
||||
Encrypted: encrypted,
|
||||
}
|
||||
app.matrix.isEncrypted[roomID] = encrypted
|
||||
if err := app.storage.storeMatrixUsers(); err != nil {
|
||||
app.err.Printf("Failed to store Matrix users: %v", err)
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Returns a list of matching users from a Discord guild, given a username (discriminator optional).
|
||||
// @Produce json
|
||||
// @Success 200 {object} DiscordUsersDTO
|
||||
// @Failure 400 {object} boolResponse
|
||||
// @Failure 500 {object} boolResponse
|
||||
// @Param username path string true "username to search."
|
||||
// @Router /users/discord/{username} [get]
|
||||
// @tags Other
|
||||
func (app *appContext) DiscordGetUsers(gc *gin.Context) {
|
||||
name := gc.Param("username")
|
||||
if name == "" {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
users := app.discord.GetUsers(name)
|
||||
resp := DiscordUsersDTO{Users: make([]DiscordUserDTO, len(users))}
|
||||
for i, u := range users {
|
||||
resp.Users[i] = DiscordUserDTO{
|
||||
Name: u.User.Username + "#" + u.User.Discriminator,
|
||||
ID: u.User.ID,
|
||||
AvatarURL: u.User.AvatarURL("32"),
|
||||
}
|
||||
}
|
||||
gc.JSON(200, resp)
|
||||
}
|
||||
|
||||
// @Summary Links a Discord account to a Jellyfin account via user IDs. Notifications are turned on by default.
|
||||
// @Produce json
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 400 {object} boolResponse
|
||||
// @Failure 500 {object} boolResponse
|
||||
// @Param DiscordConnectUserDTO body DiscordConnectUserDTO true "User's Jellyfin ID & Discord ID."
|
||||
// @Router /users/discord [post]
|
||||
// @tags Other
|
||||
func (app *appContext) DiscordConnect(gc *gin.Context) {
|
||||
var req DiscordConnectUserDTO
|
||||
gc.BindJSON(&req)
|
||||
if req.JellyfinID == "" || req.DiscordID == "" {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
user, ok := app.discord.NewUser(req.DiscordID)
|
||||
if !ok {
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
app.storage.discord[req.JellyfinID] = user
|
||||
if err := app.storage.storeDiscordUsers(); err != nil {
|
||||
app.err.Printf("Failed to store Discord users: %v", err)
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
linkExistingOmbiDiscordTelegram(app)
|
||||
respondBool(200, true, gc)
|
||||
}
|
119
api-ombi.go
Normal file
119
api-ombi.go
Normal file
@ -0,0 +1,119 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (app *appContext) getOmbiUser(jfID string) (map[string]interface{}, int, error) {
|
||||
ombiUsers, code, err := app.ombi.GetUsers()
|
||||
if err != nil || code != 200 {
|
||||
return nil, code, err
|
||||
}
|
||||
jfUser, code, err := app.jf.UserByID(jfID, false)
|
||||
if err != nil || code != 200 {
|
||||
return nil, code, err
|
||||
}
|
||||
username := jfUser.Name
|
||||
email := ""
|
||||
if e, ok := app.storage.emails[jfID]; ok {
|
||||
email = e.Addr
|
||||
}
|
||||
for _, ombiUser := range ombiUsers {
|
||||
ombiAddr := ""
|
||||
if a, ok := ombiUser["emailAddress"]; ok && a != nil {
|
||||
ombiAddr = a.(string)
|
||||
}
|
||||
if ombiUser["userName"].(string) == username || (ombiAddr == email && email != "") {
|
||||
return ombiUser, code, err
|
||||
}
|
||||
}
|
||||
return nil, 400, fmt.Errorf("Couldn't find user")
|
||||
}
|
||||
|
||||
// @Summary Get a list of Ombi users.
|
||||
// @Produce json
|
||||
// @Success 200 {object} ombiUsersDTO
|
||||
// @Failure 500 {object} stringResponse
|
||||
// @Router /ombi/users [get]
|
||||
// @Security Bearer
|
||||
// @tags Ombi
|
||||
func (app *appContext) OmbiUsers(gc *gin.Context) {
|
||||
app.debug.Println("Ombi users requested")
|
||||
users, status, err := app.ombi.GetUsers()
|
||||
if err != nil || status != 200 {
|
||||
app.err.Printf("Failed to get users from Ombi (%d): %v", status, err)
|
||||
respond(500, "Couldn't get users", gc)
|
||||
return
|
||||
}
|
||||
userlist := make([]ombiUser, len(users))
|
||||
for i, data := range users {
|
||||
userlist[i] = ombiUser{
|
||||
Name: data["userName"].(string),
|
||||
ID: data["id"].(string),
|
||||
}
|
||||
}
|
||||
gc.JSON(200, ombiUsersDTO{Users: userlist})
|
||||
}
|
||||
|
||||
// @Summary Store Ombi user template in an existing profile.
|
||||
// @Produce json
|
||||
// @Param ombiUser body ombiUser true "User to source settings from"
|
||||
// @Param profile path string true "Name of profile to store in"
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 400 {object} boolResponse
|
||||
// @Failure 500 {object} stringResponse
|
||||
// @Router /profiles/ombi/{profile} [post]
|
||||
// @Security Bearer
|
||||
// @tags Ombi
|
||||
func (app *appContext) SetOmbiProfile(gc *gin.Context) {
|
||||
var req ombiUser
|
||||
gc.BindJSON(&req)
|
||||
profileName := gc.Param("profile")
|
||||
profile, ok := app.storage.profiles[profileName]
|
||||
if !ok {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
template, code, err := app.ombi.TemplateByID(req.ID)
|
||||
if err != nil || code != 200 || len(template) == 0 {
|
||||
app.err.Printf("Couldn't get user from Ombi (%d): %v", code, err)
|
||||
respond(500, "Couldn't get user", gc)
|
||||
return
|
||||
}
|
||||
profile.Ombi = template
|
||||
app.storage.profiles[profileName] = profile
|
||||
if err := app.storage.storeProfiles(); err != nil {
|
||||
respond(500, "Failed to store profile", gc)
|
||||
app.err.Printf("Failed to store profiles: %v", err)
|
||||
return
|
||||
}
|
||||
respondBool(204, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Remove ombi user template from a profile.
|
||||
// @Produce json
|
||||
// @Param profile path string true "Name of profile to store in"
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 400 {object} boolResponse
|
||||
// @Failure 500 {object} stringResponse
|
||||
// @Router /profiles/ombi/{profile} [delete]
|
||||
// @Security Bearer
|
||||
// @tags Ombi
|
||||
func (app *appContext) DeleteOmbiProfile(gc *gin.Context) {
|
||||
profileName := gc.Param("profile")
|
||||
profile, ok := app.storage.profiles[profileName]
|
||||
if !ok {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
profile.Ombi = nil
|
||||
app.storage.profiles[profileName] = profile
|
||||
if err := app.storage.storeProfiles(); err != nil {
|
||||
respond(500, "Failed to store profile", gc)
|
||||
app.err.Printf("Failed to store profiles: %v", err)
|
||||
return
|
||||
}
|
||||
respondBool(204, true, gc)
|
||||
}
|
121
api-profiles.go
Normal file
121
api-profiles.go
Normal file
@ -0,0 +1,121 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Summary Get a list of profiles
|
||||
// @Produce json
|
||||
// @Success 200 {object} getProfilesDTO
|
||||
// @Router /profiles [get]
|
||||
// @Security Bearer
|
||||
// @tags Profiles & Settings
|
||||
func (app *appContext) GetProfiles(gc *gin.Context) {
|
||||
app.storage.loadProfiles()
|
||||
app.debug.Println("Profiles requested")
|
||||
out := getProfilesDTO{
|
||||
DefaultProfile: app.storage.defaultProfile,
|
||||
Profiles: map[string]profileDTO{},
|
||||
}
|
||||
for name, p := range app.storage.profiles {
|
||||
out.Profiles[name] = profileDTO{
|
||||
Admin: p.Admin,
|
||||
LibraryAccess: p.LibraryAccess,
|
||||
FromUser: p.FromUser,
|
||||
Ombi: p.Ombi != nil,
|
||||
}
|
||||
}
|
||||
gc.JSON(200, out)
|
||||
}
|
||||
|
||||
// @Summary Set the default profile to use.
|
||||
// @Produce json
|
||||
// @Param profileChangeDTO body profileChangeDTO true "Default profile object"
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 500 {object} stringResponse
|
||||
// @Router /profiles/default [post]
|
||||
// @Security Bearer
|
||||
// @tags Profiles & Settings
|
||||
func (app *appContext) SetDefaultProfile(gc *gin.Context) {
|
||||
req := profileChangeDTO{}
|
||||
gc.BindJSON(&req)
|
||||
app.info.Printf("Setting default profile to \"%s\"", req.Name)
|
||||
if _, ok := app.storage.profiles[req.Name]; !ok {
|
||||
app.err.Printf("Profile not found: \"%s\"", req.Name)
|
||||
respond(500, "Profile not found", gc)
|
||||
return
|
||||
}
|
||||
for name, profile := range app.storage.profiles {
|
||||
if name == req.Name {
|
||||
profile.Admin = true
|
||||
app.storage.profiles[name] = profile
|
||||
} else {
|
||||
profile.Admin = false
|
||||
}
|
||||
}
|
||||
app.storage.defaultProfile = req.Name
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Create a profile based on a Jellyfin user's settings.
|
||||
// @Produce json
|
||||
// @Param newProfileDTO body newProfileDTO true "New profile object"
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 500 {object} stringResponse
|
||||
// @Router /profiles [post]
|
||||
// @Security Bearer
|
||||
// @tags Profiles & Settings
|
||||
func (app *appContext) CreateProfile(gc *gin.Context) {
|
||||
app.info.Println("Profile creation requested")
|
||||
var req newProfileDTO
|
||||
gc.BindJSON(&req)
|
||||
app.jf.CacheExpiry = time.Now()
|
||||
user, status, err := app.jf.UserByID(req.ID, false)
|
||||
if !(status == 200 || status == 204) || err != nil {
|
||||
app.err.Printf("Failed to get user from Jellyfin (%d): %v", status, err)
|
||||
respond(500, "Couldn't get user", gc)
|
||||
return
|
||||
}
|
||||
profile := Profile{
|
||||
FromUser: user.Name,
|
||||
Policy: user.Policy,
|
||||
}
|
||||
app.debug.Printf("Creating profile from user \"%s\"", user.Name)
|
||||
if req.Homescreen {
|
||||
profile.Configuration = user.Configuration
|
||||
profile.Displayprefs, status, err = app.jf.GetDisplayPreferences(req.ID)
|
||||
if !(status == 200 || status == 204) || err != nil {
|
||||
app.err.Printf("Failed to get DisplayPrefs (%d): %v", status, err)
|
||||
respond(500, "Couldn't get displayprefs", gc)
|
||||
return
|
||||
}
|
||||
}
|
||||
app.storage.loadProfiles()
|
||||
app.storage.profiles[req.Name] = profile
|
||||
app.storage.storeProfiles()
|
||||
app.storage.loadProfiles()
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Delete an existing profile
|
||||
// @Produce json
|
||||
// @Param profileChangeDTO body profileChangeDTO true "Delete profile object"
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Router /profiles [delete]
|
||||
// @Security Bearer
|
||||
// @tags Profiles & Settings
|
||||
func (app *appContext) DeleteProfile(gc *gin.Context) {
|
||||
req := profileChangeDTO{}
|
||||
gc.BindJSON(&req)
|
||||
name := req.Name
|
||||
if _, ok := app.storage.profiles[name]; ok {
|
||||
if app.storage.defaultProfile == name {
|
||||
app.storage.defaultProfile = ""
|
||||
}
|
||||
delete(app.storage.profiles, name)
|
||||
}
|
||||
app.storage.storeProfiles()
|
||||
respondBool(200, true, gc)
|
||||
}
|
1140
api-users.go
Normal file
1140
api-users.go
Normal file
File diff suppressed because it is too large
Load Diff
12
go.mod
12
go.mod
@ -12,6 +12,8 @@ replace github.com/hrfee/jfa-go/logger => ./logger
|
||||
|
||||
replace github.com/hrfee/jfa-go/linecache => ./linecache
|
||||
|
||||
replace github.com/hrfee/jfa-go/api => ./api
|
||||
|
||||
require (
|
||||
github.com/bwmarrin/discordgo v0.23.3-0.20211228023845-29269347e820
|
||||
github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
||||
@ -24,6 +26,7 @@ require (
|
||||
github.com/gin-contrib/pprof v1.3.0
|
||||
github.com/gin-contrib/static v0.0.1
|
||||
github.com/gin-gonic/gin v1.7.7
|
||||
github.com/go-openapi/swag v0.21.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.9.0 // indirect
|
||||
github.com/go-stack/stack v1.8.1 // indirect
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
|
||||
@ -49,16 +52,17 @@ require (
|
||||
github.com/steambap/captcha v1.4.1
|
||||
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2
|
||||
github.com/swaggo/gin-swagger v1.3.3
|
||||
github.com/swaggo/swag v1.7.8 // indirect
|
||||
github.com/swaggo/swag v1.8.0 // indirect
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.4 // indirect
|
||||
github.com/ugorji/go v1.2.6 // indirect
|
||||
github.com/urfave/cli/v2 v2.4.0 // indirect
|
||||
github.com/writeas/go-strip-markdown v2.0.1+incompatible
|
||||
github.com/xhit/go-simple-mail/v2 v2.10.0
|
||||
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce // indirect
|
||||
golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba // indirect
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
|
||||
golang.org/x/tools v0.1.8 // indirect
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
|
||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect
|
||||
golang.org/x/tools v0.1.10 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/ini.v1 v1.66.2
|
||||
maunium.net/go/mautrix v0.10.7
|
||||
|
16
go.sum
16
go.sum
@ -24,6 +24,8 @@ github.com/bwmarrin/discordgo v0.23.3-0.20211228023845-29269347e820 h1:MIW5DnBVJ
|
||||
github.com/bwmarrin/discordgo v0.23.3-0.20211228023845-29269347e820/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -104,6 +106,8 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
|
||||
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU=
|
||||
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
@ -257,6 +261,8 @@ github.com/swaggo/swag v1.6.7/go.mod h1:xDhTyuFIujYiN3DKWC/H/83xcfHp+UE/IzWWampG
|
||||
github.com/swaggo/swag v1.7.4/go.mod h1:zD8h6h4SPv7t3l+4BKdRquqW1ASWjKZgT6Qv9z3kNqI=
|
||||
github.com/swaggo/swag v1.7.8 h1:w249t0l/kc/DKMGlS0fppNJQxKyJ8heNaUWB6nsH3zc=
|
||||
github.com/swaggo/swag v1.7.8/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU=
|
||||
github.com/swaggo/swag v1.8.0 h1:80NNhvpJcuItNpBDqgJwDuKlMmaZ/OATOzhG3bhcM3w=
|
||||
github.com/swaggo/swag v1.8.0/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU=
|
||||
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.10.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
@ -284,6 +290,8 @@ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijb
|
||||
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/urfave/cli/v2 v2.4.0 h1:m2pxjjDFgDxSPtO8WSdbndj17Wu2y8vOT86wE/tjr+I=
|
||||
github.com/urfave/cli/v2 v2.4.0/go.mod h1:NX9W0zmTvedE5oDoOMs2RTC8RvdK98NTYZE5LbaEYPg=
|
||||
github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6FkrCNfXkvbR+Nbu1ls48pXYcw=
|
||||
github.com/writeas/go-strip-markdown v2.0.1+incompatible/go.mod h1:Rsyu10ZhbEK9pXdk8V6MVnZmTzRG0alMNLMwa0J01fE=
|
||||
github.com/xhit/go-simple-mail/v2 v2.10.0 h1:nib6RaJ4qVh5HD9UE9QJqnUZyWp3upv+Z6CFxaMj0V8=
|
||||
@ -308,6 +316,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -328,6 +338,8 @@ golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba h1:6u6sik+bn/y7vILcYkK3iwTBWN7WtBvB0+SZswQnbf8=
|
||||
golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -358,6 +370,8 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs=
|
||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -377,6 +391,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w=
|
||||
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
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=
|
||||
|
@ -43,7 +43,7 @@
|
||||
"settingsRequiredOrRestartMessage": "Hinweis: {n} zeigt ein erforderliches Feld an, {n} zeigt an, dass Änderungen einen Neustart erfordern.",
|
||||
"settingsSave": "Speichern",
|
||||
"ombiUserDefaults": "Ombi-Benutzerstandardeinstellungen",
|
||||
"ombiUserDefaultsDescription": "Erstelle einen Ombi-Benutzer und konfiguriere ihn, dann wähle ihn unten aus. Seine Einstellungen/Berechtigungen werden gespeichert und auf neue Ombi-Benutzer, erstellt von jfa-go, angewendet",
|
||||
"ombiUserDefaultsDescription": "Erstelle einen Ombi-Benutzer, konfiguriere ihn und wähle ihn dann unten aus. Seine Einstellungen/Berechtigungen werden gespeichert und auf neue Ombi-Benutzer, welche von jfa-go erstellt werden, angewendet, sofern dieses Profil ausgewählt ist.",
|
||||
"userProfiles": "Benutzerprofile",
|
||||
"userProfilesDescription": "Profile werden auf Benutzer angewendet, wenn sie ein Konto erstellen. Ein Profil beinhaltet Bibliothekszugriffsrechte und das Startbildschirmlayout.",
|
||||
"userProfilesIsDefault": "Standard",
|
||||
@ -69,7 +69,7 @@
|
||||
"preview": "Vorschau",
|
||||
"reset": "Zurücksetzen",
|
||||
"edit": "Bearbeiten",
|
||||
"customizeMessages": "E-Mails anpassen",
|
||||
"customizeMessages": "Benachrichtigungen anpassen",
|
||||
"customizeMessagesDescription": "Wenn du jfa-go's E-Mail-Vorlagen nicht benutzen willst, kannst du deinen eigenen unter Verwendung von Markdown erstellen.",
|
||||
"announce": "Ankündigen",
|
||||
"subject": "Betreff",
|
||||
@ -101,7 +101,20 @@
|
||||
"findDiscordUser": "Suche Discord-Benutzer",
|
||||
"linkMatrixDescription": "Gib den Benutzernamen und das Passwort des Benutzers ein, der als Bot verwendet werden soll. Nach dem Absenden wird die App neu gestartet.",
|
||||
"matrixHomeServer": "Adresse des Homeservers",
|
||||
"templates": "Vorlagen"
|
||||
"templates": "Vorlagen",
|
||||
"ombiProfile": "Ombi-Benutzerprofil",
|
||||
"accessJFA": "jfa-go Zugriff",
|
||||
"sendPWRValidFor": "Der Link ist 30m gültig.",
|
||||
"logs": "Logdaten",
|
||||
"setExpiry": "Ablauf setzen",
|
||||
"sendPWRSuccess": "Link zur Passwortrücksetzung versandt.",
|
||||
"sendPWRSuccessManual": "Falls der Benutzer ihn nicht erhalten hat, klicke \"Kopieren\" und sende ihm den Link manuell.",
|
||||
"sendPWR": "Sende Passwortrücksetzung",
|
||||
"sendPWRManual": "Benutzer {n} hat keine Kontaktmöglichkeit hinterlegt. Klicke \"Kopieren\" um einen Link zu erhalten, den du dem Benutzer manuell senden kannst.",
|
||||
"accessJFASettings": "Kann nicht geändert werden, da entweder \"Nur Admin-Benutzer\" oder \"Erlaube allen Jellyfin-Nutzern sich anzumelden\" in Einstellungen > Allgemein aktiviert ist.",
|
||||
"saveAsTemplate": "Als Vorlage speichern",
|
||||
"deleteTemplate": "Vorlage löschen",
|
||||
"templateEnterName": "Gebe einen Namen ein, um diese Vorlage zu speichern."
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "E-Mail-Adresse von {n} geändert.",
|
||||
@ -142,7 +155,9 @@
|
||||
"updateAppliedRefresh": "Update angewendet, bitte aktualisieren.",
|
||||
"telegramVerified": "Telegram-Konto verifiziert.",
|
||||
"accountConnected": "Konto verbunden.",
|
||||
"savedAnnouncement": "Ankündigung gespeichert."
|
||||
"savedAnnouncement": "Ankündigung gespeichert.",
|
||||
"errorSetOmbiProfile": "Ombi-Profil konnte nicht gespeichert werden.",
|
||||
"setOmbiProfile": "Ombi-Profil gespeichert."
|
||||
},
|
||||
"quantityStrings": {
|
||||
"modifySettingsFor": {
|
||||
@ -196,6 +211,10 @@
|
||||
"reEnableUsers": {
|
||||
"singular": "Benutzer {n} wieder aktivieren",
|
||||
"plural": "Benutzer {n} wieder aktivieren"
|
||||
},
|
||||
"setExpiry": {
|
||||
"singular": "Ablauf für {n} Benutzer setzen",
|
||||
"plural": "Ablauf für {n} Benutzer setzen"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -113,7 +113,7 @@
|
||||
"deleteTemplate": "Delete template",
|
||||
"templateEnterName": "Enter a name to save this template.",
|
||||
"accessJFA": "Access jfa-go",
|
||||
"accessJFASettings": "Cannot be changed as this either \"Admin Only\" or \"Allow All\" has been set in Settings > General."
|
||||
"accessJFASettings": "Cannot be changed as either \"Admin Only\" or \"Allow All\" has been set in Settings > General."
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "Changed email address of {n}.",
|
||||
|
@ -112,7 +112,9 @@
|
||||
"sendPWRSuccess": "Wachtwoordreset-link verstuurd.",
|
||||
"sendPWRValidFor": "De link is 30m geldig.",
|
||||
"ombiProfile": "Ombi gebruikersprofiel",
|
||||
"logs": "Logs"
|
||||
"logs": "Logs",
|
||||
"accessJFA": "Toegang tot jfa-go",
|
||||
"accessJFASettings": "Kan niet worden aangepast, omdat \"Alleen beheerders\" of \"Laat alle Jellyfin-gebruikers inloggen\" is aangevinkt in Instellingen > Algemeen."
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "E-mailadres van {n} gewijzigd.",
|
||||
@ -209,6 +211,10 @@
|
||||
"enabledUser": {
|
||||
"singular": "{n} gebruiker ingeschakeld.",
|
||||
"plural": "{n} gebruikers ingeschakeld."
|
||||
},
|
||||
"setExpiry": {
|
||||
"singular": "Stel verloop in voor {n} gebruiker",
|
||||
"plural": "Stel verloop in voor {n} gebruikers"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,8 @@
|
||||
"linkDiscord": "Link Discord",
|
||||
"linkMatrix": "Link Matrix",
|
||||
"send": "Senden",
|
||||
"contactDiscord": "Kontakt über Discord"
|
||||
"contactDiscord": "Kontakt über Discord",
|
||||
"refresh": "Aktualisieren",
|
||||
"required": "Erforderlich"
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,8 @@
|
||||
"send": "Verstuur",
|
||||
"linkDiscord": "Koppel Discord",
|
||||
"linkMatrix": "Koppel Matrix",
|
||||
"contactDiscord": "Stuur Discord bericht"
|
||||
"contactDiscord": "Stuur Discord bericht",
|
||||
"refresh": "Ververs",
|
||||
"required": "Verplicht"
|
||||
}
|
||||
}
|
||||
|
29
lang/common/sl-si.json
Normal file
29
lang/common/sl-si.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Angleščina (ZDA)"
|
||||
},
|
||||
"strings": {
|
||||
"username": "Uporabniško ime",
|
||||
"password": "Geslo",
|
||||
"emailAddress": "E-poštni naslov",
|
||||
"name": "Ime",
|
||||
"submit": "Potrdi",
|
||||
"send": "Pošlji",
|
||||
"success": "Uspeh",
|
||||
"continue": "Nadaljuj",
|
||||
"error": "Napaka",
|
||||
"copy": "Kopiraj",
|
||||
"copied": "Kopirano",
|
||||
"time24h": "24ur format čas",
|
||||
"time12h": "12ur format časa",
|
||||
"linkTelegram": "Poveži Telegram",
|
||||
"contactEmail": "Kontakt preko e-pošte",
|
||||
"contactTelegram": "Kontakt preko Telegrama",
|
||||
"linkDiscord": "Poveži Discord",
|
||||
"linkMatrix": "Poveži Matrix",
|
||||
"contactDiscord": "Kontakt preko Discorda",
|
||||
"theme": "Tema",
|
||||
"refresh": "Osveži",
|
||||
"required": "Obvezno"
|
||||
}
|
||||
}
|
@ -11,14 +11,14 @@
|
||||
"title": "Mitteilung: Benutzer erstellt",
|
||||
"aUserWasCreated": "Ein Benutzer wurde unter Verwendung des Codes {code} erstellt.",
|
||||
"time": "Zeit",
|
||||
"notificationNotice": "Hinweis: Benachrichtigungs-E-Mails können auf dem Administrator-Dashboard umgeschalten werden.",
|
||||
"notificationNotice": "Hinweis: Benachrichtigungen können auf dem Administrator-Dashboard umgeschalten werden.",
|
||||
"name": "Benutzererstellung"
|
||||
},
|
||||
"inviteExpiry": {
|
||||
"title": "Mitteilung: Invite abgelaufen",
|
||||
"inviteExpired": "Invite abgelaufen.",
|
||||
"expiredAt": "Code {code} lief um {time} ab.",
|
||||
"notificationNotice": "Hinweis: Benachrichtigungs-E-Mails können auf dem Administrator-Dashboard umgeschalten werden.",
|
||||
"notificationNotice": "Hinweis: Benachrichtigungen können auf dem Administrator-Dashboard umgeschalten werden.",
|
||||
"name": "Invite Ablaufdatum"
|
||||
},
|
||||
"passwordReset": {
|
||||
|
60
lang/form/ar-aa.json
Normal file
60
lang/form/ar-aa.json
Normal file
@ -0,0 +1,60 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "العربية (AR)"
|
||||
},
|
||||
"strings": {
|
||||
"pageTitle": "أنشاء حساب",
|
||||
"createAccountHeader": "أنشاء حساب",
|
||||
"accountDetails": "التفاصيل",
|
||||
"emailAddress": "البريد الالكتروني",
|
||||
"username": "اسم المستخدم",
|
||||
"password": "كلمة المرور",
|
||||
"reEnterPassword": "تأكيد كلمة المرور",
|
||||
"reEnterPasswordInvalid": "كلمة المرور غير مطابقة.",
|
||||
"createAccountButton": "أنشاء حساب",
|
||||
"passwordRequirementsHeader": "متطلبات كلمة المرور",
|
||||
"successHeader": "تم!",
|
||||
"confirmationRequired": "مطلوب تأكيد البريد الإلكتروني",
|
||||
"confirmationRequiredMessage": "يرجى التحقق من صندوق البريد الإلكتروني الخاص بك للتحقق من عنوانك.",
|
||||
"yourAccountIsValidUntil": "سيكون حسابك ساري المفعول حتى {date}.",
|
||||
"sendPIN": "أرسل رقم التعريف الشخصي أدناه إلى الروبوت ، ثم ارجع إلى هنا لربط حسابك.",
|
||||
"sendPINDiscord": "اكتب {command} في {server_channel} على Discord ، ثم أرسل رقم التعريف الشخصي أدناه.",
|
||||
"matrixEnterUser": "أدخل معرف المستخدم الخاص بك ، واضغط على إرسال ، وسيتم إرسال رمز PIN إليك. أدخله هنا للمتابعة."
|
||||
},
|
||||
"notifications": {
|
||||
"errorUserExists": "المستخدم موجود مسبقا.",
|
||||
"errorInvalidCode": "رمز دعوة غير صالح.",
|
||||
"errorTelegramVerification": "مطلوب التحقق من Telegram.",
|
||||
"errorDiscordVerification": "مطلوب التحقق من الخلاف.",
|
||||
"errorMatrixVerification": "مطلوب التحقق من المصفوفة.",
|
||||
"errorInvalidPIN": "رقم التعريف الشخصي غير صالح.",
|
||||
"errorUnknown": "خطأ غير معروف.",
|
||||
"errorNoEmail": "البريد الإلكتروني مطلوب.",
|
||||
"errorCaptcha": "كلمة التحقق غير صحيحة.",
|
||||
"errorPassword": "تحقق من متطلبات كلمة المرور.",
|
||||
"errorNoMatch": "كلمات المرور غير متطابقة.",
|
||||
"verified": "تم التحقق من الحساب."
|
||||
},
|
||||
"validationStrings": {
|
||||
"length": {
|
||||
"singular": "يجب أن يتألف من {n} حرف على الأقل",
|
||||
"plural": "يجب ألا يقل عدد الأحرف عن {n}"
|
||||
},
|
||||
"uppercase": {
|
||||
"singular": "يجب أن تحتوي على {n} حرف كبير على الأقل",
|
||||
"plural": "يجب ألا يقل عدد الأحرف الكبيرة عن {n}"
|
||||
},
|
||||
"lowercase": {
|
||||
"singular": "يجب أن يتألف من {n} حرف صغير على الأقل",
|
||||
"plural": "يجب ألا يقل عدد الأحرف الصغيرة عن {n}"
|
||||
},
|
||||
"number": {
|
||||
"singular": "يجب أن يحتوي على {n} رقم على الأقل",
|
||||
"plural": "يجب أن يحتوي على {n} رقم على الأقل"
|
||||
},
|
||||
"special": {
|
||||
"singular": "يجب أن يتألف من {n} حرف خاص على الأقل",
|
||||
"plural": "يجب أن يتألف من {n} حرف خاص على الأقل"
|
||||
}
|
||||
}
|
||||
}
|
@ -15,10 +15,10 @@
|
||||
"passwordRequirementsHeader": "Passwortanforderungen",
|
||||
"successHeader": "Erfolgreich!",
|
||||
"confirmationRequired": "E-Mail-Bestätigung erforderlich",
|
||||
"confirmationRequiredMessage": "Bitte überprüfe dein Posteingang und bestätige deine E-Mail-Adresse.",
|
||||
"confirmationRequiredMessage": "Bitte überprüfe deinen Posteingang und bestätige deine E-Mail-Adresse.",
|
||||
"yourAccountIsValidUntil": "Dein Konto wird bis zum {date} gültig sein.",
|
||||
"sendPIN": "Sende die untenstehende PIN an den Bot und komm dann hierher zurück, um dein Konto zu verbinden.",
|
||||
"sendPINDiscord": "Gib auf Discord {command} in {server_channel} ein und sende die untenstehende PIN als DM an den Bot.",
|
||||
"sendPINDiscord": "Gib auf Discord {command} in {server_channel} ein und sende die untenstehende PIN.",
|
||||
"matrixEnterUser": "Gib deine Benutzer-ID ein und drücke auf Absenden. Anschließend erhälst du ein PIN, die hier eingegeben wird um fortzufahren."
|
||||
},
|
||||
"validationStrings": {
|
||||
@ -53,6 +53,9 @@
|
||||
"errorMatrixVerification": "Matrix-Verifizierung erforderlich.",
|
||||
"errorUnknown": "Unbekannter Fehler.",
|
||||
"verified": "Konto verifiziert.",
|
||||
"errorNoEmail": "E-Mail Adresse erforderlich."
|
||||
"errorNoEmail": "E-Mail Adresse erforderlich.",
|
||||
"errorCaptcha": "Captcha falsch.",
|
||||
"errorPassword": "Prüfe die Passwortanforderungen.",
|
||||
"errorNoMatch": "Passwörter stimmen nicht überein."
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@
|
||||
"yourAccountIsValidUntil": "Je account zal geldig zijn tot {date}.",
|
||||
"sendPIN": "Stuur onderstaande pincode naar de bot, en kom daarna hier terug om je account te koppelen.",
|
||||
"matrixEnterUser": "Voer je gebruikers ID in, druk op versturen, en er wordt je een pincode toegestuurd. Vul die hier in om door te gaan.",
|
||||
"sendPINDiscord": "Typ {command} in {server_channel} op Discord, stuur daarna onderstaande pincode via DM naar de bot."
|
||||
"sendPINDiscord": "Typ {command} in {server_channel} op Discord, stuur daarna onderstaande pincode."
|
||||
},
|
||||
"validationStrings": {
|
||||
"length": {
|
||||
@ -53,6 +53,9 @@
|
||||
"errorUnknown": "Onbekende fout.",
|
||||
"errorMatrixVerification": "Matrix-verificatie vereist.",
|
||||
"verified": "Account geverifieerd.",
|
||||
"errorNoEmail": "E-mail vereist."
|
||||
"errorNoEmail": "E-mail vereist.",
|
||||
"errorPassword": "Controleer wachtwoordeisen.",
|
||||
"errorNoMatch": "Wachtwoorden komen niet overeen.",
|
||||
"errorCaptcha": "Captcha incorrect."
|
||||
}
|
||||
}
|
||||
|
60
lang/form/sl-si.json
Normal file
60
lang/form/sl-si.json
Normal file
@ -0,0 +1,60 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Angleščina (ZDA)"
|
||||
},
|
||||
"strings": {
|
||||
"pageTitle": "Ustvari Jellyfin Račun",
|
||||
"createAccountHeader": "Ustvari Račun",
|
||||
"accountDetails": "Podrobnosti",
|
||||
"emailAddress": "E-pošta",
|
||||
"username": "Uporabniško ime",
|
||||
"password": "Geslo",
|
||||
"reEnterPassword": "Ponovno vnesi Geslo",
|
||||
"reEnterPasswordInvalid": "Gesli se ne ujemata.",
|
||||
"createAccountButton": "Ustvari Račun",
|
||||
"passwordRequirementsHeader": "Zahteve za geslo",
|
||||
"successHeader": "Uspeh!",
|
||||
"confirmationRequired": "Potrebna potrditev e-pošte",
|
||||
"confirmationRequiredMessage": "Prosimo preverite svojo e-pošto da potrdite račun.",
|
||||
"yourAccountIsValidUntil": "Vaš račun bo veljaven do {date}.",
|
||||
"sendPIN": "Pošljite spodnji PIN botu, nato pa pridite nazaj da povežete svoj račun.",
|
||||
"sendPINDiscord": "Napišite {command} v {server_channel} na Discordu, nato pa vnesite PIN spodaj.",
|
||||
"matrixEnterUser": "Vnesite svoj uporabniški ID, pritisnite potrdi, in poslan vam bo PIN. Tega vnesite tu, da nadaljujete."
|
||||
},
|
||||
"notifications": {
|
||||
"errorUserExists": "Uporabnik že obstaja.",
|
||||
"errorInvalidCode": "Neveljavna koda povabila.",
|
||||
"errorTelegramVerification": "Potrebna je potrditev preko telegrama.",
|
||||
"errorDiscordVerification": "Potrebna je potrditev preko Discorda.",
|
||||
"errorMatrixVerification": "Potrebna je potrditev preko Matrix.",
|
||||
"errorInvalidPIN": "PIN ni veljaven.",
|
||||
"errorUnknown": "Neznana napaka.",
|
||||
"errorNoEmail": "E-pošta obvezna.",
|
||||
"errorCaptcha": "Captcha je nepravilna.",
|
||||
"errorPassword": "Prosimo preverite zahteve za geslo.",
|
||||
"errorNoMatch": "Gesli se ne ujemata.",
|
||||
"verified": "Račun potrjen."
|
||||
},
|
||||
"validationStrings": {
|
||||
"length": {
|
||||
"singular": "Potrebnih je vsaj {n} znakov",
|
||||
"plural": "Potrebnih je vsaj {n} znakov"
|
||||
},
|
||||
"uppercase": {
|
||||
"singular": "Potreben je vsaj {n} velik znak",
|
||||
"plural": "Potrebnih je vsaj {n} velikih znakov"
|
||||
},
|
||||
"lowercase": {
|
||||
"singular": "Potreben je vsaj {n} majhen znak",
|
||||
"plural": "Potrebnih je vsaj {n} majhnih znakov"
|
||||
},
|
||||
"number": {
|
||||
"singular": "Potrebna je vsaj {n} števka",
|
||||
"plural": "Potrebnih je vsaj {n} števk"
|
||||
},
|
||||
"special": {
|
||||
"singular": "Poteben je vsaj {n} poseben znak",
|
||||
"plural": "Potrebnih je vsaj {n} posebnih znakov"
|
||||
}
|
||||
}
|
||||
}
|
16
lang/pwreset/ar-aa.json
Normal file
16
lang/pwreset/ar-aa.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "العربية (AR)"
|
||||
},
|
||||
"strings": {
|
||||
"passwordReset": "إعادة تعيين كلمة المرور",
|
||||
"reset": "إعادة تعيين",
|
||||
"resetFailed": "فشل إعادة تعيين كلمة المرور",
|
||||
"tryAgain": "حاول مرة اخرى.",
|
||||
"youCanLogin": "يمكنك الآن تسجيل الدخول باستخدام الرمز أدناه ككلمة المرور الخاصة بك.",
|
||||
"youCanLoginOmbi": "يمكنك الآن تسجيل الدخول باستخدام الرمز أدناه ككلمة المرور الخاصة بك.",
|
||||
"youCanLoginPassword": "يمكنك الآن تسجيل الدخول باستخدام الرمز أدناه ككلمة المرور الخاصة بك.",
|
||||
"changeYourPassword": "تأكد من تغيير كلمة المرور الخاصة بك بعد تسجيل الدخول.",
|
||||
"enterYourPassword": "أدخل كلمة المرور الجديدة أدناه."
|
||||
}
|
||||
}
|
@ -8,6 +8,9 @@
|
||||
"tryAgain": "Bitte versuche es erneut.",
|
||||
"youCanLogin": "Du kannst dich nun mit dem unten stehenden Code als Passwort anmelden.",
|
||||
"youCanLoginOmbi": "Du kannst dich jetzt bei Jellyfin und Ombi mit dem unten stehenden Code als Passwort anmelden.",
|
||||
"changeYourPassword": "Achte darauf, dass du dein Passwort nach der Anmeldung änderst."
|
||||
"changeYourPassword": "Achte darauf, dass du dein Passwort nach der Anmeldung änderst.",
|
||||
"youCanLoginPassword": "Du kannst dich nun mit deinem neuen Passwort anmelden. Klicke unten um zu Jellyfin zu gehen.",
|
||||
"reset": "Zurücksetzen",
|
||||
"enterYourPassword": "Gib unten dein neues Passwort ein."
|
||||
}
|
||||
}
|
||||
|
16
lang/pwreset/sl-si.json
Normal file
16
lang/pwreset/sl-si.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Angleščina (ZDA)"
|
||||
},
|
||||
"strings": {
|
||||
"passwordReset": "Ponastavitev gesla",
|
||||
"reset": "Ponastavi",
|
||||
"resetFailed": "Ponastavitev gesla neuspešna",
|
||||
"tryAgain": "Prosimo poskusite ponovno.",
|
||||
"youCanLogin": "Sedaj se lahko prijavite s spodnjo kodo kot svojim geslom.",
|
||||
"youCanLoginOmbi": "Sedaj se lahko prijavite v Jellyfin in Ombi s spodnjo kodo kot svojim geslom.",
|
||||
"youCanLoginPassword": "Sedaj se lahko prijavite s svojim novim geslom. Pritisnite spodnji gumb da nadaljujete na Jellyfin.",
|
||||
"changeYourPassword": "Spremenite svoje geslo po prijavi.",
|
||||
"enterYourPassword": "Vnesite svoje novo geslo spodaj."
|
||||
}
|
||||
}
|
@ -15,7 +15,11 @@
|
||||
"serverAddress": "Serveradresse",
|
||||
"emailSubject": "E-Mail-Betreff",
|
||||
"URL": "URL",
|
||||
"apiKey": "API-Schlüssel"
|
||||
"apiKey": "API-Schlüssel",
|
||||
"errorInvalidUserPass": "Benutzername/Passwort ungültig.",
|
||||
"errorNotAdmin": "Dieser Benutzer darf den Server nicht verwalten.",
|
||||
"errorUserDisabled": "Konto ist eventuell deaktiviert.",
|
||||
"error404": "404, prüfe die interne URL."
|
||||
},
|
||||
"startPage": {
|
||||
"welcome": "Willkommen!",
|
||||
@ -25,7 +29,7 @@
|
||||
},
|
||||
"endPage": {
|
||||
"finished": "Fertig!",
|
||||
"restartMessage": "Es gibt weitere Einstellungen, die du auf der Admin-Seite konfigurieren kannst. Klicke unten, um neu zu starten, dann aktualisiere die Seite.",
|
||||
"restartMessage": "In den Einstellungen kannst du Discord/Telegram/Matrix Bots konfigurieren, Benachrichtigungen anpassen und vieles mehr. Klicke unten, um neu zu starten, aktualisiere dann die Seite.",
|
||||
"refreshPage": "Aktualisieren"
|
||||
},
|
||||
"language": {
|
||||
@ -54,7 +58,9 @@
|
||||
"authorizeWithJellyfin": "Mit Jellyfin/Emby autorisieren: Anmeldedaten werden mit Jellyfin geteilt, was mehrere Benutzer ermöglicht.",
|
||||
"authorizeManual": "Benutzername und Passwort: Lege Benutzername und Passwort manuell fest.",
|
||||
"adminOnly": "Nur Admin-Benutzer (empfohlen)",
|
||||
"emailNotice": "Deine E-Mail-Adresse kann verwendet werden, um Benachrichtigungen zu erhalten."
|
||||
"emailNotice": "Deine E-Mail-Adresse kann verwendet werden, um Benachrichtigungen zu erhalten.",
|
||||
"allowAll": "Erlaube allen Jellyfin-Nutzern sich anzumelden",
|
||||
"allowAllDescription": "Nicht empfohlen. Nach der Einrichtung solltest du einzelnen Benutzern erlauben, sich anzumelden."
|
||||
},
|
||||
"jellyfinEmby": {
|
||||
"title": "Jellyfin/Emby",
|
||||
@ -86,16 +92,16 @@
|
||||
"mailgunApiURL": "API-URL"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Benachrichtigungen",
|
||||
"description": "Wenn aktiviert, kannst du (pro Invite) wählen, eine E-Mail zu erhalten, wenn ein Invite abläuft oder ein Benutzer erstellt wird. Wenn du nicht die Jellyfin-Login-Methode gewählt hast, stelle sicher, dass du deine E-Mail-Adresse angegeben hast."
|
||||
"title": "Admin-Benachrichtigungen",
|
||||
"description": "Wenn aktiviert, kannst du (pro Invite) wählen, eine Benachrichtigung zu erhalten, wenn ein Invite abläuft oder ein Benutzer erstellt wird. Wenn du nicht die Jellyfin-Login-Methode gewählt hast, stelle sicher, dass du deine E-Mail-Adresse angegeben hast, oder füge später eine andere Kontaktmöglichkeit hinzu."
|
||||
},
|
||||
"welcomeEmails": {
|
||||
"title": "Willkommens-E-Mails",
|
||||
"description": "Wenn aktiviert, wird eine E-Mail an neue Benutzer gesendet mit der Jellyfin-/Emby-URL und ihrem Benutzername."
|
||||
"title": "Willkommens-Benachrichtigungen",
|
||||
"description": "Wenn aktiviert, wird eine Benachrichtigung mit der Jellyfin-/Emby-URL und dem Benutzernamen an neue Benutzer gesendet."
|
||||
},
|
||||
"inviteEmails": {
|
||||
"title": "Einladungs-E-Mails",
|
||||
"description": "Wenn aktiviert, kannst du Invites direkt an die E-Mail-Adresse eines Benutzers schicken. Weil du möglicherweise einen Reverse-Proxy verwendest, musst du die URL angeben, von welcher auf Invites zugegriffen wird. Schreibe deine URL-Basis, und füge '/invite' hinzu."
|
||||
"title": "Einladungs-Benachrichtigungen",
|
||||
"description": "Wenn aktiviert, kannst du Invites direkt an die E-Mail-Adresse oder das Discord-/Matrix-Konto eines Benutzers schicken. Weil du möglicherweise einen Reverse-Proxy verwendest, musst du die URL angeben, von welcher auf Invites zugegriffen wird. Schreibe deine URL-Basis, und füge '/invite' hinzu."
|
||||
},
|
||||
"passwordResets": {
|
||||
"title": "Passwortzurücksetzungen",
|
||||
@ -104,7 +110,9 @@
|
||||
"pathToJellyfinNotice": "Wenn du nicht weißt, wo das ist, versuche dein Passwort in Jellyfin zurückzusetzen. Ein Popup mit '<Pfad zu Jellyfin>/passwordreset-*.json' wird angezeigt werden.",
|
||||
"resetLinks": "Einen Link statt einer PIN senden",
|
||||
"resetLinksNotice": "Wenn die Ombi-Integration aktiviert ist, verwende dies, um zurückgesetzte Passwörter von Jellyfin mit Ombi zu synchronisieren.",
|
||||
"resetLinksLanguage": "Standardsprache für den Link zum Zurücksetzen des Passworts"
|
||||
"resetLinksLanguage": "Standardsprache für den Link zum Zurücksetzen des Passworts",
|
||||
"setPassword": "Setze Password durch Link",
|
||||
"setPasswordNotice": "Wenn aktiviert, muss der Benutzer sein Passwort nicht über eine PIN ändern. So wird ebenfalls die Passwortvalidierung erzwungen."
|
||||
},
|
||||
"passwordValidation": {
|
||||
"title": "Passwortüberprüfung",
|
||||
@ -133,5 +141,9 @@
|
||||
"stable": "Stable",
|
||||
"title": "Aktualisierungen",
|
||||
"description": "Aktiviere, um informiert zu werden, wenn neue Aktualisierungen verfügbar sind. jfa-go wird {n} alle 30 Minuten überprüfen. Keine IP-Adressen oder personenbezogene Daten werden gesammelt."
|
||||
},
|
||||
"messages": {
|
||||
"title": "Mitteilungen",
|
||||
"description": "jfa-go kann Passwortrücksetzungen und verschiedene Benachrichtigungen per E-Mail, Discord, Telegram und/oder Matrix verschicken. E-Mail kannst du unten einrichten, die Anderen später in den Einstellungen. Anweisungen findest du im {n}. Falls nicht benötigt, kannst du diese Funktionen hier deaktivieren."
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,8 @@
|
||||
"errorInvalidUserPass": "Invalid username/password.",
|
||||
"errorNotAdmin": "User is not allowed to manage server.",
|
||||
"errorUserDisabled": "User may be disabled.",
|
||||
"error404": "404, check the internal URL."
|
||||
"error404": "404, check the internal URL.",
|
||||
"errorConnectionRefused": "Connection refused."
|
||||
},
|
||||
"startPage": {
|
||||
"welcome": "Welcome!",
|
||||
|
@ -58,7 +58,9 @@
|
||||
"authorizeWithJellyfin": "Jellyfin/Emby authorisatie: Inloggegevens worden gedeeld met Jellyfin, meerdere gebruikers mogelijk.",
|
||||
"authorizeManual": "Gebruikersnaam en wachtwoord: Stel handmatig een gebruikersnaam en wachtwoord in.",
|
||||
"adminOnly": "Alleen beheerders (aanbevolen)",
|
||||
"emailNotice": "Je e-mailadres kan gebruikt worden om meldingen te ontvangen."
|
||||
"emailNotice": "Je e-mailadres kan gebruikt worden om meldingen te ontvangen.",
|
||||
"allowAll": "Laat alle Jellyfin-gebruikers inloggen",
|
||||
"allowAllDescription": "Afgeraden, je kunt beter individuele gebruikers toegang geven na de setup."
|
||||
},
|
||||
"jellyfinEmby": {
|
||||
"title": "Jellyfin/Emby",
|
||||
|
149
lang/setup/sl-si.json
Normal file
149
lang/setup/sl-si.json
Normal file
@ -0,0 +1,149 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Angleščina (ZDA)"
|
||||
},
|
||||
"strings": {
|
||||
"pageTitle": "",
|
||||
"next": "",
|
||||
"back": "",
|
||||
"optional": "",
|
||||
"serverType": "",
|
||||
"disabled": "",
|
||||
"enabled": "",
|
||||
"port": "",
|
||||
"message": "",
|
||||
"serverAddress": "",
|
||||
"emailSubject": "",
|
||||
"URL": "",
|
||||
"apiKey": "",
|
||||
"errorInvalidUserPass": "",
|
||||
"errorNotAdmin": "",
|
||||
"errorUserDisabled": "",
|
||||
"error404": ""
|
||||
},
|
||||
"startPage": {
|
||||
"welcome": "",
|
||||
"pressStart": "",
|
||||
"httpsNotice": "",
|
||||
"start": ""
|
||||
},
|
||||
"endPage": {
|
||||
"finished": "",
|
||||
"restartMessage": "",
|
||||
"refreshPage": ""
|
||||
},
|
||||
"language": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"defaultAdminLang": "",
|
||||
"defaultFormLang": "",
|
||||
"defaultEmailLang": ""
|
||||
},
|
||||
"general": {
|
||||
"title": "",
|
||||
"listenAddress": "",
|
||||
"urlBase": "",
|
||||
"urlBaseNotice": "",
|
||||
"lightTheme": "",
|
||||
"darkTheme": "",
|
||||
"useHTTPS": "",
|
||||
"httpsPort": "",
|
||||
"useHTTPSNotice": "",
|
||||
"pathToCertificate": "",
|
||||
"pathToKeyFile": ""
|
||||
},
|
||||
"updates": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"updateChannel": "",
|
||||
"stable": "",
|
||||
"unstable": ""
|
||||
},
|
||||
"login": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"authorizeWithJellyfin": "",
|
||||
"authorizeManual": "",
|
||||
"adminOnly": "",
|
||||
"allowAll": "",
|
||||
"allowAllDescription": "",
|
||||
"emailNotice": ""
|
||||
},
|
||||
"jellyfinEmby": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"embyNotice": "",
|
||||
"internal": "",
|
||||
"external": "",
|
||||
"replaceJellyfin": "",
|
||||
"replaceJellyfinNotice": "",
|
||||
"addressExternalNotice": "",
|
||||
"testConnection": ""
|
||||
},
|
||||
"ombi": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"apiKeyNotice": ""
|
||||
},
|
||||
"messages": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
},
|
||||
"email": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"method": "",
|
||||
"useEmailAsUsername": "",
|
||||
"useEmailAsUsernameNotice": "",
|
||||
"fromAddress": "",
|
||||
"senderName": "",
|
||||
"dateFormat": "",
|
||||
"dateFormatNotice": "",
|
||||
"encryption": "",
|
||||
"mailgunApiURL": ""
|
||||
},
|
||||
"notifications": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
},
|
||||
"welcomeEmails": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
},
|
||||
"inviteEmails": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
},
|
||||
"passwordResets": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"pathToJellyfin": "",
|
||||
"pathToJellyfinNotice": "",
|
||||
"resetLinks": "",
|
||||
"resetLinksNotice": "",
|
||||
"resetLinksLanguage": "",
|
||||
"setPassword": "",
|
||||
"setPasswordNotice": ""
|
||||
},
|
||||
"passwordValidation": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"length": "",
|
||||
"uppercase": "",
|
||||
"lowercase": "",
|
||||
"numbers": "",
|
||||
"special": ""
|
||||
},
|
||||
"helpMessages": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"contactMessage": "",
|
||||
"contactMessageNotice": "",
|
||||
"helpMessage": "",
|
||||
"helpMessageNotice": "",
|
||||
"successMessage": "",
|
||||
"successMessageNotice": "",
|
||||
"emailMessage": "",
|
||||
"emailMessageNotice": ""
|
||||
}
|
||||
}
|
16
lang/telegram/ar-AA.json
Normal file
16
lang/telegram/ar-AA.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "العربية (AR)"
|
||||
},
|
||||
"strings": {
|
||||
"startMessage": "مرحبا!\nضع رقمك السري للتحقق من حسابك",
|
||||
"discordStartMessage": "مرحبا!\nضع رقمك السري `/pin <PIN>` للتحقق من حسابك.",
|
||||
"matrixStartMessage": "أهلا\nأدخل رقم التعريف الشخصي أدناه في صفحة الاشتراك في Jellyfin للتحقق من حسابك.",
|
||||
"invalidPIN": "رقم التعريف الشخصي هذا غير صالح ، حاول مرة أخرى.",
|
||||
"pinSuccess": "تم! يمكنك الآن العودة إلى صفحة التسجيل.",
|
||||
"languageMessage": "ملاحظة: اطلع على اللغات المتاحة باستخدام {command} ، وقم بتعيين اللغة باستخدام {command} <language code>.",
|
||||
"languageMessageDiscord": "ملاحظة: اضبط لغتك باستخدام / lang <language name>.",
|
||||
"languageSet": "تم تعيين اللغة على {language}.",
|
||||
"discordDMs": "يرجى التحقق من الرسائل المباشرة الخاصة بك للحصول على رد."
|
||||
}
|
||||
}
|
@ -3,10 +3,14 @@
|
||||
"name": "Deutsch (DE)"
|
||||
},
|
||||
"strings": {
|
||||
"startMessage": "Hallo,\ngib deinen Jellyfin-PIN-Code ein, um dein Konto zu verifizieren.",
|
||||
"matrixStartMessage": "Hallo,\ngib den untenstehenden PIN auf der Anmeldeseite von Jellyfin ein, um dein Konto zu verifizieren.",
|
||||
"startMessage": "Hi!\nGib deinen Jellyfin-PIN-Code ein, um dein Konto zu verifizieren.",
|
||||
"matrixStartMessage": "Hi!\nGib die untenstehende PIN auf der Anmeldeseite von Jellyfin ein, um dein Konto zu verifizieren.",
|
||||
"invalidPIN": "Diese PIN war ungültig, versuche es erneut.",
|
||||
"pinSuccess": "Erfolg! Du kannst nun zur Anmeldeseite zurückkehren.",
|
||||
"languageMessage": "Hinweis: Zeige verfügbare Sprachen mit {command} an und stelle mit {command} <Sprachcode> die gewünschte ein."
|
||||
"languageMessage": "Hinweis: Mit {command} kannst du alle verfügbaren Sprachen sehen und mit {command} <Sprachcode> die gewünschte Sprache einstellen.",
|
||||
"discordStartMessage": "Hi!\n Gib \"/pin\" gefolgt von deiner PIN ein, um dein Konto zu verifizieren.",
|
||||
"languageMessageDiscord": "Hinweis: Du kannst deine Sprache mit \"/lang <Sprache>\" ändern.",
|
||||
"languageSet": "Sprache auf {language} geändert.",
|
||||
"discordDMs": "Schaue in deinen DMs nach einer Antwort."
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,10 @@
|
||||
"matrixStartMessage": "Hallo\nVoer onderstaande pincode in op de Jellyfin aanmeldpagina om je account te verifiëren.",
|
||||
"invalidPIN": "Die pincode was ongeldig, probeer het nogmaals.",
|
||||
"pinSuccess": "Succes! Je kunt nu teruggaan naar de aanmeldpagina.",
|
||||
"languageMessage": "Opmerking: Bekijk beschikbare talen met {command}, en stel de taal in met {command} <taalcode>."
|
||||
"languageMessage": "Opmerking: Bekijk beschikbare talen met {command}, en stel de taal in met {command} <taalcode>.",
|
||||
"discordStartMessage": "Hallo!\nVoer je pincode in met `/pin <PINCODE>` om je account te verifiëren.",
|
||||
"languageMessageDiscord": "Opmerking: stel je taal in met /lang <taal>.",
|
||||
"languageSet": "Taal ingesteld als {language}.",
|
||||
"discordDMs": "Bekijk alsjeblieft je DMs voor een antwoord."
|
||||
}
|
||||
}
|
||||
|
16
lang/telegram/sl-si.json
Normal file
16
lang/telegram/sl-si.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Angleščina (ZDA)"
|
||||
},
|
||||
"strings": {
|
||||
"startMessage": "Pozdravljeni!\nVnesite svoj Jellyfin PIN da potrdite svoj račun.",
|
||||
"discordStartMessage": "Pozdravljeni!\n Vnesite svoj PIN v formatu `/pin <PIN>` da potrdite svoj račun.",
|
||||
"matrixStartMessage": "Pozdravljeni\nVnesite spodnji PIN na Jellyfin stran za registracijo da potrdite svoj račun.",
|
||||
"invalidPIN": "Ta PIN je neveljaven, prosimo poskusite ponovno.",
|
||||
"pinSuccess": "Uspeh! Sedaj se lahko vrnete na stran za registracijo.",
|
||||
"languageMessage": "Opomin: Druge jezike lahko vidite s pomočjo {command}, in ga nastavite s pomočjo {command} <language code>.",
|
||||
"languageMessageDiscord": "Opomin: nastavite jezik z ukazom /lang <language name>.",
|
||||
"languageSet": "Jezik nastavljen na {language}.",
|
||||
"discordDMs": "Prosimo preverite svoja zasebna sporočila za odziv."
|
||||
}
|
||||
}
|
16
package-lock.json
generated
16
package-lock.json
generated
@ -20,7 +20,6 @@
|
||||
"jsdom": "^19.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"mjml": "^4.12.0",
|
||||
"nightwind": "github:yonson2/nightwind",
|
||||
"perl-regex": "^1.0.4",
|
||||
"postcss": "^8.4.5",
|
||||
"remixicon": "^2.5.0",
|
||||
@ -4067,15 +4066,6 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/nightwind": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "git+ssh://git@github.com/yonson2/nightwind.git#755e4b34e0cd782291822b88ad969c69404900da",
|
||||
"integrity": "sha512-eibdkEdOFtBj/1iTk3W0CP9+GXwW1OXcoLBm+Pf7Sq5JLRmailuhaxGrKlNjZH9Wn8yYPO+LuYHg3NMG27Gjuw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"tailwindcss": ">= 2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/no-case": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz",
|
||||
@ -10163,12 +10153,6 @@
|
||||
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==",
|
||||
"dev": true
|
||||
},
|
||||
"nightwind": {
|
||||
"version": "git+ssh://git@github.com/yonson2/nightwind.git#755e4b34e0cd782291822b88ad969c69404900da",
|
||||
"integrity": "sha512-eibdkEdOFtBj/1iTk3W0CP9+GXwW1OXcoLBm+Pf7Sq5JLRmailuhaxGrKlNjZH9Wn8yYPO+LuYHg3NMG27Gjuw==",
|
||||
"from": "nightwind@github:yonson2/nightwind",
|
||||
"requires": {}
|
||||
},
|
||||
"no-case": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz",
|
||||
|
@ -28,7 +28,6 @@
|
||||
"jsdom": "^19.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"mjml": "^4.12.0",
|
||||
"nightwind": "github:yonson2/nightwind",
|
||||
"perl-regex": "^1.0.4",
|
||||
"postcss": "^8.4.5",
|
||||
"remixicon": "^2.5.0",
|
||||
|
Loading…
Reference in New Issue
Block a user