diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index b7f0edf..348f64d 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -44,7 +44,7 @@ class Channel extends SnowFlake { children!: Channel[]; guild_id!: string; permission_overwrites!: Map; - permission_overwritesar: [Role, Permissions][] = []; + permission_overwritesar: [Role | Promise, Permissions][] = []; topic!: string; nsfw!: boolean; position: number = 0; @@ -356,14 +356,16 @@ class Channel extends SnowFlake { } } const s1 = settings.addButton(I18n.channel.permissions(), {optName: ""}); - s1.options.push( - new RoleList( - this.permission_overwritesar, - this.guild, - this.updateRolePermissions.bind(this), - this, - ), - ); + + (async () => { + const list = await Promise.all( + this.permission_overwritesar.map(async (_) => { + return [await _[0], _[1]] as [Role | User, Permissions]; + }), + ); + + s1.options.push(new RoleList(list, this.guild, this.updateRolePermissions.bind(this), this)); + })(); const inviteMenu = settings.addButton(I18n.guild.invites()); makeInviteMenu(inviteMenu, this.owner, this.info.api + `/channels/${this.id}/invites`); @@ -374,9 +376,13 @@ class Channel extends SnowFlake { settings.show(); } sortPerms() { + console.log(this.permission_overwritesar + ""); this.permission_overwritesar.sort((a, b) => { + if (a[0] instanceof Promise) return -1; + if (b[0] instanceof Promise) return 1; return this.guild.roles.indexOf(a[0]) - this.guild.roles.indexOf(b[0]); }); + console.log(this.permission_overwritesar + ""); } setUpInfiniteScroller() { this.infinite = new InfiniteScroller( @@ -469,6 +475,8 @@ class Channel extends SnowFlake { const role = this.guild.roleids.get(thing.id); if (role) { this.permission_overwritesar.push([role, permission]); + } else { + this.permission_overwritesar.push([this.localuser.getUser(thing.id), permission]); } } } @@ -608,6 +616,15 @@ class Channel extends SnowFlake { if (!member.user.bot || true) { roles.add(everyone); } + + const premission = this.permission_overwrites.get(member.id); + if (premission) { + const perm = premission.getPermission(name); + if (perm) { + return perm === 1; + } + } + for (const thing of roles) { const premission = this.permission_overwrites.get(thing.id); if (premission) { @@ -2184,15 +2201,14 @@ class Channel extends SnowFlake { this.permission_overwrites = new Map(); this.permission_overwritesar = []; for (const thing of json.permission_overwrites) { - if (thing.id === "1182819038095799904" || thing.id === "1182820803700625444") { - continue; - } this.permission_overwrites.set(thing.id, new Permissions(thing.allow, thing.deny)); const permisions = this.permission_overwrites.get(thing.id); if (permisions) { const role = this.guild.roleids.get(thing.id); if (role) { this.permission_overwritesar.push([role, permisions]); + } else { + this.permission_overwritesar.push([this.localuser.getUser(thing.id), permisions]); } } } @@ -2202,6 +2218,11 @@ class Channel extends SnowFlake { const role = this.guild.roleids.get(thing); if (role) { this.croleUpdate(role, new Permissions("0"), false); + } else { + const user = this.localuser.getUser(thing); + user.then((_) => { + if (_) this.croleUpdate(_, new Permissions("0"), false); + }); } } for (const thing of pchange) { @@ -2209,13 +2230,18 @@ class Channel extends SnowFlake { const perms = this.permission_overwrites.get(thing); if (role && perms) { this.croleUpdate(role, perms, true); + } else if (perms) { + const user = this.localuser.getUser(thing); + user.then((_) => { + if (_) this.croleUpdate(_, perms, true); + }); } } console.log(pchange, nchange); this.topic = json.topic; this.nsfw = json.nsfw; } - croleUpdate: (role: Role, perm: Permissions, added: boolean) => unknown = () => {}; + croleUpdate: (role: Role | User, perm: Permissions, added: boolean) => unknown = () => {}; typingstart() { if (this.typing > Date.now()) { return; @@ -2633,7 +2659,7 @@ class Channel extends SnowFlake { } } voiceMode: "VoiceOnly" | "ChatAndVoice" = "VoiceOnly"; - async addRoleToPerms(role: Role) { + async addRoleToPerms(role: Role | User) { await fetch(this.info.api + "/channels/" + this.id + "/permissions/" + role.id, { method: "PUT", headers: this.headers, @@ -2641,12 +2667,15 @@ class Channel extends SnowFlake { allow: "0", deny: "0", id: role.id, - type: 0, + type: role instanceof User ? 1 : 0, }), }); const perm = new Permissions("0", "0"); this.permission_overwrites.set(role.id, perm); - this.permission_overwritesar.push([role, perm]); + this.permission_overwritesar.push([ + role instanceof User ? new Promise((res) => res(role)) : role, + perm, + ]); } async updateRolePermissions(id: string, perms: Permissions) { const permission = this.permission_overwrites.get(id); @@ -2663,7 +2692,7 @@ class Channel extends SnowFlake { allow: perms.allow.toString(), deny: perms.deny.toString(), id, - type: 0, + type: this.localuser.userMap.get(id) ? 1 : 0, }), }); } diff --git a/src/webpage/contextmenu.ts b/src/webpage/contextmenu.ts index 5697b73..41cb85f 100644 --- a/src/webpage/contextmenu.ts +++ b/src/webpage/contextmenu.ts @@ -15,7 +15,7 @@ interface menuPart { } class ContextButton implements menuPart { - private text: string | ((this: x) => string); + private text: string | ((this: x, arg: y) => string); private onClick: (this: x, arg: y, e: MouseEvent) => void; private icon?: iconJson; private visable?: (this: x, arg: y) => boolean; @@ -50,7 +50,7 @@ class ContextButton implements menuPart { const intext = document.createElement("button"); intext.classList.add("contextbutton"); - intext.append(this.textContent(obj1)); + intext.append(this.textContent(obj1, obj2)); intext.disabled = !!this.enabled && !this.enabled.call(obj1, obj2); @@ -95,9 +95,9 @@ class ContextButton implements menuPart { menu.append(intext); } - textContent(x: x) { + textContent(x: x, y: y) { if (this.text instanceof Function) { - return this.text.call(x); + return this.text.call(x, y); } return this.text; } @@ -179,7 +179,7 @@ class Contextmenu { for (const button of this.buttons) { button.makeContextHTML(addinfo, other, div); } - if (div.children[div.children.length - 1].tagName === "HR") { + if (div.children[div.children.length - 1]?.tagName === "HR") { div.children[div.children.length - 1].remove(); } //NOTE I don't know if this'll ever actually happen in reality diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index c229ce2..7b11700 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -20,6 +20,7 @@ import { GuildOverrides, commandJson, applicationJson, + presencejson, } from "./jsontypes.js"; import {User} from "./user.js"; import {I18n} from "./i18n.js"; @@ -489,6 +490,60 @@ class Guild extends SnowFlake { }; loadResults(); } + async searchMembers(limit: number, query: string): Promise { + if (this.id !== "@me") { + return new Promise((res) => { + const nonce = Math.floor(Math.random() * 10 ** 8) + ""; + this.localuser.ws!.send( + JSON.stringify({ + op: 8, + d: { + guild_id: [this.id], + query, + limit, + presences: true, + nonce, + }, + }), + ); + this.localuser.searchMap.set( + nonce, + async (e: { + chunk_index: number; + chunk_count: number; + nonce: string; + not_found?: string[]; + members?: memberjson[]; + presences: presencejson[]; + }) => { + console.log(e); + if (e.members && e.members[0]) { + if (e.members[0].user) { + res( + (await Promise.all(e.members.map(async (_) => await Member.new(_, this)))).filter( + (_) => _ !== undefined, + ), + ); + } else { + const prom1: Promise[] = []; + for (const thing of e.members) { + prom1.push(this.localuser.getUser(thing.id)); + } + await Promise.all(prom1); + res( + (await Promise.all(e.members.map(async (_) => await Member.new(_, this)))).filter( + (_) => _ !== undefined, + ), + ); + } + } + return []; + }, + ); + }); + } + return []; + } generateSettings() { const settings = new Settings(I18n.guild.settingsFor(this.properties.name)); const textChannels = this.channels.filter((e) => { diff --git a/src/webpage/index.ts b/src/webpage/index.ts index e436822..572c632 100644 --- a/src/webpage/index.ts +++ b/src/webpage/index.ts @@ -148,7 +148,8 @@ async function handleEnter(event: KeyboardEvent): Promise { const channel = thisUser.channelfocus; if (!channel) return; - if (markdown.rawString === "" && event.key === "ArrowUp") { + const content = MarkDown.gatherBoxText(typebox); + if (content === "" && event.key === "ArrowUp") { channel.editLast(); return; } @@ -168,7 +169,7 @@ async function handleEnter(event: KeyboardEvent): Promise { thisUser.channelfocus.replyingto = null; } - channel.sendMessage(markdown.rawString, { + channel.sendMessage(content, { attachments: images.filter((_) => document.contains(imagesHtml.get(_) || null)), embeds: [], // Add an empty array for the embeds property replyingto: replyingTo, diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 06f55c7..2151230 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -114,10 +114,17 @@ class Localuser { onswap?.(thisUser); } static userMenu = this.generateUserMenu(); + userResMap = new Map>(); async getUser(id: string) { let user = this.userMap.get(id); if (user) return user; - return User.resolve(id, this); + const cache = this.userResMap.get(id); + if (cache) return cache; + const prom = User.resolve(id, this); + this.userResMap.set(id, prom); + await prom; + this.userResMap.delete(id); + return prom; } static generateUserMenu() { const menu = new Contextmenu(""); @@ -3579,45 +3586,11 @@ class Localuser { MDFindMention(name: string, original: string, box: HTMLDivElement, typebox: MarkDown) { if (this.ws && this.lookingguild) { this.MDFineMentionGen(name, original, box, typebox); - const nonce = Math.floor(Math.random() * 10 ** 8) + ""; if (this.lookingguild.member_count <= this.lookingguild.members.size) return; if (this.lookingguild.id !== "@me") { - this.ws.send( - JSON.stringify({ - op: 8, - d: { - guild_id: [this.lookingguild.id], - query: name, - limit: 8, - presences: true, - nonce, - }, - }), - ); - this.searchMap.set(nonce, async (e) => { - console.log(e); - if (e.members && e.members[0]) { - if (e.members[0].user) { - for (const thing of e.members) { - await Member.new(thing, this.lookingguild as Guild); - } - } else { - const prom1: Promise[] = []; - for (const thing of e.members) { - prom1.push(this.getUser(thing.id)); - } - Promise.all(prom1); - for (const thing of e.members) { - if (!this.userMap.has(thing.id)) { - console.warn("Dumb server bug for this member", thing); - continue; - } - await Member.new(thing, this.lookingguild as Guild); - } - } - if (!typebox.rawString.startsWith(original)) return; - this.MDFineMentionGen(name, original, box, typebox); - } + this.lookingguild.searchMembers(8, name).then(async () => { + if (!typebox.rawString.startsWith(original)) return; + this.MDFineMentionGen(name, original, box, typebox); }); } } diff --git a/src/webpage/role.ts b/src/webpage/role.ts index e359723..852554a 100644 --- a/src/webpage/role.ts +++ b/src/webpage/role.ts @@ -129,6 +129,7 @@ import {Options} from "./settings.js"; import {createImg} from "./utils/utils.js"; import {Hover} from "./hover.js"; import {Emoji} from "./emoji.js"; +import {User} from "./user.js"; class PermissionToggle implements OptionsElement { readonly rolejson: { name: string; @@ -206,13 +207,13 @@ class PermissionToggle implements OptionsElement { } class RoleList extends Buttons { - permissions: [Role, Permissions][]; + permissions: [Role | User, Permissions][]; permission: Permissions; readonly guild: Guild; readonly channel: false | Channel; declare buttons: [string, string][]; readonly options: Options; - onchange: Function; + onchange: (id: string, perms: Permissions) => void; curid?: string; get info() { return this.guild.info; @@ -221,9 +222,9 @@ class RoleList extends Buttons { return this.guild.headers; } constructor( - permissions: [Role, Permissions][], + permissions: [Role | User, Permissions][], guild: Guild, - onchange: Function, + onchange: (id: string, perms: Permissions) => void, channel: false | Channel, ) { super(""); @@ -270,7 +271,7 @@ class RoleList extends Buttons { } this.redoButtons(); } - private croleUpdate(role: Role, perm: Permissions, added: boolean) { + private croleUpdate(role: Role | User, perm: Permissions, added: boolean) { if (added) { this.permissions.push([role, perm]); } else { @@ -400,17 +401,20 @@ class RoleList extends Buttons { secondary_color: colorInputs[1] ? Number("0x" + colors[1].substring(1)) : undefined, tertiary_color: colorInputs[2] ? Number("0x" + colors[2].substring(1)) : undefined, }; - - console.log(obj.color); }); }); } static channelrolemenu = this.ChannelRoleMenu(); static guildrolemenu = this.GuildRoleMenu(); private static ChannelRoleMenu() { - const menu = new Contextmenu("role settings"); + const menu = new Contextmenu("role settings"); menu.addButton( - () => I18n.role.remove(), + function (user) { + if (user instanceof User) { + return I18n.user.remove(); + } + return I18n.role.remove(); + }, function (role) { if (!this.channel) return; console.log(role); @@ -420,21 +424,24 @@ class RoleList extends Buttons { }); }, { - visable: (role) => role.id !== role.guild.id, + visable: function (role) { + //TODO, maybe this needs a check if the user is above/bellow the other user, hard to say + return role.id !== this.guild.id; + }, }, ); return menu; } - deleteRole(role: Role) { + deleteRole(role: Role | User) { const dio = new Dialog(I18n.role.confirmDelete(role.name)); const opt = dio.options.addOptions("", {ltr: true}); opt.addButtonInput("", I18n.yes(), async () => { opt.removeAll(); opt.addText(I18n.role.deleting()); - await fetch(role.info.api + "/guilds/" + role.guild.id + "/roles/" + role.id, { + await fetch(role.info.api + "/guilds/" + this.guild.id + "/roles/" + role.id, { method: "DELETE", - headers: role.headers, + headers: this.guild.headers, }); if (this.curid === role.id) { const id = this.permissions.filter((_) => _[0].id !== role.id)[0][0].id; @@ -463,13 +470,15 @@ class RoleList extends Buttons { } redoButtons() { this.buttons = []; - this.permissions.sort(([a], [b]) => b.position - a.position); + this.permissions.sort(([a], [b]) => { + if (b instanceof User) return 1; + if (a instanceof User) return -1; + return b.position - a.position; + }); for (const i of this.permissions) { this.buttons.push([i[0].name, i[0].id]); } - console.log("in here :P"); if (!this.buttonList) return; - console.log("in here :P"); const elms = Array.from(this.buttonList.children); const div = elms[0] as HTMLDivElement; const div2 = elms[1] as HTMLDivElement; @@ -548,7 +557,13 @@ class RoleList extends Buttons { } roles.push([role, [role.name]]); } - const search = new Search(roles); + const search = new Search(roles, async (str) => { + const users = (await this.guild.searchMembers(3, str)) + .map((_) => _.user) + .map((_) => [_.name, _] as [string, User]); + console.log(users); + return users; + }); const found = await search.find(box.left, box.top); @@ -590,16 +605,16 @@ class RoleList extends Buttons { this.buttonMap.set(thing[0], button); button.classList.add("SettingsButton"); button.textContent = thing[0]; - const role = this.guild.roleids.get(thing[1]); + const role = this.guild.roleids.get(thing[1]) || this.guild.localuser.userMap.get(thing[1]); if (role) { if (!this.channel) { - if (role.canManage()) { + if (role instanceof Role && role.canManage()) { this.buttonDragEvents(button, role); button.draggable = true; RoleList.guildrolemenu.bindContextmenu(button, this, role); } } else { - if (role.canManage()) { + if (role instanceof User || role.canManage()) { RoleList.channelrolemenu.bindContextmenu(button, this, role); } } @@ -639,7 +654,7 @@ class RoleList extends Buttons { return this.options.generateHTML(); } save() { - if (this.options.subOptions) return; + if (this.options.subOptions || !this.curid) return; this.onchange(this.curid, this.permission); } } diff --git a/src/webpage/search.ts b/src/webpage/search.ts index 910d118..40ac6cf 100644 --- a/src/webpage/search.ts +++ b/src/webpage/search.ts @@ -3,29 +3,61 @@ import {Contextmenu} from "./contextmenu.js"; class Search { options: Map; readonly keys: string[]; - constructor(options: [E, string[]][]) { + dynamic: (str: string) => Promise<[string, E][]> | [string, E][]; + constructor( + options: [E, string[]][], + dynamic: (str: string) => Promise<[string, E][]> | [string, E][] = async () => [], + ) { const map = options.flatMap((e) => { const val = e[1].map((f) => [f, e[0]]); return val as [string, E][]; }); this.options = new Map(map); this.keys = [...this.options.keys()]; + this.dynamic = dynamic; } generateList(str: string, max: number, res: (e: E) => void) { str = str.toLowerCase(); - const options = this.keys.filter((e) => { + let options = this.keys.filter((e) => { return e.toLowerCase().includes(str); }); + const dyn = this.dynamic(str); + const div = document.createElement("div"); div.classList.add("OptionList", "flexttb"); - for (const option of options.slice(0, max)) { - const hoption = document.createElement("span"); - hoption.textContent = option; - hoption.onclick = () => { - if (!this.options.has(option)) return; - res(this.options.get(option) as E); - }; - div.append(hoption); + + const cmap = new Map(); + const genDiv = () => { + div.innerHTML = ""; + for (const option of options.slice(0, max)) { + const hoption = document.createElement("span"); + hoption.textContent = option; + hoption.onclick = () => { + if (cmap.has(option)) { + res(cmap.get(option) as E); + } + if (!this.options.has(option)) return; + res(this.options.get(option) as E); + }; + div.append(hoption); + } + }; + if (dyn instanceof Promise) { + genDiv(); + dyn.then((arr) => { + options = [...options, ...arr.map(([_]) => _)]; + for (const thing of arr) { + cmap.set(...thing); + } + genDiv(); + }); + } else { + const arr = dyn; + options = [...options, ...arr.map(([_]) => _)]; + for (const thing of arr) { + cmap.set(...thing); + } + genDiv(); } return div; } diff --git a/src/webpage/user.ts b/src/webpage/user.ts index 04d5032..e99c98a 100644 --- a/src/webpage/user.ts +++ b/src/webpage/user.ts @@ -11,7 +11,7 @@ import {I18n} from "./i18n.js"; import {Direct} from "./direct.js"; import {Hover} from "./hover.js"; import {Dialog, Float} from "./settings.js"; -import {createImg, removeAni} from "./utils/utils.js"; +import {createImg, removeAni, safeImg} from "./utils/utils.js"; import {Permissions} from "./permissions.js"; class User extends SnowFlake { owner: Localuser; @@ -32,7 +32,7 @@ class User extends SnowFlake { hypotheticalbanner!: boolean; premium_since!: string; premium_type!: number; - theme_colors!: string; + theme_colors: [number, number] | null = null; badge_ids!: string[]; members: WeakMap> = new WeakMap(); status!: string; @@ -558,6 +558,16 @@ class User extends SnowFlake { } userupdate(json: userjson): void { + if (json.avatar !== this.avatar) { + Array.from(document.getElementsByClassName("userid:" + this.id)).forEach((element) => { + const img = element as safeImg; + if ("setSrcs" in element) { + img.setSrcs(this.getpfpsrc()); + } else { + console.warn("element didn't have setSrcs property"); + } + }); + } for (const key of Object.keys(json)) { if (key === "bio") { this.bio = new MarkDown(json[key], this.localuser); @@ -647,11 +657,6 @@ class User extends SnowFlake { changepfp(update: string | null): void { this.avatar = update; this.hypotheticalpfp = false; - //const src = this.getpfpsrc(); - Array.from(document.getElementsByClassName("userid:" + this.id)).forEach((_element) => { - //(element as HTMLImageElement).src = src; - //FIXME - }); } async block() { diff --git a/src/webpage/utils/utils.ts b/src/webpage/utils/utils.ts index 830a8cd..4befdee 100644 --- a/src/webpage/utils/utils.ts +++ b/src/webpage/utils/utils.ts @@ -674,11 +674,15 @@ export async function removeAni(elm: HTMLElement, time = 500) { ]); elm.remove(); } +export type safeImg = HTMLImageElement & { + setSrcs: (nsrc: string, nstaticsrc: string | void) => void; + isAnimated: () => Promise; +}; export function createImg( src: string | undefined, staticsrc: string | void, elm: HTMLElement | void, -) { +): safeImg { const settings = localStorage.getItem("gifSetting") || ("hover" as "hover") || "always" || "never"; const img = document.createElement("img"); diff --git a/translations/en.json b/translations/en.json index 15bab4c..df70cf6 100644 --- a/translations/en.json +++ b/translations/en.json @@ -670,7 +670,8 @@ "editServerProfile": "Edit guild profile", "instanceBan": "Instance ban", "confirmInstBan": "Are you sure you want to instance ban $1?", - "unban": "Unban $1" + "unban": "Unban $1", + "remove":"Remove user" }, "login": { "checking": "Checking Instance",