diff --git a/api.go b/api.go index 4a00fbb..494a19e 100644 --- a/api.go +++ b/api.go @@ -282,7 +282,7 @@ func (app *appContext) NewUserAdmin(gc *gin.Context) { } } app.jf.CacheExpiry = time.Now() - if app.config.Section("password_resets").Key("enabled").MustBool(false) { + if emailEnabled { app.storage.emails[id] = req.Email app.storage.storeEmails() } @@ -500,15 +500,16 @@ 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) + if (emailEnabled && app.config.Section("welcome_email").Key("enabled").MustBool(false) && req.Email != "") || telegramTokenIndex != -1 { + name := app.getAddressOrName(user.ID) + app.debug.Printf("%s: Sending welcome message to %s", req.Username, name) msg, err := app.email.constructWelcome(req.Username, expiry, app, false) if err != nil { - app.err.Printf("%s: Failed to construct welcome email: %v", req.Username, err) - } else if err := app.email.send(msg, req.Email); err != nil { - app.err.Printf("%s: Failed to send welcome email: %v", req.Username, err) + app.err.Printf("%s: Failed to construct welcome message: %v", req.Username, err) + } else if err := app.sendByID(msg, user.ID); err != nil { + app.err.Printf("%s: Failed to send welcome message: %v", req.Username, err) } else { - app.info.Printf("%s: Sent welcome email to \"%s\"", req.Username, req.Email) + app.info.Printf("%s: Sent welcome message to \"%s\"", req.Username, name) } } app.jf.CacheExpiry = time.Now() @@ -575,7 +576,20 @@ func (app *appContext) EnableDisableUsers(gc *gin.Context) { "GetUser": map[string]string{}, "SetPolicy": map[string]string{}, } - var addresses []string + sendMail := emailEnabled || app.config.Section("telegram").Key("enabled").MustBool(false) + var msg *Message + var err error + if sendMail { + if req.Enabled { + msg, err = app.email.constructEnabled(req.Reason, app, false) + } else { + msg, err = app.email.constructDisabled(req.Reason, app, false) + } + if err != nil { + app.err.Printf("Failed to construct account enabled/disabled emails: %v", err) + sendMail = false + } + } for _, userID := range req.Users { user, status, err := app.jf.UserByID(userID, false) if status != 200 || err != nil { @@ -590,31 +604,13 @@ func (app *appContext) EnableDisableUsers(gc *gin.Context) { app.err.Printf("Failed to set policy for user \"%s\" (%d): %v", userID, status, err) continue } - if emailEnabled && req.Notify { - addr, ok := app.storage.emails[userID] - if addr != nil && ok { - addresses = append(addresses, addr.(string)) + if sendMail && req.Notify { + if err := app.sendByID(msg, userID); err != nil { + app.err.Printf("Failed to send account enabled/disabled email: %v", err) + continue } } } - if len(addresses) != 0 { - go func(reason string, addresses []string) { - var msg *Message - var err error - if req.Enabled { - msg, err = app.email.constructEnabled(reason, app, false) - } else { - msg, err = app.email.constructDisabled(reason, app, false) - } - if err != nil { - app.err.Printf("Failed to construct account enabled/disabled emails: %v", err) - } else if err := app.email.send(msg, addresses...); err != nil { - app.err.Printf("Failed to send account enabled/disabled emails: %v", err) - } else { - app.info.Println("Sent account enabled/disabled emails") - } - }(req.Reason, addresses) - } app.jf.CacheExpiry = time.Now() if len(errors["GetUser"]) != 0 || len(errors["SetPolicy"]) != 0 { gc.JSON(500, errors) @@ -634,10 +630,19 @@ func (app *appContext) EnableDisableUsers(gc *gin.Context) { // @tags Users func (app *appContext) DeleteUsers(gc *gin.Context) { var req deleteUserDTO - var addresses []string gc.BindJSON(&req) errors := map[string]string{} ombiEnabled := app.config.Section("ombi").Key("enabled").MustBool(false) + sendMail := emailEnabled || app.config.Section("telegram").Key("enabled").MustBool(false) + var msg *Message + var err error + if sendMail { + msg, err = app.email.constructDeleted(req.Reason, app, false) + if err != nil { + app.err.Printf("Failed to construct account deletion emails: %v", err) + sendMail = false + } + } for _, userID := range req.Users { if ombiEnabled { ombiUser, code, err := app.getOmbiUser(userID) @@ -660,25 +665,12 @@ func (app *appContext) DeleteUsers(gc *gin.Context) { errors[userID] += msg } } - if emailEnabled && req.Notify { - addr, ok := app.storage.emails[userID] - if addr != nil && ok { - addresses = append(addresses, addr.(string)) + if sendMail && req.Notify { + if err := app.sendByID(msg, userID); err != nil { + app.err.Printf("Failed to send account deletion email: %v", err) } } } - if len(addresses) != 0 { - go func(reason string, addresses []string) { - msg, err := app.email.constructDeleted(reason, app, false) - if err != nil { - app.err.Printf("Failed to construct account deletion emails: %v", err) - } else if err := app.email.send(msg, addresses...); err != nil { - app.err.Printf("Failed to send account deletion emails: %v", err) - } else { - app.info.Println("Sent account deletion emails") - } - }(req.Reason, addresses) - } app.jf.CacheExpiry = time.Now() if len(errors) == len(req.Users) { respondBool(500, false, gc) @@ -735,29 +727,21 @@ func (app *appContext) ExtendExpiry(gc *gin.Context) { func (app *appContext) Announce(gc *gin.Context) { var req announcementDTO gc.BindJSON(&req) - if !emailEnabled { + if !(emailEnabled || app.config.Section("telegram").Key("enabled").MustBool(false)) { respondBool(400, false, gc) return } - addresses := []string{} - for _, userID := range req.Users { - addr, ok := app.storage.emails[userID] - if !ok || addr == "" { - continue - } - addresses = append(addresses, addr.(string)) - } msg, err := app.email.constructTemplate(req.Subject, req.Message, app) if err != nil { - app.err.Printf("Failed to construct announcement emails: %v", err) + app.err.Printf("Failed to construct announcement messages: %v", err) respondBool(500, false, gc) return - } else if err := app.email.send(msg, addresses...); err != nil { - app.err.Printf("Failed to send announcement emails: %v", err) + } else if err := app.sendByID(msg, req.Users...); err != nil { + app.err.Printf("Failed to send announcement messages: %v", err) respondBool(500, false, gc) return } - app.info.Printf("Sent announcement email to %d users", len(addresses)) + app.info.Println("Sent announcement messages") respondBool(200, true, gc) } diff --git a/email.go b/email.go index e7c4f65..bded1c8 100644 --- a/email.go +++ b/email.go @@ -755,3 +755,30 @@ func (emailer *Emailer) constructUserExpired(app *appContext, noSub bool) (*Mess func (emailer *Emailer) send(email *Message, address ...string) error { return emailer.sender.Send(emailer.fromName, emailer.fromAddr, email, address...) } + +func (app *appContext) sendByID(email *Message, ID ...string) error { + tgEnabled := app.config.Section("telegram").Key("enabled").MustBool(false) + for _, id := range ID { + var err error + if tgChat, ok := app.storage.telegram[id]; ok && tgChat.Contact && tgEnabled { + err = app.telegram.Send(email, tgChat.ChatID) + } else if address, ok := app.storage.emails[id]; ok { + err = app.email.send(email, address.(string)) + } + if err != nil { + return err + } + } + return nil +} + +func (app *appContext) getAddressOrName(jfID string) string { + tgEnabled := app.config.Section("telegram").Key("enabled").MustBool(false) + if tgChat, ok := app.storage.telegram[jfID]; ok && tgChat.Contact && tgEnabled { + return "@" + tgChat.Username + } + if addr, ok := app.storage.emails[jfID]; ok { + return addr.(string) + } + return "" +} diff --git a/html/admin.html b/html/admin.html index fb5912d..408b2bf 100644 --- a/html/admin.html +++ b/html/admin.html @@ -6,6 +6,7 @@ window.URLBase = "{{ .urlBase }}"; window.notificationsEnabled = {{ .notifications }}; window.emailEnabled = {{ .email_enabled }}; + window.telegramEnabled = {{ .telegram_enabled }}; window.ombiEnabled = {{ .ombiEnabled }}; window.usernameEnabled = {{ .username }}; window.langFile = JSON.parse({{ .language }}); diff --git a/pwreset.go b/pwreset.go index 98ed169..7518c6d 100644 --- a/pwreset.go +++ b/pwreset.go @@ -69,27 +69,24 @@ func pwrMonitor(app *appContext, watcher *fsnotify.Watcher) { return } app.storage.loadEmails() - var address string uid := user.ID if uid == "" { app.err.Printf("Couldn't get user ID for user \"%s\"", pwr.Username) return } - addr, ok := app.storage.emails[uid] - if !ok || addr == nil { - app.err.Printf("Couldn't find email for user \"%s\". Make sure it's set", pwr.Username) - return - } - address = addr.(string) - 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) - } else if err := app.email.send(msg, address); err != nil { - app.err.Printf("Failed to send password reset email to \"%s\"", address) - app.debug.Printf("%s: Error: %s", pwr.Username, err) - } else { - app.info.Printf("Sent password reset email to \"%s\"", address) + name := app.getAddressOrName(uid) + if name != "" { + msg, err := app.email.constructReset(pwr, app, false) + + if err != nil { + app.err.Printf("Failed to construct password reset message for %s", pwr.Username) + app.debug.Printf("%s: Error: %s", pwr.Username, err) + } else if err := app.sendByID(msg, uid); err != nil { + app.err.Printf("Failed to send password reset message to \"%s\"", name) + app.debug.Printf("%s: Error: %s", pwr.Username, err) + } else { + app.info.Printf("Sent password reset message to \"%s\"", name) + } } } else { app.err.Printf("Password reset for user \"%s\" has already expired (%s). Check your time settings.", pwr.Username, pwr.Expiry) diff --git a/telegram.go b/telegram.go index f8655f0..1b23c48 100644 --- a/telegram.go +++ b/telegram.go @@ -3,7 +3,6 @@ package main import ( "fmt" "math/rand" - "strconv" "strings" "time" @@ -194,15 +193,11 @@ func (t *TelegramDaemon) QuoteReply(upd *tg.Update, content string) error { return err } -// Send adds compatibility with EmailClient, fromName/fromAddr are discarded, message.Text is used, addresses are Chat IDs as strings. -func (t *TelegramDaemon) Send(fromName, fromAddr string, message *Message, address ...string) error { - for _, addr := range address { - ChatID, err := strconv.ParseInt(addr, 10, 64) - if err != nil { - return err - } - msg := tg.NewMessage(ChatID, message.Text) - _, err = t.bot.Send(msg) +// Send will send a telegram message to a list of chat IDs. message.text is used. +func (t *TelegramDaemon) Send(message *Message, ID ...int64) error { + for _, id := range ID { + msg := tg.NewMessage(id, message.Text) + _, err := t.bot.Send(msg) if err != nil { return err } diff --git a/ts/form.ts b/ts/form.ts index 5e3a019..17c84a1 100644 --- a/ts/form.ts +++ b/ts/form.ts @@ -12,7 +12,6 @@ interface formWindow extends Window { code: string; messages: { [key: string]: string }; confirmation: boolean; - telegramEnabled: boolean; telegramRequired: boolean; telegramPIN: string; userExpiryEnabled: boolean; diff --git a/ts/modules/accounts.ts b/ts/modules/accounts.ts index 15d17bb..9db78a5 100644 --- a/ts/modules/accounts.ts +++ b/ts/modules/accounts.ts @@ -334,7 +334,7 @@ export class accountsList { this._selectAll.checked = false; this._modifySettings.classList.add("unfocused"); this._deleteUser.classList.add("unfocused"); - if (window.emailEnabled) { + if (window.emailEnabled || window.telegramEnabled) { this._announceButton.classList.add("unfocused"); } this._extendExpiry.classList.add("unfocused"); @@ -356,7 +356,7 @@ export class accountsList { this._modifySettings.classList.remove("unfocused"); this._deleteUser.classList.remove("unfocused"); this._deleteUser.textContent = window.lang.quantity("deleteUser", list.length); - if (window.emailEnabled) { + if (window.emailEnabled || window.telegramEnabled) { this._announceButton.classList.remove("unfocused"); } let anyNonExpiries = list.length == 0 ? true : false; diff --git a/ts/typings/d.ts b/ts/typings/d.ts index a6aa1b9..70a260f 100644 --- a/ts/typings/d.ts +++ b/ts/typings/d.ts @@ -18,6 +18,7 @@ declare interface Window { jfUsers: Array; notificationsEnabled: boolean; emailEnabled: boolean; + telegramEnabled: boolean; ombiEnabled: boolean; usernameEnabled: boolean; token: string; diff --git a/userdaemon.go b/userdaemon.go index 627f4de..39f46b3 100644 --- a/userdaemon.go +++ b/userdaemon.go @@ -71,9 +71,10 @@ func (app *appContext) checkUsers() { mode = "delete" termPlural = "Deleting" } - email := false - if emailEnabled && app.config.Section("user_expiry").Key("send_email").MustBool(true) { - email = true + contact := false + if (emailEnabled && app.config.Section("user_expiry").Key("send_email").MustBool(true)) || + app.config.Section("telegram").Key("enabled").MustBool(false) { + contact = true } // Use a map to speed up checking for deleted users later userExists := map[string]bool{} @@ -114,18 +115,18 @@ func (app *appContext) checkUsers() { } delete(app.storage.users, id) app.jf.CacheExpiry = time.Now() - if email { - address, ok := app.storage.emails[id] + if contact { if !ok { continue } + name := app.getAddressOrName(user.ID) msg, err := app.email.constructUserExpired(app, false) if err != nil { - app.err.Printf("Failed to construct expiry email for \"%s\": %s", user.Name, err) - } else if err := app.email.send(msg, address.(string)); err != nil { - app.err.Printf("Failed to send expiry email to \"%s\": %s", user.Name, err) + app.err.Printf("Failed to construct expiry message for \"%s\": %s", user.Name, err) + } else if err := app.sendByID(msg, user.ID); err != nil { + app.err.Printf("Failed to send expiry message to \"%s\": %s", name, err) } else { - app.info.Printf("Sent expiry notification to \"%s\"", address.(string)) + app.info.Printf("Sent expiry notification to \"%s\"", name) } } } diff --git a/views.go b/views.go index f103bab..11c751f 100644 --- a/views.go +++ b/views.go @@ -116,20 +116,21 @@ func (app *appContext) AdminPage(gc *gin.Context) { } license = string(l) gcHTML(gc, http.StatusOK, "admin.html", gin.H{ - "urlBase": app.getURLBase(gc), - "cssClass": app.cssClass, - "contactMessage": "", - "email_enabled": emailEnabled, - "notifications": notificationsEnabled, - "version": version, - "commit": commit, - "ombiEnabled": ombiEnabled, - "username": !app.config.Section("email").Key("no_username").MustBool(false), - "strings": app.storage.lang.Admin[lang].Strings, - "quantityStrings": app.storage.lang.Admin[lang].QuantityStrings, - "language": app.storage.lang.Admin[lang].JSON, - "langName": lang, - "license": license, + "urlBase": app.getURLBase(gc), + "cssClass": app.cssClass, + "contactMessage": "", + "email_enabled": emailEnabled, + "telegram_enabled": app.config.Section("telegram").Key("enabled").MustBool(false), + "notifications": notificationsEnabled, + "version": version, + "commit": commit, + "ombiEnabled": ombiEnabled, + "username": !app.config.Section("email").Key("no_username").MustBool(false), + "strings": app.storage.lang.Admin[lang].Strings, + "quantityStrings": app.storage.lang.Admin[lang].QuantityStrings, + "language": app.storage.lang.Admin[lang].JSON, + "langName": lang, + "license": license, }) }