mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-29 12:30:11 +00:00
Compare commits
5 Commits
5c87d109a3
...
6ffdd4dad7
Author | SHA1 | Date | |
---|---|---|---|
6ffdd4dad7 | |||
98d59ba4e0 | |||
938523c18b | |||
cc4e12c405 | |||
eb406ef951 |
253
api.go
253
api.go
@ -126,7 +126,7 @@ func (app *appContext) checkInvites() {
|
||||
wait.Add(1)
|
||||
go func(addr string) {
|
||||
defer wait.Done()
|
||||
msg, err := app.email.constructExpiry(code, data, app)
|
||||
msg, err := app.email.constructExpiry(code, data, app, false)
|
||||
if err != nil {
|
||||
app.err.Printf("%s: Failed to construct expiry notification: %s", code, err)
|
||||
} 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 {
|
||||
if settings["notify-expiry"] {
|
||||
go func() {
|
||||
msg, err := app.email.constructExpiry(code, inv, app)
|
||||
msg, err := app.email.constructExpiry(code, inv, app, false)
|
||||
if err != nil {
|
||||
app.err.Printf("%s: Failed to construct expiry notification: %s", code, err)
|
||||
} 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 != "" {
|
||||
app.debug.Printf("%s: Sending welcome email to %s", req.Username, req.Email)
|
||||
msg, err := app.email.constructWelcome(req.Username, app)
|
||||
msg, err := app.email.constructWelcome(req.Username, app, false)
|
||||
if err != nil {
|
||||
app.err.Printf("%s: Failed to construct welcome email: %s", req.Username, err)
|
||||
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) {
|
||||
app.debug.Printf("%s: Email confirmation required", req.Code)
|
||||
respond(401, "confirmEmail", gc)
|
||||
msg, err := app.email.constructConfirmation(req.Code, req.Username, key, app)
|
||||
msg, err := app.email.constructConfirmation(req.Code, req.Username, key, app, false)
|
||||
if err != nil {
|
||||
app.err.Printf("%s: Failed to construct confirmation email: %s", req.Code, err)
|
||||
} 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 {
|
||||
if settings["notify-creation"] {
|
||||
go func() {
|
||||
msg, err := app.email.constructCreated(req.Code, req.Username, req.Email, invite, app)
|
||||
msg, err := app.email.constructCreated(req.Code, req.Username, req.Email, invite, app, false)
|
||||
if err != nil {
|
||||
app.err.Printf("%s: Failed to construct user creation notification: %s", req.Code, err)
|
||||
} 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 != "" {
|
||||
app.debug.Printf("%s: Sending welcome email to %s", req.Username, req.Email)
|
||||
msg, err := app.email.constructWelcome(req.Username, app)
|
||||
msg, err := app.email.constructWelcome(req.Username, app, false)
|
||||
if err != nil {
|
||||
app.err.Printf("%s: Failed to construct welcome email: %s", req.Username, err)
|
||||
} 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))
|
||||
}
|
||||
msg, err := app.email.constructAnnouncement(req.Subject, req.Message, app)
|
||||
msg, err := app.email.constructTemplate(req.Subject, req.Message, app)
|
||||
if err != nil {
|
||||
app.err.Printf("Failed to construct announcement emails: %s", err)
|
||||
respondBool(500, false, gc)
|
||||
@ -576,7 +576,7 @@ func (app *appContext) DeleteUsers(gc *gin.Context) {
|
||||
}
|
||||
if len(addresses) != 0 {
|
||||
go func(reason string, addresses []string) {
|
||||
msg, err := app.email.constructDeleted(reason, app)
|
||||
msg, err := app.email.constructDeleted(reason, app, false)
|
||||
if err != nil {
|
||||
app.err.Printf("Failed to construct account deletion emails: %s", err)
|
||||
} 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) {
|
||||
app.debug.Printf("%s: Sending invite email", inviteCode)
|
||||
invite.Email = req.Email
|
||||
msg, err := app.email.constructInvite(inviteCode, invite, app)
|
||||
msg, err := app.email.constructInvite(inviteCode, invite, app, false)
|
||||
if err != nil {
|
||||
invite.Email = fmt.Sprintf("Failed to send to %s", req.Email)
|
||||
app.err.Printf("%s: Failed to construct invite email: %s", inviteCode, err)
|
||||
@ -1269,6 +1269,239 @@ 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.
|
||||
// @Produce json
|
||||
// @Success 200 {object} boolResponse
|
||||
@ -1291,7 +1524,7 @@ func (app *appContext) Logout(gc *gin.Context) {
|
||||
// @Produce json
|
||||
// @Success 200 {object} langDTO
|
||||
// @Failure 500 {object} stringResponse
|
||||
// @Router /lang [get]
|
||||
// @Router /lang/{page} [get]
|
||||
// @tags Other
|
||||
func (app *appContext) GetLanguages(gc *gin.Context) {
|
||||
page := gc.Param("page")
|
||||
|
12
config.go
12
config.go
@ -32,11 +32,12 @@ 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()))
|
||||
|
||||
for _, key := range app.config.Section("files").Keys() {
|
||||
if key.Name() != "html_templates" {
|
||||
if name := key.Name(); name != "html_templates" && name != "lang_files" {
|
||||
fmt.Println(name)
|
||||
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"} {
|
||||
for _, key := range []string{"user_configuration", "user_displayprefs", "user_profiles", "ombi_template", "invites", "emails", "user_template", "custom_emails"} {
|
||||
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(""), "/")
|
||||
@ -63,8 +64,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_text").SetValue(app.config.Section("welcome_email").Key("email_text").MustString("jfa-go:" + "welcome.txt"))
|
||||
|
||||
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("announcement_email").Key("email_text").SetValue(app.config.Section("announcement_email").Key("email_text").MustString("jfa-go:" + "announcement.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("template_email").Key("email_text").SetValue(app.config.Section("template_email").Key("email_text").MustString("jfa-go:" + "template.txt"))
|
||||
|
||||
app.config.Section("jellyfin").Key("version").SetValue(VERSION)
|
||||
app.config.Section("jellyfin").Key("device").SetValue("jfa-go")
|
||||
@ -76,6 +77,9 @@ func (app *appContext) loadConfig() error {
|
||||
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("")
|
||||
|
||||
oldFormLang := app.config.Section("ui").Key("language").MustString("")
|
||||
|
@ -853,6 +853,14 @@
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"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."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
285
email.go
285
email.go
@ -18,7 +18,6 @@ import (
|
||||
jEmail "github.com/jordan-wright/email"
|
||||
"github.com/knz/strtime"
|
||||
"github.com/mailgun/mailgun-go/v4"
|
||||
stripmd "github.com/writeas/go-strip-markdown"
|
||||
)
|
||||
|
||||
// implements email sending, right now via smtp or mailgun.
|
||||
@ -212,36 +211,58 @@ func (emailer *Emailer) construct(app *appContext, section, keyFragment string,
|
||||
return
|
||||
}
|
||||
|
||||
func (emailer *Emailer) constructConfirmation(code, username, key string, app *appContext) (*Email, error) {
|
||||
func (emailer *Emailer) constructConfirmation(code, username, key string, app *appContext, noSub bool) (*Email, error) {
|
||||
email := &Email{
|
||||
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
|
||||
email.html, email.text, err = emailer.construct(app, "email_confirmation", "email_", map[string]interface{}{
|
||||
"helloUser": emailer.lang.Strings.template("helloUser", tmpl{"username": username}),
|
||||
template := map[string]interface{}{
|
||||
"clickBelow": emailer.lang.EmailConfirmation.get("clickBelow"),
|
||||
"ifItWasNotYou": emailer.lang.Strings.get("ifItWasNotYou"),
|
||||
"urlVal": inviteLink,
|
||||
"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 {
|
||||
return nil, err
|
||||
}
|
||||
return email, nil
|
||||
}
|
||||
|
||||
func (emailer *Emailer) constructAnnouncement(subject, md string, app *appContext) (*Email, error) {
|
||||
func (emailer *Emailer) constructTemplate(subject, md string, app *appContext) (*Email, error) {
|
||||
email := &Email{subject: subject}
|
||||
renderer := html.NewRenderer(html.RendererOptions{Flags: html.Smartypants})
|
||||
html := markdown.ToHTML([]byte(md), nil, renderer)
|
||||
text := strings.TrimPrefix(strings.TrimSuffix(stripmd.Strip(md), "</p>"), "<p>")
|
||||
text := stripMarkdown(md)
|
||||
message := app.config.Section("email").Key("message").String()
|
||||
var err error
|
||||
email.html, email.text, err = emailer.construct(app, "announcement_email", "email_", map[string]interface{}{
|
||||
email.html, email.text, err = emailer.construct(app, "template_email", "email_", map[string]interface{}{
|
||||
"text": template.HTML(html),
|
||||
"plaintext": text,
|
||||
"message": message,
|
||||
@ -252,7 +273,7 @@ func (emailer *Emailer) constructAnnouncement(subject, md string, app *appContex
|
||||
return email, nil
|
||||
}
|
||||
|
||||
func (emailer *Emailer) constructInvite(code string, invite Invite, app *appContext) (*Email, error) {
|
||||
func (emailer *Emailer) constructInvite(code string, invite Invite, app *appContext, noSub bool) (*Email, error) {
|
||||
email := &Email{
|
||||
subject: app.config.Section("email_confirmation").Key("subject").MustString(emailer.lang.InviteEmail.get("title")),
|
||||
}
|
||||
@ -261,120 +282,251 @@ func (emailer *Emailer) constructInvite(code string, invite Invite, app *appCont
|
||||
message := app.config.Section("email").Key("message").String()
|
||||
inviteLink := app.config.Section("invite_emails").Key("url_base").String()
|
||||
inviteLink = fmt.Sprintf("%s/%s", inviteLink, code)
|
||||
var err error
|
||||
email.html, email.text, err = emailer.construct(app, "invite_emails", "email_", map[string]interface{}{
|
||||
template := map[string]interface{}{
|
||||
"hello": emailer.lang.InviteEmail.get("hello"),
|
||||
"youHaveBeenInvited": emailer.lang.InviteEmail.get("youHaveBeenInvited"),
|
||||
"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"),
|
||||
"invite_link": inviteLink,
|
||||
"message": message,
|
||||
})
|
||||
"message": "",
|
||||
"date": d,
|
||||
"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 {
|
||||
return nil, err
|
||||
}
|
||||
return email, nil
|
||||
}
|
||||
|
||||
func (emailer *Emailer) constructExpiry(code string, invite Invite, app *appContext) (*Email, error) {
|
||||
func (emailer *Emailer) constructExpiry(code string, invite Invite, app *appContext, noSub bool) (*Email, error) {
|
||||
email := &Email{
|
||||
subject: emailer.lang.InviteExpiry.get("title"),
|
||||
}
|
||||
expiry := app.formatDatetime(invite.ValidTill)
|
||||
var err error
|
||||
email.html, email.text, err = emailer.construct(app, "notifications", "expiry_", map[string]interface{}{
|
||||
template := map[string]interface{}{
|
||||
"inviteExpired": emailer.lang.InviteExpiry.get("inviteExpired"),
|
||||
"expiredAt": emailer.lang.InviteExpiry.template("expiredAt", tmpl{"code": "\"" + code + "\"", "time": expiry}),
|
||||
"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
|
||||
if app.storage.customEmails.InviteExpiry.Enabled {
|
||||
content := app.storage.customEmails.InviteExpiry.Content
|
||||
for _, v := range app.storage.customEmails.InviteExpiry.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, "notifications", "expiry_", template)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return email, nil
|
||||
}
|
||||
|
||||
func (emailer *Emailer) constructCreated(code, username, address string, invite Invite, app *appContext) (*Email, error) {
|
||||
func (emailer *Emailer) constructCreated(code, username, address string, invite Invite, app *appContext, noSub bool) (*Email, error) {
|
||||
email := &Email{
|
||||
subject: emailer.lang.UserCreated.get("title"),
|
||||
}
|
||||
created := app.formatDatetime(invite.Created)
|
||||
var tplAddress string
|
||||
if app.config.Section("email").Key("no_username").MustBool(false) {
|
||||
tplAddress = "n/a"
|
||||
template := map[string]interface{}{
|
||||
"nameString": emailer.lang.Strings.get("name"),
|
||||
"addressString": emailer.lang.Strings.get("emailAddress"),
|
||||
"timeString": emailer.lang.UserCreated.get("time"),
|
||||
"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 {
|
||||
tplAddress = address
|
||||
created := app.formatDatetime(invite.Created)
|
||||
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
|
||||
email.html, email.text, err = emailer.construct(app, "notifications", "created_", map[string]interface{}{
|
||||
"aUserWasCreated": emailer.lang.UserCreated.template("aUserWasCreated", tmpl{"code": "\"" + code + "\""}),
|
||||
"name": emailer.lang.Strings.get("name"),
|
||||
"address": emailer.lang.Strings.get("emailAddress"),
|
||||
"time": emailer.lang.UserCreated.get("time"),
|
||||
"nameVal": username,
|
||||
"addressVal": tplAddress,
|
||||
"timeVal": created,
|
||||
"notificationNotice": emailer.lang.UserCreated.get("notificationNotice"),
|
||||
})
|
||||
if app.storage.customEmails.UserCreated.Enabled {
|
||||
content := app.storage.customEmails.UserCreated.Content
|
||||
for _, v := range app.storage.customEmails.UserCreated.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, "notifications", "created_", template)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return email, nil
|
||||
}
|
||||
|
||||
func (emailer *Emailer) constructReset(pwr PasswordReset, app *appContext) (*Email, error) {
|
||||
func (emailer *Emailer) constructReset(pwr PasswordReset, app *appContext, noSub bool) (*Email, error) {
|
||||
email := &Email{
|
||||
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)
|
||||
message := app.config.Section("email").Key("message").String()
|
||||
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}),
|
||||
template := map[string]interface{}{
|
||||
"someoneHasRequestedReset": emailer.lang.PasswordReset.get("someoneHasRequestedReset"),
|
||||
"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"),
|
||||
"pin": emailer.lang.PasswordReset.get("pin"),
|
||||
"pinVal": pwr.Pin,
|
||||
"message": message,
|
||||
})
|
||||
"pinString": emailer.lang.PasswordReset.get("pin"),
|
||||
"message": "",
|
||||
"username": pwr.Username,
|
||||
"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 {
|
||||
return nil, err
|
||||
}
|
||||
return email, nil
|
||||
}
|
||||
|
||||
func (emailer *Emailer) constructDeleted(reason string, app *appContext) (*Email, error) {
|
||||
func (emailer *Emailer) constructDeleted(reason string, app *appContext, noSub bool) (*Email, error) {
|
||||
email := &Email{
|
||||
subject: app.config.Section("deletion").Key("subject").MustString(emailer.lang.UserDeleted.get("title")),
|
||||
}
|
||||
var err error
|
||||
email.html, email.text, err = emailer.construct(app, "deletion", "email_", map[string]interface{}{
|
||||
template := map[string]interface{}{
|
||||
"yourAccountWasDeleted": emailer.lang.UserDeleted.get("yourAccountWasDeleted"),
|
||||
"reason": emailer.lang.UserDeleted.get("reason"),
|
||||
"reasonVal": reason,
|
||||
})
|
||||
"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
|
||||
if app.storage.customEmails.UserDeleted.Enabled {
|
||||
content := app.storage.customEmails.UserDeleted.Content
|
||||
for _, v := range app.storage.customEmails.UserDeleted.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, "deletion", "email_", template)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return email, nil
|
||||
}
|
||||
|
||||
func (emailer *Emailer) constructWelcome(username string, app *appContext) (*Email, error) {
|
||||
func (emailer *Emailer) constructWelcome(username string, app *appContext, noSub bool) (*Email, error) {
|
||||
email := &Email{
|
||||
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
|
||||
email.html, email.text, err = emailer.construct(app, "welcome_email", "email_", map[string]interface{}{
|
||||
"welcome": emailer.lang.WelcomeEmail.get("welcome"),
|
||||
"youCanLoginWith": emailer.lang.WelcomeEmail.get("youCanLoginWith"),
|
||||
"jellyfinURL": emailer.lang.WelcomeEmail.get("jellyfinURL"),
|
||||
"jellyfinURLVal": app.config.Section("jellyfin").Key("public_server").String(),
|
||||
"username": emailer.lang.Strings.get("username"),
|
||||
"usernameVal": username,
|
||||
"message": app.config.Section("email").Key("message").String(),
|
||||
})
|
||||
if app.storage.customEmails.WelcomeEmail.Enabled {
|
||||
content := app.storage.customEmails.WelcomeEmail.Content
|
||||
for _, v := range app.storage.customEmails.WelcomeEmail.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, "welcome_email", "email_", template)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -383,5 +535,6 @@ func (emailer *Emailer) constructWelcome(username string, app *appContext) (*Ema
|
||||
|
||||
// calls the send method in the underlying emailClient.
|
||||
func (emailer *Emailer) send(email *Email, address ...string) error {
|
||||
fmt.Printf("%+v\n", email)
|
||||
return emailer.sender.send(emailer.fromName, emailer.fromAddr, email, address...)
|
||||
}
|
||||
|
8
lang.go
8
lang.go
@ -120,14 +120,18 @@ func (ls *setupLangs) getOptions() [][2]string {
|
||||
type langSection map[string]string
|
||||
type tmpl map[string]string
|
||||
|
||||
func (el langSection) template(field string, vals tmpl) string {
|
||||
text := el.get(field)
|
||||
func templateString(text string, vals tmpl) string {
|
||||
for key, val := range vals {
|
||||
text = strings.ReplaceAll(text, "{"+key+"}", val)
|
||||
}
|
||||
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 {
|
||||
text := el.get(field)
|
||||
for _, val := range vals {
|
||||
|
@ -8,21 +8,21 @@
|
||||
},
|
||||
"userCreated": {
|
||||
"title": "Mitteilung: Benutzer erstellt",
|
||||
"aUserWasCreated": "Ein Benutzer wurde unter Verwendung des Codes {n] erstellt.",
|
||||
"aUserWasCreated": "Ein Benutzer wurde unter Verwendung des Codes {code} erstellt.",
|
||||
"time": "Zeit",
|
||||
"notificationNotice": "Hinweis: Benachrichtigungs-E-Mails können auf dem Administrator-Dashboard umgeschalten werden."
|
||||
},
|
||||
"inviteExpiry": {
|
||||
"title": "Mitteilung: Invite abgelaufen",
|
||||
"inviteExpired": "Invite abgelaufen.",
|
||||
"expiredAt": "Code {code} lief um {code} ab.",
|
||||
"expiredAt": "Code {code} lief um {time} ab.",
|
||||
"notificationNotice": "Hinweis: Benachrichtigungs-E-Mails können auf dem Administrator-Dashboard umgeschalten werden."
|
||||
},
|
||||
"passwordReset": {
|
||||
"title": "Passwortzurücksetzung angefordert - Jellyfin",
|
||||
"someoneHasRequestedReset": "Jemand hat vor kurzem eine Passwortzurücksetzung auf Jellyfin angefordert.",
|
||||
"ifItWasYou": "Wenn du das warst, gib die PIN unten in die Eingabeaufforderung ein.",
|
||||
"codeExpiry": "Der Code wird am {time}, um [n} UTC ablaufen, was in {date} ist.",
|
||||
"codeExpiry": "Der Code wird am {date}, um {time} UTC ablaufen, was in {expiresInMinutes} ist.",
|
||||
"pin": "PIN"
|
||||
},
|
||||
"userDeleted": {
|
||||
@ -35,7 +35,7 @@
|
||||
"hello": "Hallo",
|
||||
"youHaveBeenInvited": "Du wurdest zu Jellyfin eingeladen.",
|
||||
"toJoin": "Um beizutreten, folge dem untenstehenden Link.",
|
||||
"inviteExpiry": "Dieser Invite wird am {time}; um {expiresInMinutes} ablaufen, was in {date} ist, also handle schnell.",
|
||||
"inviteExpiry": "Dieser Invite wird am {date}; um {time} ablaufen, was in {expiresInMinutes} ist, also handle schnell.",
|
||||
"linkButton": "Richte dein Konto ein"
|
||||
},
|
||||
"welcomeEmail": {
|
||||
|
@ -7,18 +7,21 @@
|
||||
"helloUser": "Hi {username},"
|
||||
},
|
||||
"userCreated": {
|
||||
"name": "User creation",
|
||||
"title": "Notice: User created",
|
||||
"aUserWasCreated": "A user was created using code {code}.",
|
||||
"time": "Time",
|
||||
"notificationNotice": "Note: Notification emails can be toggled on the admin dashboard."
|
||||
},
|
||||
"inviteExpiry": {
|
||||
"name": "Invite expiry",
|
||||
"title": "Notice: Invite expired",
|
||||
"inviteExpired": "Invite expired.",
|
||||
"expiredAt": "Code {code} expired at {time}.",
|
||||
"notificationNotice": "Note: Notification emails can be toggled on the admin dashboard."
|
||||
},
|
||||
"passwordReset": {
|
||||
"name": "Password reset",
|
||||
"title": "Password reset requested - Jellyfin",
|
||||
"someoneHasRequestedReset": "Someone has recently requested a password reset on Jellyfin.",
|
||||
"ifItWasYou": "If this was you, enter the pin below into the prompt.",
|
||||
@ -26,11 +29,13 @@
|
||||
"pin": "PIN"
|
||||
},
|
||||
"userDeleted": {
|
||||
"name": "User deletion",
|
||||
"title": "Your account was deleted - Jellyfin",
|
||||
"yourAccountWasDeleted": "Your Jellyfin account was deleted.",
|
||||
"reason": "Reason"
|
||||
},
|
||||
"inviteEmail": {
|
||||
"name": "Invite email",
|
||||
"title": "Invite - Jellyfin",
|
||||
"hello": "Hi",
|
||||
"youHaveBeenInvited": "You've been invited to Jellyfin.",
|
||||
@ -39,12 +44,14 @@
|
||||
"linkButton": "Setup your account"
|
||||
},
|
||||
"welcomeEmail": {
|
||||
"name": "Welcome email",
|
||||
"title": "Welcome to Jellyfin",
|
||||
"welcome": "Welcome to Jellyfin!",
|
||||
"youCanLoginWith": "You can login with the details below",
|
||||
"jellyfinURL": "URL"
|
||||
},
|
||||
"emailConfirmation": {
|
||||
"name": "Confirmation email",
|
||||
"title": "Confirm your email - Jellyfin",
|
||||
"clickBelow": "Click the link below to confirm your email address and start using Jellyfin.",
|
||||
"confirmEmail": "Confirm Email"
|
||||
|
@ -64,7 +64,7 @@
|
||||
<p>{{ .clickBelow }}</p>
|
||||
<p>{{ .ifItWasNotYou }}</p>
|
||||
</mj-text>
|
||||
<mj-button mj-class="blue bold" href="{{ .urlVal }}">{{ .confirmEmail }}</mj-button>
|
||||
<mj-button mj-class="blue bold" href="{{ .confirmationURL }}">{{ .confirmEmail }}</mj-button>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
<mj-section mj-class="bg2">
|
||||
|
@ -3,6 +3,6 @@
|
||||
{{ .clickBelow }}
|
||||
{{ .ifItWasNotYou }}
|
||||
|
||||
{{ .urlVal }}
|
||||
{{ .confirmationURL }}
|
||||
|
||||
{{ .message }}
|
||||
|
@ -64,14 +64,14 @@
|
||||
</mj-text>
|
||||
<mj-table mj-class="text" container-background-color="#242424">
|
||||
<tr style="text-align: left;">
|
||||
<th>{{ .name }}</th>
|
||||
<th>{{ .address }}</th>
|
||||
<th>{{ .time }}</th>
|
||||
<th>{{ .nameString }}</th>
|
||||
<th>{{ .addressString }}</th>
|
||||
<th>{{ .timeString }}</th>
|
||||
</tr>
|
||||
<tr style="font-style: italic; text-align: left; color: rgb(153,153,153);">
|
||||
<th>{{ .nameVal }}</th>
|
||||
<th>{{ .addressVal }}</th>
|
||||
<th>{{ .timeVal }}</th>
|
||||
<th>{{ .name }}</th>
|
||||
<th>{{ .address }}</th>
|
||||
<th>{{ .time }}</th>
|
||||
</mj-table>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
@ -1,7 +1,7 @@
|
||||
{{ .aUserWasCreated }}
|
||||
|
||||
{{ .name }}: {{ .nameVal }}
|
||||
{{ .address }}: {{ .addressVal }}
|
||||
{{ .time }}: {{ .timeVal }}
|
||||
{{ .nameString }}: {{ .name }}
|
||||
{{ .addressString }}: {{ .address }}
|
||||
{{ .timeString }}: {{ .time }}
|
||||
|
||||
{{ .notificationNotice }}
|
||||
|
@ -61,7 +61,7 @@
|
||||
<mj-column>
|
||||
<mj-text mj-class="text" font-size="16px" font-family="Noto Sans, Helvetica, Arial, sans-serif">
|
||||
<h3>{{ .yourAccountWasDeleted }}</h3>
|
||||
<p>{{ .reason }}: <i>{{ .reasonVal }}</i></p>
|
||||
<p>{{ .reasonString }}: <i>{{ .reason }}</i></p>
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
@ -1,4 +1,4 @@
|
||||
{{ .yourAccountWasDeleted }}
|
||||
{{ .reason }}: {{ .reasonVal }}
|
||||
{{ .reasonString }}: {{ .reason }}
|
||||
|
||||
{{ .message }}
|
||||
|
@ -66,7 +66,7 @@
|
||||
<p>{{ .codeExpiry }}</p>
|
||||
<p>{{ .ifItWasNotYou }}</p>
|
||||
</mj-text>
|
||||
<mj-button mj-class="blue bold"><mj-raw>{{ .pinVal }}</mj-raw></mj-button>
|
||||
<mj-button mj-class="blue bold"><mj-raw>{{ .pin }}</mj-raw></mj-button>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
<mj-section mj-class="bg2">
|
||||
|
@ -5,6 +5,6 @@
|
||||
{{ .codeExpiry }}
|
||||
{{ .ifItWasNotYou }}
|
||||
|
||||
{{ .pin }}: {{ .pinVal }}
|
||||
{{ .pinString }}: {{ .pin }}
|
||||
|
||||
{{ .message }}
|
||||
|
@ -65,7 +65,7 @@
|
||||
<p>{{ .toJoin }}</p>
|
||||
<p>{{ .inviteExpiry }}</p>
|
||||
</mj-text>
|
||||
<mj-button mj-class="blue bold" href="{{ .invite_link }}">{{ .linkButton }}</mj-button>
|
||||
<mj-button mj-class="blue bold" href="{{ .inviteURL }}">{{ .linkButton }}</mj-button>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
<mj-section mj-class="bg2">
|
||||
|
@ -3,6 +3,6 @@
|
||||
{{ .toJoin }}
|
||||
{{ .inviteExpiry }}
|
||||
|
||||
{{ .invite_link }}
|
||||
{{ .inviteURL }}
|
||||
|
||||
{{ .message }}
|
||||
|
@ -62,8 +62,8 @@
|
||||
<mj-text mj-class="text" font-size="16px" font-family="Noto Sans, Helvetica, Arial, sans-serif">
|
||||
<h3>{{ .welcome }}</h3>
|
||||
<p>{{ .youCanLoginWith }}:</p>
|
||||
{{ .jellyfinURL }}: <a href="{{ .jellyfinURLVal }}">{{ .jellyfinURLVal }}</a>
|
||||
<p>{{ .username }}: <i>{{ .usernameVal }}</i></p>
|
||||
{{ .jellyfinURLString }}: <a href="{{ .jellyfinURLVal }}">{{ .jellyfinURL }}</a>
|
||||
<p>{{ .usernameString }}: <i>{{ .username }}</i></p>
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
{{ .youCanLoginWith }}:
|
||||
|
||||
{{ .jellyfinURL }}: {{ .jellyfinURLVal }}
|
||||
{{ .username }}: {{ .usernameVal }}
|
||||
{{ .jellyfinURLString }}: {{ .jellyfinURL }}
|
||||
{{ .usernameString }}: {{ .username }}
|
||||
|
||||
|
||||
{{ .message }}
|
||||
|
@ -174,3 +174,9 @@ type settings struct {
|
||||
}
|
||||
|
||||
type langDTO map[string]string
|
||||
|
||||
type emailListDTO map[string]string
|
||||
|
||||
type emailSetDTO struct {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ func pwrMonitor(app *appContext, watcher *fsnotify.Watcher) {
|
||||
return
|
||||
}
|
||||
address = addr.(string)
|
||||
msg, err := app.email.constructReset(pwr, app)
|
||||
msg, err := app.email.constructReset(pwr, app, false)
|
||||
if err != nil {
|
||||
app.err.Printf("Failed to construct password reset email for %s", pwr.Username)
|
||||
app.debug.Printf("%s: Error: %s", pwr.Username, err)
|
||||
|
@ -139,6 +139,10 @@ 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+"/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.POST(p+"/config", app.ModifyConfig)
|
||||
api.POST(p+"/restart", app.restart)
|
||||
|
43
storage.go
43
storage.go
@ -14,15 +14,32 @@ import (
|
||||
)
|
||||
|
||||
type Storage struct {
|
||||
timePattern string
|
||||
invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path string
|
||||
invites Invites
|
||||
profiles map[string]Profile
|
||||
defaultProfile string
|
||||
emails, displayprefs, ombi_template map[string]interface{}
|
||||
policy mediabrowser.Policy
|
||||
configuration mediabrowser.Configuration
|
||||
lang Lang
|
||||
timePattern string
|
||||
invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path, customEmails_path string
|
||||
invites Invites
|
||||
profiles map[string]Profile
|
||||
defaultProfile string
|
||||
emails, displayprefs, ombi_template map[string]interface{}
|
||||
customEmails customEmails
|
||||
policy mediabrowser.Policy
|
||||
configuration mediabrowser.Configuration
|
||||
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
|
||||
@ -394,6 +411,14 @@ func (st *Storage) storeEmails() error {
|
||||
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 {
|
||||
return loadJSON(st.policy_path, &st.policy)
|
||||
}
|
||||
|
53
stripmd.go
Normal file
53
stripmd.go
Normal file
@ -0,0 +1,53 @@
|
||||
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>")
|
||||
}
|
Loading…
Reference in New Issue
Block a user