1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-10-18 17:10:11 +00:00

Compare commits

..

No commits in common. "6ffdd4dad79c1ae834fdfd672c5d46afe0cbcc3a" and "5c87d109a329346e6839b7d3c8025d230f58313e" have entirely different histories.

26 changed files with 118 additions and 615 deletions

253
api.go
View File

@ -126,7 +126,7 @@ func (app *appContext) checkInvites() {
wait.Add(1) wait.Add(1)
go func(addr string) { go func(addr string) {
defer wait.Done() defer wait.Done()
msg, err := app.email.constructExpiry(code, data, app, false) msg, err := app.email.constructExpiry(code, data, app)
if err != nil { if err != nil {
app.err.Printf("%s: Failed to construct expiry notification: %s", code, err) app.err.Printf("%s: Failed to construct expiry notification: %s", code, err)
} else if err := app.email.send(msg, addr); err != nil { } else if err := app.email.send(msg, addr); err != nil {
@ -163,7 +163,7 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool
for address, settings := range notify { for address, settings := range notify {
if settings["notify-expiry"] { if settings["notify-expiry"] {
go func() { go func() {
msg, err := app.email.constructExpiry(code, inv, app, false) msg, err := app.email.constructExpiry(code, inv, app)
if err != nil { if err != nil {
app.err.Printf("%s: Failed to construct expiry notification: %s", code, err) app.err.Printf("%s: Failed to construct expiry notification: %s", code, err)
} else if err := app.email.send(msg, address); err != nil { } else if err := app.email.send(msg, address); err != nil {
@ -295,7 +295,7 @@ func (app *appContext) NewUserAdmin(gc *gin.Context) {
} }
if emailEnabled && app.config.Section("welcome_email").Key("enabled").MustBool(false) && req.Email != "" { if emailEnabled && app.config.Section("welcome_email").Key("enabled").MustBool(false) && req.Email != "" {
app.debug.Printf("%s: Sending welcome email to %s", req.Username, req.Email) app.debug.Printf("%s: Sending welcome email to %s", req.Username, req.Email)
msg, err := app.email.constructWelcome(req.Username, app, false) msg, err := app.email.constructWelcome(req.Username, app)
if err != nil { if err != nil {
app.err.Printf("%s: Failed to construct welcome email: %s", req.Username, err) app.err.Printf("%s: Failed to construct welcome email: %s", req.Username, err)
respondUser(500, true, false, err.Error(), gc) respondUser(500, true, false, err.Error(), gc)
@ -351,7 +351,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
f = func(gc *gin.Context) { f = func(gc *gin.Context) {
app.debug.Printf("%s: Email confirmation required", req.Code) app.debug.Printf("%s: Email confirmation required", req.Code)
respond(401, "confirmEmail", gc) respond(401, "confirmEmail", gc)
msg, err := app.email.constructConfirmation(req.Code, req.Username, key, app, false) msg, err := app.email.constructConfirmation(req.Code, req.Username, key, app)
if err != nil { if err != nil {
app.err.Printf("%s: Failed to construct confirmation email: %s", req.Code, err) app.err.Printf("%s: Failed to construct confirmation email: %s", req.Code, err)
} else if err := app.email.send(msg, req.Email); err != nil { } else if err := app.email.send(msg, req.Email); err != nil {
@ -380,7 +380,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
for address, settings := range invite.Notify { for address, settings := range invite.Notify {
if settings["notify-creation"] { if settings["notify-creation"] {
go func() { go func() {
msg, err := app.email.constructCreated(req.Code, req.Username, req.Email, invite, app, false) msg, err := app.email.constructCreated(req.Code, req.Username, req.Email, invite, app)
if err != nil { if err != nil {
app.err.Printf("%s: Failed to construct user creation notification: %s", req.Code, err) app.err.Printf("%s: Failed to construct user creation notification: %s", req.Code, err)
} else if err := app.email.send(msg, address); err != nil { } else if err := app.email.send(msg, address); err != nil {
@ -436,7 +436,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
} }
if emailEnabled && app.config.Section("welcome_email").Key("enabled").MustBool(false) && req.Email != "" { if emailEnabled && app.config.Section("welcome_email").Key("enabled").MustBool(false) && req.Email != "" {
app.debug.Printf("%s: Sending welcome email to %s", req.Username, req.Email) app.debug.Printf("%s: Sending welcome email to %s", req.Username, req.Email)
msg, err := app.email.constructWelcome(req.Username, app, false) msg, err := app.email.constructWelcome(req.Username, app)
if err != nil { if err != nil {
app.err.Printf("%s: Failed to construct welcome email: %s", req.Username, err) app.err.Printf("%s: Failed to construct welcome email: %s", req.Username, err)
} else if err := app.email.send(msg, req.Email); err != nil { } else if err := app.email.send(msg, req.Email); err != nil {
@ -516,7 +516,7 @@ func (app *appContext) Announce(gc *gin.Context) {
} }
addresses = append(addresses, addr.(string)) addresses = append(addresses, addr.(string))
} }
msg, err := app.email.constructTemplate(req.Subject, req.Message, app) msg, err := app.email.constructAnnouncement(req.Subject, req.Message, app)
if err != nil { if err != nil {
app.err.Printf("Failed to construct announcement emails: %s", err) app.err.Printf("Failed to construct announcement emails: %s", err)
respondBool(500, false, gc) respondBool(500, false, gc)
@ -576,7 +576,7 @@ func (app *appContext) DeleteUsers(gc *gin.Context) {
} }
if len(addresses) != 0 { if len(addresses) != 0 {
go func(reason string, addresses []string) { go func(reason string, addresses []string) {
msg, err := app.email.constructDeleted(reason, app, false) msg, err := app.email.constructDeleted(reason, app)
if err != nil { if err != nil {
app.err.Printf("Failed to construct account deletion emails: %s", err) app.err.Printf("Failed to construct account deletion emails: %s", err)
} else if err := app.email.send(msg, addresses...); err != nil { } else if err := app.email.send(msg, addresses...); err != nil {
@ -638,7 +638,7 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
if emailEnabled && req.Email != "" && app.config.Section("invite_emails").Key("enabled").MustBool(false) { if emailEnabled && req.Email != "" && app.config.Section("invite_emails").Key("enabled").MustBool(false) {
app.debug.Printf("%s: Sending invite email", inviteCode) app.debug.Printf("%s: Sending invite email", inviteCode)
invite.Email = req.Email invite.Email = req.Email
msg, err := app.email.constructInvite(inviteCode, invite, app, false) msg, err := app.email.constructInvite(inviteCode, invite, app)
if err != nil { if err != nil {
invite.Email = fmt.Sprintf("Failed to send to %s", req.Email) invite.Email = fmt.Sprintf("Failed to send to %s", req.Email)
app.err.Printf("%s: Failed to construct invite email: %s", inviteCode, err) app.err.Printf("%s: Failed to construct invite email: %s", inviteCode, err)
@ -1269,239 +1269,6 @@ func (app *appContext) ModifyConfig(gc *gin.Context) {
} }
} }
// @Summary Get a list of email names and IDs.
// @Produce json
// @Success 200 {object} emailListDTO
// @Router /config/emails [get]
// @tags Configuration
func (app *appContext) GetEmails(gc *gin.Context) {
gc.JSON(200, emailListDTO{
"UserCreated": app.storage.lang.Email["en-us"].UserCreated["name"],
"InviteExpiry": app.storage.lang.Email["en-us"].InviteExpiry["name"],
"PasswordReset": app.storage.lang.Email["en-us"].PasswordReset["name"],
"UserDeleted": app.storage.lang.Email["en-us"].UserDeleted["name"],
"InviteEmail": app.storage.lang.Email["en-us"].InviteEmail["name"],
"WelcomeEmail": app.storage.lang.Email["en-us"].WelcomeEmail["name"],
"EmailConfirmation": app.storage.lang.Email["en-us"].EmailConfirmation["name"],
})
}
// @Summary Sets the corresponding custom email.
// @Produce json
// @Param customEmail body customEmail true "Content = email (in markdown)."
// @Success 200 {object} boolResponse
// @Failure 400 {object} boolResponse
// @Failure 500 {object} boolResponse
// @Router /config/emails/{id} [post]
// @tags Configuration
func (app *appContext) SetEmail(gc *gin.Context) {
var req customEmail
gc.BindJSON(&req)
id := gc.Param("id")
if req.Content == "" {
respondBool(400, false, gc)
return
}
if id == "UserCreated" {
app.storage.customEmails.UserCreated.Content = req.Content
app.storage.customEmails.UserCreated.Enabled = true
} else if id == "InviteExpiry" {
app.storage.customEmails.InviteExpiry.Content = req.Content
app.storage.customEmails.InviteExpiry.Enabled = true
} else if id == "PasswordReset" {
app.storage.customEmails.PasswordReset.Content = req.Content
app.storage.customEmails.PasswordReset.Enabled = true
} else if id == "UserDeleted" {
app.storage.customEmails.UserDeleted.Content = req.Content
app.storage.customEmails.UserDeleted.Enabled = true
} else if id == "InviteEmail" {
app.storage.customEmails.InviteEmail.Content = req.Content
app.storage.customEmails.InviteEmail.Enabled = true
} else if id == "WelcomeEmail" {
app.storage.customEmails.WelcomeEmail.Content = req.Content
app.storage.customEmails.WelcomeEmail.Enabled = true
} else if id == "EmailConfirmation" {
app.storage.customEmails.EmailConfirmation.Content = req.Content
app.storage.customEmails.EmailConfirmation.Enabled = true
} else {
respondBool(400, false, gc)
return
}
if app.storage.storeCustomEmails() != nil {
respondBool(500, false, gc)
return
}
respondBool(200, true, gc)
}
// @Summary Enable/Disable custom email.
// @Produce json
// @Success 200 {object} boolResponse
// @Failure 400 {object} boolResponse
// @Failure 500 {object} boolResponse
// @Router /config/emails/{id}/{enable/disable} [post]
// @tags Configuration
func (app *appContext) SetEmailState(gc *gin.Context) {
id := gc.Param("id")
s := gc.Param("state")
enabled := false
if s == "enable" {
enabled = true
} else if s != "disable" {
respondBool(400, false, gc)
}
if id == "UserCreated" {
app.storage.customEmails.UserCreated.Enabled = enabled
} else if id == "InviteExpiry" {
app.storage.customEmails.InviteExpiry.Enabled = enabled
} else if id == "PasswordReset" {
app.storage.customEmails.PasswordReset.Enabled = enabled
} else if id == "UserDeleted" {
app.storage.customEmails.UserDeleted.Enabled = enabled
} else if id == "InviteEmail" {
app.storage.customEmails.InviteEmail.Enabled = enabled
} else if id == "WelcomeEmail" {
app.storage.customEmails.WelcomeEmail.Enabled = enabled
} else if id == "EmailConfirmation" {
app.storage.customEmails.EmailConfirmation.Enabled = enabled
} else {
respondBool(400, false, gc)
return
}
if app.storage.storeCustomEmails() != nil {
respondBool(500, false, gc)
return
}
respondBool(200, true, gc)
}
// @Summary Returns the boilerplate email and list of used variables in it.
// @Produce json
// @Success 200 {object} customEmail
// @Failure 400 {object} boolResponse
// @Failure 500 {object} boolResponse
// @Router /config/emails/{id} [get]
// @tags Configuration
func (app *appContext) GetEmail(gc *gin.Context) {
id := gc.Param("id")
var content string
var err error
var msg *Email
var variables []string
var writeVars func(variables []string)
newEmail := false
if id == "UserCreated" {
content = app.storage.customEmails.UserCreated.Content
if content == "" {
newEmail = true
msg, err = app.email.constructCreated("", "", "", Invite{}, app, true)
content = msg.text
} else {
variables = app.storage.customEmails.UserCreated.Variables
}
writeVars = func(variables []string) { app.storage.customEmails.UserCreated.Variables = variables }
// app.storage.customEmails.UserCreated = content
} else if id == "InviteExpiry" {
content = app.storage.customEmails.InviteExpiry.Content
if content == "" {
newEmail = true
msg, err = app.email.constructExpiry("", Invite{}, app, true)
content = msg.text
} else {
variables = app.storage.customEmails.InviteExpiry.Variables
}
writeVars = func(variables []string) { app.storage.customEmails.InviteExpiry.Variables = variables }
// app.storage.customEmails.InviteExpiry = content
} else if id == "PasswordReset" {
content = app.storage.customEmails.PasswordReset.Content
if content == "" {
newEmail = true
msg, err = app.email.constructReset(PasswordReset{}, app, true)
content = msg.text
} else {
variables = app.storage.customEmails.PasswordReset.Variables
}
writeVars = func(variables []string) { app.storage.customEmails.PasswordReset.Variables = variables }
// app.storage.customEmails.PasswordReset = content
} else if id == "UserDeleted" {
content = app.storage.customEmails.UserDeleted.Content
if content == "" {
newEmail = true
msg, err = app.email.constructDeleted("", app, true)
content = msg.text
} else {
variables = app.storage.customEmails.UserDeleted.Variables
}
writeVars = func(variables []string) { app.storage.customEmails.UserDeleted.Variables = variables }
// app.storage.customEmails.UserDeleted = content
} else if id == "InviteEmail" {
content = app.storage.customEmails.InviteEmail.Content
if content == "" {
newEmail = true
msg, err = app.email.constructInvite("", Invite{}, app, true)
content = msg.text
} else {
variables = app.storage.customEmails.InviteEmail.Variables
}
writeVars = func(variables []string) { app.storage.customEmails.InviteEmail.Variables = variables }
// app.storage.customEmails.InviteEmail = content
} else if id == "WelcomeEmail" {
content = app.storage.customEmails.WelcomeEmail.Content
if content == "" {
newEmail = true
msg, err = app.email.constructWelcome("", app, true)
content = msg.text
} else {
variables = app.storage.customEmails.WelcomeEmail.Variables
}
writeVars = func(variables []string) { app.storage.customEmails.WelcomeEmail.Variables = variables }
// app.storage.customEmails.WelcomeEmail = content
} else if id == "EmailConfirmation" {
content = app.storage.customEmails.EmailConfirmation.Content
if content == "" {
newEmail = true
msg, err = app.email.constructConfirmation("", "", "", app, true)
content = msg.text
} else {
variables = app.storage.customEmails.EmailConfirmation.Variables
}
writeVars = func(variables []string) { app.storage.customEmails.EmailConfirmation.Variables = variables }
// app.storage.customEmails.EmailConfirmation = content
} else {
respondBool(400, false, gc)
return
}
if err != nil {
respondBool(500, false, gc)
return
}
if newEmail {
variables = make([]string, strings.Count(content, "{"))
i := 0
found := false
buf := ""
for _, c := range content {
if !found && c != '{' && c != '}' {
continue
}
found = true
buf += string(c)
if c == '}' {
found = false
variables[i] = buf
buf = ""
i++
}
}
writeVars(variables)
}
if app.storage.storeCustomEmails() != nil {
respondBool(500, false, gc)
return
}
gc.JSON(200, customEmail{Content: content, Variables: variables})
}
// @Summary Logout by deleting refresh token from cookies. // @Summary Logout by deleting refresh token from cookies.
// @Produce json // @Produce json
// @Success 200 {object} boolResponse // @Success 200 {object} boolResponse
@ -1524,7 +1291,7 @@ func (app *appContext) Logout(gc *gin.Context) {
// @Produce json // @Produce json
// @Success 200 {object} langDTO // @Success 200 {object} langDTO
// @Failure 500 {object} stringResponse // @Failure 500 {object} stringResponse
// @Router /lang/{page} [get] // @Router /lang [get]
// @tags Other // @tags Other
func (app *appContext) GetLanguages(gc *gin.Context) { func (app *appContext) GetLanguages(gc *gin.Context) {
page := gc.Param("page") page := gc.Param("page")

View File

@ -32,12 +32,11 @@ func (app *appContext) loadConfig() error {
app.config.Section("jellyfin").Key("public_server").SetValue(app.config.Section("jellyfin").Key("public_server").MustString(app.config.Section("jellyfin").Key("server").String())) app.config.Section("jellyfin").Key("public_server").SetValue(app.config.Section("jellyfin").Key("public_server").MustString(app.config.Section("jellyfin").Key("server").String()))
for _, key := range app.config.Section("files").Keys() { for _, key := range app.config.Section("files").Keys() {
if name := key.Name(); name != "html_templates" && name != "lang_files" { if key.Name() != "html_templates" {
fmt.Println(name)
key.SetValue(key.MustString(filepath.Join(app.dataPath, (key.Name() + ".json")))) 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"} { for _, key := range []string{"user_configuration", "user_displayprefs", "user_profiles", "ombi_template", "invites", "emails", "user_template"} {
app.config.Section("files").Key(key).SetValue(app.config.Section("files").Key(key).MustString(filepath.Join(app.dataPath, (key + ".json")))) app.config.Section("files").Key(key).SetValue(app.config.Section("files").Key(key).MustString(filepath.Join(app.dataPath, (key + ".json"))))
} }
app.URLBase = strings.TrimSuffix(app.config.Section("ui").Key("url_base").MustString(""), "/") app.URLBase = strings.TrimSuffix(app.config.Section("ui").Key("url_base").MustString(""), "/")
@ -64,8 +63,8 @@ func (app *appContext) loadConfig() error {
app.config.Section("welcome_email").Key("email_html").SetValue(app.config.Section("welcome_email").Key("email_html").MustString("jfa-go:" + "welcome.html")) app.config.Section("welcome_email").Key("email_html").SetValue(app.config.Section("welcome_email").Key("email_html").MustString("jfa-go:" + "welcome.html"))
app.config.Section("welcome_email").Key("email_text").SetValue(app.config.Section("welcome_email").Key("email_text").MustString("jfa-go:" + "welcome.txt")) app.config.Section("welcome_email").Key("email_text").SetValue(app.config.Section("welcome_email").Key("email_text").MustString("jfa-go:" + "welcome.txt"))
app.config.Section("template_email").Key("email_html").SetValue(app.config.Section("template_email").Key("email_html").MustString("jfa-go:" + "template.html")) app.config.Section("announcement_email").Key("email_html").SetValue(app.config.Section("announcement_email").Key("email_html").MustString("jfa-go:" + "announcement.html"))
app.config.Section("template_email").Key("email_text").SetValue(app.config.Section("template_email").Key("email_text").MustString("jfa-go:" + "template.txt")) app.config.Section("announcement_email").Key("email_text").SetValue(app.config.Section("announcement_email").Key("email_text").MustString("jfa-go:" + "announcement.txt"))
app.config.Section("jellyfin").Key("version").SetValue(VERSION) app.config.Section("jellyfin").Key("version").SetValue(VERSION)
app.config.Section("jellyfin").Key("device").SetValue("jfa-go") app.config.Section("jellyfin").Key("device").SetValue("jfa-go")
@ -77,9 +76,6 @@ func (app *appContext) loadConfig() error {
emailEnabled = true emailEnabled = true
} }
app.storage.customEmails_path = app.config.Section("files").Key("custom_emails").String()
app.storage.loadCustomEmails()
substituteStrings = app.config.Section("jellyfin").Key("substitute_jellyfin_strings").MustString("") substituteStrings = app.config.Section("jellyfin").Key("substitute_jellyfin_strings").MustString("")
oldFormLang := app.config.Section("ui").Key("language").MustString("") oldFormLang := app.config.Section("ui").Key("language").MustString("")

View File

@ -853,14 +853,6 @@
"type": "text", "type": "text",
"value": "", "value": "",
"description": "The path to a directory which following the same form as the internal 'lang/' directory. See GitHub for more info." "description": "The path to a directory which following the same form as the internal 'lang/' directory. See GitHub for more info."
},
"custom_emails": {
"name": "Custom email content",
"required": false,
"requires_restart": false,
"type": "text",
"value": "",
"description": "JSON file generated by program in settings, different from email_html/email_text. See wiki for more info."
} }
} }
} }

287
email.go
View File

@ -18,6 +18,7 @@ import (
jEmail "github.com/jordan-wright/email" jEmail "github.com/jordan-wright/email"
"github.com/knz/strtime" "github.com/knz/strtime"
"github.com/mailgun/mailgun-go/v4" "github.com/mailgun/mailgun-go/v4"
stripmd "github.com/writeas/go-strip-markdown"
) )
// implements email sending, right now via smtp or mailgun. // implements email sending, right now via smtp or mailgun.
@ -211,58 +212,36 @@ func (emailer *Emailer) construct(app *appContext, section, keyFragment string,
return return
} }
func (emailer *Emailer) constructConfirmation(code, username, key string, app *appContext, noSub bool) (*Email, error) { func (emailer *Emailer) constructConfirmation(code, username, key string, app *appContext) (*Email, error) {
email := &Email{ email := &Email{
subject: app.config.Section("email_confirmation").Key("subject").MustString(emailer.lang.EmailConfirmation.get("title")), subject: app.config.Section("email_confirmation").Key("subject").MustString(emailer.lang.EmailConfirmation.get("title")),
} }
message := app.config.Section("email").Key("message").String()
inviteLink := app.config.Section("invite_emails").Key("url_base").String()
inviteLink = fmt.Sprintf("%s/%s?key=%s", inviteLink, code, key)
var err error var err error
template := map[string]interface{}{ email.html, email.text, err = emailer.construct(app, "email_confirmation", "email_", map[string]interface{}{
"helloUser": emailer.lang.Strings.template("helloUser", tmpl{"username": username}),
"clickBelow": emailer.lang.EmailConfirmation.get("clickBelow"), "clickBelow": emailer.lang.EmailConfirmation.get("clickBelow"),
"ifItWasNotYou": emailer.lang.Strings.get("ifItWasNotYou"), "ifItWasNotYou": emailer.lang.Strings.get("ifItWasNotYou"),
"urlVal": inviteLink,
"confirmEmail": emailer.lang.EmailConfirmation.get("confirmEmail"), "confirmEmail": emailer.lang.EmailConfirmation.get("confirmEmail"),
"message": "", "message": message,
"username": username, })
}
if noSub {
template["helloUser"] = emailer.lang.Strings.get("helloUser")
empty := []string{"confirmationURL"}
for _, v := range empty {
template[v] = "{" + v + "}"
}
} else {
message := app.config.Section("email").Key("message").String()
inviteLink := app.config.Section("invite_emails").Key("url_base").String()
inviteLink = fmt.Sprintf("%s/%s?key=%s", inviteLink, code, key)
template["helloUser"] = emailer.lang.Strings.template("helloUser", tmpl{"username": username})
template["confirmationURL"] = inviteLink
template["message"] = message
}
if app.storage.customEmails.EmailConfirmation.Enabled {
content := app.storage.customEmails.EmailConfirmation.Content
for _, v := range app.storage.customEmails.EmailConfirmation.Variables {
replaceWith, ok := template[v[1:len(v)-1]]
if ok {
content = strings.ReplaceAll(content, v, replaceWith.(string))
}
}
email, err = emailer.constructTemplate(email.subject, content, app)
} else {
email.html, email.text, err = emailer.construct(app, "email_confirmation", "email_", template)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
return email, nil return email, nil
} }
func (emailer *Emailer) constructTemplate(subject, md string, app *appContext) (*Email, error) { func (emailer *Emailer) constructAnnouncement(subject, md string, app *appContext) (*Email, error) {
email := &Email{subject: subject} email := &Email{subject: subject}
renderer := html.NewRenderer(html.RendererOptions{Flags: html.Smartypants}) renderer := html.NewRenderer(html.RendererOptions{Flags: html.Smartypants})
html := markdown.ToHTML([]byte(md), nil, renderer) html := markdown.ToHTML([]byte(md), nil, renderer)
text := stripMarkdown(md) text := strings.TrimPrefix(strings.TrimSuffix(stripmd.Strip(md), "</p>"), "<p>")
message := app.config.Section("email").Key("message").String() message := app.config.Section("email").Key("message").String()
var err error var err error
email.html, email.text, err = emailer.construct(app, "template_email", "email_", map[string]interface{}{ email.html, email.text, err = emailer.construct(app, "announcement_email", "email_", map[string]interface{}{
"text": template.HTML(html), "text": template.HTML(html),
"plaintext": text, "plaintext": text,
"message": message, "message": message,
@ -273,7 +252,7 @@ func (emailer *Emailer) constructTemplate(subject, md string, app *appContext) (
return email, nil return email, nil
} }
func (emailer *Emailer) constructInvite(code string, invite Invite, app *appContext, noSub bool) (*Email, error) { func (emailer *Emailer) constructInvite(code string, invite Invite, app *appContext) (*Email, error) {
email := &Email{ email := &Email{
subject: app.config.Section("email_confirmation").Key("subject").MustString(emailer.lang.InviteEmail.get("title")), subject: app.config.Section("email_confirmation").Key("subject").MustString(emailer.lang.InviteEmail.get("title")),
} }
@ -282,251 +261,120 @@ func (emailer *Emailer) constructInvite(code string, invite Invite, app *appCont
message := app.config.Section("email").Key("message").String() message := app.config.Section("email").Key("message").String()
inviteLink := app.config.Section("invite_emails").Key("url_base").String() inviteLink := app.config.Section("invite_emails").Key("url_base").String()
inviteLink = fmt.Sprintf("%s/%s", inviteLink, code) inviteLink = fmt.Sprintf("%s/%s", inviteLink, code)
template := map[string]interface{}{ var err error
email.html, email.text, err = emailer.construct(app, "invite_emails", "email_", map[string]interface{}{
"hello": emailer.lang.InviteEmail.get("hello"), "hello": emailer.lang.InviteEmail.get("hello"),
"youHaveBeenInvited": emailer.lang.InviteEmail.get("youHaveBeenInvited"), "youHaveBeenInvited": emailer.lang.InviteEmail.get("youHaveBeenInvited"),
"toJoin": emailer.lang.InviteEmail.get("toJoin"), "toJoin": emailer.lang.InviteEmail.get("toJoin"),
"inviteExpiry": emailer.lang.InviteEmail.template("inviteExpiry", tmpl{"date": d, "time": t, "expiresInMinutes": expiresIn}),
"linkButton": emailer.lang.InviteEmail.get("linkButton"), "linkButton": emailer.lang.InviteEmail.get("linkButton"),
"message": "", "invite_link": inviteLink,
"date": d, "message": message,
"time": t, })
"expiresInMinutes": expiresIn,
}
if noSub {
template["inviteExpiry"] = emailer.lang.InviteEmail.get("inviteExpiry")
empty := []string{"inviteURL"}
for _, v := range empty {
template[v] = "{" + v + "}"
}
} else {
template["inviteExpiry"] = emailer.lang.InviteEmail.template("inviteExpiry", tmpl{"date": d, "time": t, "expiresInMinutes": expiresIn})
template["inviteURL"] = inviteLink
template["message"] = message
}
var err error
if app.storage.customEmails.InviteEmail.Enabled {
content := app.storage.customEmails.InviteEmail.Content
for _, v := range app.storage.customEmails.InviteEmail.Variables {
replaceWith, ok := template[v[1:len(v)-1]]
if ok {
content = strings.ReplaceAll(content, v, replaceWith.(string))
}
}
email, err = emailer.constructTemplate(email.subject, content, app)
} else {
email.html, email.text, err = emailer.construct(app, "invite_emails", "email_", template)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
return email, nil return email, nil
} }
func (emailer *Emailer) constructExpiry(code string, invite Invite, app *appContext, noSub bool) (*Email, error) { func (emailer *Emailer) constructExpiry(code string, invite Invite, app *appContext) (*Email, error) {
email := &Email{ email := &Email{
subject: emailer.lang.InviteExpiry.get("title"), subject: emailer.lang.InviteExpiry.get("title"),
} }
expiry := app.formatDatetime(invite.ValidTill) expiry := app.formatDatetime(invite.ValidTill)
template := map[string]interface{}{
"inviteExpired": emailer.lang.InviteExpiry.get("inviteExpired"),
"notificationNotice": emailer.lang.InviteExpiry.get("notificationNotice"),
"code": "\"" + code + "\"",
"time": expiry,
}
if noSub {
template["expiredAt"] = emailer.lang.InviteExpiry.get("expiredAt")
} else {
template["expiredAt"] = emailer.lang.InviteExpiry.template("expiredAt", tmpl{"code": template["code"].(string), "time": template["time"].(string)})
}
var err error var err error
if app.storage.customEmails.InviteExpiry.Enabled { email.html, email.text, err = emailer.construct(app, "notifications", "expiry_", map[string]interface{}{
content := app.storage.customEmails.InviteExpiry.Content "inviteExpired": emailer.lang.InviteExpiry.get("inviteExpired"),
for _, v := range app.storage.customEmails.InviteExpiry.Variables { "expiredAt": emailer.lang.InviteExpiry.template("expiredAt", tmpl{"code": "\"" + code + "\"", "time": expiry}),
replaceWith, ok := template[v[1:len(v)-1]] "notificationNotice": emailer.lang.InviteExpiry.get("notificationNotice"),
if ok { })
content = strings.ReplaceAll(content, v, replaceWith.(string))
}
}
email, err = emailer.constructTemplate(email.subject, content, app)
} else {
email.html, email.text, err = emailer.construct(app, "notifications", "expiry_", template)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
return email, nil return email, nil
} }
func (emailer *Emailer) constructCreated(code, username, address string, invite Invite, app *appContext, noSub bool) (*Email, error) { func (emailer *Emailer) constructCreated(code, username, address string, invite Invite, app *appContext) (*Email, error) {
email := &Email{ email := &Email{
subject: emailer.lang.UserCreated.get("title"), subject: emailer.lang.UserCreated.get("title"),
} }
template := map[string]interface{}{ created := app.formatDatetime(invite.Created)
"nameString": emailer.lang.Strings.get("name"), var tplAddress string
"addressString": emailer.lang.Strings.get("emailAddress"), if app.config.Section("email").Key("no_username").MustBool(false) {
"timeString": emailer.lang.UserCreated.get("time"), tplAddress = "n/a"
"notificationNotice": "",
"code": "\"" + code + "\"",
}
if noSub {
template["aUserWasCreated"] = emailer.lang.UserCreated.get("aUserWasCreated")
empty := []string{"name", "address", "time"}
for _, v := range empty {
template[v] = "{" + v + "}"
}
} else { } else {
created := app.formatDatetime(invite.Created) tplAddress = address
var tplAddress string
if app.config.Section("email").Key("no_username").MustBool(false) {
tplAddress = "n/a"
} else {
tplAddress = address
}
template["aUserWasCreated"] = emailer.lang.UserCreated.template("aUserWasCreated", tmpl{"code": template["code"].(string)})
template["name"] = username
template["address"] = tplAddress
template["time"] = created
template["notificationNotice"] = emailer.lang.UserCreated.get("notificationNotice")
} }
var err error var err error
if app.storage.customEmails.UserCreated.Enabled { email.html, email.text, err = emailer.construct(app, "notifications", "created_", map[string]interface{}{
content := app.storage.customEmails.UserCreated.Content "aUserWasCreated": emailer.lang.UserCreated.template("aUserWasCreated", tmpl{"code": "\"" + code + "\""}),
for _, v := range app.storage.customEmails.UserCreated.Variables { "name": emailer.lang.Strings.get("name"),
replaceWith, ok := template[v[1:len(v)-1]] "address": emailer.lang.Strings.get("emailAddress"),
if ok { "time": emailer.lang.UserCreated.get("time"),
content = strings.ReplaceAll(content, v, replaceWith.(string)) "nameVal": username,
} "addressVal": tplAddress,
} "timeVal": created,
email, err = emailer.constructTemplate(email.subject, content, app) "notificationNotice": emailer.lang.UserCreated.get("notificationNotice"),
} else { })
email.html, email.text, err = emailer.construct(app, "notifications", "created_", template)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
return email, nil return email, nil
} }
func (emailer *Emailer) constructReset(pwr PasswordReset, app *appContext, noSub bool) (*Email, error) { func (emailer *Emailer) constructReset(pwr PasswordReset, app *appContext) (*Email, error) {
email := &Email{ email := &Email{
subject: app.config.Section("password_resets").Key("subject").MustString(emailer.lang.PasswordReset.get("title")), subject: app.config.Section("password_resets").Key("subject").MustString(emailer.lang.PasswordReset.get("title")),
} }
d, t, expiresIn := emailer.formatExpiry(pwr.Expiry, true, app.datePattern, app.timePattern) d, t, expiresIn := emailer.formatExpiry(pwr.Expiry, true, app.datePattern, app.timePattern)
message := app.config.Section("email").Key("message").String() message := app.config.Section("email").Key("message").String()
template := map[string]interface{}{ var err error
email.html, email.text, err = emailer.construct(app, "password_resets", "email_", map[string]interface{}{
"helloUser": emailer.lang.Strings.template("helloUser", tmpl{"username": pwr.Username}),
"someoneHasRequestedReset": emailer.lang.PasswordReset.get("someoneHasRequestedReset"), "someoneHasRequestedReset": emailer.lang.PasswordReset.get("someoneHasRequestedReset"),
"ifItWasYou": emailer.lang.PasswordReset.get("ifItWasYou"), "ifItWasYou": emailer.lang.PasswordReset.get("ifItWasYou"),
"codeExpiry": emailer.lang.PasswordReset.template("codeExpiry", tmpl{"date": d, "time": t, "expiresInMinutes": expiresIn}),
"ifItWasNotYou": emailer.lang.Strings.get("ifItWasNotYou"), "ifItWasNotYou": emailer.lang.Strings.get("ifItWasNotYou"),
"pinString": emailer.lang.PasswordReset.get("pin"), "pin": emailer.lang.PasswordReset.get("pin"),
"message": "", "pinVal": pwr.Pin,
"username": pwr.Username, "message": message,
"date": d, })
"time": t,
"expiresInMinutes": expiresIn,
}
if noSub {
template["helloUser"] = emailer.lang.Strings.get("helloUser")
template["codeExpiry"] = emailer.lang.PasswordReset.get("codeExpiry")
empty := []string{"pin"}
for _, v := range empty {
template[v] = "{" + v + "}"
}
} else {
template["helloUser"] = emailer.lang.Strings.template("helloUser", tmpl{"username": pwr.Username})
template["codeExpiry"] = emailer.lang.PasswordReset.template("codeExpiry", tmpl{"date": d, "time": t, "expiresInMinutes": expiresIn})
template["pin"] = pwr.Pin
template["message"] = message
}
var err error
if app.storage.customEmails.PasswordReset.Enabled {
content := app.storage.customEmails.PasswordReset.Content
for _, v := range app.storage.customEmails.PasswordReset.Variables {
replaceWith, ok := template[v[1:len(v)-1]]
if ok {
content = strings.ReplaceAll(content, v, replaceWith.(string))
}
}
email, err = emailer.constructTemplate(email.subject, content, app)
} else {
email.html, email.text, err = emailer.construct(app, "password_resets", "email_", template)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
return email, nil return email, nil
} }
func (emailer *Emailer) constructDeleted(reason string, app *appContext, noSub bool) (*Email, error) { func (emailer *Emailer) constructDeleted(reason string, app *appContext) (*Email, error) {
email := &Email{ email := &Email{
subject: app.config.Section("deletion").Key("subject").MustString(emailer.lang.UserDeleted.get("title")), subject: app.config.Section("deletion").Key("subject").MustString(emailer.lang.UserDeleted.get("title")),
} }
template := map[string]interface{}{
"yourAccountWasDeleted": emailer.lang.UserDeleted.get("yourAccountWasDeleted"),
"reasonString": emailer.lang.UserDeleted.get("reason"),
"message": "",
}
if noSub {
empty := []string{"reason"}
for _, v := range empty {
template[v] = "{" + v + "}"
}
} else {
template["reason"] = reason
template["message"] = app.config.Section("email").Key("message").String()
}
var err error var err error
if app.storage.customEmails.UserDeleted.Enabled { email.html, email.text, err = emailer.construct(app, "deletion", "email_", map[string]interface{}{
content := app.storage.customEmails.UserDeleted.Content "yourAccountWasDeleted": emailer.lang.UserDeleted.get("yourAccountWasDeleted"),
for _, v := range app.storage.customEmails.UserDeleted.Variables { "reason": emailer.lang.UserDeleted.get("reason"),
replaceWith, ok := template[v[1:len(v)-1]] "reasonVal": reason,
if ok { })
content = strings.ReplaceAll(content, v, replaceWith.(string))
}
}
email, err = emailer.constructTemplate(email.subject, content, app)
} else {
email.html, email.text, err = emailer.construct(app, "deletion", "email_", template)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
return email, nil return email, nil
} }
func (emailer *Emailer) constructWelcome(username string, app *appContext, noSub bool) (*Email, error) { func (emailer *Emailer) constructWelcome(username string, app *appContext) (*Email, error) {
email := &Email{ email := &Email{
subject: app.config.Section("welcome_email").Key("subject").MustString(emailer.lang.WelcomeEmail.get("title")), subject: app.config.Section("welcome_email").Key("subject").MustString(emailer.lang.WelcomeEmail.get("title")),
} }
template := map[string]interface{}{
"welcome": emailer.lang.WelcomeEmail.get("welcome"),
"youCanLoginWith": emailer.lang.WelcomeEmail.get("youCanLoginWith"),
"jellyfinURLString": emailer.lang.WelcomeEmail.get("jellyfinURL"),
"usernameString": emailer.lang.Strings.get("username"),
"message": "",
}
if noSub {
empty := []string{"jellyfinURL", "username"}
for _, v := range empty {
template[v] = "{" + v + "}"
}
} else {
template["jellyfinURL"] = app.config.Section("jellyfin").Key("public_server").String()
template["username"] = username
template["message"] = app.config.Section("email").Key("message").String()
}
var err error var err error
if app.storage.customEmails.WelcomeEmail.Enabled { email.html, email.text, err = emailer.construct(app, "welcome_email", "email_", map[string]interface{}{
content := app.storage.customEmails.WelcomeEmail.Content "welcome": emailer.lang.WelcomeEmail.get("welcome"),
for _, v := range app.storage.customEmails.WelcomeEmail.Variables { "youCanLoginWith": emailer.lang.WelcomeEmail.get("youCanLoginWith"),
replaceWith, ok := template[v[1:len(v)-1]] "jellyfinURL": emailer.lang.WelcomeEmail.get("jellyfinURL"),
if ok { "jellyfinURLVal": app.config.Section("jellyfin").Key("public_server").String(),
content = strings.ReplaceAll(content, v, replaceWith.(string)) "username": emailer.lang.Strings.get("username"),
} "usernameVal": username,
} "message": app.config.Section("email").Key("message").String(),
email, err = emailer.constructTemplate(email.subject, content, app) })
} else {
email.html, email.text, err = emailer.construct(app, "welcome_email", "email_", template)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -535,6 +383,5 @@ func (emailer *Emailer) constructWelcome(username string, app *appContext, noSub
// calls the send method in the underlying emailClient. // calls the send method in the underlying emailClient.
func (emailer *Emailer) send(email *Email, address ...string) error { func (emailer *Emailer) send(email *Email, address ...string) error {
fmt.Printf("%+v\n", email)
return emailer.sender.send(emailer.fromName, emailer.fromAddr, email, address...) return emailer.sender.send(emailer.fromName, emailer.fromAddr, email, address...)
} }

View File

@ -120,18 +120,14 @@ func (ls *setupLangs) getOptions() [][2]string {
type langSection map[string]string type langSection map[string]string
type tmpl map[string]string type tmpl map[string]string
func templateString(text string, vals tmpl) string { func (el langSection) template(field string, vals tmpl) string {
text := el.get(field)
for key, val := range vals { for key, val := range vals {
text = strings.ReplaceAll(text, "{"+key+"}", val) text = strings.ReplaceAll(text, "{"+key+"}", val)
} }
return text return text
} }
func (el langSection) template(field string, vals tmpl) string {
text := el.get(field)
return templateString(text, vals)
}
func (el langSection) format(field string, vals ...string) string { func (el langSection) format(field string, vals ...string) string {
text := el.get(field) text := el.get(field)
for _, val := range vals { for _, val := range vals {

View File

@ -8,21 +8,21 @@
}, },
"userCreated": { "userCreated": {
"title": "Mitteilung: Benutzer erstellt", "title": "Mitteilung: Benutzer erstellt",
"aUserWasCreated": "Ein Benutzer wurde unter Verwendung des Codes {code} erstellt.", "aUserWasCreated": "Ein Benutzer wurde unter Verwendung des Codes {n] erstellt.",
"time": "Zeit", "time": "Zeit",
"notificationNotice": "Hinweis: Benachrichtigungs-E-Mails können auf dem Administrator-Dashboard umgeschalten werden." "notificationNotice": "Hinweis: Benachrichtigungs-E-Mails können auf dem Administrator-Dashboard umgeschalten werden."
}, },
"inviteExpiry": { "inviteExpiry": {
"title": "Mitteilung: Invite abgelaufen", "title": "Mitteilung: Invite abgelaufen",
"inviteExpired": "Invite abgelaufen.", "inviteExpired": "Invite abgelaufen.",
"expiredAt": "Code {code} lief um {time} ab.", "expiredAt": "Code {code} lief um {code} ab.",
"notificationNotice": "Hinweis: Benachrichtigungs-E-Mails können auf dem Administrator-Dashboard umgeschalten werden." "notificationNotice": "Hinweis: Benachrichtigungs-E-Mails können auf dem Administrator-Dashboard umgeschalten werden."
}, },
"passwordReset": { "passwordReset": {
"title": "Passwortzurücksetzung angefordert - Jellyfin", "title": "Passwortzurücksetzung angefordert - Jellyfin",
"someoneHasRequestedReset": "Jemand hat vor kurzem eine Passwortzurücksetzung auf Jellyfin angefordert.", "someoneHasRequestedReset": "Jemand hat vor kurzem eine Passwortzurücksetzung auf Jellyfin angefordert.",
"ifItWasYou": "Wenn du das warst, gib die PIN unten in die Eingabeaufforderung ein.", "ifItWasYou": "Wenn du das warst, gib die PIN unten in die Eingabeaufforderung ein.",
"codeExpiry": "Der Code wird am {date}, um {time} UTC ablaufen, was in {expiresInMinutes} ist.", "codeExpiry": "Der Code wird am {time}, um [n} UTC ablaufen, was in {date} ist.",
"pin": "PIN" "pin": "PIN"
}, },
"userDeleted": { "userDeleted": {
@ -35,7 +35,7 @@
"hello": "Hallo", "hello": "Hallo",
"youHaveBeenInvited": "Du wurdest zu Jellyfin eingeladen.", "youHaveBeenInvited": "Du wurdest zu Jellyfin eingeladen.",
"toJoin": "Um beizutreten, folge dem untenstehenden Link.", "toJoin": "Um beizutreten, folge dem untenstehenden Link.",
"inviteExpiry": "Dieser Invite wird am {date}; um {time} ablaufen, was in {expiresInMinutes} ist, also handle schnell.", "inviteExpiry": "Dieser Invite wird am {time}; um {expiresInMinutes} ablaufen, was in {date} ist, also handle schnell.",
"linkButton": "Richte dein Konto ein" "linkButton": "Richte dein Konto ein"
}, },
"welcomeEmail": { "welcomeEmail": {

View File

@ -7,21 +7,18 @@
"helloUser": "Hi {username}," "helloUser": "Hi {username},"
}, },
"userCreated": { "userCreated": {
"name": "User creation",
"title": "Notice: User created", "title": "Notice: User created",
"aUserWasCreated": "A user was created using code {code}.", "aUserWasCreated": "A user was created using code {code}.",
"time": "Time", "time": "Time",
"notificationNotice": "Note: Notification emails can be toggled on the admin dashboard." "notificationNotice": "Note: Notification emails can be toggled on the admin dashboard."
}, },
"inviteExpiry": { "inviteExpiry": {
"name": "Invite expiry",
"title": "Notice: Invite expired", "title": "Notice: Invite expired",
"inviteExpired": "Invite expired.", "inviteExpired": "Invite expired.",
"expiredAt": "Code {code} expired at {time}.", "expiredAt": "Code {code} expired at {time}.",
"notificationNotice": "Note: Notification emails can be toggled on the admin dashboard." "notificationNotice": "Note: Notification emails can be toggled on the admin dashboard."
}, },
"passwordReset": { "passwordReset": {
"name": "Password reset",
"title": "Password reset requested - Jellyfin", "title": "Password reset requested - Jellyfin",
"someoneHasRequestedReset": "Someone has recently requested a password reset on Jellyfin.", "someoneHasRequestedReset": "Someone has recently requested a password reset on Jellyfin.",
"ifItWasYou": "If this was you, enter the pin below into the prompt.", "ifItWasYou": "If this was you, enter the pin below into the prompt.",
@ -29,13 +26,11 @@
"pin": "PIN" "pin": "PIN"
}, },
"userDeleted": { "userDeleted": {
"name": "User deletion",
"title": "Your account was deleted - Jellyfin", "title": "Your account was deleted - Jellyfin",
"yourAccountWasDeleted": "Your Jellyfin account was deleted.", "yourAccountWasDeleted": "Your Jellyfin account was deleted.",
"reason": "Reason" "reason": "Reason"
}, },
"inviteEmail": { "inviteEmail": {
"name": "Invite email",
"title": "Invite - Jellyfin", "title": "Invite - Jellyfin",
"hello": "Hi", "hello": "Hi",
"youHaveBeenInvited": "You've been invited to Jellyfin.", "youHaveBeenInvited": "You've been invited to Jellyfin.",
@ -44,14 +39,12 @@
"linkButton": "Setup your account" "linkButton": "Setup your account"
}, },
"welcomeEmail": { "welcomeEmail": {
"name": "Welcome email",
"title": "Welcome to Jellyfin", "title": "Welcome to Jellyfin",
"welcome": "Welcome to Jellyfin!", "welcome": "Welcome to Jellyfin!",
"youCanLoginWith": "You can login with the details below", "youCanLoginWith": "You can login with the details below",
"jellyfinURL": "URL" "jellyfinURL": "URL"
}, },
"emailConfirmation": { "emailConfirmation": {
"name": "Confirmation email",
"title": "Confirm your email - Jellyfin", "title": "Confirm your email - Jellyfin",
"clickBelow": "Click the link below to confirm your email address and start using Jellyfin.", "clickBelow": "Click the link below to confirm your email address and start using Jellyfin.",
"confirmEmail": "Confirm Email" "confirmEmail": "Confirm Email"

View File

@ -64,7 +64,7 @@
<p>{{ .clickBelow }}</p> <p>{{ .clickBelow }}</p>
<p>{{ .ifItWasNotYou }}</p> <p>{{ .ifItWasNotYou }}</p>
</mj-text> </mj-text>
<mj-button mj-class="blue bold" href="{{ .confirmationURL }}">{{ .confirmEmail }}</mj-button> <mj-button mj-class="blue bold" href="{{ .urlVal }}">{{ .confirmEmail }}</mj-button>
</mj-column> </mj-column>
</mj-section> </mj-section>
<mj-section mj-class="bg2"> <mj-section mj-class="bg2">

View File

@ -3,6 +3,6 @@
{{ .clickBelow }} {{ .clickBelow }}
{{ .ifItWasNotYou }} {{ .ifItWasNotYou }}
{{ .confirmationURL }} {{ .urlVal }}
{{ .message }} {{ .message }}

View File

@ -64,14 +64,14 @@
</mj-text> </mj-text>
<mj-table mj-class="text" container-background-color="#242424"> <mj-table mj-class="text" container-background-color="#242424">
<tr style="text-align: left;"> <tr style="text-align: left;">
<th>{{ .nameString }}</th> <th>{{ .name }}</th>
<th>{{ .addressString }}</th> <th>{{ .address }}</th>
<th>{{ .timeString }}</th> <th>{{ .time }}</th>
</tr> </tr>
<tr style="font-style: italic; text-align: left; color: rgb(153,153,153);"> <tr style="font-style: italic; text-align: left; color: rgb(153,153,153);">
<th>{{ .name }}</th> <th>{{ .nameVal }}</th>
<th>{{ .address }}</th> <th>{{ .addressVal }}</th>
<th>{{ .time }}</th> <th>{{ .timeVal }}</th>
</mj-table> </mj-table>
</mj-column> </mj-column>
</mj-section> </mj-section>

View File

@ -1,7 +1,7 @@
{{ .aUserWasCreated }} {{ .aUserWasCreated }}
{{ .nameString }}: {{ .name }} {{ .name }}: {{ .nameVal }}
{{ .addressString }}: {{ .address }} {{ .address }}: {{ .addressVal }}
{{ .timeString }}: {{ .time }} {{ .time }}: {{ .timeVal }}
{{ .notificationNotice }} {{ .notificationNotice }}

View File

@ -61,7 +61,7 @@
<mj-column> <mj-column>
<mj-text mj-class="text" font-size="16px" font-family="Noto Sans, Helvetica, Arial, sans-serif"> <mj-text mj-class="text" font-size="16px" font-family="Noto Sans, Helvetica, Arial, sans-serif">
<h3>{{ .yourAccountWasDeleted }}</h3> <h3>{{ .yourAccountWasDeleted }}</h3>
<p>{{ .reasonString }}: <i>{{ .reason }}</i></p> <p>{{ .reason }}: <i>{{ .reasonVal }}</i></p>
</mj-text> </mj-text>
</mj-column> </mj-column>
</mj-section> </mj-section>

View File

@ -1,4 +1,4 @@
{{ .yourAccountWasDeleted }} {{ .yourAccountWasDeleted }}
{{ .reasonString }}: {{ .reason }} {{ .reason }}: {{ .reasonVal }}
{{ .message }} {{ .message }}

View File

@ -66,7 +66,7 @@
<p>{{ .codeExpiry }}</p> <p>{{ .codeExpiry }}</p>
<p>{{ .ifItWasNotYou }}</p> <p>{{ .ifItWasNotYou }}</p>
</mj-text> </mj-text>
<mj-button mj-class="blue bold"><mj-raw>{{ .pin }}</mj-raw></mj-button> <mj-button mj-class="blue bold"><mj-raw>{{ .pinVal }}</mj-raw></mj-button>
</mj-column> </mj-column>
</mj-section> </mj-section>
<mj-section mj-class="bg2"> <mj-section mj-class="bg2">

View File

@ -5,6 +5,6 @@
{{ .codeExpiry }} {{ .codeExpiry }}
{{ .ifItWasNotYou }} {{ .ifItWasNotYou }}
{{ .pinString }}: {{ .pin }} {{ .pin }}: {{ .pinVal }}
{{ .message }} {{ .message }}

View File

@ -65,7 +65,7 @@
<p>{{ .toJoin }}</p> <p>{{ .toJoin }}</p>
<p>{{ .inviteExpiry }}</p> <p>{{ .inviteExpiry }}</p>
</mj-text> </mj-text>
<mj-button mj-class="blue bold" href="{{ .inviteURL }}">{{ .linkButton }}</mj-button> <mj-button mj-class="blue bold" href="{{ .invite_link }}">{{ .linkButton }}</mj-button>
</mj-column> </mj-column>
</mj-section> </mj-section>
<mj-section mj-class="bg2"> <mj-section mj-class="bg2">

View File

@ -3,6 +3,6 @@
{{ .toJoin }} {{ .toJoin }}
{{ .inviteExpiry }} {{ .inviteExpiry }}
{{ .inviteURL }} {{ .invite_link }}
{{ .message }} {{ .message }}

View File

@ -62,8 +62,8 @@
<mj-text mj-class="text" font-size="16px" font-family="Noto Sans, Helvetica, Arial, sans-serif"> <mj-text mj-class="text" font-size="16px" font-family="Noto Sans, Helvetica, Arial, sans-serif">
<h3>{{ .welcome }}</h3> <h3>{{ .welcome }}</h3>
<p>{{ .youCanLoginWith }}:</p> <p>{{ .youCanLoginWith }}:</p>
{{ .jellyfinURLString }}: <a href="{{ .jellyfinURLVal }}">{{ .jellyfinURL }}</a> {{ .jellyfinURL }}: <a href="{{ .jellyfinURLVal }}">{{ .jellyfinURLVal }}</a>
<p>{{ .usernameString }}: <i>{{ .username }}</i></p> <p>{{ .username }}: <i>{{ .usernameVal }}</i></p>
</mj-text> </mj-text>
</mj-column> </mj-column>
</mj-section> </mj-section>

View File

@ -2,8 +2,8 @@
{{ .youCanLoginWith }}: {{ .youCanLoginWith }}:
{{ .jellyfinURLString }}: {{ .jellyfinURL }} {{ .jellyfinURL }}: {{ .jellyfinURLVal }}
{{ .usernameString }}: {{ .username }} {{ .username }}: {{ .usernameVal }}
{{ .message }} {{ .message }}

View File

@ -174,9 +174,3 @@ type settings struct {
} }
type langDTO map[string]string type langDTO map[string]string
type emailListDTO map[string]string
type emailSetDTO struct {
Content string `json:"content"`
}

View File

@ -81,7 +81,7 @@ func pwrMonitor(app *appContext, watcher *fsnotify.Watcher) {
return return
} }
address = addr.(string) address = addr.(string)
msg, err := app.email.constructReset(pwr, app, false) msg, err := app.email.constructReset(pwr, app)
if err != nil { if err != nil {
app.err.Printf("Failed to construct password reset email for %s", pwr.Username) app.err.Printf("Failed to construct password reset email for %s", pwr.Username)
app.debug.Printf("%s: Error: %s", pwr.Username, err) app.debug.Printf("%s: Error: %s", pwr.Username, err)

View File

@ -139,10 +139,6 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
// api.POST(p + "/setDefaults", app.SetDefaults) // api.POST(p + "/setDefaults", app.SetDefaults)
api.POST(p+"/users/settings", app.ApplySettings) api.POST(p+"/users/settings", app.ApplySettings)
api.POST(p+"/users/announce", app.Announce) api.POST(p+"/users/announce", app.Announce)
api.GET(p+"/config/emails", app.GetEmails)
api.GET(p+"/config/emails/:id", app.GetEmail)
api.POST(p+"/config/emails/:id", app.SetEmail)
api.POST(p+"/config/emails/:id/:state", app.SetEmailState)
api.GET(p+"/config", app.GetConfig) api.GET(p+"/config", app.GetConfig)
api.POST(p+"/config", app.ModifyConfig) api.POST(p+"/config", app.ModifyConfig)
api.POST(p+"/restart", app.restart) api.POST(p+"/restart", app.restart)

View File

@ -14,32 +14,15 @@ import (
) )
type Storage struct { type Storage struct {
timePattern string timePattern string
invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path, customEmails_path string invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path string
invites Invites invites Invites
profiles map[string]Profile profiles map[string]Profile
defaultProfile string defaultProfile string
emails, displayprefs, ombi_template map[string]interface{} emails, displayprefs, ombi_template map[string]interface{}
customEmails customEmails policy mediabrowser.Policy
policy mediabrowser.Policy configuration mediabrowser.Configuration
configuration mediabrowser.Configuration lang Lang
lang Lang
}
type customEmails struct {
UserCreated customEmail `json:"userCreated"`
InviteExpiry customEmail `json:"inviteExpiry"`
PasswordReset customEmail `json:"passwordReset"`
UserDeleted customEmail `json:"userDeleted"`
InviteEmail customEmail `json:"inviteEmail"`
WelcomeEmail customEmail `json:"welcomeEmail"`
EmailConfirmation customEmail `json:"emailConfirmation"`
}
type customEmail struct {
Enabled bool `json:"enabled,omitempty"`
Content string `json:"content"`
Variables []string `json:"variables,omitempty"`
} }
// timePattern: %Y-%m-%dT%H:%M:%S.%f // timePattern: %Y-%m-%dT%H:%M:%S.%f
@ -411,14 +394,6 @@ func (st *Storage) storeEmails() error {
return storeJSON(st.emails_path, st.emails) return storeJSON(st.emails_path, st.emails)
} }
func (st *Storage) loadCustomEmails() error {
return loadJSON(st.customEmails_path, &st.customEmails)
}
func (st *Storage) storeCustomEmails() error {
return storeJSON(st.customEmails_path, st.customEmails)
}
func (st *Storage) loadPolicy() error { func (st *Storage) loadPolicy() error {
return loadJSON(st.policy_path, &st.policy) return loadJSON(st.policy_path, &st.policy)
} }

View File

@ -1,53 +0,0 @@
package main
import (
"strings"
stripmd "github.com/writeas/go-strip-markdown"
)
func stripMarkdown(md string) string {
// Search for markdown-formatted urls, and replace them with just the url, then use a library to strip any traces of markdown. You'll need some eyebleach after this.
foundOpenSquare := false
openSquare := -1
openBracket := -1
closeBracket := -1
openSquares := []int{}
closeBrackets := []int{}
links := []string{}
foundOpen := false
for i, c := range md {
if !foundOpenSquare && !foundOpen && c != '[' && c != ']' {
continue
}
if c == '[' && md[i-1] != '!' {
foundOpenSquare = true
openSquare = i
} else if c == ']' {
if md[i+1] == '(' {
foundOpenSquare = false
foundOpen = true
openBracket = i + 1
continue
}
} else if c == ')' {
closeBracket = i
openSquares = append(openSquares, openSquare)
closeBrackets = append(closeBrackets, closeBracket)
links = append(links, md[openBracket+1:closeBracket])
openBracket = -1
closeBracket = -1
openSquare = -1
foundOpenSquare = false
foundOpen = false
}
}
fullLinks := make([]string, len(openSquares))
for i := range openSquares {
fullLinks[i] = md[openSquares[i] : closeBrackets[i]+1]
}
for i, _ := range openSquares {
md = strings.Replace(md, fullLinks[i], links[i], 1)
}
return strings.TrimPrefix(strings.TrimSuffix(stripmd.Strip(md), "</p>"), "<p>")
}