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

telegram: modularize, add to userpage

This commit is contained in:
Harvey Tindall 2023-06-19 22:11:35 +01:00
parent 68aedf07ae
commit fcedea110d
Signed by: hrfee
GPG Key ID: BBC65952848FB1A2
10 changed files with 179 additions and 98 deletions

View File

@ -26,11 +26,11 @@ before:
- cp -r ts tempts - cp -r ts tempts
- scripts/dark-variant.sh tempts - scripts/dark-variant.sh tempts
- scripts/dark-variant.sh tempts/modules - scripts/dark-variant.sh tempts/modules
- npx esbuild --bundle tempts/admin.ts --outfile=./data/web/js/admin.js --minify - npx esbuild --target=es6 --format=esm --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 --target=es6 --format=esm --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 --target=es6 --format=esm --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 --target=es6 --format=esm --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/crash.ts --outfile=./data/crash.js --minify
- rm -r tempts - rm -r tempts
- npx esbuild --bundle css/base.css --outfile=./data/web/css/bundle.css --external:remixicon.css --minify - npx esbuild --bundle css/base.css --outfile=./data/web/css/bundle.css --external:remixicon.css --minify
- cp html/crash.html data/ - cp html/crash.html data/

View File

@ -329,8 +329,58 @@ func (app *appContext) MyDiscordVerifiedInvite(gc *gin.Context) {
} }
} }
dc := app.storage.discord 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 dc[gc.GetString("jfId")] = dcUser
app.storage.discord = dc app.storage.discord = dc
app.storage.storeDiscordUsers() app.storage.storeDiscordUsers()
respondBool(200, true, gc) 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)
}

View File

@ -5,7 +5,10 @@
<p class="content mb-4"> {{ .discordSendPINMessage }}</p> <p class="content mb-4"> {{ .discordSendPINMessage }}</p>
<h1 class="text-center text-2xl mb-2 pin"></h1> <h1 class="text-center text-2xl mb-2 pin"></h1>
<div class="row center"> <div class="row center">
<a id="discord-invite" class="my-5 hover:underline"></a> <a class="my-5 hover:underline">
<span class="mr-2">{{ .strings.joinTheServer }}</span>
<span id="discord-invite"></span>
</a>
</div> </div>
<span class="button ~info @low full-width center mt-4" id="discord-waiting">{{ .strings.success }}</span> <span class="button ~info @low full-width center mt-4" id="discord-waiting">{{ .strings.success }}</span>
</div> </div>
@ -16,7 +19,7 @@
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3"> <div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3">
<span class="heading mb-4">{{ .strings.linkTelegram }}</span> <span class="heading mb-4">{{ .strings.linkTelegram }}</span>
<p class="content mb-4">{{ .strings.sendPIN }}</p> <p class="content mb-4">{{ .strings.sendPIN }}</p>
<p class="text-center text-2xl mb-2">{{ .telegramPIN }}</p> <p class="text-center text-2xl mb-2 pin"></p>
<a class="subheading link-center" href="{{ .telegramURL }}" target="_blank"> <a class="subheading link-center" href="{{ .telegramURL }}" target="_blank">
<span class="shield ~info mr-4"> <span class="shield ~info mr-4">
<span class="icon"> <span class="icon">

View File

@ -10,6 +10,8 @@
window.language = "{{ .langName }}"; window.language = "{{ .langName }}";
window.telegramEnabled = {{ .telegramEnabled }}; window.telegramEnabled = {{ .telegramEnabled }};
window.telegramRequired = {{ .telegramRequired }}; window.telegramRequired = {{ .telegramRequired }};
window.telegramUsername = {{ .telegramUsername }};
window.telegramURL = {{ .telegramURL }};
window.emailEnabled = {{ .emailEnabled }}; window.emailEnabled = {{ .emailEnabled }};
window.emailRequired = {{ .emailRequired }}; window.emailRequired = {{ .emailRequired }};
window.discordEnabled = {{ .discordEnabled }}; window.discordEnabled = {{ .discordEnabled }};

View File

