From c8e3125c5d2908304eea94951f590a6a7a58f360 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Tue, 29 Oct 2024 20:30:24 -0500 Subject: [PATCH] roles --- src/webpage/channel.ts | 159 +++++++++------------- src/webpage/guild.ts | 57 +++++++- src/webpage/jsontypes.ts | 23 +++- src/webpage/localuser.ts | 283 +++++++++++++++++++++------------------ src/webpage/member.ts | 44 ++++++ src/webpage/role.ts | 267 ++++++++++++++++++++++++++++++++++-- src/webpage/search.ts | 72 ++++++++++ src/webpage/settings.ts | 23 +++- src/webpage/style.css | 27 +++- src/webpage/user.ts | 54 ++++++++ 10 files changed, 759 insertions(+), 250 deletions(-) create mode 100644 src/webpage/search.ts diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index 1910540..f830bf6 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -61,7 +61,7 @@ class Channel extends SnowFlake{ this.readbottom(); }); - this.contextmenu.addbutton("Settings[temp]", function(this: Channel){ + this.contextmenu.addbutton("Settings", function(this: Channel){ this.generateSettings(); }); @@ -76,17 +76,6 @@ class Channel extends SnowFlake{ } ); - this.contextmenu.addbutton( - "Edit channel", - function(this: Channel){ - this.editChannel(); - }, - null, - function(){ - return this.isAdmin(); - } - ); - this.contextmenu.addbutton( "Make invite", function(this: Channel){ @@ -205,15 +194,33 @@ class Channel extends SnowFlake{ generateSettings(){ this.sortPerms(); const settings = new Settings("Settings for " + this.name); - - const s1 = settings.addButton("roles"); - + { + const gensettings=settings.addButton("Settings"); + const form=gensettings.addForm("",()=>{},{ + fetchURL:this.info.api + "/channels/" + this.id, + method: "PATCH", + headers: this.headers, + }); + form.addTextInput("Name:","name",{initText:this.name}); + form.addMDInput("Topic:","topic",{initText:this.topic}); + form.addCheckboxInput("NSFW:","nsfw",{initState:this.nsfw}); + if(this.type!==4){ + const options=["voice", "text", "announcement"]; + form.addSelect("Type:","type",options,{ + defaultIndex:options.indexOf({0:"text", 2:"voice", 5:"announcement", 4:"category" }[this.type] as string) + }) + } + form.addPreprocessor((obj:any)=>{ + obj.type={text: 0, voice: 2, announcement: 5, category: 4 }[obj.type as string] + }) + } + const s1 = settings.addButton("Permisions"); s1.options.push( new RoleList( this.permission_overwritesar, this.guild, this.updateRolePermissions.bind(this), - true + this ) ); settings.show(); @@ -739,68 +746,6 @@ class Channel extends SnowFlake{ }), }); } - editChannel(){ - let name = this.name; - let topic = this.topic; - let nsfw = this.nsfw; - const thisid = this.id; - const thistype = this.type; - const full = new Dialog([ - "hdiv", - [ - "vdiv", - [ - "textbox", - "Channel name:", - this.name, - function(this: HTMLInputElement){ - name = this.value; - }, - ], - [ - "mdbox", - "Channel topic:", - this.topic, - function(this: HTMLTextAreaElement){ - topic = this.value; - }, - ], - [ - "checkbox", - "NSFW Channel", - this.nsfw, - function(this: HTMLInputElement){ - nsfw = this.checked; - }, - ], - [ - "button", - "", - "submit", - ()=>{ - fetch(this.info.api + "/channels/" + thisid, { - method: "PATCH", - headers: this.headers, - body: JSON.stringify({ - name, - type: thistype, - topic, - bitrate: 64000, - user_limit: 0, - nsfw, - flags: 0, - rate_limit_per_user: 0, - }), - }); - console.log(full); - full.hide(); - }, - ], - ], - ]); - full.show(); - console.log(full); - } deleteChannel(){ fetch(this.info.api + "/channels/" + this.id, { method: "DELETE", @@ -1231,12 +1176,11 @@ class Channel extends SnowFlake{ this.children = []; this.guild_id = json.guild_id; + const oldover=this.permission_overwrites; this.permission_overwrites = new Map(); + this.permission_overwritesar=[]; for(const thing of json.permission_overwrites){ - if( - thing.id === "1182819038095799904" || - thing.id === "1182820803700625444" - ){ + if(thing.id === "1182819038095799904" || thing.id === "1182820803700625444"){ continue; } this.permission_overwrites.set( @@ -1251,9 +1195,26 @@ class Channel extends SnowFlake{ } } } + const nchange=[...new Set().union(oldover).difference(this.permission_overwrites)]; + const pchange=[...new Set().union(this.permission_overwrites).difference(oldover)]; + for(const thing of nchange){ + const role=this.guild.roleids.get(thing); + if(role){ + this.croleUpdate(role,new Permissions("0"),false) + } + } + for(const thing of pchange){ + const role=this.guild.roleids.get(thing); + const perms=this.permission_overwrites.get(thing); + if(role&&perms){ + this.croleUpdate(role,perms,true); + } + } + console.log(pchange,nchange); this.topic = json.topic; this.nsfw = json.nsfw; } + croleUpdate:(role:Role,perm:Permissions,added:boolean)=>unknown=()=>{}; typingstart(){ if(this.typing > Date.now()){ return; @@ -1366,16 +1327,14 @@ class Channel extends SnowFlake{ return; } if( - this.localuser.lookingguild?.prevchannel === this && - document.hasFocus() + this.localuser.lookingguild?.prevchannel === this && document.hasFocus() ){ return; } if(this.notification === "all"){ this.notify(messagez); }else if( - this.notification === "mentions" && - messagez.mentionsuser(this.localuser.user) + this.notification === "mentions" && messagez.mentionsuser(this.localuser.user) ){ this.notify(messagez); } @@ -1445,20 +1404,22 @@ class Channel extends SnowFlake{ if(permission){ permission.allow = perms.allow; permission.deny = perms.deny; - await fetch( - this.info.api + "/channels/" + this.id + "/permissions/" + id, - { - method: "PUT", - headers: this.headers, - body: JSON.stringify({ - allow: permission.allow.toString(), - deny: permission.deny.toString(), - id, - type: 0, - }), - } - ); + }else{ + //this.permission_overwrites.set(id,perms); } + await fetch( + this.info.api + "/channels/" + this.id + "/permissions/" + id, + { + method: "PUT", + headers: this.headers, + body: JSON.stringify({ + allow: perms.allow.toString(), + deny: perms.deny.toString(), + id, + type: 0, + }), + } + ); } } Channel.setupcontextmenu(); diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index e3ab317..22b16e7 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -13,6 +13,7 @@ import{ emojijson, memberjson, invitejson, + rolesjson, }from"./jsontypes.js"; import{ User }from"./user.js"; @@ -114,16 +115,67 @@ class Guild extends SnowFlake{ } form.addTextInput("Region:", "region", { initText: region }); } - const s1 = settings.addButton("roles"); + const s1 = settings.addButton("Roles"); const permlist: [Role, Permissions][] = []; for(const thing of this.roles){ permlist.push([thing, thing.permissions]); } s1.options.push( - new RoleList(permlist, this, this.updateRolePermissions.bind(this)) + new RoleList(permlist, this, this.updateRolePermissions.bind(this),false) ); settings.show(); } + roleUpdate:(role:Role,added:-1|0|1)=>unknown=()=>{}; + sortRoles(){ + this.roles.sort((a,b)=>(b.position-a.position)); + } + async recalcRoles(){ + let position=this.roles.length; + const map=this.roles.map(_=>{ + position--; + return {id:_.id,position}; + }) + await fetch(this.info.api+"/guilds/"+this.id+"/roles",{ + method:"PATCH", + body:JSON.stringify(map), + headers:this.headers + }) + } + newRole(rolej:rolesjson){ + const role=new Role(rolej,this); + this.roles.push(role); + this.roleids.set(role.id, role); + this.sortRoles(); + this.roleUpdate(role,1); + } + updateRole(rolej:rolesjson){ + const role=this.roleids.get(rolej.id) as Role; + role.newJson(rolej); + this.roleUpdate(role,0); + } + memberupdate(json:memberjson){ + let member:undefined|Member=undefined; + for(const thing of this.members){ + if(thing.id===json.id){ + member=thing; + break; + } + } + + if(!member) return; + member.update(json); + if(member===this.member){ + console.log(member); + this.loadGuild(); + } + } + deleteRole(id:string){ + const role = this.roleids.get(id); + if(!role) return; + this.roleids.delete(id); + this.roles.splice(this.roles.indexOf(role),1); + this.roleUpdate(role,-1); + } constructor( json: guildjson | -1, owner: Localuser, @@ -153,6 +205,7 @@ class Guild extends SnowFlake{ this.roles.push(roleh); this.roleids.set(roleh.id, roleh); } + this.sortRoles(); if(member instanceof User){ Member.resolveMember(member, this).then(_=>{ if(_){ diff --git a/src/webpage/jsontypes.ts b/src/webpage/jsontypes.ts index 2ba11e5..9e38b13 100644 --- a/src/webpage/jsontypes.ts +++ b/src/webpage/jsontypes.ts @@ -478,7 +478,28 @@ roleCreate | { guild_id: string; emoji: emojijson; }; - s: 3; + s: number; +}|{ + op: 0, + t: "GUILD_ROLE_UPDATE", + d: { + guild_id: string, + role: rolesjson + }, + "s": number +}|{ + op: 0, + t: "GUILD_ROLE_DELETE", + d: { + guild_id: string, + role_id: string + }, + s:number +}|{ + op: 0, + t: "GUILD_MEMBER_UPDATE", + d: memberjson, + "s": 3 }|memberlistupdatejson|voiceupdate|voiceserverupdate; diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 176bb3f..36ff5f2 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -354,149 +354,174 @@ class Localuser{ if(temp.s)this.lastSequence = temp.s; if(temp.op == 0){ switch(temp.t){ - case"MESSAGE_CREATE": - if(this.initialized){ - this.messageCreate(temp); - } - break; - case"MESSAGE_DELETE": { - temp.d.guild_id ??= "@me"; - const channel = this.channelids.get(temp.d.channel_id); - if(!channel)break; - const message = channel.messages.get(temp.d.id); - if(!message)break; - message.deleteEvent(); - break; - } - case"READY": - await this.gottenReady(temp as readyjson); - break; - case"MESSAGE_UPDATE": { - temp.d.guild_id ??= "@me"; - const channel = this.channelids.get(temp.d.channel_id); - if(!channel)break; - const message = channel.messages.get(temp.d.id); - if(!message)break; - message.giveData(temp.d); - break; - } - case"TYPING_START": - if(this.initialized){ - this.typingStart(temp); - } - break; - case"USER_UPDATE": - if(this.initialized){ - const users = this.userMap.get(temp.d.id); - if(users){ - users.userupdate(temp.d); + case"MESSAGE_CREATE": + if(this.initialized){ + this.messageCreate(temp); } - } - break; - case"CHANNEL_UPDATE": - if(this.initialized){ - this.updateChannel(temp.d); - } - break; - case"CHANNEL_CREATE": - if(this.initialized){ - this.createChannel(temp.d); - } - break; - case"CHANNEL_DELETE": - if(this.initialized){ - this.delChannel(temp.d); - } - break; - case"GUILD_DELETE": { - const guildy = this.guildids.get(temp.d.id); - if(guildy){ - this.guildids.delete(temp.d.id); - this.guilds.splice(this.guilds.indexOf(guildy), 1); - guildy.html.remove(); - } - break; - } - case"GUILD_CREATE": { - const guildy = new Guild(temp.d, this, this.user); - this.guilds.push(guildy); - this.guildids.set(guildy.id, guildy); - (document.getElementById("servers") as HTMLDivElement).insertBefore( - guildy.generateGuildIcon(), - document.getElementById("bottomseparator") - ); - break; - } - case"MESSAGE_REACTION_ADD": - { + break; + case"MESSAGE_DELETE": { temp.d.guild_id ??= "@me"; - const guild = this.guildids.get(temp.d.guild_id); - if(!guild)break; const channel = this.channelids.get(temp.d.channel_id); if(!channel)break; - const message = channel.messages.get(temp.d.message_id); + const message = channel.messages.get(temp.d.id); if(!message)break; - let thing: Member | { id: string }; - if(temp.d.member){ - thing = (await Member.new(temp.d.member, guild)) as Member; - }else{ - thing = { id: temp.d.user_id }; + message.deleteEvent(); + break; + } + case"READY": + await this.gottenReady(temp as readyjson); + break; + case"MESSAGE_UPDATE": { + temp.d.guild_id ??= "@me"; + const channel = this.channelids.get(temp.d.channel_id); + if(!channel)break; + const message = channel.messages.get(temp.d.id); + if(!message)break; + message.giveData(temp.d); + break; + } + case"TYPING_START": + if(this.initialized){ + this.typingStart(temp); } - message.reactionAdd(temp.d.emoji, thing); + break; + case"USER_UPDATE": + if(this.initialized){ + const users = this.userMap.get(temp.d.id); + if(users){ + users.userupdate(temp.d); + } + } + break; + case"CHANNEL_UPDATE": + if(this.initialized){ + this.updateChannel(temp.d); + } + break; + case"CHANNEL_CREATE": + if(this.initialized){ + this.createChannel(temp.d); + } + break; + case"CHANNEL_DELETE": + if(this.initialized){ + this.delChannel(temp.d); + } + break; + case"GUILD_DELETE": { + const guildy = this.guildids.get(temp.d.id); + if(guildy){ + this.guildids.delete(temp.d.id); + this.guilds.splice(this.guilds.indexOf(guildy), 1); + guildy.html.remove(); + } + break; } - break; - case"MESSAGE_REACTION_REMOVE": + case"GUILD_CREATE": { + const guildy = new Guild(temp.d, this, this.user); + this.guilds.push(guildy); + this.guildids.set(guildy.id, guildy); + (document.getElementById("servers") as HTMLDivElement).insertBefore( + guildy.generateGuildIcon(), + document.getElementById("bottomseparator") + ); + break; + } + case"MESSAGE_REACTION_ADD": + { + temp.d.guild_id ??= "@me"; + const guild = this.guildids.get(temp.d.guild_id); + if(!guild)break; + const channel = this.channelids.get(temp.d.channel_id); + if(!channel)break; + const message = channel.messages.get(temp.d.message_id); + if(!message)break; + let thing: Member | { id: string }; + if(temp.d.member){ + thing = (await Member.new(temp.d.member, guild)) as Member; + }else{ + thing = { id: temp.d.user_id }; + } + message.reactionAdd(temp.d.emoji, thing); + } + break; + case"MESSAGE_REACTION_REMOVE": + { + temp.d.guild_id ??= "@me"; + const channel = this.channelids.get(temp.d.channel_id); + if(!channel)break; + const message = channel.messages.get(temp.d.message_id); + if(!message)break; + message.reactionRemove(temp.d.emoji, temp.d.user_id); + } + break; + case"MESSAGE_REACTION_REMOVE_ALL": + { + temp.d.guild_id ??= "@me"; + const channel = this.channelids.get(temp.d.channel_id); + if(!channel)break; + const message = channel.messages.get(temp.d.message_id); + if(!message)break; + message.reactionRemoveAll(); + } + break; + case"MESSAGE_REACTION_REMOVE_EMOJI": + { + temp.d.guild_id ??= "@me"; + const channel = this.channelids.get(temp.d.channel_id); + if(!channel)break; + const message = channel.messages.get(temp.d.message_id); + if(!message)break; + message.reactionRemoveEmoji(temp.d.emoji); + } + break; + case"GUILD_MEMBERS_CHUNK": + this.gotChunk(temp.d); + break; + case"GUILD_MEMBER_LIST_UPDATE": { - temp.d.guild_id ??= "@me"; - const channel = this.channelids.get(temp.d.channel_id); - if(!channel)break; - const message = channel.messages.get(temp.d.message_id); - if(!message)break; - message.reactionRemove(temp.d.emoji, temp.d.user_id); - } - break; - case"MESSAGE_REACTION_REMOVE_ALL": - { - temp.d.guild_id ??= "@me"; - const channel = this.channelids.get(temp.d.channel_id); - if(!channel)break; - const message = channel.messages.get(temp.d.message_id); - if(!message)break; - message.reactionRemoveAll(); - } - break; - case"MESSAGE_REACTION_REMOVE_EMOJI": - { - temp.d.guild_id ??= "@me"; - const channel = this.channelids.get(temp.d.channel_id); - if(!channel)break; - const message = channel.messages.get(temp.d.message_id); - if(!message)break; - message.reactionRemoveEmoji(temp.d.emoji); - } - break; - case"GUILD_MEMBERS_CHUNK": - this.gotChunk(temp.d); - break; - case"GUILD_MEMBER_LIST_UPDATE": - { - this.memberListUpdate(temp) - break; - } - case "VOICE_STATE_UPDATE": - if(this.voiceFactory){ - this.voiceFactory.voiceStateUpdate(temp) + this.memberListUpdate(temp) + break; } + case "VOICE_STATE_UPDATE": + if(this.voiceFactory){ + this.voiceFactory.voiceStateUpdate(temp) + } - break; - case "VOICE_SERVER_UPDATE": - if(this.voiceFactory){ - this.voiceFactory.voiceServerUpdate(temp) + break; + case "VOICE_SERVER_UPDATE": + if(this.voiceFactory){ + this.voiceFactory.voiceServerUpdate(temp) + } + break; + case "GUILD_ROLE_CREATE":{ + const guild=this.guildids.get(temp.d.guild_id); + if(!guild) break; + guild.newRole(temp.d.role); + break; + } + case "GUILD_ROLE_UPDATE":{ + const guild=this.guildids.get(temp.d.guild_id); + if(!guild) break; + guild.updateRole(temp.d.role); + break; + } + case "GUILD_ROLE_DELETE":{ + const guild=this.guildids.get(temp.d.guild_id); + if(!guild) break; + guild.deleteRole(temp.d.role_id); + break; + } + case "GUILD_MEMBER_UPDATE":{ + const guild=this.guildids.get(temp.d.guild_id); + if(!guild) break; + guild.memberupdate(temp.d) + break } - break; } + }else if(temp.op === 10){ if(!this.ws)return; console.log("heartbeat down"); diff --git a/src/webpage/member.ts b/src/webpage/member.ts index b4bd79f..4c49ecf 100644 --- a/src/webpage/member.ts +++ b/src/webpage/member.ts @@ -49,6 +49,32 @@ class Member extends SnowFlake{ return this.guild.roles.indexOf(a) - this.guild.roles.indexOf(b); }); } + update(memberjson: memberjson){ + this.roles=[]; + 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; } @@ -241,6 +267,24 @@ class Member extends SnowFlake{ ]); menu.show(); } + addRole(role:Role){ + const roles=this.roles.map(_=>_.id) + roles.push(role.id); + fetch(this.info.api+"/guilds/"+this.guild.id+"/members/"+this.id,{ + method:"PATCH", + headers:this.guild.headers, + body:JSON.stringify({roles}) + }) + } + removeRole(role:Role){ + let roles=this.roles.map(_=>_.id) + roles=roles.filter(_=>_!==role.id); + fetch(this.info.api+"/guilds/"+this.guild.id+"/members/"+this.id,{ + method:"PATCH", + headers:this.guild.headers, + body:JSON.stringify({roles}) + }) + } banAPI(reason: string){ const headers = structuredClone(this.guild.headers); (headers as any)["x-audit-log-reason"] = reason; diff --git a/src/webpage/role.ts b/src/webpage/role.ts index 8add9b6..8618b1c 100644 --- a/src/webpage/role.ts +++ b/src/webpage/role.ts @@ -3,6 +3,7 @@ import{ Localuser }from"./localuser.js"; import{ Guild }from"./guild.js"; import{ SnowFlake }from"./snowflake.js"; import{ rolesjson }from"./jsontypes.js"; +import{ Search }from"./search.js"; class Role extends SnowFlake{ permissions: Permissions; owner: Guild; @@ -13,6 +14,7 @@ class Role extends SnowFlake{ icon!: string; mentionable!: boolean; unicode_emoji!: string; + position!:number; headers: Guild["headers"]; constructor(json: rolesjson, owner: Guild){ super(json.id); @@ -27,6 +29,15 @@ class Role extends SnowFlake{ this.permissions = new Permissions(json.permissions); this.owner = owner; } + newJson(json: rolesjson){ + for(const thing of Object.keys(json)){ + if(thing === "id"||thing==="permissions"){ + continue; + } + (this as any)[thing] = (json as any)[thing]; + } + this.permissions.allow=BigInt(json.permissions); + } get guild(): Guild{ return this.owner; } @@ -39,6 +50,14 @@ class Role extends SnowFlake{ } return`#${this.color.toString(16)}`; } + canManage(){ + if(this.guild.member.hasPermission("MANAGE_ROLES")){ + let max=-Infinity; + this.guild.member.roles.forEach(r=>max=Math.max(max,r.position)) + return this.position<=max||this.guild.properties.owner_id===this.guild.member.id; + } + return false; + } } export{ Role }; import{ Options }from"./settings.js"; @@ -121,22 +140,25 @@ class PermissionToggle implements OptionsElement{ submit(){} } import{ OptionsElement, Buttons }from"./settings.js"; +import { Contextmenu } from "./contextmenu.js"; +import { Channel } from "./channel.js"; class RoleList extends Buttons{ - readonly permissions: [Role, Permissions][]; + permissions: [Role, Permissions][]; permission: Permissions; readonly guild: Guild; - readonly channel: boolean; - declare readonly buttons: [string, string][]; + readonly channel: false|Channel; + declare buttons: [string, string][]; readonly options: Options; onchange: Function; - curid!: string; - constructor( - permissions: [Role, Permissions][], - guild: Guild, - onchange: Function, - channel = false - ){ - super("Roles"); + curid?: string; + get info(){ + return this.guild.info; + } + get headers(){ + return this.guild.headers; + } + constructor(permissions:[Role, Permissions][], guild:Guild, onchange:Function, channel:false|Channel){ + super(""); this.guild = guild; this.permissions = permissions; this.channel = channel; @@ -147,16 +169,237 @@ class RoleList extends Buttons{ }else{ this.permission = new Permissions("0"); } + this.makeguildmenus(options); for(const thing of Permissions.info){ options.options.push( new PermissionToggle(thing, this.permission, options) ); } for(const i of permissions){ - console.log(i); this.buttons.push([i[0].name, i[0].id]); } this.options = options; + guild.roleUpdate=this.groleUpdate.bind(this); + if(channel){ + channel.croleUpdate=this.croleUpdate.bind(this); + } + } + private groleUpdate(role:Role,added:1|0|-1){ + if(!this.channel){ + if(added===1){ + this.permissions.push([role,role.permissions]); + } + } + if(added===-1){ + this.permissions=this.permissions.filter(r=>r[0]!==role); + } + this.redoButtons(); + } + private croleUpdate(role:Role,perm:Permissions,added:boolean){ + if(added){ + this.permissions.push([role,perm]) + }else{ + this.permissions=this.permissions.filter(r=>r[0]!==role); + } + this.redoButtons(); + } + makeguildmenus(option:Options){ + option.addButtonInput("","Display settings",()=>{ + const role=this.guild.roleids.get(this.curid as string); + if(!role) return; + const form=option.addSubForm("Display settings",()=>{},{ + fetchURL:this.info.api+"/guilds/"+this.guild.id+"/roles/"+this.curid, + method:"PATCH", + headers:this.headers + }); + form.addTextInput("Role Name:","name",{ + initText:role.name + }); + form.addCheckboxInput("Hoisted:","hoist",{ + initState:role.hoist + }); + form.addCheckboxInput("Allow anyone to ping this role:","mentionable",{ + initState:role.mentionable + }); + const color="#"+role.color.toString(16).padStart(6,"0"); + form.addColorInput("Color","color",{ + initColor:color + }); + form.addPreprocessor((obj:any)=>{ + obj.color=Number("0x"+obj.color.substring(1)); + console.log(obj.color); + }) + }) + } + static channelrolemenu=this.ChannelRoleMenu(); + static guildrolemenu=this.GuildRoleMenu(); + private static ChannelRoleMenu(){ + const menu=new Contextmenu("role settings"); + menu.addbutton("Remove role",function(role){ + if(!this.channel) return; + console.log(role); + fetch(this.info.api+"/channels/"+this.channel.id+"/permissions/"+role.id,{ + method:"DELETE", + headers:this.headers + }) + },null); + return menu; + } + private static GuildRoleMenu(){ + const menu=new Contextmenu("role settings"); + menu.addbutton("Delete Role",function(role){ + if(!confirm("Are you sure you want to delete "+role.name+"?")) return; + console.log(role); + fetch(this.info.api+"/guilds/"+this.guild.id+"/roles/"+role.id,{ + method:"DELETE", + headers:this.headers + }) + },null); + return menu; + } + redoButtons(){ + this.buttons=[]; + this.permissions.sort(([a],[b])=>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; + console.log(div); + div.innerHTML=""; + div.append(this.buttonListGen(div2));//not actually sure why the html is needed + } + buttonMap=new WeakMap(); + dragged?:HTMLButtonElement; + buttonDragEvents(button:HTMLButtonElement,role:Role){ + this.buttonMap.set(button,role); + button.addEventListener("dragstart", e=>{ + this.dragged = button; + e.stopImmediatePropagation(); + }); + + button.addEventListener("dragend", ()=>{ + this.dragged = undefined; + }); + + button.addEventListener("dragenter", event=>{ + console.log("enter"); + event.preventDefault(); + return true; + }); + + button.addEventListener("dragover", event=>{ + event.preventDefault(); + return true; + }); + + button.addEventListener("drop", _=>{ + const role2=this.buttonMap.get(this.dragged as HTMLButtonElement); + if(!role2) return; + const index2=this.guild.roles.indexOf(role2); + this.guild.roles.splice(index2,1); + const index=this.guild.roles.indexOf(role); + this.guild.roles.splice(index+1,0,role2); + this.guild.recalcRoles(); + console.log(role); + }); + } + buttonListGen(html:HTMLElement){ + const buttonTable=document.createElement("div"); + buttonTable.classList.add("flexttb"); + + const roleRow=document.createElement("div"); + roleRow.classList.add("flexltr"); + roleRow.append("Roles"); + const add=document.createElement("span"); + add.classList.add("svg-plus","svgicon","addrole"); + add.onclick=async (e)=>{ + const box=add.getBoundingClientRect(); + e.stopPropagation(); + if(this.channel){ + const roles:[Role,string[]][]=[]; + for(const role of this.guild.roles){ + if(this.permissions.find(r=>r[0]==role)){ + continue; + } + roles.push([role,[role.name]]); + } + const search=new Search(roles); + + const found=await search.find(box.left,box.top); + + + if(!found) return; + console.log(found); + this.onchange(found.id,new Permissions("0","0")); + }else{ + const bar=document.createElement("input"); + bar.classList.add("fixedsearch"); + bar.style.left=(box.left^0)+"px"; + bar.style.top=(box.top^0)+"px"; + document.body.append(bar); + if(Contextmenu.currentmenu != ""){ + Contextmenu.currentmenu.remove(); + } + Contextmenu.currentmenu=bar; + Contextmenu.keepOnScreen(bar); + bar.onchange=()=>{ + bar.remove(); + console.log(bar.value) + if(bar.value==="") return; + fetch(this.info.api+`/guilds/${this.guild.id}/roles`,{ + method:"POST", + headers:this.headers, + body:JSON.stringify({ + color:0, + name:bar.value, + permissions:"" + }) + }) + } + } + } + roleRow.append(add); + + buttonTable.append(roleRow); + for(const thing of this.buttons){ + const button = document.createElement("button"); + button.classList.add("SettingsButton"); + button.textContent = thing[0]; + const role=this.guild.roleids.get(thing[1]); + if(role){ + if(!this.channel){ + if(role.canManage()){ + this.buttonDragEvents(button,role); + button.draggable=true; + RoleList.guildrolemenu.bindContextmenu(button,this,role) + } + }else{ + if(role.canManage()){ + RoleList.channelrolemenu.bindContextmenu(button,this,role) + } + } + } + button.onclick = _=>{ + this.generateHTMLArea(thing[1], html); + if(this.warndiv){ + this.warndiv.remove(); + } + }; + buttonTable.append(button); + } + return buttonTable; + } + + generateButtons(html:HTMLElement):HTMLDivElement{ + const div = document.createElement("div"); + div.classList.add("settingbuttons"); + div.append(this.buttonListGen(html)); + return div; } handleString(str: string): HTMLElement{ this.curid = str; diff --git a/src/webpage/search.ts b/src/webpage/search.ts new file mode 100644 index 0000000..00205c0 --- /dev/null +++ b/src/webpage/search.ts @@ -0,0 +1,72 @@ +import { Contextmenu } from "./contextmenu.js"; + +class Search{ + options:Map; + readonly keys:string[]; + constructor(options:[E,string[]][]){ + 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()]; + } + generateList(str:string,max:number,res:(e:E)=>void){ + str=str.toLowerCase(); + const options=this.keys.filter(e=>{ + return e.toLowerCase().includes(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); + } + return div; + } + async find(x:number,y:number,max=4):Promise{ + return new Promise((res)=>{ + + const container=document.createElement("div"); + container.classList.add("fixedsearch"); + console.log((x^0)+"",(y^0)+""); + container.style.left=(x^0)+"px"; + container.style.top=(y^0)+"px"; + const remove=container.remove; + container.remove=()=>{ + remove.call(container); + res(undefined); + } + + function resolve(e:E){ + res(e); + container.remove(); + } + const bar=document.createElement("input"); + const options=document.createElement("div"); + const keydown=()=>{ + const html=this.generateList(bar.value,max,resolve); + options.innerHTML=""; + options.append(html); + } + bar.oninput=keydown; + keydown(); + bar.type="text"; + container.append(bar); + container.append(options); + document.body.append(container); + if(Contextmenu.currentmenu != ""){ + Contextmenu.currentmenu.remove(); + } + Contextmenu.currentmenu=container; + Contextmenu.keepOnScreen(container); + + }) + } +} +export {Search}; diff --git a/src/webpage/settings.ts b/src/webpage/settings.ts index 19fe4bb..83a1465 100644 --- a/src/webpage/settings.ts +++ b/src/webpage/settings.ts @@ -30,6 +30,15 @@ class Buttons implements OptionsElement{ this.buttonList = buttonList; const htmlarea = document.createElement("div"); htmlarea.classList.add("flexgrow"); + const buttonTable = this.generateButtons(htmlarea); + if(this.buttons[0]){ + this.generateHTMLArea(this.buttons[0][1], htmlarea); + } + buttonList.append(buttonTable); + buttonList.append(htmlarea); + return buttonList; + } + generateButtons(optionsArea:HTMLElement){ const buttonTable = document.createElement("div"); buttonTable.classList.add("settingbuttons"); for(const thing of this.buttons){ @@ -37,24 +46,21 @@ class Buttons implements OptionsElement{ button.classList.add("SettingsButton"); button.textContent = thing[0]; button.onclick = _=>{ - this.generateHTMLArea(thing[1], htmlarea); + this.generateHTMLArea(thing[1], optionsArea); if(this.warndiv){ this.warndiv.remove(); } }; buttonTable.append(button); } - this.generateHTMLArea(this.buttons[0][1], htmlarea); - buttonList.append(buttonTable); - buttonList.append(htmlarea); - return buttonList; + return buttonTable; } handleString(str: string): HTMLElement{ const div = document.createElement("span"); div.textContent = str; return div; } - private generateHTMLArea( + generateHTMLArea( buttonInfo: Options | string, htmlarea: HTMLElement ){ @@ -1066,6 +1072,10 @@ class Form implements OptionsElement{ this.owner.changed(); } } + preprocessor:(obj:Object)=>void=()=>{}; + addPreprocessor(func:(obj:Object)=>void){ + this.preprocessor=func; + } async submit(){ if(this.options.subOptions){ this.options.subOptions.submit(); @@ -1130,6 +1140,7 @@ class Form implements OptionsElement{ } console.log("middle2"); await Promise.allSettled(promises); + this.preprocessor(build); if(this.fetchURL !== ""){ fetch(this.fetchURL, { method: this.method, diff --git a/src/webpage/style.css b/src/webpage/style.css index 03a8eb0..ac26358 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -1933,4 +1933,29 @@ fieldset input[type="radio"] { height: 100px; width: 100%; } -} \ No newline at end of file +} +.addrole{ + width:.1in; + height: .1in; + margin-left: .1in; + margin-top: .04in; + cursor: pointer; +} +.fixedsearch{ + position: absolute; + background: var(--primary-bg); + min-height: .2in; + padding:.05in; + border:solid .03in var(--black); + border-radius:.05in; + span{ + margin-top:.1in; + width:100%; + padding:.03in; + border:solid .03in var(--black); + box-sizing:border-box; + border-radius:.05in; + cursor:pointer; + } + +} diff --git a/src/webpage/user.ts b/src/webpage/user.ts index 9c789d3..dd0f5a8 100644 --- a/src/webpage/user.ts +++ b/src/webpage/user.ts @@ -5,6 +5,8 @@ import{ Localuser }from"./localuser.js"; import{ Guild }from"./guild.js"; import{ SnowFlake }from"./snowflake.js"; import{ presencejson, userjson }from"./jsontypes.js"; +import { Role } from "./role.js"; +import { Search } from "./search.js"; class User extends SnowFlake{ owner: Localuser; @@ -174,6 +176,58 @@ class User extends SnowFlake{ return us.hasPermission("BAN_MEMBERS") || false; } ); + this.contextmenu.addbutton( + "Add roles", + async function(this: User, member: Member | undefined,e){ + if(member){ + e.stopPropagation(); + const roles:[Role,string[]][]=[]; + for(const role of member.guild.roles){ + if(!role.canManage()||member.roles.indexOf(role)!==-1){ + continue; + } + roles.push([role,[role.name]]); + } + const search=new Search(roles); + const result=await search.find(e.x,e.y); + if(!result) return; + member.addRole(result); + } + }, + null, + member=>{ + if(!member)return false; + const us = member.guild.member; + console.log(us.hasPermission("MANAGE_ROLES")) + return us.hasPermission("MANAGE_ROLES") || false; + } + ); + this.contextmenu.addbutton( + "Remove roles", + async function(this: User, member: Member | undefined,e){ + if(member){ + e.stopPropagation(); + const roles:[Role,string[]][]=[]; + for(const role of member.roles){ + if(!role.canManage()){ + continue; + } + roles.push([role,[role.name]]); + } + const search=new Search(roles); + const result=await search.find(e.x,e.y); + if(!result) return; + member.removeRole(result); + } + }, + null, + member=>{ + if(!member)return false; + const us = member.guild.member; + console.log(us.hasPermission("MANAGE_ROLES")) + return us.hasPermission("MANAGE_ROLES") || false; + } + ); } static checkuser(user: User | userjson, owner: Localuser): User{