From 1a3ce1e3ff2b3d1c8e393760c422b866819ce2c0 Mon Sep 17 00:00:00 2001
From: TomatoCake <60300461+DEVTomatoCake@users.noreply.github.com>
Date: Sat, 20 Jul 2024 19:19:23 +0200
Subject: [PATCH] implement very basic connections & applications
---
webpage/index.html | 7 +-
webpage/index.ts | 11 ++
webpage/localuser.ts | 260 ++++++++++++++++++++++++++++++++++++++++++-
webpage/style.css | 39 +++++--
4 files changed, 303 insertions(+), 14 deletions(-)
diff --git a/webpage/index.html b/webpage/index.html
index 07b70b1..c970952 100644
--- a/webpage/index.html
+++ b/webpage/index.html
@@ -37,7 +37,12 @@
STATUS
- ⚙
+
+
+
⚙
+ 🔗
+ 🤖
+
diff --git a/webpage/index.ts b/webpage/index.ts
index a5d367e..3c21fa6 100644
--- a/webpage/index.ts
+++ b/webpage/index.ts
@@ -245,6 +245,17 @@ function userSettings(){
thisuser.usersettings.show();
}
document.getElementById("settings").onclick=userSettings;
+
+function userConnections(){
+ thisuser.userConnections.show();
+}
+document.getElementById("connections").onclick=userConnections;
+
+function devPortal(){
+ thisuser.devPortal.show();
+}
+document.getElementById("dev-portal").onclick=devPortal;
+
let triggered=false;
document.getElementById("messagecontainer").addEventListener("scroll",(e)=>{
const messagecontainer=document.getElementById("messagecontainer")
diff --git a/webpage/localuser.ts b/webpage/localuser.ts
index 9ca8b11..87cf0c5 100644
--- a/webpage/localuser.ts
+++ b/webpage/localuser.ts
@@ -19,6 +19,8 @@ class Localuser{
info;
headers:{"Content-type":string,Authorization:string};
usersettings:Fullscreen;
+ userConnections:Fullscreen;
+ devPortal:Fullscreen;
ready;
guilds:Guild[];
guildids:{ [key: string]: Guild };
@@ -671,6 +673,262 @@ class Localuser{
newprouns=null;
newbio=null;
}.bind(this))
- }
+
+ const connectionContainer=document.createElement("div");
+ connectionContainer.id="connection-container";
+ this.userConnections=new Fullscreen(
+ ["html",
+ connectionContainer
+ ], () => {}, async () => {
+ connectionContainer.innerHTML="";
+
+ const res=await fetch(this.info.api.toString()+"/v9/connections", {
+ headers: this.headers
+ });
+ const json=await res.json();
+
+ Object.keys(json).sort(key => json[key].enabled ? -1 : 1).forEach(key => {
+ const connection=json[key];
+
+ const container=document.createElement("div");
+ container.textContent=key.charAt(0).toUpperCase() + key.slice(1);
+
+ if (connection.enabled) {
+ container.addEventListener("click", async () => {
+ const connectionRes=await fetch(this.info.api.toString()+"/v9/connections/" + key + "/authorize", {
+ headers: this.headers
+ });
+ const connectionJSON=await connectionRes.json();
+ window.open(connectionJSON.url, "_blank", "noopener noreferrer");
+ })
+ } else {
+ container.classList.add("disabled")
+ container.title="This connection has been disabled server-side."
+ }
+
+ connectionContainer.appendChild(container);
+ })
+ }
+ );
+
+ let appName="";
+ const appListContainer=document.createElement("div");
+ appListContainer.id="app-list-container";
+ this.devPortal=new Fullscreen(
+ ["vdiv",
+ ["hdiv",
+ ["textbox", "Name:", appName, event => {
+ appName=event.target.value;
+ }],
+ ["button",
+ "",
+ "Create application",
+ async () => {
+ if (appName.trim().length == 0) return alert("Please enter a name for the application.");
+
+ const res=await fetch(this.info.api.toString()+"/v9/applications", {
+ method: "POST",
+ headers: this.headers,
+ body: JSON.stringify({
+ name: appName
+ })
+ });
+ const json=await res.json();
+ this.manageApplication(json.id);
+ this.devPortal.hide();
+ }
+ ]
+ ],
+ ["html",
+ appListContainer
+ ]
+ ], () => {}, async () => {
+ appListContainer.innerHTML="";
+
+ const res=await fetch(this.info.api.toString()+"/v9/applications", {
+ headers: this.headers
+ });
+ const json=await res.json();
+
+ json.forEach(application => {
+ const container=document.createElement("div");
+
+ if (application.cover_image) {
+ const cover=document.createElement("img");
+ cover.crossOrigin="anonymous";
+ cover.src=this.info.cdn.toString()+"/app-icons/" + application.id + "/" + application.cover_image + ".png?size=256";
+ cover.alt="";
+ cover.loading="lazy";
+ container.appendChild(cover);
+ }
+
+ const name=document.createElement("h2");
+ name.textContent=application.name + (application.bot ? " (Bot)" : "");
+ container.appendChild(name);
+
+ container.addEventListener("click", async () => {
+ this.devPortal.hide();
+ this.manageApplication(application.id);
+ });
+ appListContainer.appendChild(container);
+ })
+ }
+ )
+ }
+ async manageApplication(appId="") {
+ const res=await fetch(this.info.api.toString()+"/v9/applications/" + appId, {
+ headers: this.headers
+ });
+ const json=await res.json();
+
+ const fields: any={};
+ const appDialog=new Fullscreen(
+ ["vdiv",
+ ["title",
+ "Editing " + json.name
+ ],
+ ["hdiv",
+ ["textbox", "Application name:", json.name, event => {
+ fields.name=event.target.value;
+ }],
+ ["mdbox", "Description:", json.description, event => {
+ fields.description=event.target.value;
+ }],
+ ["vdiv",
+ json.icon ? ["img", this.info.cdn.toString()+"/app-icons/" + appId + "/" + json.icon + ".png?size=128", [128, 128]] : ["text", "No icon"],
+ ["fileupload", "Application icon:", event => {
+ const reader=new FileReader();
+ reader.readAsDataURL(event.target.files[0]);
+ reader.onload=() => {
+ fields.icon=reader.result;
+ }
+ }]
+ ]
+ ],
+ ["hdiv",
+ ["textbox", "Privacy policy URL:", json.privacy_policy_url || "", event => {
+ fields.privacy_policy_url=event.target.value;
+ }],
+ ["textbox", "Terms of Service URL:", json.terms_of_service_url || "", event => {
+ fields.terms_of_service_url=event.target.value;
+ }]
+ ],
+ ["hdiv",
+ ["checkbox", "Make bot publicly inviteable?", json.bot_public, event => {
+ fields.bot_public=event.target.checked;
+ }],
+ ["checkbox", "Require code grant to invite the bot?", json.bot_require_code_grant, event => {
+ fields.bot_require_code_grant=event.target.checked;
+ }]
+ ],
+ ["hdiv",
+ ["button",
+ "",
+ "Save changes",
+ async () => {
+ const updateRes=await fetch(this.info.api.toString()+"/v9/applications/" + appId, {
+ method: "PATCH",
+ headers: this.headers,
+ body: JSON.stringify(fields)
+ });
+ if (updateRes.ok) appDialog.hide();
+ else {
+ const updateJSON=await updateRes.json();
+ alert("An error occurred: " + updateJSON.message);
+ }
+ }
+ ],
+ ["button",
+ "",
+ (json.bot ? "Manage" : "Add") + " bot",
+ async () => {
+ if (!json.bot) {
+ if (!confirm("Are you sure you want to add a bot to this application? There's no going back.")) return;
+
+ const updateRes=await fetch(this.info.api.toString()+"/v9/applications/" + appId + "/bot", {
+ method: "POST",
+ headers: this.headers
+ });
+ const updateJSON=await updateRes.json();
+ alert("Bot token:\n" + updateJSON.token);
+ }
+
+ appDialog.hide();
+ this.manageBot(appId);
+ }
+ ]
+ ]
+ ]
+ )
+ appDialog.show();
+ }
+ async manageBot(appId="") {
+ const res=await fetch(this.info.api.toString()+"/v9/applications/" + appId, {
+ headers: this.headers
+ });
+ const json=await res.json();
+ if (!json.bot) return alert("For some reason, this application doesn't have a bot (yet).");
+
+ const fields: any={
+ username: json.bot.username,
+ avatar: json.bot.avatar ? (this.info.cdn.toString()+"/app-icons/" + appId + "/" + json.bot.avatar + ".png?size=256") : ""
+ };
+ const botDialog=new Fullscreen(
+ ["vdiv",
+ ["title",
+ "Editing bot: " + json.bot.username
+ ],
+ ["hdiv",
+ ["textbox", "Bot username:", json.bot.username, event => {
+ fields.username=event.target.value
+ }],
+ ["vdiv",
+ fields.avatar ? ["img", fields.avatar, [128, 128]] : ["text", "No avatar"],
+ ["fileupload", "Bot avatar:", event => {
+ const reader=new FileReader();
+ reader.readAsDataURL(event.target.files[0]);
+ reader.onload=() => {
+ fields.avatar=reader.result;
+ }
+ }]
+ ]
+ ],
+ ["hdiv",
+ ["button",
+ "",
+ "Save changes",
+ async () => {
+ const updateRes=await fetch(this.info.api.toString()+"/v9/applications/" + appId + "/bot", {
+ method: "PATCH",
+ headers: this.headers,
+ body: JSON.stringify(fields)
+ });
+ if (updateRes.ok) botDialog.hide();
+ else {
+ const updateJSON=await updateRes.json();
+ alert("An error occurred: " + updateJSON.message);
+ }
+ }
+ ],
+ ["button",
+ "",
+ "Reset token",
+ async () => {
+ if (!confirm("Are you sure you want to reset the bot token? Your bot will stop working until you update it.")) return;
+
+ const updateRes=await fetch(this.info.api.toString()+"/v9/applications/" + appId + "/bot/reset", {
+ method: "POST",
+ headers: this.headers
+ });
+ const updateJSON=await updateRes.json();
+ alert("New token:\n" + updateJSON.token);
+ botDialog.hide();
+ }
+ ]
+ ]
+ ]
+ );
+ botDialog.show();
+ }
}
export {Localuser};
diff --git a/webpage/style.css b/webpage/style.css
index b92af19..d3d03c7 100644
--- a/webpage/style.css
+++ b/webpage/style.css
@@ -699,22 +699,20 @@ textarea {
flex-shrink: 1;
}
-#settings {
+#user-actions {
+ display: flex;
+ flex-wrap: wrap;
+}
+#user-actions h2 {
cursor: pointer;
user-select: none;
- border-radius: .3in;
- transition: background 1s;
+ border-radius: .1in;
+ transition: color .5s;
text-align: center;
- font-size: .25in;
- width: .3in;
- height: .3in;
overflow: visible;
}
-
-#settings:hover {
- background-color: var(--settings-hover);
- cursor: pointer;
- user-select: none;
+#user-actions h2:hover, #user-actions h2:focus {
+ color: var(--timestamp-color);
}
#userinfo {
@@ -1352,4 +1350,21 @@ span {
width: 100%;
flex-direction: row;
max-height:100in;
-}
\ No newline at end of file
+}
+
+#connection-container, #app-list-container {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+ max-width: 700px;
+}
+#connection-container div, #app-list-container div {
+ padding: 5px 10px;
+ border-radius: 5px;
+ background-color: var(--textarea-bg);
+ cursor: pointer;
+}
+#connection-container .disabled {
+ background-color: var(--embed-fallback);
+ cursor: not-allowed;
+}