diff --git a/.goreleaser.yml b/.goreleaser.yml index 88c2404..b15cb59 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -26,11 +26,11 @@ before: - cp -r ts tempts - scripts/dark-variant.sh tempts - scripts/dark-variant.sh tempts/modules - - npx esbuild --bundle tempts/admin.ts --outfile=./data/web/js/admin.js --minify - - npx esbuild --bundle tempts/pwr.ts --outfile=./data/web/js/pwr.js --minify - - npx esbuild --bundle tempts/form.ts --outfile=./data/web/js/form.js --minify - - npx esbuild --bundle tempts/setup.ts --outfile=./data/web/js/setup.js --minify - - npx esbuild --bundle tempts/crash.ts --outfile=./data/crash.js --minify + - npx esbuild --target=es6 --format=esm --bundle tempts/admin.ts --outfile=./data/web/js/admin.js --minify + - npx esbuild --target=es6 --format=esm --bundle tempts/pwr.ts --outfile=./data/web/js/pwr.js --minify + - npx esbuild --target=es6 --format=esm --bundle tempts/form.ts --outfile=./data/web/js/form.js --minify + - npx esbuild --target=es6 --format=esm --bundle tempts/setup.ts --outfile=./data/web/js/setup.js --minify + - npx esbuild --target=es6 --format=esm --bundle tempts/crash.ts --outfile=./data/crash.js --minify - rm -r tempts - npx esbuild --bundle css/base.css --outfile=./data/web/css/bundle.css --external:remixicon.css --minify - cp html/crash.html data/ diff --git a/api-userpage.go b/api-userpage.go index c771b73..8c3f288 100644 --- a/api-userpage.go +++ b/api-userpage.go @@ -329,8 +329,58 @@ func (app *appContext) MyDiscordVerifiedInvite(gc *gin.Context) { } } dc := app.storage.discord + existingUser, ok := app.storage.discord[gc.GetString("jfId")] + if ok { + dcUser.Lang = existingUser.Lang + dcUser.Contact = existingUser.Contact + } dc[gc.GetString("jfId")] = dcUser app.storage.discord = dc app.storage.storeDiscordUsers() respondBool(200, true, gc) } + +// @Summary Returns true/false on whether or not your telegram PIN was verified, and assigns the telegram user to you. +// @Produce json +// @Success 200 {object} boolResponse +// @Failure 401 {object} boolResponse +// @Param pin path string true "PIN code to check" +// @Router /my/telegram/verified/{pin} [get] +// @tags User Page +func (app *appContext) MyTelegramVerifiedInvite(gc *gin.Context) { + pin := gc.Param("pin") + tokenIndex := -1 + for i, v := range app.telegram.verifiedTokens { + if v.Token == pin { + tokenIndex = i + break + } + } + if tokenIndex == -1 { + respondBool(200, false, gc) + return + } + if app.config.Section("telegram").Key("require_unique").MustBool(false) { + for _, u := range app.storage.telegram { + if app.telegram.verifiedTokens[tokenIndex].Username == u.Username { + respondBool(400, false, gc) + return + } + } + } + tgUser := TelegramUser{ + ChatID: app.telegram.verifiedTokens[tokenIndex].ChatID, + Username: app.telegram.verifiedTokens[tokenIndex].Username, + Contact: true, + } + + tg := app.storage.telegram + existingUser, ok := app.storage.telegram[gc.GetString("jfId")] + if ok { + tgUser.Lang = existingUser.Lang + tgUser.Contact = existingUser.Contact + } + tg[gc.GetString("jfId")] = tgUser + app.storage.storeTelegramUsers() + respondBool(200, true, gc) +} diff --git a/html/account-linking.html b/html/account-linking.html index 0fcaf66..e38f013 100644 --- a/html/account-linking.html +++ b/html/account-linking.html @@ -5,7 +5,10 @@

{{ .discordSendPINMessage }}

- + + {{ .strings.joinTheServer }} + +
{{ .strings.success }} @@ -16,7 +19,7 @@
{{ .strings.linkTelegram }}

{{ .strings.sendPIN }}

-

{{ .telegramPIN }}

+

