Merge remote-tracking branch 'upstream/main'

This commit is contained in:
ygg2 2024-10-30 20:30:26 -04:00
commit dfe83427e9
17 changed files with 1231 additions and 604 deletions

View file

@ -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);

View file

@ -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<string>().union(oldover).difference(this.permission_overwrites)];
const pchange=[...new Set<string>().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();

View file

@ -96,7 +96,7 @@ class Contextmenu<x, y>{
event.stopImmediatePropagation();
this.makemenu(event.touches[0].clientX, event.touches[0].clientY, addinfo, other);
}
});
},{passive:true});
return func;
}
static keepOnScreen(obj: HTMLElement){

View file

@ -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(_){

View file

@ -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");
}

View file

@ -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",

View file

@ -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;

View file

@ -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");

View file

@ -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;

View file

@ -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;

View file

@ -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<number>{
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<RoleList,Role>("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<RoleList,Role>("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<HTMLButtonElement,Role>();
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;

72
src/webpage/search.ts Normal file
View file

@ -0,0 +1,72 @@
import { Contextmenu } from "./contextmenu.js";
class Search<E>{
options:Map<string,E>;
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<E|undefined>{
return new Promise<E|undefined>((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};

View file

@ -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<Response>{
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;
}
})

View file

@ -30,6 +30,15 @@ class Buttons implements OptionsElement<unknown>{
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<unknown>{
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<boolean>{
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<object>{
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<object>{
}
console.log("middle2");
await Promise.allSettled(promises);
this.preprocessor(build);
if(this.fetchURL !== ""){
fetch(this.fetchURL, {
method: this.method,

View file

@ -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%;
}
}
}
.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;
}
}

View file

@ -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{

View file

@ -9,7 +9,8 @@
"incremental": true,
"lib": [
"esnext",
"DOM"
"DOM",
"webworker"
],
"module": "ESNext",
"moduleResolution": "Bundler",