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

telegram: Fix UI and store useful Telegram info

Creation now works, and language preferences made before signup are
kept. telegram file storage now uses the Jellyfin ID as a key, which
makes much more sense. Also added radios to select preferred notification
method (email/telegram) as well, which the admin will soon be able to
change also.
This commit is contained in:
Harvey Tindall 2021-05-07 14:32:51 +01:00
parent 326c2cf70a
commit 72bf280e2d
Signed by: hrfee
GPG Key ID: BBC65952848FB1A2
7 changed files with 157 additions and 53 deletions

76
api.go
View File

@ -330,15 +330,44 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
success = false success = false
return return
} }
telegramTokenIndex := -1
if app.config.Section("telegram").Key("enabled").MustBool(false) {
if req.TelegramPIN == "" {
if app.config.Section("telegram").Key("required").MustBool(false) {
f = func(gc *gin.Context) {
app.debug.Printf("%s: New user failed: Telegram verification not completed", req.Code)
respond(401, "errorTelegramVerification", gc)
}
success = false
return
}
} else {
for i, v := range app.telegram.verifiedTokens {
if v.Token == req.TelegramPIN {
telegramTokenIndex = i
break
}
}
if telegramTokenIndex == -1 {
f = func(gc *gin.Context) {
app.debug.Printf("%s: New user failed: Telegram PIN was invalid", req.Code)
respond(401, "errorInvalidPIN", gc)
}
success = false
return
}
}
}
if emailEnabled && app.config.Section("email_confirmation").Key("enabled").MustBool(false) && !confirmed { if emailEnabled && app.config.Section("email_confirmation").Key("enabled").MustBool(false) && !confirmed {
claims := jwt.MapClaims{ claims := jwt.MapClaims{
"valid": true, "valid": true,
"invite": req.Code, "invite": req.Code,
"email": req.Email, "email": req.Email,
"username": req.Username, "username": req.Username,
"password": req.Password, "password": req.Password,
"exp": strconv.FormatInt(time.Now().Add(time.Hour*12).Unix(), 10), "telegramPIN": req.TelegramPIN,
"type": "confirmation", "exp": strconv.FormatInt(time.Now().Add(time.Hour*12).Unix(), 10),
"type": "confirmation",
} }
tk := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tk := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
key, err := tk.SignedString([]byte(os.Getenv("JFA_SECRET"))) key, err := tk.SignedString([]byte(os.Getenv("JFA_SECRET")))
@ -450,6 +479,27 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
app.err.Printf("Failed to store user duration: %v", err) app.err.Printf("Failed to store user duration: %v", err)
} }
} }
if app.config.Section("telegram").Key("enabled").MustBool(false) && telegramTokenIndex != -1 {
tgToken := app.telegram.verifiedTokens[telegramTokenIndex]
tgUser := TelegramUser{
ChatID: tgToken.ChatID,
Username: tgToken.Username,
Contact: req.TelegramContact,
}
if lang, ok := app.telegram.languages[tgToken.ChatID]; ok {
tgUser.Lang = lang
}
app.storage.telegram[user.ID] = tgUser
err := app.storage.storeTelegramUsers()
if err != nil {
app.err.Printf("Failed to store Telegram users: %v", err)
} else {
app.telegram.verifiedTokens[len(app.telegram.verifiedTokens)-1], app.telegram.verifiedTokens[telegramTokenIndex] = app.telegram.verifiedTokens[telegramTokenIndex], app.telegram.verifiedTokens[len(app.telegram.verifiedTokens)-1]
app.telegram.verifiedTokens = app.telegram.verifiedTokens[:len(app.telegram.verifiedTokens)-1]
}
}
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, expiry, app, false) msg, err := app.email.constructWelcome(req.Username, expiry, app, false)
@ -1891,16 +1941,16 @@ func (app *appContext) TelegramVerified(gc *gin.Context) {
pin := gc.Param("pin") pin := gc.Param("pin")
tokenIndex := -1 tokenIndex := -1
for i, v := range app.telegram.verifiedTokens { for i, v := range app.telegram.verifiedTokens {
if v == pin { if v.Token == pin {
tokenIndex = i tokenIndex = i
break break
} }
} }
if tokenIndex != -1 { // if tokenIndex != -1 {
length := len(app.telegram.verifiedTokens) // length := len(app.telegram.verifiedTokens)
app.telegram.verifiedTokens[length-1], app.telegram.verifiedTokens[tokenIndex] = app.telegram.verifiedTokens[tokenIndex], app.telegram.verifiedTokens[length-1] // app.telegram.verifiedTokens[length-1], app.telegram.verifiedTokens[tokenIndex] = app.telegram.verifiedTokens[tokenIndex], app.telegram.verifiedTokens[length-1]
app.telegram.verifiedTokens = app.telegram.verifiedTokens[:length-1] // app.telegram.verifiedTokens = app.telegram.verifiedTokens[:length-1]
} // }
respondBool(200, tokenIndex != -1, gc) respondBool(200, tokenIndex != -1, gc)
} }

