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/channel.ts b/src/webpage/channel.ts index 1910540..21d925c 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", @@ -898,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(); @@ -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/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){ 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/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/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", diff --git a/src/webpage/jsontypes.ts b/src/webpage/jsontypes.ts index 2ab85c2..9e38b13 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; @@ -469,7 +478,28 @@ type wsjson = 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..7284ca9 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"; @@ -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; @@ -354,149 +355,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"); @@ -1229,6 +1255,40 @@ 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. (this feature is for devs, please don't enable if you don't know what you're doing)")){ + 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 update=settings.addButton("Update settings") + const sw=update.addSelect("Service Worker setting",()=>{},["False","Offline only","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"); diff --git a/src/webpage/login.ts b/src/webpage/login.ts index d8802cc..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; } @@ -532,31 +539,55 @@ 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"){ + localStorage.setItem("SWMode",mode); + 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/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..24a07f1 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,238 @@ 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, + traditionalSubmit:true + }); + 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/service.ts b/src/webpage/service.ts index cf93f36..5c2f0a3 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,99 @@ 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); + if(path === "/instances.json"){ + return await fetch(path); + } + 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/src/webpage/settings.ts b/src/webpage/settings.ts index 19fe4bb..245948b 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 ){ @@ -202,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){ @@ -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..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; @@ -1933,4 +1934,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{ 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",