@ -234,6 +234,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
user.GET(p+"/discord/invite", app.MyDiscordServerInvite) user.GET(p+"/discord/invite", app.MyDiscordServerInvite)
user.GET(p+"/pin/:service", app.GetMyPIN) user.GET(p+"/pin/:service", app.GetMyPIN)
user.GET(p+"/discord/verified/:pin", app.MyDiscordVerifiedInvite) user.GET(p+"/discord/verified/:pin", app.MyDiscordVerifiedInvite)
user.GET(p+"/telegram/verified/:pin", app.MyTelegramVerifiedInvite)
} }
} }
} }

View File

@ -3,7 +3,7 @@ import { notificationBox, whichAnimationEvent } from "./modules/common.js";
import { _get, _post, toggleLoader, addLoader, removeLoader, toDateString } from "./modules/common.js"; import { _get, _post, toggleLoader, addLoader, removeLoader, toDateString } from "./modules/common.js";
import { loadLangSelector } from "./modules/lang.js"; import { loadLangSelector } from "./modules/lang.js";
import { initValidator } from "./modules/validator.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 { interface formWindow extends Window {
invalidPassword: string; invalidPassword: string;
@ -50,31 +50,18 @@ 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);
const telegramButton = document.getElementById("link-telegram") as HTMLSpanElement; const telegramButton = document.getElementById("link-telegram") as HTMLSpanElement;
telegramButton.onclick = () => {
const waiting = document.getElementById("telegram-waiting") as HTMLSpanElement; const telegramConf: ServiceConfiguration = {
toggleLoader(waiting); modal: window.telegramModal as Modal,
window.telegramModal.show(); pin: window.telegramPIN,
let modalClosed = false; pinURL: "",
window.telegramModal.onclose = () => { verifiedURL: "/invite/" + window.code + "/telegram/verified/",
modalClosed = true; invalidCodeError: window.messages["errorInvalidCode"],
toggleLoader(waiting); accountLinkedError: window.messages["errorAccountLinked"],
} successError: window.messages["verified"],
const checkVerified = () => _get("/invite/" + window.code + "/telegram/verified/" + window.telegramPIN, null, (req: XMLHttpRequest) => { successFunc: (modalClosed: boolean) => {
if (req.readyState == 4) { if (modalClosed) return;
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; 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"); telegramButton.classList.add("unfocused");
document.getElementById("contact-via").classList.remove("unfocused"); document.getElementById("contact-via").classList.remove("unfocused");
document.getElementById("contact-via-email").parentElement.classList.remove("unfocused"); document.getElementById("contact-via-email").parentElement.classList.remove("unfocused");
@ -82,14 +69,12 @@ if (window.telegramEnabled) {
radio.parentElement.classList.remove("unfocused"); radio.parentElement.classList.remove("unfocused");
radio.checked = true; radio.checked = true;
validatorFunc(); validatorFunc();
} else if (!modalClosed) {
setTimeout(checkVerified, 1500);
} }
}
}
});
checkVerified();
}; };
const telegram = new Telegram(telegramConf);
telegramButton.onclick = () => { telegram.onclick(); };
} }
var discordVerified = false; var discordVerified = false;
@ -97,7 +82,7 @@ if (window.discordEnabled) {
window.discordModal = new Modal(document.getElementById("modal-discord"), window.discordRequired); window.discordModal = new Modal(document.getElementById("modal-discord"), window.discordRequired);
const discordButton = document.getElementById("link-discord") as HTMLSpanElement; const discordButton = document.getElementById("link-discord") as HTMLSpanElement;
const discordConf: DiscordConfiguration = { const discordConf: ServiceConfiguration = {
modal: window.discordModal as Modal, modal: window.discordModal as Modal,
pin: window.discordPIN, pin: window.discordPIN,
inviteURL: window.discordInviteLink ? ("/invite/" + window.code + "/discord/invite") : "", inviteURL: window.discordInviteLink ? ("/invite/" + window.code + "/discord/invite") : "",
@ -107,7 +92,8 @@ if (window.discordEnabled) {
accountLinkedError: window.messages["errorAccountLinked"], accountLinkedError: window.messages["errorAccountLinked"],
successError: window.messages["verified"], successError: window.messages["verified"],
successFunc: (modalClosed: boolean) => { successFunc: (modalClosed: boolean) => {
if (!modalClosed) { if (modalClosed) return;
discordVerified = true;
discordButton.classList.add("unfocused"); discordButton.classList.add("unfocused");
document.getElementById("contact-via").classList.remove("unfocused"); document.getElementById("contact-via").classList.remove("unfocused");
document.getElementById("contact-via-email").parentElement.classList.remove("unfocused"); document.getElementById("contact-via-email").parentElement.classList.remove("unfocused");
@ -116,12 +102,11 @@ if (window.discordEnabled) {
radio.checked = true; radio.checked = true;
validatorFunc(); validatorFunc();
} }
}
}; };
const discord = new Discord(discordConf); const discord = new Discord(discordConf);
discordButton.onclick = discord.onclick; discordButton.onclick = () => { discord.onclick(); };
} }
var matrixVerified = false; var matrixVerified = false;

View File

@ -35,10 +35,10 @@ interface formWindow extends Window {
declare var window: formWindow; declare var window: formWindow;
export interface DiscordConfiguration { export interface ServiceConfiguration {
modal: Modal; modal: Modal;
pin: string; pin: string;
inviteURL: string; inviteURL?: string;
pinURL: string; pinURL: string;
verifiedURL: string; verifiedURL: string;
invalidCodeError: string; invalidCodeError: string;
@ -52,16 +52,17 @@ export interface DiscordInvite {
icon: string; icon: string;
} }
export class Discord { export class ServiceLinker {
private _conf: DiscordConfiguration; protected _conf: ServiceConfiguration;
private _pinAcquired = false; protected _pinAcquired = false;
private _modalClosed = false; protected _modalClosed = false;
private _waiting = document.getElementById("discord-waiting") as HTMLSpanElement; protected _waiting: HTMLSpanElement;
private _verified = false; protected _verified = false;
protected _name: string;
get verified(): boolean { return this._verified; } get verified(): boolean { return this._verified; }
constructor(conf: DiscordConfiguration) { constructor(conf: ServiceConfiguration) {
this._conf = conf; this._conf = conf;
this._conf.modal.onclose = () => { this._conf.modal.onclose = () => {
this._modalClosed = true; this._modalClosed = true;
@ -69,24 +70,7 @@ export class Discord {
}; };
} }
private _getInviteURL = () => _get(this._conf.inviteURL, null, (req: XMLHttpRequest) => { protected _checkVerified = () => {
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 = `<span class="mr-2">${window.lang.strings("joinTheServer")}</span>`;
if (inv.icon != "") {
innerHTML += `<span class="img-circle lg mr-4"><img class="img-circle" src="${inv.icon}" width="64" height="64"></span>${window.discordServerName}`;
} else {
innerHTML += `
<span class="shield mr-4 bg-discord"><i class="ri-discord-fill ri-xl text-white"></i></span>${window.discordServerName}
`;
}
link.innerHTML = innerHTML;
});
private _checkVerified = () => {
if (this._modalClosed) return; if (this._modalClosed) return;
if (!this._pinAcquired) { if (!this._pinAcquired) {
setTimeout(this._checkVerified, 1500); setTimeout(this._checkVerified, 1500);
@ -105,7 +89,7 @@ export class Discord {
this._verified = true; this._verified = true;
this._waiting.classList.add("~positive"); this._waiting.classList.add("~positive");
this._waiting.classList.remove("~info"); 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) { if (this._conf.successFunc) {
this._conf.successFunc(false); this._conf.successFunc(false);
} }
@ -123,18 +107,14 @@ export class Discord {
}); });
}; };
onclick = () => { onclick() {
if (this._conf.inviteURL != "") {
this._getInviteURL();
}
toggleLoader(this._waiting); toggleLoader(this._waiting);
this._pinAcquired = false; this._pinAcquired = false;
if (this._conf.pin) { if (this._conf.pin) {
this._pinAcquired = true; this._pinAcquired = true;
this._conf.modal.modal.querySelector(".pin").textContent = this._conf.pin; this._conf.modal.modal.querySelector(".pin").textContent = this._conf.pin;
} else { } else if (this._conf.pinURL) {
_get(this._conf.pinURL, null, (req: XMLHttpRequest) => { _get(this._conf.pinURL, null, (req: XMLHttpRequest) => {
if (req.readyState == 4 && req.status == 200) { if (req.readyState == 4 && req.status == 200) {
this._conf.pin = req.response["pin"]; this._conf.pin = req.response["pin"];
@ -150,3 +130,45 @@ export class Discord {
this._checkVerified(); 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 += `<span class="img-circle lg mr-4"><img class="img-circle" src="${inv.icon}" width="64" height="64"></span>${window.discordServerName}`;
} else {
innerHTML += `
<span class="shield mr-4 bg-discord"><i class="ri-discord-fill ri-xl text-white"></i></span>${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;
}
};

View File

@ -1,10 +1,10 @@
{ {
"compilerOptions": { "compilerOptions": {
"outDir": "../js", "outDir": "../js",
"target": "es6", "target": "es2017",
"lib": ["dom", "es2017"], "lib": ["dom", "es2017"],
"typeRoots": ["./typings", "../node_modules/@types"], "typeRoots": ["./typings", "../node_modules/@types"],
"moduleResolution": "node", "moduleResolution": "nodenext",
"esModuleInterop": true "esModuleInterop": true
} }
} }

View File

@ -3,7 +3,7 @@ import { lang, LangFile, loadLangSelector } from "./modules/lang.js";
import { Modal } from "./modules/modal.js"; import { Modal } from "./modules/modal.js";
import { _get, _post, notificationBox, whichAnimationEvent, toDateString, toggleLoader } from "./modules/common.js"; import { _get, _post, notificationBox, whichAnimationEvent, toDateString, toggleLoader } from "./modules/common.js";
import { Login } from "./modules/login.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 { interface userWindow extends Window {
jellyfinID: string; jellyfinID: string;
@ -285,7 +285,7 @@ const addEditEmail = (add: boolean): void => {
window.modals.email.show(); window.modals.email.show();
} }
const discordConf: DiscordConfiguration = { const discordConf: ServiceConfiguration = {
modal: window.modals.discord as Modal, modal: window.modals.discord as Modal,
pin: "", pin: "",
inviteURL: window.discordInviteLink ? "/my/discord/invite" : "", inviteURL: window.discordInviteLink ? "/my/discord/invite" : "",
@ -301,6 +301,21 @@ const discordConf: DiscordConfiguration = {
let discord = new Discord(discordConf); 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", () => { document.addEventListener("details-reload", () => {
_get("/my/details", null, (req: XMLHttpRequest) => { _get("/my/details", null, (req: XMLHttpRequest) => {
if (req.readyState == 4) { if (req.readyState == 4) {
@ -325,10 +340,13 @@ document.addEventListener("details-reload", () => {
contactMethodList.clear(); 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 }[] = [ const contactMethods: { name: string, icon: string, f: (add: boolean) => void }[] = [
{name: "email", icon: `<i class="ri-mail-fill ri-lg"></i>`, f: addEditEmail}, {name: "email", icon: `<i class="ri-mail-fill ri-lg"></i>`, f: addEditEmail},
{name: "discord", icon: `<i class="ri-discord-fill ri-lg"></i>`, f: discord.onclick}, {name: "discord", icon: `<i class="ri-discord-fill ri-lg"></i>`, f: (add: boolean) => { discord.onclick(); }},
{name: "telegram", icon: `<i class="ri-telegram-fill ri-lg"></i>`, f: null}, {name: "telegram", icon: `<i class="ri-telegram-fill ri-lg"></i>`, f: (add: boolean) => { telegram.onclick() }},
{name: "matrix", icon: `<span class="font-bold">[m]</span>`, f: null} {name: "matrix", icon: `<span class="font-bold">[m]</span>`, f: null}
]; ];

View File

@ -185,7 +185,7 @@ func (app *appContext) MyUserPage(gc *gin.Context) {
"langName": lang, "langName": lang,
} }
if telegramEnabled { if telegramEnabled {
data["telegramUser"] = app.telegram.username data["telegramUsername"] = app.telegram.username
data["telegramURL"] = app.telegram.link data["telegramURL"] = app.telegram.link
data["telegramRequired"] = app.config.Section("telegram").Key("required").MustBool(false) data["telegramRequired"] = app.config.Section("telegram").Key("required").MustBool(false)
} }