diff --git a/html/user.html b/html/user.html index 5135c2e..609f7c0 100644 --- a/html/user.html +++ b/html/user.html @@ -10,6 +10,8 @@ window.language = "{{ .langName }}"; window.telegramEnabled = {{ .telegramEnabled }}; window.telegramRequired = {{ .telegramRequired }}; + window.telegramUsername = {{ .telegramUsername }}; + window.telegramURL = {{ .telegramURL }}; window.emailEnabled = {{ .emailEnabled }}; window.emailRequired = {{ .emailRequired }}; window.discordEnabled = {{ .discordEnabled }}; diff --git a/router.go b/router.go index 80bec1a..b1b5b95 100644 --- a/router.go +++ b/router.go @@ -234,6 +234,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) { user.GET(p+"/discord/invite", app.MyDiscordServerInvite) user.GET(p+"/pin/:service", app.GetMyPIN) user.GET(p+"/discord/verified/:pin", app.MyDiscordVerifiedInvite) + user.GET(p+"/telegram/verified/:pin", app.MyTelegramVerifiedInvite) } } } diff --git a/ts/form.ts b/ts/form.ts index 01b8d87..59f4de2 100644 --- a/ts/form.ts +++ b/ts/form.ts @@ -3,7 +3,7 @@ import { notificationBox, whichAnimationEvent } from "./modules/common.js"; import { _get, _post, toggleLoader, addLoader, removeLoader, toDateString } from "./modules/common.js"; import { loadLangSelector } from "./modules/lang.js"; import { initValidator } from "./modules/validator.js"; -import { Discord, DiscordConfiguration } from "./modules/account-linking.js"; +import { Discord, Telegram, ServiceConfiguration } from "./modules/account-linking.js"; interface formWindow extends Window { invalidPassword: string; @@ -50,46 +50,31 @@ var telegramVerified = false; if (window.telegramEnabled) { window.telegramModal = new Modal(document.getElementById("modal-telegram"), window.telegramRequired); const telegramButton = document.getElementById("link-telegram") as HTMLSpanElement; - telegramButton.onclick = () => { - const waiting = document.getElementById("telegram-waiting") as HTMLSpanElement; - toggleLoader(waiting); - window.telegramModal.show(); - let modalClosed = false; - window.telegramModal.onclose = () => { - modalClosed = true; - toggleLoader(waiting); + + const telegramConf: ServiceConfiguration = { + modal: window.telegramModal as Modal, + pin: window.telegramPIN, + pinURL: "", + verifiedURL: "/invite/" + window.code + "/telegram/verified/", + invalidCodeError: window.messages["errorInvalidCode"], + accountLinkedError: window.messages["errorAccountLinked"], + successError: window.messages["verified"], + successFunc: (modalClosed: boolean) => { + if (modalClosed) return; + telegramVerified = true; + telegramButton.classList.add("unfocused"); + document.getElementById("contact-via").classList.remove("unfocused"); + document.getElementById("contact-via-email").parentElement.classList.remove("unfocused"); + const radio = document.getElementById("contact-via-telegram") as HTMLInputElement; + radio.parentElement.classList.remove("unfocused"); + radio.checked = true; + validatorFunc(); } - const checkVerified = () => _get("/invite/" + window.code + "/telegram/verified/" + window.telegramPIN, null, (req: XMLHttpRequest) => { - if (req.readyState == 4) { - if (req.status == 401) { - window.telegramModal.close(); - window.notifications.customError("invalidCodeError", window.messages["errorInvalidCode"]); - return; - } else if (req.status == 400) { - window.telegramModal.close(); - window.notifications.customError("accountLinkedError", window.messages["errorAccountLinked"]); - } else if (req.status == 200) { - if (req.response["success"] as boolean) { - telegramVerified = true; - waiting.classList.add("~positive"); - waiting.classList.remove("~info"); - window.notifications.customPositive("telegramVerified", "", window.messages["verified"]); - setTimeout(window.telegramModal.close, 2000); - telegramButton.classList.add("unfocused"); - document.getElementById("contact-via").classList.remove("unfocused"); - document.getElementById("contact-via-email").parentElement.classList.remove("unfocused"); - const radio = document.getElementById("contact-via-telegram") as HTMLInputElement; - radio.parentElement.classList.remove("unfocused"); - radio.checked = true; - validatorFunc(); - } else if (!modalClosed) { - setTimeout(checkVerified, 1500); - } - } - } - }); - checkVerified(); }; + + const telegram = new Telegram(telegramConf); + + telegramButton.onclick = () => { telegram.onclick(); }; } var discordVerified = false; @@ -97,7 +82,7 @@ if (window.discordEnabled) { window.discordModal = new Modal(document.getElementById("modal-discord"), window.discordRequired); const discordButton = document.getElementById("link-discord") as HTMLSpanElement; - const discordConf: DiscordConfiguration = { + const discordConf: ServiceConfiguration = { modal: window.discordModal as Modal, pin: window.discordPIN, inviteURL: window.discordInviteLink ? ("/invite/" + window.code + "/discord/invite") : "", @@ -107,21 +92,21 @@ if (window.discordEnabled) { accountLinkedError: window.messages["errorAccountLinked"], successError: window.messages["verified"], successFunc: (modalClosed: boolean) => { - if (!modalClosed) { - discordButton.classList.add("unfocused"); - document.getElementById("contact-via").classList.remove("unfocused"); - document.getElementById("contact-via-email").parentElement.classList.remove("unfocused"); - const radio = document.getElementById("contact-via-discord") as HTMLInputElement; - radio.parentElement.classList.remove("unfocused") - radio.checked = true; - validatorFunc(); - } + if (modalClosed) return; + discordVerified = true; + discordButton.classList.add("unfocused"); + document.getElementById("contact-via").classList.remove("unfocused"); + document.getElementById("contact-via-email").parentElement.classList.remove("unfocused"); + const radio = document.getElementById("contact-via-discord") as HTMLInputElement; + radio.parentElement.classList.remove("unfocused") + radio.checked = true; + validatorFunc(); } }; const discord = new Discord(discordConf); - discordButton.onclick = discord.onclick; + discordButton.onclick = () => { discord.onclick(); }; } var matrixVerified = false; diff --git a/ts/modules/account-linking.ts b/ts/modules/account-linking.ts index 5902732..61bed1e 100644 --- a/ts/modules/account-linking.ts +++ b/ts/modules/account-linking.ts @@ -35,10 +35,10 @@ interface formWindow extends Window { declare var window: formWindow; -export interface DiscordConfiguration { +export interface ServiceConfiguration { modal: Modal; pin: string; - inviteURL: string; + inviteURL?: string; pinURL: string; verifiedURL: string; invalidCodeError: string; @@ -52,16 +52,17 @@ export interface DiscordInvite { icon: string; } -export class Discord { - private _conf: DiscordConfiguration; - private _pinAcquired = false; - private _modalClosed = false; - private _waiting = document.getElementById("discord-waiting") as HTMLSpanElement; - private _verified = false; +export class ServiceLinker { + protected _conf: ServiceConfiguration; + protected _pinAcquired = false; + protected _modalClosed = false; + protected _waiting: HTMLSpanElement; + protected _verified = false; + protected _name: string; get verified(): boolean { return this._verified; } - constructor(conf: DiscordConfiguration) { + constructor(conf: ServiceConfiguration) { this._conf = conf; this._conf.modal.onclose = () => { this._modalClosed = true; @@ -69,24 +70,7 @@ export class Discord { }; } - private _getInviteURL = () => _get(this._conf.inviteURL, null, (req: XMLHttpRequest) => { - if (req.readyState != 4) return; - const inv = req.response as DiscordInvite; - const link = document.getElementById("discord-invite") as HTMLAnchorElement; - link.href = inv.invite; - link.target = "_blank"; - let innerHTML = `${window.lang.strings("joinTheServer")}`; - if (inv.icon != "") { - innerHTML += `${window.discordServerName}`; - } else { - innerHTML += ` - ${window.discordServerName} - `; - } - link.innerHTML = innerHTML; - }); - - private _checkVerified = () => { + protected _checkVerified = () => { if (this._modalClosed) return; if (!this._pinAcquired) { setTimeout(this._checkVerified, 1500); @@ -105,7 +89,7 @@ export class Discord { this._verified = true; this._waiting.classList.add("~positive"); this._waiting.classList.remove("~info"); - window.notifications.customPositive("discordVerified", "", this._conf.successError); + window.notifications.customPositive(this._name + "Verified", "", this._conf.successError); if (this._conf.successFunc) { this._conf.successFunc(false); } @@ -123,18 +107,14 @@ export class Discord { }); }; - onclick = () => { - if (this._conf.inviteURL != "") { - this._getInviteURL(); - } - + onclick() { toggleLoader(this._waiting); this._pinAcquired = false; if (this._conf.pin) { this._pinAcquired = true; this._conf.modal.modal.querySelector(".pin").textContent = this._conf.pin; - } else { + } else if (this._conf.pinURL) { _get(this._conf.pinURL, null, (req: XMLHttpRequest) => { if (req.readyState == 4 && req.status == 200) { this._conf.pin = req.response["pin"]; @@ -150,3 +130,45 @@ export class Discord { this._checkVerified(); } } + +export class Discord extends ServiceLinker { + + constructor(conf: ServiceConfiguration) { + super(conf); + this._name = "discord"; + this._waiting = document.getElementById("discord-waiting") as HTMLSpanElement; + } + + private _getInviteURL = () => _get(this._conf.inviteURL, null, (req: XMLHttpRequest) => { + if (req.readyState != 4) return; + const inv = req.response as DiscordInvite; + const link = document.getElementById("discord-invite") as HTMLSpanElement; + (link.parentElement as HTMLAnchorElement).href = inv.invite; + (link.parentElement as HTMLAnchorElement).target = "_blank"; + let innerHTML = ``; + if (inv.icon != "") { + innerHTML += `${window.discordServerName}`; + } else { + innerHTML += ` + ${window.discordServerName} + `; + } + link.innerHTML = innerHTML; + }); + + onclick() { + if (this._conf.inviteURL != "") { + this._getInviteURL(); + } + + super.onclick(); + } +} + +export class Telegram extends ServiceLinker { + constructor(conf: ServiceConfiguration) { + super(conf); + this._name = "telegram"; + this._waiting = document.getElementById("telegram-waiting") as HTMLSpanElement; + } +}; diff --git a/ts/tsconfig.json b/ts/tsconfig.json index 4bdf020..939f873 100644 --- a/ts/tsconfig.json +++ b/ts/tsconfig.json @@ -1,10 +1,10 @@ { "compilerOptions": { "outDir": "../js", - "target": "es6", + "target": "es2017", "lib": ["dom", "es2017"], "typeRoots": ["./typings", "../node_modules/@types"], - "moduleResolution": "node", + "moduleResolution": "nodenext", "esModuleInterop": true } } diff --git a/ts/user.ts b/ts/user.ts index 340f7e5..bf621c1 100644 --- a/ts/user.ts +++ b/ts/user.ts @@ -3,7 +3,7 @@ import { lang, LangFile, loadLangSelector } from "./modules/lang.js"; import { Modal } from "./modules/modal.js"; import { _get, _post, notificationBox, whichAnimationEvent, toDateString, toggleLoader } from "./modules/common.js"; import { Login } from "./modules/login.js"; -import { Discord, DiscordConfiguration } from "./modules/account-linking.js"; +import { Discord, Telegram, ServiceConfiguration } from "./modules/account-linking.js"; interface userWindow extends Window { jellyfinID: string; @@ -285,7 +285,7 @@ const addEditEmail = (add: boolean): void => { window.modals.email.show(); } -const discordConf: DiscordConfiguration = { +const discordConf: ServiceConfiguration = { modal: window.modals.discord as Modal, pin: "", inviteURL: window.discordInviteLink ? "/my/discord/invite" : "", @@ -301,6 +301,21 @@ const discordConf: DiscordConfiguration = { let discord = new Discord(discordConf); +const telegramConf: ServiceConfiguration = { + modal: window.modals.telegram as Modal, + pin: "", + pinURL: "/my/pin/telegram", + verifiedURL: "/my/telegram/verified/", + invalidCodeError: window.lang.notif("errorInvalidCode"), + accountLinkedError: window.lang.notif("errorAccountLinked"), + successError: window.lang.notif("verified"), + successFunc: (modalClosed: boolean) => { + if (modalClosed) window.location.reload(); + } +}; + +let telegram = new Telegram(telegramConf); + document.addEventListener("details-reload", () => { _get("/my/details", null, (req: XMLHttpRequest) => { if (req.readyState == 4) { @@ -325,10 +340,13 @@ document.addEventListener("details-reload", () => { contactMethodList.clear(); + // Note the weird format of the functions for discord/telegram: + // "this" was being redefined within the onclick() method, so + // they had to be wrapped in an anonymous function. const contactMethods: { name: string, icon: string, f: (add: boolean) => void }[] = [ {name: "email", icon: ``, f: addEditEmail}, - {name: "discord", icon: ``, f: discord.onclick}, - {name: "telegram", icon: ``, f: null}, + {name: "discord", icon: ``, f: (add: boolean) => { discord.onclick(); }}, + {name: "telegram", icon: ``, f: (add: boolean) => { telegram.onclick() }}, {name: "matrix", icon: `[m]`, f: null} ]; diff --git a/views.go b/views.go index ebb855b..055ca88 100644 --- a/views.go +++ b/views.go @@ -185,7 +185,7 @@ func (app *appContext) MyUserPage(gc *gin.Context) { "langName": lang, } if telegramEnabled { - data["telegramUser"] = app.telegram.username + data["telegramUsername"] = app.telegram.username data["telegramURL"] = app.telegram.link data["telegramRequired"] = app.config.Section("telegram").Key("required").MustBool(false) }