diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index 6d97e5b..9036149 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -14,6 +14,8 @@ import { rolesjson, emojipjson, extendedProperties, + banObj, + addInfoBan, } from "./jsontypes.js"; import {User} from "./user.js"; import {I18n} from "./i18n.js"; @@ -21,6 +23,7 @@ import {Emoji} from "./emoji.js"; import {webhookMenu} from "./webhooks.js"; import {createImg} from "./utils/utils.js"; import {Sticker} from "./sticker.js"; +import {ProgessiveDecodeJSON} from "./utils/progessiveLoad.js"; class Guild extends SnowFlake { owner!: Localuser; @@ -423,9 +426,133 @@ class Guild extends SnowFlake { genDiv(); emoji.addHTMLArea(containdiv); } + const banMenu = settings.addButton(I18n.guild.bans()); + const makeBanMenu = () => { + const banDiv = document.createElement("div"); + const bansp = ProgessiveDecodeJSON(this.info.api + "/guilds/" + this.id + "/bans", { + headers: this.headers, + }); + const createBanHTML = (ban: banObj) => { + const div = document.createElement("div"); + div.classList.add("flexltr", "bandiv"); + let src: string; + if (ban.user.avatar !== null) { + src = `${this.info.cdn}/avatars/${ban.user.id}/${ban.user.avatar}.png`; + } else { + const int = Number((BigInt(ban.user.id) >> 22n) % 6n); + src = `${this.info.cdn}/embed/avatars/${int}.png`; + } + const img = createImg(src); + img.classList.add("pfp"); + const divUserRes = document.createElement("div"); + divUserRes.classList.add("flexttb"); + const username = document.createElement("span"); + username.textContent = ban.user.username; + + divUserRes.append(username); + if (ban.reason) { + const reason = document.createElement("span"); + reason.innerText = ban.reason; + divUserRes.append(I18n.guild.banReason(ban.reason)); + } + div.append(img, divUserRes); + div.onclick = async (_) => { + const opt = banMenu.addSubOptions(ban.user.username); + + opt.addHTMLArea(img.cloneNode(true) as HTMLElement); + opt.addText(ban.user.username); + if (ban.reason) opt.addText(I18n.guild.banReason(ban.reason)); + //FIXME the API sends back the wrong responce, so I don't have this info + /* + const moreInfo = (await ( + await fetch(this.info.api + "/guilds/" + this.id + "/bans/" + ban.user.id, { + headers: this.headers, + }) + ).json()) as addInfoBan; + const userWhoBanned = await User.resolve(moreInfo.executor_id, this.localuser); + opt.addHTMLArea(userWhoBanned.createWidget(this)); + //*/ + opt.addButtonInput("", I18n.user.unban(ban.user.username), async () => { + bansArr = bansArr.filter((_) => _ !== ban); + + await fetch(this.info.api + "/guilds/" + this.id + "/bans/" + ban.user.id, { + headers: this.headers, + method: "DELETE", + }); + loadPage(currentPage); + banMenu.returnFromSub(); + }); + }; + return div; + }; + let bansArr: banObj[] = []; + let onpage = 0; + async function loadArr() { + let bansArr2: banObj[] = []; + let waiting = false; + async function addHTML() { + if (waiting) return; + waiting = true; + await new Promise((res) => setTimeout(res, 0)); + waiting = false; + banDiv.append(...bansArr2.map((ban) => createBanHTML(ban))); + bansArr2 = []; + } + while (!(await bansp).done) { + const ban = await (await (await bansp).getNext()).getWhole(); + bansArr.push(ban); + if (onpage < 50) { + bansArr2.push(ban); + addHTML(); + onpage++; + } else { + next.disabled = false; + } + } + } + let currentPage = 0; + function loadPage(page = 0) { + banDiv.innerHTML = ""; + for (onpage = 0; onpage < 50; onpage++) { + const ban = bansArr[onpage + page * 50]; + if (!ban) break; + banDiv.append(createBanHTML(ban)); + } + if (onpage === 50 && bansArr[onpage + page * 50]) { + next.disabled = false; + } else { + next.disabled = true; + } + } + + const pageNav = document.createElement("div"); + const back = document.createElement("button"); + back.textContent = I18n.search.back(); + back.disabled = !currentPage; + back.onclick = () => { + back.disabled = !(currentPage - 1); + next.disabled = false; + loadPage(--currentPage); + }; + + const next = document.createElement("button"); + next.textContent = I18n.search.next(); + next.disabled = true; + pageNav.append(back, next); + banMenu.addHTMLArea(pageNav); + next.onclick = () => { + loadPage(++currentPage); + back.disabled = false; + }; + + loadArr(); + loadPage(currentPage); + return banDiv; + }; + banMenu.addHTMLArea(makeBanMenu); + const widgetMenu = settings.addButton(I18n.widget()); (async () => { - const widgetMenu = settings.addButton(I18n.widget()); const cur = (await ( await fetch(this.info.api + "/guilds/" + this.id + "/widget", { headers: this.headers, diff --git a/src/webpage/jsontypes.ts b/src/webpage/jsontypes.ts index edb69e4..c3eb533 100644 --- a/src/webpage/jsontypes.ts +++ b/src/webpage/jsontypes.ts @@ -121,6 +121,23 @@ type readyjson = { }; }; }; +interface banObj { + reason: string | null; + user: { + username: string; + discriminator: string; + id: string; + avatar: string | null; + public_flags: number; + }; +} +interface addInfoBan { + id: string; + user_id: string; + guild_id: string; + executor_id: string; + reason?: string | undefined; +} type mainuserjson = userjson & { flags: number; mfa_enabled?: boolean; @@ -819,4 +836,6 @@ export { webhookInfo, webhookType, stickerJson, + banObj, + addInfoBan, }; diff --git a/src/webpage/settings.ts b/src/webpage/settings.ts index 038a57a..29a9c49 100644 --- a/src/webpage/settings.ts +++ b/src/webpage/settings.ts @@ -660,7 +660,7 @@ class HtmlArea implements OptionsElement { } generateHTML(): HTMLElement { if (this.html instanceof Function) { - return this.html(); + return (this.html = this.html()); } else { return this.html; } diff --git a/src/webpage/style.css b/src/webpage/style.css index 1cbebeb..3941d9e 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -48,6 +48,17 @@ body { align-items: center; } } +.bandiv { + padding: 10px; + align-items: center; + margin-bottom: 8px; + background: var(--secondary-bg); + border-radius: 6px; + cursor: pointer; + div { + margin-left: 8px; + } +} .flexltr { min-height: 0; display: flex; diff --git a/src/webpage/user.ts b/src/webpage/user.ts index 3d812ac..a30c640 100644 --- a/src/webpage/user.ts +++ b/src/webpage/user.ts @@ -475,7 +475,30 @@ class User extends SnowFlake { } return pfp; } - + createWidget(guild: Guild) { + const div = document.createElement("div"); + div.classList.add("flexltr", "createdWebhook"); + //TODO make sure this is something I can actually do here + const name = document.createElement("b"); + name.textContent = this.name; + const nameBox = document.createElement("div"); + nameBox.classList.add("flexttb"); + nameBox.append(name); + const pfp = this.buildpfp(undefined, div); + div.append(pfp, nameBox); + Member.resolveMember(this, guild).then((_) => { + if (_) { + name.textContent = _.name; + pfp.src = _.getpfpsrc(); + } else { + const notFound = document.createElement("span"); + notFound.textContent = I18n.webhooks.notFound(); + nameBox.append(notFound); + } + }); + this.bind(div, guild); + return div; + } async buildstatuspfp(guild: Guild | void | Member | null): Promise { const div = document.createElement("div"); div.classList.add("pfpDiv"); @@ -554,6 +577,25 @@ class User extends SnowFlake { const json = await fetch(localuser.info.api.toString() + "/users/" + id + "/profile", { headers: localuser.headers, }).then((res) => res.json()); + if (json.code === 404) { + return new User( + { + id: "0", + public_flags: 0, + username: I18n.friends.notfound(), + avatar: null, + discriminator: "0000", + bio: "", + bot: false, + premium_type: 0, + premium_since: "", + accent_color: 0, + theme_colors: "", + badge_ids: [], + }, + localuser, + ); + } return new User(json.user, localuser); } diff --git a/src/webpage/utils/progessiveLoad.ts b/src/webpage/utils/progessiveLoad.ts index f31a9a0..e8409d7 100644 --- a/src/webpage/utils/progessiveLoad.ts +++ b/src/webpage/utils/progessiveLoad.ts @@ -44,17 +44,29 @@ export class ProgressiveArray { } decoder = new TextDecoder(); backChar?: string; + chars = ""; + curchar = 0; async getChar() { if (this.backChar) { const temp = this.backChar; delete this.backChar; return temp; } - let char = ""; - while (!char) { - char = this.decoder.decode((await this.get8BitArray(1)).buffer, {stream: true}); + let char: string; + if ((char = this.chars[this.curchar])) { + this.curchar++; + return char; } - return char; + let chars = ""; + while (!chars) { + const buflen = (this.cbuff?.length || 0) - this.index; + chars = this.decoder.decode((await this.get8BitArray(buflen <= 0 ? 1 : buflen)).buffer, { + stream: true, + }); + } + this.chars = chars; + this.curchar = 1; + return chars[0]; } putBackChar(char: string) { this.backChar = char; diff --git a/src/webpage/webhooks.ts b/src/webpage/webhooks.ts index 93ac879..bfbd936 100644 --- a/src/webpage/webhooks.ts +++ b/src/webpage/webhooks.ts @@ -146,30 +146,9 @@ async function webhookMenu( form.addText(I18n.webhooks.createdBy()); try { - const div = document.createElement("div"); - div.classList.add("flexltr", "createdWebhook"); - //TODO make sure this is something I can actually do here const user = new User(hook.user, guild.localuser); - const name = document.createElement("b"); - name.textContent = user.name; - const nameBox = document.createElement("div"); - nameBox.classList.add("flexttb"); - nameBox.append(name); - const pfp = user.buildpfp(undefined, div); - div.append(pfp, nameBox); + const div = user.createWidget(guild); form.addHTMLArea(div); - - Member.resolveMember(user, guild).then((_) => { - if (_) { - name.textContent = _.name; - pfp.src = _.getpfpsrc(); - } else { - const notFound = document.createElement("span"); - notFound.textContent = I18n.webhooks.notFound(); - nameBox.append(notFound); - } - }); - user.bind(div, guild); } catch {} form.addButtonInput("", I18n.webhooks.deleteWebhook(), () => { const d = new Dialog("areYouSureDelete"); @@ -191,9 +170,6 @@ async function webhookMenu( d.show(); }); }; - - console.log(hook); - webhooks.addHTMLArea(div); }; regenArea(); diff --git a/translations/en.json b/translations/en.json index 26f6df3..c2c37af 100644 --- a/translations/en.json +++ b/translations/en.json @@ -245,6 +245,9 @@ "nevermind": "Nevermind", "submit": "submit", "guild": { + "bannedBy": "Banned by:", + "banReason": "Ban reason: $1", + "bans": "Bans", "ruleId": "Rules Channel:", "community": "Community", "creating": "Creating guild", @@ -498,7 +501,8 @@ "removeRole": "Remove roles", "editServerProfile": "Edit server profile", "instanceBan": "Instance ban", - "confirmInstBan": "Are you sure you want to instance ban $1?" + "confirmInstBan": "Are you sure you want to instance ban $1?", + "unban": "Unban $1" }, "login": { "checking": "Checking Instance",