View File

@ -33,7 +33,7 @@
</span> </span>
&#64;{{ .telegramUsername }} &#64;{{ .telegramUsername }}
</a> </a>
<span class="button ~info !normal full-width center mt-1" id="telegram-waiting">.</span> <span class="button ~info !normal full-width center mt-1" id="telegram-waiting">{{ .strings.success }}</span>
</div> </div>
</div> </div>
{{ end }} {{ end }}
@ -68,7 +68,15 @@
<label class="label supra" for="create-email">{{ .strings.emailAddress }}</label> <label class="label supra" for="create-email">{{ .strings.emailAddress }}</label>
<input type="email" class="input ~neutral !high mt-half mb-1" placeholder="{{ .strings.emailAddress }}" id="create-email" aria-label="{{ .strings.emailAddress }}" value="{{ .email }}"> <input type="email" class="input ~neutral !high mt-half mb-1" placeholder="{{ .strings.emailAddress }}" id="create-email" aria-label="{{ .strings.emailAddress }}" value="{{ .email }}">
{{ if .telegramEnabled }} {{ if .telegramEnabled }}
<span class="button ~info !normal full-width center" id="link-telegram">{{ .strings.linkTelegram }}</span> <span class="button ~info !normal full-width center mb-1" id="link-telegram">{{ .strings.linkTelegram }}</span>
<div id="contact-via" class="unfocused">
<label class="row switch pb-1">
<input type="radio" name="contact-via" value="email"><span>Contact through Email</span>
</label>
<label class="row switch pb-1">
<input type="radio" name="contact-via" value="telegram" id="contact-via-telegram"><span>Contact through Telegram</span>
</label>
</div>
{{ end }} {{ end }}
<label class="label supra" for="create-password">{{ .strings.password }}</label> <label class="label supra" for="create-password">{{ .strings.password }}</label>
<input type="password" class="input ~neutral !high mt-half mb-1" placeholder="{{ .strings.password }}" id="create-password" aria-label="{{ .strings.password }}"> <input type="password" class="input ~neutral !high mt-half mb-1" placeholder="{{ .strings.password }}" id="create-password" aria-label="{{ .strings.password }}">

View File

