From 71554e0c85f1b6cddf39de0f9ae2c36f1b93b33e Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Sat, 8 May 2021 15:53:42 +0100 Subject: [PATCH] Telegram: Change user's contact method in accounts By clicking the cog next to the telegram username, one can select whether to contact through telegram or email. --- api.go | 37 +++++++++++++++++++++ css/base.css | 6 ++++ lang/admin/en-us.json | 1 + models.go | 22 ++++++++----- router.go | 1 + ts/modules/accounts.ts | 75 +++++++++++++++++++++++++++++++++++++++++- 6 files changed, 133 insertions(+), 9 deletions(-) diff --git a/api.go b/api.go index b473c38..f56267f 100644 --- a/api.go +++ b/api.go @@ -1176,6 +1176,7 @@ func (app *appContext) GetUsers(gc *gin.Context) { } if tgUser, ok := app.storage.telegram[jfUser.ID]; ok { user.Telegram = tgUser.Username + user.NotifyThroughTelegram = tgUser.Contact } resp.UserList[i] = user i++ @@ -1991,6 +1992,42 @@ func (app *appContext) TelegramAddUser(gc *gin.Context) { respondBool(200, true, gc) } +// @Summary Sets whether to notify a user through telegram or not. +// @Produce json +// @Param telegramNotifyDTO body telegramNotifyDTO true "User's Jellyfin ID and whether or not to notify then through Telegram." +// @Success 200 {object} boolResponse +// @Success 400 {object} boolResponse +// @Success 500 {object} boolResponse +// @Router /users/telegram/notify [post] +// @Security Bearer +// @tags Other +func (app *appContext) TelegramSetNotify(gc *gin.Context) { + var req telegramNotifyDTO + gc.BindJSON(&req) + if req.ID == "" { + respondBool(400, false, gc) + return + } + if tgUser, ok := app.storage.telegram[req.ID]; ok { + tgUser.Contact = req.Enabled + app.storage.telegram[req.ID] = tgUser + if err := app.storage.storeTelegramUsers(); err != nil { + respondBool(500, false, gc) + app.err.Printf("Telegram: Failed to store users: %v", err) + return + } + respondBool(200, true, gc) + msg := "" + if !req.Enabled { + msg = "not" + } + app.debug.Printf("Telegram: User \"%s\" will %s be notified through Telegram.", tgUser.Username, msg) + return + } + app.err.Printf("Telegram: User \"%s\" does not have a telegram account registered.", req.ID) + respondBool(400, false, gc) +} + // @Summary Returns true/false on whether or not a telegram PIN was verified. Requires bearer auth. // @Produce json // @Success 200 {object} boolResponse diff --git a/css/base.css b/css/base.css index 976062b..4b1e040 100644 --- a/css/base.css +++ b/css/base.css @@ -171,6 +171,12 @@ div.card:contains(section.banner.footer) { margin: 0.5rem; } +p.sm, +span.sm { + font-size: 0.75rem; +} + + .col.sm { margin: .25rem; } diff --git a/lang/admin/en-us.json b/lang/admin/en-us.json index 533ac1d..8fb42d1 100644 --- a/lang/admin/en-us.json +++ b/lang/admin/en-us.json @@ -54,6 +54,7 @@ "reset": "Reset", "edit": "Edit", "donate": "Donate", + "contactThrough": "Contact through:", "extendExpiry": "Extend expiry", "customizeMessages": "Customize Messages", "customizeMessagesDescription": "If you don't want to use jfa-go's message templates, you can create your own using Markdown.", diff --git a/models.go b/models.go index e7adc17..772238b 100644 --- a/models.go +++ b/models.go @@ -122,14 +122,15 @@ type deleteInviteDTO struct { } type respUser struct { - ID string `json:"id" example:"fdgsdfg45534fa"` // userID of user - Name string `json:"name" example:"jeff"` // Username of user - Email string `json:"email,omitempty" example:"jeff@jellyf.in"` // Email address of user (if available) - LastActive int64 `json:"last_active" example:"1617737207510"` // Time of last activity on Jellyfin - Admin bool `json:"admin" example:"false"` // Whether or not the user is Administrator - Expiry int64 `json:"expiry" example:"1617737207510"` // Expiry time of user as Epoch/Unix time. - Disabled bool `json:"disabled"` // Whether or not the user is disabled. - Telegram string `json:"telegram"` // Telegram username (if known) + ID string `json:"id" example:"fdgsdfg45534fa"` // userID of user + Name string `json:"name" example:"jeff"` // Username of user + Email string `json:"email,omitempty" example:"jeff@jellyf.in"` // Email address of user (if available) + LastActive int64 `json:"last_active" example:"1617737207510"` // Time of last activity on Jellyfin + Admin bool `json:"admin" example:"false"` // Whether or not the user is Administrator + Expiry int64 `json:"expiry" example:"1617737207510"` // Expiry time of user as Epoch/Unix time. + Disabled bool `json:"disabled"` // Whether or not the user is disabled. + Telegram string `json:"telegram"` // Telegram username (if known) + NotifyThroughTelegram bool `json:"notify_telegram"` } type getUsersDTO struct { @@ -247,3 +248,8 @@ type telegramSetDTO struct { Token string `json:"token" example:"A1-B2-3C"` ID string `json:"id"` // Jellyfin ID of user. } + +type telegramNotifyDTO struct { + ID string `json:"id"` + Enabled bool `json:"enabled"` +} diff --git a/router.go b/router.go index 2af7aeb..355fb3f 100644 --- a/router.go +++ b/router.go @@ -162,6 +162,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) { api.GET(p+"/telegram/pin", app.TelegramGetPin) api.GET(p+"/telegram/verified/:pin", app.TelegramVerified) api.POST(p+"/users/telegram", app.TelegramAddUser) + api.POST(p+"/users/telegram/notify", app.TelegramSetNotify) } if app.config.Section("ombi").Key("enabled").MustBool(false) { api.GET(p+"/ombi/users", app.OmbiUsers) diff --git a/ts/modules/accounts.ts b/ts/modules/accounts.ts index 4201895..e383b4a 100644 --- a/ts/modules/accounts.ts +++ b/ts/modules/accounts.ts @@ -12,6 +12,7 @@ interface User { disabled: boolean; expiry: number; telegram: string; + notify_telegram: boolean; } interface getPinResponse { @@ -30,6 +31,7 @@ class user implements User { private _emailEditButton: HTMLElement; private _telegram: HTMLTableDataCellElement; private _telegramUsername: string; + private _notifyTelegram: boolean; private _expiry: HTMLTableDataCellElement; private _expiryUnix: number; private _lastActive: HTMLTableDataCellElement; @@ -88,10 +90,80 @@ class user implements User { this._telegram.innerHTML = `Add`; (this._telegram.querySelector("span") as HTMLSpanElement).onclick = this._addTelegram; } else { - this._telegram.innerHTML = `@${u}`; + this._telegram.innerHTML = ` + @${u} + + + `; + // Javascript is necessary as including the button inside the dropdown would make it too wide to display next to the username. + const button = this._telegram.querySelector("i"); + const dropdown = this._telegram.querySelector("div.dropdown") as HTMLDivElement; + const radios = this._telegram.querySelectorAll("input") as NodeListOf; + for (let i = 0; i < radios.length; i++) { + radios[i].onclick = this._setTelegramNotify; + } + + button.onclick = () => { + dropdown.classList.add("selected"); + document.addEventListener("click", outerClickListener); + }; + const outerClickListener = (event: Event) => { + if (!(event.target instanceof HTMLElement && (this._telegram.contains(event.target) || button.contains(event.target)))) { + dropdown.classList.remove("selected"); + document.removeEventListener("click", outerClickListener); + } + }; } } + + get notify_telegram(): boolean { return this._notifyTelegram; } + set notify_telegram(s: boolean) { + if (!window.telegramEnabled || !this._telegramUsername) return; + this._notifyTelegram = s; + const radios = this._telegram.querySelectorAll("input") as NodeListOf; + radios[0].checked = !s; + radios[1].checked = s; + } + private _setTelegramNotify = () => { + const radios = this._telegram.querySelectorAll("input") as NodeListOf; + let send = { + id: this.id, + enabled: radios[1].checked + }; + _post("/users/telegram/notify", send, (req: XMLHttpRequest) => { + if (req.readyState == 4) { + if (req.status != 200) { + window.notifications.customError("errorSetTelegramNotify", window.lang.notif("errorSaveSettings")); + radios[0].checked, radios[1].checked= radios[1].checked, radios[0].checked; + return; + } + } + }, false, (req: XMLHttpRequest) => { + if (req.status == 0) { + window.notifications.connectionError(); + radios[0].checked, radios[1].checked= radios[1].checked, radios[0].checked; + return; + } else if (req.status == 401) { + radios[0].checked, radios[1].checked= radios[1].checked, radios[0].checked; + window.notifications.customError("401Error", window.lang.notif("error401Unauthorized")); + } + }); + } get expiry(): number { return this._expiryUnix; } set expiry(unix: number) { @@ -252,6 +324,7 @@ class user implements User { this.admin = user.admin; this.disabled = user.disabled; this.expiry = user.expiry; + this.notify_telegram = user.notify_telegram; } asElement = (): HTMLTableRowElement => { return this._row; }