From 96a3a705a0c0d2ec7a234e5dc1fbb18bcedf9863 Mon Sep 17 00:00:00 2001 From: greysilly7 Date: Fri, 25 Oct 2024 00:03:30 -0400 Subject: [PATCH 01/12] CF makes you pay for multi-level subdomains --- src/webpage/instances.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/webpage/instances.json b/src/webpage/instances.json index c92983a..4949fbc 100644 --- a/src/webpage/instances.json +++ b/src/webpage/instances.json @@ -21,9 +21,9 @@ "display": true, "urls": { "wellknown": "https://greysilly7.xyz", - "api": "https://api.spacebar.greysilly7.xyz/api", - "cdn": "https://cdn.spacebar.greysilly7.xyz", - "gateway": "wss://gateway.spacebar.greysilly7.xyz" + "api": "https://api-spacebar.greysilly7.xyz/api", + "cdn": "https://cdn-spacebar.greysilly7.xyz", + "gateway": "wss://gateway-spacebar.greysilly7.xyz" }, "contactInfo": { "dicord": "greysilly7", From e06d304064093a4fd8d49b7a7dc18615faa0ad71 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Tue, 29 Oct 2024 15:10:15 -0500 Subject: [PATCH 02/12] fixing formatting --- src/webpage/jsontypes.ts | 547 ++++++++++++++++++++------------------- 1 file changed, 278 insertions(+), 269 deletions(-) diff --git a/src/webpage/jsontypes.ts b/src/webpage/jsontypes.ts index 2ab85c2..2ba11e5 100644 --- a/src/webpage/jsontypes.ts +++ b/src/webpage/jsontypes.ts @@ -1,120 +1,120 @@ type readyjson = { -op: 0; -t: "READY"; -s: number; -d: { -v: number; -user: mainuserjson; -user_settings: { -index: number; -afk_timeout: number; -allow_accessibility_detection: boolean; -animate_emoji: boolean; -animate_stickers: number; -contact_sync_enabled: boolean; -convert_emoticons: boolean; -custom_status: string; -default_guilds_restricted: boolean; -detect_platform_accounts: boolean; -developer_mode: boolean; -disable_games_tab: boolean; -enable_tts_command: boolean; -explicit_content_filter: 0; -friend_discovery_flags: 0; -friend_source_flags: { -all: boolean; -}; //might be missing things here -gateway_connected: boolean; -gif_auto_play: boolean; -guild_folders: []; //need an example of this not empty -guild_positions: []; //need an example of this not empty -inline_attachment_media: boolean; -inline_embed_media: boolean; -locale: string; -message_display_compact: boolean; -native_phone_integration_enabled: boolean; -render_embeds: boolean; -render_reactions: boolean; -restricted_guilds: []; //need an example of this not empty -show_current_game: boolean; -status: string; -stream_notifications_enabled: boolean; -theme: string; -timezone_offset: number; -view_nsfw_guilds: boolean; -}; -guilds: guildjson[]; -relationships: { -id: string; -type: 0 | 1 | 2 | 3 | 4; -nickname: string | null; -user: userjson; -}[]; -read_state: { -entries: { -id: string; -channel_id: string; -last_message_id: string; -last_pin_timestamp: string; -mention_count: number; //in theory, the server doesn't actually send this as far as I'm aware -}[]; -partial: boolean; -version: number; -}; -user_guild_settings: { -entries: { -channel_overrides: unknown[]; //will have to find example -message_notifications: number; -flags: number; -hide_muted_channels: boolean; -mobile_push: boolean; -mute_config: null; -mute_scheduled_events: boolean; -muted: boolean; -notify_highlights: number; -suppress_everyone: boolean; -suppress_roles: boolean; -version: number; -guild_id: string; -}[]; -partial: boolean; -version: number; -}; -private_channels: dirrectjson[]; -session_id: string; -country_code: string; -users: userjson[]; -merged_members: [memberjson][]; -sessions: { -active: boolean; -activities: []; //will need to find example of this -client_info: { -version: number; -}; -session_id: string; -status: string; -}[]; -resume_gateway_url: string; -consents: { -personalization: { -consented: boolean; -}; -}; -experiments: []; //not sure if I need to do this :P -guild_join_requests: []; //need to get examples -connected_accounts: []; //need to get examples -guild_experiments: []; //need to get examples -geo_ordered_rtc_regions: []; //need to get examples -api_code_version: number; -friend_suggestion_count: number; -analytics_token: string; -tutorial: boolean; -session_type: string; -auth_session_id_hash: string; -notification_settings: { -flags: number; -}; -}; + op: 0; + t: "READY"; + s: number; + d: { + v: number; + user: mainuserjson; + user_settings: { + index: number; + afk_timeout: number; + allow_accessibility_detection: boolean; + animate_emoji: boolean; + animate_stickers: number; + contact_sync_enabled: boolean; + convert_emoticons: boolean; + custom_status: string; + default_guilds_restricted: boolean; + detect_platform_accounts: boolean; + developer_mode: boolean; + disable_games_tab: boolean; + enable_tts_command: boolean; + explicit_content_filter: 0; + friend_discovery_flags: 0; + friend_source_flags: { + all: boolean; + }; //might be missing things here + gateway_connected: boolean; + gif_auto_play: boolean; + guild_folders: []; //need an example of this not empty + guild_positions: []; //need an example of this not empty + inline_attachment_media: boolean; + inline_embed_media: boolean; + locale: string; + message_display_compact: boolean; + native_phone_integration_enabled: boolean; + render_embeds: boolean; + render_reactions: boolean; + restricted_guilds: []; //need an example of this not empty + show_current_game: boolean; + status: string; + stream_notifications_enabled: boolean; + theme: string; + timezone_offset: number; + view_nsfw_guilds: boolean; + }; + guilds: guildjson[]; + relationships: { + id: string; + type: 0 | 1 | 2 | 3 | 4; + nickname: string | null; + user: userjson; + }[]; + read_state: { + entries: { + id: string; + channel_id: string; + last_message_id: string; + last_pin_timestamp: string; + mention_count: number; //in theory, the server doesn't actually send this as far as I'm aware + }[]; + partial: boolean; + version: number; + }; + user_guild_settings: { + entries: { + channel_overrides: unknown[]; //will have to find example + message_notifications: number; + flags: number; + hide_muted_channels: boolean; + mobile_push: boolean; + mute_config: null; + mute_scheduled_events: boolean; + muted: boolean; + notify_highlights: number; + suppress_everyone: boolean; + suppress_roles: boolean; + version: number; + guild_id: string; + }[]; + partial: boolean; + version: number; + }; + private_channels: dirrectjson[]; + session_id: string; + country_code: string; + users: userjson[]; + merged_members: [memberjson][]; + sessions: { + active: boolean; + activities: []; //will need to find example of this + client_info: { + version: number; + }; + session_id: string; + status: string; + }[]; + resume_gateway_url: string; + consents: { + personalization: { + consented: boolean; + }; + }; + experiments: []; //not sure if I need to do this :P + guild_join_requests: []; //need to get examples + connected_accounts: []; //need to get examples + guild_experiments: []; //need to get examples + geo_ordered_rtc_regions: []; //need to get examples + api_code_version: number; + friend_suggestion_count: number; + analytics_token: string; + tutorial: boolean; + session_type: string; + auth_session_id_hash: string; + notification_settings: { + flags: number; + }; + }; }; type mainuserjson = userjson & { flags: number; @@ -129,20 +129,20 @@ type mainuserjson = userjson & { disabled: boolean; }; type userjson = { -username: string; -discriminator: string; -id: string; -public_flags: number; -avatar: string | null; -accent_color: number; -banner?: string; -bio: string; -bot: boolean; -premium_since: string; -premium_type: number; -theme_colors: string; -pronouns: string; -badge_ids: string[]; + username: string; + discriminator: string; + id: string; + public_flags: number; + avatar: string | null; + accent_color: number; + banner?: string; + bio: string; + bot: boolean; + premium_since: string; + premium_type: number; + theme_colors: string; + pronouns: string; + badge_ids: string[]; }; type memberjson = { index?: number; @@ -163,9 +163,9 @@ type memberjson = { last_message_id?: boolean; //What??? }; type emojijson = { -name: string; -id?: string; -animated?: boolean; + name: string; + id?: string; + animated?: boolean; }; type guildjson = { @@ -225,37 +225,37 @@ type guildjson = { joined_at: string; }; type startTypingjson = { -d: { -channel_id: string; -guild_id?: string; -user_id: string; -timestamp: number; -member?: memberjson; -}; + d: { + channel_id: string; + guild_id?: string; + user_id: string; + timestamp: number; + member?: memberjson; + }; }; type channeljson = { -id: string; -created_at: string; -name: string; -icon: string; -type: number; -last_message_id: string; -guild_id: string; -parent_id: string; -last_pin_timestamp: string; -default_auto_archive_duration: number; -permission_overwrites: { -id: string; -allow: string; -deny: string; -}[]; -video_quality_mode: null; -nsfw: boolean; -topic: string; -retention_policy_id: string; -flags: number; -default_thread_rate_limit_per_user: number; -position: number; + id: string; + created_at: string; + name: string; + icon: string; + type: number; + last_message_id: string; + guild_id: string; + parent_id: string; + last_pin_timestamp: string; + default_auto_archive_duration: number; + permission_overwrites: { + id: string; + allow: string; + deny: string; + }[]; + video_quality_mode: null; + nsfw: boolean; + topic: string; + retention_policy_id: string; + flags: number; + default_thread_rate_limit_per_user: number; + position: number; }; type rolesjson = { id: string; @@ -272,127 +272,136 @@ type rolesjson = { flags: number; }; type dirrectjson = { -id: string; -flags: number; -last_message_id: string; -type: number; -recipients: userjson[]; -is_spam: boolean; + id: string; + flags: number; + last_message_id: string; + type: number; + recipients: userjson[]; + is_spam: boolean; }; type messagejson = { -id: string; -channel_id: string; -guild_id: string; -author: userjson; -member?: memberjson; -content: string; -timestamp: string; -edited_timestamp: string; -tts: boolean; -mention_everyone: boolean; -mentions: []; //need examples to fix -mention_roles: []; //need examples to fix -attachments: filejson[]; -embeds: embedjson[]; -reactions: { -count: number; -emoji: emojijson; //very likely needs expanding -me: boolean; -}[]; -nonce: string; -pinned: boolean; -type: number; + id: string; + channel_id: string; + guild_id: string; + author: userjson; + member?: memberjson; + content: string; + timestamp: string; + edited_timestamp: string; + tts: boolean; + mention_everyone: boolean; + mentions: []; //need examples to fix + mention_roles: []; //need examples to fix + attachments: filejson[]; + embeds: embedjson[]; + reactions: { + count: number; + emoji: emojijson; //very likely needs expanding + me: boolean; + }[]; + nonce: string; + pinned: boolean; + type: number; }; type filejson = { -id: string; -filename: string; -content_type: string; -width?: number; -height?: number; -proxy_url: string | undefined; -url: string; -size: number; + id: string; + filename: string; + content_type: string; + width?: number; + height?: number; + proxy_url: string | undefined; + url: string; + size: number; }; type embedjson = { -type: string | null; -color?: number; -author: { -icon_url?: string; -name?: string; -url?: string; -title?: string; -}; -title?: string; -url?: string; -description?: string; -fields?: { -name: string; -value: string; -inline: boolean; -}[]; -footer?: { -icon_url?: string; -text?: string; -thumbnail?: string; -}; -timestamp?: string; -thumbnail: { -proxy_url: string; -url: string; -width: number; -height: number; -}; -provider: { -name: string; -}; -video?: { -url: string; -width?: number | null; -height?: number | null; -proxy_url?: string; -}; -invite?: { -url: string; -code: string; -}; + type: string | null; + color?: number; + author: { + icon_url?: string; + name?: string; + url?: string; + title?: string; + }; + title?: string; + url?: string; + description?: string; + fields?: { + name: string; + value: string; + inline: boolean; + }[]; + footer?: { + icon_url?: string; + text?: string; + thumbnail?: string; + }; + timestamp?: string; + thumbnail: { + proxy_url: string; + url: string; + width: number; + height: number; + }; + provider: { + name: string; + }; + video?: { + url: string; + width?: number | null; + height?: number | null; + proxy_url?: string; + }; + invite?: { + url: string; + code: string; + }; }; type invitejson = { -code: string; -temporary: boolean; -uses: number; -max_use: number; -max_age: number; -created_at: string; -expires_at: string; -guild_id: string; -channel_id: string; -inviter_id: string; -target_user_id: string | null; -target_user_type: string | null; -vanity_url: string | null; -flags: number; -guild: guildjson["properties"]; -channel: channeljson; -inviter: userjson; + code: string; + temporary: boolean; + uses: number; + max_use: number; + max_age: number; + created_at: string; + expires_at: string; + guild_id: string; + channel_id: string; + inviter_id: string; + target_user_id: string | null; + target_user_type: string | null; + vanity_url: string | null; + flags: number; + guild: guildjson["properties"]; + channel: channeljson; + inviter: userjson; }; type presencejson = { -status: string; -since: number | null; -activities: any[]; //bit more complicated but not now -afk: boolean; -user?: userjson; + status: string; + since: number | null; + activities: any[]; //bit more complicated but not now + afk: boolean; + user?: userjson; }; type messageCreateJson = { -op: 0; -d: { -guild_id?: string; -channel_id?: string; -} & messagejson; -s: number; -t: "MESSAGE_CREATE"; + op: 0; + d: { + guild_id?: string; + channel_id?: string; + } & messagejson; + s: number; + t: "MESSAGE_CREATE"; }; +type roleCreate={ + op: 0, + t: "GUILD_ROLE_CREATE", + d: { + guild_id: string, + role: rolesjson + }, + s: 6 +} type wsjson = -| { +roleCreate | { op: 0; d: any; s: number; From c8e3125c5d2908304eea94951f590a6a7a58f360 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Tue, 29 Oct 2024 20:30:24 -0500 Subject: [PATCH 03/12] 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{ From d955499227041276f00c6ab9416a2f34742bfd99 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Tue, 29 Oct 2024 20:34:21 -0500 Subject: [PATCH 04/12] slight CSS update --- src/webpage/style.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index ac26358..995ccab 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -581,6 +581,7 @@ span.instanceStatus { .channels { overflow-y: hidden; transition: height .2s ease-in-out; + padding-left: 6px; } #channels > div > div:first-child { margin-top: 8px; @@ -592,7 +593,7 @@ span.instanceStatus { } .channelbutton { height: 2em; - padding: 0 8px; + padding: 0 5px; background: transparent; color: var(--primary-text-soft); display: flex; From abc63bc166a492ce7b6a8714f8bf22c873f9913e Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Tue, 29 Oct 2024 20:36:25 -0500 Subject: [PATCH 05/12] toggle traditional submit --- src/webpage/role.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/webpage/role.ts b/src/webpage/role.ts index 8618b1c..24a07f1 100644 --- a/src/webpage/role.ts +++ b/src/webpage/role.ts @@ -210,7 +210,8 @@ class RoleList extends Buttons{ const form=option.addSubForm("Display settings",()=>{},{ fetchURL:this.info.api+"/guilds/"+this.guild.id+"/roles/"+this.curid, method:"PATCH", - headers:this.headers + headers:this.headers, + traditionalSubmit:true }); form.addTextInput("Role Name:","name",{ initText:role.name From 7f51b6c89fbaa6939f193b0e3674192df97ab0b3 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Tue, 29 Oct 2024 20:53:20 -0500 Subject: [PATCH 06/12] Voice setting --- src/webpage/channel.ts | 2 +- src/webpage/localuser.ts | 19 +++++++++++++++++++ src/webpage/settings.ts | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index f830bf6..21d925c 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -843,7 +843,7 @@ class Channel extends SnowFlake{ loading.classList.add("loading"); this.rendertyping(); this.localuser.getSidePannel(); - if(this.voice){ + if(this.voice&&localStorage.getItem("Voice enabled")){ this.localuser.joinVoice(this); } await this.putmessages(); diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 36ff5f2..3db855b 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -1254,6 +1254,25 @@ class Localuser{ { initColor: userinfos.accent_color } ); } + { + const box=tas.addCheckboxInput("Enable experimental Voice support",()=>{},{initState:Boolean(localStorage.getItem("Voice enabled"))}); + box.onchange=(e)=>{ + if(e){ + if(confirm("Are you sure you want to enable this, this is very experimental and is likely to cause issues")){ + localStorage.setItem("Voice enabled","true") + + }else{ + box.value=true; + const checkbox=box.input.deref(); + if(checkbox){ + checkbox.checked=false; + } + } + }else{ + localStorage.removeItem("Voice enabled"); + } + } + } } { const security = settings.addButton("Account Settings"); diff --git a/src/webpage/settings.ts b/src/webpage/settings.ts index 83a1465..245948b 100644 --- a/src/webpage/settings.ts +++ b/src/webpage/settings.ts @@ -208,8 +208,8 @@ class CheckboxInput implements OptionsElement{ const input = this.input.deref(); if(input){ const value = input.checked as boolean; - this.onchange(value); this.value = value; + this.onchange(value); } } setState(state:boolean){ From 94ccfb2aedcdfe22ee73ad5cad5c7c950ef71511 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Tue, 29 Oct 2024 21:06:30 -0500 Subject: [PATCH 07/12] passive scroll events --- src/webpage/contextmenu.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/contextmenu.ts b/src/webpage/contextmenu.ts index 56f2256..903b72a 100644 --- a/src/webpage/contextmenu.ts +++ b/src/webpage/contextmenu.ts @@ -96,7 +96,7 @@ class Contextmenu{ event.stopImmediatePropagation(); this.makemenu(event.touches[0].clientX, event.touches[0].clientY, addinfo, other); } - }); + },{passive:true}); return func; } static keepOnScreen(obj: HTMLElement){ From 262659d4fe63c0272b8643505befd13845ebcdf4 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Tue, 29 Oct 2024 21:08:18 -0500 Subject: [PATCH 08/12] better message --- src/webpage/localuser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 3db855b..5cbdf89 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -1258,7 +1258,7 @@ class Localuser{ const box=tas.addCheckboxInput("Enable experimental Voice support",()=>{},{initState:Boolean(localStorage.getItem("Voice enabled"))}); box.onchange=(e)=>{ if(e){ - if(confirm("Are you sure you want to enable this, this is very experimental and is likely to cause issues")){ + if(confirm("Are you sure you want to enable this, this is very experimental and is likely to cause issues. (this feature is for devs, please don't enable if you don't know what you're doing)")){ localStorage.setItem("Voice enabled","true") }else{ From 85e2cceca5b210af457aa6e49ad4c7feffbafe92 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 30 Oct 2024 15:31:41 -0500 Subject: [PATCH 09/12] service worker fixes --- src/index.ts | 4 -- src/webpage/localuser.ts | 17 ++++++- src/webpage/login.ts | 65 ++++++++++++++++-------- src/webpage/service.ts | 107 +++++++++++++++++++++++++++------------ tsconfig.json | 3 +- 5 files changed, 137 insertions(+), 59 deletions(-) diff --git a/src/index.ts b/src/index.ts index 882a219..4e82800 100644 --- a/src/index.ts +++ b/src/index.ts @@ -102,10 +102,6 @@ app.use("/", async (req: Request, res: Response)=>{ res.sendFile(path.join(__dirname, "webpage", "invite.html")); return; } - if(req.path.endsWith("service.js")){ - res.send("nope :3"); - return; - } const filePath = path.join(__dirname, "webpage", req.path); try{ await fs.access(filePath); diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 5cbdf89..494e573 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -4,7 +4,7 @@ import{ Direct }from"./direct.js"; import{ AVoice }from"./audio.js"; import{ User }from"./user.js"; import{ Dialog }from"./dialog.js"; -import{ getapiurls, getBulkInfo, setTheme, Specialuser }from"./login.js"; +import{ getapiurls, getBulkInfo, setTheme, Specialuser, SW }from"./login.js"; import{channeljson,guildjson,mainuserjson,memberjson,memberlistupdatejson,messageCreateJson,presencejson,readyjson,startTypingjson,wsjson,}from"./jsontypes.js"; import{ Member }from"./member.js"; import{ Form, FormError, Options, Settings }from"./settings.js"; @@ -1274,6 +1274,21 @@ class Localuser{ } } } + { + const update=settings.addButton("Update settings") + const sw=update.addSelect("Service Worker setting",()=>{},["false","offlineOnly","true"],{ + defaultIndex:["false","offlineOnly","true"].indexOf(localStorage.getItem("SWMode") as string) + }); + sw.onchange=(e)=>{ + SW.setMode(["false","offlineOnly","true"][e] as "false"|"offlineOnly"|"true") + } + update.addButtonInput("","Check for update",()=>{ + SW.checkUpdate(); + }); + update.addButtonInput("","Clear cache",()=>{ + SW.forceClear(); + }); + } { const security = settings.addButton("Account Settings"); const genSecurity = ()=>{ diff --git a/src/webpage/login.ts b/src/webpage/login.ts index d8802cc..914d260 100644 --- a/src/webpage/login.ts +++ b/src/webpage/login.ts @@ -532,31 +532,54 @@ if(document.getElementById("form")){ } } //this currently does not work, and need to be implemented better at some time. -/* - if ("serviceWorker" in navigator){ +if(!localStorage.getItem("SWMode")){ + localStorage.setItem("SWMode","true"); +} +class SW{ + static worker:undefined|ServiceWorker; + static setMode(mode:"false"|"offlineOnly"|"true"){ + if(this.worker){ + this.worker.postMessage({data:mode,code:"setMode"}); + } + } + static checkUpdate(){ + if(this.worker){ + this.worker.postMessage({code:"CheckUpdate"}); + } + } + static forceClear(){ + if(this.worker){ + this.worker.postMessage({code:"ForceClear"}); + } + } +} +export {SW}; +if ("serviceWorker" in navigator){ navigator.serviceWorker.register("/service.js", { scope: "/", }).then((registration) => { - let serviceWorker:ServiceWorker; - if (registration.installing) { - serviceWorker = registration.installing; - console.log("installing"); - } else if (registration.waiting) { - serviceWorker = registration.waiting; - console.log("waiting"); - } else if (registration.active) { - serviceWorker = registration.active; - console.log("active"); - } - if (serviceWorker) { - console.log(serviceWorker.state); - serviceWorker.addEventListener("statechange", (e) => { - console.log(serviceWorker.state); - }); - } + let serviceWorker:ServiceWorker|undefined; + if (registration.installing) { + serviceWorker = registration.installing; + console.log("installing"); + } else if (registration.waiting) { + serviceWorker = registration.waiting; + console.log("waiting"); + } else if (registration.active) { + serviceWorker = registration.active; + console.log("active"); + } + SW.worker=serviceWorker; + SW.setMode(localStorage.getItem("SWMode") as "false"|"offlineOnly"|"true"); + if (serviceWorker) { + console.log(serviceWorker.state); + serviceWorker.addEventListener("statechange", (_) => { + console.log(serviceWorker.state); + }); + } }) - } - */ +} + const switchurl = document.getElementById("switch") as HTMLAreaElement; if(switchurl){ switchurl.href += window.location.search; diff --git a/src/webpage/service.ts b/src/webpage/service.ts index cf93f36..c1f494b 100644 --- a/src/webpage/service.ts +++ b/src/webpage/service.ts @@ -13,13 +13,13 @@ async function putInCache(request: URL | RequestInfo, response: Response){ console.error(error); } } -console.log("test"); let lastcache: string; self.addEventListener("activate", async ()=>{ - console.log("test2"); + console.log("Service Worker activated"); checkCache(); }); + async function checkCache(){ if(checkedrecently){ return; @@ -34,7 +34,7 @@ async function checkCache(){ console.log(text, lastcache); if(lastcache !== text){ deleteoldcache(); - putInCache("/getupdates", data.clone()); + putInCache("/getupdates", data); } checkedrecently = true; setTimeout((_: any)=>{ @@ -43,54 +43,97 @@ async function checkCache(){ }); } var checkedrecently = false; + function samedomain(url: string | URL){ return new URL(url).origin === self.origin; } -function isindexhtml(url: string | URL){ - console.log(url); - if(new URL(url).pathname.startsWith("/channels")){ - return true; + +const htmlFiles=new Set(["/index","/login","/home","/register","/oauth2/auth"]); + + +function isHtml(url:string):string|void{ + const path=new URL(url).pathname; + if(htmlFiles.has(path)||htmlFiles.has(path+".html")){ + return path+path.endsWith(".html")?"":".html"; } - return false; } -async function getfile(event: { -request: { url: URL | RequestInfo; clone: () => string | URL | Request }; -}){ - checkCache(); - if(!samedomain(event.request.url.toString())){ - return await fetch(event.request.clone()); +let enabled="false"; +let offline=false; + +function toPath(url:string):string{ + const Url= new URL(url); + let html=isHtml(url); + if(!html){ + const path=Url.pathname; + if(path.startsWith("/channels")){ + html="./index.html" + }else if(path.startsWith("/invite")){ + html="./invite.html" + } } - const responseFromCache = await caches.match(event.request.url); - console.log(responseFromCache, caches); + return html||Url.pathname; +} +let fails=0; +async function getfile(event: FetchEvent):Promise{ + checkCache(); + if(!samedomain(event.request.url)||enabled==="false"||(enabled==="offlineOnly"&&!offline)){ + const responce=await fetch(event.request.clone()); + if(samedomain(event.request.url)){ + if(enabled==="offlineOnly"&&responce.ok){ + putInCache(toPath(event.request.url),responce.clone()); + } + if(!responce.ok){ + fails++; + if(fails>5){ + offline=true; + } + } + } + return responce; + } + + let path=toPath(event.request.url); + + console.log("Getting path: "+path); + const responseFromCache = await caches.match(path); if(responseFromCache){ console.log("cache hit"); return responseFromCache; } - if(isindexhtml(event.request.url.toString())){ - console.log("is index.html"); - const responseFromCache = await caches.match("/index.html"); - if(responseFromCache){ - console.log("cache hit"); - return responseFromCache; - } - const responseFromNetwork = await fetch("/index.html"); - await putInCache("/index.html", responseFromNetwork.clone()); - return responseFromNetwork; - } - const responseFromNetwork = await fetch(event.request.clone()); - console.log(event.request.clone()); - await putInCache(event.request.clone(), responseFromNetwork.clone()); try{ + const responseFromNetwork = await fetch(path); + if(responseFromNetwork.ok){ + await putInCache(path, responseFromNetwork.clone()); + } return responseFromNetwork; }catch(e){ console.error(e); - return e; + return new Response(null); } } -self.addEventListener("fetch", (event: any)=>{ + + +self.addEventListener("fetch", (e)=>{ + const event=e as FetchEvent; try{ event.respondWith(getfile(event)); }catch(e){ console.error(e); } }); + +self.addEventListener("message", (message)=>{ + const data=message.data; + switch(data.code){ + case "setMode": + enabled=data.data; + break; + case "CheckUpdate": + checkedrecently=false; + checkCache(); + break; + case "ForceClear": + deleteoldcache(); + break; + } +}) diff --git a/tsconfig.json b/tsconfig.json index 0b4a564..5ec89b3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,8 @@ "incremental": true, "lib": [ "esnext", - "DOM" + "DOM", + "webworker" ], "module": "ESNext", "moduleResolution": "Bundler", From 02d58bc64e21675f30a4f50517857602aad86723 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 30 Oct 2024 15:33:56 -0500 Subject: [PATCH 10/12] slight fixes --- src/webpage/localuser.ts | 2 +- src/webpage/login.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 494e573..19ab1ee 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -1276,7 +1276,7 @@ class Localuser{ } { const update=settings.addButton("Update settings") - const sw=update.addSelect("Service Worker setting",()=>{},["false","offlineOnly","true"],{ + const sw=update.addSelect("Service Worker setting",()=>{},["False","Offline only","True"],{ defaultIndex:["false","offlineOnly","true"].indexOf(localStorage.getItem("SWMode") as string) }); sw.onchange=(e)=>{ diff --git a/src/webpage/login.ts b/src/webpage/login.ts index 914d260..8e0e996 100644 --- a/src/webpage/login.ts +++ b/src/webpage/login.ts @@ -538,6 +538,7 @@ if(!localStorage.getItem("SWMode")){ class SW{ static worker:undefined|ServiceWorker; static setMode(mode:"false"|"offlineOnly"|"true"){ + localStorage.setItem("SWMode",mode); if(this.worker){ this.worker.postMessage({data:mode,code:"setMode"}); } From 4c8ac492c040c0d1f90a7286c83b70e2eae70dd0 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 30 Oct 2024 15:39:04 -0500 Subject: [PATCH 11/12] don't cache instances.json --- src/webpage/home.ts | 35 +++++++++++++++++------------------ src/webpage/service.ts | 4 +++- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/webpage/home.ts b/src/webpage/home.ts index 9f41e77..ce94e44 100644 --- a/src/webpage/home.ts +++ b/src/webpage/home.ts @@ -7,22 +7,22 @@ fetch("/instances.json") .then( ( json: { -name: string; -description?: string; -descriptionLong?: string; -image?: string; -url?: string; -display?: boolean; -online?: boolean; -uptime: { alltime: number; daytime: number; weektime: number }; -urls: { -wellknown: string; -api: string; -cdn: string; -gateway: string; -login?: string; -}; -}[] + name: string; + description?: string; + descriptionLong?: string; + image?: string; + url?: string; + display?: boolean; + online?: boolean; + uptime: { alltime: number; daytime: number; weektime: number }; + urls: { + wellknown: string; + api: string; + cdn: string; + gateway: string; + login?: string; + }; + }[] )=>{ console.warn(json); for(const instance of json){ @@ -77,8 +77,7 @@ login?: string; div.append(statbox); div.onclick = _=>{ if(instance.online){ - window.location.href = -"/register.html?instance=" + encodeURI(instance.name); + window.location.href = "/register.html?instance=" + encodeURI(instance.name); }else{ alert("Instance is offline, can't connect"); } diff --git a/src/webpage/service.ts b/src/webpage/service.ts index c1f494b..5c2f0a3 100644 --- a/src/webpage/service.ts +++ b/src/webpage/service.ts @@ -93,7 +93,9 @@ async function getfile(event: FetchEvent):Promise{ } let path=toPath(event.request.url); - + if(path === "/instances.json"){ + return await fetch(path); + } console.log("Getting path: "+path); const responseFromCache = await caches.match(path); if(responseFromCache){ From 8bf7097aaa87804292854e56d36abd97bec5b08e Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 30 Oct 2024 15:49:56 -0500 Subject: [PATCH 12/12] correct trim switched --- src/webpage/localuser.ts | 1 + src/webpage/login.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 19ab1ee..7284ca9 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -80,6 +80,7 @@ class Localuser{ this.handleVoice(); this.mfa_enabled = ready.d.user.mfa_enabled as boolean; this.userinfo.username = this.user.username; + this.userinfo.id = this.user.id; this.userinfo.pfpsrc = this.user.getpfpsrc(); this.status = this.ready.d.user_settings.status; this.channelfocus = undefined; diff --git a/src/webpage/login.ts b/src/webpage/login.ts index 8e0e996..3eaac20 100644 --- a/src/webpage/login.ts +++ b/src/webpage/login.ts @@ -47,7 +47,7 @@ function trimswitcher(){ if(wellknown.at(-1) !== "/"){ wellknown += "/"; } - wellknown += user.username; + wellknown =(user.id||user.email)+"@"+wellknown; if(map.has(wellknown)){ const otheruser = map.get(wellknown); if(otheruser[1].serverurls.wellknown.at(-1) === "/"){ @@ -178,6 +178,13 @@ class Specialuser{ get localuserStore(){ return this.json.localuserStore; } + set id(e){ + this.json.id = e; + this.updateLocal(); + } + get id(){ + return this.json.id; + } get uid(){ return this.email + this.serverurls.wellknown; }