@ -19,11 +19,15 @@
"confirmationRequiredMessage": "Please check your email inbox to verify your address.", "confirmationRequiredMessage": "Please check your email inbox to verify your address.",
"yourAccountIsValidUntil": "Your account will be valid until {date}.", "yourAccountIsValidUntil": "Your account will be valid until {date}.",
"linkTelegram": "Link Telegram", "linkTelegram": "Link Telegram",
"sendPIN": "Send the PIN below to the bot, then come back here to link your account." "sendPIN": "Send the PIN below to the bot, then come back here to link your account.",
"contactEmail": "Contact through Email",
"contactTelegram": "Contact through Telegram"
}, },
"notifications": { "notifications": {
"errorUserExists": "User already exists.", "errorUserExists": "User already exists.",
"errorInvalidCode": "Invalid invite code.", "errorInvalidCode": "Invalid invite code.",
"errorTelegramVerification": "Telegram verification required.",
"errorInvalidPIN": "Telegram PIN is invalid.",
"telegramVerified": "Telegram account verified." "telegramVerified": "Telegram account verified."
}, },
"validationStrings": { "validationStrings": {

View File

@ -11,10 +11,12 @@ type boolResponse struct {
} }
type newUserDTO struct { type newUserDTO struct {
Username string `json:"username" example:"jeff" binding:"required"` // User's username Username string `json:"username" example:"jeff" binding:"required"` // User's username
Password string `json:"password" example:"guest" binding:"required"` // User's password Password string `json:"password" example:"guest" binding:"required"` // User's password
Email string `json:"email" example:"jeff@jellyf.in"` // User's email address Email string `json:"email" example:"jeff@jellyf.in"` // User's email address
Code string `json:"code" example:"abc0933jncjkcjj"` // Invite code (required on /newUser) Code string `json:"code" example:"abc0933jncjkcjj"` // Invite code (required on /newUser)
TelegramPIN string `json:"telegram_pin" example:"A1-B2-3C"` // Telegram verification PIN (if used)
TelegramContact bool `json:"telegram_contact"` // Whether or not to use telegram for notifications/pwrs
} }
type newUserResponse struct { type newUserResponse struct {

View File

@ -22,7 +22,7 @@ type Storage struct {
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{}
telegram map[int64]TelegramUser telegram map[string]TelegramUser // Map of Jellyfin User IDs to telegram users.
customEmails customEmails customEmails customEmails
policy mediabrowser.Policy policy mediabrowser.Policy
configuration mediabrowser.Configuration configuration mediabrowser.Configuration
@ -34,6 +34,7 @@ type TelegramUser struct {
ChatID int64 ChatID int64
Username string Username string
Lang string Lang string
Contact bool // Whether to contact through telegram or not
} }
type customEmails struct { type customEmails struct {

View File

@ -10,13 +10,20 @@ import (
tg "github.com/go-telegram-bot-api/telegram-bot-api" tg "github.com/go-telegram-bot-api/telegram-bot-api"
) )
type VerifiedToken struct {
Token string
ChatID int64
Username string
}
type TelegramDaemon struct { type TelegramDaemon struct {
Stopped bool Stopped bool
ShutdownChannel chan string ShutdownChannel chan string
bot *tg.BotAPI bot *tg.BotAPI
username string username string
tokens []string tokens []string
verifiedTokens []string verifiedTokens []VerifiedToken
languages map[int64]string // Store of languages for chatIDs. Added to on first interaction, and loaded from app.storage.telegram on start.
link string link string
app *appContext app *appContext
} }
@ -30,16 +37,23 @@ func newTelegramDaemon(app *appContext) (*TelegramDaemon, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &TelegramDaemon{ td := &TelegramDaemon{
Stopped: false, Stopped: false,
ShutdownChannel: make(chan string), ShutdownChannel: make(chan string),
bot: bot, bot: bot,
username: bot.Self.UserName, username: bot.Self.UserName,
tokens: []string{}, tokens: []string{},
verifiedTokens: []string{}, verifiedTokens: []VerifiedToken{},
languages: map[int64]string{},
link: "https://t.me/" + bot.Self.UserName, link: "https://t.me/" + bot.Self.UserName,
app: app, app: app,
}, nil }
for _, user := range app.storage.telegram {
if user.Lang != "" {
td.languages[user.ChatID] = user.Lang
}
}
return td, nil
} }
var runes = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") var runes = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
@ -80,28 +94,21 @@ func (t *TelegramDaemon) run() {
continue continue
} }
lang := t.app.storage.lang.chosenTelegramLang lang := t.app.storage.lang.chosenTelegramLang
user, ok := t.app.storage.telegram[upd.Message.Chat.ID] storedLang, ok := t.languages[upd.Message.Chat.ID]
if !ok { if !ok {
user := TelegramUser{ found := false
Username: upd.Message.Chat.UserName,
ChatID: upd.Message.Chat.ID,
Lang: "",
}
t.app.storage.telegram[upd.Message.Chat.ID] = user
err := t.app.storage.storeTelegramUsers()
if err != nil {
t.app.err.Printf("Failed to store Telegram users: %v", err)
}
}
if user.Lang != "" {
lang = user.Lang
} else {
for code := range t.app.storage.lang.Telegram { for code := range t.app.storage.lang.Telegram {
if code[:2] == upd.Message.From.LanguageCode { if code[:2] == upd.Message.From.LanguageCode {
lang = code lang = code
found = true
break break
} }
} }
if found {
t.languages[upd.Message.Chat.ID] = lang
}
} else {
lang = storedLang
} }
switch msg := sects[0]; msg { switch msg := sects[0]; msg {
case "/start": case "/start":
@ -125,11 +132,17 @@ func (t *TelegramDaemon) run() {
continue continue
} }
if _, ok := t.app.storage.lang.Telegram[sects[1]]; ok { if _, ok := t.app.storage.lang.Telegram[sects[1]]; ok {
user.Lang = sects[1] t.languages[upd.Message.Chat.ID] = sects[1]
t.app.storage.telegram[upd.Message.Chat.ID] = user for jfID, user := range t.app.storage.telegram {
err := t.app.storage.storeTelegramUsers() if user.ChatID == upd.Message.Chat.ID {
if err != nil { user.Lang = sects[1]
t.app.err.Printf("Failed to store Telegram users: %v", err) t.app.storage.telegram[jfID] = user
err := t.app.storage.storeTelegramUsers()
if err != nil {
t.app.err.Printf("Failed to store Telegram users: %v", err)
}
break
}
} }
} }
continue continue
@ -148,11 +161,15 @@ func (t *TelegramDaemon) run() {
} }
continue continue
} }
err := t.QuoteReply(&upd, t.app.storage.lang.Telegram[lang].Strings.get("success")) err := t.QuoteReply(&upd, t.app.storage.lang.Telegram[lang].Strings.get("pinSuccess"))
if err != nil { if err != nil {
t.app.err.Printf("Telegram: Failed to send message to \"%s\": %v", upd.Message.From.UserName, err) t.app.err.Printf("Telegram: Failed to send message to \"%s\": %v", upd.Message.From.UserName, err)
} }
t.verifiedTokens = append(t.verifiedTokens, upd.Message.Text) t.verifiedTokens = append(t.verifiedTokens, VerifiedToken{
Token: upd.Message.Text,
ChatID: upd.Message.Chat.ID,
Username: upd.Message.Chat.UserName,
})
t.tokens[len(t.tokens)-1], t.tokens[tokenIndex] = t.tokens[tokenIndex], t.tokens[len(t.tokens)-1] t.tokens[len(t.tokens)-1], t.tokens[tokenIndex] = t.tokens[tokenIndex], t.tokens[len(t.tokens)-1]
t.tokens = t.tokens[:len(t.tokens)-1] t.tokens = t.tokens[:len(t.tokens)-1]
} }

