mirror of
https://github.com/hrfee/jfa-go.git
synced 2026-03-21 15:10:36 +01:00
Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f04411e137 | ||
|
|
1336a87ae2 | ||
|
|
872c366384 | ||
|
|
762d5325fb | ||
|
|
7f37633423 | ||
|
|
8ec4031ba3 | ||
|
|
4c10996c09 | ||
|
|
833d02b032 | ||
|
|
30198fab87 | ||
|
|
51768958c6 | ||
|
|
3e55cd1e31 | ||
|
|
35f0fead53 | ||
|
|
a95d8bff29 | ||
|
|
48332a4ffa | ||
|
|
2266bbc320 | ||
|
|
b682685a3b | ||
|
|
91411437e2 | ||
|
|
119bed7024 | ||
|
|
6d70a5b24b | ||
|
|
a99ee04aca | ||
|
|
3ca2315290 | ||
|
|
d4bcf229e9 | ||
|
|
3950455a3f | ||
|
|
7e8e242db0 | ||
|
|
cda90f20af | ||
|
|
8ba393ebc0 | ||
|
|
2de1570a98 | ||
|
|
6b01e0d44d | ||
|
|
af4dcd1e2a | ||
|
|
a8ce68959d | ||
|
|
05bc38565c | ||
|
|
574ca4734d | ||
|
|
0957dd58c2 | ||
|
|
4db5d96bb1 | ||
|
|
76c19731cb | ||
|
|
fea368aaae | ||
|
|
1f8bc027c8 | ||
|
|
f2240ebf0d | ||
|
|
9b9f34ae96 | ||
|
|
86559d5c76 | ||
|
|
40ec5b9933 | ||
|
|
fbb9f20026 | ||
|
|
d5a33cf242 | ||
|
|
c35fdc2cbe | ||
|
|
84d5bc8f67 | ||
|
|
b8d9d22545 | ||
|
|
788afa1025 | ||
|
|
6ca3ab899c | ||
|
|
d4096d0062 | ||
|
|
306ede47d6 | ||
|
|
fc0e86ffd8 | ||
|
|
729fc7baf7 | ||
|
|
2d83e9ff7e |
@@ -153,12 +153,8 @@ type: docker
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: golang:latest
|
||||
image: hrfee/jfa-go-build-docker:latest
|
||||
commands:
|
||||
- apt-get update -y
|
||||
- apt-get install build-essential python3-pip curl software-properties-common sed upx gcc libgtk-3-dev libappindicator3-dev gcc-mingw-w64-x86-64 -y
|
||||
- (curl -sL https://deb.nodesource.com/setup_14.x | bash -)
|
||||
- apt-get install nodejs
|
||||
- curl -sL https://git.io/goreleaser > goreleaser
|
||||
- chmod +x goreleaser
|
||||
- ./scripts/version.sh ./goreleaser --snapshot --skip-publish --rm-dist
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,4 +1,6 @@
|
||||
node_modules/
|
||||
site/node_modules/
|
||||
site/out/
|
||||
mail/*.html
|
||||
dist/
|
||||
build/
|
||||
@@ -15,3 +17,5 @@ server.crt
|
||||
instructions-debian.txt
|
||||
cl.md
|
||||
./telegram/
|
||||
mautrix/
|
||||
matacc.txt
|
||||
|
||||
@@ -17,4 +17,4 @@ Prefix each of these with `make DEBUG=on INTERNAL=off `:
|
||||
* `email` will compile email mjml, and copy the text versions in to `build/data`.
|
||||
* `copy` will copy iconography, html, language files and static data into `build/data`.
|
||||
|
||||
See the [wiki](https://github.com/hrfee/jfa-go/wiki/Build) for more info.
|
||||
See the [wiki](https://wiki.jfa-go.com/docs/build/binary/) for more info.
|
||||
|
||||
@@ -6,7 +6,7 @@ RUN apt-get update -y \
|
||||
&& apt-get install build-essential python3-pip curl software-properties-common sed -y \
|
||||
&& (curl -sL https://deb.nodesource.com/setup_14.x | bash -) \
|
||||
&& apt-get install nodejs \
|
||||
&& (cd /opt/build; make configuration npm email typescript bundle-css swagger copy INTERNAL=off GOESBUILD=on) \
|
||||
&& (cd /opt/build; make configuration npm email typescript bundle-css inline swagger copy INTERNAL=off GOESBUILD=on) \
|
||||
&& sed -i 's#id="password_resets-watch_directory" placeholder="/config/jellyfin"#id="password_resets-watch_directory" value="/jf" disabled#g' /opt/build/build/data/html/setup.html
|
||||
|
||||
|
||||
|
||||
3
LICENSE
3
LICENSE
@@ -1,3 +1,5 @@
|
||||
---jfa-go---
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Harvey Tindall
|
||||
@@ -19,3 +21,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
20
Makefile
20
Makefile
@@ -18,22 +18,30 @@ else ifneq ($(UPDATER), off)
|
||||
LDFLAGS := $(LDFLAGS) -X main.updater=$(UPDATER)
|
||||
endif
|
||||
|
||||
|
||||
|
||||
INTERNAL ?= on
|
||||
TRAY ?= off
|
||||
E2EE ?= off
|
||||
TAGS := -tags "
|
||||
|
||||
ifeq ($(INTERNAL), on)
|
||||
TAGS :=
|
||||
DATA := data
|
||||
else
|
||||
DATA := build/data
|
||||
TAGS := -tags external
|
||||
TAGS := $(TAGS) external
|
||||
endif
|
||||
|
||||
TRAY ?= off
|
||||
ifeq ($(INTERNAL)$(TRAY), offon)
|
||||
ifeq ($(TRAY), on)
|
||||
TAGS := $(TAGS) tray
|
||||
else ifeq ($(INTERNAL)$(TRAY), onon)
|
||||
TAGS := -tags tray
|
||||
endif
|
||||
|
||||
ifeq ($(E2EE), on)
|
||||
TAGS := $(TAGS) e2ee
|
||||
endif
|
||||
|
||||
TAGS := $(TAGS)"
|
||||
|
||||
OS := $(shell go env GOOS)
|
||||
ifeq ($(TRAY)$(OS), onwindows)
|
||||
LDFLAGS := $(LDFLAGS) -H=windowsgui
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||

|
||||
[](https://drone.hrfee.dev/hrfee/jfa-go)
|
||||
[](https://hub.docker.com/r/hrfee/jfa-go)
|
||||
[](https://weblate.hrfee.pw/engage/jfa-go/)
|
||||
[](https://weblate.jfa-go.com/engage/jfa-go/)
|
||||
[](https://wiki.jfa-go.com)
|
||||
|
||||
##### Downloads:
|
||||
##### [docker](#docker) | [debian/ubuntu](#debian) | [arch (aur)](#aur) | [other platforms](#other-platforms)
|
||||
@@ -102,7 +103,7 @@ Run the executable to start.
|
||||
#### Build from source
|
||||
If you're using docker, a Dockerfile is provided that builds from source.
|
||||
|
||||
Otherwise, full build instructions can be found [here](https://github.com/hrfee/jfa-go/wiki/Build).
|
||||
Otherwise, full build instructions can be found [here](https://wiki.jfa-go.com/docs/build/).
|
||||
|
||||
#### Usage
|
||||
Simply run `jfa-go` to start the application. A setup wizard will start on `localhost:8056` (or your own specified address). Upon completion, refresh the page.
|
||||
@@ -150,6 +151,6 @@ If you're switching from jellyfin-accounts, copy your existing `~/.jf-accounts`
|
||||
#### Contributing
|
||||
See [CONTRIBUTING.md](https://github.com/hrfee/jfa-go/blob/main/CONTRIBUTING.md).
|
||||
##### Translation
|
||||
[](https://weblate.hrfee.pw/engage/jfa-go/)
|
||||
[](https://weblate.jfa-go.com/engage/jfa-go/)
|
||||
|
||||
For translations, use the weblate instance [here](https://weblate.hrfee.pw/engage/jfa-go/). You can login with github.
|
||||
For translations, use the weblate instance [here](https://weblate.jfa-go.com/engage/jfa-go/). You can login with github.
|
||||
|
||||
98
api.go
98
api.go
@@ -820,6 +820,82 @@ func (app *appContext) Announce(gc *gin.Context) {
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Save an announcement as a template for use or editing later.
|
||||
// @Produce json
|
||||
// @Param announcementTemplate body announcementTemplate true "Announcement request object"
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 500 {object} boolResponse
|
||||
// @Router /users/announce/template [post]
|
||||
// @Security Bearer
|
||||
// @tags Users
|
||||
func (app *appContext) SaveAnnounceTemplate(gc *gin.Context) {
|
||||
var req announcementTemplate
|
||||
gc.BindJSON(&req)
|
||||
if !messagesEnabled {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
app.storage.announcements[req.Name] = req
|
||||
if err := app.storage.storeAnnouncements(); err != nil {
|
||||
respondBool(500, false, gc)
|
||||
app.err.Printf("Failed to store announcement templates: %v", err)
|
||||
return
|
||||
}
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Save an announcement as a template for use or editing later.
|
||||
// @Produce json
|
||||
// @Success 200 {object} getAnnouncementsDTO
|
||||
// @Router /users/announce/template [get]
|
||||
// @Security Bearer
|
||||
// @tags Users
|
||||
func (app *appContext) GetAnnounceTemplates(gc *gin.Context) {
|
||||
resp := &getAnnouncementsDTO{make([]string, len(app.storage.announcements))}
|
||||
i := 0
|
||||
for name := range app.storage.announcements {
|
||||
resp.Announcements[i] = name
|
||||
i++
|
||||
}
|
||||
gc.JSON(200, resp)
|
||||
}
|
||||
|
||||
// @Summary Get an announcement template.
|
||||
// @Produce json
|
||||
// @Success 200 {object} announcementTemplate
|
||||
// @Failure 400 {object} boolResponse
|
||||
// @Param name path string true "name of template"
|
||||
// @Router /users/announce/template/{name} [get]
|
||||
// @Security Bearer
|
||||
// @tags Users
|
||||
func (app *appContext) GetAnnounceTemplate(gc *gin.Context) {
|
||||
name := gc.Param("name")
|
||||
if announcement, ok := app.storage.announcements[name]; ok {
|
||||
gc.JSON(200, announcement)
|
||||
return
|
||||
}
|
||||
respondBool(400, false, gc)
|
||||
}
|
||||
|
||||
// @Summary Delete an announcement template.
|
||||
// @Produce json
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 500 {object} boolResponse
|
||||
// @Param name path string true "name of template"
|
||||
// @Router /users/announce/template/{name} [delete]
|
||||
// @Security Bearer
|
||||
// @tags Users
|
||||
func (app *appContext) DeleteAnnounceTemplate(gc *gin.Context) {
|
||||
name := gc.Param("name")
|
||||
delete(app.storage.announcements, name)
|
||||
if err := app.storage.storeAnnouncements(); err != nil {
|
||||
respondBool(500, false, gc)
|
||||
app.err.Printf("Failed to store announcement templates: %v", err)
|
||||
return
|
||||
}
|
||||
respondBool(200, false, gc)
|
||||
}
|
||||
|
||||
// @Summary Create a new invite.
|
||||
// @Produce json
|
||||
// @Param generateInviteDTO body generateInviteDTO true "New invite request object"
|
||||
@@ -1591,6 +1667,16 @@ func (app *appContext) GetConfig(gc *gin.Context) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if !MatrixE2EE() {
|
||||
delete(resp.Sections["matrix"].Settings, "encryption")
|
||||
for i, v := range resp.Sections["matrix"].Order {
|
||||
if v == "encryption" {
|
||||
sect := resp.Sections["matrix"]
|
||||
sect.Order = append(sect.Order[:i], sect.Order[i+1:]...)
|
||||
resp.Sections["matrix"] = sect
|
||||
}
|
||||
}
|
||||
}
|
||||
for sectName, section := range resp.Sections {
|
||||
for settingName, setting := range section.Settings {
|
||||
val := app.config.Section(sectName).Key(settingName)
|
||||
@@ -2495,18 +2581,20 @@ func (app *appContext) MatrixConnect(gc *gin.Context) {
|
||||
if app.storage.matrix == nil {
|
||||
app.storage.matrix = map[string]MatrixUser{}
|
||||
}
|
||||
roomID, err := app.matrix.CreateRoom(req.UserID)
|
||||
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: roomID,
|
||||
Lang: "en-us",
|
||||
Contact: true,
|
||||
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)
|
||||
|
||||
10
config.go
10
config.go
@@ -44,9 +44,12 @@ func (app *appContext) loadConfig() error {
|
||||
key.SetValue(key.MustString(filepath.Join(app.dataPath, (key.Name() + ".json"))))
|
||||
}
|
||||
}
|
||||
for _, key := range []string{"user_configuration", "user_displayprefs", "user_profiles", "ombi_template", "invites", "emails", "user_template", "custom_emails", "users", "telegram_users", "discord_users", "matrix_users"} {
|
||||
for _, key := range []string{"user_configuration", "user_displayprefs", "user_profiles", "ombi_template", "invites", "emails", "user_template", "custom_emails", "users", "telegram_users", "discord_users", "matrix_users", "announcements"} {
|
||||
app.config.Section("files").Key(key).SetValue(app.config.Section("files").Key(key).MustString(filepath.Join(app.dataPath, (key + ".json"))))
|
||||
}
|
||||
for _, key := range []string{"matrix_sql"} {
|
||||
app.config.Section("files").Key(key).SetValue(app.config.Section("files").Key(key).MustString(filepath.Join(app.dataPath, (key + ".db"))))
|
||||
}
|
||||
app.URLBase = strings.TrimSuffix(app.config.Section("ui").Key("url_base").MustString(""), "/")
|
||||
app.config.Section("email").Key("no_username").SetValue(strconv.FormatBool(app.config.Section("email").Key("no_username").MustBool(false)))
|
||||
|
||||
@@ -68,6 +71,11 @@ func (app *appContext) loadConfig() error {
|
||||
app.MustSetValue("deletion", "email_html", "jfa-go:"+"deleted.html")
|
||||
app.MustSetValue("deletion", "email_text", "jfa-go:"+"deleted.txt")
|
||||
|
||||
jfUrl := app.config.Section("jellyfin").Key("server").String()
|
||||
if !(strings.HasPrefix(jfUrl, "http://") || strings.HasPrefix(jfUrl, "https://")) {
|
||||
app.config.Section("jellyfin").Key("server").SetValue("http://" + jfUrl)
|
||||
}
|
||||
|
||||
// Deletion template is good enough for these as well.
|
||||
app.MustSetValue("disable_enable", "disabled_html", "jfa-go:"+"deleted.html")
|
||||
app.MustSetValue("disable_enable", "disabled_text", "jfa-go:"+"deleted.txt")
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
["en-us", "English (US)"]
|
||||
],
|
||||
"value": "en-us",
|
||||
"description": "Default Account Form Language. Visit weblate.hrfee.dev if you'd like to translate."
|
||||
"description": "Default Account Form Language. Visit weblate.jfa-go.com if you'd like to translate."
|
||||
},
|
||||
"language-admin": {
|
||||
"name": "Default Admin Language",
|
||||
@@ -135,7 +135,7 @@
|
||||
["en-us", "English (US)"]
|
||||
],
|
||||
"value": "en-us",
|
||||
"description": "Default Admin page Language. Settings has not been translated. Visit weblate.hrfee.dev if you'd like to translate."
|
||||
"description": "Default Admin page Language. Settings has not been translated. Visit weblate.jfa-go.com if you'd like to translate."
|
||||
},
|
||||
"theme": {
|
||||
"name": "Default Look",
|
||||
@@ -747,6 +747,16 @@
|
||||
],
|
||||
"value": "en-us",
|
||||
"description": "Default Matrix message language. Visit weblate if you'd like to translate."
|
||||
},
|
||||
"encryption": {
|
||||
"name": "End-to-end encryption (experimental)",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"depends_true": "enabled",
|
||||
"advanced": true,
|
||||
"type": "bool",
|
||||
"value": false,
|
||||
"description": "Enable end-to-end encryption for messages. Very experimental, currently does not support receiving commands (e.g !lang)."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1324,6 +1334,14 @@
|
||||
"value": "",
|
||||
"description": "Stores matrix user IDs and language preferences."
|
||||
},
|
||||
"matrix_sql": {
|
||||
"name": "Matrix encryption DB",
|
||||
"required": false,
|
||||
"requires_restart": false,
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"description": "Stores cryptographic material for Matrix end-to-end encryption."
|
||||
},
|
||||
"discord_users": {
|
||||
"name": "Discord users",
|
||||
"required": false,
|
||||
@@ -1331,6 +1349,14 @@
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"description": "Stores discord user IDs and language preferences."
|
||||
},
|
||||
"announcements": {
|
||||
"name": "Announcement templates",
|
||||
"required": false,
|
||||
"requires_restart": false,
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"description": "Stores custom announcement templates."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +134,10 @@ div.card:contains(section.banner.footer) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.h-100 {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
4
email.go
4
email.go
@@ -536,7 +536,7 @@ func (emailer *Emailer) resetValues(pwr PasswordReset, app *appContext, noSub bo
|
||||
if inviteLink != "" {
|
||||
// Strip /invite form end of this URL, ik its ugly.
|
||||
template["link_reset"] = true
|
||||
pinLink := fmt.Sprintf("%s/reset?pin=%s", strings.TrimPrefix(inviteLink, "/invite"), pwr.Pin)
|
||||
pinLink := fmt.Sprintf("%s/reset?pin=%s", strings.TrimSuffix(inviteLink, "/invite"), pwr.Pin)
|
||||
template["pin"] = pinLink
|
||||
// Only used in html email.
|
||||
template["pin_code"] = pwr.Pin
|
||||
@@ -819,7 +819,7 @@ func (app *appContext) sendByID(email *Message, ID ...string) error {
|
||||
}
|
||||
}
|
||||
if mxChat, ok := app.storage.matrix[id]; ok && mxChat.Contact && matrixEnabled {
|
||||
err = app.matrix.Send(email, mxChat.RoomID)
|
||||
err = app.matrix.Send(email, mxChat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
22
external.go
22
external.go
@@ -12,8 +12,8 @@ import (
|
||||
|
||||
const binaryType = "external"
|
||||
|
||||
var localFS fs.FS
|
||||
var langFS fs.FS
|
||||
var localFS dirFS
|
||||
var langFS dirFS
|
||||
|
||||
// When using os.DirFS, even on Windows the separator seems to be '/'.
|
||||
// func FSJoin(elem ...string) string { return filepath.Join(elem...) }
|
||||
@@ -29,9 +29,23 @@ func FSJoin(elem ...string) string {
|
||||
return strings.TrimSuffix(path, sep)
|
||||
}
|
||||
|
||||
type dirFS string
|
||||
|
||||
func (dir dirFS) Open(name string) (fs.File, error) {
|
||||
return os.Open(string(dir) + "/" + name)
|
||||
}
|
||||
|
||||
func (dir dirFS) ReadFile(name string) ([]byte, error) {
|
||||
return os.ReadFile(string(dir) + "/" + name)
|
||||
}
|
||||
|
||||
func (dir dirFS) ReadDir(name string) ([]fs.DirEntry, error) {
|
||||
return os.ReadDir(string(dir) + "/" + name)
|
||||
}
|
||||
|
||||
func loadFilesystems() {
|
||||
log.Println("Using external storage")
|
||||
executable, _ := os.Executable()
|
||||
localFS = os.DirFS(filepath.Join(filepath.Dir(executable), "data"))
|
||||
langFS = os.DirFS(filepath.Join(filepath.Dir(executable), "data", "lang"))
|
||||
localFS = dirFS(filepath.Join(filepath.Dir(executable), "data"))
|
||||
langFS = dirFS(filepath.Join(filepath.Dir(executable), "data", "lang"))
|
||||
}
|
||||
|
||||
13
go.mod
13
go.mod
@@ -14,7 +14,6 @@ replace github.com/hrfee/jfa-go/linecache => ./linecache
|
||||
|
||||
require (
|
||||
github.com/bwmarrin/discordgo v0.23.2
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
||||
github.com/fatih/color v1.10.0
|
||||
@@ -30,21 +29,21 @@ require (
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
|
||||
github.com/golang/protobuf v1.4.3 // indirect
|
||||
github.com/gomarkdown/markdown v0.0.0-20210408062403-ad838ccf8cdd
|
||||
github.com/google/go-cmp v0.5.3 // indirect
|
||||
github.com/google/uuid v1.1.2 // indirect
|
||||
github.com/hrfee/jfa-go/common v0.0.0-20210105184019-fdc97b4e86cc
|
||||
github.com/hrfee/jfa-go/docs v0.0.0-20201112212552-b6f3cd7c1f71
|
||||
github.com/hrfee/jfa-go/linecache v0.0.0-00010101000000-000000000000 // indirect
|
||||
github.com/hrfee/jfa-go/linecache v0.0.0-00010101000000-000000000000
|
||||
github.com/hrfee/jfa-go/logger v0.0.0-00010101000000-000000000000
|
||||
github.com/hrfee/jfa-go/ombi v0.0.0-20201112212552-b6f3cd7c1f71
|
||||
github.com/hrfee/mediabrowser v0.3.4
|
||||
github.com/hrfee/mediabrowser v0.3.5
|
||||
github.com/itchyny/timefmt-go v0.1.2
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
|
||||
github.com/lithammer/shortuuid/v3 v3.0.4
|
||||
github.com/mailgun/mailgun-go/v4 v4.5.1
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16
|
||||
github.com/pkg/browser v0.0.0-20210606212950-a7b7a6107d32 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.7 // indirect
|
||||
github.com/pkg/browser v0.0.0-20210606212950-a7b7a6107d32
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14
|
||||
@@ -53,10 +52,10 @@ require (
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
|
||||
github.com/ugorji/go v1.2.0 // indirect
|
||||
github.com/writeas/go-strip-markdown v2.0.1+incompatible
|
||||
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 // indirect
|
||||
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b // indirect
|
||||
golang.org/x/sys v0.0.0-20210611083646-a4fc73990273 // indirect
|
||||
golang.org/x/tools v0.1.3 // indirect
|
||||
google.golang.org/protobuf v1.25.0 // indirect
|
||||
gopkg.in/ini.v1 v1.62.0
|
||||
maunium.net/go/mautrix v0.9.14
|
||||
)
|
||||
|
||||
107
go.sum
107
go.sum
@@ -1,6 +1,4 @@
|
||||
cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
@@ -9,32 +7,35 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts=
|
||||
github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts=
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
||||
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
|
||||
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/bwmarrin/discordgo v0.23.2 h1:BzrtTktixGHIu9Tt7dEE6diysEF9HWnXeHuoJEt2fH4=
|
||||
github.com/bwmarrin/discordgo v0.23.2/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
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.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
|
||||
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=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk=
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
||||
github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:M88ob4TyDnEqNuL3PgsE/p3bDujfspnulR+0dQWNYZs=
|
||||
github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:buzQsO8HHkZX2Q45fdfGH1xejPjuDQaXH8btcYMFzPM=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473 h1:4cmBvAEBNJaGARUEs3/suWRyfyBfhf7I60WBZq+bv2w=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ=
|
||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
|
||||
@@ -44,6 +45,7 @@ github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQD
|
||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
|
||||
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4=
|
||||
@@ -60,7 +62,6 @@ github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSl
|
||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
|
||||
github.com/getlantern/systray v1.1.0 h1:U0wCEqseLi2ok1fE6b88gJklzriavPJixZysZPkZd/Y=
|
||||
github.com/getlantern/systray v1.1.0/go.mod h1:AecygODWIsBquJCJFop8MEQcJbWFfw/1yWbVabNgpCM=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc=
|
||||
github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w=
|
||||
@@ -88,7 +89,6 @@ github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3Hfo
|
||||
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
|
||||
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
|
||||
github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
|
||||
github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM=
|
||||
github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
|
||||
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
|
||||
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
|
||||
@@ -119,9 +119,7 @@ github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@@ -141,9 +139,9 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
|
||||
github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||
@@ -152,16 +150,20 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/hrfee/mediabrowser v0.3.4 h1:D2FTnuRDXUUAHW80L1kamhVUKNifm8peZVVPNe0yWmA=
|
||||
github.com/hrfee/mediabrowser v0.3.4/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/hrfee/mediabrowser v0.3.5 h1:bOJlI2HLvw7v0c7mcRw5XDRMUHReQzk5z0EJYRyYjpo=
|
||||
github.com/hrfee/mediabrowser v0.3.5/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U=
|
||||
github.com/itchyny/timefmt-go v0.1.2 h1:q0Xa4P5it6K6D7ISsbLAMwx1PnWlixDcJL6/sFs93Hs=
|
||||
github.com/itchyny/timefmt-go v0.1.2/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A=
|
||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
@@ -170,10 +172,9 @@ github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.5 h1:hyz3dwM5QLc1Rfoz4FuWJQG5BN7tc6K1MndAUnGpQr4=
|
||||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@@ -181,6 +182,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
|
||||
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.4 h1:uj4xhotfY92Y1Oa6n6HUiFn87CdoEHYUlTy0+IgbLrs=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.4/go.mod h1:RviRjexKqIzx/7r1peoAITm6m7gnif/h+0zmolKJjzw=
|
||||
github.com/mailgun/mailgun-go/v4 v4.5.1 h1:XrQQ/ZgqFvINRKy+eBqowLl7k3pQO6OCLpKphliMOFs=
|
||||
@@ -191,8 +194,6 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 h1:ZtO5uywdd5dLDCud4r0r55eP4j9FuUNpl60Gmntcop4=
|
||||
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
@@ -201,6 +202,9 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.7 h1:fxWBnXkxfM6sRiuH3bqJ4CfzZojMOLVc0UTsTglEghA=
|
||||
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -209,6 +213,9 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
|
||||
github.com/pkg/browser v0.0.0-20210606212950-a7b7a6107d32 h1:K3WnH8Ka32vWygzmjKEhz1zAVqckNoWDqX3azMxuiSA=
|
||||
@@ -217,15 +224,10 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
@@ -234,7 +236,6 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
@@ -252,6 +253,14 @@ github.com/swaggo/swag v1.7.0 h1:5bCA/MTLQoIqDXXyHfOpMeDvL9j68OY/udlK4pQoo4E=
|
||||
github.com/swaggo/swag v1.7.0/go.mod h1:BdPIL73gvS9NBsdi7M1JOxLvlbfvNRaBP8m6WT6Aajo=
|
||||
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.6.8 h1:CTmXMClGYPAmln7652e69B7OLXfTi5ABcPPwjIWUv7w=
|
||||
github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
|
||||
github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
|
||||
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
|
||||
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/sjson v1.1.5 h1:wsUceI/XDyZk3J1FUvuuYlK62zJv2HO2Pzb8A5EWdUE=
|
||||
github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
@@ -264,36 +273,33 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY
|
||||
github.com/ugorji/go/codec v1.1.13/go.mod h1:oNVt3Dq+FO91WNQ/9JnHKQP2QJxTzoN7wCBFCq1OeuU=
|
||||
github.com/ugorji/go/codec v1.2.0 h1:As6RccOIlbm9wHuWYMlB30dErcI+4WiKWsYsmPkyrUw=
|
||||
github.com/ugorji/go/codec v1.2.0/go.mod h1:dXvG35r7zTX6QImXOSFhGMmKtX+wJ7VTWzGvYQGIjBs=
|
||||
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
||||
github.com/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/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/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5 h1:dPmz1Snjq0kmkz159iL7S6WzdahUTHnHB5M56WFVifs=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
golang.org/dl v0.0.0-20190829154251-82a15e2f2ead h1:jeP6FgaSLNTMP+Yri3qjlACywQLye+huGLmNGhBzm6k=
|
||||
golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 h1:umElSU9WZirRdgu2yFHY0ayQkEnKiOC1TtM3fWXFnoU=
|
||||
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4 h1:c2HOrn5iMezYjSlGPncknSEr/8x5LELb/ilJbXi9DEA=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-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=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -307,22 +313,20 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b h1:k+E048sYJHyVnsr1GDrRZWQ32D2C7lWs9JRc0bel53A=
|
||||
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
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=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -331,6 +335,7 @@ golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -341,11 +346,9 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210531080801-fdfd190a6549 h1:OL5GcZ2XPkte3dpfuFQ9o884vrE3BZQhajdntNMruv4=
|
||||
golang.org/x/sys v0.0.0-20210531080801-fdfd190a6549/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210611083646-a4fc73990273 h1:faDu4veV+8pcThn4fewv6TVlNCezafGoC1gM/mxQLbQ=
|
||||
golang.org/x/sys v0.0.0-20210611083646-a4fc73990273/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
@@ -365,8 +368,6 @@ golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgw
|
||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201120155355-20be4ac4bd6e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.3 h1:L69ShwSZEyCsLKoAxDKeMvLDZkumEe8gXUZAjab0tX8=
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -375,15 +376,12 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
@@ -399,14 +397,13 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc=
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
||||
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
@@ -418,5 +415,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
maunium.net/go/maulogger/v2 v2.2.4/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A=
|
||||
maunium.net/go/mautrix v0.9.14 h1:2MMJ630VM+xfa4Q5AooMAhPG1+wQnQybSr/z8PlRZ8A=
|
||||
maunium.net/go/mautrix v0.9.14/go.mod h1:7IzKfWvpQtN+W2Lzxc0rLvIxFM3ryKX6Ys3S/ZoWbg8=
|
||||
|
||||
@@ -163,15 +163,24 @@
|
||||
<span class="heading"><span id="header-announce"></span> <span class="modal-close">×</span></span>
|
||||
<div class="row">
|
||||
<div class="col flex-col content mt-half">
|
||||
<label class="label supra" for="announce-subject"> {{ .strings.subject }}</label>
|
||||
<input type="text" id="announce-subject" class="input ~neutral !normal mb-1 mt-half">
|
||||
<label class="label supra" for="textarea-announce">{{ .strings.message }}</label>
|
||||
<textarea id="textarea-announce" class="textarea full-width ~neutral !normal mt-half monospace"></textarea>
|
||||
<p class="support mt-half mb-1">{{ .strings.markdownSupported }}</p>
|
||||
<label>
|
||||
<input type="submit" class="unfocused">
|
||||
<span class="button ~urge !normal full-width center supra submit">{{ .strings.submit }}</span>
|
||||
<div id="announce-details">
|
||||
<label class="label supra" for="announce-subject"> {{ .strings.subject }}</label>
|
||||
<input type="text" id="announce-subject" class="input ~neutral !normal mb-1 mt-half">
|
||||
<label class="label supra" for="textarea-announce">{{ .strings.message }}</label>
|
||||
<textarea id="textarea-announce" class="textarea full-width ~neutral !normal mt-half monospace"></textarea>
|
||||
<p class="support mt-half mb-1">{{ .strings.markdownSupported }}</p>
|
||||
</div>
|
||||
<label class="label unfocused" id="announce-name"><p class="supra">{{ .strings.name }}</p>
|
||||
<input type="text" class="input ~neutral !normal mb-1 mt-half">
|
||||
<p class="support">{{ .strings.templateEnterName }}</p>
|
||||
</label>
|
||||
<div class="row flex-expand">
|
||||
<label>
|
||||
<input type="submit" class="unfocused">
|
||||
<span class="button ~urge !normal center supra submit">{{ .strings.send }}</span>
|
||||
</label>
|
||||
<span class="button ~info !normal center supra" id="save-announce">{{ .strings.saveAsTemplate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col card ~neutral !low">
|
||||
<span class="subheading supra">{{ .strings.preview }}</span>
|
||||
@@ -542,7 +551,15 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="col sm button ~neutral !normal center mb-half" id="accounts-add-user">{{ .quantityStrings.addUser.Singular }}</span>
|
||||
<span class="col sm button ~info !normal center mb-half" id="accounts-announce">{{ .strings.announce }}</span>
|
||||
<div id="accounts-announce-dropdown" class="col sm dropdown" tabindex="0">
|
||||
<span class="h-100 sm button ~info !normal center mb-half" id="accounts-announce">{{ .strings.announce }}</span>
|
||||
<div class="dropdown-display">
|
||||
<div class="card ~neutral !low">
|
||||
<span class="supra sm">{{ .strings.templates }}</span>
|
||||
<div id="accounts-announce-templates"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="col sm button ~urge !normal center mb-half" id="accounts-modify-user">{{ .strings.modifySettings }}</span>
|
||||
<span class="col sm button ~warning !normal center mb-half" id="accounts-extend-expiry">{{ .strings.extendExpiry }}</span>
|
||||
<span class="col sm button ~positive !normal center mb-half" id="accounts-disable-enable">{{ .strings.disable }}</span>
|
||||
|
||||
149
html/setup.html
149
html/setup.html
@@ -233,85 +233,92 @@
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral !low mb-1 unfocused">
|
||||
<span class="heading">{{ .lang.Email.title }}</span>
|
||||
<p class="content" id="email-description"></p>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label class="label">
|
||||
<span>{{ .lang.Email.method }}</span>
|
||||
<div class="select ~neutral !normal mt-half mb-1">
|
||||
<select id="email-method">
|
||||
<option value="">{{ .lang.Strings.disabled }}</option>
|
||||
<option value="smtp">SMTP</option>
|
||||
<option value="mailgun">Mailgun</option>
|
||||
</select>
|
||||
</div>
|
||||
</label>
|
||||
<label class="row switch">
|
||||
<input type="checkbox" id="email-no_username"><span>{{ .lang.Email.useEmailAsUsername }}</span>
|
||||
<p class="support mb-1">{{ .lang.Email.useEmailAsUsernameNotice }}</p>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Email.fromAddress }}</span>
|
||||
<input type="email" class="input ~neutral !normal mt-half mb-1" id="email-address" placeholder="mail@jellyf.in">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Email.senderName }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half mb-1" id="email-from" value="Jellyfin">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Email.dateFormat }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half" id="email-date_format" value="%d/%m/%y">
|
||||
<p class="support mb-1" id="email-dateformat-notice"></p>
|
||||
</label>
|
||||
<div>
|
||||
<label class="row switch pb-1">
|
||||
<input type="radio" name="email-24h" value="true" checked><span>{{ .lang.Strings.time24h }}</span>
|
||||
</label>
|
||||
<label class="row switch pb-1">
|
||||
<input type="radio" name="email-24h" value="false"><span>{{ .lang.Strings.time12h }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div id="email-smtp">
|
||||
<p class="subheading">SMTP</p>
|
||||
<span class="heading">{{ .lang.Messages.title }}</span>
|
||||
<p class="content" id="messages-description"></p>
|
||||
<label class="row switch pb-1">
|
||||
<input type="checkbox" id="messages-enabled" checked><span>{{ .lang.Strings.enabled }}</span>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Email.dateFormat }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half" id="email-date_format" value="%d/%m/%y">
|
||||
<p class="support mb-1" id="email-dateformat-notice"></p>
|
||||
</label>
|
||||
<div>
|
||||
<label class="row switch pb-1">
|
||||
<input type="radio" name="email-24h" value="true" checked><span>{{ .lang.Strings.time24h }}</span>
|
||||
</label>
|
||||
<label class="row switch pb-1">
|
||||
<input type="radio" name="email-24h" value="false"><span>{{ .lang.Strings.time12h }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div id="email-sect">
|
||||
<span class="heading">{{ .lang.Email.title }}</span>
|
||||
<p class="content" id="email-description"></p>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label class="label">
|
||||
<span>{{ .lang.Email.encryption }}</span>
|
||||
<span>{{ .lang.Email.method }}</span>
|
||||
<div class="select ~neutral !normal mt-half mb-1">
|
||||
<select id="smtp-encryption">
|
||||
<option value="starttls">STARTTLS ({{ .lang.Strings.port }} 587)</option>
|
||||
<option value="ssl_tls">SSL/TLS ({{ .lang.Strings.port }} 465)</option>
|
||||
<select id="email-method">
|
||||
<option value="">{{ .lang.Strings.disabled }}</option>
|
||||
<option value="smtp">SMTP</option>
|
||||
<option value="mailgun">Mailgun</option>
|
||||
</select>
|
||||
</div>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.serverAddress }}</span>
|
||||
<input type="url" class="input ~neutral !normal mt-half mb-1" id="smtp-server" placeholder="smtp.jellyf.in">
|
||||
<label class="row switch">
|
||||
<input type="checkbox" id="email-no_username"><span>{{ .lang.Email.useEmailAsUsername }}</span>
|
||||
<p class="support mb-1">{{ .lang.Email.useEmailAsUsernameNotice }}</p>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.port }}</span>
|
||||
<input type="number" class="input ~neutral !normal mt-half mb-1" id="smtp-port" placeholder="587">
|
||||
<span class="mt-half">{{ .lang.Email.fromAddress }}</span>
|
||||
<input type="email" class="input ~neutral !normal mt-half mb-1" id="email-address" placeholder="mail@jellyf.in">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.username }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half mb-1" id="smtp-username">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.password }}</span>
|
||||
<input type="password" class="input ~neutral !normal mt-half mb-1" id="smtp-password">
|
||||
<span class="mt-half">{{ .lang.Email.senderName }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half mb-1" id="email-from" value="Jellyfin">
|
||||
</label>
|
||||
</div>
|
||||
<div id="email-mailgun">
|
||||
<p class="subheading">Mailgun</p>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Email.mailgunApiURL }}</span>
|
||||
<input type="url" class="input ~neutral !normal mt-half mb-1" id="mailgun-api_url" placeholder="https://api.eu.mailgun.net/v3/mail.jellyf.in/messages">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.apiKey }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half mb-1" id="mailgun-api_key">
|
||||
</label>
|
||||
<div class="col">
|
||||
<div id="email-smtp">
|
||||
<p class="subheading">SMTP</p>
|
||||
<label class="label">
|
||||
<span>{{ .lang.Email.encryption }}</span>
|
||||
<div class="select ~neutral !normal mt-half mb-1">
|
||||
<select id="smtp-encryption">
|
||||
<option value="starttls">STARTTLS ({{ .lang.Strings.port }} 587)</option>
|
||||
<option value="ssl_tls">SSL/TLS ({{ .lang.Strings.port }} 465)</option>
|
||||
</select>
|
||||
</div>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.serverAddress }}</span>
|
||||
<input type="url" class="input ~neutral !normal mt-half mb-1" id="smtp-server" placeholder="smtp.jellyf.in">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.port }}</span>
|
||||
<input type="number" class="input ~neutral !normal mt-half mb-1" id="smtp-port" placeholder="587">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.username }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half mb-1" id="smtp-username">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.password }}</span>
|
||||
<input type="password" class="input ~neutral !normal mt-half mb-1" id="smtp-password">
|
||||
</label>
|
||||
</div>
|
||||
<div id="email-mailgun">
|
||||
<p class="subheading">Mailgun</p>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Email.mailgunApiURL }}</span>
|
||||
<input type="url" class="input ~neutral !normal mt-half mb-1" id="mailgun-api_url" placeholder="https://api.eu.mailgun.net/v3/mail.jellyf.in/messages">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.apiKey }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half mb-1" id="mailgun-api_key">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -380,7 +387,11 @@
|
||||
<input type="checkbox" id="password_resets-link_reset"><span>{{ .lang.PasswordResets.resetLinks }}</span>
|
||||
<p class="support mb-1">{{ .lang.PasswordResets.resetLinksNotice }}</p>
|
||||
</label>
|
||||
<label class="row label">
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="password_resets-set_password"><span>{{ .lang.PasswordResets.setPassword }}</span>
|
||||
<p class="support mb-1">{{ .lang.PasswordResets.setPasswordNotice }}</p>
|
||||
</label>
|
||||
<label class="label">
|
||||
<p class="mt-half">{{ .lang.PasswordResets.resetLinksLanguage }}</p>
|
||||
<div class="select ~neutral !normal mt-half mb-1">
|
||||
<select id="password_resets-language">
|
||||
|
||||
1
lang.go
1
lang.go
@@ -117,6 +117,7 @@ type setupLang struct {
|
||||
JellyfinEmby langSection `json:"jellyfinEmby"`
|
||||
Ombi langSection `json:"ombi"`
|
||||
Email langSection `json:"email"`
|
||||
Messages langSection `json:"messages"`
|
||||
Notifications langSection `json:"notifications"`
|
||||
WelcomeEmails langSection `json:"welcomeEmails"`
|
||||
PasswordResets langSection `json:"passwordResets"`
|
||||
|
||||
199
lang/admin/da-dk.json
Normal file
199
lang/admin/da-dk.json
Normal file
@@ -0,0 +1,199 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Dansk"
|
||||
},
|
||||
"strings": {
|
||||
"invites": "Invitationer",
|
||||
"accounts": "Konti",
|
||||
"settings": "Indstillinger",
|
||||
"inviteMonths": "Måneder",
|
||||
"inviteDays": "Dage",
|
||||
"inviteHours": "Timer",
|
||||
"inviteMinutes": "Minutter",
|
||||
"inviteNumberOfUses": "Antal anvendelser",
|
||||
"inviteDuration": "Invitations varighed",
|
||||
"warning": "Advarsel",
|
||||
"inviteInfiniteUsesWarning": "invitationer med uendelig brug kan blive misbrugt",
|
||||
"inviteSendToEmail": "Send til",
|
||||
"login": "Log på",
|
||||
"logout": "Log ud",
|
||||
"create": "Opret",
|
||||
"apply": "Anvend",
|
||||
"delete": "Slet",
|
||||
"add": "Tilføj",
|
||||
"select": "Vælg",
|
||||
"name": "Navn",
|
||||
"date": "Dato",
|
||||
"enabled": "Aktiveret",
|
||||
"disabled": "Deaktiveret",
|
||||
"reEnable": "Genaktiver",
|
||||
"disable": "Deaktiver",
|
||||
"admin": "Administrator",
|
||||
"updates": "Opdateringer",
|
||||
"update": "Opdatering",
|
||||
"download": "Hent",
|
||||
"search": "Søg",
|
||||
"advancedSettings": "Avanceret Indstillinger",
|
||||
"lastActiveTime": "Sidst Aktiv",
|
||||
"from": "Fra",
|
||||
"user": "Bruger",
|
||||
"expiry": "Udløb",
|
||||
"userExpiry": "Brugerens Udløb",
|
||||
"userExpiryDescription": "En specificeret tid efter hver tilmelding, sletter/deaktiverer jfa-go kontoen. Du kan ændre denne adfærd i indstillingerne.",
|
||||
"aboutProgram": "Om",
|
||||
"version": "Version",
|
||||
"commitNoun": "Commit",
|
||||
"newUser": "Ny Bruger",
|
||||
"profile": "Profil",
|
||||
"unknown": "Ukendt",
|
||||
"label": "Etiket",
|
||||
"announce": "Annoncere",
|
||||
"subject": "Emne",
|
||||
"message": "Meddelelse",
|
||||
"variables": "Variabler",
|
||||
"conditionals": "Betingelser",
|
||||
"preview": "Eksempel",
|
||||
"reset": "Nulstil",
|
||||
"edit": "Rediger",
|
||||
"donate": "Doner",
|
||||
"contactThrough": "Kontakt gennem:",
|
||||
"extendExpiry": "Forlæng udløb",
|
||||
"customizeMessages": "Tilpas Meddelelser",
|
||||
"customizeMessagesDescription": "Hvis du ikke vil bruge jfa-go's meddelelses skabeloner, kan du oprette din egen ved hjælp af Markdown.",
|
||||
"markdownSupported": "Markdown understøttes.",
|
||||
"modifySettings": "Rediger indstillinger",
|
||||
"modifySettingsDescription": "Anvend indstillinger fra en eksisterende profil, eller hent dem direkte fra en bruger.",
|
||||
"applyHomescreenLayout": "Anvend startskærmens layout",
|
||||
"sendDeleteNotificationEmail": "Send notifikations meddelelse",
|
||||
"sendDeleteNotifiationExample": "Din konto er blevet slettet.",
|
||||
"settingsRestart": "Genstart",
|
||||
"settingsRestarting": "Genstarter…",
|
||||
"settingsRestartRequired": "Genstart nødvendig",
|
||||
"settingsRestartRequiredDescription": "En genstart er nødvendig for at anvende nogle indstillinger du har ændret. Genstart nu eller senere?",
|
||||
"settingsApplyRestartLater": "Anvend, genstart senere",
|
||||
"settingsApplyRestartNow": "Anvend & genstart",
|
||||
"settingsApplied": "Indstillingerne anvendt.",
|
||||
"settingsRefreshPage": "Opdater siden om få sekunder.",
|
||||
"settingsRequiredOrRestartMessage": "Bemærk: {n} angiver et obligatorisk felt, {n} angiver at ændringer kræver genstart.",
|
||||
"settingsSave": "Gem",
|
||||
"ombiUserDefaults": "Ombi bruger standarder",
|
||||
"ombiUserDefaultsDescription": "Opret en Ombi bruger og konfigurer den, vælg den derefter nedenfor. Brugerens indstillinger/tilladelser gemmes og anvendes på nye Ombi brugere oprettet af jfa-go",
|
||||
"userProfiles": "Bruger Profiler",
|
||||
"userProfilesDescription": "Profiler anvendes på brugere når de opretter en konto. En profil inkluderer adgangsrettigheder til biblioteket og layout på startskærmen.",
|
||||
"userProfilesIsDefault": "Standard",
|
||||
"userProfilesLibraries": "Biblioteker",
|
||||
"addProfile": "Tilføj Profil",
|
||||
"addProfileDescription": "Opret en Jellyfin bruger og konfigurer den, vælg den derefter nedenfor. Når denne profil anvendes på en invitation, oprettes nye brugere med indstillingerne.",
|
||||
"addProfileNameOf": "Profil Navn",
|
||||
"addProfileStoreHomescreenLayout": "Gem startskærmens layout",
|
||||
"inviteNoUsersCreated": "Ingen endnu!",
|
||||
"inviteUsersCreated": "Oprettet brugere",
|
||||
"inviteNoProfile": "Ingen Profil",
|
||||
"inviteDateCreated": "Oprettet",
|
||||
"inviteRemainingUses": "Resterende anvendelser",
|
||||
"inviteNoInvites": "Ingen",
|
||||
"inviteExpiresInTime": "Udløber om {n}",
|
||||
"notifyEvent": "Meddel den:",
|
||||
"notifyInviteExpiry": "Ved udløb",
|
||||
"notifyUserCreation": "Ved oprettelse af brugere",
|
||||
"sendPIN": "Bed brugeren om at sende pinkoden nedenfor til boten.",
|
||||
"searchDiscordUser": "Begynd at skrive Discord brugernavnet for at finde brugeren.",
|
||||
"findDiscordUser": "Find Discord bruger",
|
||||
"linkMatrixDescription": "Indtast brugernavnet og adgangskoden til den bruger der skal bruges som en bot. Når indsendt, genstarter appen.",
|
||||
"matrixHomeServer": "Hjemme server adresse"
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "Ændret e-mail adresse på {n}.",
|
||||
"userCreated": "Bruger {n} oprettet.",
|
||||
"createProfile": "Oprettede profil {n}.",
|
||||
"saveSettings": "Indstillingerne blev gemt",
|
||||
"saveEmail": "E-mail gemt.",
|
||||
"sentAnnouncement": "Meddelelse sendt.",
|
||||
"setOmbiDefaults": "Ombi standarder gemt.",
|
||||
"updateApplied": "Opdatering anvendt, genstart.",
|
||||
"updateAppliedRefresh": "Opdatering anvendt, genindlæs venligst siden.",
|
||||
"telegramVerified": "Telegram konto verificeret.",
|
||||
"accountConnected": "Konto tilsluttet.",
|
||||
"errorConnection": "Kunne ikke oprette forbindelse til jfa-go.",
|
||||
"error401Unauthorized": "Adgang nægtet. Prøv at genindlæse siden.",
|
||||
"errorSettingsAppliedNoHomescreenLayout": "Indstillingerne blev anvendt, men anvendelse af startskærmens layout mislykkedes muligvis.",
|
||||
"errorHomescreenAppliedNoSettings": "Startskærmens layout blev anvendt, men anvendelsen af indstillingerne mislykkedes muligvis.",
|
||||
"errorSettingsFailed": "Ansøgningen mislykkedes.",
|
||||
"errorLoginBlank": "Brugernavnet og/eller adgangskoden blev efterladt tomme.",
|
||||
"errorUnknown": "Ukendt fejl.",
|
||||
"errorSaveEmail": "Kunne ikke gemme e-mail.",
|
||||
"errorBlankFields": "Felter blev efterladt tomme",
|
||||
"errorDeleteProfile": "Kunne ikke slette profilen {n}",
|
||||
"errorLoadProfiles": "Profiler kunne ikke indlæses.",
|
||||
"errorCreateProfile": "Kunne ikke oprette profilen {n}",
|
||||
"errorSetDefaultProfile": "Standard profilen kunne ikke indstilles.",
|
||||
"errorLoadUsers": "Kunne ikke indlæse brugere.",
|
||||
"errorSaveSettings": "Kunne ikke gemme indstillingerne.",
|
||||
"errorLoadSettings": "Indstillingerne kunne ikke indlæses.",
|
||||
"errorSetOmbiDefaults": "Ombi standarderne kunne ikke gemmes.",
|
||||
"errorLoadOmbiUsers": "Kunne ikke indlæse ombi brugere.",
|
||||
"errorChangedEmailAddress": "Kunne ikke ændre e-mail adressen på {n}.",
|
||||
"errorFailureCheckLogs": "Mislykkedes (tjek konsol/logfiler)",
|
||||
"errorPartialFailureCheckLogs": "Delvis fejl (tjek konsol/logfiler)",
|
||||
"errorUserCreated": "Kunne ikke oprette bruger {n}.",
|
||||
"errorSendWelcomeEmail": "Kunne ikke sende velkomst meddelelse (tjek konsol/logfiler",
|
||||
"errorApplyUpdate": "Kunne ikke anvende opdateringen, prøv manuelt.",
|
||||
"errorCheckUpdate": "Kunne ikke kontrollere for opdatering.",
|
||||
"updateAvailable": "En ny opdatering er tilgængelig, tjek indstillingerne.",
|
||||
"noUpdatesAvailable": "Ingen nye opdateringer tilgængelige."
|
||||
},
|
||||
"quantityStrings": {
|
||||
"modifySettingsFor": {
|
||||
"singular": "Rediger indstillinger for {n} bruger",
|
||||
"plural": "Rediger indstillinger for {n} brugere"
|
||||
},
|
||||
"deleteNUsers": {
|
||||
"singular": "Slet {n} bruger",
|
||||
"plural": "Slet {n} brugere"
|
||||
},
|
||||
"disableUsers": {
|
||||
"singular": "Deaktiver {n} bruger",
|
||||
"plural": "Deaktiver {n} brugere"
|
||||
},
|
||||
"reEnableUsers": {
|
||||
"singular": "Genaktiver {n} bruger",
|
||||
"plural": "Genaktiver {n} brugere"
|
||||
},
|
||||
"addUser": {
|
||||
"singular": "Tilføj bruger",
|
||||
"plural": "Tilføj brugere"
|
||||
},
|
||||
"deleteUser": {
|
||||
"singular": "Slet bruger",
|
||||
"plural": "Slet brugere"
|
||||
},
|
||||
"deletedUser": {
|
||||
"singular": "Slettede {n} bruger.",
|
||||
"plural": "Slettede {n} brugere."
|
||||
},
|
||||
"disabledUser": {
|
||||
"singular": "Deaktiveret {n} bruger.",
|
||||
"plural": "Deaktiverede {n} brugere."
|
||||
},
|
||||
"enabledUser": {
|
||||
"singular": "Aktiveret {n} bruger.",
|
||||
"plural": "Aktiveret {n} brugere."
|
||||
},
|
||||
"announceTo": {
|
||||
"singular": "Annoncer til {n} bruger",
|
||||
"plural": "Annoncer til {n} brugere"
|
||||
},
|
||||
"appliedSettings": {
|
||||
"singular": "Anvendte indstillinger til {n} bruger.",
|
||||
"plural": "Anvendte indstillinger til {n} brugere."
|
||||
},
|
||||
"extendExpiry": {
|
||||
"singular": "Forlæng udløbet for {n} bruger",
|
||||
"plural": "Forlæng udløbet for {n} brugere"
|
||||
},
|
||||
"extendedExpiry": {
|
||||
"singular": "Forlængede udløb for {n} bruger.",
|
||||
"plural": "Forlængede udløb for {n} brugere."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,7 @@
|
||||
"unknown": "Unknown",
|
||||
"label": "Label",
|
||||
"announce": "Announce",
|
||||
"templates": "Templates",
|
||||
"subject": "Subject",
|
||||
"message": "Message",
|
||||
"variables": "Variables",
|
||||
@@ -100,7 +101,10 @@
|
||||
"searchDiscordUser": "Start typing the Discord username to find the user.",
|
||||
"findDiscordUser": "Find Discord user",
|
||||
"linkMatrixDescription": "Enter the username and password of the user to use as a bot. Once submitted, the app will restart.",
|
||||
"matrixHomeServer": "Home server address"
|
||||
"matrixHomeServer": "Home server address",
|
||||
"saveAsTemplate": "Save as template",
|
||||
"deleteTemplate": "Delete template",
|
||||
"templateEnterName": "Enter a name to save this template."
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "Changed email address of {n}.",
|
||||
@@ -109,6 +113,7 @@
|
||||
"saveSettings": "Settings were saved",
|
||||
"saveEmail": "Email saved.",
|
||||
"sentAnnouncement": "Announcement sent.",
|
||||
"savedAnnouncement": "Announcement saved.",
|
||||
"setOmbiDefaults": "Stored ombi defaults.",
|
||||
"updateApplied": "Update applied, please restart.",
|
||||
"updateAppliedRefresh": "Update applied, please refresh.",
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
"customizeMessagesDescription": "Si vous ne souhaitez pas utiliser les modèles d'e-mails de jfa-go, vous pouvez créer les vôtres à l'aide de Markdown.",
|
||||
"variables": "Variables",
|
||||
"preview": "Aperçu",
|
||||
"reset": "Réinitialiser",
|
||||
"reset": "Réinitialisation",
|
||||
"edit": "Éditer",
|
||||
"customizeMessages": "Personnaliser les e-mails",
|
||||
"inviteDuration": "Durée de l'invitation",
|
||||
@@ -94,8 +94,14 @@
|
||||
"userExpiryDescription": "Un laps de temps spécifié après chaque inscription, jfa-go supprimera / désactivera le compte. Vous pouvez modifier ce comportement dans les paramètres.",
|
||||
"donate": "Faire un don",
|
||||
"extendExpiry": "Prolonger l'expiration",
|
||||
"contactThrough": "Contactez par :",
|
||||
"sendPIN": "Demandez à l'utilisateur d'envoyer le code PIN ci-dessous au bot."
|
||||
"contactThrough": "Contacté par :",
|
||||
"sendPIN": "Demandez à l'utilisateur d'envoyer le code PIN ci-dessous au bot.",
|
||||
"add": "Ajouter",
|
||||
"select": "Sélectionner",
|
||||
"findDiscordUser": "Trouver l'utilisateur Discord",
|
||||
"linkMatrixDescription": "Entrez le nom d'utilisateur et le mot de passe de l'utilisateur pour l’utilisateur comme bot. Une fois soumis, l'application va redémarrer.",
|
||||
"searchDiscordUser": "Commencez à taper le nom d'utilisateur Discord pour trouver l'utilisateur.",
|
||||
"matrixHomeServer": "Adresse du serveur"
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "Adresse e-mail modifiée de {n}.",
|
||||
@@ -134,7 +140,8 @@
|
||||
"updateAvailable": "Une nouvelle mise à jour est disponible, vérifiez les paramètres.",
|
||||
"noUpdatesAvailable": "Aucune nouvelle mise à jour disponible.",
|
||||
"telegramVerified": "Compte Telegram vérifié.",
|
||||
"updateAppliedRefresh": "Mise à jour appliquée, veuillez actualiser."
|
||||
"updateAppliedRefresh": "Mise à jour appliquée, veuillez actualiser.",
|
||||
"accountConnected": "Compte connecté."
|
||||
},
|
||||
"quantityStrings": {
|
||||
"modifySettingsFor": {
|
||||
|
||||
26
lang/common/da-dk.json
Normal file
26
lang/common/da-dk.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Dansk"
|
||||
},
|
||||
"strings": {
|
||||
"username": "Brugernavn",
|
||||
"password": "Adgangskode",
|
||||
"emailAddress": "E-mail Adresse",
|
||||
"name": "Navn",
|
||||
"submit": "Indsend",
|
||||
"send": "Send",
|
||||
"success": "Succes",
|
||||
"error": "Fejl",
|
||||
"copy": "Kopiér",
|
||||
"copied": "Kopiret",
|
||||
"time24h": "24 timers tid",
|
||||
"time12h": "12 timers tid",
|
||||
"linkTelegram": "Link Telegram",
|
||||
"contactEmail": "Kontakt gennem E-mail",
|
||||
"contactTelegram": "Kontakt gennem Telegram",
|
||||
"linkDiscord": "Link Discord",
|
||||
"linkMatrix": "Link Matrix",
|
||||
"contactDiscord": "Kontakt gennem Discord",
|
||||
"theme": "Tema"
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,10 @@
|
||||
"copied": "Copié",
|
||||
"linkTelegram": "Lien Telegram",
|
||||
"contactEmail": "Contact par e-mail",
|
||||
"contactTelegram": "Contact par Telegram"
|
||||
"contactTelegram": "Contact par Telegram",
|
||||
"linkDiscord": "Lier Discord",
|
||||
"linkMatrix": "Lier Matrix",
|
||||
"send": "Envoyer",
|
||||
"contactDiscord": "Contacter par Discord"
|
||||
}
|
||||
}
|
||||
|
||||
77
lang/email/da-dk.json
Normal file
77
lang/email/da-dk.json
Normal file
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Dansk"
|
||||
},
|
||||
"strings": {
|
||||
"ifItWasNotYou": "Ignorer venligst hvis dette ikke var dig.",
|
||||
"helloUser": "Hej {username},",
|
||||
"reason": "Grund"
|
||||
},
|
||||
"userCreated": {
|
||||
"name": "Bruger oprettet",
|
||||
"title": "Meddelelse: Bruger oprettet",
|
||||
"aUserWasCreated": "En bruger blev oprettet med koden {code}.",
|
||||
"time": "Tid",
|
||||
"notificationNotice": "Meddelelse: Notifikations e-mails kan blive ændret på admin-siden."
|
||||
},
|
||||
"inviteExpiry": {
|
||||
"name": "Invitationens udløb",
|
||||
"title": "Meddelelse: Invitation udløbet",
|
||||
"inviteExpired": "Invitation udløbet.",
|
||||
"expiredAt": "Koden {code} udløber om {time}.",
|
||||
"notificationNotice": "Meddelelse: Notifikations e-mails kan blive ændret på admin-siden."
|
||||
},
|
||||
"passwordReset": {
|
||||
"name": "Nulstil Adgangskode",
|
||||
"title": "Nulstilling af adgangskode anmodet - Jellyfin",
|
||||
"someoneHasRequestedReset": "Nogen har for nylig anmodet om nulstilling af din adgangskode på Jellyfin.",
|
||||
"ifItWasYou": "Hvis dette var dig, så indtast venligst pinkoden nedenunder ind i prompten.",
|
||||
"ifItWasYouLink": "Hvis dette var dig, så tryk på linket nedenunder.",
|
||||
"codeExpiry": "Koden udløber den {date}, klokken {time} UTC, hvilket er om {expiresInMinutes}.",
|
||||
"pin": "PINKODE"
|
||||
},
|
||||
"userDeleted": {
|
||||
"name": "Sletning af bruger",
|
||||
"title": "Din konto blev slettet - Jellyfin",
|
||||
"yourAccountWasDeleted": "Din Jellyfin konto blev slettet."
|
||||
},
|
||||
"userDisabled": {
|
||||
"name": "Bruger deaktiveret",
|
||||
"title": "Din konto er blevet deaktiveret - Jellyfin",
|
||||
"yourAccountWasDisabled": "Din konto blev deaktiveret."
|
||||
},
|
||||
"userEnabled": {
|
||||
"name": "Bruger aktiveret",
|
||||
"title": "Din konto er blevet genaktiveret - Jellyfin",
|
||||
"yourAccountWasEnabled": "Din konto blev genaktiveret."
|
||||
},
|
||||
"inviteEmail": {
|
||||
"name": "Invitations e-mail",
|
||||
"title": "Invitation - Jellyfin",
|
||||
"hello": "Hej",
|
||||
"youHaveBeenInvited": "Du er blevet inviteret til Jellyfin.",
|
||||
"toJoin": "Tilmeld dig med linket nedenfor.",
|
||||
"inviteExpiry": "Invitationen vil udløbe den {date} kl. {time}, hvilket er om {expiresInMinutes}, så skynd dig.",
|
||||
"linkButton": "Opsæt din konto"
|
||||
},
|
||||
"welcomeEmail": {
|
||||
"name": "Velkommen",
|
||||
"title": "Velkommen til Jellyfin",
|
||||
"welcome": "Velkommen til Jellyfin!",
|
||||
"youCanLoginWith": "Du kan logge ind med nedenstående oplysninger",
|
||||
"yourAccountWillExpire": "Din konto udløber den {date}.",
|
||||
"jellyfinURL": "URL"
|
||||
},
|
||||
"emailConfirmation": {
|
||||
"name": "Bekræftelses e-mail",
|
||||
"title": "Bekræft din e-mail - Jellyfin",
|
||||
"clickBelow": "Klik på linket nedenunder for at bekræfte din e-mail adresse og start med at bruge Jellyfin.",
|
||||
"confirmEmail": "Bekræft E-mail"
|
||||
},
|
||||
"userExpired": {
|
||||
"name": "Brugerens udløb",
|
||||
"title": "Din konto er udløbet - Jellyfin",
|
||||
"yourAccountHasExpired": "Din konto er udløbet.",
|
||||
"contactTheAdmin": "Kontakt administratoren for mere information."
|
||||
}
|
||||
}
|
||||
57
lang/form/da-dk.json
Normal file
57
lang/form/da-dk.json
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Dansk"
|
||||
},
|
||||
"strings": {
|
||||
"pageTitle": "Opret en Jellyfin Konto",
|
||||
"createAccountHeader": "Opret Konto",
|
||||
"accountDetails": "Detaljer",
|
||||
"emailAddress": "E-mail",
|
||||
"username": "Brugernavn",
|
||||
"password": "Adgangskode",
|
||||
"reEnterPassword": "Genindtast Adgangskode",
|
||||
"reEnterPasswordInvalid": "Adgangskoderne er ikke ens.",
|
||||
"createAccountButton": "Opret Konto",
|
||||
"passwordRequirementsHeader": "Adgangskodekrav",
|
||||
"successHeader": "Succes!",
|
||||
"successContinueButton": "Fortsæt",
|
||||
"confirmationRequired": "E-mail bekræftelse er påkrævet",
|
||||
"confirmationRequiredMessage": "Tjek venligst din e-mail indbakke for at verificere din adresse.",
|
||||
"yourAccountIsValidUntil": "Din konto er gyldig indtil {date}.",
|
||||
"sendPIN": "Send nedenstående pinkode til boten, og kom derefter tilbage her for at linke din konto.",
|
||||
"sendPINDiscord": "Skriv {command} i {server_channel} på Discord, og send PIN-koden nedenfor via. DM til boten.",
|
||||
"matrixEnterUser": "Skriv dit Bruger ID, tryk Indsend, og en PIN-kode vil blive sendt til dig. Skriv den her efter for at fortsætte."
|
||||
},
|
||||
"notifications": {
|
||||
"errorUserExists": "Brugeren eksistere allerede.",
|
||||
"errorInvalidCode": "Ugyldig invitations kode.",
|
||||
"errorTelegramVerification": "Telegram verifikation påkrævet.",
|
||||
"errorDiscordVerification": "Discord verifikation påkrævet.",
|
||||
"errorMatrixVerification": "Matrix verifikation påkrævet.",
|
||||
"errorInvalidPIN": "PIN-koden er ugyldig.",
|
||||
"errorUnknown": "Ukendt fejl.",
|
||||
"verified": "konto verificeret."
|
||||
},
|
||||
"validationStrings": {
|
||||
"length": {
|
||||
"singular": "Skal mindst have {n} tegn",
|
||||
"plural": "Skal mindst have {n} tegn"
|
||||
},
|
||||
"uppercase": {
|
||||
"singular": "Skal mindst have {n} store bogstaver",
|
||||
"plural": "Skal mindst have {n} store bogstaver"
|
||||
},
|
||||
"lowercase": {
|
||||
"singular": "Skal mindst have {n} små bogstaver",
|
||||
"plural": "Skal mindst have {n} små bogstaver"
|
||||
},
|
||||
"number": {
|
||||
"singular": "Skal mindst have {n} tal",
|
||||
"plural": "Skal mindst have {n} tal"
|
||||
},
|
||||
"special": {
|
||||
"singular": "Skal mindst have {n} specialtegn",
|
||||
"plural": "Skal mindst have {n} specialtegn"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,12 +14,14 @@
|
||||
"reEnterPasswordInvalid": "Les mots de passe ne correspondent pas.",
|
||||
"createAccountButton": "Créer le compte",
|
||||
"passwordRequirementsHeader": "Mot de passe requis",
|
||||
"successHeader": "Succes!",
|
||||
"successHeader": "Succès !",
|
||||
"successContinueButton": "Continuer",
|
||||
"confirmationRequired": "Confirmation de l'adresse e-mail requise",
|
||||
"confirmationRequiredMessage": "Veuillez vérifier votre boite de réception pour confirmer votre adresse e-mail.",
|
||||
"yourAccountIsValidUntil": "Votre compte sera valide jusqu'au {date}.",
|
||||
"sendPIN": "Envoyez le code PIN ci-dessous au bot, puis revenez ici pour lier votre compte."
|
||||
"sendPIN": "Envoyez le code PIN ci-dessous au bot, puis revenez ici pour lier votre compte.",
|
||||
"sendPINDiscord": "Écrivez {command} dans le salon {server_channel} sur Discord puis envoyez le PIN en message privé au bot.",
|
||||
"matrixEnterUser": "Entrez votre nom d'utilisateur, appuyez sur soumettre et un code PIN vous sera envoyé. Cliquez ici pour continuez."
|
||||
},
|
||||
"validationStrings": {
|
||||
"length": {
|
||||
@@ -48,6 +50,10 @@
|
||||
"errorInvalidCode": "Code d’invitation non valide.",
|
||||
"errorTelegramVerification": "Vérification Telegram requise.",
|
||||
"errorInvalidPIN": "PIN Telegram invalide.",
|
||||
"telegramVerified": "Compte Telegram vérifié."
|
||||
"telegramVerified": "Compte Telegram vérifié.",
|
||||
"errorDiscordVerification": "Vérification Discord requise.",
|
||||
"errorMatrixVerification": "Vérification Matrix requise.",
|
||||
"errorUnknown": "Erreur inconnue.",
|
||||
"verified": "Compte vérifié."
|
||||
}
|
||||
}
|
||||
|
||||
15
lang/pwreset/da-dk.json
Normal file
15
lang/pwreset/da-dk.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Dansk"
|
||||
},
|
||||
"strings": {
|
||||
"passwordReset": "Nulstil adgangskode",
|
||||
"reset": "Nulstil",
|
||||
"resetFailed": "Nulstilling af adgangskode fejlede",
|
||||
"tryAgain": "Prøv venligst igen.",
|
||||
"youCanLogin": "Du kan nu logge ind med koden nedenfor som din adgangskode.",
|
||||
"youCanLoginOmbi": "Du kan nu logge ind på Jellyfin & Ombi med koden nedenfor som din adgangskode.",
|
||||
"changeYourPassword": "Sørg for at ændre din adgangskode, når du har logget ind.",
|
||||
"enterYourPassword": "Indtast din nye adgangskode nedenfor."
|
||||
}
|
||||
}
|
||||
15
lang/pwreset/fr-fr.json
Normal file
15
lang/pwreset/fr-fr.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Français (FR)"
|
||||
},
|
||||
"strings": {
|
||||
"passwordReset": "Réinitialisation du mot de passe",
|
||||
"reset": "Réinitialisation",
|
||||
"resetFailed": "Réinitialisation du mot de passe échouée",
|
||||
"tryAgain": "Veuillez réessayer.",
|
||||
"youCanLogin": "Vous pouvez maintenant vous connecter en utilisant ce code comme mot de passe.",
|
||||
"youCanLoginOmbi": "Vous pouvez maintenant vous connecter à Jellyfin et Ombi en utilisant ce mot de passe.",
|
||||
"changeYourPassword": "Assurez-vous de changer votre mot de passe après s'être connecté.",
|
||||
"enterYourPassword": "Entrez votre nouveau mot de passe ici."
|
||||
}
|
||||
}
|
||||
137
lang/setup/da-dk.json
Normal file
137
lang/setup/da-dk.json
Normal file
@@ -0,0 +1,137 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Dansk"
|
||||
},
|
||||
"strings": {
|
||||
"pageTitle": "Installer - jfa-go",
|
||||
"next": "Næste",
|
||||
"back": "Tilbage",
|
||||
"optional": "Valgfri",
|
||||
"serverType": "Servertype",
|
||||
"disabled": "Deaktiveret",
|
||||
"enabled": "Aktiveret",
|
||||
"port": "Port",
|
||||
"message": "Meddelelse",
|
||||
"serverAddress": "Serveradresse",
|
||||
"emailSubject": "E-mail emne",
|
||||
"URL": "URL",
|
||||
"apiKey": "API Nøgle"
|
||||
},
|
||||
"startPage": {
|
||||
"welcome": "Velkommen!",
|
||||
"pressStart": "Du bliver nødt til at gøre et par ting for at konfigurere jfa-go. Tryk på start for at fortsætte.",
|
||||
"httpsNotice": "Sørg for, at du tilgår denne side via HTTPS eller på et privat netværk.",
|
||||
"start": "Start"
|
||||
},
|
||||
"endPage": {
|
||||
"finished": "Færdig!",
|
||||
"restartMessage": "Der er flere indstillinger du kan konfigurere på admin-siden. Klik nedenfor for at genstarte, og opdater derefter siden.",
|
||||
"refreshPage": "Opdater"
|
||||
},
|
||||
"language": {
|
||||
"title": "Sprog",
|
||||
"description": "Fællesskabsoversættelser er tilgængelige for de fleste dele af jfa-go. Du kan vælge standardsprogene nedenfor, men brugere kan stadig ændre det, hvis de ønsker det. Hvis du vil hjælpe med at oversætte, skal du tilmelde dig til {n} for at begynde at bidrage!",
|
||||
"defaultAdminLang": "Standard administrator sprog",
|
||||
"defaultFormLang": "Standard kontooprettelses sprog",
|
||||
"defaultEmailLang": "Standard e-mail sprog"
|
||||
},
|
||||
"general": {
|
||||
"title": "Generel",
|
||||
"listenAddress": "Listen Address",
|
||||
"urlBase": "URL-base",
|
||||
"urlBaseNotice": "Kun nødvendigt hvis du bruger en omvendt proxy på et underdomæne (f.eks. 'Jellyf.in/accounts').",
|
||||
"lightTheme": "Lys",
|
||||
"darkTheme": "Mørk",
|
||||
"useHTTPS": "Brug HTTPS",
|
||||
"httpsPort": "HTTPS Port",
|
||||
"useHTTPSNotice": "Anbefales kun hvis du ikke bruger en omvendt proxy.",
|
||||
"pathToCertificate": "Sti til certifikat",
|
||||
"pathToKeyFile": "Sti til nøglefil"
|
||||
},
|
||||
"updates": {
|
||||
"title": "Opdateringer",
|
||||
"description": "Få besked når nye opdateringer er tilgængelige. jfa-go kontrollerer {n} hvert 30 minut. Ingen IP'er eller personlige identificerbare oplysninger indsamles.",
|
||||
"updateChannel": "Opdaterings Kanal",
|
||||
"stable": "Stabil",
|
||||
"unstable": "Ustabil"
|
||||
},
|
||||
"login": {
|
||||
"title": "Log på",
|
||||
"description": "For at få adgang til admin-siden skal du logge ind med nedenstående metode:",
|
||||
"authorizeWithJellyfin": "Autoriser med Jellyfin/Emby: Loginoplysninger deles med Jellyfin, hvilket giver mulighed for flere brugere.",
|
||||
"authorizeManual": "Brugernavn og adgangskode: indtast brugernavn og adgangskode manuelt.",
|
||||
"adminOnly": "Kun administratorbrugere (anbefalet)",
|
||||
"emailNotice": "Din e-mail adresse kan bruges til at modtage underretninger."
|
||||
},
|
||||
"jellyfinEmby": {
|
||||
"title": "Jellyfin/Emby",
|
||||
"description": "En administratorkonto er nødvendig fordi API'en ikke tillader oprettelse af brugere ved hjælp af en API-nøgle. Du skal oprette en separat konto og markere 'Tillad denne bruger at administrere serveren'. Du kan deaktivere alt andet. Når du er færdig, skal du indtaste loginoplysningerne her.",
|
||||
"embyNotice": "Emby support er begrænset og understøtter ikke nulstilling af adgangskode.",
|
||||
"internal": "Intern",
|
||||
"external": "Ekstern",
|
||||
"replaceJellyfin": "Server navn",
|
||||
"replaceJellyfinNotice": "Hvis angivet, vil dette erstatte enhver forekomst af 'Jellyfin' i appen.",
|
||||
"addressExternalNotice": "Lad det være tomt for at bruge den samme adresse.",
|
||||
"testConnection": "Test forbindelse"
|
||||
},
|
||||
"ombi": {
|
||||
"title": "Ombi",
|
||||
"description": "Ved at oprette forbindelse til Ombi, oprettes både en Jellyfin og Ombi konto når en bruger tilmelder sig via jfa-go. Når installationen er afsluttet, skal du gå til Indstillinger for at indstille en standardprofil til nye Ombi brugere.",
|
||||
"apiKeyNotice": "Find dette i den første fane i Ombi indstillinger."
|
||||
},
|
||||
"email": {
|
||||
"title": "E-mail",
|
||||
"description": "jfa-go kan sende PIN-koder til nulstilling af adgangskoder og forskellige meddelelser via e-mail. Du kan oprette forbindelse til en SMTP-server eller bruge {n} API.",
|
||||
"method": "Afsendelsesmetode",
|
||||
"useEmailAsUsername": "Brug e-mail adresser som brugernavn",
|
||||
"useEmailAsUsernameNotice": "Hvis aktiveret, logger nye brugere på Jellyfin/Emby med deres e-mail adresse i stedet for et brugernavn.",
|
||||
"fromAddress": "Fra adresse",
|
||||
"senderName": "Afsender navn",
|
||||
"dateFormat": "Datoformat",
|
||||
"dateFormatNotice": "Dato følger strftime formatet. For flere oplysninger, besøg {n}.",
|
||||
"encryption": "Kryptering",
|
||||
"mailgunApiURL": "API-URL"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Meddelelser",
|
||||
"description": "Hvis aktiveret, kan du vælge (pr. Invitation) at modtage en e-mail når en invitation udløber, eller når en bruger oprettes. Hvis du ikke valgte Jellyfin login metoden, skal du sørge for at angive din e-mail adresse."
|
||||
},
|
||||
"welcomeEmails": {
|
||||
"title": "Velkomstmails",
|
||||
"description": "Hvis aktiveret, sendes en e-mail til nye brugere med Jellyfin/Emby URL'en og deres brugernavn."
|
||||
},
|
||||
"inviteEmails": {
|
||||
"title": "Invitations E-mails",
|
||||
"description": "Hvis aktiveret, kan du sende invitationer direkte til en brugers e-mail adresse. Fordi du muligvis bruger en omvendt proxy, skal du angive en URL, invitationer tilgås fra. Skriv din URL-base, og tilføj '/invite'."
|
||||
},
|
||||
"passwordResets": {
|
||||
"title": "Nulstilling af Adgangskoder",
|
||||
"description": "Når en bruger forsøger at nulstille deres adgangskode, opretter Jellyfin en fil med navnet 'passwordreset - *. Json', som indeholder en PIN-kode. jfa-go læser filen og sender PIN-koden til brugeren.",
|
||||
"pathToJellyfin": "Sti til Jellyfin's konfigurations mappe",
|
||||
"pathToJellyfinNotice": "Hvis du ikke ved hvor dette er, kan du prøve at nulstille din adgangskode i Jellyfin. En popup med '<sti til jellyfin>/passwordreset - *. Json' vises.",
|
||||
"resetLinks": "Send et link i stedet for en PIN-kode",
|
||||
"resetLinksNotice": "Hvis Ombi integration er aktiveret, skal du bruge denne til at synkronisere nulstilling af Jellyfin's adgangskode med Ombi.",
|
||||
"resetLinksLanguage": "Standard sprog til nulstillings link"
|
||||
},
|
||||
"passwordValidation": {
|
||||
"title": "Validering af adgangskode",
|
||||
"description": "Hvis aktiveret, vises et sæt adgangskrav på siden til oprettelse af konto, såsom minimumslængde, store/små bogstaver osv.",
|
||||
"length": "Længde",
|
||||
"uppercase": "Store bogstaver",
|
||||
"lowercase": "Små bogstaver",
|
||||
"numbers": "Tal",
|
||||
"special": "Specialtegn (%, * osv.)"
|
||||
},
|
||||
"helpMessages": {
|
||||
"title": "Hjælpe Meddelelser",
|
||||
"description": "Disse meddelelser vises på siden til oprettelse af konto og i nogle e-mails.",
|
||||
"contactMessage": "Kontakt Meddelelse",
|
||||
"contactMessageNotice": "Vises nederst på alle sider undtagen admin-siden.",
|
||||
"helpMessage": "Hjælpe Meddelelse",
|
||||
"helpMessageNotice": "Vises på siden til oprettelse af konto.",
|
||||
"successMessage": "Succes Meddelelse",
|
||||
"successMessageNotice": "Vises når en bruger opretter sin konto.",
|
||||
"emailMessage": "E-mail Meddelelse",
|
||||
"emailMessageNotice": "Vises i bunden af e-mails."
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"endPage": {
|
||||
"finished": "Finished!",
|
||||
"restartMessage": "There are more settings you can configure on the admin page. Click below to restart, then refresh the page.",
|
||||
"restartMessage": "You can configure Discord/Telegram/Matrix bots, customize your messages and more in Settings. Click below to restart, then refresh the page.",
|
||||
"refreshPage": "Refresh"
|
||||
},
|
||||
"language": {
|
||||
@@ -79,6 +79,10 @@
|
||||
"description": "By connecting to Ombi, both a Jellyfin and Ombi account will be created when a user joins through jfa-go. After setup is finished, go to Settings to set a default profile for new ombi users.",
|
||||
"apiKeyNotice": "Find this in the first tab of Ombi settings."
|
||||
},
|
||||
"messages": {
|
||||
"title": "Messages",
|
||||
"description": "jfa-go can send password resets and various messages through Email, Discord, Telegram, and/or Matrix. You can set up email below, and the others can be configured in Settings later. Instructions can be found on the {n}. If you don't need this, you can disable these features here."
|
||||
},
|
||||
"email": {
|
||||
"title": "Email",
|
||||
"description": "jfa-go can send password reset PINs and various notifications through email. You can connect to an SMTP server, or use the {n} API.",
|
||||
@@ -93,16 +97,16 @@
|
||||
"mailgunApiURL": "API URL"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Notifications",
|
||||
"description": "If enabled, you can choose (per invite) to receive an email when an invite expires, or a user is created. If you didn't choose the Jellyfin login method, make sure you provided your email address."
|
||||
"title": "Admin Notifications",
|
||||
"description": "If enabled, you can choose (per invite) to receive an message when an invite expires, or a user is created. If you didn't choose the Jellyfin login method, make sure you provided your email address, or add another contact method later."
|
||||
},
|
||||
"welcomeEmails": {
|
||||
"title": "Welcome emails",
|
||||
"description": "If enabled, an email will be sent to new users with the Jellyfin/Emby URL and their username."
|
||||
"title": "Welcome messages",
|
||||
"description": "If enabled, an message will be sent to new users with the Jellyfin/Emby URL and their username."
|
||||
},
|
||||
"inviteEmails": {
|
||||
"title": "Invite Emails",
|
||||
"description": "If enabled, you can send invites directly to a user's email address. Because you might be using a reverse proxy, you need to provide the URL invites are accessed from. Write your URL Base, and append '/invite'."
|
||||
"title": "Invite Messages",
|
||||
"description": "If enabled, you can send invites directly to a user's email address, Discord or Matrix user. Because you might be using a reverse proxy, you need to provide the URL invites are accessed from. Write your URL Base, and append '/invite'."
|
||||
},
|
||||
"passwordResets": {
|
||||
"title": "Password Resets",
|
||||
@@ -111,7 +115,9 @@
|
||||
"pathToJellyfinNotice": "If you don't know where this is, try resetting your password in Jellyfin. A popup with '<path to jellyfin>/passwordreset-*.json' will appear.",
|
||||
"resetLinks": "Send a link instead of a PIN",
|
||||
"resetLinksNotice": "If Ombi integration is enabled, use this to sync Jellyfin password resets with Ombi.",
|
||||
"resetLinksLanguage": "Default reset link language"
|
||||
"resetLinksLanguage": "Default reset link language",
|
||||
"setPassword": "Set password through link",
|
||||
"setPasswordNotice": "Enabling this means the user doesn't have to change their password from the PIN after the reset. Password validation will also be enforced."
|
||||
},
|
||||
"passwordValidation": {
|
||||
"title": "Password Validation",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"endPage": {
|
||||
"finished": "Klaar!",
|
||||
"restartMessage": "Er staan meer instellingen op de beheerderspagina. Druk op de knop hieronder om opnieuw op te starten, en ververs daarna de pagina.",
|
||||
"restartMessage": "Je kunt Discord/Telegram/Matrix bots instellen, berichten aanpassen en meer bij Instellingen. Klik hieronder om te herstarten, en ververs de pagina.",
|
||||
"refreshPage": "Verversen"
|
||||
},
|
||||
"language": {
|
||||
@@ -86,16 +86,16 @@
|
||||
"mailgunApiURL": "API-URL"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Meldingen",
|
||||
"description": "Indien ingeschakeld kun je er (per uitnodiging) voor kiezen om een e-mail te ontvangen wanneer een uitnodiging verloopt of er een gebruiker wordt aangemaakt. Als je niet inlogt via Jellyfin, controleer dan dat je je e-mailadres hebt ingevuld."
|
||||
"title": "Beheersmeldingen",
|
||||
"description": "Indien ingeschakeld kun je er (per uitnodiging) voor kiezen om een bericht te ontvangen wanneer een uitnodiging verloopt of er een gebruiker wordt aangemaakt. Als je niet inlogt via Jellyfin, controleer dan dat je je e-mailadres hebt ingevuld, of voeg later een andere contactmethode toe."
|
||||
},
|
||||
"welcomeEmails": {
|
||||
"title": "Welkomste-mails",
|
||||
"description": "Indien ingeschakeld wordt er een e-mail gestuurd aan nieuwe gebruikers met de Jellyfin/Emby-URL en hun gebruikersnaam."
|
||||
"title": "Welkomstberichten",
|
||||
"description": "Indien ingeschakeld wordt er een bericht gestuurd aan nieuwe gebruikers met de Jellyfin/Emby-URL en hun gebruikersnaam."
|
||||
},
|
||||
"inviteEmails": {
|
||||
"title": "Uitnodigingse-mails",
|
||||
"description": "Indien ingeschakeld kun je uitnodigingen direct naar het e-mailadres van gebruikers sturen. Omdat je misschien een reverse proxy gebruikt moet je het URL opgeven waarlangs uitnodigingen geopend worden. Geef de URL-basis op, gevolgd door '/invite'."
|
||||
"title": "Uitnodigingsbericht",
|
||||
"description": "Indien ingeschakeld kun je uitnodigingen direct naar het e-mailadres, Discord- of Matrix-account van gebruikers sturen. Omdat je misschien een reverse proxy gebruikt moet je het URL opgeven waarlangs uitnodigingen geopend worden. Geef de URL-basis op, gevolgd door '/invite'."
|
||||
},
|
||||
"passwordResets": {
|
||||
"title": "Wachtwoordresets",
|
||||
@@ -104,7 +104,9 @@
|
||||
"pathToJellyfinNotice": "Als je niet weet waar dit is, probeer de je wachtwoord te resetten in Jellyfin. Er verschijnt dan een popup met '<path to jellyfin>/passwordreset-*.json'.",
|
||||
"resetLinks": "Stuur een link in plaats van een pincode",
|
||||
"resetLinksNotice": "Als Ombi-integratie is ingeschakeld, gebruik dan dit om Jellyfin wachtwoordresets te synchroniseren met Ombi.",
|
||||
"resetLinksLanguage": "Standaard reset-link taal"
|
||||
"resetLinksLanguage": "Standaard reset-link taal",
|
||||
"setPassword": "Stel wachtwoord in via link",
|
||||
"setPasswordNotice": "Als dit aanstaat hoeft de gebruiker het wachtwoord niet te wijzigen van de PINcode na de reset. Wachtwoordvalidatie wordt ook afgedwongen."
|
||||
},
|
||||
"passwordValidation": {
|
||||
"title": "Wachtwoordvalidatie",
|
||||
@@ -133,5 +135,9 @@
|
||||
"stable": "Stabiel",
|
||||
"title": "Updates",
|
||||
"description": "Vink aan om een melding te krijgen wanneer nieuwe updates beschikbaar zijn. jfa-go controleert {n} elke 30 minuten. Er worden geen IPs of persoonsgegevens verzameld."
|
||||
},
|
||||
"messages": {
|
||||
"title": "Berichten",
|
||||
"description": "jfa-go kan wachtwoordresets en verschillende berichten sturen via E-mail, Discord, Telegram, en/of Matrix. Je kunt e-mail hieronder instellen, en de rest kan later bij Instellingen aangepast worden. Instructies staan op de {n}. Als je dit niet nodig hebt, kun je deze onderdelen hier uitschakelen."
|
||||
}
|
||||
}
|
||||
|
||||
12
lang/telegram/da-dk.json
Normal file
12
lang/telegram/da-dk.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Dansk"
|
||||
},
|
||||
"strings": {
|
||||
"startMessage": "Hej!\nIndtast din Jellyfin PIN-kode her for at verificere din konto.",
|
||||
"matrixStartMessage": "Hej!\nIndtast PIN-koden under ind i Jellyfin tilmeldingssiden for at verificere din konto.",
|
||||
"invalidPIN": "Den PIN-kode var ugyldig, prøv igen.",
|
||||
"pinSuccess": "Sådan! Du kan nu gå tilbage til tilmeldingssiden.",
|
||||
"languageMessage": "Meddelelse: Se tilgængelige sprog med {command}, og vælg sprog med {command} <sprog kode>."
|
||||
}
|
||||
}
|
||||
12
lang/telegram/fr-fr.json
Normal file
12
lang/telegram/fr-fr.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Français (FR)"
|
||||
},
|
||||
"strings": {
|
||||
"startMessage": "Salut !\nEntrez votre code PIN Jellyfin ici pour vérifier votre compte.",
|
||||
"matrixStartMessage": "Salut !\nEntre votre code PIN Jellyfin dans la page d’inscription pour vérifier votre compte.",
|
||||
"invalidPIN": "Ce code PIN est invalide, réessayez.",
|
||||
"pinSuccess": "Succès ! Vous pouvez maintenant retourner à la page d’inscription.",
|
||||
"languageMessage": "Note : Découvrez les langues disponibles avec {command} et paramétrez la langue souhaitée avec {command} <language code>."
|
||||
}
|
||||
}
|
||||
2
log.go
2
log.go
@@ -16,11 +16,11 @@ var lineCache = linecache.NewLineCache(100)
|
||||
|
||||
func logOutput() (closeFunc func()) {
|
||||
old := os.Stdout
|
||||
log.Printf("Logging to \"%s\"", logPath)
|
||||
writers := []io.Writer{old, colorStripper{lineCache}}
|
||||
wExit := make(chan bool)
|
||||
r, w, _ := os.Pipe()
|
||||
if TRAY {
|
||||
log.Printf("Logging to \"%s\"", logPath)
|
||||
f, err := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
closeFunc = func() {}
|
||||
|
||||
7
main.go
7
main.go
@@ -359,6 +359,10 @@ func start(asDaemon, firstCall bool) {
|
||||
if err := app.storage.loadMatrixUsers(); err != nil {
|
||||
app.err.Printf("Failed to load Matrix users: %v", err)
|
||||
}
|
||||
app.storage.announcements_path = app.config.Section("files").Key("announcements").String()
|
||||
if err := app.storage.loadAnnouncements(); err != nil {
|
||||
app.err.Printf("Failed to load announcement templates: %v", err)
|
||||
}
|
||||
|
||||
app.storage.profiles_path = app.config.Section("files").Key("user_profiles").String()
|
||||
app.storage.loadProfiles()
|
||||
@@ -648,7 +652,8 @@ func main() {
|
||||
Exit(r)
|
||||
}
|
||||
}()
|
||||
defer logOutput()()
|
||||
f := logOutput()
|
||||
defer f()
|
||||
printVersion()
|
||||
SOCK = filepath.Join(temp, SOCK)
|
||||
fmt.Println("Socket:", SOCK)
|
||||
|
||||
189
matrix.go
189
matrix.go
@@ -1,22 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gomarkdown/markdown"
|
||||
"github.com/matrix-org/gomatrix"
|
||||
"maunium.net/go/mautrix"
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
type MatrixDaemon struct {
|
||||
Stopped bool
|
||||
ShutdownChannel chan string
|
||||
bot *gomatrix.Client
|
||||
userID string
|
||||
bot *mautrix.Client
|
||||
userID id.UserID
|
||||
tokens map[string]UnverifiedUser // Map of tokens to users
|
||||
languages map[string]string // Map of roomIDs to language codes
|
||||
languages map[id.RoomID]string // Map of roomIDs to language codes
|
||||
Encryption bool
|
||||
isEncrypted map[id.RoomID]bool
|
||||
crypto Crypto
|
||||
app *appContext
|
||||
start int64
|
||||
}
|
||||
|
||||
type UnverifiedUser struct {
|
||||
@@ -25,25 +31,20 @@ type UnverifiedUser struct {
|
||||
}
|
||||
|
||||
type MatrixUser struct {
|
||||
RoomID string
|
||||
UserID string
|
||||
Lang string
|
||||
Contact bool
|
||||
RoomID string
|
||||
Encrypted bool
|
||||
UserID string
|
||||
Lang string
|
||||
Contact bool
|
||||
}
|
||||
|
||||
type MatrixIdentifier struct {
|
||||
User string `json:"user"`
|
||||
IdentType string `json:"type"`
|
||||
}
|
||||
|
||||
func (m MatrixIdentifier) Type() string { return m.IdentType }
|
||||
|
||||
var matrixFilter = gomatrix.Filter{
|
||||
Room: gomatrix.RoomFilter{
|
||||
Timeline: gomatrix.FilterPart{
|
||||
Types: []string{
|
||||
"m.room.message",
|
||||
"m.room.member",
|
||||
var matrixFilter = mautrix.Filter{
|
||||
Room: mautrix.RoomFilter{
|
||||
Timeline: mautrix.FilterPart{
|
||||
Types: []event.Type{
|
||||
event.EventMessage,
|
||||
event.EventEncrypted,
|
||||
event.StateMember,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -53,8 +54,10 @@ var matrixFilter = gomatrix.Filter{
|
||||
"room_id",
|
||||
"state_key",
|
||||
"sender",
|
||||
"content.body",
|
||||
"content.membership",
|
||||
"content",
|
||||
"timestamp",
|
||||
// "content.body",
|
||||
// "content.membership",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -64,40 +67,43 @@ func newMatrixDaemon(app *appContext) (d *MatrixDaemon, err error) {
|
||||
token := matrix.Key("token").String()
|
||||
d = &MatrixDaemon{
|
||||
ShutdownChannel: make(chan string),
|
||||
userID: matrix.Key("user_id").String(),
|
||||
userID: id.UserID(matrix.Key("user_id").String()),
|
||||
tokens: map[string]UnverifiedUser{},
|
||||
languages: map[string]string{},
|
||||
languages: map[id.RoomID]string{},
|
||||
isEncrypted: map[id.RoomID]bool{},
|
||||
app: app,
|
||||
start: time.Now().UnixNano() / 1e6,
|
||||
}
|
||||
d.bot, err = gomatrix.NewClient(homeserver, d.userID, token)
|
||||
d.bot, err = mautrix.NewClient(homeserver, d.userID, token)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
filter, err := json.Marshal(matrixFilter)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resp, err := d.bot.CreateFilter(filter)
|
||||
d.bot.Store.SaveFilterID(d.userID, resp.FilterID)
|
||||
// resp, err := d.bot.CreateFilter(&matrixFilter)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// d.bot.Store.SaveFilterID(d.userID, resp.FilterID)
|
||||
for _, user := range app.storage.matrix {
|
||||
if user.Lang != "" {
|
||||
d.languages[user.RoomID] = user.Lang
|
||||
d.languages[id.RoomID(user.RoomID)] = user.Lang
|
||||
}
|
||||
d.isEncrypted[id.RoomID(user.RoomID)] = user.Encrypted
|
||||
}
|
||||
err = InitMatrixCrypto(d)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *MatrixDaemon) generateAccessToken(homeserver, username, password string) (string, error) {
|
||||
req := &gomatrix.ReqLogin{
|
||||
Type: "m.login.password",
|
||||
Identifier: MatrixIdentifier{
|
||||
User: username,
|
||||
IdentType: "m.id.user",
|
||||
req := &mautrix.ReqLogin{
|
||||
Type: mautrix.AuthTypePassword,
|
||||
Identifier: mautrix.UserIdentifier{
|
||||
Type: mautrix.IdentifierTypeUser,
|
||||
User: username,
|
||||
},
|
||||
Password: password,
|
||||
DeviceID: "jfa-go-" + commit,
|
||||
DeviceID: id.DeviceID("jfa-go-" + commit),
|
||||
}
|
||||
bot, err := gomatrix.NewClient(homeserver, username, "")
|
||||
bot, err := mautrix.NewClient(homeserver, id.UserID(username), "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -109,84 +115,95 @@ func (d *MatrixDaemon) generateAccessToken(homeserver, username, password string
|
||||
}
|
||||
|
||||
func (d *MatrixDaemon) run() {
|
||||
startTime := d.start
|
||||
d.app.info.Println("Starting Matrix bot daemon")
|
||||
syncer := d.bot.Syncer.(*gomatrix.DefaultSyncer)
|
||||
syncer.OnEventType("m.room.message", d.handleMessage)
|
||||
// syncer.OnEventType("m.room.member", d.handleMembership)
|
||||
syncer := d.bot.Syncer.(*mautrix.DefaultSyncer)
|
||||
HandleSyncerCrypto(startTime, d, syncer)
|
||||
syncer.OnEventType(event.EventMessage, d.handleMessage)
|
||||
|
||||
if err := d.bot.Sync(); err != nil {
|
||||
d.app.err.Printf("Matrix sync failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *MatrixDaemon) Shutdown() {
|
||||
CryptoShutdown(d)
|
||||
d.bot.StopSync()
|
||||
d.Stopped = true
|
||||
close(d.ShutdownChannel)
|
||||
}
|
||||
|
||||
func (d *MatrixDaemon) handleMessage(event *gomatrix.Event) {
|
||||
if event.Sender == d.userID {
|
||||
func (d *MatrixDaemon) handleMessage(source mautrix.EventSource, evt *event.Event) {
|
||||
if evt.Timestamp < d.start {
|
||||
return
|
||||
}
|
||||
if evt.Sender == d.userID {
|
||||
return
|
||||
}
|
||||
fmt.Printf("RECV %+v\n", evt.Content)
|
||||
lang := "en-us"
|
||||
if l, ok := d.languages[event.RoomID]; ok {
|
||||
if l, ok := d.languages[evt.RoomID]; ok {
|
||||
if _, ok := d.app.storage.lang.Telegram[l]; ok {
|
||||
lang = l
|
||||
}
|
||||
}
|
||||
sects := strings.Split(event.Content["body"].(string), " ")
|
||||
sects := strings.Split(evt.Content.Raw["body"].(string), " ")
|
||||
switch sects[0] {
|
||||
case "!lang":
|
||||
if len(sects) == 2 {
|
||||
d.commandLang(event, sects[1], lang)
|
||||
d.commandLang(evt, sects[1], lang)
|
||||
} else {
|
||||
d.commandLang(event, "", lang)
|
||||
d.commandLang(evt, "", lang)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *MatrixDaemon) commandLang(event *gomatrix.Event, code, lang string) {
|
||||
func (d *MatrixDaemon) commandLang(evt *event.Event, code, lang string) {
|
||||
if code == "" {
|
||||
list := "!lang <lang>\n"
|
||||
for c := range d.app.storage.lang.Telegram {
|
||||
list += fmt.Sprintf("%s: %s\n", c, d.app.storage.lang.Telegram[c].Meta.Name)
|
||||
}
|
||||
_, err := d.bot.SendText(
|
||||
event.RoomID,
|
||||
evt.RoomID,
|
||||
list,
|
||||
)
|
||||
if err != nil {
|
||||
d.app.err.Printf("Matrix: Failed to send message to \"%s\": %v", event.Sender, err)
|
||||
d.app.err.Printf("Matrix: Failed to send message to \"%s\": %v", evt.Sender, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if _, ok := d.app.storage.lang.Telegram[code]; !ok {
|
||||
return
|
||||
}
|
||||
d.languages[event.RoomID] = code
|
||||
if u, ok := d.app.storage.matrix[event.RoomID]; ok {
|
||||
d.languages[evt.RoomID] = code
|
||||
if u, ok := d.app.storage.matrix[string(evt.RoomID)]; ok {
|
||||
u.Lang = code
|
||||
d.app.storage.matrix[event.RoomID] = u
|
||||
d.app.storage.matrix[string(evt.RoomID)] = u
|
||||
if err := d.app.storage.storeMatrixUsers(); err != nil {
|
||||
d.app.err.Printf("Matrix: Failed to store Matrix users: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *MatrixDaemon) CreateRoom(userID string) (string, error) {
|
||||
room, err := d.bot.CreateRoom(&gomatrix.ReqCreateRoom{
|
||||
func (d *MatrixDaemon) CreateRoom(userID string) (roomID id.RoomID, encrypted bool, err error) {
|
||||
var room *mautrix.RespCreateRoom
|
||||
room, err = d.bot.CreateRoom(&mautrix.ReqCreateRoom{
|
||||
Visibility: "private",
|
||||
Invite: []string{userID},
|
||||
Invite: []id.UserID{id.UserID(userID)},
|
||||
Topic: d.app.config.Section("matrix").Key("topic").String(),
|
||||
IsDirect: true,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
return
|
||||
}
|
||||
return room.RoomID, nil
|
||||
encrypted = EncryptRoom(d, room, id.UserID(userID))
|
||||
roomID = room.RoomID
|
||||
return
|
||||
}
|
||||
|
||||
func (d *MatrixDaemon) SendStart(userID string) (ok bool) {
|
||||
roomID, err := d.CreateRoom(userID)
|
||||
roomID, encrypted, err := d.CreateRoom(userID)
|
||||
if err != nil {
|
||||
d.app.err.Printf("Failed to create room for user \"%s\": %v", userID, err)
|
||||
return
|
||||
@@ -196,15 +213,19 @@ func (d *MatrixDaemon) SendStart(userID string) (ok bool) {
|
||||
d.tokens[pin] = UnverifiedUser{
|
||||
false,
|
||||
&MatrixUser{
|
||||
RoomID: roomID,
|
||||
UserID: userID,
|
||||
Lang: lang,
|
||||
RoomID: string(roomID),
|
||||
UserID: userID,
|
||||
Lang: lang,
|
||||
Encrypted: encrypted,
|
||||
},
|
||||
}
|
||||
_, err = d.bot.SendText(
|
||||
err = d.sendToRoom(
|
||||
&event.MessageEventContent{
|
||||
MsgType: event.MsgText,
|
||||
Body: d.app.storage.lang.Telegram[lang].Strings.get("matrixStartMessage") + "\n\n" + pin + "\n\n" +
|
||||
d.app.storage.lang.Telegram[lang].Strings.template("languageMessage", tmpl{"command": "!lang"}),
|
||||
},
|
||||
roomID,
|
||||
d.app.storage.lang.Telegram[lang].Strings.get("matrixStartMessage")+"\n\n"+pin+"\n\n"+
|
||||
d.app.storage.lang.Telegram[lang].Strings.template("languageMessage", tmpl{"command": "!lang"}),
|
||||
)
|
||||
if err != nil {
|
||||
d.app.err.Printf("Matrix: Failed to send welcome message to \"%s\": %v", userID, err)
|
||||
@@ -214,18 +235,36 @@ func (d *MatrixDaemon) SendStart(userID string) (ok bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func (d *MatrixDaemon) Send(message *Message, roomID ...string) (err error) {
|
||||
func (d *MatrixDaemon) sendToRoom(content *event.MessageEventContent, roomID id.RoomID) (err error) {
|
||||
if encrypted, ok := d.isEncrypted[roomID]; ok && encrypted {
|
||||
err = SendEncrypted(d, content, roomID)
|
||||
} else {
|
||||
_, err = d.bot.SendMessageEvent(roomID, event.EventMessage, content, mautrix.ReqSendEvent{})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *MatrixDaemon) send(content *event.MessageEventContent, roomID id.RoomID) (err error) {
|
||||
_, err = d.bot.SendMessageEvent(roomID, event.EventMessage, content, mautrix.ReqSendEvent{})
|
||||
return
|
||||
}
|
||||
|
||||
func (d *MatrixDaemon) Send(message *Message, users ...MatrixUser) (err error) {
|
||||
md := ""
|
||||
if message.Markdown != "" {
|
||||
// Convert images to links
|
||||
md = string(markdown.ToHTML([]byte(strings.ReplaceAll(message.Markdown, "![", "[")), nil, renderer))
|
||||
}
|
||||
for _, id := range roomID {
|
||||
if md != "" {
|
||||
_, err = d.bot.SendFormattedText(id, message.Text, md)
|
||||
} else {
|
||||
_, err = d.bot.SendText(id, message.Text)
|
||||
}
|
||||
content := &event.MessageEventContent{
|
||||
MsgType: "m.text",
|
||||
Body: message.Text,
|
||||
}
|
||||
if md != "" {
|
||||
content.FormattedBody = md
|
||||
content.Format = "org.matrix.custom.html"
|
||||
}
|
||||
for _, user := range users {
|
||||
err = d.sendToRoom(content, id.RoomID(user.RoomID))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
224
matrix_crypto.go
Normal file
224
matrix_crypto.go
Normal file
@@ -0,0 +1,224 @@
|
||||
// +build e2ee
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"maunium.net/go/mautrix"
|
||||
"maunium.net/go/mautrix/crypto"
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
type Crypto struct {
|
||||
cryptoStore *crypto.GobStore
|
||||
olm *crypto.OlmMachine
|
||||
}
|
||||
|
||||
func MatrixE2EE() bool { return true }
|
||||
|
||||
type stateStore struct {
|
||||
isEncrypted *map[id.RoomID]bool
|
||||
}
|
||||
|
||||
func (m *stateStore) IsEncrypted(roomID id.RoomID) bool {
|
||||
// encrypted, ok := (*m.isEncrypted)[roomID]
|
||||
// return ok && encrypted
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *stateStore) GetEncryptionEvent(roomID id.RoomID) *event.EncryptionEventContent {
|
||||
return &event.EncryptionEventContent{
|
||||
Algorithm: id.AlgorithmMegolmV1,
|
||||
RotationPeriodMillis: 7 * 24 * 60 * 60 * 1000,
|
||||
RotationPeriodMessages: 100,
|
||||
}
|
||||
}
|
||||
|
||||
// Users are assumed to only have one common channel with the bot, so we can stub this out.
|
||||
func (m *stateStore) FindSharedRooms(userID id.UserID) []id.RoomID {
|
||||
// for _, user := range m.app.storage.matrix {
|
||||
// if id.UserID(user.UserID) == userID {
|
||||
// return []id.RoomID{id.RoomID(user.RoomID)}
|
||||
// }
|
||||
// }
|
||||
return []id.RoomID{}
|
||||
}
|
||||
|
||||
func (d *MatrixDaemon) getUserIDs(roomID id.RoomID) (list []id.UserID, err error) {
|
||||
members, err := d.bot.JoinedMembers(roomID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
list = make([]id.UserID, len(members.Joined))
|
||||
i := 0
|
||||
for id := range members.Joined {
|
||||
list[i] = id
|
||||
i++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type olmLogger struct {
|
||||
app *appContext
|
||||
}
|
||||
|
||||
func (o olmLogger) Error(message string, args ...interface{}) {
|
||||
o.app.err.Printf("OLM: "+message+"\n", args)
|
||||
}
|
||||
|
||||
func (o olmLogger) Warn(message string, args ...interface{}) {
|
||||
o.app.info.Printf("OLM: "+message+"\n", args)
|
||||
}
|
||||
|
||||
func (o olmLogger) Debug(message string, args ...interface{}) {
|
||||
o.app.debug.Printf("OLM: "+message+"\n", args)
|
||||
}
|
||||
|
||||
func (o olmLogger) Trace(message string, args ...interface{}) {
|
||||
if strings.HasPrefix(message, "Got membership state event") {
|
||||
return
|
||||
}
|
||||
o.app.debug.Printf("OLM [TRACE]: "+message+"\n", args)
|
||||
}
|
||||
|
||||
func InitMatrixCrypto(d *MatrixDaemon) (err error) {
|
||||
d.Encryption = d.app.config.Section("matrix").Key("encryption").MustBool(false)
|
||||
if !d.Encryption {
|
||||
return
|
||||
}
|
||||
for _, user := range d.app.storage.matrix {
|
||||
d.isEncrypted[id.RoomID(user.RoomID)] = user.Encrypted
|
||||
}
|
||||
dbPath := d.app.config.Section("files").Key("matrix_sql").String()
|
||||
// If the db is maintained after restart, element reports "The secure channel with the sender was corrupted" when sending a message from the bot.
|
||||
// This obviously isn't right, but it seems to work.
|
||||
// Since its not really used anyway, just use the deprecated GobStore. This reduces cgo usage anyway.
|
||||
// os.Remove(dbPath)
|
||||
var cryptoStore *crypto.GobStore
|
||||
cryptoStore, err = crypto.NewGobStore(dbPath)
|
||||
// d.db, err = sql.Open("sqlite3", dbPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
olmLog := &olmLogger{d.app}
|
||||
// deviceID := "jfa-go" + commit
|
||||
// cryptoStore := crypto.NewSQLCryptoStore(d.db, "sqlite3", string(d.userID)+deviceID, id.DeviceID(deviceID), []byte("jfa-go"), olmLog)
|
||||
// err = cryptoStore.CreateTables()
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
olm := crypto.NewOlmMachine(d.bot, olmLog, cryptoStore, &stateStore{&d.isEncrypted})
|
||||
olm.AllowUnverifiedDevices = true
|
||||
err = d.crypto.olm.Load()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
d.crypto = Crypto{
|
||||
cryptoStore: cryptoStore,
|
||||
olm: olm,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func HandleSyncerCrypto(startTime int64, d *MatrixDaemon, syncer *mautrix.DefaultSyncer) {
|
||||
if !d.Encryption {
|
||||
return
|
||||
}
|
||||
syncer.OnSync(func(resp *mautrix.RespSync, since string) bool {
|
||||
d.crypto.olm.ProcessSyncResponse(resp, since)
|
||||
return true
|
||||
})
|
||||
syncer.OnEventType(event.StateMember, func(source mautrix.EventSource, evt *event.Event) {
|
||||
d.crypto.olm.HandleMemberEvent(evt)
|
||||
// if evt.Content.AsMember().Membership != event.MembershipJoin {
|
||||
// return
|
||||
// }
|
||||
// userIDs, err := d.getUserIDs(evt.RoomID)
|
||||
// if err != nil || len(userIDs) < 2 {
|
||||
// fmt.Println("FS", err)
|
||||
// return
|
||||
// }
|
||||
// err = d.crypto.olm.ShareGroupSession(evt.RoomID, userIDs)
|
||||
// if err != nil {
|
||||
// fmt.Println("FS", err)
|
||||
// return
|
||||
// }
|
||||
})
|
||||
syncer.OnEventType(event.EventEncrypted, func(source mautrix.EventSource, evt *event.Event) {
|
||||
if evt.Timestamp < startTime {
|
||||
return
|
||||
}
|
||||
fmt.Printf("%+v\n", d.crypto.cryptoStore.GroupSessions)
|
||||
decrypted, err := d.crypto.olm.DecryptMegolmEvent(evt)
|
||||
if err != nil {
|
||||
d.app.err.Printf("Failed to decrypt Matrix message: %v", err)
|
||||
return
|
||||
}
|
||||
d.handleMessage(source, decrypted)
|
||||
})
|
||||
}
|
||||
|
||||
func CryptoShutdown(d *MatrixDaemon) {
|
||||
if d.Encryption {
|
||||
d.crypto.olm.FlushStore()
|
||||
}
|
||||
}
|
||||
|
||||
func EncryptRoom(d *MatrixDaemon, room *mautrix.RespCreateRoom, userID id.UserID) (encrypted bool) {
|
||||
if !d.Encryption {
|
||||
return
|
||||
}
|
||||
_, err := d.bot.SendStateEvent(room.RoomID, event.StateEncryption, "", &event.EncryptionEventContent{
|
||||
Algorithm: id.AlgorithmMegolmV1,
|
||||
RotationPeriodMillis: 7 * 24 * 60 * 60 * 1000,
|
||||
RotationPeriodMessages: 100,
|
||||
})
|
||||
if err == nil {
|
||||
encrypted = true
|
||||
} else {
|
||||
d.app.debug.Printf("Matrix: Failed to enable encryption in room: %v", err)
|
||||
return
|
||||
}
|
||||
d.isEncrypted[room.RoomID] = encrypted
|
||||
var userIDs []id.UserID
|
||||
userIDs, err = d.getUserIDs(room.RoomID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
userIDs = append(userIDs, userID)
|
||||
err = d.crypto.olm.ShareGroupSession(room.RoomID, userIDs)
|
||||
return
|
||||
}
|
||||
|
||||
func SendEncrypted(d *MatrixDaemon, content *event.MessageEventContent, roomID id.RoomID) (err error) {
|
||||
if !d.Encryption {
|
||||
err = d.send(content, roomID)
|
||||
return
|
||||
}
|
||||
var encrypted *event.EncryptedEventContent
|
||||
encrypted, err = d.crypto.olm.EncryptMegolmEvent(roomID, event.EventMessage, content)
|
||||
if err == crypto.SessionExpired || err == crypto.SessionNotShared || err == crypto.NoGroupSession {
|
||||
// err = d.crypto.olm.ShareGroupSession(id.RoomID(user.RoomID), []id.UserID{id.UserID(user.UserID), d.userID})
|
||||
var userIDs []id.UserID
|
||||
userIDs, err = d.getUserIDs(roomID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = d.crypto.olm.ShareGroupSession(roomID, userIDs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
encrypted, err = d.crypto.olm.EncryptMegolmEvent(roomID, event.EventMessage, content)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = d.bot.SendMessageEvent(roomID, event.EventEncrypted, &event.Content{Parsed: encrypted})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
35
matrix_nocrypto.go
Normal file
35
matrix_nocrypto.go
Normal file
@@ -0,0 +1,35 @@
|
||||
// +build !e2ee
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"maunium.net/go/mautrix"
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
type Crypto struct{}
|
||||
|
||||
func MatrixE2EE() bool { return false }
|
||||
|
||||
func InitMatrixCrypto(d *MatrixDaemon) (err error) {
|
||||
d.Encryption = false
|
||||
return
|
||||
}
|
||||
|
||||
func HandleSyncerCrypto(startTime int64, d *MatrixDaemon, syncer *mautrix.DefaultSyncer) {
|
||||
return
|
||||
}
|
||||
|
||||
func CryptoShutdown(d *MatrixDaemon) {
|
||||
return
|
||||
}
|
||||
|
||||
func EncryptRoom(d *MatrixDaemon, room *mautrix.RespCreateRoom, userID id.UserID) (encrypted bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func SendEncrypted(d *MatrixDaemon, content *event.MessageEventContent, roomID id.RoomID) (err error) {
|
||||
err = d.send(content, roomID)
|
||||
return
|
||||
}
|
||||
10
models.go
10
models.go
@@ -172,6 +172,16 @@ type announcementDTO struct {
|
||||
Message string `json:"message"` // Email content (markdown supported)
|
||||
}
|
||||
|
||||
type announcementTemplate struct {
|
||||
Name string `json:"name"` // Name of template
|
||||
Subject string `json:"subject"` // Email subject
|
||||
Message string `json:"message"` // Email content (markdown supported)
|
||||
}
|
||||
|
||||
type getAnnouncementsDTO struct {
|
||||
Announcements []string `json:"announcements"` // list of announcement names.
|
||||
}
|
||||
|
||||
type errorListDTO map[string]map[string]string
|
||||
|
||||
type configDTO map[string]interface{}
|
||||
|
||||
@@ -163,6 +163,12 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
|
||||
// api.POST(p + "/setDefaults", app.SetDefaults)
|
||||
api.POST(p+"/users/settings", app.ApplySettings)
|
||||
api.POST(p+"/users/announce", app.Announce)
|
||||
|
||||
api.GET(p+"/users/announce", app.GetAnnounceTemplates)
|
||||
api.POST(p+"/users/announce/template", app.SaveAnnounceTemplate)
|
||||
api.GET(p+"/users/announce/:name", app.GetAnnounceTemplate)
|
||||
api.DELETE(p+"/users/announce/:name", app.DeleteAnnounceTemplate)
|
||||
|
||||
api.GET(p+"/config/update", app.CheckUpdate)
|
||||
api.POST(p+"/config/update", app.ApplyUpdate)
|
||||
api.GET(p+"/config/emails", app.GetCustomEmails)
|
||||
|
||||
3
setup.go
3
setup.go
@@ -28,7 +28,7 @@ func (app *appContext) ServeSetup(gc *gin.Context) {
|
||||
"help_message": app.config.Section("ui").Key("help_message").String(),
|
||||
"success_message": app.config.Section("ui").Key("success_message").String(),
|
||||
},
|
||||
"email": {
|
||||
"messages": {
|
||||
"message": app.config.Section("messages").Key("message").String(),
|
||||
},
|
||||
}
|
||||
@@ -106,6 +106,7 @@ func (st *Storage) loadLangSetup(filesystems ...fs.FS) error {
|
||||
patchLang(&lang.Login, &fallback.Login, &english.Login)
|
||||
patchLang(&lang.JellyfinEmby, &fallback.JellyfinEmby, &english.JellyfinEmby)
|
||||
patchLang(&lang.Email, &fallback.Email, &english.Email)
|
||||
patchLang(&lang.Messages, &fallback.Messages, &english.Messages)
|
||||
patchLang(&lang.Notifications, &fallback.Notifications, &english.Notifications)
|
||||
patchLang(&lang.PasswordResets, &fallback.PasswordResets, &english.PasswordResets)
|
||||
patchLang(&lang.InviteEmails, &fallback.InviteEmails, &english.InviteEmails)
|
||||
|
||||
23
site/Makefile
Normal file
23
site/Makefile
Normal file
@@ -0,0 +1,23 @@
|
||||
all:
|
||||
-mkdir -p out
|
||||
cp index.html ../css/modal.css out/
|
||||
cp -r node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2 out/
|
||||
npx esbuild --bundle ts/main.ts --outfile=out/main.js --minify
|
||||
npx esbuild --bundle base.css --outfile=out/bundle.css --external:remixicon.css --external:modal.css --minify
|
||||
cd out && npx uncss index.html --stylesheets bundle.css > _bundle.css; cd ..
|
||||
mv out/_bundle.css out/bundle.css
|
||||
cd out && npx uncss index.html --stylesheets remixicon.css > _remixicon.css; cd ..
|
||||
mv out/_remixicon.css out/remixicon.css
|
||||
cp ../static/* out/
|
||||
|
||||
debug:
|
||||
-mkdir -p out
|
||||
cp index.html out/
|
||||
cp -r node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2 out/
|
||||
npx esbuild --bundle base.css --outfile=out/bundle.css --external:remixicon.css --minify
|
||||
npx esbuild --bundle ts/main.ts --sourcemap --outfile=out/main.js --minify
|
||||
cp ../static/* out/
|
||||
|
||||
monitor:
|
||||
npx live-server --watch=out --open=out/index.html &
|
||||
npx nodemon -e html,css,ts -i out --exec "make debug"
|
||||
3
site/README.md
Normal file
3
site/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
Landing page for [jfa-go.com](https://jfa-go.com).
|
||||
|
||||
`make all/debug` will place content in `out/`. `make monitor` will monitor code for changes and refresh a browser preview.
|
||||
13
site/base.css
Normal file
13
site/base.css
Normal file
@@ -0,0 +1,13 @@
|
||||
@import "../css/base.css";
|
||||
|
||||
:root {
|
||||
--c-1: #ffe3ef;
|
||||
--c-2: rgba(173, 201, 233, 0.4);
|
||||
--grad-base: rgb(238,174,202);
|
||||
--grad: linear-gradient(45deg, rgba(238,174,202,0.5) 0%, rgba(148,187,233,0.5) 100%);
|
||||
}
|
||||
|
||||
body {
|
||||
background: #AA5CC3;
|
||||
background: linear-gradient(90deg, #AA5CC3 0%, #00A4DC 100%) !important;
|
||||
}
|
||||
103
site/index.html
Normal file
103
site/index.html
Normal file
@@ -0,0 +1,103 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="light-theme">
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="bundle.css">
|
||||
<link rel="stylesheet" type="text/css" href="modal.css">
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="Description" content="jfa-go, a better way to manage Jellyfin users.">
|
||||
<meta name="color-scheme" content="dark light">
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||
<link rel="manifest" href="/site.webmanifest">
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
|
||||
<meta name="msapplication-TileColor" content="#603cba">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<title>jfa-go - a better way to manage Jellyfin users</title>
|
||||
</head>
|
||||
<body class="max-w-full overflow-x-hidden section">
|
||||
<div id="modal-deb" class="modal">
|
||||
<div class="modal-content wide card ~neutral">
|
||||
<span class="heading"> Debian/Ubuntu (apt)</span>
|
||||
<div class="mt-1">
|
||||
<pre style="margin: 0; line-height: 125%">curl https://apt.hrfee.dev/hrfee.pubkey.gpg | sudo apt-key add -
|
||||
|
||||
echo <span style="color: #aa5500">"deb https://apt.hrfee.dev trusty<span id="deb-unstable" class="unfocused">-unstable</span> main"</span> | sudo tee /etc/apt/sources.list.d/hrfee.list
|
||||
|
||||
sudo apt-get update
|
||||
|
||||
<span style="color: #aaaaaa; font-style: italic"># For servers</span>
|
||||
sudo apt-get install jfa-go
|
||||
<span style="color: #aaaaaa; font-style: italic"># ------</span>
|
||||
<span style="color: #aaaaaa; font-style: italic"># For desktops/servers with GUI (has dependencies)</span>
|
||||
sudo apt-get install jfa-go-tray
|
||||
<span style="color: #aaaaaa; font-style: italic"># ------</span></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="modal-docker" class="modal">
|
||||
<div class="modal-content wide card ~neutral">
|
||||
<span class="heading"> Docker</span>
|
||||
<div class="mt-1">
|
||||
<pre style="margin: 0; line-height: 125%">docker create <span style="color: #BB6622; font-weight: bold">\</span>
|
||||
--name <span style="color: #BA2121">"jfa-go"</span> <span style="color: #BB6622; font-weight: bold">\ </span><span style="color: #408080; font-style: italic"># Whatever you want to name it</span>
|
||||
-p 8056:8056 <span style="color: #BB6622; font-weight: bold">\</span>
|
||||
<span style="color: #408080; font-style: italic"># -p 8057:8057 if using tls</span>
|
||||
-v /path/to/.config/jfa-go:/data <span style="color: #BB6622; font-weight: bold">\ </span><span style="color: #408080; font-style: italic"># Path to wherever you want to store the config file and other data</span>
|
||||
-v /path/to/jellyfin:/jf <span style="color: #BB6622; font-weight: bold">\ </span><span style="color: #408080; font-style: italic"># Path to Jellyfin config directory, ignore if using Emby</span>
|
||||
-v /etc/localtime:/etc/localtime:ro <span style="color: #BB6622; font-weight: bold">\ </span><span style="color: #408080; font-style: italic"># Makes sure time is correct</span>
|
||||
hrfee/jfa-go<span id="docker-unstable" class="unfocused">:unstable</span></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-container" id="page-container">
|
||||
<div class="card ~neutral !low mb-1">
|
||||
<div class="row col flex center">
|
||||
<span class="heading welcome">jellyfin-accounts (go)</span>
|
||||
</div>
|
||||
<div class="row col flex center">
|
||||
<p class="content">a better way to manage your Jellyfin users.</p>
|
||||
</div>
|
||||
<span class="row col flex center supra">links</span>
|
||||
<div class="row col flex center">
|
||||
<a class="button ~info mr-half mt-1 mb-1" href="https://github.com/hrfee/jfa-go">github</a>
|
||||
<a class="button ~urge mt-1 mb-1 mr-half" href="https://wiki.jfa-go.com">wiki/docs</a>
|
||||
<a class="button ~positive mt-1 mb-1 mr-half" href="https://weblate.jfa-go.com">translation</a>
|
||||
</div>
|
||||
<p class="row col flex center supra">downloads</p>
|
||||
<p class="row col flex center support">instructions can be found <a target="_blank" href="https://github.com/hrfee/jfa-go#install">here</a></p>
|
||||
<p class="row col flex center support">note: tray icon builds on linux require extra dependencies, see the github README for more info.</p>
|
||||
<div class="row col flex center">
|
||||
<span class="button ~neutral !high mr-1 mt-1" id="download-stable">Stable</span>
|
||||
<span class="button ~neutral mt-1 mr-1" id="download-unstable">Unstable</span>
|
||||
</div>
|
||||
<div class="mt-1" id="sect-stable">
|
||||
<p class="row center">Usually released once/twice every month, and aren't necessarily super stable.</p>
|
||||
<div class="row col flex center">
|
||||
<a class="button ~info mr-half mb-half lang-link" target="_blank" href="https://github.com/hrfee/jfa-go/releases">windows/mac/linux</a>
|
||||
<a class="button ~info mr-half mb-half lang-link" id="download-docker">docker</a>
|
||||
<a class="button ~info mr-half mb-half lang-link" id="download-deb">debian/ubuntu</a>
|
||||
<a class="button ~info mr-half mb-half lang-link" target="_blank" href="https://aur.archlinux.org/packages/jfa-go">arch (aur)</a>
|
||||
<a class="button ~info mr-half mb-half lang-link" target="_blank" href="https://aur.archlinux.org/packages/jfa-go-bin">arch (aur binary)</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-1 unfocused" id="sect-unstable">
|
||||
<p class="row center">These are built on every commit, so may include incomplete/broken features.</p>
|
||||
<div class="row col flex center">
|
||||
<a class="button ~info mr-half mb-half lang-link" target="_blank" href="https://dl.jfa-go.com/view/hrfee/jfa-go">windows/mac/linux</a>
|
||||
<a class="button ~info mr-half mb-half lang-link" id="download-docker-unstable">docker</a>
|
||||
<a class="button ~info mr-half mb-half lang-link" id="download-deb-unstable">debian/ubuntu</a>
|
||||
<a class="button ~info mr-half mb-half lang-link" target="_blank" href="https://aur.archlinux.org/packages/jfa-go-git">arch (aur git)</a>
|
||||
</div>
|
||||
</div>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<a href="https://github.com/hrfee/jfa-go/blob/main/LICENSE" class="support">© 2021 Harvey Tindall</a>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<script src="main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
6074
site/package-lock.json
generated
Normal file
6074
site/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
20
site/package.json
Normal file
20
site/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "jfa-go.com",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Harvey Tindall <hrfee@hrfee.dev>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"a17t": "^0.5.1",
|
||||
"esbuild": "^0.12.12",
|
||||
"remixicon": "^2.5.0",
|
||||
"uncss": "^0.17.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"live-server": "^1.1.0"
|
||||
}
|
||||
}
|
||||
54
site/ts/main.ts
Normal file
54
site/ts/main.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Modal } from "../../ts/modules/modal.js";
|
||||
import { whichAnimationEvent } from "../../ts/modules/common.js";
|
||||
|
||||
interface window extends Window {
|
||||
animationEvent: string;
|
||||
}
|
||||
|
||||
declare var window: window;
|
||||
|
||||
window.animationEvent = whichAnimationEvent();
|
||||
|
||||
const debModal = new Modal(document.getElementById("modal-deb"));
|
||||
const debButton = document.getElementById("download-deb") as HTMLAnchorElement;
|
||||
debButton.onclick = debModal.toggle;
|
||||
|
||||
const debUnstable = document.getElementById("deb-unstable");
|
||||
const debUnstableButton = document.getElementById("download-deb-unstable") as HTMLAnchorElement;
|
||||
debUnstableButton.onclick = debModal.toggle;
|
||||
|
||||
const stableSect = document.getElementById("sect-stable");
|
||||
const unstableSect = document.getElementById("sect-unstable");
|
||||
|
||||
const stableButton = document.getElementById("download-stable") as HTMLSpanElement;
|
||||
const unstableButton = document.getElementById("download-unstable") as HTMLSpanElement;
|
||||
|
||||
const dockerUnstable = document.getElementById("docker-unstable");
|
||||
|
||||
stableButton.onclick = () => {
|
||||
debUnstable.classList.add("unfocused");
|
||||
dockerUnstable.classList.add("unfocused");
|
||||
stableButton.classList.add("!high");
|
||||
unstableButton.classList.remove("!high");
|
||||
stableSect.classList.remove("unfocused");
|
||||
unstableSect.classList.add("unfocused");
|
||||
|
||||
}
|
||||
|
||||
unstableButton.onclick = () => {
|
||||
debUnstable.classList.remove("unfocused");
|
||||
dockerUnstable.classList.remove("unfocused");
|
||||
unstableButton.classList.add("!high");
|
||||
stableButton.classList.remove("!high");
|
||||
stableSect.classList.add("unfocused");
|
||||
unstableSect.classList.remove("unfocused");
|
||||
}
|
||||
|
||||
const dockerModal = new Modal(document.getElementById("modal-docker"));
|
||||
const dockerButton = document.getElementById("download-docker") as HTMLSpanElement;
|
||||
const dockerUnstableButton = document.getElementById("download-docker-unstable") as HTMLSpanElement;
|
||||
|
||||
dockerButton.onclick = dockerModal.toggle;
|
||||
dockerUnstableButton.onclick = dockerModal.toggle;
|
||||
|
||||
|
||||
10
site/ts/tsconfig.json
Normal file
10
site/ts/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "../js",
|
||||
"target": "es6",
|
||||
"lib": ["dom", "es2017"],
|
||||
"typeRoots": ["./typings", "../node_modules/@types"],
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true
|
||||
}
|
||||
}
|
||||
41
storage.go
41
storage.go
@@ -15,22 +15,23 @@ import (
|
||||
)
|
||||
|
||||
type Storage struct {
|
||||
timePattern string
|
||||
invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path, customEmails_path, users_path, telegram_path, discord_path, matrix_path string
|
||||
users map[string]time.Time
|
||||
invites Invites
|
||||
profiles map[string]Profile
|
||||
defaultProfile string
|
||||
displayprefs, ombi_template map[string]interface{}
|
||||
emails map[string]EmailAddress
|
||||
telegram map[string]TelegramUser // Map of Jellyfin User IDs to telegram users.
|
||||
discord map[string]DiscordUser // Map of Jellyfin user IDs to discord users.
|
||||
matrix map[string]MatrixUser // Map of Jellyfin user IDs to Matrix users.
|
||||
customEmails customEmails
|
||||
policy mediabrowser.Policy
|
||||
configuration mediabrowser.Configuration
|
||||
lang Lang
|
||||
invitesLock, usersLock sync.Mutex
|
||||
timePattern string
|
||||
invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path, customEmails_path, users_path, telegram_path, discord_path, matrix_path, announcements_path, matrix_sql_path string
|
||||
users map[string]time.Time
|
||||
invites Invites
|
||||
profiles map[string]Profile
|
||||
defaultProfile string
|
||||
displayprefs, ombi_template map[string]interface{}
|
||||
emails map[string]EmailAddress
|
||||
telegram map[string]TelegramUser // Map of Jellyfin User IDs to telegram users.
|
||||
discord map[string]DiscordUser // Map of Jellyfin user IDs to discord users.
|
||||
matrix map[string]MatrixUser // Map of Jellyfin user IDs to Matrix users.
|
||||
customEmails customEmails
|
||||
policy mediabrowser.Policy
|
||||
configuration mediabrowser.Configuration
|
||||
lang Lang
|
||||
announcements map[string]announcementTemplate
|
||||
invitesLock, usersLock sync.Mutex
|
||||
}
|
||||
|
||||
type TelegramUser struct {
|
||||
@@ -839,6 +840,14 @@ func (st *Storage) storeOmbiTemplate() error {
|
||||
return storeJSON(st.ombi_path, st.ombi_template)
|
||||
}
|
||||
|
||||
func (st *Storage) loadAnnouncements() error {
|
||||
return loadJSON(st.announcements_path, &st.announcements)
|
||||
}
|
||||
|
||||
func (st *Storage) storeAnnouncements() error {
|
||||
return storeJSON(st.announcements_path, st.announcements)
|
||||
}
|
||||
|
||||
func (st *Storage) loadProfiles() error {
|
||||
err := loadJSON(st.profiles_path, &st.profiles)
|
||||
for name, profile := range st.profiles {
|
||||
|
||||
@@ -26,7 +26,13 @@ interface getPinResponse {
|
||||
token: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
|
||||
interface announcementTemplate {
|
||||
name: string;
|
||||
subject: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
var addDiscord: (passData: string) => void;
|
||||
|
||||
class user implements User {
|
||||
@@ -547,6 +553,8 @@ export class accountsList {
|
||||
|
||||
private _addUserButton = document.getElementById("accounts-add-user") as HTMLSpanElement;
|
||||
private _announceButton = document.getElementById("accounts-announce") as HTMLSpanElement;
|
||||
private _announceSaveButton = document.getElementById("save-announce") as HTMLSpanElement;
|
||||
private _announceNameLabel = document.getElementById("announce-name") as HTMLLabelElement;
|
||||
private _announcePreview: HTMLElement;
|
||||
private _previewLoaded = false;
|
||||
private _announceTextarea = document.getElementById("textarea-announce") as HTMLTextAreaElement;
|
||||
@@ -799,16 +807,60 @@ export class accountsList {
|
||||
this._announcePreview.innerHTML = content;
|
||||
}
|
||||
}
|
||||
announce = () => {
|
||||
saveAnnouncement = (event: Event) => {
|
||||
event.preventDefault();
|
||||
const form = document.getElementById("form-announce") as HTMLFormElement;
|
||||
const button = form.querySelector("span.submit") as HTMLSpanElement;
|
||||
if (this._announceNameLabel.classList.contains("unfocused")) {
|
||||
this._announceNameLabel.classList.remove("unfocused");
|
||||
form.onsubmit = this.saveAnnouncement;
|
||||
button.textContent = window.lang.get("strings", "saveAsTemplate");
|
||||
this._announceSaveButton.classList.add("unfocused");
|
||||
const details = document.getElementById("announce-details");
|
||||
details.classList.add("unfocused");
|
||||
return;
|
||||
}
|
||||
const name = (this._announceNameLabel.querySelector("input") as HTMLInputElement).value;
|
||||
if (!name) { return; }
|
||||
const subject = document.getElementById("announce-subject") as HTMLInputElement;
|
||||
let send: announcementTemplate = {
|
||||
name: name,
|
||||
subject: subject.value,
|
||||
message: this._announceTextarea.value
|
||||
}
|
||||
_post("/users/announce/template", send, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
this.reload();
|
||||
toggleLoader(button);
|
||||
window.modals.announce.close();
|
||||
if (req.status != 200 && req.status != 204) {
|
||||
window.notifications.customError("announcementError", window.lang.notif("errorFailureCheckLogs"));
|
||||
} else {
|
||||
window.notifications.customSuccess("announcementSuccess", window.lang.notif("savedAnnouncement"));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
announce = (event?: Event, template?: announcementTemplate) => {
|
||||
const modalHeader = document.getElementById("header-announce");
|
||||
modalHeader.textContent = window.lang.quantity("announceTo", this._collectUsers().length);
|
||||
const form = document.getElementById("form-announce") as HTMLFormElement;
|
||||
let list = this._collectUsers();
|
||||
const button = form.querySelector("span.submit") as HTMLSpanElement;
|
||||
removeLoader(button);
|
||||
button.textContent = window.lang.get("strings", "send");
|
||||
const details = document.getElementById("announce-details");
|
||||
details.classList.remove("unfocused");
|
||||
this._announceSaveButton.classList.remove("unfocused");
|
||||
const subject = document.getElementById("announce-subject") as HTMLInputElement;
|
||||
|
||||
subject.value = "";
|
||||
this._announceTextarea.value = "";
|
||||
this._announceNameLabel.classList.add("unfocused");
|
||||
if (template) {
|
||||
subject.value = template.subject;
|
||||
this._announceTextarea.value = template.message;
|
||||
} else {
|
||||
subject.value = "";
|
||||
this._announceTextarea.value = "";
|
||||
}
|
||||
form.onsubmit = (event: Event) => {
|
||||
event.preventDefault();
|
||||
toggleLoader(button);
|
||||
@@ -853,7 +905,53 @@ export class accountsList {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadTemplates = () => _get("/users/announce", null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status != 200) {
|
||||
this._announceButton.nextElementSibling.children[0].classList.add("unfocused");
|
||||
return;
|
||||
}
|
||||
this._announceButton.nextElementSibling.children[0].classList.remove("unfocused");
|
||||
const list = req.response["announcements"] as string[];
|
||||
if (list.length == 0) {
|
||||
this._announceButton.nextElementSibling.children[0].classList.add("unfocused");
|
||||
return;
|
||||
}
|
||||
const dList = document.getElementById("accounts-announce-templates") as HTMLDivElement;
|
||||
dList.textContent = '';
|
||||
for (let name of list) {
|
||||
const el = document.createElement("div") as HTMLDivElement;
|
||||
el.classList.add("flex-expand", "ellipsis", "mt-half");
|
||||
el.innerHTML = `
|
||||
<span class="button ~neutral sm full-width accounts-announce-template-button">${name}</span><span class="button ~critical fr ml-1 accounts-announce-template-delete">×</span>
|
||||
`;
|
||||
(el.querySelector("span.accounts-announce-template-button") as HTMLSpanElement).onclick = () => {
|
||||
_get("/users/announce/" + name, null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
let template: announcementTemplate;
|
||||
if (req.status != 200) {
|
||||
window.notifications.customError("getTemplateError", window.lang.notif("errorFailureCheckLogs"));
|
||||
} else {
|
||||
template = req.response;
|
||||
}
|
||||
this.announce(null, template);
|
||||
}
|
||||
});
|
||||
};
|
||||
(el.querySelector("span.accounts-announce-template-delete") as HTMLSpanElement).onclick = () => {
|
||||
_delete("/users/announce/" + name, null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status != 200) {
|
||||
window.notifications.customError("deleteTemplateError", window.lang.notif("errorFailureCheckLogs"));
|
||||
}
|
||||
this.reload();
|
||||
}
|
||||
});
|
||||
};
|
||||
dList.appendChild(el);
|
||||
}
|
||||
}
|
||||
});
|
||||
enableDisableUsers = () => {
|
||||
// We can share the delete modal for this
|
||||
const modalHeader = document.getElementById("header-delete-user");
|
||||
@@ -1154,26 +1252,31 @@ export class accountsList {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this._announceSaveButton.onclick = this.saveAnnouncement;
|
||||
}
|
||||
|
||||
reload = () => _get("/users", null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4 && req.status == 200) {
|
||||
// same method as inviteList.reload()
|
||||
let accountsOnDOM: { [id: string]: boolean } = {};
|
||||
for (let id in this._users) { accountsOnDOM[id] = true; }
|
||||
for (let u of (req.response["users"] as User[])) {
|
||||
if (u.id in this._users) {
|
||||
this._users[u.id].update(u);
|
||||
delete accountsOnDOM[u.id];
|
||||
} else {
|
||||
this.add(u);
|
||||
reload = () => {
|
||||
_get("/users", null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4 && req.status == 200) {
|
||||
// same method as inviteList.reload()
|
||||
let accountsOnDOM: { [id: string]: boolean } = {};
|
||||
for (let id in this._users) { accountsOnDOM[id] = true; }
|
||||
for (let u of (req.response["users"] as User[])) {
|
||||
if (u.id in this._users) {
|
||||
this._users[u.id].update(u);
|
||||
delete accountsOnDOM[u.id];
|
||||
} else {
|
||||
this.add(u);
|
||||
}
|
||||
}
|
||||
for (let id in accountsOnDOM) {
|
||||
this._users[id].remove();
|
||||
delete this._users[id];
|
||||
}
|
||||
this._checkCheckCount();
|
||||
}
|
||||
for (let id in accountsOnDOM) {
|
||||
this._users[id].remove();
|
||||
delete this._users[id];
|
||||
}
|
||||
this._checkCheckCount();
|
||||
}
|
||||
})
|
||||
});
|
||||
this.loadTemplates();
|
||||
}
|
||||
}
|
||||
|
||||
62
ts/setup.ts
62
ts/setup.ts
@@ -42,19 +42,29 @@ class Input {
|
||||
|
||||
class Checkbox {
|
||||
private _el: HTMLInputElement;
|
||||
private _hideEl: HTMLElement;
|
||||
get value(): string { return this._el.checked ? "true" : "false"; }
|
||||
set value(v: string) { this._el.checked = (v == "true") ? true : false; }
|
||||
|
||||
private _section: string;
|
||||
private _setting: string;
|
||||
broadcast = () => {
|
||||
let state = this._el.checked;
|
||||
if (this._hideEl.classList.contains("unfocused")) {
|
||||
state = false;
|
||||
}
|
||||
if (this._section && this._setting) {
|
||||
const ev = new CustomEvent(`settings-${this._section}-${this._setting}`, { "detail": this._el.checked })
|
||||
const ev = new CustomEvent(`settings-${this._section}-${this._setting}`, { "detail": state })
|
||||
document.dispatchEvent(ev);
|
||||
}
|
||||
}
|
||||
set onchange(f: () => void) {
|
||||
this._el.addEventListener("change", f);
|
||||
}
|
||||
constructor(el: HTMLElement, depends?: string, dependsTrue?: boolean, section?: string, setting?: string) {
|
||||
this._el = el as HTMLInputElement;
|
||||
this._hideEl = this._el as HTMLElement;
|
||||
if (this._hideEl.parentElement.tagName == "LABEL") { this._hideEl = this._hideEl.parentElement; }
|
||||
if (section && setting) {
|
||||
this._section = section;
|
||||
this._setting = setting;
|
||||
@@ -62,12 +72,12 @@ class Checkbox {
|
||||
}
|
||||
if (depends) {
|
||||
document.addEventListener(`settings-${section}-${depends}`, (event: boolEvent) => {
|
||||
let el = this._el as HTMLElement;
|
||||
if (el.parentElement.tagName == "LABEL") { el = el.parentElement; }
|
||||
if (event.detail !== dependsTrue) {
|
||||
el.classList.add("unfocused");
|
||||
this._hideEl.classList.add("unfocused");
|
||||
this.broadcast();
|
||||
} else {
|
||||
el.classList.remove("unfocused");
|
||||
this._hideEl.classList.remove("unfocused");
|
||||
this.broadcast();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -201,10 +211,11 @@ class LangSelect extends Select {
|
||||
}
|
||||
|
||||
window.lang = new lang(window.langFile as LangFile);
|
||||
html("language-description", window.lang.var("language", "description", `<a href="https://weblate.hrfee.pw">Weblate</a>`));
|
||||
html("email-description", window.lang.var("email", "description", `<a href="https://mailgun.com">Mailgun</a>`));
|
||||
html("email-dateformat-notice", window.lang.var("email", "dateFormatNotice", `<a href="https://strftime.ninja/">strftime.ninja</a>`));
|
||||
html("updates-description", window.lang.var("updates", "description", `<a href="https://builds.hrfee.dev/view/hrfee/jfa-go">buildrone</a>`));
|
||||
html("language-description", window.lang.var("language", "description", `<a target="_blank" href="https://weblate.jfa-go.com">Weblate</a>`));
|
||||
html("email-description", window.lang.var("email", "description", `<a target="_blank" href="https://mailgun.com">Mailgun</a>`));
|
||||
html("email-dateformat-notice", window.lang.var("email", "dateFormatNotice", `<a target="_blank" href="https://strftime.ninja/">strftime.ninja</a>`));
|
||||
html("updates-description", window.lang.var("updates", "description", `<a target="_blank" href="https://builds.hrfee.dev/view/hrfee/jfa-go">buildrone</a>`));
|
||||
html("messages-description", window.lang.var("messages", "description", `<a target="_blank" href="https://wiki.jfa-go.com">Wiki</a>`));
|
||||
|
||||
const settings = {
|
||||
"jellyfin": {
|
||||
@@ -243,12 +254,15 @@ const settings = {
|
||||
"number": new Input(get("password_validation-number"), "", 1, "enabled", true, "password_validation"),
|
||||
"special": new Input(get("password_validation-special"), "", 0, "enabled", true, "password_validation")
|
||||
},
|
||||
"messages": {
|
||||
"enabled": new Checkbox(get("messages-enabled"), "", false, "messages", "enabled"),
|
||||
"use_24h": new BoolRadios("email-24h", "enabled", true, "messages"),
|
||||
"date_format": new Input(get("email-date_format"), "", "%d/%m/%y", "enabled", true, "messages"),
|
||||
"message": new Input(get("email-message"), window.messages["messages"]["message"], "", "enabled", true, "messages")
|
||||
},
|
||||
"email": {
|
||||
"language": new LangSelect("email", get("email-language")),
|
||||
"no_username": new Checkbox(get("email-no_username"), "method", true, "email"),
|
||||
"use_24h": new BoolRadios("email-24h", "method", true, "email"),
|
||||
"date_format": new Input(get("email-date_format"), "", "%d/%m/%y", "method", true, "email"),
|
||||
"message": new Input(get("email-message"), window.messages["email"]["message"], "", "method", true, "email"),
|
||||
"method": new Select(get("email-method"), "", false, "email", "method"),
|
||||
"address": new Input(get("email-address"), "jellyfin@jellyf.in", "", "method", true, "email"),
|
||||
"from": new Input(get("email-from"), "", "Jellyfin", "method", true, "email")
|
||||
@@ -258,7 +272,8 @@ const settings = {
|
||||
"watch_directory": new Input(get("password_resets-watch_directory"), "", "", "enabled", true, "password_resets"),
|
||||
"subject": new Input(get("password_resets-subject"), "", "", "enabled", true, "password_resets"),
|
||||
"link_reset": new Checkbox(get("password_resets-link_reset"), "enabled", true, "password_resets", "link_reset"),
|
||||
"language": new LangSelect("pwr", get("password_resets-language"), "link_reset", true, "password_resets", "language")
|
||||
"language": new LangSelect("pwr", get("password_resets-language"), "link_reset", true, "password_resets", "language"),
|
||||
"set_password": new Checkbox(get("password_resets-set_password"), "link_reset", true, "password_resets", "set_password")
|
||||
},
|
||||
"notifications": {
|
||||
"enabled": new Checkbox(get("notifications-enabled"))
|
||||
@@ -342,12 +357,23 @@ const emailMethodChange = () => {
|
||||
const val = settings["email"]["method"].value;
|
||||
const smtp = document.getElementById("email-smtp");
|
||||
const mailgun = document.getElementById("email-mailgun");
|
||||
if (val == "smtp") {
|
||||
smtp.classList.remove("unfocused");
|
||||
mailgun.classList.add("unfocused");
|
||||
const emailSect = document.getElementById("email-sect");
|
||||
const enabled = settings["messages"]["enabled"].value;
|
||||
if (enabled == "false") {
|
||||
for (let el of relatedToEmail) {
|
||||
el.classList.add("hidden");
|
||||
}
|
||||
emailSect.classList.add("unfocused");
|
||||
return;
|
||||
} else {
|
||||
for (let el of relatedToEmail) {
|
||||
el.classList.remove("hidden");
|
||||
}
|
||||
emailSect.classList.remove("unfocused");
|
||||
}
|
||||
if (val == "smtp") {
|
||||
smtp.classList.remove("unfocused");
|
||||
mailgun.classList.add("unfocused");
|
||||
} else if (val == "mailgun") {
|
||||
mailgun.classList.remove("unfocused");
|
||||
smtp.classList.add("unfocused");
|
||||
@@ -357,12 +383,10 @@ const emailMethodChange = () => {
|
||||
} else {
|
||||
mailgun.classList.add("unfocused");
|
||||
smtp.classList.add("unfocused");
|
||||
for (let el of relatedToEmail) {
|
||||
el.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
};
|
||||
settings["email"]["method"].onchange = emailMethodChange;
|
||||
settings["messages"]["enabled"].onchange = emailMethodChange;
|
||||
emailMethodChange();
|
||||
|
||||
const embyHidePWR = () => {
|
||||
|
||||
Reference in New Issue
Block a user