diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index 9635961..3a9462e 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -377,7 +377,10 @@ class Channel extends SnowFlake{ if(member.isAdmin()){ return true; } - for(const thing of member.roles){ + const roles=new Set(member.roles); + const everyone=this.guild.roles[this.guild.roles.length-1]; + roles.add(everyone) + for(const thing of roles){ const premission = this.permission_overwrites.get(thing.id); if(premission){ const perm = premission.getPermission(name); @@ -834,6 +837,7 @@ class Channel extends SnowFlake{ Channel.regenLoadingMessages(); loading.classList.add("loading"); this.rendertyping(); + this.localuser.getSidePannel(); await this.putmessages(); await prom; if(id !== Channel.genid){ diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index af0ceb9..ae2a9a8 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -34,6 +34,7 @@ class Guild extends SnowFlake{ html!: HTMLElement; emojis!: emojijson[]; large!: boolean; + members=new Set(); static contextmenu = new Contextmenu("guild menu"); static setupcontextmenu(){ Guild.contextmenu.addbutton("Copy Guild id", function(this: Guild){ diff --git a/src/webpage/index.html b/src/webpage/index.html index 67308f8..e13007e 100644 --- a/src/webpage/index.html +++ b/src/webpage/index.html @@ -56,27 +56,33 @@ Channel name -
-
-
-
-
-
-
-
-
-
- - \ No newline at end of file + diff --git a/src/webpage/jsontypes.ts b/src/webpage/jsontypes.ts index 26a1bcc..98a38f2 100644 --- a/src/webpage/jsontypes.ts +++ b/src/webpage/jsontypes.ts @@ -145,21 +145,22 @@ pronouns: string; badge_ids: string[]; }; type memberjson = { -index?: number; -id: string; -user: userjson | null; -guild_id: string; -guild: { -id: string; -} | null; -nick?: string; -roles: string[]; -joined_at: string; -premium_since: string; -deaf: boolean; -mute: boolean; -pending: boolean; -last_message_id?: boolean; //What??? + index?: number; + id: string; + user: userjson | null; + guild_id: string; + guild: { + id: string; + } | null; + presence?:presencejson + nick?: string; + roles: string[]; + joined_at: string; + premium_since: string; + deaf: boolean; + mute: boolean; + pending: boolean; + last_message_id?: boolean; //What??? }; type emojijson = { name: string; @@ -257,18 +258,18 @@ default_thread_rate_limit_per_user: number; position: number; }; type rolesjson = { -id: string; -guild_id: string; -color: number; -hoist: boolean; -managed: boolean; -mentionable: boolean; -name: string; -permissions: string; -position: number; -icon: string; -unicode_emoji: string; -flags: number; + id: string; + guild_id: string; + color: number; + hoist: boolean; + managed: boolean; + mentionable: boolean; + name: string; + permissions: string; + position: number; + icon: string; + unicode_emoji: string; + flags: number; }; type dirrectjson = { id: string; @@ -392,20 +393,20 @@ t: "MESSAGE_CREATE"; }; type wsjson = | { -op: 0; -d: any; -s: number; -t: -| "TYPING_START" -| "USER_UPDATE" -| "CHANNEL_UPDATE" -| "CHANNEL_CREATE" -| "CHANNEL_DELETE" -| "GUILD_DELETE" -| "GUILD_CREATE" -| "MESSAGE_REACTION_REMOVE_ALL" -| "MESSAGE_REACTION_REMOVE_EMOJI"; -} + op: 0; + d: any; + s: number; + t: + | "TYPING_START" + | "USER_UPDATE" + | "CHANNEL_UPDATE" + | "CHANNEL_CREATE" + | "CHANNEL_DELETE" + | "GUILD_DELETE" + | "GUILD_CREATE" + | "MESSAGE_REACTION_REMOVE_ALL" + | "MESSAGE_REACTION_REMOVE_EMOJI"; + } | { op: 0; t: "GUILD_MEMBERS_CHUNK"; @@ -469,7 +470,7 @@ guild_id: string; emoji: emojijson; }; s: 3; -}; +}|memberlistupdatejson; type memberChunk = { guild_id: string; nonce: string; @@ -479,6 +480,39 @@ chunk_index: number; chunk_count: number; not_found: string[]; }; + +type memberlistupdatejson={ + op: 0, + s: number, + t: "GUILD_MEMBER_LIST_UPDATE", + d: { + ops: [ + { + items:({ + group:{ + count:number, + id:string + } + }|{ + member:memberjson + })[] + op: "SYNC", + range: [ + number, + number + ] + } + ], + online_count: number, + member_count: number, + id: string, + guild_id: string, + groups: { + count: number, + id: string + }[] + } +} export{ readyjson, dirrectjson, @@ -498,4 +532,5 @@ export{ messageCreateJson, memberChunk, invitejson, + memberlistupdatejson }; diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 5739fab..f639d36 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -10,6 +10,7 @@ import{ guildjson, mainuserjson, memberjson, + memberlistupdatejson, messageCreateJson, presencejson, readyjson, @@ -20,6 +21,7 @@ import{ Member }from"./member.js"; import{ Form, FormError, Options, Settings }from"./settings.js"; import{ MarkDown }from"./markdown.js"; import { Bot } from "./bot.js"; +import { Role } from "./role.js"; const wsCodesRetry = new Set([4000, 4003, 4005, 4007, 4008, 4009]); @@ -479,7 +481,13 @@ class Localuser{ case"GUILD_MEMBERS_CHUNK": this.gotChunk(temp.d); break; + case"GUILD_MEMBER_LIST_UPDATE": + { + this.memberListUpdate(temp) + break; } + } + }else if(temp.op === 10){ if(!this.ws)return; console.log("heartbeat down"); @@ -518,6 +526,108 @@ class Localuser{ } return channel; // Add this line to return the 'channel' variable } + async memberListUpdate(list:memberlistupdatejson){ + const div=document.getElementById("sideDiv") as HTMLDivElement; + div.innerHTML=""; + const counts=new Map(); + const guild=this.lookingguild; + if(!guild) return; + const channel=this.channelfocus; + if(!channel) return; + for(const thing of list.d.ops[0].items){ + if("member" in thing){ + await Member.new(thing.member,guild); + }else{ + counts.set(thing.group.id,thing.group.count); + } + } + + const elms:Map=new Map([["offline",[]],["online",[]]]); + for(const role of guild.roles){ + console.log(guild.roles); + if(role.hoist){ + elms.set(role,[]); + } + } + const members=new Set(guild.members); + members.forEach((member)=>{ + if(!channel.hasPermission("VIEW_CHANNEL",member)){ + members.delete(member); + console.log(member) + return; + } + }) + for(const [role, list] of elms){ + members.forEach((member)=>{ + if(role === "offline"){ + if(member.user.status === "offline"){ + list.push(member); + members.delete(member); + } + return; + } + if(role !== "online"&&member.hasRole(role.id)){ + list.push(member); + members.delete(member); + } + }); + if(!list.length) continue; + list.sort((a,b)=>{ + return (a.name.toLowerCase()>b.name.toLowerCase())?1:-1; + }); + } + const online=[...members]; + online.sort((a,b)=>{ + return (a.name.toLowerCase()>b.name.toLowerCase())?1:-1; + }); + elms.set("online",online); + for(const [role, list] of elms){ + if(!list.length) continue; + const category=document.createElement("div"); + category.classList.add("memberList"); + let title=document.createElement("h3"); + if(role==="offline"){ + title.textContent="Offline"; + category.classList.add("offline"); + }else if(role==="online"){ + title.textContent="Online"; + }else{ + title.textContent=role.name; + } + category.append(title); + const membershtml=document.createElement("div"); + membershtml.classList.add("flexttb"); + + for(const member of list){ + const memberdiv=document.createElement("div"); + const pfp=member.user.buildpfp(); + const username=document.createElement("span"); + username.textContent=member.name; + member.bind(username) + member.user.bind(memberdiv,member.guild,false); + memberdiv.append(pfp,username); + memberdiv.classList.add("flexltr"); + membershtml.append(memberdiv); + } + category.append(membershtml); + div.prepend(category); + } + + console.log(elms); + } + async getSidePannel(){ + if(this.ws&&this.channelfocus){ + this.ws.send(JSON.stringify({ + d:{ + channels:{[this.channelfocus.id]:[[0,99]]}, + guild_id:this.channelfocus.guild.id + }, + op:14 + })) + }else{ + console.log("false? :3") + } + } gotoid: string | undefined; async goToChannel(id: string){ const channel = this.channelids.get(id); diff --git a/src/webpage/member.ts b/src/webpage/member.ts index ef2b3cf..b4bd79f 100644 --- a/src/webpage/member.ts +++ b/src/webpage/member.ts @@ -11,246 +11,254 @@ class Member extends SnowFlake{ user: User; roles: Role[] = []; nick!: string; -[key: string]: any; -private constructor(memberjson: memberjson, owner: Guild){ - super(memberjson.id); - this.owner = owner; - if(this.localuser.userMap.has(memberjson.id)){ - this.user = this.localuser.userMap.get(memberjson.id) as User; - }else if(memberjson.user){ - this.user = new User(memberjson.user, owner.localuser); - }else{ - throw new Error("Missing user object of this member"); - } - - for(const key of Object.keys(memberjson)){ - if(key === "guild" || key === "owner"){ - continue; - } - - if(key === "roles"){ - for(const strrole of memberjson.roles){ - const role = this.guild.roleids.get(strrole); - if(!role)continue; - this.roles.push(role); - } - continue; - } - (this as any)[key] = (memberjson as any)[key]; - } - if(this.localuser.userMap.has(this?.id)){ - this.user = this.localuser.userMap.get(this?.id) as User; - } - this.roles.sort((a, b)=>{ - return this.guild.roles.indexOf(a) - this.guild.roles.indexOf(b); - }); -} -get guild(){ - return this.owner; -} -get localuser(){ - return this.guild.localuser; -} -get info(){ - return this.owner.info; -} -static async new( - memberjson: memberjson, - owner: Guild -): Promise{ - let user: User; - if(owner.localuser.userMap.has(memberjson.id)){ - user = owner.localuser.userMap.get(memberjson.id) as User; - }else if(memberjson.user){ - user = new User(memberjson.user, owner.localuser); - }else{ - throw new Error("missing user object of this member"); - } - if(user.members.has(owner)){ - let memb = user.members.get(owner); - if(memb === undefined){ - memb = new Member(memberjson, owner); - user.members.set(owner, memb); - return memb; - }else if(memb instanceof Promise){ - return await memb; //I should do something else, though for now this is "good enough" + private constructor(memberjson: memberjson, owner: Guild){ + super(memberjson.id); + this.owner = owner; + if(this.localuser.userMap.has(memberjson.id)){ + this.user = this.localuser.userMap.get(memberjson.id) as User; + }else if(memberjson.user){ + this.user = new User(memberjson.user, owner.localuser); }else{ + throw new Error("Missing user object of this member"); + } + if(this.localuser.userMap.has(this?.id)){ + this.user = this.localuser.userMap.get(this?.id) as User; + } + for(const key of Object.keys(memberjson)){ + if(key === "guild" || key === "owner" || key === "user"){ + continue; + } + + if(key === "roles"){ + for(const strrole of memberjson.roles){ + const role = this.guild.roleids.get(strrole); + if(!role)continue; + this.roles.push(role); + } + continue; + } + if(key === "presence"){ + this.getPresence(memberjson.presence); + continue; + } + (this as any)[key] = (memberjson as any)[key]; + } + + this.roles.sort((a, b)=>{ + return this.guild.roles.indexOf(a) - this.guild.roles.indexOf(b); + }); + } + get guild(){ + return this.owner; + } + get localuser(){ + return this.guild.localuser; + } + get info(){ + return this.owner.info; + } + static async new( + memberjson: memberjson, + owner: Guild + ): Promise{ + let user: User; + if(owner.localuser.userMap.has(memberjson.id)){ + user = owner.localuser.userMap.get(memberjson.id) as User; + }else if(memberjson.user){ + user = new User(memberjson.user, owner.localuser); + }else{ + throw new Error("missing user object of this member"); + } + if(user.members.has(owner)){ + let memb = user.members.get(owner); + if(memb === undefined){ + memb = new Member(memberjson, owner); + user.members.set(owner, memb); + owner.members.add(memb); + return memb; + }else if(memb instanceof Promise){ + return await memb; //I should do something else, though for now this is "good enough" + }else{ + if(memberjson.presence){ + memb.getPresence(memberjson.presence); + } + return memb; + } + }else{ + const memb = new Member(memberjson, owner); + user.members.set(owner, memb); + owner.members.add(memb); return memb; } - }else{ - const memb = new Member(memberjson, owner); - user.members.set(owner, memb); - return memb; } -} -static async resolveMember( - user: User, - guild: Guild -): Promise{ - const maybe = user.members.get(guild); - if(!user.members.has(guild)){ - const membpromise = guild.localuser.resolvemember(user.id, guild.id); - const promise = new Promise(async res=>{ - const membjson = await membpromise; - if(membjson === undefined){ - return res(undefined); - }else{ - const member = new Member(membjson, guild); - const map = guild.localuser.presences; - member.getPresence(map.get(member.id)); - map.delete(member.id); - res(member); - return member; + static async resolveMember( + user: User, + guild: Guild + ): Promise{ + const maybe = user.members.get(guild); + if(!user.members.has(guild)){ + const membpromise = guild.localuser.resolvemember(user.id, guild.id); + const promise = new Promise(async res=>{ + const membjson = await membpromise; + if(membjson === undefined){ + return res(undefined); + }else{ + const member = new Member(membjson, guild); + const map = guild.localuser.presences; + member.getPresence(map.get(member.id)); + map.delete(member.id); + res(member); + return member; + } + }); + user.members.set(guild, promise); + } + if(maybe instanceof Promise){ + return await maybe; + }else{ + return maybe; + } + } + public getPresence(presence: presencejson | undefined){ + this.user.getPresence(presence); + } + /** + * @todo + */ + highInfo(){ + fetch( + this.info.api + + "/users/" + + this.id + + "/profile?with_mutual_guilds=true&with_mutual_friends_count=true&guild_id=" + + this.guild.id, + { headers: this.guild.headers } + ); + } + hasRole(ID: string){ + console.log(this.roles, ID); + for(const thing of this.roles){ + if(thing.id === ID){ + return true; } + } + return false; + } + getColor(){ + for(const thing of this.roles){ + const color = thing.getColor(); + if(color){ + return color; + } + } + return""; + } + isAdmin(){ + for(const role of this.roles){ + if(role.permissions.getPermission("ADMINISTRATOR")){ + return true; + } + } + return this.guild.properties.owner_id === this.user.id; + } + bind(html: HTMLElement){ + if(html.tagName === "SPAN"){ + if(!this){ + return; + } + /* + if(this.error){ + + } + */ + html.style.color = this.getColor(); + } + + //this.profileclick(html); + } + profileclick(/* html: HTMLElement */){ + //to be implemented + } + get name(){ + return this.nick || this.user.username; + } + kick(){ + let reason = ""; + const menu = new Dialog([ + "vdiv", + ["title", "Kick " + this.name + " from " + this.guild.properties.name], + [ + "textbox", + "Reason:", + "", + function(e: Event){ + reason = (e.target as HTMLInputElement).value; + }, + ], + [ + "button", + "", + "submit", + ()=>{ + this.kickAPI(reason); + menu.hide(); + }, + ], + ]); + menu.show(); + } + kickAPI(reason: string){ + const headers = structuredClone(this.guild.headers); + (headers as any)["x-audit-log-reason"] = reason; + fetch(`${this.info.api}/guilds/${this.guild.id}/members/${this.id}`, { + method: "DELETE", + headers, }); - user.members.set(guild, promise); } - if(maybe instanceof Promise){ - return await maybe; - }else{ - return maybe; + ban(){ + let reason = ""; + const menu = new Dialog([ + "vdiv", + ["title", "Ban " + this.name + " from " + this.guild.properties.name], + [ + "textbox", + "Reason:", + "", + function(e: Event){ + reason = (e.target as HTMLInputElement).value; + }, + ], + [ + "button", + "", + "submit", + ()=>{ + this.banAPI(reason); + menu.hide(); + }, + ], + ]); + menu.show(); } -} -public getPresence(presence: presencejson | undefined){ - this.user.getPresence(presence); -} -/** - * @todo - */ -highInfo(){ - fetch( - this.info.api + - "/users/" + - this.id + - "/profile?with_mutual_guilds=true&with_mutual_friends_count=true&guild_id=" + - this.guild.id, - { headers: this.guild.headers } - ); -} -hasRole(ID: string){ - console.log(this.roles, ID); - for(const thing of this.roles){ - if(thing.id === ID){ + banAPI(reason: string){ + const headers = structuredClone(this.guild.headers); + (headers as any)["x-audit-log-reason"] = reason; + fetch(`${this.info.api}/guilds/${this.guild.id}/bans/${this.id}`, { + method: "PUT", + headers, + }); + } + hasPermission(name: string): boolean{ + if(this.isAdmin()){ return true; } - } - return false; -} -getColor(){ - for(const thing of this.roles){ - const color = thing.getColor(); - if(color){ - return color; - } - } - return""; -} -isAdmin(){ - for(const role of this.roles){ - if(role.permissions.getPermission("ADMINISTRATOR")){ - return true; - } - } - return this.guild.properties.owner_id === this.user.id; -} -bind(html: HTMLElement){ - if(html.tagName === "SPAN"){ - if(!this){ - return; - } - /* - if(this.error){ - + for(const thing of this.roles){ + if(thing.permissions.getPermission(name)){ + return true; } - */ - html.style.color = this.getColor(); - } - - //this.profileclick(html); -} -profileclick(/* html: HTMLElement */){ - //to be implemented -} -get name(){ - return this.nick || this.user.username; -} -kick(){ - let reason = ""; - const menu = new Dialog([ - "vdiv", - ["title", "Kick " + this.name + " from " + this.guild.properties.name], - [ - "textbox", - "Reason:", - "", - function(e: Event){ - reason = (e.target as HTMLInputElement).value; - }, - ], - [ - "button", - "", - "submit", - ()=>{ - this.kickAPI(reason); - menu.hide(); - }, - ], - ]); - menu.show(); -} -kickAPI(reason: string){ - const headers = structuredClone(this.guild.headers); - (headers as any)["x-audit-log-reason"] = reason; - fetch(`${this.info.api}/guilds/${this.guild.id}/members/${this.id}`, { - method: "DELETE", - headers, - }); -} -ban(){ - let reason = ""; - const menu = new Dialog([ - "vdiv", - ["title", "Ban " + this.name + " from " + this.guild.properties.name], - [ - "textbox", - "Reason:", - "", - function(e: Event){ - reason = (e.target as HTMLInputElement).value; - }, - ], - [ - "button", - "", - "submit", - ()=>{ - this.banAPI(reason); - menu.hide(); - }, - ], - ]); - menu.show(); -} -banAPI(reason: string){ - const headers = structuredClone(this.guild.headers); - (headers as any)["x-audit-log-reason"] = reason; - fetch(`${this.info.api}/guilds/${this.guild.id}/bans/${this.id}`, { - method: "PUT", - headers, - }); -} -hasPermission(name: string): boolean{ - if(this.isAdmin()){ - return true; - } - for(const thing of this.roles){ - if(thing.permissions.getPermission(name)){ - return true; } + return false; } - return false; -} } export{ Member }; diff --git a/src/webpage/style.css b/src/webpage/style.css index b83659d..e3bb30d 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -1379,6 +1379,7 @@ span { width: 100%; height: 100dvh; align-content: space-around; + align-items: stretch; } .userflex{ display:flex; @@ -2219,3 +2220,51 @@ form div{ .mentionMD:hover{ background:color-mix(in srgb,var(--mention-md-bg),white 10%); } +#sideDiv{ + flex-grow:0; + flex-shrink:0; + width:2.5in; + box-shadow: -.02in 0 .04in black; + &:empty{ + width:0in; + } +; + align-items: stretch; + /* overflow-x: hidden; */ + overflow-y: unset; + scrollbar-width: none; +} + +.memberList{ + padding:.05in; + >div{ + width: 100%; + >div{ + width:100%; + padding:.04in .02in; + margin: .01in 0.0in; + border-radius:.1in; + &:hover{ + background:var(--message-bg-hover); + } + flex-shrink: 1; + cursor: pointer; + box-sizing:border-box; + gap:.05in; + align-items: center; + } + img{ + width:40px; + height:40px; + } + box-sizing: border-box; + } +} +.offline{ + h3{ + color:var(--primary-text) + } + >div{ + opacity:.4; + } +} \ No newline at end of file diff --git a/src/webpage/user.ts b/src/webpage/user.ts index 661f48e..8cddb12 100644 --- a/src/webpage/user.ts +++ b/src/webpage/user.ts @@ -27,7 +27,7 @@ class User extends SnowFlake{ badge_ids!: string[]; members: WeakMap> = new WeakMap(); - private status!: string; + status!: string; resolving: false | Promise = false; constructor(userjson: userjson, owner: Localuser, dontclone = false){