diff --git a/.dist/channel.js b/.dist/channel.js index 97806ab..734d600 100644 --- a/.dist/channel.js +++ b/.dist/channel.js @@ -65,6 +65,24 @@ class Channel { }, null, (_) => { return _.hasPermission("CREATE_INSTANT_INVITE") && _.type !== 4; }); + /* + this.contextmenu.addbutton("Test button",function(){ + this.localuser.ws.send(JSON.stringify({ + "op": 14, + "d": { + "guild_id": this.guild.id, + "channels": { + [this.id]: [ + [ + 0, + 99 + ] + ] + } + } + })) + },null); + /**/ } createInvite() { const div = document.createElement("div"); diff --git a/.dist/localuser.js b/.dist/localuser.js index d4263ab..09773b9 100644 --- a/.dist/localuser.js +++ b/.dist/localuser.js @@ -52,6 +52,7 @@ class Localuser { this.guilds = []; this.guildids = new Map(); this.user = new User(ready.d.user, this); + this.user.setstatus("online"); this.mfa_enabled = ready.d.user.mfa_enabled; this.userinfo.username = this.user.username; this.userinfo.pfpsrc = this.user.getpfpsrc(); @@ -609,6 +610,30 @@ class Localuser { }); }; } + updatebanner(file) { + if (file) { + var reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => { + fetch(this.info.api + "/users/@me", { + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + banner: reader.result, + }) + }); + }; + } + else { + fetch(this.info.api + "/users/@me", { + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + banner: null, + }) + }); + } + } updateProfile(json) { fetch(this.info.api + "/users/@me/profile", { method: "PATCH", @@ -664,9 +689,10 @@ class Localuser { let newpronouns = undefined; let newbio = undefined; let hypouser = this.user.clone(); - function regen() { + let color; + async function regen() { hypotheticalProfile.textContent = ""; - const hypoprofile = hypouser.buildprofile(-1, -1); + const hypoprofile = await hypouser.buildprofile(-1, -1); hypotheticalProfile.appendChild(hypoprofile); } regen(); @@ -687,9 +713,31 @@ class Localuser { regen(); } }); + let bfile = undefined; + const binput = settingsLeft.addFileInput("Upload banner:", _ => { + if (bfile !== undefined) { + this.updatebanner(bfile); + } + }); + binput.watchForChange(_ => { + if (_.length) { + bfile = _[0]; + const blob = URL.createObjectURL(bfile); + hypouser.banner = blob; + hypouser.hypotheticalbanner = true; + regen(); + } + }); + const bclear = settingsLeft.addButtonInput("Clear banner", "Clear", () => { + bfile = null; + hypouser.banner = null; + settingsLeft.changed(); + regen(); + }); + let changed = false; const pronounbox = settingsLeft.addTextInput("Pronouns", _ => { - if (newpronouns || newbio) { - this.updateProfile({ pronouns: newpronouns, bio: newbio }); + if (newpronouns || newbio || changed) { + this.updateProfile({ pronouns: newpronouns, bio: newbio, accent_color: parseInt("0x" + color.substr(1), 16) }); } }, { initText: this.user.pronouns }); pronounbox.watchForChange(_ => { @@ -704,6 +752,20 @@ class Localuser { hypouser.bio = new MarkDown(_, this); regen(); }); + if (this.user.accent_color) { + color = "#" + this.user.accent_color.toString(16); + } + else { + color = "transparent"; + } + const colorPicker = settingsLeft.addColorInput("Profile color", (_) => { }, { initColor: color }); + colorPicker.watchForChange(_ => { + console.log(); + color = _; + hypouser.accent_color = parseInt("0x" + _.substr(1), 16); + changed = true; + regen(); + }); } { const tas = settings.addButton("Themes & sounds"); @@ -1022,6 +1084,7 @@ class Localuser { } //---------- resolving members code ----------- waitingmembers = new Map(); + presences = new Map(); async resolvemember(id, guildid) { if (!this.waitingmembers.has(guildid)) { this.waitingmembers.set(guildid, new Map()); @@ -1038,6 +1101,9 @@ class Localuser { noncemap = new Map(); noncebuild = new Map(); async gotChunk(chunk) { + for (const thing of chunk.presences) { + this.presences.set(thing.user.id, thing); + } console.log(chunk); chunk.members ??= []; const arr = this.noncebuild.get(chunk.nonce); diff --git a/.dist/member.js b/.dist/member.js index 2615097..83683e8 100644 --- a/.dist/member.js +++ b/.dist/member.js @@ -101,6 +101,9 @@ class Member { } else { const member = new Member(membjson, guild); + const map = guild.localuser.presences; + member.getPresence(map.get(member.id)); + map.delete(member.id); res(member); return member; } @@ -112,6 +115,9 @@ class Member { return maybe; } } + getPresence(presence) { + this.user.getPresence(presence); + } /** * @todo */ diff --git a/.dist/user.js b/.dist/user.js index 2ff96dc..56182f4 100644 --- a/.dist/user.js +++ b/.dist/user.js @@ -17,11 +17,13 @@ class User { public_flags; accent_color; banner; + hypotheticalbanner; premium_since; premium_type; theme_colors; badge_ids; members = new WeakMap(); + status; clone() { return new User({ username: this.username, @@ -40,6 +42,25 @@ class User { badge_ids: this.badge_ids }, this.owner); } + getPresence(presence) { + if (presence) { + this.setstatus(presence.status); + } + else { + this.setstatus("offline"); + } + } + setstatus(status) { + this.status = status; + } + async getStatus() { + if (this.status) { + return this.status; + } + else { + return "offline"; + } + } get id() { return this.snowflake.id; } @@ -107,6 +128,27 @@ class User { pfp.classList.add("userid:" + this.id); return pfp; } + async buildstatuspfp() { + const div = document.createElement("div"); + div.style.position = "relative"; + const pfp = this.buildpfp(); + div.append(pfp); + { + const status = document.createElement("div"); + status.classList.add("statusDiv"); + switch (await this.getStatus()) { + case "offline": + status.classList.add("offlinestatus"); + break; + case "online": + default: + status.classList.add("onlinestatus"); + break; + } + div.append(status); + } + return div; + } userupdate(json) { if (json.avatar !== this.avatar) { console.log; @@ -149,7 +191,7 @@ class User { return this.avatar; } if (this.avatar != null) { - return this.info.cdn + "/avatars/" + this.id + "/" + this.avatar + ".png"; + return this.info.cdn + "/avatars/" + this.id.replace("#clone", "") + "/" + this.avatar + ".png"; } else { const int = new Number((BigInt(this.id) >> 22n) % 6n); @@ -159,21 +201,42 @@ class User { createjankpromises() { new Promise(_ => { }); } - buildprofile(x, y) { + async buildprofile(x, y) { if (Contextmenu.currentmenu != "") { Contextmenu.currentmenu.remove(); } const div = document.createElement("div"); + if (this.accent_color) { + div.style.setProperty("--accent_color", "#" + this.accent_color.toString(16).padStart(6, "0")); + } + else { + div.style.setProperty("--accent_color", "transparent"); + } + if (this.banner) { + const banner = document.createElement("img"); + let src; + if (!this.hypotheticalbanner) { + src = this.info.cdn + "/avatars/" + this.id.replace("#clone", "") + "/" + this.banner + ".png"; + } + else { + src = this.banner; + } + console.log(src, this.banner); + banner.src = src; + banner.classList.add("banner"); + div.append(banner); + } if (x !== -1) { div.style.left = x + "px"; div.style.top = y + "px"; div.classList.add("profile", "flexttb"); } else { + this.setstatus("online"); div.classList.add("hypoprofile", "flexttb"); } { - const pfp = this.buildpfp(); + const pfp = await this.buildstatuspfp(); div.appendChild(pfp); } { diff --git a/webpage/channel.ts b/webpage/channel.ts index d7d0f03..b7c2669 100644 --- a/webpage/channel.ts +++ b/webpage/channel.ts @@ -79,6 +79,24 @@ class Channel{ },null,(_:Channel)=>{ return _.hasPermission("CREATE_INSTANT_INVITE")&&_.type!==4 }); + /* + this.contextmenu.addbutton("Test button",function(){ + this.localuser.ws.send(JSON.stringify({ + "op": 14, + "d": { + "guild_id": this.guild.id, + "channels": { + [this.id]: [ + [ + 0, + 99 + ] + ] + } + } + })) + },null); + /**/ } createInvite(){ const div=document.createElement("div"); diff --git a/webpage/jsontypes.ts b/webpage/jsontypes.ts index 1352964..0678bab 100644 --- a/webpage/jsontypes.ts +++ b/webpage/jsontypes.ts @@ -129,7 +129,7 @@ type userjson={ id: string, public_flags: number, avatar: string, - accent_color: string, + accent_color: number, banner: string, bio: string, bot: boolean, @@ -327,4 +327,11 @@ type embedjson={ name:string, } } -export {readyjson,dirrectjson,channeljson,guildjson,rolesjson,userjson,memberjson,mainuserjson,messagejson,filejson,embedjson,emojijson}; +type presencejson={ + status: string, + since: number|null, + activities: any[],//bit more complicated but not now + afk: boolean, + user?:userjson, +} +export {readyjson,dirrectjson,channeljson,guildjson,rolesjson,userjson,memberjson,mainuserjson,messagejson,filejson,embedjson,emojijson,presencejson}; diff --git a/webpage/localuser.ts b/webpage/localuser.ts index 393cbf6..e18181b 100644 --- a/webpage/localuser.ts +++ b/webpage/localuser.ts @@ -7,7 +7,7 @@ import {Dialog} from "./dialog.js"; import {getBulkInfo, setTheme, Specialuser} from "./login.js"; import { SnowFlake } from "./snowflake.js"; import { Message } from "./message.js"; -import { channeljson, guildjson, memberjson, readyjson } from "./jsontypes.js"; +import { channeljson, guildjson, memberjson, presencejson, readyjson } from "./jsontypes.js"; import { Member } from "./member.js"; import { Settings } from "./settings.js"; import { MarkDown } from "./markdown.js"; @@ -56,6 +56,7 @@ class Localuser{ this.guilds=[]; this.guildids=new Map(); this.user=new User(ready.d.user,this); + this.user.setstatus("online"); this.mfa_enabled=ready.d.user.mfa_enabled; this.userinfo.username=this.user.username; this.userinfo.pfpsrc=this.user.getpfpsrc(); @@ -628,7 +629,32 @@ class Localuser{ }; } - updateProfile(json:{bio?:string,pronouns?:string}){ + updatebanner(file:Blob|null):void{ + if(file){ + var reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = ()=>{ + fetch(this.info.api+"/users/@me",{ + method:"PATCH", + headers:this.headers, + body:JSON.stringify({ + banner:reader.result, + }) + }); + }; + }else{ + fetch(this.info.api+"/users/@me",{ + method:"PATCH", + headers:this.headers, + body:JSON.stringify({ + banner:null, + }) + }); + } + + + } + updateProfile(json:{bio?:string,pronouns?:string,accent_color?:number}){ fetch(this.info.api+"/users/@me/profile",{ method:"PATCH", headers:this.headers, @@ -679,9 +705,10 @@ class Localuser{ let newpronouns:string=undefined; let newbio:string=undefined; let hypouser=this.user.clone(); - function regen(){ + let color:string; + async function regen(){ hypotheticalProfile.textContent=""; - const hypoprofile=hypouser.buildprofile(-1,-1); + const hypoprofile=await hypouser.buildprofile(-1,-1); hypotheticalProfile.appendChild(hypoprofile) } @@ -704,9 +731,31 @@ class Localuser{ regen(); } }); + let bfile=undefined; + const binput=settingsLeft.addFileInput("Upload banner:",_=>{ + if(bfile!==undefined){ + this.updatebanner(bfile) + } + }); + binput.watchForChange(_=>{ + if(_.length){ + bfile=_[0]; + const blob = URL.createObjectURL(bfile); + hypouser.banner = blob; + hypouser.hypotheticalbanner=true; + regen(); + } + }); + const bclear=settingsLeft.addButtonInput("Clear banner","Clear",()=>{ + bfile=null; + hypouser.banner = null; + settingsLeft.changed(); + regen(); + }) + let changed=false; const pronounbox=settingsLeft.addTextInput("Pronouns",_=>{ - if(newpronouns||newbio){ - this.updateProfile({pronouns:newpronouns,bio:newbio}); + if(newpronouns||newbio||changed){ + this.updateProfile({pronouns:newpronouns,bio:newbio,accent_color:parseInt("0x"+color.substr(1),16)}); } },{initText:this.user.pronouns}); pronounbox.watchForChange(_=>{ @@ -721,6 +770,20 @@ class Localuser{ newbio=_; hypouser.bio=new MarkDown(_,this); regen(); + }); + + if(this.user.accent_color){ + color="#"+this.user.accent_color.toString(16); + }else{ + color="transparent"; + } + const colorPicker=settingsLeft.addColorInput("Profile color",(_)=>{},{initColor:color}); + colorPicker.watchForChange(_=>{ + console.log() + color=_; + hypouser.accent_color=parseInt("0x"+_.substr(1),16); + changed=true; + regen(); }) } { @@ -1053,7 +1116,8 @@ class Localuser{ } //---------- resolving members code ----------- - waitingmembers:Mapvoid>>=new Map(); + readonly waitingmembers:Mapvoid>>=new Map(); + readonly presences:Map=new Map(); async resolvemember(id:string,guildid:string):Promise{ if(!this.waitingmembers.has(guildid)){ this.waitingmembers.set(guildid,new Map()); @@ -1069,7 +1133,10 @@ class Localuser{ fetchingmembers:Map=new Map(); noncemap:Mapvoid>=new Map(); noncebuild:Map=new Map(); - async gotChunk(chunk:{chunk_index:number,chunk_count:number,nonce:string,not_found?:string[],members?:memberjson[]}){ + async gotChunk(chunk:{chunk_index:number,chunk_count:number,nonce:string,not_found?:string[],members?:memberjson[],presences:presencejson[]}){ + for(const thing of chunk.presences){ + this.presences.set(thing.user.id,thing); + } console.log(chunk); chunk.members??=[]; const arr=this.noncebuild.get(chunk.nonce); @@ -1085,6 +1152,7 @@ class Localuser{ func([arr[0],arr[1]]); this.noncemap.delete(chunk.nonce); } + } async getmembers(){ let res:Function diff --git a/webpage/member.ts b/webpage/member.ts index 584ead3..dbbd865 100644 --- a/webpage/member.ts +++ b/webpage/member.ts @@ -3,7 +3,7 @@ import {Role} from "./role.js"; import {Guild} from "./guild.js"; import { Contextmenu } from "./contextmenu.js"; import { SnowFlake } from "./snowflake.js"; -import { memberjson, userjson } from "./jsontypes.js"; +import { memberjson, presencejson, userjson } from "./jsontypes.js"; class Member{ static already={}; @@ -97,6 +97,9 @@ class Member{ return undefined; }else{ const member=new Member(membjson,guild); + const map=guild.localuser.presences; + member.getPresence(map.get(member.id)); + map.delete(member.id); res(member); return member; } @@ -107,6 +110,9 @@ class Member{ return maybe } } + public getPresence(presence:presencejson|null){ + this.user.getPresence(presence); + } /** * @todo */ diff --git a/webpage/style.css b/webpage/style.css index feb8ccb..a6d7319 100644 --- a/webpage/style.css +++ b/webpage/style.css @@ -18,6 +18,7 @@ body { width: 7cm !important; height: 8cm; border: solid, color-mix(in hsl,var(--black),transparent 80%), .03in; + box-shadow: inset .03in .35in .2in var(--accent_color); } video{ max-width: 3in; @@ -177,6 +178,9 @@ a:hover { padding: .2cm; width: 7cm !important; height: 8cm; + border: solid, color-mix(in hsl,var(--black),transparent 80%), .03in; + box-shadow: inset .03in .35in .2in var(--accent_color); + position: relative; } h1, @@ -1818,4 +1822,30 @@ form div{ box-sizing:border-box; box-shadow: .02in 0 .03in var(--black); cursor:pointer; +} +.statusDiv{ + position:absolute; + bottom: .025in; + right: .025in; + width: .125in; + height: .125in; + background: var(--black); + border: solid .03in black; + box-sizing:border-box; + border-radius: .1in; +} +.onlinestatus{ + background:var(--green); +} +.offlinestatus{ + background:var(--primary-bg); +} +.banner{ + position:absolute; + z-index:0; + top:0; + left:0; + width:100%; + height:.5in; + object-fit: cover; } \ No newline at end of file diff --git a/webpage/user.ts b/webpage/user.ts index cab3c32..55f9020 100644 --- a/webpage/user.ts +++ b/webpage/user.ts @@ -5,7 +5,7 @@ import {Contextmenu} from "./contextmenu.js"; import {Localuser} from "./localuser.js"; import {Guild} from "./guild.js"; import { SnowFlake } from "./snowflake.js"; -import { userjson } from "./jsontypes.js"; +import { presencejson, userjson } from "./jsontypes.js"; class User{ static userids={}; @@ -19,13 +19,15 @@ class User{ pronouns:string; bot:boolean; public_flags: number; - accent_color: string; + accent_color: number; banner: string; + hypotheticalbanner:boolean; premium_since: string; premium_type: number; theme_colors: string; badge_ids: string; members: WeakMap>=new WeakMap(); + private status:string; clone(){ return new User({ username:this.username, @@ -44,6 +46,23 @@ class User{ badge_ids:this.badge_ids },this.owner) } + public getPresence(presence:presencejson|null){ + if(presence){ + this.setstatus(presence.status); + }else{ + this.setstatus("offline"); + } + } + setstatus(status:string){ + this.status=status; + } + async getStatus(){ + if(this.status){ + return this.status; + }else{ + return "offline"; + } + } get id(){ return this.snowflake.id; } @@ -109,6 +128,27 @@ class User{ pfp.classList.add("userid:"+this.id); return pfp; } + async buildstatuspfp(){ + const div=document.createElement("div"); + div.style.position="relative"; + const pfp=this.buildpfp(); + div.append(pfp); + { + const status=document.createElement("div"); + status.classList.add("statusDiv"); + switch(await this.getStatus()){ + case "offline": + status.classList.add("offlinestatus"); + break; + case "online": + default: + status.classList.add("onlinestatus"); + break; + } + div.append(status); + } + return div; + } userupdate(json:userjson){ if(json.avatar!==this.avatar){ console.log @@ -153,7 +193,7 @@ class User{ return this.avatar; } if(this.avatar!=null){ - return this.info.cdn+"/avatars/"+this.id+"/"+this.avatar+".png"; + return this.info.cdn+"/avatars/"+this.id.replace("#clone","")+"/"+this.avatar+".png"; }else{ const int=new Number((BigInt(this.id) >> 22n) % 6n); return this.info.cdn+`/embed/avatars/${int}.png`; @@ -162,23 +202,43 @@ class User{ createjankpromises(){ new Promise(_=>{}) } - buildprofile(x:number,y:number){ + async buildprofile(x:number,y:number){ if(Contextmenu.currentmenu!=""){ Contextmenu.currentmenu.remove(); } const div=document.createElement("div"); + + if(this.accent_color){ + div.style.setProperty("--accent_color","#"+this.accent_color.toString(16).padStart(6,"0")); + }else{ + div.style.setProperty("--accent_color","transparent"); + } + if(this.banner){ + const banner=document.createElement("img") + let src:string; + if(!this.hypotheticalbanner){ + src=this.info.cdn+"/avatars/"+this.id.replace("#clone","")+"/"+this.banner+".png"; + }else{ + src=this.banner; + } + console.log(src,this.banner); + banner.src=src; + banner.classList.add("banner"); + div.append(banner); + } if(x!==-1){ div.style.left=x+"px"; div.style.top=y+"px"; div.classList.add("profile","flexttb"); }else{ + this.setstatus("online"); div.classList.add("hypoprofile","flexttb"); } { - const pfp=this.buildpfp(); + const pfp=await this.buildstatuspfp(); div.appendChild(pfp); } {