View File

@ -1,5 +1,5 @@
import { Modal } from "./modules/modal.js"; import { Modal } from "./modules/modal.js";
import { notificationBox } from "./modules/common.js"; import { notificationBox, whichAnimationEvent } from "./modules/common.js";
import { _get, _post, toggleLoader, toDateString } from "./modules/common.js"; import { _get, _post, toggleLoader, toDateString } from "./modules/common.js";
import { loadLangSelector } from "./modules/lang.js"; import { loadLangSelector } from "./modules/lang.js";
@ -41,26 +41,39 @@ loadLangSelector("form");
window.notifications = new notificationBox(document.getElementById("notification-box") as HTMLDivElement); window.notifications = new notificationBox(document.getElementById("notification-box") as HTMLDivElement);
window.animationEvent = whichAnimationEvent();
window.successModal = new Modal(document.getElementById("modal-success"), true); window.successModal = new Modal(document.getElementById("modal-success"), true);
var telegramVerified = false;
if (window.telegramEnabled) { if (window.telegramEnabled) {
window.telegramModal = new Modal(document.getElementById("modal-telegram"), window.telegramRequired); window.telegramModal = new Modal(document.getElementById("modal-telegram"), window.telegramRequired);
(document.getElementById("link-telegram") as HTMLSpanElement).onclick = () => { const telegramButton = document.getElementById("link-telegram") as HTMLSpanElement;
telegramButton.onclick = () => {
const waiting = document.getElementById("telegram-waiting") as HTMLSpanElement; const waiting = document.getElementById("telegram-waiting") as HTMLSpanElement;
toggleLoader(waiting); toggleLoader(waiting);
window.telegramModal.show(); window.telegramModal.show();
let modalClosed = false;
window.telegramModal.onclose = () => { modalClosed = true; }
const checkVerified = () => _get("/invite/" + window.code + "/telegram/verified/" + window.telegramPIN, null, (req: XMLHttpRequest) => { const checkVerified = () => _get("/invite/" + window.code + "/telegram/verified/" + window.telegramPIN, null, (req: XMLHttpRequest) => {
if (req.readyState == 4) { if (req.readyState == 4) {
if (req.status == 401) { if (req.status == 401) {
window.telegramModal.close(); window.telegramModal.close();
window.notifications.customError("invalidCodeError", window.lang.notif("errorInvalidCode")); window.notifications.customError("invalidCodeError", window.messages["errorInvalidCode"]);
return; return;
} else if (req.status == 200) { } else if (req.status == 200) {
if (req.response["success"] as boolean) { if (req.response["success"] as boolean) {
telegramVerified = true;
toggleLoader(waiting); toggleLoader(waiting);
window.telegramModal.close() waiting.classList.add("~positive");
window.notifications.customSuccess("accountVerified", window.lang.notif("telegramVerified")) waiting.classList.remove("~info");
} else { window.notifications.customPositive("telegramVerified", "", window.messages["telegramVerified"]);
setTimeout(window.telegramModal.close, 2000);
telegramButton.classList.add("unfocused");
document.getElementById("contact-via").classList.remove("unfocused");
const radio = document.getElementById("contact-via-telegram") as HTMLInputElement;
radio.checked = true;
} else if (!modalClosed) {
setTimeout(checkVerified, 1500); setTimeout(checkVerified, 1500);
} }
} }
@ -145,6 +158,8 @@ interface sendDTO {
email: string; email: string;
username: string; username: string;
password: string; password: string;
telegram_pin?: string;
telegram_contact?: boolean;
} }
const create = (event: SubmitEvent) => { const create = (event: SubmitEvent) => {
@ -156,6 +171,13 @@ const create = (event: SubmitEvent) => {
email: emailField.value, email: emailField.value,
password: passwordField.value password: passwordField.value
}; };
if (telegramVerified) {
send.telegram_pin = window.telegramPIN;
const radio = document.getElementById("contact-via-telegram") as HTMLInputElement;
if (radio.checked) {
send.telegram_contact = true;
}
}
_post("/newUser", send, (req: XMLHttpRequest) => { _post("/newUser", send, (req: XMLHttpRequest) => {
if (req.readyState == 4) { if (req.readyState == 4) {
let vals = req.response as respDTO; let vals = req.response as respDTO;