compressed web socket support
This commit is contained in:
@@ -106,7 +106,7 @@ class Localuser {
|
|||||||
async initwebsocket() {
|
async initwebsocket() {
|
||||||
let returny = null;
|
let returny = null;
|
||||||
const promise = new Promise((res) => { returny = res; });
|
const promise = new Promise((res) => { returny = res; });
|
||||||
this.ws = new WebSocket(this.serverurls.gateway.toString());
|
this.ws = new WebSocket(this.serverurls.gateway.toString() + "?encoding=json&v=9" + (DecompressionStream ? "&compress=zlib-stream" : ""));
|
||||||
this.ws.addEventListener('open', (event) => {
|
this.ws.addEventListener('open', (event) => {
|
||||||
console.log('WebSocket connected');
|
console.log('WebSocket connected');
|
||||||
this.ws.send(JSON.stringify({
|
this.ws.send(JSON.stringify({
|
||||||
@@ -120,7 +120,7 @@ class Localuser {
|
|||||||
"release_channel": "Custom",
|
"release_channel": "Custom",
|
||||||
"browser_user_agent": navigator.userAgent
|
"browser_user_agent": navigator.userAgent
|
||||||
},
|
},
|
||||||
"compress": false,
|
"compress": !!DecompressionStream,
|
||||||
"presence": {
|
"presence": {
|
||||||
"status": "online",
|
"status": "online",
|
||||||
"since": new Date().getTime(),
|
"since": new Date().getTime(),
|
||||||
@@ -130,85 +130,46 @@ class Localuser {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
this.ws.addEventListener('message', (event) => {
|
const ds = new DecompressionStream("deflate");
|
||||||
const temp = JSON.parse(event.data);
|
const w = ds.writable.getWriter();
|
||||||
console.log(temp);
|
const r = ds.readable.getReader();
|
||||||
if (temp.s)
|
let arr = new Uint8Array();
|
||||||
this.lastSequence = temp.s;
|
let build = "";
|
||||||
if (temp.op == 0) {
|
this.ws.addEventListener('message', async (event) => {
|
||||||
switch (temp.t) {
|
let temp;
|
||||||
case "MESSAGE_CREATE":
|
if (event.data instanceof Blob) {
|
||||||
if (this.initialized) {
|
const buff = await event.data.arrayBuffer();
|
||||||
this.messageCreate(temp);
|
const array = new Uint8Array(buff);
|
||||||
}
|
const temparr = new Uint8Array(array.length + arr.length);
|
||||||
break;
|
temparr.set(arr, 0);
|
||||||
case "MESSAGE_DELETE":
|
temparr.set(array, arr.length);
|
||||||
console.log(temp.d);
|
arr = temparr;
|
||||||
SnowFlake.getSnowFlakeFromID(temp.d.id, Message).getObject().deleteEvent();
|
const len = array.length;
|
||||||
break;
|
if (!(array[len - 1] === 255 && array[len - 2] === 255 && array[len - 3] === 0 && array[len - 4] === 0)) {
|
||||||
case "READY":
|
return;
|
||||||
this.gottenReady(temp);
|
}
|
||||||
this.genusersettings();
|
w.write(arr.buffer);
|
||||||
returny();
|
arr = new Uint8Array();
|
||||||
break;
|
//console.log(data,test);
|
||||||
case "MESSAGE_UPDATE":
|
const read = (await r.read());
|
||||||
const message = SnowFlake.getSnowFlakeFromID(temp.d.id, Message).getObject();
|
const data = new TextDecoder().decode(read.value);
|
||||||
message.giveData(temp.d);
|
build += data;
|
||||||
break;
|
console.log("temp");
|
||||||
case "TYPING_START":
|
try {
|
||||||
if (this.initialized) {
|
temp = JSON.parse(build);
|
||||||
this.typingStart(temp);
|
build = "";
|
||||||
}
|
}
|
||||||
break;
|
catch {
|
||||||
case "USER_UPDATE":
|
return;
|
||||||
if (this.initialized) {
|
|
||||||
const users = SnowFlake.getSnowFlakeFromID(temp.d.id, User).getObject();
|
|
||||||
console.log(users, 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);
|
|
||||||
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").insertBefore(guildy.generateGuildIcon(), document.getElementById("bottomseparator"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (temp.op === 10) {
|
else {
|
||||||
console.log("heartbeat down");
|
temp = JSON.parse(event.data);
|
||||||
this.wsinterval = setInterval(_ => {
|
|
||||||
if (this.connectionSucceed === 0)
|
|
||||||
this.connectionSucceed = Date.now();
|
|
||||||
this.ws.send(JSON.stringify({ op: 1, d: this.lastSequence }));
|
|
||||||
}, temp.d.heartbeat_interval);
|
|
||||||
}
|
}
|
||||||
|
if (temp.op === 0 && temp.t === "READY") {
|
||||||
|
returny();
|
||||||
|
}
|
||||||
|
this.handleEvent(temp);
|
||||||
});
|
});
|
||||||
this.ws.addEventListener("close", event => {
|
this.ws.addEventListener("close", event => {
|
||||||
console.log("WebSocket closed with code " + event.code);
|
console.log("WebSocket closed with code " + event.code);
|
||||||
@@ -241,6 +202,84 @@ class Localuser {
|
|||||||
await promise;
|
await promise;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
handleEvent(temp) {
|
||||||
|
console.debug(temp);
|
||||||
|
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":
|
||||||
|
console.log(temp.d);
|
||||||
|
SnowFlake.getSnowFlakeFromID(temp.d.id, Message).getObject().deleteEvent();
|
||||||
|
break;
|
||||||
|
case "READY":
|
||||||
|
this.gottenReady(temp);
|
||||||
|
this.genusersettings();
|
||||||
|
break;
|
||||||
|
case "MESSAGE_UPDATE":
|
||||||
|
const message = SnowFlake.getSnowFlakeFromID(temp.d.id, Message).getObject();
|
||||||
|
message.giveData(temp.d);
|
||||||
|
break;
|
||||||
|
case "TYPING_START":
|
||||||
|
if (this.initialized) {
|
||||||
|
this.typingStart(temp);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "USER_UPDATE":
|
||||||
|
if (this.initialized) {
|
||||||
|
const users = SnowFlake.getSnowFlakeFromID(temp.d.id, User).getObject();
|
||||||
|
console.log(users, 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);
|
||||||
|
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").insertBefore(guildy.generateGuildIcon(), document.getElementById("bottomseparator"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (temp.op === 10) {
|
||||||
|
console.log("heartbeat down");
|
||||||
|
this.wsinterval = setInterval(_ => {
|
||||||
|
if (this.connectionSucceed === 0)
|
||||||
|
this.connectionSucceed = Date.now();
|
||||||
|
this.ws.send(JSON.stringify({ op: 1, d: this.lastSequence }));
|
||||||
|
}, temp.d.heartbeat_interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
resolveChannelFromID(ID) {
|
resolveChannelFromID(ID) {
|
||||||
let resolve = this.guilds.find(guild => guild.channelids[ID]);
|
let resolve = this.guilds.find(guild => guild.channelids[ID]);
|
||||||
if (resolve) {
|
if (resolve) {
|
||||||
|
@@ -99,7 +99,9 @@ class Member {
|
|||||||
}
|
}
|
||||||
return memb;
|
return memb;
|
||||||
}
|
}
|
||||||
const promoise = fetch(guild.info.api.toString() + "/users/" + id + "/profile?with_mutual_guilds=true&with_mutual_friends_count=true&guild_id=" + guild.snowflake, { headers: guild.headers }).then(_ => _.json()).then(json => {
|
const prom1 = fetch(guild.info.api.toString() + "/users/" + id + "/profile?with_mutual_guilds=true&with_mutual_friends_count=true&guild_id=" + guild.snowflake, { headers: guild.headers });
|
||||||
|
prom1.catch(_ => { console.log(_); });
|
||||||
|
const promoise = prom1.then(_ => _.json()).then(json => {
|
||||||
const memb = new Member(json, guild);
|
const memb = new Member(json, guild);
|
||||||
Member.already[guild.id][id] = memb;
|
Member.already[guild.id][id] = memb;
|
||||||
console.log("resolved");
|
console.log("resolved");
|
||||||
|
@@ -112,7 +112,7 @@ class Localuser{
|
|||||||
async initwebsocket():Promise<void>{
|
async initwebsocket():Promise<void>{
|
||||||
let returny=null
|
let returny=null
|
||||||
const promise=new Promise((res)=>{returny=res});
|
const promise=new Promise((res)=>{returny=res});
|
||||||
this.ws = new WebSocket(this.serverurls.gateway.toString());
|
this.ws = new WebSocket(this.serverurls.gateway.toString()+"?encoding=json&v=9"+(DecompressionStream?"&compress=zlib-stream":""));
|
||||||
this.ws.addEventListener('open', (event) => {
|
this.ws.addEventListener('open', (event) => {
|
||||||
console.log('WebSocket connected');
|
console.log('WebSocket connected');
|
||||||
this.ws.send(JSON.stringify({
|
this.ws.send(JSON.stringify({
|
||||||
@@ -126,7 +126,7 @@ class Localuser{
|
|||||||
"release_channel": "Custom",
|
"release_channel": "Custom",
|
||||||
"browser_user_agent": navigator.userAgent
|
"browser_user_agent": navigator.userAgent
|
||||||
},
|
},
|
||||||
"compress": false,
|
"compress": !!DecompressionStream,
|
||||||
"presence": {
|
"presence": {
|
||||||
"status": "online",
|
"status": "online",
|
||||||
"since": new Date().getTime(),
|
"since": new Date().getTime(),
|
||||||
@@ -136,90 +136,46 @@ class Localuser{
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
|
const ds = new DecompressionStream("deflate");
|
||||||
|
const w= ds.writable.getWriter();
|
||||||
|
const r=ds.readable.getReader();
|
||||||
|
let arr=new Uint8Array();
|
||||||
|
let build="";
|
||||||
|
this.ws.addEventListener('message', async (event) => {
|
||||||
|
let temp:{op:number,t:string};
|
||||||
|
if(event.data instanceof Blob){
|
||||||
|
const buff=await event.data.arrayBuffer()
|
||||||
|
const array=new Uint8Array(buff);
|
||||||
|
|
||||||
this.ws.addEventListener('message', (event) => {
|
const temparr=new Uint8Array(array.length+arr.length);
|
||||||
|
temparr.set(arr, 0);
|
||||||
|
temparr.set(array, arr.length);
|
||||||
|
arr=temparr;
|
||||||
|
|
||||||
|
const len=array.length;
|
||||||
|
if(!(array[len-1]===255&&array[len-2]===255&&array[len-3]===0&&array[len-4]===0)){
|
||||||
const temp=JSON.parse(event.data);
|
return;
|
||||||
console.log(temp)
|
|
||||||
|
|
||||||
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":
|
|
||||||
console.log(temp.d);
|
|
||||||
SnowFlake.getSnowFlakeFromID(temp.d.id,Message).getObject().deleteEvent();
|
|
||||||
break;
|
|
||||||
case "READY":
|
|
||||||
this.gottenReady(temp as readyjson);
|
|
||||||
this.genusersettings();
|
|
||||||
returny();
|
|
||||||
break;
|
|
||||||
case "MESSAGE_UPDATE":
|
|
||||||
const message=SnowFlake.getSnowFlakeFromID(temp.d.id,Message).getObject();
|
|
||||||
message.giveData(temp.d);
|
|
||||||
break;
|
|
||||||
case "TYPING_START":
|
|
||||||
if(this.initialized){
|
|
||||||
this.typingStart(temp);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "USER_UPDATE":
|
|
||||||
if(this.initialized){
|
|
||||||
const users=SnowFlake.getSnowFlakeFromID(temp.d.id,User).getObject() as User;
|
|
||||||
console.log(users,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);
|
|
||||||
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").insertBefore(guildy.generateGuildIcon(),document.getElementById("bottomseparator"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
w.write(arr.buffer);
|
||||||
}else if(temp.op===10){
|
arr=new Uint8Array();
|
||||||
console.log("heartbeat down")
|
//console.log(data,test);
|
||||||
this.wsinterval=setInterval(_=>{
|
const read=(await r.read());
|
||||||
if (this.connectionSucceed===0) this.connectionSucceed=Date.now()
|
const data=new TextDecoder().decode(read.value);
|
||||||
|
build+=data;
|
||||||
this.ws.send(JSON.stringify({op:1,d:this.lastSequence}))
|
console.log("temp");
|
||||||
},temp.d.heartbeat_interval)
|
try{
|
||||||
|
temp=JSON.parse(build);
|
||||||
|
build="";
|
||||||
|
}catch{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
temp=JSON.parse(event.data);
|
||||||
}
|
}
|
||||||
|
if(temp.op===0&&temp.t==="READY"){
|
||||||
|
returny();
|
||||||
|
}
|
||||||
|
this.handleEvent(temp);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.ws.addEventListener("close", event => {
|
this.ws.addEventListener("close", event => {
|
||||||
@@ -253,6 +209,83 @@ class Localuser{
|
|||||||
await promise;
|
await promise;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
handleEvent(temp){
|
||||||
|
console.debug(temp);
|
||||||
|
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":
|
||||||
|
console.log(temp.d);
|
||||||
|
SnowFlake.getSnowFlakeFromID(temp.d.id,Message).getObject().deleteEvent();
|
||||||
|
break;
|
||||||
|
case "READY":
|
||||||
|
this.gottenReady(temp as readyjson);
|
||||||
|
this.genusersettings();
|
||||||
|
break;
|
||||||
|
case "MESSAGE_UPDATE":
|
||||||
|
const message=SnowFlake.getSnowFlakeFromID(temp.d.id,Message).getObject();
|
||||||
|
message.giveData(temp.d);
|
||||||
|
break;
|
||||||
|
case "TYPING_START":
|
||||||
|
if(this.initialized){
|
||||||
|
this.typingStart(temp);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "USER_UPDATE":
|
||||||
|
if(this.initialized){
|
||||||
|
const users=SnowFlake.getSnowFlakeFromID(temp.d.id,User).getObject() as User;
|
||||||
|
console.log(users,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);
|
||||||
|
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").insertBefore(guildy.generateGuildIcon(),document.getElementById("bottomseparator"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}else if(temp.op===10){
|
||||||
|
console.log("heartbeat down")
|
||||||
|
this.wsinterval=setInterval(_=>{
|
||||||
|
if (this.connectionSucceed===0) this.connectionSucceed=Date.now()
|
||||||
|
|
||||||
|
this.ws.send(JSON.stringify({op:1,d:this.lastSequence}))
|
||||||
|
},temp.d.heartbeat_interval)
|
||||||
|
}
|
||||||
|
}
|
||||||
resolveChannelFromID(ID:string):Channel{
|
resolveChannelFromID(ID:string):Channel{
|
||||||
let resolve=this.guilds.find(guild => guild.channelids[ID]);
|
let resolve=this.guilds.find(guild => guild.channelids[ID]);
|
||||||
if(resolve){
|
if(resolve){
|
||||||
|
@@ -92,7 +92,9 @@ class Member{
|
|||||||
}
|
}
|
||||||
return memb;
|
return memb;
|
||||||
}
|
}
|
||||||
const promoise= fetch(guild.info.api.toString()+"/users/"+id+"/profile?with_mutual_guilds=true&with_mutual_friends_count=true&guild_id="+guild.snowflake,{headers:guild.headers}).then(_=>_.json()).then(json=>{
|
const prom1= fetch(guild.info.api.toString()+"/users/"+id+"/profile?with_mutual_guilds=true&with_mutual_friends_count=true&guild_id="+guild.snowflake,{headers:guild.headers})
|
||||||
|
prom1.catch(_=>{console.log(_)})
|
||||||
|
const promoise=prom1.then(_=>_.json()).then(json=>{
|
||||||
const memb=new Member(json,guild);
|
const memb=new Member(json,guild);
|
||||||
Member.already[guild.id][id]=memb;
|
Member.already[guild.id][id]=memb;
|
||||||
console.log("resolved")
|
console.log("resolved")
|
||||||
|
@@ -944,6 +944,7 @@ span {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
transition: transform .2s;
|
transition: transform .2s;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
#centerdiv {
|
#centerdiv {
|
||||||
@@ -951,6 +952,8 @@ span {
|
|||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
#loading.doneloading {
|
#loading.doneloading {
|
||||||
|
Reference in New Issue
Block a user