marge inital template support

This commit is contained in:
MathMan05 2025-04-15 12:17:51 -05:00
parent 51902f5307
commit 8bc009dc19
10 changed files with 359 additions and 3 deletions

View file

@ -177,6 +177,10 @@ app.use("/", async (req: Request, res: Response) => {
res.sendFile(path.join(__dirname, "webpage", "invite.html"));
return;
}
if (req.path.startsWith("/template/")) {
res.sendFile(path.join(__dirname, "webpage", "template.html"));
return;
}
const filePath = await combinePath("/webpage/" + req.path);
res.sendFile(filePath);
});

View file

@ -16,6 +16,7 @@ import {
extendedProperties,
banObj,
addInfoBan,
templateSkim,
} from "./jsontypes.js";
import {User} from "./user.js";
import {I18n} from "./i18n.js";
@ -24,6 +25,7 @@ import {webhookMenu} from "./webhooks.js";
import {createImg} from "./utils/utils.js";
import {Sticker} from "./sticker.js";
import {ProgessiveDecodeJSON} from "./utils/progessiveLoad.js";
import {getApiUrls} from "../utils.js";
class Guild extends SnowFlake {
owner!: Localuser;
@ -581,7 +583,89 @@ class Guild extends SnowFlake {
})();
const webhooks = settings.addButton(I18n.webhooks.base());
webhookMenu(this, this.info.api + `/guilds/${this.id}/webhooks`, webhooks);
console.log(this.properties.features, this.properties.features.includes("COMMUNITY"));
const template = settings.addButton(I18n.guild.templates());
(async () => {
template.addText(I18n.guild.templcateMetaDesc());
const generateTemplateArea = (temp: templateSkim) => {
const div = document.createElement("div");
div.classList.add("flexltr", "templateMiniBox");
const code = document.createElement("span");
code.textContent = temp.code + ` (${temp.name})`;
const edit = document.createElement("button");
edit.textContent = I18n.edit();
edit.onclick = () => {
const form = template.addSubForm(
I18n.guild.editingTemplate(temp.name),
(tempy) => {
const template = tempy as templateSkim;
temp.name = template.name;
temp.description = template.description;
},
{
fetchURL: this.info.api + "/guilds/" + this.id + "/templates/" + temp.code,
method: "PATCH",
headers: this.headers,
},
);
const search = new URLSearchParams([["instance", this.info.wellknown]]);
form.addMDText(
I18n.guild.templateURL(
window.location.origin + "/template/" + temp.code + "?" + search,
),
);
const name = form.addTextInput(I18n.guild.templateName(), "name", {
initText: temp.name,
});
form.addMDInput(I18n.guild.templateDesc(), "description", {
initText: temp.description,
});
User.resolve(temp.creator_id, this.localuser).then((_) => {
form.addText(I18n.guild.tempCreatedBy());
form.addHTMLArea(_.createWidget(this));
});
form.addText(I18n.guild.tempUseCount((temp.usage_count || 0) + ""));
form.addPreprocessor(() => {
if (name.value.length < 2) {
throw new FormError(name, I18n.guild.templateNameShort());
}
});
};
div.append(code, edit);
template.addHTMLArea(div);
};
template.addButtonInput("", I18n.guild.createNewTemplate(), () => {
const form = template.addSubForm(
I18n.guild.createNewTemplate(),
(code) => {
template.returnFromSub();
generateTemplateArea(code as templateSkim);
},
{
fetchURL: this.info.api + "/guilds/" + this.id + "/templates",
method: "POST",
headers: this.headers,
},
);
form.addText(I18n.guild.templcateMetaDesc());
const name = form.addTextInput(I18n.guild.templateName(), "name");
form.addMDInput(I18n.guild.templateDesc(), "description");
form.addPreprocessor(() => {
if (name.value.length < 2) {
throw new FormError(name, I18n.guild.templateNameShort());
}
});
});
const templates = (await (
await fetch(this.info.api + "/guilds/" + this.id + "/templates", {headers: this.headers})
).json()) as templateSkim[];
for (const temp of templates.reverse()) {
generateTemplateArea(temp);
}
})();
let com = false;
if (this.properties.features.includes("COMMUNITY")) {
this.addCommunity(settings, textChannels);

View file

@ -7,6 +7,7 @@ import {Message} from "./message.js";
import {File} from "./file.js";
import {I18n} from "./i18n.js";
(async () => {
let templateID = new URLSearchParams(window.location.search).get("templateID");
await I18n.done;
if (!Localuser.users.currentuser) {
@ -54,6 +55,9 @@ import {I18n} from "./i18n.js";
loading.classList.add("doneloading");
loading.classList.remove("loading");
console.log("done loading");
if (templateID) {
thisUser.passTemplateID(templateID);
}
});
} catch (e) {
console.error(e);

View file

@ -131,6 +131,37 @@ interface banObj {
public_flags: number;
};
}
interface templateSkim {
id: string;
code: string;
name: string;
description: string;
usage_count: null | number;
creator_id: string;
created_at: string;
updated_at: string;
source_guild_id: string;
serialized_source_guild: {
id: string;
afk_channel_id: null | string;
afk_timeout: number;
default_message_notifications: number;
description: null | "string";
explicit_content_filter: number;
features: string[];
icon: null | string;
large: boolean;
name: string;
preferred_locale: string;
region: string;
system_channel_id: null | string;
system_channel_flags: number;
verification_level: number;
widget_enabled: boolean;
nsfw: boolean;
premium_progress_bar_enabled: boolean;
};
}
interface addInfoBan {
id: string;
user_id: string;
@ -838,4 +869,5 @@ export {
stickerJson,
banObj,
addInfoBan,
templateSkim,
};

View file

@ -1046,6 +1046,7 @@ class Localuser {
return false;
}
}
loadGuild(id: string, forceReload = false): Guild | undefined {
this.searching = false;
let guild = this.guildids.get(id);
@ -1172,7 +1173,10 @@ class Localuser {
}
this.unreads();
}
createGuild() {
passTemplateID(id: string) {
this.createGuild(id);
}
createGuild(templateID?: string) {
const full = new Dialog("");
const buttons = full.options.addButtons("", {top: true});
const viacode = buttons.add(I18n.getTranslation("invite.joinUsing"));
@ -1224,7 +1228,61 @@ class Localuser {
full.hide();
});
}
const guildcreateFromTemplate = buttons.add(I18n.guild.createFromTemplate());
{
const form = guildcreateFromTemplate.addForm(
"",
(_: any) => {
if (_.message) {
loading.hide();
full.show();
alert(_.message);
const htmlarea = buttons.htmlarea.deref();
if (htmlarea) buttons.generateHTMLArea(guildcreateFromTemplate, htmlarea);
} else {
loading.hide();
full.hide();
}
},
{
method: "POST",
headers: this.headers,
},
);
const template = form.addTextInput(I18n.guild.template(), "template", {
initText: templateID || "",
});
form.addFileInput(I18n.getTranslation("guild.icon:"), "icon", {files: "one"});
form.addTextInput(I18n.getTranslation("guild.name:"), "name", {required: true});
const loading = new Dialog("");
loading.float.options.addTitle(I18n.guild.creating());
form.onFormError = () => {
loading.hide();
full.show();
};
form.addPreprocessor((e) => {
loading.show();
full.hide();
if ("template" in e) delete e.template;
let code: string;
if (URL.canParse(template.value)) {
const url = new URL(template.value);
code = url.pathname.split("/").at(-1) as string;
if (url.host === "discord.com") {
code = "discord:" + code;
}
} else {
code = template.value;
}
form.fetchURL = this.info.api + "/guilds/templates/" + code;
});
}
full.show();
if (templateID) {
const htmlarea = buttons.htmlarea.deref();
if (htmlarea) buttons.generateHTMLArea(guildcreateFromTemplate, htmlarea);
}
}
async makeGuild(fields: {name: string; icon: string | null}) {
return await (

View file

@ -1095,7 +1095,7 @@ class Form implements OptionsElement<object> {
readonly names: Map<string, OptionsElement<any>> = new Map();
readonly required: WeakSet<OptionsElement<any>> = new WeakSet();
readonly submitText: string;
readonly fetchURL: string;
fetchURL: string;
readonly headers = {};
readonly method: string;
value!: object;

View file

@ -59,6 +59,18 @@ body {
margin-left: 8px;
}
}
.templateMiniBox {
display: flex;
align-items: center;
padding: 6px;
border-radius: 4px;
width: fit-content;
background: var(--secondary-bg);
button {
margin-left: 4px;
}
}
.flexltr {
min-height: 0;
display: flex;
@ -1952,6 +1964,7 @@ img.bigembedimg {
border-radius: 8px;
overflow: hidden;
align-items: flex-start;
z-index: 3;
}
.hypoprofile {
position: relative;

35
src/webpage/template.html Normal file
View file

@ -0,0 +1,35 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jank Client</title>
<meta content="Invite" property="og:title" />
<meta content="Accept this invite for a spacebar guild" property="og:description" />
<meta name="description" content="A server template" />
<meta content="/logo.webp" property="og:image" />
<meta content="#4b458c" data-react-helmet="true" name="theme-color" />
<link href="/style.css" rel="stylesheet" />
<link href="/themes.css" rel="stylesheet" id="lightcss" />
<style>
body.no-theme {
background: #16191b;
}
@media (prefers-color-scheme: light) {
body.no-theme {
background: #9397bd;
}
}
</style>
</head>
<body class="no-theme">
<div>
<div id="invitebody">
<h1 id="templatename">Use Template Name</h1>
<p id="templatedescription"></p>
<button id="usetemplate">Use template</button>
</div>
</div>
<script type="module" src="/templatePage.js"></script>
</body>
</html>

110
src/webpage/templatePage.ts Normal file
View file

@ -0,0 +1,110 @@
import {I18n} from "./i18n.js";
import {templateSkim} from "./jsontypes.js";
import {getapiurls} from "./utils/utils.js";
import {getBulkUsers, Specialuser} from "./utils/utils.js";
(async () => {
const users = getBulkUsers();
const well = new URLSearchParams(window.location.search).get("instance");
const joinable: Specialuser[] = [];
for (const key in users.users) {
if (Object.prototype.hasOwnProperty.call(users.users, key)) {
const user: Specialuser = users.users[key];
if (well && user.serverurls.wellknown.includes(well)) {
joinable.push(user);
}
console.log(user);
}
}
let urls: {api: string; cdn: string} | undefined;
if (!joinable.length && well) {
const out = await getapiurls(well);
if (out) {
urls = out;
for (const key in users.users) {
if (Object.prototype.hasOwnProperty.call(users.users, key)) {
const user: Specialuser = users.users[key];
if (user.serverurls.api.includes(out.api)) {
joinable.push(user);
}
console.log(user);
}
}
} else {
throw new Error("Someone needs to handle the case where the servers don't exist");
}
} else {
urls = joinable[0].serverurls;
}
await I18n.done;
if (!joinable.length) {
document.getElementById("usetemplate")!.textContent = I18n.htmlPages.noAccount();
}
const code = window.location.pathname.split("/")[2];
fetch(`${urls!.api}/guilds/templates/${code}`, {
method: "GET",
headers: {
Authorization: joinable[0].token,
},
})
.then((response) => response.json())
.then((json) => {
const template = json as templateSkim;
document.getElementById("templatename")!.textContent = I18n.useTemplate(template.name);
document.getElementById("templatedescription")!.textContent = template.description;
});
function showAccounts(): void {
const table = document.createElement("dialog");
for (const user of joinable) {
console.log(user.pfpsrc);
const userinfo = document.createElement("div");
userinfo.classList.add("flexltr", "switchtable");
const pfp = document.createElement("img");
pfp.src = user.pfpsrc;
pfp.classList.add("pfp");
userinfo.append(pfp);
const userDiv = document.createElement("div");
userDiv.classList.add("userinfo");
userDiv.textContent = user.username;
userDiv.append(document.createElement("br"));
const span = document.createElement("span");
span.textContent = user.serverurls.wellknown.replace("https://", "").replace("http://", "");
span.classList.add("serverURL");
userDiv.append(span);
userinfo.append(userDiv);
table.append(userinfo);
userinfo.addEventListener("click", () => {
const search = new URLSearchParams();
search.set("templateID", code);
sessionStorage.setItem("currentuser", user.uid);
window.location.assign("/channels/@me?" + search);
});
}
if (!joinable.length) {
const l = new URLSearchParams("?");
l.set("goback", window.location.href);
l.set("instance", well!);
window.location.href = "/login?" + l.toString();
}
table.classList.add("flexttb", "accountSwitcher");
console.log(table);
document.body.append(table);
}
document.getElementById("usetemplate")!.addEventListener("click", showAccounts);
document.getElementById("usetemplate")!.textContent = I18n.useTemplateButton();
})();

View file

@ -230,6 +230,8 @@
"box3title": "Contribute to Jank Client",
"box3description": "We always appreciate some help, whether that be in the form of bug reports, or code, or even just pointing out some typos."
},
"useTemplate": "Use $1 as a template",
"useTemplateButton": "Use Template",
"register": {
"passwordError:": "Password: $1",
"usernameError": "Username: $1",
@ -244,7 +246,21 @@
"goThereTrust": "Go there and trust in the future",
"nevermind": "Nevermind",
"submit": "submit",
"edit": "Edit",
"guild": {
"template": "Template:",
"viewTemplate": "View Template",
"createFromTemplate": "Create From Template",
"tempUseCount": "Template has been used $1 {{PLURAL:$1|time|times}}",
"tempCreatedBy": "Template created by:",
"editingTemplate": "Editing $1",
"createNewTemplate": "Create New Template",
"templates": "Templates",
"templateName": "Template Name:",
"templateDesc": "Template Description:",
"templcateMetaDesc": "A template allows others to use this guild as a base for their own guilds, it'll copy the channels, roles, and settings of this guild, but not the messages from within the guild, the bots, or the guilds icon.",
"templateNameShort": "Template name must at least be 2 characters long",
"templateURL": "Template URL: $1",
"bannedBy": "Banned by:",
"banReason": "Ban reason: $1",
"bans": "Bans",