From 845c7f661217019b8b75b2cf098afc3600b7afa0 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Thu, 27 Jun 2024 11:27:38 -0500 Subject: [PATCH 1/7] TS conversion --- .dist/audio.js | 138 +++ .dist/channel.js | 648 ++++++++++++++ .dist/contextmenu.js | 63 ++ .dist/direct.js | 190 ++++ .dist/embed.js | 195 ++++ .dist/fullscreen.js | 247 ++++++ .dist/guild.js | 496 +++++++++++ .dist/index.js | 276 ++++++ .dist/localuser.js | 555 ++++++++++++ .dist/login.js | 237 +++++ .dist/markdown.js | 357 ++++++++ .dist/member.js | 80 ++ .dist/message.js | 318 +++++++ .dist/permissions.js | 246 +++++ .dist/role.js | 28 + .dist/service.js | 93 ++ .dist/user.js | 139 +++ Archive.tar.gz | Bin 0 -> 90298 bytes index.js | 11 + package-lock.json | 985 +++++++++++++++++++++ package.json | 7 +- tsconfig.json | 16 + webpage/{audio.js => audio.ts} | 62 +- webpage/{channel.js => channel.ts} | 188 ++-- webpage/{contextmenu.js => contextmenu.ts} | 37 +- webpage/{direct.js => direct.ts} | 61 +- webpage/{embed.js => embed.ts} | 17 +- webpage/favicon.ico | Bin 0 -> 2840 bytes webpage/{fullscreen.js => fullscreen.ts} | 13 +- webpage/{guild.js => guild.ts} | 156 +++- webpage/index.html | 40 +- webpage/{index.js => index.ts} | 217 ++--- webpage/{localuser.js => localuser.ts} | 177 ++-- webpage/login.html | 2 +- webpage/{login.js => login.ts} | 28 +- webpage/{markdown.js => markdown.ts} | 24 +- webpage/{member.js => member.ts} | 38 +- webpage/{message.js => message.ts} | 147 ++- webpage/{permissions.js => permissions.ts} | 46 +- webpage/role.js | 20 - webpage/role.ts | 28 + webpage/{service.js => service.ts} | 8 +- webpage/user.js | 61 -- webpage/user.ts | 141 +++ 44 files changed, 6225 insertions(+), 611 deletions(-) create mode 100644 .dist/audio.js create mode 100644 .dist/channel.js create mode 100644 .dist/contextmenu.js create mode 100644 .dist/direct.js create mode 100644 .dist/embed.js create mode 100644 .dist/fullscreen.js create mode 100644 .dist/guild.js create mode 100644 .dist/index.js create mode 100644 .dist/localuser.js create mode 100644 .dist/login.js create mode 100644 .dist/markdown.js create mode 100644 .dist/member.js create mode 100644 .dist/message.js create mode 100644 .dist/permissions.js create mode 100644 .dist/role.js create mode 100644 .dist/service.js create mode 100644 .dist/user.js create mode 100644 Archive.tar.gz create mode 100644 package-lock.json create mode 100644 tsconfig.json rename webpage/{audio.js => audio.ts} (71%) rename webpage/{channel.js => channel.ts} (76%) rename webpage/{contextmenu.js => contextmenu.ts} (53%) rename webpage/{direct.js => direct.ts} (79%) rename webpage/{embed.js => embed.ts} (94%) create mode 100644 webpage/favicon.ico rename webpage/{fullscreen.js => fullscreen.ts} (96%) rename webpage/{guild.js => guild.ts} (71%) rename webpage/{index.js => index.ts} (63%) rename webpage/{localuser.js => localuser.ts} (78%) rename webpage/{login.js => login.ts} (89%) rename webpage/{markdown.js => markdown.ts} (95%) rename webpage/{member.js => member.ts} (55%) rename webpage/{message.js => message.ts} (63%) rename webpage/{permissions.js => permissions.ts} (87%) delete mode 100644 webpage/role.js create mode 100644 webpage/role.ts rename webpage/{service.js => service.ts} (92%) delete mode 100644 webpage/user.js create mode 100644 webpage/user.ts diff --git a/.dist/audio.js b/.dist/audio.js new file mode 100644 index 0000000..e98dad7 --- /dev/null +++ b/.dist/audio.js @@ -0,0 +1,138 @@ +import { getBulkInfo } from "./login.js"; +class Voice { + audioCtx; + info; + playing; + myArrayBuffer; + gainNode; + buffer; + source; + constructor(wave, freq, volume = 1) { + this.audioCtx = new (window.AudioContext)(); + this.info = { wave: wave, freq: freq }; + this.playing = false; + this.myArrayBuffer = this.audioCtx.createBuffer(1, this.audioCtx.sampleRate, this.audioCtx.sampleRate); + this.gainNode = this.audioCtx.createGain(); + this.gainNode.gain.value = volume; + this.gainNode.connect(this.audioCtx.destination); + this.buffer = this.myArrayBuffer.getChannelData(0); + this.source = this.audioCtx.createBufferSource(); + this.source.buffer = this.myArrayBuffer; + this.source.loop = true; + this.source.start(); + this.updateWave(); + } + get wave() { + return this.info.wave; + } + get freq() { + return this.info.freq; + } + set wave(wave) { + this.info.wave = wave; + this.updateWave(); + } + set freq(freq) { + this.info.freq = freq; + this.updateWave(); + } + updateWave() { + const func = this.waveFucnion(); + for (let i = 0; i < this.buffer.length; i++) { + this.buffer[i] = func(i / this.audioCtx.sampleRate, this.freq); + } + } + waveFucnion() { + if (typeof this.wave === 'function') { + return this.wave; + } + switch (this.wave) { + case "sin": + return (t, freq) => { + return Math.sin(t * Math.PI * 2 * freq); + }; + case "triangle": + return (t, freq) => { + return Math.abs((4 * t * freq) % 4 - 2) - 1; + }; + case "sawtooth": + return (t, freq) => { + return ((t * freq) % 1) * 2 - 1; + }; + case "square": + return (t, freq) => { + return (t * freq) % 2 < 1 ? 1 : -1; + }; + case "white": + return (_t, _freq) => { + return Math.random() * 2 - 1; + }; + case "noise": + return (_t, _freq) => { + return 0; + }; + } + } + play() { + if (this.playing) { + return; + } + this.source.connect(this.gainNode); + this.playing = true; + } + stop() { + if (this.playing) { + this.source.disconnect(); + this.playing = false; + } + } + static noises(noise) { + switch (noise) { + case "three": { + const voicy = new Voice("sin", 800); + voicy.play(); + setTimeout(_ => { voicy.freq = 1000; }, 50); + setTimeout(_ => { voicy.freq = 1300; }, 100); + setTimeout(_ => { voicy.stop(); }, 150); + break; + } + case "zip": { + const voicy = new Voice((t, freq) => { + return Math.sin(((t + 2) ** (Math.cos(t * 4))) * Math.PI * 2 * freq); + }, 700); + voicy.play(); + setTimeout(_ => { voicy.stop(); }, 150); + break; + } + case "square": { + const voicy = new Voice("square", 600, .4); + voicy.play(); + setTimeout(_ => { voicy.freq = 800; }, 50); + setTimeout(_ => { voicy.freq = 1000; }, 100); + setTimeout(_ => { voicy.stop(); }, 150); + break; + } + case "beep": { + const voicy = new Voice("sin", 800); + voicy.play(); + setTimeout(_ => { voicy.stop(); }, 50); + setTimeout(_ => { voicy.play(); }, 100); + setTimeout(_ => { voicy.stop(); }, 150); + break; + } + } + } + static get sounds() { + return ["three", "zip", "square", "beep"]; + } + static setNotificationSound(sound) { + let userinfos = getBulkInfo(); + userinfos.preferances.notisound = sound; + localStorage.setItem("userinfos", JSON.stringify(userinfos)); + } + static getNotificationSound() { + let userinfos = getBulkInfo(); + return userinfos.preferances.notisound; + } +} +export { Voice as Voice }; diff --git a/.dist/channel.js b/.dist/channel.js new file mode 100644 index 0000000..1d65758 --- /dev/null +++ b/.dist/channel.js @@ -0,0 +1,648 @@ +"use strict"; +import { Message } from "./message.js"; +import { Voice } from "./audio.js"; +import { Contextmenu } from "./contextmenu.js"; +import { Fullscreen } from "./fullscreen.js"; +import { markdown } from "./markdown.js"; +import { Permissions } from "./permissions.js"; +class Channel { + editing; + type; + owner; + headers; + messages; + name; + id; + parent_id; + parrent; + children; + guild_id; + messageids; + permission_overwrites; + topic; + nsfw; + position; + lastreadmessageid; + lastmessageid; + mentions; + lastpin; + move_id; + typing; + message_notifications; + allthewayup; + static contextmenu = new Contextmenu("channel menu"); + static setupcontextmenu() { + Channel.contextmenu.addbutton("Copy channel id", function () { + console.log(this); + navigator.clipboard.writeText(this.id); + }); + Channel.contextmenu.addbutton("Mark as read", function () { + console.log(this); + this.readbottom(); + }); + Channel.contextmenu.addbutton("Delete channel", function () { + console.log(this); + this.deleteChannel(); + }, null, _ => { console.log(_); return _.isAdmin(); }); + Channel.contextmenu.addbutton("Edit channel", function () { + this.editChannel(this); + }, null, _ => { return _.isAdmin(); }); + } + constructor(JSON, owner) { + if (JSON === -1) { + return; + } + this.editing; + this.type = JSON.type; + this.owner = owner; + this.headers = this.owner.headers; + this.messages = []; + this.name = JSON.name; + this.id = JSON.id; + this.parent_id = JSON.parent_id; + this.parrent = null; + this.children = []; + this.guild_id = JSON.guild_id; + this.messageids = {}; + this.permission_overwrites = {}; + for (const thing of JSON.permission_overwrites) { + this.permission_overwrites[thing.id] = new Permissions(thing.allow, thing.deny); + } + console.log(this.permission_overwrites); + this.topic = JSON.topic; + this.nsfw = JSON.nsfw; + this.position = JSON.position; + this.lastreadmessageid = null; + this.lastmessageid = JSON.last_message_id; + } + isAdmin() { + return this.guild.isAdmin(); + } + get guild() { + return this.owner; + } + get localuser() { + return this.guild.localuser; + } + get info() { + return this.owner.info; + } + readStateInfo(json) { + this.lastreadmessageid = json.last_message_id; + this.mentions = json.mention_count; + this.mentions ??= 0; + this.lastpin = json.last_pin_timestamp; + } + get hasunreads() { + return this.lastmessageid !== this.lastreadmessageid && this.type !== 4; + } + get canMessage() { + console.log("this should run"); + for (const thing of Object.entries(this.permission_overwrites)) { + const perm = thing[1].getPermision("SEND_MESSAGES"); + if (perm === 1) { + return true; + } + if (perm === -1) { + return false; + } + } + return true; + } + sortchildren() { + this.children.sort((a, b) => { return a.position - b.position; }); + } + resolveparent(guild) { + this.parrent = guild.channelids[this.parent_id]; + this.parrent ??= null; + if (this.parrent !== null) { + this.parrent.children.push(this); + } + return this.parrent === null; + } + calculateReorder() { + let position = -1; + let build = []; + for (const thing of this.children) { + const thisthing = { id: thing.id, position: undefined, parent_id: undefined }; + if (thing.position < position) { + thing.position = thisthing.position = position + 1; + } + position = thing.position; + if (thing.move_id && thing.move_id !== thing.parent_id) { + thing.parent_id = thing.move_id; + thisthing.parent_id = thing.parent_id; + thing.move_id = undefined; + console.log(this.guild.channelids[thisthing.parent_id]); + } + if (thisthing.position || thisthing.parent_id) { + build.push(thisthing); + } + } + return build; + } + static dragged = []; + createguildHTML(admin = false) { + const div = document.createElement("div"); + div["all"] = this; + div.draggable = admin; + div.addEventListener("dragstart", (e) => { Channel.dragged = [this, div]; e.stopImmediatePropagation(); }); + div.addEventListener("dragend", () => { Channel.dragged = []; }); + if (this.type === 4) { + this.sortchildren(); + const caps = document.createElement("div"); + const decdiv = document.createElement("div"); + const decoration = document.createElement("b"); + decoration.textContent = "â–ŧ"; + decdiv.appendChild(decoration); + const myhtml = document.createElement("p2"); + myhtml.textContent = this.name; + decdiv.appendChild(myhtml); + caps.appendChild(decdiv); + const childrendiv = document.createElement("div"); + if (admin) { + const addchannel = document.createElement("span"); + addchannel.textContent = "+"; + addchannel.classList.add("addchannel"); + caps.appendChild(addchannel); + addchannel.onclick = function () { + this.guild.createchannels(this.createChannel.bind(this)); + }.bind(this); + this.coatDropDiv(decdiv, childrendiv); + } + div.appendChild(caps); + caps.classList.add("capsflex"); + decdiv.classList.add("channeleffects"); + decdiv.classList.add("channel"); + Channel.contextmenu.bind(decdiv, this); + decdiv["all"] = this; + for (const channel of this.children) { + childrendiv.appendChild(channel.createguildHTML(admin)); + } + childrendiv.classList.add("channels"); + setTimeout(_ => { childrendiv.style.height = childrendiv.scrollHeight + 'px'; }, 100); + decdiv.onclick = function () { + if (decoration.textContent === "â–ŧ") { // + decoration.textContent = "▲"; + //childrendiv.classList.add("colapsediv"); + childrendiv.style.height = '0px'; + } + else { + decoration.textContent = "â–ŧ"; + //childrendiv.classList.remove("colapsediv") + childrendiv.style.height = childrendiv.scrollHeight + 'px'; + } + }; + div.appendChild(childrendiv); + } + else { + div.classList.add("channel"); + if (this.hasunreads) { + div.classList.add("cunread"); + } + Channel.contextmenu.bind(div, this); + if (admin) { + this.coatDropDiv(div); + } + div["all"] = this; + const myhtml = document.createElement("span"); + myhtml.textContent = this.name; + if (this.type === 0) { + const decoration = document.createElement("b"); + decoration.textContent = "#"; + div.appendChild(decoration); + decoration.classList.add("space"); + } + else if (this.type === 2) { // + const decoration = document.createElement("b"); + decoration.textContent = "đŸ•Ē"; + div.appendChild(decoration); + decoration.classList.add("spacee"); + } + else if (this.type === 5) { // + const decoration = document.createElement("b"); + decoration.textContent = "đŸ“Ŗ"; + div.appendChild(decoration); + decoration.classList.add("spacee"); + } + else { + console.log(this.type); + } + div.appendChild(myhtml); + div.onclick = _ => { + this.getHTML(); + }; + } + return div; + } + get myhtml() { + const search = document.getElementById("channels").children[0].children; + if (this.guild !== this.localuser.lookingguild) { + return null; + } + else if (this.parrent) { + for (const thing of search) { + if (thing["all"] === this.parrent) { + for (const thing2 of thing.children[1].children) { + if (thing2["all"] === this) { + return thing2; + } + } + } + } + } + else { + for (const thing of search) { + if (thing["all"] === this) { + return thing; + } + } + } + return null; + } + readbottom() { + if (!this.hasunreads) { + return; + } + fetch(this.info.api.toString() + "/v9/channels/" + this.id + "/messages/" + this.lastmessageid + "/ack", { + method: "POST", + headers: this.headers, + body: JSON.stringify({}) + }); + this.lastreadmessageid = this.lastmessageid; + this.guild.unreads(); + if (this.myhtml !== null) { + this.myhtml.classList.remove("cunread"); + } + } + coatDropDiv(div, container = false) { + div.addEventListener("dragenter", (event) => { + console.log("enter"); + event.preventDefault(); + }); + div.addEventListener("dragover", (event) => { + event.preventDefault(); + }); + div.addEventListener("drop", (event) => { + const that = Channel.dragged[0]; + event.preventDefault(); + if (container) { + that.move_id = this.id; + if (that.parrent) { + that.parrent.children.splice(that.parrent.children.indexOf(that), 1); + } + that.parrent = this; + container.prepend(Channel.dragged[1]); + this.children.unshift(that); + } + else { + console.log(this, Channel.dragged); + that.move_id = this.parent_id; + if (that.parrent) { + that.parrent.children.splice(that.parrent.children.indexOf(that), 1); + } + else { + this.guild.headchannels.splice(this.guild.headchannels.indexOf(that), 1); + } + that.parrent = this.parrent; + if (that.parrent) { + const build = []; + for (let i = 0; i < that.parrent.children.length; i++) { + build.push(that.parrent.children[i]); + if (that.parrent.children[i] === this) { + build.push(that); + } + } + that.parrent.children = build; + } + else { + const build = []; + for (let i = 0; i < this.guild.headchannels.length; i++) { + build.push(this.guild.headchannels[i]); + if (this.guild.headchannels[i] === this) { + build.push(that); + } + } + this.guild.headchannels = build; + } + div.after(Channel.dragged[1]); + } + this.guild.calculateReorder(); + }); + return div; + } + createChannel(name, type) { + fetch(this.info.api.toString() + "/guilds/" + this.guild.id + "/channels", { + method: "Post", + headers: this.headers, + body: JSON.stringify({ + name: name, + type: type, + parent_id: this.id, + permission_overwrites: [], + }) + }); + } + editChannel() { + let name = this.name; + let topic = this.topic; + let nsfw = this.nsfw; + const thisid = this.id; + const thistype = this.type; + const full = new Fullscreen(["hdiv", + ["vdiv", + ["textbox", "Channel name:", this.name, function () { name = this.value; }], + ["mdbox", "Channel topic:", this.topic, function () { topic = this.value; }], + ["checkbox", "NSFW Channel", this.nsfw, function () { nsfw = this.checked; }], + ["button", "", "submit", function () { + fetch(this.info.api.toString() + "/v9/channels/" + thisid, { + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + "name": name, + "type": thistype, + "topic": topic, + "bitrate": 64000, + "user_limit": 0, + "nsfw": nsfw, + "flags": 0, + "rate_limit_per_user": 0 + }) + }); + console.log(full); + full.hide(); + }] + ] + ]); + full.show(); + console.log(full); + } + deleteChannel() { + fetch(this.info.api.toString() + "/v9/channels/" + this.id, { + method: "DELETE", + headers: this.headers + }); + } + getHTML() { + if (this.guild !== this.localuser.lookingguild) { + this.guild.loadGuild(); + } + this.guild.prevchannel = this; + this.localuser.channelfocus = this; + this.putmessages(); + history.pushState(null, null, "/channels/" + this.guild_id + "/" + this.id); + document.getElementById("channelname").textContent = "#" + this.name; + console.log(this); + document.getElementById("typebox").disabled = !this.canMessage; + } + putmessages() { + const out = this; + fetch(this.info.api.toString() + "/channels/" + this.id + "/messages?limit=100", { + method: 'GET', + headers: this.headers, + }).then((j) => { return j.json(); }).then(responce => { + document.getElementById("messages").innerHTML = ''; + for (const thing of responce) { + const messager = new Message(thing, this); + if (out.messageids[messager.id] == undefined) { + out.messageids[messager.id] = messager; + out.messages.push(messager); + } + } + out.buildmessages(); + }); + } + delChannel(JSON) { + const build = []; + for (const thing of this.children) { + if (thing.id !== JSON.id) { + build.push(thing); + } + } + this.children = build; + } + async grabmoremessages() { + if (this.messages.length === 0 || this.allthewayup) { + return; + } + const out = this; + await fetch(this.info.api.toString() + "/channels/" + this.id + "/messages?before=" + this.messages[this.messages.length - 1].id + "&limit=100", { + method: "GET", + headers: this.headers + }).then((j) => { return j.json(); }).then(responce => { + //messages.innerHTML = ''; + //responce.reverse() + let next; + if (responce.length === 0) { + out.allthewayup = true; + } + for (const i in responce) { + let messager; + if (!next) { + messager = new Message(responce[i], this); + } + else { + messager = next; + } + if (responce[+i + 1] !== undefined) { + next = new Message(responce[+i + 1], this); + } + else { + next = undefined; + console.log("ohno", +i + 1); + } + if (out.messageids[messager.id] == undefined) { + out.messageids[messager.id] = messager; + out.buildmessage(messager, next); + out.messages.push(messager); + } + else { + console.log("How???"); + } + } + //out.buildmessages(); + }); + return; + } + buildmessage(message, next) { + const built = message.buildhtml(next); + document.getElementById("messages").prepend(built); + } + buildmessages() { + for (const i in this.messages) { + const prev = this.messages[(+i) + 1]; + const built = this.messages[i].buildhtml(prev); + document.getElementById("messages").prepend(built); + } + document.getElementById("messagecontainer").scrollTop = document.getElementById("messagecontainer").scrollHeight; + } + updateChannel(JSON) { + this.type = JSON.type; + this.name = JSON.name; + this.parent_id = JSON.parent_id; + this.parrent = null; + this.children = []; + this.guild_id = JSON.guild_id; + this.messageids = {}; + this.permission_overwrites = JSON.permission_overwrites; + this.topic = JSON.topic; + this.nsfw = JSON.nsfw; + } + typingstart() { + if (this.typing > new Date().getTime()) { + return; + } + this.typing = new Date().getTime() + 6000; + fetch(this.info.api.toString() + "/channels/" + this.id + "/typing", { + method: "POST", + headers: this.headers + }); + } + get notification() { + let notinumber = this.message_notifications; + if (+notinumber === 3) { + notinumber = null; + } + notinumber ??= this.guild.message_notifications; + switch (+notinumber) { + case 0: + return "all"; + case 1: + return "mentions"; + case 2: + return "none"; + case 3: + return "default"; + } + } + async sendMessage(content, { attachments = [], embeds = [], replyingto = null }) { + let replyjson; + if (replyingto) { + replyjson = + { + "guild_id": replyingto.guild.id, + "channel_id": replyingto.channel.id, + "message_id": replyingto.id, + }; + } + ; + if (attachments.length === 0) { + const body = { + content: content, + nonce: Math.floor(Math.random() * 1000000000), + message_reference: undefined + }; + if (replyjson) { + body.message_reference = replyjson; + } + console.log(body); + return await fetch(this.info.api.toString() + "/channels/" + this.id + "/messages", { + method: "POST", + headers: this.headers, + body: JSON.stringify(body) + }); + } + else { + const formData = new FormData(); + const body = { + content: content, + nonce: Math.floor(Math.random() * 1000000000), + message_reference: undefined + }; + if (replyjson) { + body.message_reference = replyjson; + } + formData.append('payload_json', JSON.stringify(body)); + for (const i in attachments) { + console.log(attachments[i]); + formData.append("files[" + i + "]", attachments[i]); + } + return await fetch(this.info.api.toString() + "/channels/" + this.id + "/messages", { + method: 'POST', + body: formData, + headers: { "Authorization": this.headers.Authorization } + }); + } + } + messageCreate(messagep) { + const messagez = new Message(messagep.d, this); + this.lastmessageid = messagez.id; + if (messagez.author === this.localuser.user) { + this.lastreadmessageid = messagez.id; + if (this.myhtml) { + this.myhtml.classList.remove("cunread"); + } + } + else { + if (this.myhtml) { + this.myhtml.classList.add("cunread"); + } + } + this.guild.unreads(); + this.messages.unshift(messagez); + const scrolly = document.getElementById("messagecontainer"); + this.messageids[messagez.id] = messagez; + if (this.localuser.lookingguild.prevchannel === this) { + var shouldScroll = scrolly.scrollTop + scrolly.clientHeight > scrolly.scrollHeight - 20; + document.getElementById("messages").appendChild(messagez.buildhtml(this.messages[1])); + } + if (shouldScroll) { + scrolly.scrollTop = scrolly.scrollHeight; + } + if (messagez.author === this.localuser.user) { + return; + } + if (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.notify(messagez); + } + } + notititle(message) { + return message.author.username + " > " + this.guild.properties.name + " > " + this.name; + } + notify(message, deep = 0) { + Voice.noises(Voice.getNotificationSound()); + if (!("Notification" in window)) { + } + else if (Notification.permission === "granted") { + let noticontent = markdown(message.content).textContent; + if (message.embeds[0]) { + noticontent ||= message.embeds[0].json.title; + noticontent ||= markdown(message.embeds[0].json.description).textContent; + } + noticontent ||= "Blank Message"; + let imgurl = null; + const images = message.getimages(); + if (images.length) { + const image = images[0]; + imgurl ||= image.proxy_url; + imgurl ||= image.url; + } + const notification = new Notification(this.notititle(message), { + body: noticontent, + icon: message.author.getpfpsrc(), + image: imgurl, + }); + notification.addEventListener("click", _ => { + window.focus(); + this.getHTML(); + }); + } + else if (Notification.permission !== "denied") { + Notification.requestPermission().then(() => { + if (deep === 3) { + return; + } + ; + this.notify(message, deep + 1); + }); + } + } +} +Channel.setupcontextmenu(); +export { Channel }; diff --git a/.dist/contextmenu.js b/.dist/contextmenu.js new file mode 100644 index 0000000..dc7d998 --- /dev/null +++ b/.dist/contextmenu.js @@ -0,0 +1,63 @@ +class Contextmenu { + static currentmenu; + name; + buttons; + div; + static setup() { + Contextmenu.currentmenu = ""; + document.addEventListener('click', function (event) { + if (Contextmenu.currentmenu == "") { + return; + } + if (!Contextmenu.currentmenu.contains(event.target)) { + Contextmenu.currentmenu.remove(); + Contextmenu.currentmenu = ""; + } + }); + } + constructor(name) { + this.name = name; + this.buttons = []; + } + addbutton(text, onclick, img = null, shown = _ => true, enabled = _ => true) { + this.buttons.push([text, onclick, img, shown, enabled]); + return {}; + } + makemenu(x, y, addinfo, obj) { + const div = document.createElement("table"); + div.classList.add("contextmenu"); + for (const thing of this.buttons) { + if (!thing[3](addinfo)) { + continue; + } + const textb = document.createElement("tr"); + const intext = document.createElement("button"); + intext.disabled = !thing[4](); + textb["button"] = intext; + intext.classList.add("contextbutton"); + intext.textContent = thing[0]; + textb.appendChild(intext); + console.log(thing); + intext.onclick = thing[1].bind(addinfo, obj); + div.appendChild(textb); + } + if (Contextmenu.currentmenu != "") { + Contextmenu.currentmenu.remove(); + } + div.style.top = y + 'px'; + div.style.left = x + 'px'; + document.body.appendChild(div); + console.log(div); + Contextmenu.currentmenu = div; + return this.div; + } + bind(obj, addinfo = undefined) { + obj.addEventListener("contextmenu", (event) => { + event.preventDefault(); + event.stopImmediatePropagation(); + this.makemenu(event.clientX, event.clientY, addinfo, obj); + }); + } +} +Contextmenu.setup(); +export { Contextmenu as Contextmenu }; diff --git a/.dist/direct.js b/.dist/direct.js new file mode 100644 index 0000000..9fcf9f6 --- /dev/null +++ b/.dist/direct.js @@ -0,0 +1,190 @@ +import { Guild } from "./guild.js"; +import { Channel } from "./channel.js"; +import { Message } from "./message.js"; +import { User } from "./user.js"; +class Direct extends Guild { + constructor(JSON, owner) { + super(-1, owner); + this.message_notifications = 0; + console.log(JSON); + this.owner = owner; + if (!this.localuser) { + console.error("Owner was not included, please fix"); + } + this.headers = this.localuser.headers; + this.channels = []; + this.channelids = {}; + this.id = "@me"; + this.properties = {}; + this.roles = []; + this.roleids = {}; + this.prevchannel = undefined; + this.properties.name = "Dirrect Messages"; + for (const thing of JSON) { + const temp = new Group(thing, this); + this.channels.push(temp); + this.channelids[temp.id] = temp; + } + this.headchannels = this.channels; + } + createChannelpac(JSON) { + const thischannel = new Group(JSON, this); + this.channelids[JSON.id] = thischannel; + this.channels.push(thischannel); + this.calculateReorder(); + this.printServers(); + } + sortchannels() { + this.headchannels.sort((a, b) => { + const result = (BigInt(a.lastmessageid) - BigInt(b.lastmessageid)); + return Number(-result); + }); + } + giveMember(member) { + console.error("not a real guild, can't give member object"); + } + getRole(ID) { + return null; + } + hasRole(r) { + return false; + } + isAdmin() { + return false; + } + unreaddms() { + for (const thing of this.channels) { + thing.unreads(); + } + } +} +class Group extends Channel { + user; + constructor(JSON, owner) { + super(-1, owner); + this.owner = owner; + this.headers = this.guild.headers; + this.messages = []; + this.name = JSON.recipients[0]?.username; + if (JSON.recipients[0]) { + this.user = new User(JSON.recipients[0], this.localuser); + } + else { + this.user = this.localuser.user; + } + this.name ??= this.localuser.user.username; + this.id = JSON.id; + this.parent_id = null; + this.parrent = null; + this.children = []; + this.guild_id = "@me"; + this.messageids = {}; + this.permission_overwrites = {}; + this.lastmessageid = JSON.last_message_id; + this.lastmessageid ??= "0"; + this.mentions = 0; + } + createguildHTML() { + const div = document.createElement("div"); + div.classList.add("channeleffects"); + const myhtml = document.createElement("span"); + myhtml.textContent = this.name; + div.appendChild(this.user.buildpfp()); + div.appendChild(myhtml); + div["myinfo"] = this; + div.onclick = _ => { + this.getHTML(); + }; + return div; + } + getHTML() { + this.guild.prevchannel = this; + this.localuser.channelfocus = this; + this.putmessages(); + history.pushState(null, null, "/channels/" + this.guild_id + "/" + this.id); + document.getElementById("channelname").textContent = "@" + this.name; + } + messageCreate(messagep) { + const messagez = new Message(messagep.d, this); + this.lastmessageid = messagez.id; + if (messagez.author === this.localuser.user) { + this.lastreadmessageid = messagez.id; + } + this.messages.unshift(messagez); + const scrolly = document.getElementById("messagecontainer"); + this.messageids[messagez.id] = messagez; + if (this.localuser.lookingguild.prevchannel === this) { + var shouldScroll = scrolly.scrollTop + scrolly.clientHeight > scrolly.scrollHeight - 20; + document.getElementById("messages").appendChild(messagez.buildhtml(this.messages[1])); + } + if (shouldScroll) { + scrolly.scrollTop = scrolly.scrollHeight; + } + console.log(document.getElementById("channels").children); + if (this.localuser.lookingguild === this.guild) { + const channellist = document.getElementById("channels").children[0]; + for (const thing of channellist.children) { + if (thing["myinfo"] === this) { + channellist.prepend(thing); + break; + } + } + } + this.unreads(); + if (messagez.author === this.localuser.user) { + return; + } + if (this.localuser.lookingguild.prevchannel === this && document.hasFocus()) { + return; + } + console.log(this.notification); + if (this.notification === "all") { + this.notify(messagez); + } + else if (this.notification === "mentions" && messagez.mentionsuser(this.localuser.user)) { + this.notify(messagez); + } + } + notititle(message) { + return message.author.username; + } + unreads() { + const sentdms = document.getElementById("sentdms"); + let current = null; + for (const thing of sentdms.children) { + if (thing["all"] === this) { + current = thing; + } + } + if (this.hasunreads) { + if (current) { + current.noti.textContent = this.mentions; + return; + } + const div = document.createElement("div"); + div.classList.add("servernoti"); + const noti = document.createElement("div"); + noti.classList.add("unread", "notiunread", "pinged"); + noti.textContent = "" + this.mentions; + console.log(this.mentions); + div["noti"] = noti; + div.append(noti); + const buildpfp = this.user.buildpfp(); + div["all"] = this; + buildpfp.classList.add("mentioned"); + console.log(this); + div.append(buildpfp); + sentdms.append(div); + div.onclick = function () { + this["noti"].guild.loadGuild(); + this["noti"].getHTML(); + }; + } + else if (current) { + current.remove(); + } + else { + } + } +} +export { Direct, Group }; diff --git a/.dist/embed.js b/.dist/embed.js new file mode 100644 index 0000000..67cb7ea --- /dev/null +++ b/.dist/embed.js @@ -0,0 +1,195 @@ +import { Fullscreen } from "./fullscreen.js"; +class Embed { + type; + owner; + json; + constructor(json, owner) { + this.type = this.getType(json); + this.owner = owner; + this.json = json; + } + getType(json) { + return json.type || "rich"; + } + generateHTML() { + switch (this.type) { + case "rich": + return this.generateRich(); + case "image": + return this.generateImage(); + case "link": + return this.generateLink(); + case "article": + return this.generateArticle(); + default: + console.warn(`unsupported embed type ${this.type}, please add support dev :3`, this.json); + return document.createElement("div"); //prevent errors by giving blank div + } + } + generateRich() { + console.log(this.json); + const div = document.createElement("div"); + if (this.json.color) { + div.style.backgroundColor = "#" + this.json.color.toString(16); + } + div.classList.add("embed-color"); + const embed = document.createElement("div"); + embed.classList.add("embed"); + div.append(embed); + if (this.json.author) { + const authorline = document.createElement("div"); + if (this.json.author.icon_url) { + const img = document.createElement("img"); + img.classList.add("embedimg"); + img.src = this.json.author.icon_url; + authorline.append(img); + } + const a = document.createElement("a"); + a.innerText = this.json.author.name; + if (this.json.author.url) { + a.href = this.json.author.url; + } + a.classList.add("username"); + authorline.append(a); + embed.append(authorline); + } + const title = document.createElement("a"); + title.textContent = this.json.title; + if (this.json.url) { + title.href = this.json.url; + } + title.classList.add("embedtitle"); + embed.append(title); + if (this.json.description) { + const p = document.createElement("p"); + p.textContent = this.json.description; + embed.append(p); + } + embed.append(document.createElement("br")); + if (this.json.fields) { + for (const thing of this.json.fields) { + const div = document.createElement("div"); + const b = document.createElement("b"); + b.textContent = thing.name; + div.append(b); + let p; + p = document.createElement("p"); + p.textContent = thing.value; + p.classList.add("embedp"); + div.append(p); + if (thing.inline) { + div.classList.add("inline"); + } + embed.append(div); + } + } + if (this.json.footer || this.json.timestamp) { + const footer = document.createElement("div"); + if (this.json?.footer?.icon_url) { + const img = document.createElement("img"); + img.src = this.json.footer.icon_url; + img.classList.add("embedicon"); + footer.append(img); + } + if (this.json?.footer?.text) { + const span = document.createElement("span"); + span.textContent = this.json.footer.text; + span.classList.add("spaceright"); + footer.append(span); + } + if (this.json?.footer && this.json?.timestamp) { + const span = document.createElement("span"); + span.textContent = "â€ĸ"; + span.classList.add("spaceright"); + footer.append(span); + } + if (this.json?.timestamp) { + const span = document.createElement("span"); + span.textContent = new Date(this.json.timestamp).toLocaleString(); + ; + footer.append(span); + } + embed.append(footer); + } + return div; + } + generateImage() { + const img = document.createElement("img"); + img.classList.add("messageimg"); + img.onclick = function () { + const full = new Fullscreen(["img", img.src, ["fit"]]); + full.show(); + }; + img.src = this.json.thumbnail.proxy_url; + return img; + } + generateLink() { + const table = document.createElement("table"); + table.classList.add("embed", "linkembed"); + const trtop = document.createElement("tr"); + table.append(trtop); + { + const td = document.createElement("td"); + const a = document.createElement("a"); + a.href = this.json.url; + a.textContent = this.json.title; + td.append(a); + trtop.append(td); + } + { + const td = document.createElement("td"); + const img = document.createElement("img"); + if (this.json.thumbnail) { + img.classList.add("embedimg"); + img.onclick = function () { + const full = new Fullscreen(["img", img.src, ["fit"]]); + full.show(); + }; + img.src = this.json.thumbnail.proxy_url; + td.append(img); + } + trtop.append(td); + } + const bottomtr = document.createElement("tr"); + const td = document.createElement("td"); + const span = document.createElement("span"); + span.textContent = this.json.description; + td.append(span); + bottomtr.append(td); + table.append(bottomtr); + return table; + } + generateArticle() { + const colordiv = document.createElement("div"); + colordiv.style.backgroundColor = "#000000"; + colordiv.classList.add("embed-color"); + const div = document.createElement("div"); + div.classList.add("embed"); + if (this.json.provider) { + const providor = document.createElement("p"); + providor.classList.add("provider"); + providor.textContent = this.json.provider.name; + div.append(providor); + } + const a = document.createElement("a"); + a.href = this.json.url; + a.textContent = this.json.title; + div.append(a); + const description = document.createElement("p"); + description.textContent = this.json.description; + div.append(description); + { + const img = document.createElement("img"); + img.classList.add("bigembedimg"); + img.onclick = function () { + const full = new Fullscreen(["img", img.src, ["fit"]]); + full.show(); + }; + img.src = this.json.thumbnail.proxy_url; + div.append(img); + } + colordiv.append(div); + return colordiv; + } +} +export { Embed }; diff --git a/.dist/fullscreen.js b/.dist/fullscreen.js new file mode 100644 index 0000000..3d271b0 --- /dev/null +++ b/.dist/fullscreen.js @@ -0,0 +1,247 @@ +export { Fullscreen }; +class Fullscreen { + layout; + onclose; + onopen; + html; + background; + constructor(layout, onclose = _ => { }, onopen = _ => { }) { + this.layout = layout; + this.onclose = onclose; + this.onopen = onopen; + const div = document.createElement("div"); + div.appendChild(this.tohtml(layout)); + this.html = div; + this.html.classList.add("centeritem"); + if (!(layout[0] === "img")) { + this.html.classList.add("nonimagecenter"); + } + } + tohtml(array) { + switch (array[0]) { + case "img": + const img = document.createElement("img"); + img.src = array[1]; + if (array[2] != undefined) { + if (array[2].length == 2) { + img.width = array[2][0]; + img.height = array[2][1]; + } + else if (array[2][0] == "fit") { + img.classList.add("imgfit"); + } + } + return img; + case "hdiv": + const hdiv = document.createElement("table"); + const tr = document.createElement("tr"); + hdiv.appendChild(tr); + for (const thing of array) { + if (thing === "hdiv") { + continue; + } + const td = document.createElement("td"); + td.appendChild(this.tohtml(thing)); + tr.appendChild(td); + } + return hdiv; + case "vdiv": + const vdiv = document.createElement("table"); + for (const thing of array) { + if (thing === "vdiv") { + continue; + } + const tr = document.createElement("tr"); + tr.appendChild(this.tohtml(thing)); + vdiv.appendChild(tr); + } + return vdiv; + case "checkbox": + { + const div = document.createElement("div"); + const checkbox = document.createElement('input'); + div.appendChild(checkbox); + const label = document.createElement("span"); + checkbox.value = array[2]; + label.textContent = array[1]; + div.appendChild(label); + checkbox.addEventListener("change", array[3]); + checkbox.type = "checkbox"; + return div; + } + case "button": + { + const div = document.createElement("div"); + const input = document.createElement('button'); + const label = document.createElement("span"); + input.textContent = array[2]; + label.textContent = array[1]; + div.appendChild(label); + div.appendChild(input); + input.addEventListener("click", array[3]); + return div; + } + case "mdbox": + { + const div = document.createElement("div"); + const input = document.createElement("textarea"); + input.value = array[2]; + const label = document.createElement("span"); + label.textContent = array[1]; + input.addEventListener("input", array[3]); + div.appendChild(label); + div.appendChild(document.createElement("br")); + div.appendChild(input); + return div; + } + case "textbox": + { + const div = document.createElement("div"); + const input = document.createElement("input"); + input.value = array[2]; + input.type = "text"; + const label = document.createElement("span"); + label.textContent = array[1]; + console.log(array[3]); + input.addEventListener("input", array[3]); + div.appendChild(label); + div.appendChild(input); + return div; + } + case "fileupload": + { + const div = document.createElement("div"); + const input = document.createElement("input"); + input.type = "file"; + const label = document.createElement("span"); + label.textContent = array[1]; + div.appendChild(label); + div.appendChild(input); + input.addEventListener("change", array[2]); + console.log(array); + return div; + } + case "text": { + const span = document.createElement("span"); + span.textContent = array[1]; + return span; + } + case "title": { + const span = document.createElement("span"); + span.classList.add("title"); + span.textContent = array[1]; + return span; + } + case "radio": { + const div = document.createElement("div"); + const fieldset = document.createElement("fieldset"); + fieldset.addEventListener("change", function () { + let i = -1; + for (const thing of fieldset.children) { + i++; + if (i === 0) { + continue; + } + const checkbox = thing.children[0].children[0]; + if (checkbox.checked) { + array[3](checkbox.value); + } + } + }); + const legend = document.createElement("legend"); + legend.textContent = array[1]; + fieldset.appendChild(legend); + let i = 0; + for (const thing of array[2]) { + const div = document.createElement("div"); + const input = document.createElement("input"); + input.classList.add("radio"); + input.type = "radio"; + input.name = array[1]; + input.value = thing; + if (i === array[4]) { + input.checked = true; + } + const label = document.createElement("label"); + label.appendChild(input); + const span = document.createElement("span"); + span.textContent = thing; + label.appendChild(span); + div.appendChild(label); + fieldset.appendChild(div); + i++; + } + div.appendChild(fieldset); + return div; + } + case "html": { + return array[1]; + } + case "select": { + const div = document.createElement("div"); + const label = document.createElement("label"); + const select = document.createElement("select"); + label.textContent = array[1]; + div.append(label); + div.appendChild(select); + for (const thing of array[2]) { + const option = document.createElement("option"); + option.textContent = thing; + select.appendChild(option); + } + select.selectedIndex = array[4]; + select.addEventListener("change", array[3]); + return div; + } + case "tabs": { + const table = document.createElement("table"); + const tabs = document.createElement("tr"); + tabs.classList.add("tabbed-head"); + table.appendChild(tabs); + const td = document.createElement("td"); + tabs.appendChild(td); + const content = document.createElement("tr"); + content.classList.add("tabbed-content"); + table.appendChild(content); + let shown; + for (const thing of array[1]) { + const button = document.createElement("button"); + button.textContent = thing[0]; + td.appendChild(button); + const tdcontent = document.createElement("td"); + tdcontent.colSpan = array[1].length; + tdcontent.appendChild(this.tohtml(thing[1])); + content.appendChild(tdcontent); + if (!shown) { + shown = tdcontent; + } + else { + tdcontent.hidden = true; + } + button.addEventListener("click", _ => { + shown.hidden = true; + tdcontent.hidden = false; + shown = tdcontent; + }); + } + return table; + } + default: + console.error("can't find element:" + array[0], " full element:" + array); + return; + } + } + show() { + this.onopen(); + console.log("fullscreen"); + this.background = document.createElement("div"); + this.background.classList.add("background"); + document.body.appendChild(this.background); + document.body.appendChild(this.html); + this.background.onclick = function () { this.hide(); }.bind(this); + } + hide() { + document.body.removeChild(this.background); + document.body.removeChild(this.html); + } +} diff --git a/.dist/guild.js b/.dist/guild.js new file mode 100644 index 0000000..161bd18 --- /dev/null +++ b/.dist/guild.js @@ -0,0 +1,496 @@ +import { Channel } from "./channel.js"; +import { Contextmenu } from "./contextmenu.js"; +import { Role } from "./role.js"; +import { Fullscreen } from "./fullscreen.js"; +class Guild { + owner; + headers; + channels; + channelids; + id; + properties; + roles; + roleids; + prevchannel; + message_notifications; + headchannels; + position; + parent_id; + member; + html; + static contextmenu = new Contextmenu("guild menu"); + static setupcontextmenu() { + Guild.contextmenu.addbutton("Copy Guild id", function () { + console.log(this); + navigator.clipboard.writeText(this.id); + }); + Guild.contextmenu.addbutton("Mark as read", function () { + console.log(this); + this.markAsRead(); + }); + Guild.contextmenu.addbutton("Notifications", function () { + console.log(this); + this.setnotifcation(); + }); + Guild.contextmenu.addbutton("Leave guild", function () { + this.confirmleave(); + }, null, function (_) { return _.properties.owner_id !== _.member.user.id; }); + Guild.contextmenu.addbutton("Delete guild", function () { + this.confirmDelete(); + }, null, function (_) { return _.properties.owner_id === _.member.user.id; }); + Guild.contextmenu.addbutton("Create invite", function () { + console.log(this); + }, null, _ => true, _ => false); + /* -----things left for later----- + guild.contextmenu.addbutton("Leave Guild",function(){ + console.log(this) + this.deleteChannel(); + },null,_=>{return thisuser.isAdmin()}) + + guild.contextmenu.addbutton("Mute Guild",function(){ + editchannelf(this); + },null,_=>{return thisuser.isAdmin()}) + */ + } + constructor(JSON, owner) { + if (JSON === -1) { + return; + } + this.owner = owner; + this.headers = this.owner.headers; + if (!this.owner) { + console.error("localuser was not included, please fix"); + } + this.channels = []; + this.channelids = {}; + this.id = JSON.id; + this.properties = JSON.properties; + this.roles = []; + this.roleids = {}; + this.prevchannel = undefined; + this.message_notifications = 0; + for (const roley of JSON.roles) { + const roleh = new Role(roley, this); + this.roles.push(roleh); + this.roleids[roleh.id] = roleh; + } + for (const thing of JSON.channels) { + const temp = new Channel(thing, this); + this.channels.push(temp); + this.channelids[temp.id] = temp; + } + this.headchannels = []; + for (const thing of this.channels) { + if (thing.resolveparent(this)) { + this.headchannels.push(thing); + } + } + } + notisetting(settings) { + this.message_notifications = settings.message_notifications; + } + setnotifcation() { + let noti = this.message_notifications; + const notiselect = new Fullscreen(["vdiv", + ["radio", "select notifications type", + ["all", "only mentions", "none"], + function (e) { + noti = ["all", "only mentions", "none"].indexOf(e); + }, + noti + ], + ["button", "", "submit", _ => { + fetch(this.info.api.toString() + "/v9/users/@me/guilds/settings", { + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + "guilds": { + [this.id]: { + "message_notifications": noti + } + } + }) + }); + this.message_notifications = noti; + }] + ]); + notiselect.show(); + } + confirmleave() { + const full = new Fullscreen([ + "vdiv", + ["title", + "Are you sure you want to leave?" + ], + ["hdiv", + ["button", + "", + "Yes, I'm sure", + _ => { + this.leave().then(_ => { + full.hide(); + }); + } + ], + ["button", + "", + "Nevermind", + _ => { + full.hide(); + } + ] + ] + ]); + full.show(); + } + async leave() { + return fetch(this.info.api.toString() + "/users/@me/guilds/" + this.id, { + method: "DELETE", + headers: this.headers + }); + } + printServers() { + let build = ""; + for (const thing of this.headchannels) { + build += (thing.name + ":" + thing.position) + "\n"; + for (const thingy of thing.children) { + build += (" " + thingy.name + ":" + thingy.position) + "\n"; + } + } + console.log(build); + } + calculateReorder() { + let position = -1; + let build = []; + for (const thing of this.headchannels) { + const thisthing = { id: thing.id, position: undefined, parent_id: undefined }; + if (thing.position <= position) { + thing.position = (thisthing.position = position + 1); + } + position = thing.position; + console.log(position); + if (thing.move_id && thing.move_id !== thing.parent_id) { + thing.parent_id = thing.move_id; + thisthing.parent_id = thing.parent_id; + thing.move_id = undefined; + } + if (thisthing.position || thisthing.parent_id) { + build.push(thisthing); + console.log(this.channelids[thisthing.parent_id]); + } + if (thing.children.length > 0) { + const things = thing.calculateReorder(); + for (const thing of things) { + build.push(thing); + } + } + } + console.log(build); + this.printServers(); + if (build.length === 0) { + return; + } + const serverbug = false; + if (serverbug) { + for (const thing of build) { + console.log(build, thing); + fetch(this.info.api.toString() + "/v9/guilds/" + this.id + "/channels", { + method: "PATCH", + headers: this.headers, + body: JSON.stringify([thing]) + }); + } + } + else { + fetch(this.info.api.toString() + "/v9/guilds/" + this.id + "/channels", { + method: "PATCH", + headers: this.headers, + body: JSON.stringify(build) + }); + } + } + get localuser() { + return this.owner; + } + get info() { + return this.owner.info; + } + sortchannels() { + this.headchannels.sort((a, b) => { return a.position - b.position; }); + } + generateGuildIcon() { + const divy = document.createElement("div"); + divy.classList.add("servernoti"); + const noti = document.createElement("div"); + noti.classList.add("unread"); + divy.append(noti); + this.localuser.guildhtml[this.id] = divy; + if (this.properties.icon != null) { + const img = document.createElement("img"); + img.classList.add("pfp", "servericon"); + img.src = this.info.cdn.toString() + "icons/" + this.properties.id + "/" + this.properties.icon + ".png"; + divy.appendChild(img); + img.onclick = () => { + console.log(this.loadGuild); + this.loadGuild(); + this.loadChannel(); + }; + Guild.contextmenu.bind(img, this); + } + else { + const div = document.createElement("div"); + let build = ""; + for (const char of this.properties.name.split(" ")) { + build += char[0]; + } + div.textContent = build; + div.classList.add("blankserver", "servericon"); + divy.appendChild(div); + div.onclick = () => { + this.loadGuild(); + this.loadChannel(); + }; + Guild.contextmenu.bind(div, this); + } + return divy; + } + confirmDelete() { + let confirmname = ""; + const full = new Fullscreen([ + "vdiv", + ["title", + "Are you sure you want to delete " + this.properties.name + "?" + ], + ["textbox", + "Name of server:", + "", + function () { + confirmname = this.value; + } + ], + ["hdiv", + ["button", + "", + "Yes, I'm sure", + _ => { + console.log(confirmname); + if (confirmname !== this.properties.name) { + return; + } + this.delete().then(_ => { + full.hide(); + }); + } + ], + ["button", + "", + "Nevermind", + _ => { + full.hide(); + } + ] + ] + ]); + full.show(); + } + async delete() { + return fetch(this.info.api.toString() + "/guilds/" + this.id + "/delete", { + method: "POST", + headers: this.headers, + }); + } + unreads(html = undefined) { + if (html) { + this.html = html; + } + else { + html = this.html; + } + let read = true; + for (const thing of this.channels) { + if (thing.hasunreads) { + console.log(thing); + read = false; + break; + } + } + if (!html) { + return; + } + if (read) { + html.children[0].classList.remove("notiunread"); + } + else { + html.children[0].classList.add("notiunread"); + } + } + getHTML() { + //this.printServers(); + this.sortchannels(); + this.printServers(); + const build = document.createElement("div"); + for (const thing of this.headchannels) { + build.appendChild(thing.createguildHTML(this.isAdmin())); + } + return build; + } + isAdmin() { + return this.member.isAdmin(); + } + async markAsRead() { + const build = { read_states: [] }; + for (const thing of this.channels) { + if (thing.hasunreads) { + build.read_states.push({ channel_id: thing.id, message_id: thing.lastmessageid, read_state_type: 0 }); + thing.lastreadmessageid = thing.lastmessageid; + thing.myhtml.classList.remove("cunread"); + } + } + this.unreads(); + fetch(this.info.api.toString() + "/v9/read-states/ack-bulk", { + method: "POST", + headers: this.headers, + body: JSON.stringify(build) + }); + } + fillMember(member) { + const realroles = []; + for (const thing of member.roles) { + realroles.push(this.getRole(thing)); + } + member.roles = realroles; + return member; + } + giveMember(member) { + this.fillMember(member); + this.member = member; + } + getRole(ID) { + return this.roleids[ID]; + } + hasRole(r) { + console.log("this should run"); + if ((typeof r) !== (typeof "")) { + r = r.id; + } + return this.member.hasRole(r); + } + loadChannel(ID = undefined) { + if (ID && this.channelids[ID]) { + this.channelids[ID].getHTML(); + return; + } + if (this.prevchannel) { + console.log(this.prevchannel); + this.prevchannel.getHTML(); + return; + } + for (const thing of this.channels) { + if (thing.children.length === 0) { + thing.getHTML(); + return; + } + } + } + loadGuild() { + this.localuser.loadGuild(this.id); + } + updateChannel(JSON) { + this.channelids[JSON.id].updateChannel(JSON); + this.headchannels = []; + for (const thing of this.channels) { + thing.children = []; + } + for (const thing of this.channels) { + if (thing.resolveparent(this)) { + this.headchannels.push(thing); + } + } + this.printServers(); + } + createChannelpac(JSON) { + const thischannel = new Channel(JSON, this); + this.channelids[JSON.id] = thischannel; + this.channels.push(thischannel); + thischannel.resolveparent(this); + if (!thischannel.parrent) { + this.headchannels.push(thischannel); + } + this.calculateReorder(); + this.printServers(); + } + createchannels(func = this.createChannel) { + let name = ""; + let category = 0; + const channelselect = new Fullscreen(["vdiv", + ["radio", "select channel type", + ["voice", "text", "announcement"], + function (e) { + console.log(e); + category = { "text": 0, "voice": 2, "announcement": 5, "category": 4 }[e]; + }, + 1 + ], + ["textbox", "Name of channel", "", function () { + console.log(this); + name = this.value; + }], + ["button", "", "submit", function () { + console.log(name, category); + func(name, category); + channelselect.hide(); + }.bind(this)] + ]); + channelselect.show(); + } + createcategory() { + let name = ""; + let category = 4; + const channelselect = new Fullscreen(["vdiv", + ["textbox", "Name of category", "", function () { + console.log(this); + name = this.value; + }], + ["button", "", "submit", function () { + console.log(name, category); + this.createChannel(name, category); + channelselect.hide(); + }] + ]); + channelselect.show(); + } + delChannel(JSON) { + const channel = this.channelids[JSON.id]; + delete this.channelids[JSON.id]; + this.channels.splice(this.channels.indexOf(channel), 1); + const indexy = this.headchannels.indexOf(channel); + if (indexy !== -1) { + this.headchannels.splice(indexy, 1); + } + /* + const build=[]; + for(const thing of this.channels){ + console.log(thing.id); + if(thing!==channel){ + build.push(thing) + }else{ + console.log("fail"); + if(thing.parrent){ + thing.parrent.delChannel(JSON); + } + } + } + this.channels=build; + */ + this.printServers(); + } + createChannel(name, type) { + fetch(this.info.api.toString() + "/guilds/" + this.id + "/channels", { + method: "Post", + headers: this.headers, + body: JSON.stringify({ name: name, type: type }) + }); + } +} +Guild.setupcontextmenu(); +export { Guild }; diff --git a/.dist/index.js b/.dist/index.js new file mode 100644 index 0000000..e20cb84 --- /dev/null +++ b/.dist/index.js @@ -0,0 +1,276 @@ +import { Localuser } from "./localuser.js"; +import { Contextmenu } from "./contextmenu.js"; +import { mobile, getBulkUsers, setTheme } from "./login.js"; +async function waitforload() { + let res; + new Promise(r => { res = r; }); + document.addEventListener("DOMContentLoaded", function () { + res(); + }); + await res; +} +await waitforload(); +function setDynamicHeight() { + var servertdHeight = document.getElementById('servertd').offsetHeight + document.getElementById('typebox').offsetHeight + document.getElementById('pasteimage').offsetHeight; + document.documentElement.style.setProperty('--servertd-height', servertdHeight + 'px'); +} +const resizeObserver = new ResizeObserver(() => { + setDynamicHeight(); +}); +resizeObserver.observe(document.getElementById('servertd')); +resizeObserver.observe(document.getElementById('typebox')); +resizeObserver.observe(document.getElementById('pasteimage')); +setDynamicHeight(); +const users = getBulkUsers(); +if (!users.currentuser) { + window.location.href = '/login.html'; +} +var info = users.users[users.currentuser].serverurls; +let token = users.users[users.currentuser].token; +let READY; +let thisuser = new Localuser(users.users[users.currentuser]); +thisuser.initwebsocket().then(_ => { + thisuser.loaduser(); + thisuser.init(); + document.getElementById("loading").classList.add("doneloading"); + document.getElementById("loading").classList.remove("loading"); + console.log("done loading"); +}); +{ + const userinfo = document.getElementById("userinfo"); + const userdock = document.getElementById("userdock"); + userinfo.addEventListener("click", function (event) { + const table = document.createElement("table"); + for (const thing of Object.values(users.users)) { + const specialuser = thing; + console.log(specialuser.pfpsrc); + const tr = document.createElement("tr"); + const td = document.createElement("td"); + const userinfo = document.createElement("table"); + userinfo.classList.add("switchtable"); + const row = document.createElement("tr"); + userinfo.append(row); + const pfpcell = document.createElement("td"); + row.append(pfpcell); + const pfp = document.createElement("img"); + pfpcell.append(pfp); + const usertd = document.createElement("td"); + row.append(usertd); + const user = document.createElement("div"); + usertd.append(user); + user.append(specialuser.username); + user.append(document.createElement("br")); + const span = document.createElement("span"); + span.textContent = specialuser.serverurls.wellknown.hostname; + user.append(span); + span.classList.add("serverURL"); + pfp.src = specialuser.pfpsrc; + pfp.classList.add("pfp"); + td.append(userinfo); + tr.append(td); + table.append(tr); + tr.addEventListener("click", _ => { + thisuser.unload(); + document.getElementById("loading").classList.remove("doneloading"); + document.getElementById("loading").classList.add("loading"); + thisuser = new Localuser(specialuser); + users["currentuser"] = specialuser.uid; + localStorage.setItem("userinfos", JSON.stringify(users)); + thisuser.initwebsocket().then(_ => { + thisuser.loaduser(); + thisuser.init(); + document.getElementById("loading").classList.add("doneloading"); + document.getElementById("loading").classList.remove("loading"); + console.log("done loading"); + }); + }); + } + { + const tr = document.createElement("tr"); + const td = document.createElement("td"); + tr.append(td); + td.append("Switch accounts ⇌"); + td.addEventListener("click", _ => { + window.location.href = "/login.html"; + }); + table.append(tr); + } + table.classList.add("accountSwitcher"); + if (Contextmenu.currentmenu != "") { + Contextmenu.currentmenu.remove(); + } + Contextmenu.currentmenu = table; + console.log(table); + userdock.append(table); + event.stopImmediatePropagation(); + }); +} +{ + const menu = new Contextmenu("create rightclick"); + menu.addbutton("Create channel", function () { + thisuser.lookingguild.createchannels(); + }, null, _ => { return thisuser.isAdmin(); }); + menu.addbutton("Create category", function () { + thisuser.lookingguild.createcategory(); + }, null, _ => { return thisuser.isAdmin(); }); + menu.bind(document.getElementById("channels")); +} +function editchannelf(channel) { channel.editChannel(); } +const pasteimage = document.getElementById("pasteimage"); +let replyingto = null; +async function enter(event) { + const channel = thisuser.channelfocus; + channel.typingstart(); + if (event.key === "Enter" && !event.shiftKey) { + event.preventDefault(); + if (channel.editing) { + channel.editing.edit((typebox).value); + channel.editing = null; + } + else { + let replying = replyingto?.all; + if (replyingto) { + replyingto.classList.remove("replying"); + } + replyingto = false; + channel.sendMessage(typebox.value, { + attachments: images, + replyingto: replying, + }); + } + while (images.length != 0) { + images.pop(); + pasteimage.removeChild(imageshtml.pop()); + } + typebox.value = ""; + return; + } +} +const typebox = document.getElementById("typebox"); +typebox.addEventListener("keyup", enter); +typebox.addEventListener("keydown", event => { + if (event.key === "Enter" && !event.shiftKey) + event.preventDefault(); +}); +console.log(typebox); +typebox.onclick = console.log; +let serverz = 0; +let serverid = []; +let cchanel = 0; +function getguildinfo() { + const path = window.location.pathname.split("/"); + const channel = path[3]; + this.ws.send(JSON.stringify({ op: 14, d: { guild_id: path[2], channels: { [channel]: [[0, 99]] } } })); +} +const images = []; +const imageshtml = []; +function createunknown(fname, fsize) { + const div = document.createElement("table"); + div.classList.add("unknownfile"); + const nametr = document.createElement("tr"); + div.append(nametr); + const fileicon = document.createElement("td"); + nametr.append(fileicon); + fileicon.append("🗎"); + fileicon.classList.add("fileicon"); + fileicon.rowSpan = 2; + const nametd = document.createElement("td"); + { + nametd.textContent = fname; + } + nametd.classList.add("filename"); + nametr.append(nametd); + const sizetr = document.createElement("tr"); + const size = document.createElement("td"); + sizetr.append(size); + size.textContent = "Size:" + filesizehuman(fsize); + size.classList.add("filesize"); + div.appendChild(sizetr); + return div; +} +function filesizehuman(fsize) { + var i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1024)); + return +((fsize / Math.pow(1024, i)).toFixed(2)) * 1 + ' ' + ['Bytes', 'Kilobytes', 'Megabytes', 'Gigabytes', 'Terabytes'][i]; +} +function createunknownfile(file) { + return createunknown(file.name, file.size); +} +function filetohtml(file) { + if (file.type.startsWith('image/')) { + const img = document.createElement('img'); + const blob = URL.createObjectURL(file); + img.src = blob; + return img; + } + else { + console.log(file.name); + return createunknownfile(file); + } +} +document.addEventListener('paste', async (e) => { + Array.from(e.clipboardData.files).forEach(async (file) => { + e.preventDefault(); + const html = filetohtml(file); + pasteimage.appendChild(html); + const blob = URL.createObjectURL(file); + images.push(file); + imageshtml.push(html); + console.log(file.type); + }); +}); +setTheme(); +function userSettings() { + thisuser.usersettings.show(); +} +document.getElementById("settings").onclick = userSettings; +let triggered = false; +document.getElementById("messagecontainer").addEventListener("scroll", (e) => { + const messagecontainer = document.getElementById("messagecontainer"); + if (messagecontainer.scrollTop < 2000) { + if (!triggered) { + thisuser.lookingguild.prevchannel.grabmoremessages().then(() => { + triggered = false; + if (messagecontainer.scrollTop === 0) { + messagecontainer.scrollTop = 1; + } + }); + } + triggered = true; + } + else { + if (Math.abs(messagecontainer.scrollHeight - messagecontainer.scrollTop - messagecontainer.clientHeight) < 3) { + thisuser.lookingguild.prevchannel.readbottom(); + } + } + // +}); +if (mobile) { + document.getElementById("channelw").onclick = function () { + document.getElementById("channels").parentNode.classList.add("collapse"); + document.getElementById("servertd").classList.add("collapse"); + document.getElementById("servers").classList.add("collapse"); + }; + document.getElementById("mobileback").textContent = "#"; + document.getElementById("mobileback").onclick = function () { + document.getElementById("channels").parentNode.classList.remove("collapse"); + document.getElementById("servertd").classList.remove("collapse"); + document.getElementById("servers").classList.remove("collapse"); + }; +} +/* +{ + const messages=document.getElementById("messages"); + let height=messages.clientHeight; + // + const resizeObserver=new ResizeObserver(()=>{ + console.log(messages.scrollTop,messages.clientHeight-height-messages.scrollHeight); + messages.scrollTop-=height-messages.scrollHeight; + console.log(messages.scrollTop) + //if(shouldsnap){ + // document.getElementById("messagecontainer").scrollTop = document.getElementById("messagecontainer").scrollHeight; + //} + height=messages.scrollHeight; + }) + resizeObserver.observe(messages) +} +*/ diff --git a/.dist/localuser.js b/.dist/localuser.js new file mode 100644 index 0000000..f708502 --- /dev/null +++ b/.dist/localuser.js @@ -0,0 +1,555 @@ +import { Guild } from "./guild.js"; +import { Direct } from "./direct.js"; +import { Voice } from "./audio.js"; +import { User } from "./user.js"; +import { Member } from "./member.js"; +import { markdown } from "./markdown.js"; +import { Fullscreen } from "./fullscreen.js"; +import { setTheme } from "./login.js"; +class Localuser { + packets; + token; + userinfo; + serverurls; + initialized; + info; + headers; + usersettings; + ready; + guilds; + guildids; + user; + status; + channelfocus; + lookingguild; + guildhtml; + ws; + typing; + wsinterval; + constructor(userinfo) { + this.packets = 1; + this.token = userinfo.token; + this.userinfo = userinfo; + this.serverurls = this.userinfo.serverurls; + this.initialized = false; + this.info = this.serverurls; + this.headers = { "Content-type": "application/json; charset=UTF-8", Authorization: this.userinfo.token }; + } + gottenReady(ready) { + this.usersettings = null; + this.initialized = true; + this.ready = ready; + this.guilds = []; + this.guildids = {}; + this.user = new User(ready.d.user, this); + this.userinfo.username = this.user.username; + this.userinfo.pfpsrc = this.user.getpfpsrc(); + this.status = this.ready.d.user_settings.status; + this.channelfocus = null; + this.lookingguild = null; + this.guildhtml = {}; + for (const thing of ready.d.guilds) { + const temp = new Guild(thing, this); + this.guilds.push(temp); + this.guildids[temp.id] = temp; + } + { + const temp = new Direct(ready.d.private_channels, this); + this.guilds.push(temp); + this.guildids[temp.id] = temp; + } + console.log(ready.d.user_guild_settings.entries); + for (const thing of ready.d.user_guild_settings.entries) { + this.guildids[thing.guild_id].notisetting(thing); + } + for (const thing of ready.d.merged_members) { + const guild = this.guildids[thing[0].guild_id]; + const temp = new Member(thing[0], guild); + guild.giveMember(temp); + } + for (const thing of ready.d.read_state.entries) { + const guild = this.resolveChannelFromID(thing.id).guild; + if (guild === undefined) { + continue; + } + const guildid = guild.id; + this.guildids[guildid].channelids[thing.channel_id].readStateInfo(thing); + } + this.typing = []; + } + outoffocus() { + document.getElementById("servers").textContent = ""; + document.getElementById("channels").textContent = ""; + document.getElementById("messages").textContent = ""; + this.lookingguild = null; + this.channelfocus = null; + } + unload() { + this.initialized = false; + clearInterval(this.wsinterval); + this.outoffocus(); + this.guilds = []; + this.guildids = {}; + this.ws.close(4000); + } + async initwebsocket() { + let returny = null; + const promise = new Promise((res) => { returny = res; }); + this.ws = new WebSocket(this.serverurls.gateway.toString()); + this.ws.addEventListener('open', (event) => { + console.log('WebSocket connected'); + this.ws.send(JSON.stringify({ + "op": 2, + "d": { + "token": this.token, + "capabilities": 16381, + "properties": { + "browser": "Jank Client", + "client_build_number": 0, + "release_channel": "Custom", + "browser_user_agent": navigator.userAgent + }, + "compress": false, + "presence": { + "status": "online", + "since": new Date().getTime(), + "activities": [], + "afk": false + } + } + })); + }); + this.ws.addEventListener('message', (event) => { + try { + const temp = JSON.parse(event.data); + console.log(temp); + if (temp.op == 0) { + switch (temp.t) { + case "MESSAGE_CREATE": + if (this.initialized) { + this.messageCreate(temp); + } + break; + case "READY": + this.gottenReady(temp); + this.genusersettings(); + returny(); + break; + case "MESSAGE_UPDATE": + if (this.initialized) { + if (this.channelfocus.id === temp.d.channel_id) { + const find = temp.d.id; + const messagelist = document.getElementById("messages").children; + for (const message of messagelist) { + const all = message["all"]; + if (all.id === find) { + all.content = temp.d.content; + message["txt"].innerHTML = markdown(temp.d.content).innerHTML; + break; + } + } + } + else { + this.resolveChannelFromID(temp.d.channel_id).messages.find(e => e.id === temp.d.channel_id).content = temp.d.content; + } + } + break; + case "TYPING_START": + if (this.initialized) { + this.typeingStart(temp); + } + break; + case "USER_UPDATE": + if (this.initialized) { + const users = User.userids[temp.d.id]; + 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[temp.d.id]; + delete this.guildids[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.guilds.push(guildy); + this.guildids[guildy.id] = guildy; + document.getElementById("servers").insertBefore(guildy.generateGuildIcon(), document.getElementById("bottomseperator")); + } + } + } + else if (temp.op === 10) { + console.log("heartbeat down"); + this.wsinterval = setInterval(_ => { + this.ws.send(JSON.stringify({ op: 1, d: this.packets })); + }, temp.d.heartbeat_interval); + this.packets = 1; + } + else if (temp.op != 11) { + this.packets++; + } + } + catch (error) { + console.error(error); + } + }); + this.ws.addEventListener('close', (event) => { + clearInterval(this.wsinterval); + console.log('WebSocket closed'); + console.warn(event); + if (event.code !== 4000 && this === this) { + this.unload(); + document.getElementById("loading").classList.remove("doneloading"); + document.getElementById("loading").classList.add("loading"); + this.initwebsocket().then(_ => { + this.loaduser(); + this.init(); + document.getElementById("loading").classList.add("doneloading"); + document.getElementById("loading").classList.remove("loading"); + console.log("done loading"); + }); + } + }); + await promise; + return; + } + resolveChannelFromID(ID) { + console.log(this.guilds.find(guild => guild.channelids[ID]).channelids); + let resolve = this.guilds.find(guild => guild.channelids[ID]).channelids[ID]; + resolve ??= undefined; + return resolve; + } + updateChannel(JSON) { + this.guildids[JSON.guild_id].updateChannel(JSON); + if (JSON.guild_id === this.lookingguild.id) { + this.loadGuild(JSON.guild_id); + } + } + createChannel(JSON) { + JSON.guild_id ??= "@me"; + this.guildids[JSON.guild_id].createChannelpac(JSON); + if (JSON.guild_id === this.lookingguild.id) { + this.loadGuild(JSON.guild_id); + } + } + delChannel(JSON) { + JSON.guild_id ??= "@me"; + this.guildids[JSON.guild_id].delChannel(JSON); + if (JSON.guild_id === this.lookingguild.id) { + this.loadGuild(JSON.guild_id); + } + } + init() { + const location = window.location.href.split("/"); + if (location[3] === "channels") { + const guild = this.loadGuild(location[4]); + guild.loadChannel(location[5]); + this.channelfocus = guild.channelids[location[5]]; + } + this.buildservers(); + } + loaduser() { + document.getElementById("username").textContent = this.user.username; + document.getElementById("userpfp").src = this.user.getpfpsrc(); + document.getElementById("status").textContent = this.status; + } + isAdmin() { + return this.lookingguild.isAdmin(); + } + loadGuild(id) { + let guild = this.guildids[id]; + if (!guild) { + guild = this.guildids["@me"]; + } + this.lookingguild = guild; + document.getElementById("serverName").textContent = guild.properties.name; + //console.log(this.guildids,id) + document.getElementById("channels").innerHTML = ""; + document.getElementById("channels").appendChild(guild.getHTML()); + return guild; + } + buildservers() { + const serverlist = document.getElementById("servers"); // + const div = document.createElement("div"); + div.textContent = "⌂"; + div.classList.add("home", "servericon"); + div["all"] = this.guildids["@me"]; + serverlist.appendChild(div); + div.onclick = function () { + this["all"].loadGuild(); + this["all"].loadChannel(); + }; + const sentdms = document.createElement("div"); + sentdms.classList.add("sentdms"); + serverlist.append(sentdms); + sentdms.id = "sentdms"; + const br = document.createElement("hr"); + br.classList.add("lightbr"); + serverlist.appendChild(br); + for (const thing of this.guilds) { + if (thing instanceof Direct) { + thing.unreaddms(); + continue; + } + const divy = thing.generateGuildIcon(); + serverlist.append(divy); + } + { + const br = document.createElement("hr"); + br.classList.add("lightbr"); + serverlist.appendChild(br); + br.id = "bottomseperator"; + const div = document.createElement("div"); + div.textContent = "+"; + div.classList.add("addserver", "servericon"); + serverlist.appendChild(div); + div.onclick = _ => { + console.log("clicked :3"); + this.createGuild(); + }; + } + this.unreads(); + } + createGuild() { + let inviteurl = ""; + const error = document.createElement("span"); + const full = new Fullscreen(["tabs", [ + ["Join using invite", [ + "vdiv", + ["textbox", + "Invite Link/Code", + "", + function () { + console.log(this); + inviteurl = this.value; + } + ], + ["html", error], + ["button", + "", + "Submit", + _ => { + let parsed = ""; + if (inviteurl.includes("/")) { + parsed = inviteurl.split("/")[inviteurl.split("/").length - 1]; + } + else { + parsed = inviteurl; + } + fetch(this.info.api.toString() + "/v9/invites/" + parsed, { + method: "POST", + headers: this.headers, + }).then(r => r.json()).then(_ => { + console.log(_); + if (_.message) { + error.textContent = _.message; + } + }); + } + ] + ]], + ["Create Server", [ + "text", "Not currently implemented, sorry" + ]] + ]]); + full.show(); + } + messageCreate(messagep) { + messagep.d.guild_id ??= "@me"; + this.guildids[messagep.d.guild_id].channelids[messagep.d.channel_id].messageCreate(messagep); + this.unreads(); + } + unreads() { + console.log(this.guildhtml); + for (const thing of this.guilds) { + if (thing.id === "@me") { + continue; + } + thing.unreads(this.guildhtml[thing.id]); + } + } + typeingStart(typing) { + if (this.channelfocus.id === typing.d.channel_id) { + const memb = typing.d.member; + let name; + if (memb.id === this.user.id) { + console.log("you is typing"); + return; + } + console.log("user is typing and you should see it"); + if (memb.nick) { + name = memb.nick; + } + else { + name = memb.user.username; + } + let already = false; + for (const thing of this.typing) { + if (thing[0] === name) { + thing[1] = new Date().getTime(); + already = true; + break; + } + } + if (!already) { + this.typing.push([name, new Date().getTime()]); + } + setTimeout(this.rendertyping.bind(this), 10000); + this.rendertyping(); + } + } + updatepfp(file) { + var reader = new FileReader(); + reader.readAsDataURL(file); + console.log(this.headers); + reader.onload = () => { + fetch(this.info.api.toString() + "/v9/users/@me", { + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + avatar: reader.result, + }) + }); + console.log(reader.result); + }; + } + updatepronouns(pronouns) { + fetch(this.info.api.toString() + "/v9/users/@me/profile", { + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + pronouns: pronouns, + }) + }); + } + updatebio(bio) { + fetch(this.info.api.toString() + "/v9/users/@me/profile", { + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + bio: bio, + }) + }); + } + rendertyping() { + const typingtext = document.getElementById("typing"); + let build = ""; + const array2 = []; + let showing = false; + let i = 0; + for (const thing of this.typing) { + i++; + if (thing[1] > new Date().getTime() - 5000) { + build += thing[0]; + array2.push(thing); + showing = true; + if (i !== this.typing.length) { + build += ","; + } + } + } + if (i > 1) { + build += " are typing"; + } + else { + build += " is typing"; + } + console.log(typingtext.classList); + if (showing) { + typingtext.classList.remove("hidden"); + document.getElementById("typingtext").textContent = build; + } + else { + typingtext.classList.add("hidden"); + } + } + genusersettings() { + const hypothetcialprofie = document.createElement("div"); + let file = null; + let newprouns = null; + let newbio = null; + let hypouser = new User(this.user, this, true); + function regen() { + hypothetcialprofie.textContent = ""; + const hypoprofile = hypouser.buildprofile(-1, -1); + hypothetcialprofie.appendChild(hypoprofile); + } + regen(); + this.usersettings = new Fullscreen(["hdiv", + ["vdiv", + ["fileupload", "upload pfp:", function (e) { + console.log(this.files[0]); + file = this.files[0]; + const blob = URL.createObjectURL(this.files[0]); + hypouser.avatar = blob; + hypouser.hypotheticalpfp = true; + regen(); + }], + ["textbox", "Pronouns:", this.user.pronouns, function (e) { + console.log(this.value); + hypouser.pronouns = this.value; + newprouns = this.value; + regen(); + }], + ["mdbox", "Bio:", this.user.bio, function (e) { + console.log(this.value); + hypouser.bio = this.value; + newbio = this.value; + regen(); + }], + ["button", "update user content:", "submit", function () { + if (file !== null) { + this.updatepfp(file); + } + if (newprouns !== null) { + this.updatepronouns(newprouns); + } + if (newbio !== null) { + this.updatebio(newbio); + } + }], + ["select", "Theme:", ["Dark", "Light", "WHITE"], e => { + localStorage.setItem("theme", ["Dark", "Light", "WHITE"][e.target.selectedIndex]); + setTheme(); + }, ["Dark", "Light", "WHITE"].indexOf(localStorage.getItem("theme"))], + ["select", "Notification sound:", Voice.sounds, e => { + Voice.setNotificationSound(Voice.sounds[e.target.selectedIndex]); + Voice.noises(Voice.sounds[e.target.selectedIndex]); + }, Voice.sounds.indexOf(Voice.getNotificationSound())] + ], + ["vdiv", + ["html", hypothetcialprofie] + ] + ], _ => { }, function () { + console.log(this); + hypouser = new User(this.user, this); + regen(); + file = null; + newprouns = null; + newbio = null; + }.bind(this)); + } +} +export { Localuser }; diff --git a/.dist/login.js b/.dist/login.js new file mode 100644 index 0000000..0d02490 --- /dev/null +++ b/.dist/login.js @@ -0,0 +1,237 @@ +const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); +export { mobile, getBulkUsers, getBulkInfo, setTheme, Specialuser }; +function setTheme() { + const name = localStorage.getItem("theme"); + if (!name) { + document.body.className = "Dark-theme"; + localStorage.setItem("theme", "Dark"); + } + document.body.className = name + "-theme"; +} +setTheme(); +function getBulkUsers() { + const json = getBulkInfo(); + for (const thing in json.users) { + json.users[thing] = new Specialuser(json.users[thing]); + } + return json; +} +function getBulkInfo() { + return JSON.parse(localStorage.getItem("userinfos")); +} +function setDefaults() { + let userinfos = getBulkInfo(); + if (!userinfos) { + localStorage.setItem("userinfos", JSON.stringify({ + currentuser: null, + users: {}, + preferances: { + theme: "Dark", + notifcations: false, + notisound: "three", + }, + })); + } + if (userinfos.users === undefined) { + userinfos.users = {}; + } + if (userinfos.preferances === undefined) { + userinfos.preferances = { + theme: "Dark", + notifcations: false, + notisound: "three", + }; + } + if (userinfos.preferances && (userinfos.preferances.notisound === undefined)) { + userinfos.preferances.notisound = "three"; + } + localStorage.setItem("userinfos", JSON.stringify(userinfos)); +} +setDefaults(); +class Specialuser { + serverurls; + email; + token; + loggedin; + json; + constructor(json) { + if (json instanceof Specialuser) { + console.error("specialuser can't construct from another specialuser"); + } + this.serverurls = json.serverurls; + this.serverurls.api = new URL(this.serverurls.api); + this.serverurls.cdn = new URL(this.serverurls.cdn); + this.serverurls.gateway = new URL(this.serverurls.gateway); + this.serverurls.wellknown = new URL(this.serverurls.wellknown); + this.email = json.email; + this.token = json.token; + this.loggedin = json.loggedin; + this.json = json; + if (!this.serverurls || !this.email || !this.token) { + console.error("There are fundamentally missing pieces of info missing from this user"); + } + } + set pfpsrc(e) { + console.log("this ran fr"); + this.json.pfpsrc = e; + this.updateLocal(); + } + get pfpsrc() { + return this.json.pfpsrc; + } + set username(e) { + this.json.username = e; + this.updateLocal(); + } + get username() { + return this.json.username; + } + get uid() { + return this.email + this.serverurls.wellknown; + } + toJSON() { + return this.json; + } + updateLocal() { + const info = getBulkInfo(); + info.users[this.uid] = this.toJSON(); + localStorage.setItem("userinfos", JSON.stringify(info)); + } +} +function adduser(user) { + user = new Specialuser(user); + const info = getBulkInfo(); + info.users[user.uid] = user; + info.currentuser = user.uid; + localStorage.setItem("userinfos", JSON.stringify(info)); +} +const instancein = document.getElementById("instancein"); +let timeout; +let instanceinfo; +async function checkInstance(e) { + const verify = document.getElementById("verify"); + ; + try { + verify.textContent = "Checking Instance"; + const instanceinfo = await setInstance(instancein.value); + localStorage.setItem("instanceinfo", JSON.stringify(instanceinfo)); + verify.textContent = "Instance is all good"; + if (checkInstance["alt"]) { + checkInstance["alt"](); + } + setTimeout(_ => { + console.log(verify.textContent); + verify.textContent = ""; + }, 3000); + } + catch (e) { + console.log("catch"); + verify.textContent = "Invalid Instance, try again"; + } +} +if (instancein) { + console.log(instancein); + instancein.addEventListener("keydown", e => { + const verify = document.getElementById("verify"); + verify.textContent = "Waiting to check Instance"; + clearTimeout(timeout); + timeout = setTimeout(checkInstance, 1000); + }); + if (localStorage.getItem("instanceinfo")) { + instancein.value = JSON.parse(localStorage.getItem("instanceinfo")).wellknown; + } + else { + checkInstance("https://spacebar.chat/"); + } +} +async function login(username, password) { + const options = { + method: "POST", + body: JSON.stringify({ + "login": username, + "password": password, + "undelete": false + }), + headers: { + "Content-type": "application/json; charset=UTF-8", + } + }; + try { + const info = JSON.parse(localStorage.getItem("instanceinfo")); + const url = new URL(info.login); + return await fetch(url.origin + '/api/auth/login', options).then(responce => responce.json()) + .then((response) => { + console.log(response, response.message); + if ("Invalid Form Body" === response.message) { + return response.errors.login._errors[0].message; + console.log("test"); + } + //this.serverurls||!this.email||!this.token + adduser({ serverurls: JSON.parse(localStorage.getItem("instanceinfo")), email: username, token: response.token }); + window.location.href = '/channels/@me'; + return response.token; + }); + } + catch (error) { + console.error('Error:', error); + } + ; +} +async function setInstance(url) { + url = new URL(url); + async function attempt(aurl) { + const info = await fetch(`${aurl.toString()}${aurl.pathname.includes("api") ? "" : "api"}/policies/instance/domains`) + .then((x) => x.json()); + return { + api: info.apiEndpoint, + gateway: info.gateway, + cdn: info.cdn, + wellknown: url, + login: aurl.toString() + }; + } + try { + return await attempt(url); + } + catch (e) { + } + const wellKnown = await fetch(`${url.origin}/.well-known/spacebar`) + .then((x) => x.json()) + .then((x) => new URL(x.api)); + return await attempt(wellKnown); +} +async function check(e) { + e.preventDefault(); + let h = await login(e.srcElement[1].value, e.srcElement[2].value); + document.getElementById("wrong").textContent = h; + console.log(h); +} +if (document.getElementById("form")) { + document.getElementById("form").addEventListener("submit", check); +} +//Service workers :3 +if ("serviceWorker" in navigator) { + navigator.serviceWorker.register("/service.js", { + scope: "/", + }).then((registration) => { + let 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(e.target.state); + }); + } + }); +} diff --git a/.dist/markdown.js b/.dist/markdown.js new file mode 100644 index 0000000..d756a7e --- /dev/null +++ b/.dist/markdown.js @@ -0,0 +1,357 @@ +export { markdown }; +function markdown(text, keep = false) { + let txt; + if ((typeof txt) === "string") { + txt = text.split(""); + } + else { + txt = text; + } + const span = document.createElement("span"); + let current = document.createElement("span"); + function appendcurrent() { + if (current.innerHTML !== "") { + span.append(current); + current = document.createElement("span"); + } + } + for (let i = 0; i < txt.length; i++) { + if (txt[i] === "\n" || i === 0) { + const first = i === 0; + if (first) { + i--; + } + let element = null; + let keepys = ""; + if (txt[i + 1] === "#") { + console.log("test"); + if (txt[i + 2] === "#") { + if (txt[i + 3] === "#" && txt[i + 4] === " ") { + element = document.createElement("h3"); + keepys = "### "; + i += 5; + } + else if (txt[i + 3] === " ") { + element = document.createElement("h2"); + element.classList.add("h2md"); + keepys = "## "; + i += 4; + } + } + else if (txt[i + 2] === " ") { + element = document.createElement("h1"); + keepys = "# "; + i += 3; + } + } + else if (txt[i + 1] === ">" && txt[i + 2] === " ") { + element = document.createElement("div"); + const line = document.createElement("div"); + line.classList.add("quoteline"); + element.append(line); + element.classList.add("quote"); + keepys = "> "; + i += 3; + } + if (keepys) { + appendcurrent(); + if (!first) { + span.appendChild(document.createElement("br")); + } + const build = []; + for (; txt[i] !== "\n" && txt[i] !== undefined; i++) { + build.push(txt[i]); + } + if (keep) { + element.append(keepys); + } + element.appendChild(markdown(build, keep)); + span.append(element); + i--; + continue; + } + if (first) { + i++; + } + } + if (txt[i] === "\n") { + appendcurrent(); + span.append(document.createElement("br")); + continue; + } + if (txt[i] === "`") { + let count = 1; + if (txt[i + 1] === "`") { + count++; + if (txt[i + 2] === "`") { + count++; + } + } + let build = ""; + if (keep) { + build += "`".repeat(count); + } + let find = 0; + let j = i + count; + let init = true; + for (; txt[j] !== undefined && (txt[j] !== "\n" || count === 3) && find !== count; j++) { + if (txt[j] === "`") { + find++; + } + else { + if (find !== 0) { + build += "`".repeat(find); + find = 0; + } + if (init && count === 3) { + if (txt[j] === " " || txt[j] === "\n") { + init = false; + } + if (keep) { + build += txt[j]; + } + continue; + } + build += txt[j]; + } + } + if (find === count) { + appendcurrent(); + i = j; + if (keep) { + build += "`".repeat(find); + } + if (count !== 3) { + const samp = document.createElement("samp"); + samp.textContent = build; + span.appendChild(samp); + } + else { + const pre = document.createElement("pre"); + if (build[build.length - 1] === "\n") { + build = build.substring(0, build.length - 1); + } + if (txt[i] === "\n") { + i++; + } + pre.textContent = build; + span.appendChild(pre); + } + i--; + continue; + } + } + if (txt[i] === "*") { + let count = 1; + if (txt[i + 1] === "*") { + count++; + if (txt[i + 2] === "*") { + count++; + } + } + let build = []; + let find = 0; + let j = i + count; + for (; txt[j] !== undefined && find !== count; j++) { + if (txt[j] === "*") { + find++; + } + else { + build.push(txt[j]); + if (find !== 0) { + build = build.concat(new Array(find).fill("*")); + find = 0; + } + } + } + if (find === count && (count != 1 || txt[i + 1] !== " ")) { + appendcurrent(); + i = j; + const stars = "*".repeat(count); + if (count === 1) { + const i = document.createElement("i"); + if (keep) { + i.append(stars); + } + i.appendChild(markdown(build, keep)); + if (keep) { + i.append(stars); + } + span.appendChild(i); + } + else if (count === 2) { + const b = document.createElement("b"); + if (keep) { + b.append(stars); + } + b.appendChild(markdown(build, keep)); + if (keep) { + b.append(stars); + } + span.appendChild(b); + } + else { + const b = document.createElement("b"); + const i = document.createElement("i"); + if (keep) { + b.append(stars); + } + b.appendChild(markdown(build, keep)); + if (keep) { + b.append(stars); + } + i.appendChild(b); + span.appendChild(i); + } + i--; + continue; + } + } + if (txt[i] === "_") { + let count = 1; + if (txt[i + 1] === "_") { + count++; + if (txt[i + 2] === "_") { + count++; + } + } + let build = []; + let find = 0; + let j = i + count; + for (; txt[j] !== undefined && find !== count; j++) { + if (txt[j] === "_") { + find++; + } + else { + build.push(txt[j]); + if (find !== 0) { + build = build.concat(new Array(find).fill("_")); + find = 0; + } + } + } + if (find === count && (count != 1 || (txt[j + 1] === " " || txt[j + 1] === "\n" || txt[j + 1] === undefined))) { + appendcurrent(); + i = j; + const underscores = "_".repeat(count); + if (count === 1) { + const i = document.createElement("i"); + if (keep) { + i.append(underscores); + } + i.appendChild(markdown(build, keep)); + if (keep) { + i.append(underscores); + } + span.appendChild(i); + } + else if (count === 2) { + const u = document.createElement("u"); + if (keep) { + u.append(underscores); + } + u.appendChild(markdown(build, keep)); + if (keep) { + u.append(underscores); + } + span.appendChild(u); + } + else { + const u = document.createElement("u"); + const i = document.createElement("i"); + if (keep) { + i.append(underscores); + } + i.appendChild(markdown(build, keep)); + if (keep) { + i.append(underscores); + } + u.appendChild(i); + span.appendChild(u); + } + i--; + continue; + } + } + if (txt[i] === "~" && txt[i + 1] === "~") { + let count = 2; + let build = []; + let find = 0; + let j = i + 2; + for (; txt[j] !== undefined && find !== count; j++) { + if (txt[j] === "~") { + find++; + } + else { + build.push(txt[j]); + if (find !== 0) { + build = build.concat(new Array(find).fill("~")); + find = 0; + } + } + } + if (find === count) { + appendcurrent(); + i = j; + const underscores = "~~"; + if (count === 2) { + const s = document.createElement("s"); + if (keep) { + s.append(underscores); + } + s.appendChild(markdown(build, keep)); + if (keep) { + s.append(underscores); + } + span.appendChild(s); + } + continue; + } + } + if (txt[i] === "|" && txt[i + 1] === "|") { + let count = 2; + let build = []; + let find = 0; + let j = i + 2; + for (; txt[j] !== undefined && find !== count; j++) { + if (txt[j] === "|") { + find++; + } + else { + build.push(txt[j]); + if (find !== 0) { + build = build.concat(new Array(find).fill("~")); + find = 0; + } + } + } + if (find === count) { + appendcurrent(); + i = j; + const underscores = "||"; + if (count === 2) { + const j = document.createElement("j"); + if (keep) { + j.append(underscores); + } + j.appendChild(markdown(build, keep)); + j.classList.add("spoiler"); + j.onclick = markdown.unspoil; + if (keep) { + j.append(underscores); + } + span.appendChild(j); + } + continue; + } + } + current.textContent += txt[i]; + } + appendcurrent(); + return span; +} +markdown.unspoil = function (e) { + //console.log("undone") + e.target.classList.remove("spoiler"); + e.target.classList.add("unspoiled"); +}; diff --git a/.dist/member.js b/.dist/member.js new file mode 100644 index 0000000..bd5c19a --- /dev/null +++ b/.dist/member.js @@ -0,0 +1,80 @@ +import { User } from "./user.js"; +class Member { + static already = {}; + owner; + user; + roles; + constructor(memberjson, owner) { + if (!owner) { + console.error("Guild not included in the creation of a member object"); + } + this.owner = owner; + let membery = memberjson; + if (memberjson.guild_member) { + membery = memberjson.guild_member; + this.user = memberjson.user; + } + for (const thing of Object.keys(membery)) { + if (thing === "guild") { + continue; + } + this[thing] = membery[thing]; + } + this.user = new User(this.user, owner.localuser); + } + get guild() { + return this.owner; + } + get localuser() { + return this.guild.localuser; + } + get info() { + return this.owner.info; + } + static async resolve(user, guild) { + if (guild.id === "@me") { + return null; + } + if (!Member.already[guild.id]) { + Member.already[guild.id] = {}; + } + else if (Member.already[guild.id][user.id]) { + const memb = Member.already[guild.id][user.id]; + if (memb instanceof Promise) { + return await memb; + } + return memb; + } + const promoise = fetch(guild.info.api.toString() + "/v9/users/" + user.id + "/profile?with_mutual_guilds=true&with_mutual_friends_count=true&guild_id=" + guild.id, { headers: guild.headers }).then(_ => _.json()).then(json => { + const memb = new Member(json, guild); + Member.already[guild.id][user.id] = memb; + guild.fillMember(memb); + console.log("resolved"); + return memb; + }); + Member.already[guild.id][user.id] = promoise; + return await promoise; + } + hasRole(ID) { + console.log(this.roles, ID); + for (const thing of this.roles) { + if (thing.id === ID) { + return true; + } + } + return false; + } + getColor() { + for (const thing of this.roles) { + const color = thing.getColor(); + if (color) { + return color; + } + } + return ""; + } + isAdmin() { + return this.guild.properties.owner_id === this.user.id; + } +} +export { Member }; diff --git a/.dist/message.js b/.dist/message.js new file mode 100644 index 0000000..a62498a --- /dev/null +++ b/.dist/message.js @@ -0,0 +1,318 @@ +import { Contextmenu } from "./contextmenu.js"; +import { User } from "./user.js"; +import { Member } from "./member.js"; +import { markdown } from "./markdown.js"; +import { Embed } from "./embed.js"; +import { Fullscreen } from "./fullscreen.js"; +class Message { + static contextmenu = new Contextmenu("message menu"); + owner; + headers; + embeds; + author; + mentions; + mention_roles; + attachments; + id; + message_reference; + type; + timestamp; + content; + static setupcmenu() { + Message.contextmenu.addbutton("Copy raw text", function () { + navigator.clipboard.writeText(this.content); + }); + Message.contextmenu.addbutton("Reply", function (div) { + if (this.channel.replyingto) { + this.channel.replyingto.classList.remove("replying"); + } + this.channel.replyingto = div; + console.log(div); + this.channel.replyingto.classList.add("replying"); + }); + Message.contextmenu.addbutton("Copy message id", function () { + navigator.clipboard.writeText(this.id); + }); + Message.contextmenu.addbutton("Copy user id", function () { + navigator.clipboard.writeText(this.author.id); + }); + Message.contextmenu.addbutton("Message user", function () { + fetch(this.info.api.toString() + "/v9/users/@me/channels", { method: "POST", + body: JSON.stringify({ "recipients": [this.author.id] }), + headers: this.headers + }); + }); + Message.contextmenu.addbutton("Edit", function () { + this.channel.editing = this; + document.getElementById("typebox").value = this.content; + }, null, _ => { return _.author.id === _.localuser.user.id; }); + } + constructor(messagejson, owner) { + this.owner = owner; + this.headers = this.owner.headers; + for (const thing of Object.keys(messagejson)) { + this[thing] = messagejson[thing]; + } + for (const thing in this.embeds) { + console.log(thing, this.embeds); + this.embeds[thing] = new Embed(this.embeds[thing], this); + } + this.author = new User(this.author, this.localuser); + for (const thing in this.mentions) { + this.mentions[thing] = new User(this.mentions[thing], this.localuser); + } + if (this.mentions.length || this.mention_roles.length) { //currently mention_roles isn't implemented on the spacebar servers + console.log(this.mentions, this.mention_roles); + } + if (this.mentionsuser(this.localuser.user)) { + console.log(this); + } + } + get channel() { + return this.owner; + } + get guild() { + return this.owner.guild; + } + get localuser() { + return this.owner.localuser; + } + get info() { + return this.owner.info; + } + messageevents(obj) { + Message.contextmenu.bind(obj, this); + obj.classList.add("messagediv"); + } + mentionsuser(userd) { + if (userd instanceof User) { + return this.mentions.includes(userd); + } + else if (userd instanceof Member) { + return this.mentions.includes(userd.user); + } + } + getimages() { + const build = []; + for (const thing of this.attachments) { + if (thing.content_type.startsWith('image/')) { + build.push(thing); + } + } + return build; + } + async edit(content) { + return await fetch(this.info.api.toString() + "/channels/" + this.channel.id + "/messages/" + this.id, { + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ content: content }) + }); + } + buildhtml(premessage) { + //premessage??=messages.lastChild; + const build = document.createElement('table'); + const div = document.createElement("div"); + if (this.message_reference) { + const replyline = document.createElement("div"); + const line = document.createElement("hr"); + const minipfp = document.createElement("img"); + minipfp.classList.add("replypfp"); + replyline.appendChild(line); + replyline.appendChild(minipfp); + const username = document.createElement("span"); + replyline.appendChild(username); + const reply = document.createElement("div"); + username.classList.add("username"); + Member.resolve(this.author, this.guild).then(_ => { + username.style.color = _.getColor(); + }); + reply.classList.add("replytext"); + replyline.appendChild(reply); + const line2 = document.createElement("hr"); + replyline.appendChild(line2); + line2.classList.add("reply"); + line.classList.add("startreply"); + replyline.classList.add("replyflex"); + fetch(this.info.api.toString() + "/v9/channels/" + this.message_reference.channel_id + "/messages?limit=1&around=" + this.message_reference.message_id, { headers: this.headers }).then(responce => responce.json()).then(responce => { + const author = new User(responce[0].author, this.localuser); + reply.appendChild(markdown(responce[0].content)); + minipfp.src = author.getpfpsrc(); + author.profileclick(minipfp); + username.textContent = author.username; + author.profileclick(username); + }); + div.appendChild(replyline); + } + this.messageevents(div); + build.classList.add("message"); + div.appendChild(build); + if ({ 0: true, 19: true }[this.type] || this.attachments.length !== 0) { + const pfpRow = document.createElement('th'); + let pfpparent, current; + if (premessage != null) { + pfpparent = premessage.pfpparent; + pfpparent ??= premessage; + let pfpparent2 = pfpparent.all; + pfpparent2 ??= pfpparent; + const old = (new Date(pfpparent2.timestamp).getTime()) / 1000; + const newt = (new Date(this.timestamp).getTime()) / 1000; + current = (newt - old) > 600; + } + const combine = (premessage?.userid != this.author.id && premessage?.author?.id != this.author.id) || (current) || this.message_reference; + if (combine) { + const pfp = this.author.buildpfp(); + this.author.profileclick(pfp); + pfpRow.appendChild(pfp); + } + else { + div["pfpparent"] = pfpparent; + } + pfpRow.classList.add("pfprow"); + build.appendChild(pfpRow); + const text = document.createElement("th"); + const texttxt = document.createElement("table"); + texttxt.classList.add("commentrow"); + text.appendChild(texttxt); + if (combine) { + const username = document.createElement("span"); + username.classList.add("username"); + this.author.profileclick(username); + Member.resolve(this.author, this.guild).then(_ => { + if (!_) { + return; + } + username.style.color = _.getColor(); + }); + username.textContent = this.author.username; + const userwrap = document.createElement("tr"); + userwrap.appendChild(username); + if (this.author.bot) { + const username = document.createElement("span"); + username.classList.add("bot"); + username.textContent = "BOT"; + userwrap.appendChild(username); + } + const time = document.createElement("span"); + time.textContent = " " + formatTime(new Date(this.timestamp)); + time.classList.add("timestamp"); + userwrap.appendChild(time); + texttxt.appendChild(userwrap); + } + const messaged = markdown(this.content); + div["txt"] = messaged; + const messagedwrap = document.createElement("tr"); + messagedwrap.appendChild(messaged); + texttxt.appendChild(messagedwrap); + build.appendChild(text); + if (this.attachments.length) { + console.log(this.attachments); + const attatch = document.createElement("tr"); + for (const thing of this.attachments) { + const array = thing.url.split("/"); + array.shift(); + array.shift(); + array.shift(); + const src = this.info.cdn.toString() + array.join("/"); + if (thing.content_type.startsWith('image/')) { + const img = document.createElement("img"); + img.classList.add("messageimg"); + img.onclick = function () { + const full = new Fullscreen(["img", img.src, ["fit"]]); + full.show(); + }; + img.src = src; + attatch.appendChild(img); + } + else { + attatch.appendChild(this.createunknown(thing.filename, thing.size, src)); + } + } + messagedwrap.appendChild(attatch); + } + if (this.embeds.length) { + const embeds = document.createElement("tr"); + for (const thing of this.embeds) { + embeds.appendChild(thing.generateHTML()); + } + messagedwrap.appendChild(embeds); + } + // + } + else if (this.type === 7) { + const text = document.createElement("th"); + const texttxt = document.createElement("table"); + text.appendChild(texttxt); + build.appendChild(text); + const messaged = document.createElement("p"); + div["txt"] = messaged; + messaged.textContent = "welcome: " + this.author.username; + const messagedwrap = document.createElement("tr"); + messagedwrap.appendChild(messaged); + const time = document.createElement("span"); + time.textContent = " " + formatTime(new Date(this.timestamp)); + time.classList.add("timestamp"); + messagedwrap.append(time); + texttxt.appendChild(messagedwrap); + } + div["userid"] = this.author.id; + div["all"] = this; + return (div); + } + createunknown(fname, fsize, src) { + const div = document.createElement("table"); + div.classList.add("unknownfile"); + const nametr = document.createElement("tr"); + div.append(nametr); + const fileicon = document.createElement("td"); + nametr.append(fileicon); + fileicon.append("🗎"); + fileicon.classList.add("fileicon"); + fileicon.rowSpan = 2; + const nametd = document.createElement("td"); + if (src) { + const a = document.createElement("a"); + a.href = src; + a.textContent = fname; + nametd.append(a); + } + else { + nametd.textContent = fname; + } + nametd.classList.add("filename"); + nametr.append(nametd); + const sizetr = document.createElement("tr"); + const size = document.createElement("td"); + sizetr.append(size); + size.textContent = "Size:" + this.filesizehuman(fsize); + size.classList.add("filesize"); + div.appendChild(sizetr); + return div; + } + filesizehuman(fsize) { + var i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1024)); + return +((fsize / Math.pow(1024, i)).toFixed(2)) * 1 + ' ' + ['Bytes', 'Kilobytes', 'Megabytes', 'Gigabytes', 'Terabytes'][i]; + } +} +function formatTime(date) { + const now = new Date(); + const sameDay = date.getDate() === now.getDate() && + date.getMonth() === now.getMonth() && + date.getFullYear() === now.getFullYear(); + const yesterday = new Date(now); + yesterday.setDate(now.getDate() - 1); + const isYesterday = date.getDate() === yesterday.getDate() && + date.getMonth() === yesterday.getMonth() && + date.getFullYear() === yesterday.getFullYear(); + const formatTime = date => date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + if (sameDay) { + return `Today at ${formatTime(date)}`; + } + else if (isYesterday) { + return `Yesterday at ${formatTime(date)}`; + } + else { + return `${date.toLocaleDateString()} at ${formatTime(date)}`; + } +} +Message.setupcmenu(); +export { Message }; diff --git a/.dist/permissions.js b/.dist/permissions.js new file mode 100644 index 0000000..76d7bd9 --- /dev/null +++ b/.dist/permissions.js @@ -0,0 +1,246 @@ +export { Permissions }; +class Permissions { + allow; + deny; + constructor(allow, deny = "") { + this.allow = BigInt(allow); + this.deny = BigInt(deny); + } + getPermisionbit(b, big) { + return Boolean((big >> BigInt(b)) & 1n); + } + setPermisionbit(b, state, big) { + const bit = 1n << BigInt(b); + return (big & ~bit) | (BigInt(state) << BigInt(b)); //thanks to geotale for this code :3 + } + static map; + static info; + static makeMap() { + Permissions.info = [ + { + name: "CREATE_INSTANT_INVITE", + readableName: "Create instance invite", + description: "Allows the user to create invites for the guild" + }, + { + name: "KICK_MEMBERS", + readableName: "Kick members", + description: "Allows the user to kick members from the guild" + }, + { + name: "BAN_MEMBERS", + readableName: "Ban members", + description: "Allows the user to ban members from the guild" + }, + { + name: "ADMINISTRATOR", + readableName: "Administrator", + description: "Allows all permissions and bypasses channel permission overwrites" + }, + { + name: "MANAGE_CHANNELS", + readableName: "Manage channels", + description: "Allows the user to manage and edit channels" + }, + { + name: "MANAGE_GUILD", + readableName: "Manage guild", + description: "Allows management and editing of the guild" + }, + { + name: "ADD_REACTIONS", + readableName: "Add reactions", + description: "Allows user to add reactions to messages" + }, + { + name: "VIEW_AUDIT_LOG", + readableName: "View audit log", + description: "Allows the user to view the audit log" + }, + { + name: "PRIORITY_SPEAKER", + readableName: "Priority speaker", + description: "Allows for using priority speaker in a voice channel" + }, + { + name: "STREAM", + readableName: "Stream", + description: "Allows the user to stream" + }, + { + name: "VIEW_CHANNEL", + readableName: "View channel", + description: "Allows the user to view the channel" + }, + { + name: "SEND_MESSAGES", + readableName: "Send Messages", + description: "Allows user to send messages" + }, + { + name: "SEND_TTS_MESSAGES", + readableName: "Send text-to-speech messages", + description: "Allows the user to send text-to-speech messages" + }, + { + name: "MANAGE_MESSAGES", + readableName: "Manager messages", + description: "Allows the user to delete messages that aren't their own" + }, + { + name: "EMBED_LINKS", + readableName: "Embed links", + description: "Allow links sent by this user to auto-embed" + }, + { + name: "ATTACH_FILES", + readableName: "Attach files", + description: "Allows the user to attach files" + }, + { + name: "READ_MESSAGE_HISTORY", + readableName: "Read message history", + description: "Allows user to read the message history" + }, + { + name: "MENTION_EVERYONE", + readableName: "Mention everyone", + description: "Allows the user to mention everyone" + }, + { + name: "USE_EXTERNAL_EMOJIS", + readableName: "Use external emojis", + description: "Allows the user to use external emojis" + }, + { + name: "VIEW_GUILD_INSIGHTS", + readableName: "View guild insights", + description: "Allows the user to see guild insights" + }, + { + name: "CONNECT", + readableName: "Connect", + description: "Allows the user to connect to a voice channel" + }, + { + name: "SPEAK", + readableName: "Speak", + description: "Allows the user to speak in a voice channel" + }, + { + name: "MUTE_MEMBERS", + readableName: "Mute members", + description: "Allows user to mute other members" + }, + { + name: "DEAFEN_MEMBERS", + readableName: "Deafen members", + description: "Allows user to deafen other members" + }, + { + name: "MOVE_MEMBERS", + readableName: "Move members", + description: "Allows the user to move members between voice channels" + }, + { + name: "USE_VAD", + readableName: "use voice-activity-detection", + description: "Allows user to use voice-activity-detection" + }, + { + name: "CHANGE_NICKNAME", + readableName: "Change nickname", + description: "Allows the user to change their own nickname" + }, + { + name: "MANAGE_NICKNAMES", + readableName: "Manage nicknames", + description: "Allows user to change nicknames of other members" + }, + { + name: "MANAGE_ROLES", + readableName: "Manage roles", + description: "Allows user to edit and manage roles" + }, + { + name: "MANAGE_WEBHOOKS", + readableName: "Manage webhooks", + description: "Allows management and editing of webhooks" + }, + { + name: "MANAGE_GUILD_EXPRESSIONS", + readableName: "Manage guild expressions", + description: "Allows for managing emoji, stickers, and soundboards" + }, + { + name: "USE_APPLICATION_COMMANDS", + readableName: "Use application commands", + description: "Allows the user to use application commands" + }, + { + name: "REQUEST_TO_SPEAK", + readableName: "Request to speak", + description: "Allows user to request to speak in stage channel" + }, + { + name: "MANAGE_EVENTS", + readableName: "Manage events", + description: "Allows user to edit and manage events" + }, + { + name: "MANAGE_THREADS", + readableName: "Manage threads", + description: "Allows the user to delete and archive threads and view all private threads" + }, + { + name: "CREATE_PUBLIC_THREADS", + readableName: "Create public threads", + description: "Allows the user to create public threads" + }, + { + name: "CREATE_PRIVATE_THREADS", + readableName: "Create private threads", + description: "Allows the user to create private threads" + }, + { + name: "USE_EXTERNAL_STICKERS", + readableName: "Use external stickers", + description: "Allows user to use external stickers" + }, + { + name: "SEND_MESSAGES_IN_THREADS", + readableName: "Send messages in threads", + description: "Allows the user to send messages in threads" + }, + { + name: "USE_EMBEDDED_ACTIVITIES", + readableName: "Use embedded activities", + description: "Allows the user to use embedded activities" + }, + { + name: "MODERATE_MEMBERS", + readableName: "Moderate members", + description: "Allows the user to time out other users to prevent them from sending or reacting to messages in chat and threads, and from speaking in voice and stage channels" + }, + ]; + Permissions.map = {}; + let i = 0; + for (const thing of Permissions.info) { + Permissions.map[i] = thing; + Permissions.map[thing.name] = i; + i++; + } + } + getPermision(name) { + if (this.getPermisionbit(Permissions.map[name], this.allow)) { + return 1; + } + else if (this.getPermisionbit(Permissions.map[name], this.deny)) { + return -1; + } + else { + return 0; + } + } +} +Permissions.makeMap(); diff --git a/.dist/role.js b/.dist/role.js new file mode 100644 index 0000000..8973e10 --- /dev/null +++ b/.dist/role.js @@ -0,0 +1,28 @@ +export { Role }; +import { Permissions } from "./permissions.js"; +class Role { + permissions; + owner; + color; + id; + constructor(JSON, owner) { + for (const thing of Object.keys(JSON)) { + this[thing] = JSON[thing]; + } + this.permissions = new Permissions(JSON.permissions); + this.owner = owner; + } + get guild() { + return this.owner; + } + get localuser() { + return this.guild.localuser; + } + getColor() { + if (this.color === 0) { + return null; + } + ; + return `#${this.color.toString(16)}`; + } +} diff --git a/.dist/service.js b/.dist/service.js new file mode 100644 index 0000000..789db17 --- /dev/null +++ b/.dist/service.js @@ -0,0 +1,93 @@ +function deleteoldcache() { + caches.delete("cache"); + console.log("this ran :P"); +} +async function putInCache(request, response) { + console.log(request, response); + const cache = await caches.open('cache'); + console.log("Grabbed"); + try { + console.log(await cache.put(request, response)); + } + catch (error) { + console.error(error); + } +} +; +console.log("test"); +let lastcache; +self.addEventListener("activate", async (event) => { + console.log("test2"); + checkCache(); +}); +async function checkCache() { + if (checkedrecently) { + return; + } + const promise = await caches.match("/getupdates"); + if (promise) { + lastcache = await promise.text(); + } + console.log(lastcache); + fetch("/getupdates").then(async (data) => { + const text = await data.clone().text(); + console.log(text, lastcache); + if (lastcache !== text) { + deleteoldcache(); + putInCache("/getupdates", data.clone()); + } + checkedrecently = true; + setTimeout(_ => { checkedrecently = false; }, 1000 * 60 * 30); + }); +} +var checkedrecently = false; +function samedomain(url) { + return new URL(url).origin === self.origin; +} +function isindexhtml(url) { + console.log(url); + if (new URL(url).pathname.startsWith("/channels")) { + return true; + } + return false; +} +async function getfile(event) { + checkCache(); + if (!samedomain(event.request.url)) { + return await fetch(event.request.clone()); + } + const responseFromCache = await caches.match(event.request.url); + console.log(responseFromCache, caches); + if (responseFromCache) { + console.log("cache hit"); + return responseFromCache; + } + if (isindexhtml(event.request.url)) { + 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 { + return responseFromNetwork; + } + catch (e) { + console.error(e); + } +} +self.addEventListener('fetch', (event) => { + try { + event.respondWith(getfile(event)); + } + catch (e) { + console.error(e); + } +}); diff --git a/.dist/user.js b/.dist/user.js new file mode 100644 index 0000000..71e78bd --- /dev/null +++ b/.dist/user.js @@ -0,0 +1,139 @@ +//const usercache={}; +import { Member } from "./member.js"; +import { markdown } from "./markdown.js"; +import { Contextmenu } from "./contextmenu.js"; +class User { + static userids = {}; + owner; + hypotheticalpfp; + id; + avatar; + username; + bio; + discriminator; + pronouns; + bot; + static checkuser(userjson, owner) { + if (User.userids[userjson.id]) { + return User.userids[userjson.id]; + } + else { + const tempuser = new User(userjson, owner, true); + User.userids[userjson.id] = tempuser; + return tempuser; + } + } + get info() { + return this.owner.info; + } + get localuser() { + return this.owner; + } + constructor(userjson, owner, dontclone = false) { + this.owner = owner; + if (!owner) { + console.error("missing localuser"); + } + if (dontclone) { + for (const thing of Object.keys(userjson)) { + this[thing] = userjson[thing]; + } + this.hypotheticalpfp = false; + } + else { + return User.checkuser(userjson, owner); + } + } + async resolvemember(guild) { + await Member.resolve(this, guild); + } + buildpfp() { + const pfp = document.createElement('img'); + pfp.src = this.getpfpsrc(); + pfp.classList.add("pfp"); + pfp.classList.add("userid:" + this.id); + return pfp; + } + userupdate(json) { + if (json.avatar !== this.avatar) { + console.log; + this.changepfp(json.avatar); + } + } + changepfp(update) { + this.avatar = update; + this.hypotheticalpfp = false; + const src = this.getpfpsrc(); + console.log(src); + for (const thing of document.getElementsByClassName("userid:" + this.id)) { + thing.src = src; + } + } + getpfpsrc() { + if (this.hypotheticalpfp) { + return this.avatar; + } + if (this.avatar != null) { + return this.info.cdn.toString() + "avatars/" + this.id + "/" + this.avatar + ".png"; + } + else { + return this.info.cdn.toString() + "embed/avatars/3.png"; + } + } + createjankpromises() { + new Promise(_ => { }); + } + buildprofile(x, y) { + if (Contextmenu.currentmenu != "") { + Contextmenu.currentmenu.remove(); + } + const div = document.createElement("table"); + if (x !== -1) { + div.style.left = x + "px"; + div.style.top = y + "px"; + div.classList.add("profile"); + } + else { + div.classList.add("hypoprofile"); + } + { + const pfp = this.buildpfp(); + const pfprow = document.createElement("tr"); + div.appendChild(pfprow); + pfprow.appendChild(pfp); + } + { + const userbody = document.createElement("tr"); + userbody.classList.add("infosection"); + div.appendChild(userbody); + const usernamehtml = document.createElement("h2"); + usernamehtml.textContent = this.username; + userbody.appendChild(usernamehtml); + const discrimatorhtml = document.createElement("h3"); + discrimatorhtml.classList.add("tag"); + discrimatorhtml.textContent = this.username + "#" + this.discriminator; + userbody.appendChild(discrimatorhtml); + const pronounshtml = document.createElement("p"); + pronounshtml.textContent = this.pronouns; + pronounshtml.classList.add("pronouns"); + userbody.appendChild(pronounshtml); + const rule = document.createElement("hr"); + userbody.appendChild(rule); + const biohtml = markdown(this.bio); + userbody.appendChild(biohtml); + } + console.log(div); + if (x !== -1) { + Contextmenu.currentmenu = div; + document.body.appendChild(div); + } + return div; + } + profileclick(obj) { + obj.onclick = e => { + this.buildprofile(e.clientX, e.clientY); + e.stopPropagation(); + }; + } +} +export { User }; diff --git a/Archive.tar.gz b/Archive.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..ac472663cee06534478e2d254bf3e50a3c11bd08 GIT binary patch literal 90298 zcmb2|=3qFmr6!Gm`OV(jWzz1OG1W~EK`**#|;+nm2XN8e(;f#k&Lr@T|b7?&(5 z+kWb_S>RMjNy(X#l9H0s-&yFMI$K}4-hch|+qZ9a-uVB!+S>N-|5u&AZtsq+-n(}1 z-M4jDUgy8f`TkG!R^|STkLiI^3eN@1Jofac$-J*Mz1M2R{|5a3Ki4K!=6~j#{j%xd zo8O&3TAk5+F}b|@w)wy2y>IX4+Wq@`@7+DIp8x0WU3(C}=w9`Kqm})%fOwC;^d{^S%nS(o&WOFXVEj(_j^W~NGN$UbfPhy7o4 zvbLXoT{&s)!P8$~Jb0jxC7L?#+_~sYY&LI{nOOpMAFY`*?QK~0+~yqz|EsNG-LxYv zW6IesBdup2dunCU7auj5IrVPfyd$Ya7M>5nceBn{%{frI*X=w`Fz6f`SX?EW%MOSbl-tIGYn?(GxB*=$1J*Cy9*bWPNr-(@ww{$J&l zPvR*WvM$zNzDs}7UFW`P{`Pkl6|N`;?zf(G#Pws(+mO2zHf>&)3xl1mTuR)nDSOA9 zFO+R-NT$QYee1KMw%6=EB$vDMejXF|`IoIR4)M~(7I(J)s_VV%y()RmYM)Dyn`&ZL zq@M5Hue-^pkw1jhay|=pTT)|?ND5b$sa|~WW2fGey@&T&2y|_^cFnnX_4z$H;&Qq7 zD~l%j9m|zp*j|}EH{;;kUo|({xY>G2|NJM^Vvjfa^H2VDJ*V+6%TQW9JxAo& zxqD0OZ}^n&)6hFoc9p}`^Xjan8tTmw4Qdn4&YH8xE5s>f=NgxZLE?-oGFcvP_X^3m zeKwk_J89R-C-)oQ-~WDf?LVa!Ch4#Ow^y<;Nwap&{k-@~IJ@$cH=9_E7S!#{c);{P z!+^o8Ge@9Vp+jMYfN&e%iK{N0=KDI&yuRb!va8Z4Jta?>>Gi@$rnMVW&HH3H`S$(k zd33k-p5XUlEAK$5^KWOaSo>rC+NZpy_pPm+uxOct@wc>W*;5KZ0S((_|N0oSS}NFI z=VN17BhOv2M!Eg&oI__Oc7JV`;#(mp5cZ<*!(&asLx~A0#vU(@pHYi!`Evcf0QX(S zL+x_>4gcgWT6n8C=|tLf^~mfGowaCY>W;YF%?m}>sGV$_vu}0ovz~qSmvuB;cB-n$ z?G65v`6V(#s`GQ1&D{3BLuldFO8 z?;a-It5f59QNHst$GZ4qmU4yrdAeo_z4X#}*prl z@yXd}uiI-amgSePePsuOg_AdjsY!>zjCz5E$L@c=zdxY(*LT$`1zZ`z;g`cR@>``& zR;@i&_1D(JiNQwSEppxho04DEliN-H{Fht0DD>j*giRNf44k@e_gr|iO(kN_b-`Vx zRp&T#-?{lkWjffc-DW)}>q<~_`cc9C$unQt_hi4>S0z1Z3ER3Yh1QZsnSRtg5#H{< zYVZBMHJ{|=1MMo`I~}S2!=7rs;mo>_mXJ@|AEq2#d-GrQ)0FM*=Xa)NWc}ZuWc{l* z#eM%@=1s~Vo&Ik$K0I5zdGYf?nzt5-Mym)?_V}#-POP-T{ z@A_Ok-FJbfRn_T?g*

jn{k?Q@|DOE+=qUH}|Hrwr zm>w`TX*<*(zkX9?-#Ia-t6XPvC#N~w)Je{s`M);MkF{R<*y8QaEpHpQtYmn{`!gvd zt#WqrD-VO5*!gbk@y0HBR!pCvsX{;byviIwX`eV9Gp?rvDWc&$Lsv6rdE z(J9|9KUj9dVJ4%Xi~QHWEc*|cXDIAdvAc05{fLzBr!79qs#a%w(taTSy=Bi-zw>uj z$Ll|cH1l5mvGS1ghsr4(^NuJmyj;+|dW%e~%OB>$2MWbHCI1R8uI)c_a9`^d{R`qw z95d&Ych?-z`qcKjWun-HwtCqHoz!G2?mcohW&MkhUQ4)teOxeNX+j&}a`H|e?Z$8=f-Td@f zL~iys-{WsKU6lFWcWY_-6*uvdFWwqdyZ6MrVA*j+?N!+HwZ9VAEZzKbQOswDjhTuM zS*7P5Ih?Yo<@HiVN0SSCd(^vK9)Bz;%sFMW>PvoRNWXxStWnQZ`8cB&2fj~^oMc@2 zNNL_&^|gH&xu=(#1}YqwD?WuOOYXwcqdG-uf;Q$ywn*unGMZg`!?V>`&0w*G-#*RK z-7_1y}r+r!GHU^5`K3?O z+wxgH*8b(VmzJdaGS8!t?CI?)1||o9LH6j9Jbyu@_e~-COufQF!;F z3-<5kzNl}%7^<%N=kWTpDgAf0-+DIDuwbsw_vQ;1E7Py3So)_aiVEpFu-!bM-+MjC zl(Uqt*TCa}QCMK{Q9gqQ`#<+yeiSq()#O%I<;8%Lkv6_kr+x*+x%{f+TJ`l9V_tRU z>aTt0i;dVCYMiPzT4gfsZ$H|)Ec(}4GntBxS``Ohamy9;>`YoaS4lA3pOcjqlboy& zt+?Po>Y-+O_?8^vrYTFli_lKtrylj+7axNui3d>O65Jz z#^V#s_x5bsGi`ZV@f4{)cb~G>obIU4-8Z3vzsIjopx5NqBD4STMs~A$Czl5veaMj+ zXtI6Mwvw4k1CJe6GMM{u+G8>CWS(D_!ubMD3a)6bt`x}B|Qo=W$q!*Im4Mq&0eiUie%BBAQnQY3c#T@WgHJsMvFAYAA4lu@6{*ioHJwE z+eCG#WzVa;oA%ewHk6Y%e=GN!<*#4aoCQ1f$<+#eO=r7si1pMmx0x0jT6x+ge)zTd z#p?B18~SrtHayf{^gm(g^~LuWZPVy%xZb;}M?5!g+M698w{@KADK*qiE)!!=6!bV0 zC98h%-?Tmh=1F~vGHvRY{P#;wzp1?W!0jiMHs(8Knr(<(!O-V&%$lC`TX&F z*S$X}pXfhR?Q-HI@zoFhhpm>M`}F3QZ7=G7|8OmSvp;pw7u^Z+VOQU?d_V3#Uw*Ge z)!%1VADipPUrqjBVdI;gbNE#9v8-8-&Rh5_$t?&xlQQ?=*Et`}&1xSs*n59r61=_S zv-XA^dSS0Ge?G1+S6^T8>q+}@{WuxBYy-9Pk0Wd5d4z;XP1j^+jP*X28F_G*FaN&( zFJIieeC);N$O{7HeflTdxY7%m_I+)ssybzHWwN=ue!Pjo-^U*%;ue4}{E&7P!$SaP(s8 zw7x4|QEQERBxUXD;_kOzSFJXTZGFSKe&;V0!8J=CicGw+<1EWgSy%pen^fy7udny4 zG>MqJ{^7DuJ7c`O*PUe2x}PVjv@8GKo;b183(Iq-++>?Q;pB@&PDxWGg__Exr`_A* zJ!kdv?XQkm%vA9SnXGVQ`;3pjZ5U%YwWmJ|EHjN>Yp?L$#~L89E+GG&P2H!%FU#G}&*C}!cvH{K2hFV4yqpDI ze+@r0z0i4Um4AK0rdy}O>sKECE&5xN-7e`#@~2y0H)TsYB&FwAtX|u2SX@D5j@-YO zAFjs7?aL@gt31~x&vK}Rv*r4OfIVFaX@?^?zt4}8pC|Wkxq18gIGeh%svm!z?w2>6 zVP!Asc_hMP()mZ8Q+zhhzdSYVLKDO0U)NfVROiU`@B9DIx7qIjU)v{zIkE?9^IY3z z-3SPKb$IghoX54FmL04#PyYRMMa;{F)2ko7+AG}1*0PDMTH-{Y_C+zRdozE0{j=P> z{cQi{^Wr=9>y*w5^%j1Wwnlwt(*~hq{`~Uwc=Yuj)Pj&}v)J?E8%O&d2}D zl=Rx1{4v*rJ$P-MiO(tR4_fBWPIuj9xc6iEao%$)51iO=kk?{U?553!kIm>*uc(PG z6KUEI_E^VfS-SL_?#1f$H6MRe3inFQQghi{@=3RnT=IL;9XMchrT^kH$7a0=bqTC>SA(}G2~hUTgoctoxGRo?f!ggpRPaK<0#Yn z)hQJdQ+-t*Pk80#xNGaBPEKq4eMw5`#;Sem8j=>Bvhm-sEMIad(>JbPjY51Y{@t9D zB)p<|uFiv`{251o{t)wd^|9aH&bsQ8MC%20{rQ{!+2v{+J^J~litJGhkG@TshfDWt z(r`Z%^)cat{lAhQzs|m#J$*T^Yft#e`(mDfS_>0@GB-xbxAZN#QnGyetjaeD9~O0b zXf2CoaueDf_krnD?uWY1%rX~eDaXYf_=7U`Pe1Xxuo_vmkO@B{rQWKUS>-0 z>bD(#mc2Yae_!h9WtI9zeJ|G-rhNWmHdl6HL*CV?;qLWK|Ml;RchA}W;!G@0PoY%p z-?rTEy(%7$ZM1&A)@qv0owBuf;<{-m?w6h&{uC8%#3a1y=m8PCh}HbOD~`>Rv$JiD zo?tJ#*D_1#$b$zBLD_RQY(1NLtHgBb^+UFX_0#Y34Qe$0J$ahCwW?{) zTW0nrcjD7(y~ezEGkdw2`$e6Ei+<}{l`&1`IBEWt)5u;-r_amI%d}+`dJ?z&bpfG!l%UXd-X&H z>5JVr)UrEFTSV4Wt(K6qpS6vXdF!fGv8j`KA93c`+{=1ylek1Dow0&xN#0~1VfV&w z8$~~)cb{xoG&%YHx9jKszrD_tw*5@p=bCAM?|z-Vzb@tSHGlu+_RsaEo%c?w2|W96 z{rBy`ZO8tzC;Y#C`>)9Vugx}HckBCaZSJ~Q{H{qSZffPMX9b3#qEe5!S6`VQFkvlM zV_{(0)SsLzZ&!89sH*CGuCX^eb%#a%)JYE)el0Re+7cPmpTGK)RAK+uORhot5{>rV z>-8*~ckYYr`9cfc$Kbh{i?}Yjyg(X+~9((@sHkziN z5?HW5nZ2Kj>o6Ce#3NoAwH8J*zOaRV=k{&QnriyNX^&`n_vRdx`#zg^_>`W9#>M~H z-1W3t)w;|!TV~P7Uqk}zbNPt_BP8Ur1gpW+Bu<% z78%&j4=LSc_Ayj8S0u(?C6MX%mZ%L}or2Au#p4*8XJ2C5@ooDJ%izm<^8Ur7Y<4%` zF<8cKzxQX}W5?CPwsBL$Bj^1S`aF4Mr}gr4y&KP_t7bF$=&rkMAuW^idNW&#RC2s} zRi0IDA|uNgyUfeJwU0laJLMEu>-lkZok*w$Cnxt02KT+z$G-phlfoU|#qpt?qhW#F zis}2jK5w?{my8KbWD=^sy>0fR(=KAy0f{Jgkuqv<@Q&S2QtF5IY9sfN!+|esxcIg1@3_4wuKn;rB68k_g>3dVb5ibay!-cs=J9F1lfJIo#(avM&yVSdqp!usT{@CtewqnK zGbi>r7f1XlRNCod>V1TJj=Dqtb%#FXW8avJTiYTCcm@>c>qNbtW?$*eo%DF^nvyx%SHqUATzHGua{ct`;#E3n0te2Q?%_SfyVz6c zxx16oBlq`SNkw1SrS85@nZCDa1z+UEE!%Ha9{Etx-TnF|2kZIODG@0OeUi4!wTAZh z-Go&+Y?mdv?&{eWQATH&pMf&1T8t$Dqs&UdR`@4HDGW&eh6W9h0}$D$=L_sg4I zZ`b_~GfQ7|@4()ojNX$QD9Y0)hXnpXv#sA)2Yu>k9LwaH5 zr0%AY-4S^+(!+aRD9ry?ZBs36J9CM^*QT~p6AuV2pJDK_WE;b~vsvGnOh2{gwm7|g zec_sL*_(43&pD+x2H$bHsWsnGVnJlh%z4f4#ow*%f2hm(@R<97oUUt%JR<8t^*;)k z-BGq`Tc~w(sly9jv)N0QR2GDqJ$(@HDp7d%UcNhTe63RJSyyFlPye0swcTsp-l=mJ zX+GMswW#mq>h6ejN4$jJm~Xjp>d!6t*KOTAG3&XWEATC~?mK-$__gc0Xs0L5?4olN z-lTrk5OD~2rgv~tikazE4Ys!vkG=mRHoxQ1D}}^d-cmuvhX<{imi)PO|GMwrkM0&N z=_elS`O5rYZ?pCL{Z~r5^385#Zf4ASbS`I^yyMCvCBKxn_3ldQ{iW*ucDelgeftAf zm%N(w=YsFfB^{r$*LY_5DkQPHm;~^gQe1H3Q{s+wGdzU1MSR^M$H?i`wmauqm)cbC z=R9iDv)7uv&1yBe!7J+_a9sD>W>Jnknf5b8(~TxBIUdsMy?}u~WPOs@$%EWSXMMC? zCH!>vrqV;tL>^DK-&6TdXO$4+Hs2qwLl-`swJMV9oA5L7v))&yujmY4V&9lNb90kQ zx6UquBX3#0%{j9FL7w4)hkExIAJ=}KF5e$lQ~&GN*~j7e_nt3f`sIGC=H@{IeftaZ zK3#r&IQ;$ek6n9~vR>czPvvTZ*L`N5&5XY_V?@X0%5orpd3a_vfE4yYK$L-pME1-^qOE z%DA$1*&gOn_3P{Q6?48*JhL#IeS!97?j5pTw|=b_VVb_r=JBcHufO_T$v>b`kvQ*D zpvtb?fYpsMf*s4&^#(3}y7h}hrn!Vi+^4LDsyVNj98afaX|cZ9y2I^(z0{fhs->A$ z$@SCktNASOW63MXaN-Vo^FeU2%WU4g;@2j~p5Bx>@rbSPkUh>Kn*PAu=uZ)e}&E9^& z+^*=ZhNr{AI>_lY6-3+;|;FyO?|l9Vt(hfyV|nr@(%6Dn!mA=C+pLTZvBc4>^8m( z?TQ>nQzZ%&AM`brFLYuxlYO}6ZHiKR&C^%EbrX{W&M_EleV={ne_rLpGfvz-F^i8R z=S`A`U|_fzp768oia=Uw)Aa);72*#BSha2_roSu|ia)w?m*vX1*9YS&T|cSRe7VBs zxMIq}TYT;5Nrk7sZru@aC6A|n-n=#0`%Rzxn99E`+r#B_Q_KCT`xZy?_g!4)`*z*w zpkt-83!NN($Nhfh`}WL~5XXpKo=F z*6zArBd5>OnxOsDUOy;T+LwoGiRtv>)$&F^xSVFsKP$PxO)a6Wb8h^%i54%PB_tga zkyv>tdsqIsz{nfRB<0hKx%`FZFYGIPaHQeOoe#%m1(&48O#QcU>l<|)>6x>V&v9oN zR9;^l{c4@%yxFtAh#fC@YtVGXd~M{k7q7mjt_}J!4kY*Wyg`j2hXk%%&*|8MvIIj_69LA5S6MIcf|$YY7J@k*A}3VK;t z>LFLJJ8&sG91z}mH!*5aVy~jm9AABv%7y2i$XhBKX&=8Fz$eCUaopSze^r@L!yh)=d%=Er}Q_w@~q*wrm{Em}ug9`JErQD6IP#oo%!jTts_zQ`=8Ux z=cUM)UwP2&Y;RRC+j=YG$y+NcCwy|X+qg~6PHshVq>}zz1MY|iu9g`t+ViJOb{;qcHd_BC7kD!caZ&1XE=q%`^RTaD{gdnNoXPY9K_ zF?|*K^S~ar>a`n9SJv_`n=xfc>ZKbQnJOfR2iIB#awvW*x2uGm`nvmiLd^+wAVCz*af=QYbcD<}3U zntVTfJ*39_mEb$S-)*0RYp+T#%sZ2KsL@?M=k!-CkKY%=e`Gd4<>fzfG&s|FQ%|G7 zx{z$+kcL@X?;co{_O$zo@Tb$92c!B@0~TH{>gRjIGNW?N+eiAFjHGlw&G}kgB)jyo z<^4qoWulRJ?oZav+MoXK3ai()($Y#J?OJ0~UWKFUciIJfn){_DYe~40mgG8-Tjh7> zA8PKs%B3l~V*1voXzlK+t0ryR(&%Kh=GCl4+>csYA8*Wgc4T7T&w5{L))^agR{7mfm#x%Z?Qxxq3Pa%55gr zEpm~ve?O-zz4Kn=OQ*KOsqGAx<_9e)nUdE!tZ@>9!GLh*WL^|*qzG#(E9KW+8fnmsh9DOsm{vXt8*LYK(#8z1^GG zZXc;FGM41b{b2ULp-Z>CM$7WW$Jy%k0!;zV3)zGIy>;Kp=ltQw^d7CBb*?Lu3^ijr z%wIoC;fR?aJt3xby5Xiuv(sXm7wP^tP> zGemY-xq^+w;@qx%nID?h=4*$n*lKm;;+^evMFX1vsDVR7(V5yd}3-{V0 zRoB4M-d+da{a%MNVwZApS$$`A@lE}C@H4OVs&gN{9BZCX+iq&I{$P`cLg1rt9X`7> z376B2ry`e%UoY+mitM{o(ey@rio?C2Plrx1)ji$ua}mefn6rwD*Givts!8o>`@9Jb&sclNC=2 zj_COaANdjKz^|B*De`@~*mjl4Jbbx6AA~2lecP~d$;7GbY^E1|PbkmMT5;&w^@_=#v;)$u z#J2pL#;ML0=@3x)U*uuTDn-Lt;&F{aLDTtHi$3v{%ZspB^7e7=%nO&z_q=Y<6}YZ^ zg5yB@Ym-^Ce)n2k{JHx@xVh8et5+hImR7zB2*_(@@43b0(Xi)yw*FlU+XJpf2j}<) zayK78I6-dvykkG3E57pNRXyyt|IZ*f+21Nq=vrxVz_IJ8(~dv)6o2#JbjqnGf(0(>)9ILl(a7!&z!;ZIhWz5 z!L$VNdao|l=QN9nLx1ngW1(uzY0Z9 ze@tstxV8Ur`|HiD^kCf_#RqFDgZ4dsy|U|w<@_VpwcK8O`DM7mX0uq!QsDJtNir*mZ@pz1r93 z2G8Vp^D~>J_l*;s2y$ z{mhq{uYOyUo?p0$@87|vF=2BrUkwjAay)BxZ_nhWxoL&lH%UuMi5Q$FeRGr{~x z1LLCmx^A1VY`N~TG=C{i+W+5P^Ru0de{pQtyW~2%{;|)~9E#tX+*%XebfZ4XG(90R zz9jy<@XATguB0EIY^oW&w(`)s)sLq5dAhEh)GD)+XYYpY5;u$ZZnj&h>A6Sl&J6uz zdHdZip0M3L`<~d>*(BBT-q0=e$e z7a2spO*yUG8YUP&RdRCId#1Hz(eACeUJgrD1Ag9CI`_WCj!lkvo7u06=5OxTDnIlQ zn7*2O1MkJgah(8NFeW{u{WFWY8j=+9oB_kVBGnchZ*+jre3TKszS|BISxaFN{s z<%k2W^Dayjv3%UVC*0@=v)A<8Cy8@kG$wCh;1_%U>0;6TEHlAJ8mCWppXJg~lsovc zteq(~ywW@5-Q`Xp#?Bi0hzs?%u^nYW*JlTD}tOHFIE&ukOxXYxn^8XRDocAI#6&P#l zRzKcv$F$0$e4e^b1zY&KrsIp*I|LT6{)-P?F!dT2&*3{pNrCgPUGF@1LF?kv--;Yj zmt~y{Pkfc#7*g}+a3=fCfBq47_o?$H`2JfyH}~7&Q~$SLzWr;-|K9@qI%he;9yt0+@tu6rN!xPOy?a{0!Jl~oyj4v^erX=ebeU9rPX0m8`Tv)M zzDw?$mC_URZ?)0b=GF8V8@bQ7f0VuU zG}}LobK?yTR{i_g>-Mes@MW>5m3&&E-MdE%_9VXL(hgES(ebUEciQ=zbJ}t_*xs0F zaP59yaYnZ7_UgpXQ`qgMF#gT&=I>wZVfmu`gPi|e?V$7?L958e|lri$ewMM(Pb?;d4N4K}b`&deV+1I>qR~C$^9Z1TM|ikC(W1w=w6MQfFkw(aOK}{lC@S+J2S2{`#*TSnr*9QdET6~b64l4C)4#rvbR6>GcgHrbuHZb%Y0Gd)-STtMR~+d zMAqN#EJ*!WuRdw!&WShv#D}$bW*SZLdaKv<;|%Xtr_kj~r9B*tqHE`A-QmBZlG4N3 zBPP79BwjVrZrcvFw$v3rme)OB{QL2X>Uq~Y-5$O5)H>Pq;ZuU?#7`Z&cDybTVf!7r z=eS?#os0KwncGj5-~8%GIM*!uvCUKX=0^-4zYbG}iJ-omCc}sxG@WTlLpX0}1v1MJ%D$-7jleiy4H}pV?el z)0bZK`^1xc~`FJ{IuYo~6!a?6B&q3ZlqMX?RWtG7tM_IbtM z5hfE9di1b`DQA<0{M^vw=czxu1aqDHMcza?zV6Rc@y&O*8z?s2U-EOsthlbi_b-}d z?(n?1$$DksvuUnL73F50_XN9pIqcM}=FgdH*tjcVRbT9kAUVaxl;fOEAN}ph`1{pL zPQHv*FH=m_Qkv~D#Un_1hV6-Kw(Hm?E$eW${~i~@9yH~;Pt6XG1NJRn@BT13wfW=| z_J1$ywwTp#f3~wu)cHyYE5qUs8}Bk7tcbL=Em?JS*7W56`{&(An>}0cRI0F9yl~Uf zz?AJ8+paP1+VT2QrErJawli`e*Ogxytu4>GX?OK()2w?@$63mM1@BRR(Rp_cqXy%$ zOZox96PQkP-)6{{`XtU@%$e7vSG_L%xT4+FfM*NLZlo^Z3-?e_*1O}Tb@*IW_|fM_ zW@YHih+Obi8ch@WU5{KV^_?nDI3qPHDmVDWxI22ev!(FiWh};yEu-{x~R0-`&|7U zYsI4Lt16E;Ukm>gx6;$x=~nfbkGkSc#lag*o6LB;b{?~q;44>)+;rpy^S$e596Tm( z{@msCck+Uamk$g!OkDAKYd2%MdVreTUfv@+ij+^RlX=wD5zE^D zecq{~*H<0!KNWVy&$GDB{AlXD1#R&i7A-5@c-6W2DFi#bUU1fX&Zp>d_nQ)zj!u=> zeRY16@BZFLOLfmPuz{S8-+AwVl0v$;)bE z=Bo=6g&ldhb+e8yT^1ysef%j`joZderZzG9i({vptXXiyUDASKU)86kT{WTXuBZMq z&DrzZLDi=t;qn%LS91gXpJJOIDcNdUR+THfEP2zYYFOZ|T44C;yO~9!^4??H8;ZS- zy-n*ewZDH==B3oP&eazJTpE?uDST}&d%uK3rr^sJ`Ny9+8j9z%CDq-SdwI3#v>qLi zY5m5RYy5UGzWn3BH2JMrvg`T7p>EwRS5o%Wh}!?^nAo9foVLSy-p1ZN^AxQe_g-af zOm=j5f8@)WscMu5UWMR=^|gsBm)B z?ARq{v$p+PGr=#VQ{(op%ZFGd?`wTGrS0dPTj7`Yc=FB=3t)ZjQ~NV9%4wESShD!1 z0P_`$UTZFUaxLB?Z@QA9m~$7;g=lEQvv;gCXk8S%r9?kz# zy{^uHp-)JoOVUm6(nk9gbIti?<*m(puF08j@aC4MXExlq_u}H|3iHFu+t2gemodA^ zXFIzq=w*;0TUgM%tZJ*7voC%Mp3vzq`Sht-hizs?(>fH3X^~yg9F1d)kk#)VB zWTPCm?>x`;KfhaH&nNbVg%{hLuSZ^4vFfDHwzcO)4T?AOS)6WR3Q^y%c1^Rk`i+SP z-Of&JpSpCD-4nJ-8P%F?1*OcqYI$qTK1!zuu>XxauYGH6ZT*k5T+NeyX_K=4=ST_v z*O}yHyQ;`n^{CD4-VTmQafiIltQFameQmDk(u!$of37!TEs#_Vl$xT<^68VfT*Qo= z8WVPrnL9O}+sV(WU7o+2|5|v)MVa8$(I1p2>BO&&HmGM*u=x@2`pdDj<@=sI`nUMi zrDw3y^iY{F!QIfy!f6(^<%k_F)`MDprXr#2W zEZF^WYKBI_4*vea`;%kO7;Ou&km|klzxqbx7hcC)9oaYg-JA9wtz+@;6$_d8x&6(n z+4=hx_INl|-+7j{IF?Utu3@o)h%Srf3EmBBG|tR+5$5hL@l}zneA(=9y;ftU_2jc& zJavA*1seCPKU93Gc0sLu?D5vm9V-;8Gs8Unx4dHtxV(HD4O0Ttge)(u zTr#cXuK0!*DQ!88Z=9a2x3-+q>Ep9~+mjF4;!3vv+E;Hor=Y5t@?lGunW&k|rQiGF zx^sP^%_@0U*eUC+71&XowW~u)B;|xNFR#&u#PiYzgnawtcAEbBCv_=))+)0P;j>dW zx2)RqbiL6tH$mOmX}>I&CcFvavQ?_(ob41CGwtIGrhumt<*fu&(q&J}_nh@pc#xub z`19tQNoU!0n3#50J__|MSMB7w*mzmFDm~xx-Q`=?4T7czY}pl@RXiu_(%(bT8$V4m z*A5c(u=biPB-PoR&a^Q-ZN~j6R~!@0N&al_KFz~&vsUiI$DB#2mACs|+!9n6=d3(^WG7FUq!YA;*<;!kh1iSl-<*k_oH*s& zmb%$2i*IO7X7K&1bi_1vn!lpP?mer7?KAhE@&A0nu0& z7jDrxb?eZUZI5bixmYgVx@gPFmfpB-^|`BtMd?XJi)LHpaW`sGSr=JoY$J$TVXd;bh$rGn)2d)q4CEK=BQ&Y$pEYGXQY zXI_T;o!_tSW=z_&kK_8j^=lVB`s!w~;n7BO%X!lOv^PD!y72xL_Ama|PX6^%=?>ky zvHEq{*>~&ejLH+&@A&tJ{a|RDK+{BxuDByd%L`n0op~8-x=OxwhrF&#q{q8P*YzuR zzAv6p*tq&K?-z%OLHlA?@Fa;Dh97GGFYdLmUagB&xNz|jyIy`xv(8l_+mbg*=FMDx z_H)j>C#kbIYP&;MT-qJ-HawziQQ@QmQ;eTfD7~Dc?mTDbC;M#alV73~TtE1DupRAq zcyV>6m8hM^b00J9$s3KipIFu{S+_m)Pw36P>wcufH1EFMrE`|QcLLwE2k)x9_x}yk zv<+3=v;XCz8@re$1m8Y(g~{0O#v}jdFP=Pizb7L4#w_Z--=mw+JD)}`FEm*=cb@*I z&c&-5+)p$H3;F~mom|FP@4i#-gMOpQL#Y6^$>Qphe{daHARNJ^_H*~^;BV_3BbQI9 z7BZgd>|}E3dBRcsGh4knJ7ajRbiDU!`jC1*?pn{Z&zmbJ_TM^U$h>=#zE%3{OuM4C z*tN3PyIJpQN6XHyUV0*=?3m>?@3?cBHt(h<&U+nwrQq~cDfP`$A}+hlwfT8lN%NG} zl$7XubE|^9Kg{4ca6#kkoSHYeC*C&I*gR3FTv7edwey|%_a(err7eF1ivEec)%fqQ zYwdbDx&O6uo(a@{uvKkXbMpU=y;m;7fu{k^c%Tx7V zHmO-Zm6at{u8Tg{ZO+}I9#!;o&*~?k@d0mC&PTFG=?h*6jX9uq=*ZTKDTc=mbWUd3 z@-_XX_4WB7iHS#3*QKObnQeYn^(f{_=)!+1q%A^tw)``=nkrFex;!O*m6Wy3#fb|Z zHoxC=-hf@zM}AG&Q`z)xU&%Sf=b!!z+^zokNX$2uoj%Ko6(`orPw%cgV02z#^#<pMN>|;N^z6yE(vDZRyx|l1QqwSH0Y~w(JMFXmCWY;ZOj`4KNyLI&$KSWV z{K*L0mK^+%Z6@o{=?lNM?sKSU+}`w-m+waQt%`YZYUhP@tbE^8yL7uUBzGvr9B5(R z`qn|>(N>{Empx@u5=9!ww-eq-CcWaquG>Y;+|F&PPvbtmfQ#~70(Y-`W5xo^Xp3{j^?Sm zH>52zJA7N6UBUGKzXxCEX*rw-JAX7UjnnGayTDfdJ+&upKWmf>?=iV%a%zSwv!}2B zI+wKedgtaaX(P_Z%Y4^AoOD|FqPc|6$?{3p)f&u8CK;QwF@2hUs`(jz+|^h1SEt_L zJy~NH>-l$byyr{x>#0pE10PO!!MJ*v@k^ErmK}2qwr#8lF)xnLhzw+w=P6jm`g_%n zGG7Z0*=;5QdYt+4KLtAN&ng<$N4~x$-1kcUao9!9=oPDDR!Dw-`R!u;?-%n{X{euT zQatY$z0g&?>xGZ3-eNcID4k`8Un_Q0E#IZ)_-$32xA`%-tp!pp&yRYzr5MjR_Skha5Tb$o*SJPR>n zmZMR#;^ke<+PP08>9O_+vWu*H$UoibPz|r$rLs<^4bz!3J%772|Fbx;X_l`+<38`% zvs&7v{t0afb<)!gJk|Snk55g>0f$S*Pp0}$k5{_4`q8F&tm@*kH!x=}@>#LP>Xlnx zJZDKu({uSBaqpaM|NQfh`?hDk*`e?C(ba#~=34(RuFwB|`S1PqifQ|9S8qx&OApY< zW_@@meQM2lr}e928GlXEpFLgq>RZ~bNscp zc|~rD;AWZEJFdlrq&|217rr^>#2=n*IciVdR|@QKkW2A<&_A8kvg6_0XAW$a*B?Ks z&35YfROutG+b3w|_3}uY&hfwGo5$KRPxWK!q4#aCf+dY5(lhPmI>;K`FADu%5_>*< zP3+D`za~q*_uE}#Rvf4;YoFD(AIdy;<y3jy?^<2?a(kkJMxIq;Xl<&)%RbhYW3vC3wVj=vb*KE~yBqs6EZ_f^(@Bsz zR=LPJ{Ai_-FlX}eJ@;JN96cW&@SnP_Kli=rj)R^5r6X9?=dTg?I z#`RmgSd%{}OFlWqam(vSx^3Rmj}~VQerT*uFJnv22x|0p{CY?$b@R#6B_SJSAMW;R zv07^uX%sebiRGCoQa7}mcFlERKk4;z&9s#tKOIqPROeWI{DpM&Weqiz4R4Y@^Ivvh z^PeW%{AI=z|Hltjd$YyGD!$ON-{9bIvB0vT?`U1&r6mST>ggrf_hk)UEo3gcUjNm_ z;H#@X@4Djfvz-fGOjvUDk_ppft%`Gw1v zY_=Xud-&0px4e&4*+$=eqPRd&pXbiUS62keyIyzwC-+xdA}pL=|B+u@W_ZMT9a+Dh zj*DK*nzriNJ#FTYZCO1NWdjf2yJxZBH+xXa#J;xQi~6IwN>z^4&x^d5R<`T+^R<2s`|G*GMRNZZD>Z@t zd#5>_zb-zr{du>((&ttVHQu!zk63=Km{}l|@S<8feyM|)g0N-&l%vwCW}BUKWx2+- zcXQC(A8)+oXb1oO@MN0^-?hNr-l=ZtzpSPdg=)-J(R4QZ{psTxgV%Q+FK74cu5y-= zmu_7CWv8&msRAA+!KtsBo`1^z^^j>sbJ~AB>45pUJ4Cj&P4N`HdED=ba>VB5*4>_M zn>UbXEJN$5{qN|=I$ukrt4wKBlqc_N04`4^wE+-HxtTkBuB6?exK6UKWTxM z??dI)QUNM4%$;d3WWv553f$PN^=+ph@9Me7IaN;FN{?axdca-T`{2TyT%O`(;jNPE*M9d$93|^LHk$;v>daWJ7bBKje8!FZs!5 z@F62wJo?4XNlSG1`L#TY`>!T(fU|N)UQt;B9|uy zX&%0l7SF(8lG6HfgTJWay{pTw7-@ENe7=%kn)LR)gY`0F=e4sL*G~6XzM5mvCCg(dN=nBu>*<68)D>Twu9DeP!FgI`h#rY>=-d3qBGn4Ee z&w}p1T9tYGtk@UVY4L{f`!Bs~E9(*NIe2oS2#1A&-t`MT-lD}jC**y8K1W`4X5G=9 zHC*4O*x0Vt>)gBD^cd%gt!J(#?)PH8794yx#{= z4qsRKzgM`H{j#t*d)em3Ea^FUvp>Fgd#u2)vU<8Sb6)`S-JYF|bCyo_G}y;(^ocX| zvddXx!~LGg%B=4G3v`|83#R$%naZk22zqMtzhPZ=Tg#jyuIcUWyW4L}ct_XZFQ{!I5W39e_-*#Q7%}-r;X+xLzr}Wxq@=9jG(^sUe-M!mng|TLi zY&T|KmDok zHQgyk!%fPs{M_I#ytwpl_(cnq?|m#zzb_s*mTG@1aea#Iw_@vEmVO-u2V0h=+^={X zelk?c!qFnp>ZZq0u}`Ze`U{K31Z$srI^ocjPhD^1zA&V0o^hHqXy+j{AWACfO$Imc8LvsCH|Pt5AR z^XJkbzWyof(*ne%&237u%;J%8*8SJ|)#clj<&!Qf(FpEnx+!=~blbT{B~D9yr=Hc$ zY>v}%o4L;RX~)hxKBp$9SZ{d8*ZO+8*7X1>k=24_HOf1lo~p`SztnT-ffHSy`a~r5 zEB(IRzsm2;|L>Y1+HHBp-|X%L*NP)1s?6&x~^mUiv{m5 zXl(QP(5Y>|>d_M^q2*Qv-PvBwIzF!2OFqq;xkb6-*g{Je;}xaJTYY_<^KL%ZiFaiE z^D1^$)g;rR+H=fXX5MBt`F_bQTiqb3xM)F-l3h$&&+E$S%4urC-(TIY))!h4ESQ#= zeQ(t(gMdPhQ0ZMIqIw;9k+#Pa8M*d;yK~)}*Emce@Y9M9a~S$8mZ-$=)>^l*+FZRF zVs&`x+lyZWrB&8DY<+p_&dlI*hR%F#E>AbBpY~GI;ofd?Qhphy^#`BlU%Z4v!|t>0 z7hGHS+3vezb@F3hHZ!yL&paa<4l6U5-C?n1Y1sMly-kX7gwoB%ng@HQALQKfaP#tI zZ$B9IyL<9E9(lcz;p=quW~0al`g<7`eK(K>iDULFoijKg!L<_*dO0r#`V!aqwrC=^Q#u| zFPUo&i*ld4=(A6N$T^m ze0eXfnJatc<*91~12%n}-6$5AE@^rz_j7?|&8?X0Ic)LElC37BNPb)&nQ>?*dr{!= zLTNGG%G1BoCam$0vHLoCHiK}4UGMkCUA7Ah|6l*meRty8Ft^}eS)tRS3#62G{Zid? z^Iq%b9|pGHb6HR4Fqdy-t+=^gc4@lkw)KKHpZqk}<5oETv#If<)1>yyp8`KZ@94g} z_49B3)OkPk-itjA7XH6~|5ob_kN>~_V*m92KZWzB3jglixqGY5gBSM}sx!K~b8Hs* zk&%7}5*kbM<2~Tw<_{HbwdwohV6O#5WWnJI*>(BF!_iX)N_=--;sv9>bxEYu` z$i=%J%DQ=ejx@`E@r;*$0)Fpin;d?!rYJXoL1%KAT)?upyE0izH-9$zyo+y#D`&ge z&x`LbiZvW}-nJv%aO#Tl7e8K8ZmZU6y(*+~^x#zE-D+PC=N4I;I5HJ)O@ZrZBueYvJIlH!2P$&HB_mz;<|u3Nn+ zsW00#@}KcYI@GjWQJpO^-QAV>b<%_eqp7?4H=uzQyOquHxg@FZITk=edhSD8v5MmKh% z>k@&2l`9v2VzydqQ-0^T^tQ{Y)8|M@Zke@Y(_%eQv53jXnB|Vum>&w@j&wy$vj{8`O;V1y$26RANY4`+NG`5ijP-Y=+5rY$&)JI+04z)I`j6fz=B*Y zm1nQ+aV@I;{fYmUzvD#l&xPk_{&@H7rt4|HkN;-p^M$-B;izm>^vU@f<0~`O^osHN zyAxHH7hn6lWIE%;`pVO;aXXjYYh66+>D6T~b)KFKogTp2<@MwuE0agm(a`-pGmNIm z&fUKtqt_)q>YHxmLqBts#)-O#Q=hWA9m>w?`w_$P;ep+~mW(5tH?ud-TWR+5>QRrF zh&fFfecQ$R@}?;9T5ejQm*L`3^Lp;m=B3M4)_>wzGwDa)kvpBuoDXtV3nqL?dCA4h z_3frgW%~7uvPmVDn-8n2thd`*@zs2C$*gJYfAy30Ota!}Skc6N&~WOLt2ulhERUw$ zvq_$skaco@pzXalb*~>HKi5sr&vu-!HY)P|oDMc+fmJTd^X@2da!m3qjNNZxwW;*L zy^Y~lWH)LUpWnQ>WYUATt?hpgEWS3YGTK?Ka((vE!_{xDM&8xS&_0_zag9Hx;`~`v zntEF=7^&yZJU@G@^QOao-?OjW7MJAz&Hpq0;@OQw?<@k@&hP}S(iBdx=W6%1uoNyUD>C3}Aro3QH^t2|HU z;0N8W+|B0+l(9!evb;UsD6H=+xqL0p$yL90sY+UeZj8^F=sr7e|Mk~D4;Wf3W$f>N zS2W>A28TiF7DkVC?(y!s%Q8<{Cr+}!7$wtMaRS%Utp_SLQ4n{jUXemS|la}FwJ=p7O7*l2UNX zC+pW{=?076+)tJ6n0j2iRlJg$KaYRS&S_h4z@8xryxvUfREj7CClBs%Kt<^JlKkKSTMrBvj&Xp(o>&{;gyS0Yn zch+A0nuMG6e~%v7wBqFFMYojiSGn8{5T5h&;R?O9{PZU7O*@%`CUPuo-rxO2{G0h} zrlT)1r`$1*lT#1i{*-%k&X2X(yzxSLzmDGhDEZ4T(vC^fykLi9`QnRe?^J~=19Pw4 zUz21n7#`-O+^EJw`1Gx@_sA-d2QN+b(1))AFBKg&6~bp z&7a1jEmK(!?G|C1F2vF)7i9E6=u^MotH(QzimI)heBxS@jnQexbET0heyO}uFil+~ z=jCg3(fFM8d97_PWA3%BzdUEFQ-wpvv4&9L%6k$_-WFFfmfha7j9FmTL7i0J*gOlF zr%sxyUQg3~G*#!)ulN7I{{Isn9m2T!@h96zv-9iXip}RUdQN_3=RD)xC7E4Z*RuAN z_wgiD$IRI&)12(PGnys1rX$PogZcM0msl3&ns!;P*)z{LGU1;7+jFc_rTF>s9V??Z z#$R)vUAZ^y{WsD6wt0>#yN{Zrou0bN{{qv!_IGR+L9h9?Z`jkk^4QjkyL&!!6q(E` zY?5)=`$GEL`?@bSdiuN4C+nQP{X<=5kIUP$9(!MYXS@AckMH~KJ!^EV(v@^YQk4wv z6r|o?`hHSj^#MNCc(+=&Wv1WG|9w&M_V#Nd&1+T{Z^>WwG@D{SPyPAEUmt5%{jE5B zME2j~Cl;Zaym{|54dZUxD;(JUW@p5riu(({iC*M2w7(Hk-Q{kvg8h|l99yeoSYY+% zqvBU3Iyara8M4EG`*gTVriJAn)z0G0PG7^0zn+zw;%Vo0M&z0C((9q~6>ly1v+Uun zg-olujKe>Q^D11t%9Ze#Pdao~`Rx^M<@=<38+A171?QApYR`WpP+ij`zVW8g{r+og z(vQE`=-!+&bJ=FsnHlS+R0%~I$d?@wc(Fd;NFgZ3?fTG3)dutLORmm&cYncAm8oKP=P#NR)&J$c&^*icOM5r`56`bo zzp(x9{%zare%9AcSblH5ca`@-WAXTHho)=HbGWDKqp4tGIV&?SRcrN5vzg1jGF(iK z>=(B?7tP7EWw}J(#=DOWwxvtnc(^F{#SG~x^*6o$>{1(?8R= z+0T|fU90h4aX;tQX$;35cC6d-k3pU5mXDN9pvJC8Rzll1B<+;*Uf}mgBi=H%arT9& z6%STd>cr&CS+>L~ovZLgR`%}ex)H&f75`7%xOR(z{%PLV=_|x%ZI9Yzyy~zP*CCVS zwFe`a&wgV$Rw|cJ&?OXmi2sq;-`T6T?6H`ucgoeq@~PoN{sSVfxc~l*oVV$5P@&Z+ zvCWg}pUEgcT~;$GqwT0%edg>p4{d+1%v^FjS^f6)_s292ZW4KU$KtY`deDOFd`lm& zrRm0lD!ZIXw{-QR!b5yc{@EKuc14+pmNvgIUjM*M-{nOT zult{SJKT-DeK&tuSfiKWxpHFbd@ifidp$I5dM$V^UH@BNzURTG{S&_33zjzh`2Xg5 z+vtDqbKloZ`fq<^^UlbdKYJcsbS+|t{#)Js@Pff97W?7{6OULFez5LnvMY>gOj<} z|7PqiTc=>8|8Q~U9qmkY-{qH+&F;HR=IL!Jyq*2(%H1Ckr&=yDmASWC);?jW;F_tn zapBkIId7(!%chkwOm>|sJfpMk)xND$l+-s?7yNp;`Nn>?7m-Oay%w7@t=*ONIs}g` zT^6wFC*z{KTZ*Skd`XJ-UjBU7Y=@^}hmNwI(ro!quzAky4xyTrZ?pGJd8ga*DBZlP z`J>HxzbN4uK{g+3i)_A3e(-(k59o|;W06<*j*100K1nXwzoqk)rVcav#547( z)ocge%vEPj6s;?oH0R*cx7G?T|IAG_ixqvmuyXz0rFS}dw_S88*uyDcaIrZ-!_nqpCjE1D`#eC#&h*|dI%8j5#-&@cS*+N- zmz|pBGDoKA?>4DL&o^0AUHLMbYx}BKfwl(yCMmk*Hy@k7=P|w3%GU8Fd>@CKT>0Le zC({_XIOolM#&Xo>Kg+j$oH2oSPp^L&k*m1u&26`ign}-$BP}~s0vEka;d!s4y^DvX zXJyV2&gD71XS>b{`cx_0xl#Yh<`qg z_q4zMuMibt^~>U8e6{u8d)u!3fBP=?%xxx&JsVtmZtcJ`0tUZ;=0ZRD~JoLs~*6gJQ>qukPQwQi|Aae^5M<)L<+5N9oqu8G=(~Crt`-&+xe= zu_kfvqvw1DswWnDwe_>`i2S}TrM0)Ap>4YH2P?_vCp8);l&sS_cX7Y#ql`5EDxgg_GJ&h^Oj%d1tJiHs#{dzD5Pz(=(De4=pSV zkNB{HM{@R`M_=Atd+w%OsxrjREuU<$=?px_gtG0SFuY6l?EOq7b!Kk-<@AM~q(mH)#>y2vil8a(G;pyg4 z2l-fMKCojswy2=O@6wL8wC#Qe11y)vr#w{qYn? z=aQ58=?B?yvn)cSwD&zS-Obs|N>^>Vs??K4{dR5FRoJvDC*i8qLD5Dq``B*d|r2cr>BZG~buh~Bgywm$qeBXZO zmOW+8@Bh}#+NB;hdw+Db%ajXxbJi;HzBl~^YlCT%{1QpDVla} z(#BfLN3U*uVfE#>_37cB`gxE4&sR(TnDmH6?@{W5bx$%*@qN>bcAn=gTv1`*d2(;X z-B(&}zkiB%ug+Y&a;`#&Q`g_f&l9e=Hr$xPVR!fPpT~AF|DG@Z`8}0EZrXqL+s$+D zdi}e9yY=_{g9*PU|M{;dmpb#WPt>ZcvYVlP)2FW4q5b zl7Qni2PKwzR2j(*Mq1_2@Wx*7o0Fhw$(c^{1D!)iRbZmY?+H&!Uh8`l09M z6`iqv`Dofyk!;^-G12eS($jfXtSGu4@A~9o)S_cHX-5vuC=XNZp7Du4Fw%o%&6oZ= z)1U3yyKSSQ;k_LW0@<>=x5pG5ux4J$o%SjP5yLKbl2vjeF=h5 z>ApHv0Z-SRl$O?1QPlbKek1euC8w>ools}pzfEWJ_LFP&Nr^o+jgz&j`z0PYlhbLd)SlVkb<5 znNw#Szskj5G}u_T>D&jk8ZtNR-4b7P!K9}$SN{P+g#V@mT8Czei96~qaH`Fmd_!0|TL0bEFB>+h zUDU7mup>1|^^mo<@#5sjT>ZP+W(E>l)>lsyRn3i!DK(iF$a}L`ZsGqguOtommzcM2 zes_0LV!nLE89^uAd7%yq-<{!!ne=M4?Vf;(r^{_Gez_X9eTL`5D|(wQ&2xQIt9{tT zi>;y3l{NQ3M^;QrM&>1EEveqtiG6pgD!8g*({^^~mxica&Jt!Vme%&3d!yn{$8|5Q zdtKX}2<03Mf8nPxJDTyUht)UXDK&2nm1VIhFWva7TZql>$T8WdCtZ$P;vT!n998D| z9O&}@ZT(Mu5$njZi`6dwr>XtAUGCW{IZ^B|`{@W{_mbA~GbPP`6})oxf0MYlRP@3R zPiFO_ajp!ntBtqnnRi@nEp6m9Fy!?&n`1G1kxXB0O$KYn%DKjK#YL`V#?5U#X18g3 z&d(14;(J6hZ!X)geUs@KoIbmkY2u+xbp zTKnd1Ju3e?gC#Q6s9x!^$Y!yl2lrSVZ08FM)y#a~t$9;|@sv-@JgZmwVKdhjUrw4J z?NqGOQhvT^cTDk-$lpe9W;yKMGjDU)g$;q*jhzDjHXEN^u z_=_#@WqT5y6hB!$_gOCc>`QHTKb2jpjy9O5%oO4Eso>QO%kqa8KUKYPw_z<(Z}_wC z*KHlw_w&NVHLX`~x4fYxJb!kF|CAZ&eaq8Kf3t2)xx}`j;az$}=e@))=c2^~mT~Ti znYhR;!?H`sAVg^2?t`0-Zk(~*sz&zORF?Idr|np{+U&{Fu&B3IyCcp2vOn3hGGMjd z0$%z02-ZJuK6C%Rayfp9k*3)mse^Bvk6pU_<$6!WiKV>`a+7{eoiOj%G*hkWEbZ)` zv-=l3_;}58>BQG@q849o`w6|s*(d*XyYdMQOUx-zxxi#6-z~5H`{KvK z^}@C_6-7dK57@t+wP~im{l(h9Qw!8uu1~FRF;Cm;IZ-vob5`q0d0pv6_r&MbZK=ul zl@ReO!Q>q5)G(ZylGNxDQr05<(D7`>p;v#tyqc+TuJrW-h2|R>M)yLF**02z3*(vp z)iljB<=3;N9-H1Vd=%Jm>p8R7VvVddp>jpHQXeM!aI#%LcV};<-I+%G4+X_yh;fx<^T3HEK~t zY^=*>)zv)xKB*!%|4#PdgO5vtngZgECax^$T_7MF9~xwk`yywe&$QgN*+D(`T6P$v zus`{%;(ppm(XQIDOUC?Akhq9o%bT#CPdP@8%KE1#2Q8HgVsKJgdOM=@#-{zHm5cK? zRelLIdGVDeaF%rAsXU>H?H!+ueY`4*EYf?jXRXXR)*52Ejq|r@%Zs2ZdO?@lnYDDc z*EoG$WVi5Ac7e*pz-_W0b*42>>`GkR(cZeA|BzIoa*OuIiMJnqJoP$9Vf!?B?G6v? zGp8nPR9U|G&;Hr7cQ6#PTv+607PS2S4kIz4wLO^&lwW?@b54KLMrG?BiOffpiMQN- zPdDGbq2TKo?FP+P3puh2{uj9>@g-l_d~?&4=WA27_M0ntA3gN;WevB7tFnXnfm`PL z_qZFJf3%=4Rm;8L!TUQQFC6NO#BM6@R9fZcQ$3scQp}47*-=b^xhq2NDb5ys<}Dpo zx$%Rx*RBs{d`_V+b#Jr=X|kD_U*2qX+gp5cp<(y)*ZZ^L4+&qp%UNo+r^a?S`|R1G z<=fgXtS#KE=aPCU!YeE$z4+DXv;Ju(%KsJoj?XOrS$Ati=l?ysFW=wvct6jt`G5Y~ zolyQ^oR^gIHGi3XT*~Gr+rJ#IIP-6EZ+*3m^z%rodp^4bT$b2&>)*V%L!{D5e!lvQ z&(W2F7pCodbY0L&UkXm3-?LZ`)J-kS*ni#nofHwOvNMR=wvwv+!D}E}imX z#^I%#Jybgk@0?n-Jv1fZhU(VB4C~Y$y)I|V^EJhf73!8`x4K4#+%uZF%;VOV6QOM) z*HmRB4t$zinmD6oHJ6F)6j7aNhKo+a98y?!G{QHm5r5`;mxBKh%8paz7{?|FF z=*wAC;vZwc`R!;)k$zTMLexOw>(FW?R1J#N;DPOA;{uA>vVPf-mI6xi+r8m?Ei7-^U~D~(Y9;eE{Y3&v{ZTX zq?3a4-@k6!9pdfYcKP~E-Q)h|58CdZH1Rv?y29YnvlAPbzgdXxU$rKDSNukOuC4PH zyJzoLtJ;+}*Sae`JN2qqxoBs)fS%>`-0e)3dspQdWhpmrw)PWg^|5QRZ|ds(vFqJ> zYpL^0r8loT#BSTI7OthK78*Z=<3PZ-NsJn+dU)B>8<$KmQw?FveBRHxrE?M22}$MC zGmJe%g{x=Hc7JPJeWUKT?k}IizJHG#%T^Pv<;A z>N&mvCM&0{FPxVcU|m(#*%HSYCFPuuUoWF9D)s(M!{Q8u9&bgt51Fqx zokR5Q2ahQk(c#|`SPRQ!vt~`7@H!%An%f%HxIQF7?#YzJJ`OEYvlh zE6}1cnVs#mQqE18i?)|G?0@MJUGR0$=GFia9l=?>q8X+NGSX{G)*Rd0d|B15|K^jm zVPf|;F;>Rj1(>gbAP&KGz(xzcyOvH4#9{=A|2QEg+pZBPEpQz_JA4_*^etNbMY zfY^Si;^aji1Sa2dT{5TW%fw4FzPFj==__7Kd&_vW$?d)6x3}94rbLOSTIzo@H}9^r z@oF=?l`(C$Ztv!3A1=`%ExS5)y{_)nI`XscXw80n@VJG6@8nNP&6m&h>&NfE$Y;G^ z$_;+`g6JDr+3{~5KMP!?Ix*v|cFh{00+yzZrMiV;m(R@&^_)8IQdX?bD&8H%X$(;x zYwSFK-pmvJp0nX~Q}vVP`S)tJe{`@vQD_u&=EjXr(`2(|I~z}Q7G6@sO*8h6za zr!em}-M#W_b2m*soVG_pf5)ZLwaV^+F=szX)GEhp{ggj*_2mWAcbr_Cq`_XfL??{Z z_zl|96?J=o5Y$4T-NySiY_<FV6hQH&H&Xy=5H#Z>Mj*z~j&RU)AaQt4keXQP*R{Lo*^) zm&G`>?bsxn9jfY;)%!~O`TGCgw5MDxV!7L4eri=n%3r-(J+VvHetjXmZN2*mn@iWu zKC?-dQSt5awal4qGb8U~c#-bj!>(Wa^vu&kXK$Wsx>5e*lKt(QKiYVwFFh%6)H7B@ zc>fut#UFiY(&}fU{TI-f#{4_fdC9`v-F`*cD`vKzO3RZf`0>wc%5 z3B6%QU*(w<&cEaE=jy)Y6X&W~{*64hrhG-2VK2kkI*mKmI&XH*F1o5d|F2k_?dp9W z=UOm2n=8sYYUn4{tSh@;b*SyU&sx^>oa4&@t7M*ClM1rB z0>%8hADZY!%lb*@*KKtkV?Z5xd?RVL?`kQs_zmxC(Km8Ft??Cx|H{U2|9|uD z>$h)8=Q#d({_op^$$yWxSL|cjd*J%_eNE1mzweu9CYPSc(>-Nec5d2w$9r}evCB@1 zN>pz#c&C%Rg?;Vnl9)J|9^nUvq<<_HcKp+5GA~EujlfiyFS~6S4ws$SE;y%zZ-zue z;2}5Hi%Hg}pDbwI-`%Z}IKh#*-~!Xn3}Nn^sM3#4J2E0HW#zZ1z3@C+$GF7zVtB#% zL;VI{758vWSor^|@sE>SU-$pZnz1=t!8*}WWRa4dq3o6=1}C!)iha1MBBv&7aLghg z{fJoehpjgHZ|k>O{`s}qSLc{w@>a_xu~TN>YW40d-~4mYm0y>-o(Zct$EQp#JTxod zRs=_alAD{U^g@ZM2bZl9yJxZ4Hr23a&g&7>bvgU;64T!W3+&q~?k%+rZZ_Fv{fXIF zX8%pgZ4-7B|L`@KDIVT&yx)oWDud|K?Taq_46J=JgP}&8zqMb&WKW>|gGDyl$1_i{ zR_6-rbl!8ospD2r-N$?b*~GYw|Bn}yc}LDXp8DfgCY$&J@sFuOM~x+=9?f2Fmb~Kh zGTq|6y`cB6IL!F@81Yu2Sa z6PukCkXSrp+U1ISx_akLOLMq=Di=m&FSoBa{x4Tk_7G>2>;0{lUF|a8Zs%zg&o7-L zma{N!bN_+G#|%@V9>!1jIKOX`=d{i1=YKD|VPE+AyH@poTbrW4;mnoabFZxVe{g}l z!_WNX+qawExa;;a|AXhh-wNl$@9&xI_aJM1!tr0tVjNk4M$)MQsV^5C)sNcOsQc`b z&7&eYombOL-aNP8@~ZY*w`a%wwD){bdpqX7-d472!adgi*SjK;H#d2!`55fnwd++} zZt3PfHNrOc%^G_*Y%`d|aNB>m_1(4i-uYd5{f_U(#s?4crswiV?|pYJtlnBGr+oY3 zy>aCmc5lA;{_F1B{E<87-o4lSkFDm`gZXutjo(!#3)r^@RvxelpHlfPtYann+IJ5Q zE--i47_jx%2Sw&>rx)aYFDolwT=)Ipy$6cf(-xlCaN+jd*Wcvdz1ujc`JnT+%eUWM z%Uf_a#mMuyt5C1R(Qgthx@wW}#)tCCtNFLh)L>e_J35+Q@@vtzx(n%7ZWKLyoA=(P z;6bi!%8Bd`zqAhom%QApc;{w!+07Ljn4=G0l((=x3-C_23E)Dt8 ze{KsIFMRbmtbF%>0S((5iGL+ZwwKErJlK1;y?O2o{&(aEZNuSv9bRn!@q<5J{n#wy>h?PrQ;9n{m&M2|C4@n^V)mY4!`1d;T0);RA+PJ zQNxi=_fW~4-Y@*3hZg@U*r2@IeDZ|05B&RF_g*_@aQb+}zlRNfKfhJBn_&~w)O6Lm z>am^r+52LVnpK;`-`mzq_#hUb7-*`fdGv(*b59+PebzUm3sO=`WWJSE=UdoV3#ZIK z$SSD+cdkTt<-D_-SSvDW_I(%lH!+{FjbRSYvFPYyzq?jB?5_K?V%Cz->4KMY9}73# zmVA@*@tEPh$k4><{OTRsSJyXw+MAefbK!J%qU3v$-~`}nQGyz<4mx?@v>4!XX%YG1?g zzaq?jcJLed^RMLl9?n1T>iDVTtgrps+|VTm%X8jljpOQ&jcq>;9{THeVtm_U+oL`q< zqqe`J{qVnx8t%G-e#dNYOk25Cesg-tn?GFw>DB2Und4SpeD^;3f3yKl^2I$z8E%N} z+$vfvap2y&d*_;M<35VXB(REYVB-H^(0EWoFnDGHQ}Q8NcPyF2f+ttmsh5zo~{VVeSakI_*H}*Nd`33jx+m*dM z$MH$g$~TTrW*hDQmm<1pPbde|1Pk>)$AcvPsK^-PHMg{UtlsFQ_)+2mqoctl-gkKw z;U2F27b`b9+4)O_bM~$3aEGl&SI3lv`V$iygVu> zdb;HIhaU>K92#{v9TEdsgL2Hi|M+oK|Nh7B{feh7-r9*O3#PNUF4?px^F&bLW5M70GcGHr)V;$qd+3bE6+@JyQH zzG?1;&IuE@KfO4a)1_}u#Brw+TR1m)X=EPIOp)NLt^B7b=l}0FhvS1)PxiO-Z#(k$ z{NZ%no^{1QXV@J{1O-q-bj$XR}Ps`$`@6(UnwVf7P1xopyle6Fs>RG7j^F$-!<+M1K#se|yZY|`UmtH==-^Sk*_-EE zMtIo)Zs&0Sg>MfAva2?xq__ot-g`@S`M1Q@;A&^{Ww(Xw{@iK3|EbOU48yid`g4A; z@GIZDeaLf1=)-qUDl8O#g)dBg`K(ss(}#WjdY_in+_bu5Wwom0?>?scCh}(-MBF4o z7VEk{ci!Ju{dKD0`t3dSX6E6CS__`sxua5Jc`#8hv))`16mh?;6)#o}Kw_eUKgJ8m7d|UpID_p60T<)T!e=ey>-{5<>WpI^?Nw||?wN$5g@2Z|F!q7kRf%Y>)j9?U0_cyluCenjXD(2;9TdT=c`8L(xiFN9m|X zl6QE-246<6L;D+*KBqKp)V;X4J@?+P^aY1zytVB)GKr;qX+3LK_k+N;DovGGPN(Tn zs((^f&i|1X^6bzt?Mz;~^IW>qyZ-$3m@1*R;Gm1@fAa~#-QSZd^gW-q>1miPEoS~x z!BWoDmMn5)zwq17-Hn&uy6-spIfkd8L;Jv&!bypZZ{1^9)kO+9%+mfjZ%BBZ8lcXz z^>amso_O@1?{nrZJ(}O_EZXfZ7PMc}?V^%(`=4)rkC~;rY)-NC{xI+0Y~@zIndc9) zc#GcBajVm3+N`y-<08***OJm*xgwQ~QNOPLC}*)3FfQG7RxPW5k3%U>_>J+OPsf-U zvy=qf`lh`wk?oxnDIdhjXuDCVzr^ui?T@8R9v`&11P|OZs?*a-JDFj7e}Un$jL>II z3)9-SFX!+`Tc&nP*|G3|*6)n3jp}Xgug%+86CR#qd;WOmtiT_;m&EoPguR~XvM@Yr zoxt&x}!^LJ;-4Tr)v_6DB)>$(-U zhPYna>i+*@{XhACOl#Ta>=o(R&Y}5nq1t~#v164tAAQ_(C1m>9l}3$I#Vd59O@gY~ z#nQt@n>ybQ`k7^gpy0~kna?!f*ZKs~OneI`jJ@Q{~Y4V$M_e#tk z=+$y4F7T>4yz4}%>(jtvR&N5yhZ6&?TfN8i=|Za@FYLE&dl*VaoJx9v3S zTmJG1)0QiTpPI~VR8?ED>!;$(IjXGDS1xv^a2js8(p~l?<>0xuZ#DYUEoUSyOEB!n zoy)Dh=y%3d&P~@(G6}_bFyGo-anWRLWpwo+u^WF`&T!Z@#XhWPAQ4gZH^-?OnG2JZRDOTKVdttM}N~t#FvGvHHi=wd?Qg zsaYGW^N$`LgZRq&+%D-@NOd+ON;s zxbVS5HQh;?TP)9i5-~Zt=7_?1wbM*K`rIB<_U-=4{_VMnr1+=&W94a*EhUdahw;LEYys;b5DOQ+hw)eC9AI`64k36dmH8hK= z&TeQ>Un8*II7YANh0x=JPCj0qr`h-$|8p)1eY0xw*16|y)R$Xo-d^$YV?)<;mt>cR zfwP+T80-qUaK?yX&e=MnB1WzJrr^w*HBmp>ERTlpbMD`fB9<+j=ve%(#ZtC$)5^`AxZD%gwKEWi70?d9BW`(^jdU-?&TU#>Randz?I{hJQ> zZC|eZJUTr(;J2{OoOi1t~uOZD@-|T3t9z_n0%7x zy{XBpwOy@d;s#lv!*kF(_q^?d|q&SZA)i zSmF0DeO=_^HX*U2`SYINwOaVX=u>MT$FE7hb@;Cx@LFwo|DQ5j)(TC5R)4NNJMw1U zvb=dHwfgxxzRB@sf4JH%?9>gL)}B4<#r{dNawhADI=(!p99Hsv43Ym@$9ft&GpJIW;=32MjzpfrrFNz&z$nKS&q|>*?kn7iB(Uv-g2^U)HM3%M+ zM94j;3KF`=7aw=--Ojz)r#$O_O|Enk*|MImUi$7@-Py&-l_ynS@$Q#Z{MWg7!tq(5 zO#8jPj2+*(J_+=g|Ifrbv^TqvP5f}j#|SyueeEj)#2s!H-CWR|BYAMq?``f&Kdrs! zu~j0q@>P}Zu6V&%<9}r#%Of8?jo!LwO<8qw%ABe4w?&!GFioDaF*VcaRL4B?Zo5+x zidQeOe0ldyP`6u1!O|HY${0knBX0h^#OwC+lJuMVYUW;#zZ46H9hK)eSTa3V@56rG z=;q^%2G_f?rs-PjQ&X57WaHD#z11s$`_cLvOc!Nb4CJCt?7#X^&v;JpX*2d?uFpJe z!nu|S_OBLc_Ub7&>~r_L!kT=*P{M{WbyV&G=J2hfH zui))d_+#>|-`q@d!B6$s3rs3EdALhYn9t!+&-|uzhk;D#@$Lm3yjcaRN!AN*2Anf% z;oSRiwS}tImae2rWh|4g%~PInZ@Q7*JDG;itnCUDS7uFJxvc4UWJYDP?{~}izALYc zn=3pr8EhP{zYmcM+BUJ5d*|Hy6La3rdK4!r^J7)ttE}6LzP#o8cg1V_TG7(T8~Pnm zJGytC6$r@QsInkzVnf+m)tjOvj#nOSbn;47+nuqYUe0<+ns>kyZ(ez`PnRoLrfe3? zfv!(wND!N%>I2D#=y7LV5u+%dvy{D!Ofi>K>ZeV&c6rQB{UdcY<=ySuQ3n-$?R=s@pVwUzwfg7%WU5{2?zx1$G?J!S?+Y0WN)_l&f zr}&s$Bn#DkoHE#Gy39o`XxsbKOTSFL5_i?zD0i7az}*9W$tzd#MtXjYP2T;nB&Sqg zaNne`-Pbh3<%>=~U;gA$2mkITb-ofQzaJT@Zn*bg@BdR;{@s?-xKH-QtvST=psv=s z;oLQ`dq3Yd%rn^&kSWa={lZ`^m(9V&(*zHi_OOSUN-a=ZeXnN9=LM$gqXOsOd;PFg zGv4cF(qrYz&%U<**32w6JtZPx`{bQg>35;m^`%`{V`ec_yr$xv|nt?$~FW1vO4v*_qWhZxQWd+xS{O z{OewW(oF6AVE(xo@$Ogm{t>qLaK0;4f0JAH50`+}tbkeT%M5PiNgfRNuDwcW%9_B) zdp^4#Z+zh)R;0i6{q$EEsaq24!b*1Sh`GCG%6iK^O_ArV%vt93&CHfIc>Hl$(L{;w zzB}BUUb)(Idh}nOoxYW=%u0FNBOafLQ#Y>Kk^T7kB#Dv;DPcB$rm_b~&S{ai8)kEq z`(!8_nI>UU6vDe$(|+FBkNS328PT_XL`Ii+9MsbGeA~~u@y~()_JiLha6d1;c8u*_ zg)vi?Szz_vMV)a6*pj_vZizY_d9R?mP4?eivyy_`ceivrrY&1{?wi<~EgK>&ULTr& zFE;t~=Zpi11|qyO)=ZGq3F-}+RUsO&!Y;LB-joOSH;&ZWZQ5`~gLz`K@Wrjq9slrn zaL!#Q*QGgWsc~5k`;-NSJ~yHqWadkH9WuW>S4oaXXa{?jyG=*4Vqp6ewq3JId^KI3 zo?Wk;)um%jFbJAibW6eX@;3(w3qkvw!WCO4tk`m1R`BYl zua&2kC_T@(xwvOe>ykyRqOAe#tofOnnVJfw^fF2BYv3%C*t5V)eWv$MX@-0j^YTON zPYyOb=vUab`v0=ceD*&!^TqIXdTf2tGnX-<$miC{@2d)&%{*mp&fOKI$&z*^(`IH>>Sh3wI|!((S4}Qku(n%}Cqqjmp#58HY{QeVFw_M^@2ZFPzg; zL#2N+W25cKPkE)er&Z@pNIRBP)9hW&#(K6fSi4B)@sXD&UVS<9xooQ9N@I^8TlHxR z--t`vZ+pe4ZuRp{-_`w--GBXGy(0AJ^1lCjYXARpp7)=biSgd^kN?Zcw(tA#zrIWT zC&L-VT~j{SZ!Py`m7V&2Rz`Y@mFF?*x~KnT2j4oXEv^ zi{)#SS=;%wb@EA1bquOj^tl}0_odRmQ18|D8&>m|xNcT#F0qf zDY~t=Bk`N-lXGdzcBj52GUS|Rn7K8qvUOF3oqw8g;MUBZF5cKZR|S3ZH-9vrGhxq# ztoH3n+7^kq;f>wO2M#b>-!w@q$gI!S|J(L(t+8jC`q7thRqyBV>i#a9A$L&ZZ|=1p zU$0O3yY=aTQxmQH#49fze{Z&6jo=?Q7Fj=YGbh7x)nBd$Qk6fg{3+`3PAUDx?cGnm zbvE38t@I*(=ZmQcr*Ew6WpXkUYHSX5zWuOmO_BAYJ9Dl7e6Rf@+xqIDa=XN=lb2<= zjDC8E)O@`f9;h6nv-kS_rTZT#6|ooCHf%r5Ahy+7qK4h+@ZqNnA+!2k^=?j@@tyhb zmwQ&s_x0o}o-Wv}_(JrF;sMDE*8jw%6WAmCuJ?<~oXQZ-YQZvBN#XfwsU_P(vz=ti z%dVEPggOSbJSY!Xvqa>o(bZKhzm>V(+MQY-!*=MkFh}VO>ysvV?GbV|Y{m6mapHZ~3#+e?s1H zY&{?pp%Wiibc=Q0>n+*WpIC%k;%w?V#L;S9oGf}a!1zU;&xeL>CUZ~vaGI2)&c3_1 z`XIySgR<8%mrwa}X5m_gz<(Q<9{jo6%63%wg7mlD_on4<(p<0fep+ZrU{dHkzHq(V z2dh%*7*h86Nb&NgO$yui#kPUD=Z&P%>>Guvd$<;eZ(HQHPJ`wB{jZm+Z#4bq+;hM{ zXyso|-FxNNvi24)Ul~{Qu;;w%{{5GC6|?V3m#^@Df1*@Z;O{z_vlj$9Q>`4EGa^df zcXd5`tSqKA)q_FAP1Houfnj<1yNoj}4>w*AG%R@)|Ig`**5NA;Z>say^Dcg|tHuA6 zyFyR%RzGtSX6^Oi7c*~038!2T57}|e?^eCLQHM~@RW!vVFU0K*7zQ}`b3rBasn`n-(Hr|$Z6W;&*)3D>>+Kmbp%}=j> zdfG7m$n5Cc-3uMIADkMqO+U)@{oUw=iERyA)myHvbq{~-Asqh8C7bu(>(cVX^-ArF z?&E$lCr}u_yb@rP%2j5P-!F2ljfd>lLZ!pU?{*+ZU;AVLBZfcUN;UP(*b!?G! z^K`^MvOdpG*kqJ>NFXnVJ+^VXaimMZIlcSljW6;(3%|ToV_$HJku{fZa;REAr^M5J z_jOaBXHKwPrg~=Ld&iYib_lKsYp7mZ92B`%y5Di>!ncQxoGsJLV{^R2qg80Ug89pX zTRT=>?QNfZ&?@?+)Quf>oqwlYm=X3!U|v;=$J@!~+kEBL@Aj<{wG{cf&2v*xCiDKo zxkeTz?w>cS{3Crs?Pmk))s>&0*{UW)>tCtadhNdd-RVltHIJm7YQL#;GBK+@diE;D zJIC)wt+xIxA^pavV|l9TylGz@Kb@0R}X$89o?cI5>LgvX`-eQ5w zuW2uC{ahQQWx=TLHR*zq=Cp3fLmbQY=!yNh^k&`6FOP1zYQJ(#kJO5tWu|Mb-`2^~ z6YwGWlIEkMo=LN&=uFBinDzs>$N#*<8i-rGp{D1Lj)!E;V z?R|nDT{e0DfvZ+H>#OfJrGH=dC9VD=|F<{aZ1I;h>uP0wXKswh*>i$HHpyb`w7bSN zuQq2@|I3~(`tnPB+Vq4@wOe*~oEp{V&U-Mm=jev!wYMbqP4qtg zjJcg%gWa36{noA!BLUsW&SUP0FPhWNAJsUaFVHceAyVf})z>xgNA}E%`orelAY2`0 zwlO{1_l;>m{Wh+5;Y($>+Di-T-2(1%-LU6Qh(Ee;V|v4`l~IM^o3u~WEvge)Q7?Y@ z@g3Eu0(ZUJpCWrV==QUpT(a$d*DCF(w{wn8PF*CiJYHb-_MGZNyPLSJuIav>v+>ej z<7;XkuBJ@WnKh&LmrCZAN_Fci596=0Jy&l_v|U{n>Bui<86$Y)V6$pX&ZP$%*BvT3 zxRBw{j*CB!<#lHM2?>5T&AFmx(z67i6h`*%L8~HCmn~oHe}K6_BXOFhQhCesRauE~ z?NVVgA6}U9;P)lX$mwQbUmh>;d|f5d#K~rF-dn)4-d7>%WHN)<8QcFqGfsraX*T`j z4Y=hyfy<>Z@|_rJqs4j@8`>_n%8u@Rn;|Ta!`x+Bs<%6rwpX_UYL@6WZzW7 z?psf)I<}VVp1Q#)q`RMKO|i!_*`FPMBbX0cOncb4cJGH{nMRUNJuBXxWP0+^r$$>b zcxffS%*`GBKi#h9%UFLnU%$%V$9=0u+S9gws=KBZPW}|Cej((M5VQYE>)zU=LxCD6 z__z0+*{`U^sA0;V@H9H>EW?jiTN>(FN<%(Pob7YjPGM>c?SR~ z=6CDMHVAzG_^nGwVQ2=U)Z4U7oeYJ^Wcft7j*(@5FaiOPPr{_MGQ z?KS(}2|iu4@c3!bjfwuM>vZ{a?EYPiuQ}N5^L$d*_4b9+93pzOo-cV4U zlJQ+%=zq&8HFYlx|o!sT^LZ1+pvM(mL6%8bpF+>v>BZCkR>qP5wtxa|E* zr;EPQpEk8Sr+PxK-k+n(w6CsbKE0VmCsFp~6JBT8gWAg$wd1ju+5Rv+S``x=?-|G3d%e%e#eC^#k z@!$LXKYIM$KmW@gZDxnZvhl_OB_yY?qEr`jc<{OI1%= z=hlBSTg!wT&iWm%A%m$(*Rn610KIpt#70?rQyyecLbZj(G; zazCm6$F%+VW|0@&`(4zV!(RK9r-`$xOpEVk=G z|F{f$wz90-<%&EROTPQ+;z>42M_vcM-^3WTxNXj1wk6ve{(fowS~g_^uf|h%n~$Gf zg*Qs>5BE?m=xtl7+$nYV_mfH+^PRgKx5>U(Q@>*;=ZP8WH|IQ5O*J~G>3n`tqGhIW zo!r??O^UL~HHVka+1o0#BmB@#u|wB3-RFs26Jg_7@42YQys31J?mh8Y2@CcfyBz-8 z|K1yU`8^eXpPlSBe{+Ao%$}S1TR1;DC^ua_^j6BKL*!!)NBx|ou1+Dw23NCf8oy3f zZV*V4?nu&*Zu+%zL9dDxzNsaz7Hf{_wocPbj{piy<^7ayA6T8XB?Mvy9iYNmsvC6^H#Pme@=F*-+wP} z|M%g^Zt>stVdsy1t~k~r%6Y=d*yyS5fgO_qqh2gq$N2AiK7UWCA z$spF%`1;bt&B@35=k2qs__;{xrN5hUMRQYG>=f^k<7u%+?ETN}l-u{eXv6>OkJa<% z%kHuK_wniXsSW>Cea|hf;JkdM&nsKtw4@1xeu?tyhQF6rH&0*BckfTlpI^)Ws@LCJ zAGgz9_fn7Us&0+>>N)kRcULs7)-PT#VdrIroGL>LPu{DKBC}O`zP_=qjN|b^ zse5x@oSm)iUvKg6-Gk#&!XK7sB$>TT5ZlK0DS7iRs{=;ImQQ>Ak>7`-`sb_XUJK$K z%d8C_3LcgbKB5;Evu8%2K)<}7adzO}f7~}d_kaJt>Hn+e)Ad*JE1VZT(=DOub8a_- z`z=!yz3rW)4E4XO{#X3pU%&bPtD}?U`@hfM|L1yhR#)Y0b-~9H6OuE8R4>1Y-NpA% zr!(x@z4-w_EzS=Pez)JVQjRe%SmW72L4iNVu05XHdHaMFbH%SoxoiFJhs=*O-lode zUCOs|kDA`~*#cn>w^rR-;oua-zB=IjvL~ya=ih&G-ENXy{3E?hr)Gw%bzLz_boq{n zAK9)ke!ux|^80VE=P#VE`q3ittHj!J-!(P03+C_tbDGN{js14^oZrjczGh4idCn=_ z4%cTpxSS71B_6kn6{UEVT>7V!PsP29P$+@`;_4(%g`+0Qo zbJLQ9j7-6^mmXGMkz98$yn@^7*rnW9=b|Yeu7^dP@C%P+aIXGiDf2%+!SqAmo~lUq zZ$iouDPL3dIc>Ef9V!h?Z|`6`Qh)dVxAy7!*&Mr=UVKg|n3ZbUde>-bch-wtvy{|M z{Q1(wIce(@{bkoWPAYzOJ!D#}y@I8{!+>K-+^shg&U$oS^p$p>aro~ug>Lc1Un4I~ zo?rLtXArX{9_ls1IN1`W>zheI$XZunPcw{CEMoL!Z$BR-Im+W@ayHVf`;_p6u-4R-$X$V>2A1lw*ZVG2*e?=W?)51kINP7=#pZc$?XBCiyY|cMv0S0J_`#M2tt=MJ znLAg^TDMdyiY+SY{e_P?|F2FCVS<(Lq{agMw7vDU7;xDiN&cFA=%6~uX+xGwVTes-DcYl9>U%&DH zZ;}2j|ErHaoB#1{$*bASMS1sMM3^ml|EW#yZ?ajB`iAo_HVOYcrT5czPI9TH&9zTL zcb*6wcrfS4i#6#Ehb6tr?7YSHzAt^XF463ySku{0<}W=8;_vHyjE~=6bJ6U!L4?f4 z{d;8gFv~Cae$ursG`5dp&7}9@?zKPk{*^_}*STfrcCDRb|FIVvYBx7zvvxXs|G2;J zuX4DcNpy|Nyw1hz-d+nfKKg1^=aSc-Uj~WJnKti`%mz1$TYt~;J@43^(e0*qeEY_m z-P`N`znZUK+UhrP@3WszcJW7r*H5qgCzrFjp1=Re|Ej(B{-_0<`~TklPG!P}|Lwa4 z{+Dk5-TJ?N((+k`r-l4?daOUqA$ec#t4^#z+Dh);58Gp(=@&#?a6Pa?YqR&Bt>1*| zr7Qc|m1CRar@b*U%Gwg>b!_6cQ<4+&Lp_zHHieq`cbUKWGG)u+>z`9*{F;5w)5>_e zar^vp$z>Pxi?ZiyZTf!l%&k=mY@|G;YdK~=Pbr+HGp)Px2Ad*hZWr50<;)sA`yS(e z;yNpa6_GljSYqm6Z&bo@5E{^EY+yR>sFTXU~CUu&hma%X+Z<_E0*g*{cD z-|jOxcdT5qy87hVQ_7hEa)}#Hyjb)-jDLMccWPe#?K#Je-JDP>AaY{A&n6ze`8OtO zaz5bhvG}7Unbv*LJZXtv!$bEyUn=kY&Nix;dvH(3&mA*1KicaY*z13FqRfrTc}@v; zHdkD#KIr%A|F2!2+vaWyZAc70wP4xB(rN*g#54_Ixvn0KUz=F}UDi~xUlnw&+*iAH zbY%(Zr9+4?m$t6QqAs_t{_ytCgDvKsgO%D-Q;{p0aA=T5T| z7XP%p@0{SeW;5e@jLAf?Xj@sPdxs{fJhbDGR#o11SK_0w*&TC_*xH9C`KntlyqCNi zZN1TJXI^vk>(>lc zpW{N=i&}!u#!MB7JG<4hHaz>vAz{TT?->^R?lxT7lVdbjIcXw$!SXoqrRVl^PLb`L z9(!}vVP}mCp6>3oKbj{!^FH`cdf}|hh3oC6b#3%#S@cpX%h$kc;qsZfS6yd4S!*>p zFzmj)q~w7`UcKQd2IXmy6T8@4I%2a}R2zIIAN4e{5}Oq?$;yILsK8-^8mre$o1(zQ zn%Y}8PBPYviL&xMFBExe-j1lnjrIK(cWXEJ=DT#aoYk$EujE;Kyk$ajx=etZu&$kr zd#PpKr(b_B1-vlwn6`EgFXQCCUowm@SM-D(m3N<1oiRy3e}V=B_j*Q7_MWLzC$QdE zD_kFzGowCrcA=TuwJRm&tWS5JwpcWQdEZ`(h8CS23!nB*6J=kRcqEuBEOgd8(^p!_ zj|%Qa{V_SvZ)rEpxZ}r*hl{dK%Nu1D6)LpI)-_x|)W3Y1K|$TywVz~?`co<|?K#gE zq?@c#SZ64`=)!5gwU(!Haw6;vccs@nk(QVzxS1i3Z->)Erog0)N0erLj*4MC{6Ei3 zG1l+@#ieVG)y3^RY(|AA8SAMeSB@WEXnGblJ)AHq8fWWByyi;>9^U<)BCKn*e1*;p|9|HGWRsbOWkQ7 zBdeI!mF`fqYuq09R-4Q7rpVfg2;m)1JXln7zO{Ku*v9BEpV)j;MfL5b%AHfpn}s+R zpHAbRAuuO$)>;SA$Hz8{%gVKS_1-_t_+v5O8|iSjU6ZwqE&M71Irv_#<6ivgk&;GR zVXD>~ql%~JH1v|vUz>%Cy}R|Uu2U(@Y`L;`SC4u`o7aRk+k^)7mzD?4s(0++N}JA? zy^e#W^C;`09Wz-~EoS*!9C_MswcbDc$zz7cP4m4EJl}KIVs2=IlAe0{v%ua{D(dnF zXP@p0>2o}H^!EIDcfZ7F#BSfHY`nAMWVDB}vh%f-8Jq`24P(=lBkziCJhYX?(dS0c ztLuUeDNioF`nHuvJ2q&BNOnnha)OkP!TMLHZ?*G&+Z;H_;dqUL>A93c6T4puzAokF zzcQI|<&!;Aj!G|*V=el)Fhj;rDDe+hh>uo!{N=ozOS`YUU(zz=uKJ#3ucxlyuuz>p zskz<#!6eP6vNPtVU8{ zJ+Q__?=NUQUH`#*F0cF^T>ZN}NPMY?;DLEsV(WL_O)}06o!nQLeW2@U_L?G{_6fSr zm3K;)tte=Gw%N>Zs!wx)`#%ZegIlXD>^AbOZm;BRnxyZCZcmJ@35=*eh%e27!l7UdewFHU0p|6 z{lmo)T{7!lceXTMxXd=mJ9D4fBwkk2N1Z|cXZ0`V60=wt?5ufPFTnK7f1R0UvzA6! zCD)7JSM&Mg&7z{PdGSK6zE=qxhCgl{T*mgsVS?}HlzAT#OcqX<9b)D5+hApn)YFY? z?l0v_3+p&?x#jIP=3@y;6Ha#|yy`1keV$Lr>$50NOXRBblh5|%RbMgI|9R%}da1rU z|3o^DdmVLi_PiqzRI&5!`!dcgGkh&3?(a?hWzgoV+qPFF z@A16#@6`~eSsQj*8NEn;vTXh2*7J{#`h0npd1~i5hDFQozWeSdUiqBQf#H+3&OL^* zrMs@Ge|Ye!fvczBfxxq;OWWRDef8V)_7R17s=X`Uo(ouC)^#~0Y$L}4&pZdW^;uW* zRvdV>c!k61TQAmSTu#zva9*PIBl~vd<5yx|RqgtNm-b#;e^z7T9;M1>Ru#*9&KzZr zO3%qtuMm6uuO(;i$Jx@4y`DX|8F>1~+T%w>Uvaq9|NZu0yQNRg`;W=BvQHm5Oxw85 zL}#aXuF=Zdyj!J}%CCv{yOiG5j{Xr=xbeluB^_XfUh$o~|^eebX6|Mj|`f|@r( zC0bpgin!3i znqFt=?2s9Cnd8?lCk+c-$L*W?y8ZkZPJOz&nRUL(7T1>2jAnsv>V2Vm7RfDF zT6gWklj!f)`YfG}ZhYKof4=6!uTLA2{l7j_+xyti;`Xw#mFIl-U3<1Ji#g$#k$9S- zC!=oU_5(>-yi*F!++5xIMC(uX&ZzjgcfOgK=UaXaKherunXI$B<^K=;|H+5{z3E6@ zF3Ivj;P3LoXKZpdK62V0@90w15+|g*I$w}&pZqCL_Prf)yx%-NSZ<#1q-V*+&N+>d zhO^p=S6M`e9E}a>yk6#+{=he|BcnGrb5*Bpc^~^jmKV#plfBru-0&!X(V4<#9VuYW5WZ>h8f>@GQ)bEEd^tVy3TKT5Gg6`C6OWzE|9DW&qy!??rx4|>}EwmZuu z39BY=wrI$SFJk=Eku~@Fh8Ux+Dr-ei0ePd7>ON1~_MR+kDru2kty<0e%y7r_?#`{- zKX5wqdZm7{TrOmxy`U*j`lUI4rQ6DH3r#x?AKRby(&tFW)Qv~@ZDUN%C2Yz(-0wWK zVAj9BulyTczBpd;t?qNC8CT@;gqxG{Lr=Oref;jXsJSQCe~a1~Zwo?2YY%8Wo3MOK z_G1&diLOqfu9KF16}VclSH|b^gi;Zkr7vq9FrIP=o;lHV+7;%P@~V6@mzd1aarf*~ zpD8uHcoF~I20M1yAbr=q<-K~nem}45*;`f}|J_SqUSZQR`%A`6g{!U@{M>#0hRyHG z%R092zq#XhbzXS0g>Tp8oGaOXzI-wj&AwQ>q`v=UU0~d6uNPm${<=RfWIesw>vX!^ zvn1VOf#6sPXjiTah8$SO11aShiF;CoH}F`^m)3pZHS~W?xSVR^Fkh zb|=p~F2&U(Mo%oqW^%X2+;f)E=`Mm#Zyw@(QMKgd-xD4V{CoBtzU(sdc5m)q)w;8H znOkb~Wwo+0Vmh>@F>e2OcgrKb1CPHry_@q&weZ(R{_+`lFRO|)%sm}-w&+b|$P8xI z*Kpjt+kVPg8P*V&ZD-kMt=ZkIZ76;CQ)*lCC6R2a3#~i7TlPjY9KNldcF|bmU52Qn zmW{_}6Yp8OPD~4&#a(Xcw4mtooXnaDXA?h2U6pg2)F>yhQeRz}CvkSwNpaOT0#Xwh z#P2Q(VSKc2%^EL<`A_N}ww}wRCcYX0l}Nd~MkawO{(`ekOZ49aZ`*4! ze@a1H@HbYyQ_f<0nI7C;^zg#m84JAkoH*uToXeFK&YD?1`E%0EAc;H1cBj5sUEJjN z{o1>x-=<5?m}vhvUi~>K(Qcl(VnD_F+n#0x(_cmQ9Gs*OE>O<$qi?w#z7Gmo7#6Ygsprr8=injp zRfXq7+jZ@)W)u8tj&0cZLX+j`Y0rn-pXXe8mpbe9^<_~;>S1%HtXUbGvN^}{^z2Xk zOSQJ9SE{bb+jJ};EB4K_#BEZmzZ$GMruaX@y5jPS4cjAKjw_s<^`?G*xyk{78~?NT z?xwLUnKZSY<)H4Gl&@uWH#h?ty=C4QO1{|klvUN3v6+4L=E5tTRbuyC*ZfHN;gqL& zcd@(3TjHk5l1CFB^&E7madZs5@W|gIa?Ot2i`3WhZ+`!mWmb%+S)l(-tq(1` z<^`>r6Zg>|>Hcb-H{Fs+M^q$Y9=<=Az<#9okJX8zWmSgl9<8h_o(^h4ex>g;6cgt% zoG+|V3cTcd?)ijWSHJp;-93m9-WwbrSs`&r zLC%b!<*CdP$!%>v02vHoPW%r6uln9i|JJ^3q$+FDqAPb>#XwGmlQN> zfu-D6?j>fs^>+!HhQ{nl)OBnYO?|p#sk2$;^G zsrg3MROM%`VM~%FkH`xP8|Wl%*}(Vj)ZrO|OD7%5d2AYzHH&vm2X|cQw564k*55ui z)s(4K{okh+rWp%QW*xn3$zC2__xA}mvtRYA>w(fG|H2)wJ-EB(=Hb>tlg=ZCimd9d~F*rj@S_A8F1@l3qY{XPFrG=Hd_!*Jl~NBP~c-~Ma;d;E^? z*#En=A3k{hIqv`Q#EG^Z!P?z(_O!3CPO)(oydbbs?75(ap?^33UiBaBC#2OCI+e2>_R6vjWB{DVp9T_hvVa&do$?H}G2+4za=VgG0TzeZ@e zamW7azvKQKjOVZT`2NPqMK^996Z^q_tXll=k(}qI`!0r_Jy9Flu>JfdPV0H!e0`aW z>^g-OO6GALju&>+eLtf$@vFtA2ljiEfAmY6&3N3&b2fe9>imOBea3%+FF#uBmm{`2 zO71Z4Q|++Qt3h!r3Wn}H+9D|it#wxv0@?pR*i*)}BL$f3+zu|1RK%#AP1FWoI6 zabnX4_Su^Zm|2e27N1^wa;2t$%;Y_PDyn}(mFP-uuv)I|v+0bj+^qSzt(1lNsmleAJlD^ITv($fq+qwY2d-r&+4=L|9mB~`EsfxuZlHreBj^DPKEBJ5QqMvDsKQ~zJUfA5Y;>(=e`?A+^Hvf?N|L5g{ z*KgO$KKXGVt3zVqvCWe`KN%kAVz1m>`Tup(;m_^ArEjn8&9mL`>BpWEGd9#8PB5J< zG$Zfz>$kTK{JCuucgLhnMCJDV68ZA#(nL#p*2l*TG>&skdmOqvTX1u1?X4I_!-|GI zg$dhES(#t`pkP_?#%tCbj*U$*C$Ic6{Vtn*eya7x-MpSVTl3@9ChKkb*_AMJZvUm2 zZ~n30*^eyM=4Mcn;88BzbG)9(d|^{&qTr-N$1)YO!-=g)jhd4x6c|Jn%|7wuR{fVA z(I?GMt!v&LnkAJe#CW_SjNex*y8PW=zh3vPF+NjtEdSm1sI{+RnbtJrnwVl~m_OiJ5$I_G`o{o2-95W2uPUd3k( zTltbX?91B1e*cJ%ofEcywVPD#@gUdQ%&i?4FYLN6>aI`_X((`WcUZ^2R`oZ+QgijA zF8*KBf93zF6g_!o=9qQ=@9y3IwIKTW|C=|P{@CAr_fGup`{fl4)rn=(5-$d1<}G@z zv|;(oME3h@W}R@)uw8yKCsT#*lKyr7qrv8T{Y#US=Pp%WbtXPtExX&x=yC6)hb(VS z`l;Pin8#tu$EGKByyyL_n*XY4MQ3?q{kbM`$emtd*;QK1yGQi7>yqW~ZO!*4JESKs zmH4#jspoEQ(+`K+Dpa?}O70Baro$`VyJnfw)%@i*XCHrXzv#{W*6U?V@{$E%`?eOb z@6_d}Fjy*(BGOgjrO3E&k8gl+;hedu-Je;1#DBE1jXdm=rx7si?B#`5AL_`@za1|0 zS;ybdytgyTOqKP9y!MYZZV$BovYxqVuyx_>8#Y@fJ2tMI-_0{|(TO!$RhIK4TT;#C z{MC+X9$6ZH?rkAw=8=dg8i%E{!&xmDHcY?7;D0*h^v=MWQxcAxDLca|-|u!OVd>3^ zS!eZA+SJ`V61k@qvHQz*)LM8xZ`O(Hv9frdA2I(>%}uWRNmA!S8-C3BC^BJELf`dP z{|fmd%I})*#t2VIw|TXQC#qgHp@P9(>x!ea-qnj52R?olE_$|+&u$afo8LYDq0W69 z7`=lTO>(7~n60E_bV|IH4tjmhO#f|i)%U~B`|tC2lpPe+GB{Wzn=1YxPOe<;Uy)ta zN$acPGtIrlC$FE=G)e#WPm>F!oYiVY&P$d5o#c@}w>4!_{OW|Cf0oDd*j?D^ZXwN| z@*}|abo`fw6D|rW`s=+UpQZ$uW(8ew6T2c;8&?+~B7j)Zi@`zhuhR{C5%^S4+&U!3geShcN9*H);$nESz0 z@`7P^&z7wvcUQG+O|@;ibY)SgQ)avDTtEM+o*AB<2hwlt_4#skwlZR)PwHzVOD1B)Ay7%~qLuznJLNzi`fz zIcla}Gk(X#X>Z%rews6S%KAw;hMRUWx^15D=E9z(m+hjSm5COupY-8UCwnBP&b|FU zhL`m7By&rB0t9t${|Z=gFZS(3msM#$q<^x%4a%PKM%&_x_Rr(C=3X1Vp3XVALbkz2 z#--ry>-lr+9da#?865DsvLWij)HBB#=4iE4Bz;%BwPaD}tjW)h3l@YoUyk3Iy(xmj zOsf9x)C~0}Da*xO9Cuut`a$w=`RPl#{bC_|-h+t2f(M9b6zCAj3OT>TMd6Ou#(ND-jF2%~scJnf}1`=)saxOA6TT z`FH2Y%|G(Tr|{~ki-AVT$9JmTJU#JZLrS5>wavMG`})ixf6E=zZ2P#!C`u+UcM4B9 z&xxh4Qn}?SO+v~aRPwH}@;kEs|J(oBJ6Hu*r$y+6Zg=HPu4)NuP6f2kP>BzKv;hY00?Au}x4zamhA?^18Xv<%>O~voteU$%_zq;Z|VgO#=CLZhe8p8dp$bMvGB<^nTv zi#PKG-fX#;=CnbF>oi~AyF5m|`JoB6Z!RoS@C%gnY`FFB)oG@_6=Ggo zo99)uF!Ka_3+F9ra}o;j+??0)pwh-?k7j6RT+hM0$!DHC=J~$=@Ady*|NmT}yw3c| zU4N@5|DXQ<$K$+=^M=)KCZj0r4LU(q>v|TQb~Tqgw&&Z;DO+E!Vy*nY&P%{43pBN9 zajj1;|LO+b!xQfqu45rAOF<#cuh2@MM&~ zTd<%xSF!7?pNZNo_tc`9w|{TDTkGoMx3)Xk^d+xMpljX2OO+S*b2RO@OJjC6ZV!oP z`f%d*Rrguud#0G1cS(nt9xr*6EtLp|@;jp|Bojrb_7+HbaU`}o^lemZ<^ zo#fK)j{U6u4|W}Czr6BLf1lrpfX2i+_oh8dUAT2qIGqlCS*orb*suHJO9Stw{jAMd z%@(4|3wHC!AGsah+S$51Njun6RKQ`IisY=y>Os^IKQq1TOB1P zyjdgDC;d`wb~vx-wc403vVZ4@A6w2P<*1Z3&Es!LjK+x*8wR$S)B z)|P48E^FUhYOSa`>;HL6{fEUH4sLEp^)h?A+;*$|eGc6d@~dAo%8Y*c*m=Gb0!nbC1= zCdaIGVe`2*&3$!}BlP#4%Z^T>a|C{^Fi(@tGd=$0QJePC?d7q)`7^#eJn1Kv5%F*v zZ+1NM&Z&!(`8)MkU1sw4*9N$+ozO7jY2KpF*<6pi4j=meYh!Q0j34p;XYY%jel5>M z@{-<<4=YsC)-={#*0^YB?6BL~NuBln4%6voZ#L~;WH{aE^`-k;Dj8M#B#yB!;0(-S z*v>Ll)J-(fG;1-RhQ@{i&%Ii%9hNkn@=!`uV#i_|m-#spW+!xueQdwEr19h=v-agD zKiKAfJNo+lKP}UFLOyM&+&-=KYo_;T9eTZ1z$?FZa&*)*nI&3o+iSv3vjy3wZLg7< zQD?ZtZS5q7$-VQGuB^Ibus=XXeczrp3c3oh;@W>1HW`IaVLN*Mqe^S$YuC`$MwNsL z2hFFd$0z?#S$|Q<;nZ&XG@Ta`3mT&y*7n|uXI}4nQ%bBxK}bu5*V5O$#KYd)Y|gLD zyxh2Z6ARwYNw!jZrv1ieNs#geN!#213;&<3>wVji_S$QC=F9ABR}`eAHD~5t*eE~! zrp{#JOtA#{$8-ADhOBWp`7_A*d%(EouI3ZSUZEsim8Z-Z3+r zdg{%r$*`CAFvka>NtGx4_%APJ;MTtQR6@5-!K8TKrGpMnAMX7U_x(kgDR;}Z%n2J= zPn}xKl#{dZ$Q&uT*GWp&b5ncGO57JbH$V2-H*9T0o%G2^YGz$UN9P^7!GH7T1}`A=f29`k+rI3!i1pK<2hfKD*c=3b7jk{ zcBA|bAO4E>2d_<=8JDGd)8mQVu6c@%9}?tdF66OUn#~qz<@h{zZMgNHgY!CXRl7~C zt6yH4`NwV3b!Mm2 zI73s_V^WXkU)$>vzU;(&V>R`sd^77@VuE6hxeD_8GsYFf<~vti68v|x;xWr36ET~4 z|EC&X{2|G5C(dT~2DOmzFJY}KE^UwdEnVpRsmIOVeAzmun!WR`6eaYYSfQkQ=cL!I z-`CeY5i{Fyd8%mub7|tls5c2WUo7&Ucf8Q($bP#C#sY%X5+4g}PTtVVK5qFo@8t5Q z*QY){ydjx$%i@%67k(T$aK$BXd63zpzUw9Np_pRS^<`I3r zO*t#-&J?w6bFy?EbS*jM=J{#a-PW}SH%~IXk-56?N{02-({7cES2sLZ>vf#-^@F^I zLsu&fy?(OkW6H^>s3rR!l-=GS=6C$pvpLh4j@6W;72Sw^QSolk5&df`qtb25{jcnk zTx_HJH|g%9q*qxV?T!V0*}I$V2Y>1D2h#qNVmtD9E*}r5dKSu~Y^wHAfByS5H&boR z#Xj-1Zs6Q{wdhis@1m1C4{0gYneF1ZJ^%Ofl@BZ*UzowOwE4%;SwAOPtG;&XFqzr- z=dw-KvFw&z`VH6F4_ALWTQF&p;0NXV%DW}%cW!)pL|}dKoUMO2pG16BylH1&-XHa( zb9%Z>XtTnJBhq5G53Mj{iHc7zZ9N~X?SE^oHP_VTh5uyl+*(^uGx_dJb>0usEy7Gk z1vlJbb-llhyZQD!EBiY;7k^N_uN!Km&z`Q$l<*-=8ggy=Mg6BItR6L& z%D#Tfx5>bMf@589y{gpSKfki?uWtO#FFPkZp8eI8(!>%eN!6UxBy_v@n)}9e7+~PR%tiyKwtp}&i+ngcE_55+= zJ_$30Di7{mi%LCsuZ`8( zD@}7_+*+&dC-haTlv~gmuKXtb_aGQ-zhM{a`9l%4TP;pO?_&sa$3GXPZ&vqFTB1EjDIv z`pfp5cqMT&Nc$1nhHDFt%-L~yR=JXQc~XkDpjiE(AD`tOxR`P0|1lHH-o#qUwNW{D zvbcEGgoTL{KCXNDZMRD4g{~{&mY7x3(==2VTD4x!Top zN2bololU;eo63_HE^|CpH)(&;S(d#<-i(H-Q~yLw*s#)R*5v6GuN^)oGi9<*l+sdi z-^_OGWs;R=&zjyF;{4KUU&(DR`H}JXK}SqOyNS&^jPcVJt7>7-QldOMUpZ zo>~8T^Csh0l1olUtMl*RJH@LO^FhD7@|vpI{ywt@j-L*G|EvDUUP`%YdCSS7ECvGbHC@itXR+B zg$?7SRdX65EDshbr-$uaBEr^?>MD@)L)qisH0?}V=iDNnQvtX89``KfjGZ5`Z*SY% zvY&f9!x}dAN=_0tIl5oXL)AsidwS%d4vj76j-8)9UF)>aY}vE>SmsPliI|YrGjIE& z#ALz#OFVnNY?!vx=+wp1E5#KotyQ}xo>ddqQs(~M`^~UVe?ij2DJKp2R_!r;dUO7c z$tmBNeOHRRKYO8J@8W+y^!RQriOhXJ^)I_vF`ll}-I~r(A-697!h`Ru{L_Db4mjoI z()!?>yK3qN%}Iw2>^*;E}F zix1M4-cK(ybrpTE%~|kh`ixu0Cc6s7rkvJ@@3KGlSkl7Oo#SA#waTrDw{;#Sp0W3E z`ewc4-rqS--oAM7PGq41*95KjiXV4HkBf${W9vQ?{7QwhfAz#;<@aU>i9f1Ru4Akd z_t_`C*63D|yuZAU^@G*l7cXqCl;<8^kMCO@C^HD-^W*n=~VZcQxG zU){Fs$%T8lc{|RrHRRrT-R(Ok-u_#GkDEz_*@yjS=e*WfzMk8DQ}l`0?yuJp>bw2) z=J@G*RWI$))akY?$=!P6+;RUc0ZsR2du($!9j$fUX@A`&R;3I3C+04^@?=%mF4kf- zk&t^=lRxrLp7`*xT2O)E8_s`q?xjZd653%g?Dkyk^PffuH13re+b=pS zT3~f#;dy~O&8toZuXVP4f8QiT?E2Kc9g~vZ9{jZAeabb)%L+f&THSco@ZpHM`+>`) zfH|mRr}96sV!4iXs?*!esj;yhxL1uo1MGfC9L<^zAS3y zp%Z_SRzGKdtm)ul#r$0-x4nM#>_?gB&wVUmO5^MCU*PmHA!D^ryHW1xqg<8IdV4&M z?0E5jAxTy8Li@*Ez0Q%_3-50bb`&T(6L8pgQD}LW`lW3FvVVRB+y~b3UC3g^Jn@dkL#qEV=&Pgbt_|Ht?01{$`^ zx30cm-76Dz=4z7HKgEf2*4}6p@|XSW#PjIHtUjqJO@1CPE;OXSd+Pq&EP1KG#}^$D z|Cu)wJZ^a&kYE|l@n2z^huyX^&M2;Z9XwAU1k5jsD~cLzn=V>>-~1Zy3j?p?#Jxeyhrf%u8GM! zD|frT72maJ`jUgW1}nZt-(P+~E&R~#^?m1vv@ zq&>`Zi#_`fNjwq0U(m8Z+wu66*!HZ$JB*d%C;tk)6QWzUjOSOBuDFW>iTX&Ab&U zy)1vFPH@DxBc+c`C5^RtZuD8X=%&BCJH>W#Z}{q^ul=vPZ=V=+RnxGup!}1O)!{z03cq)#1|gnQw@*EHV@ANCvbE^vdIY{B&a<^50ge%p47spsA{X2<;PcPAW` z-upYe{p7^!Pp0yxUat}kOVvC(^-KfTx17|fl|{{Y6*8q$_wKRv2;+U8|E)jqy=qa% zYL{z0!N=}v>I5l=DyG$CM?Fs5?kLgqQB}D?!8nZN`mw*#vC>EOc;AYud{i4VO-sxE z<(1z&FV#2__ugF3rJH+f_VO~>yOS@(w{x?7Q!zd{b^0x@ccNis+J(C8u6n=zxjnwN zb(>z?YqN(39PaEdI#D6Y-K1?hc>UE<>fOEW99no7gf z6S8|AUpZ}+TNXdl?X1S-Ld~^aOQVH+=lGrc@^xA4x@7@npE)Pl6=ta@3s0K(v*VOs zNojZds;NKUa4&bilpg=9zW)+y!Q}o&3~}%N-@RM8K-&yS}k+&!R$<3=Oe*ON?^xu*~`A0nLXZGq`edMEaFLjsCd7#GPkhc1e;5*fj_{*H@Ud*qXJx}ILS6WVd(H65KzZ*Z#3-Nc; z54e$$_rXneeL3&m!lhmuo#+&OW$uK&B$Y$e4xU?;JEk6( z&ARP+^=hVCmTesSmS4FipJnCzT4#yf`KgY=wRtO&-x$xhRM{mMx4lnpbw|auzDMFq z`dkAi7jN{e=5@_e$&N5OvMaV#V#b{WCcT@d^czno7VQ1P zbfs@wRTSg;8L}#3?yT$kzW&XPWNq7aY#RU7GeX{H_Bz~O6Wn(AsrbJr`3KR;U8=Kt zj^}KB!u#Ii{QJh{x6eP{*p()H{n)b5B}!9mx%rN_G)$K7ceav0zji}UUZLQuYgz?2 zX2czE+)(%=rNMZuitT2#%8jq$xC6g+i@pB-){5ubhyM8@AL5w~TE2X7M(*gpx@M#J zi8W8WJ0ATnQS3VVIO!@^lcMJI3$r$>Xih&BlC`o?y+^WTWmiMPNw%=;7K3z?*k`A; zO8eN}>z)+XaW|LfIw)m-WL;aT@m;e1#6)3!A ze^KCOaxt5w^@|qq$r=eoX?v=ogC1#aT;uY0RnL4I0nd|BlVp=#?mcx?AzeRX5l7f( z@4{?_e+MK#9<{LCSv}8mo!jHCb2cqLS=F0<+OBzYM(w5AgA3QpdMj@8dUQ;!*w!nj zC#BW<;*6(~YU7r$l{(LMzL!aQpR?-i3?(7mW#zJsY?|8NH9y|W_6?euExa=!+$zTU;?gxFWmw%II-|&0>UH04Y@9OQ2 zZjLL7y`#eX-8$y~HvTV1!e!zTW<5;W*>u+H?CQ-%GhbO$+}_r`TRrWJuV(+l{2Tmh zLgKC4J0F$2e^s$=_NNqweR~l@_kj{#yE8lmX{_ z3Dx}nqS~)gKOS;3SXOs7jBkO?-7_k4_pZNrs(+2fU5`B)mmV(BGx4v;%>VpwC;MNs zCw4`0`e|qPZ&v$^NJF>g}JsYfmU<+>@Sq znrHTGh4q`o1+(6M&M08ysuH+&Azgf%YLMR6=U4W#1T--?ZC~~6ocQaB4ed8oiutuR z;m;bE957V6v|acsBn=l}DJMROhve?ea#|EX{}4NK9w={V`eifroe3>K!a1 zId!78b)8-v1_G0+c=oWi3+=R)O8J%TFG$wrGFn zc-Yj$EzCykbysu3R>siG2aAGPc7FDr_1tWcB9G|v{d#jF_nR>KDtI)O%N@1+a^h%X zP2QvxHB$;xtPVe|_RPusBbiK{x*`4Yuh#c4Jajn`-`eqZnc$@#{r?~S&#Eb7xpN|8H9NQY^z#oU zO^&+av&D`3sLL(o_p#Lz#P}bThVNRx?OgY|XVt8B4mxR-hMcTxi$b+*l;`zJpJU&h z%bj-FE~n@6qQWP~ioP(l-&pY>GPH2l9KN(dtA&%kaGu#1CGzZtvZc508iYIplA01%2K9=;Q()ou{fea?Lg=OZre0K2Mb ztH1rT+N{^|Y>mdIOhK_#Z(O%16|ULXJ*`9diKDyn(VJRNPI#C^oN<_T#V1PZ;{uVR zU#2*ne_FkQG3p_U@@e+1!7;DzDP*l#(ip|KB;dAVtMuXc;3cV9Gd($t{I+oym2I?Lv6gN~Y7@cQPe3{$N&o3`+1_?Wga z@|!-s&hKdD;qSz=Pp^1C=P|jISK)FV+D6698_m@Bf0}Mtxl8Pir`RMNulU7}bg$OG zirMz*UwGWZ+8*f#U4Pa`^KajF_3!(`?FIjTFW9~L_tSshgKb_v&Eoy}`0C01TBlE> zd~nnJcuep3^%Yu2_%~&zx2=7BbcGFzcCE1CgDrysAAy0t^&(L>D|@id1xy@+@F50rgR znRuoxX}8&)4M(|GzTI$-@8`7L>5sa0oB7WTSB%_pkCjIjuW2zmDD^Ui)uTl^ zoYx>u_4CQri?(n4%~*omt~6Ff@V1>*;$}7b)vE; zSbfl`v9&3aHRjQjQ(FV49W0+Kvv>7@2D{VOSsd=|TF2S`Wg}a_p@KEjc>UVVKfHLi zNhM@a%?SrK-nYTmUdY~3w%?UC_4&~af1cOWHn#5E)p1*Xs%z~=4#`cMosKEqe`J`m zkw1s;(cLWvq6Kpr_|iTv)w%R2JhXQ5T8ZQjLi^;hV-hFDA9wG2{`2UnN#$CnH8=WP zXgKA&Au3TYRJ~HWu<)nQU4QHMle_P{nQL@iY^L`wH^YxzLLcw$3raLSV!nNqgLdxb zHTKv4G_!D&CEd}P{m)%rva#3nA>RTSt={R0{--Wnm~v3xaqg$toSWZW{2BG=$V*Ml zlu$v=z!>wWriLF^cHOxna%{ELRaJe#rItTbjc;~Lt}*H|k_ZucIQ1-pP4f=*@|#;S z6K>fFgfuXh^OVkI_tKd-*Wk>hpD9ayjMVcMxZ6)X(IQas#m9o&p)0SsN20GZ+Vfl+wtI+Ys07N7%6S` zo5RNBI-kt~25{E#|6JWG5D=YKK-^o zDt&s}|NeOQh^fq`e(K!mZ+9*)zq`||``X7j$LD2w*V~2rU9PsDIYB_~szhlE3gm#^-S6mu=s+ z&B=FT7}sK5yWZ7Xl>W?I*qPMKc&+No0bO@X4W7*}8I}kqcU5?(o>-Lgdeyr-hNnMV z@3*eod2RiEt#bd;rZS5~-U>`T55BG`x+uE0WlqUM!_$$g?@kXpGR1h+cClIU1_EJX zwM|#|nD;rUBqzsxd69lOCgu6LXHS=NJ(_y*tjvwX8@HZanVeg)cF~$e*^TWM&!+D2 z@VF_W_49GVr?MSN_ZsqFgt~HOcTW2d(p~peQ&VBespq$x79TkJHrc+|#Bxd1CRLko z)9t~)S59CZQOz>0!^JV|xp?%{W#wne(mF!fi4-E4fP5Wwio~mZTWz^(|-& zO?WaH-T#u$o{oHd&cH2#t)5k<6`tr$` zbA(E&@?7(9y5^P`$_^`9#b1&^Mp zpSmS}*4|m$Kk0d=?>R0Y9v|qC5GQLemQApxwWgojaP~ z{`HW6%-?^``uom47dVs6aciOP;vD(4xzl2I&OEh!n@G_-pBW$Ke@R*QA?W4j$M24R zsad0yb9c&uP{ovsJ51T!xgxFxwdrKKZnnrNc-qj>wABA^b(yACfSCQM1x9bC<=sf@ zPJYsse3avE_u);yW%C^c{&6mnf3SPscg`=dxA!0I(QEs<=h*_`f(vV3G9Hc&{MWf? zQ>K<;+)1V0|KH;OfB(PJQM75 zFQ&P#zd3u}gKY*%8il*&9LtdmD&>?p@Y`srDf@}f(~4rUSMK{d#Zlw#6i>+?cCWrK zUi-rEcf5X;ocQv&A61f>SpRXdi^W;q>bWxe-L-GElNWOdyfdwgj4V0F;-hh^FK9>Z z$$j4TDvO_$tro47EnQrFG4@;i_eF@qD~ePshpfA?c;3_m@i4~4qvIE9>8-RsojEWoC`G5sSeN5UKF6}1(TZHw(f zOzz#;xp=w9l-hGE9$eAWi5Fbz*z@1AQ@o7zZ`d0>nGM|AOjWxDo0d7u*Ic{w7mwuZ zC+5sIdRJuk8lSB*Ja{5)*12h}Nqa7dAAV_()ipC;GA^`f&HsOD%qB-&znq`0ml*pu zc0#;L%JmhQ3#MiHJl^ZqT=%zr-Su2TAiEpOeO1MZD3A!`U?kXo6l`Iix1sCBodYN)1^#yp4|B}D~{?G3hT{3 zY8^UnHZ6Sji);?vi-G&aW{T}earp3~H)82rXW;|QFC%&$Eez3_ zy6w)5jjI<;-et@2xVOPXU@hz8yPfZo7wPWXxKSuOpLbX84W9M4`fjdHZ4N(w@7mhH z%uMaYO!sFjU8_=OGH1&6l&(v+Z^*mumJ#^Ea-hfLQyI_ZvWRIHwjULp6A|COY1wQx zjgT{^LKlbFF1*x!Nc>=Ktd{1o=DO6->6;cz%UtkV__0=|-`(ygwPgWo-@J=FT4CP3 z^WW>@YvISNUUNUzWX#?9+vo8-39f2QJ#YE>%a6YNSQ4r8_@tq~+AVhDsn*Yae^hGy z6LS1;4(Ip250TGhcc}|5eY6=9D~D~9gAlirv%uf{U^=1rN3MinQOw~^`9C$E1={qZ!aK(q=N?>eJLmpn_wkipjY=z=WE6eA*FRkL=jea?heG#{ zbN-JL{a>5CJNwuF_YHsQ>%Siq|M@@IX7=n`X(^YupxLEP^)IB>k#_?S` z^UKytF^VTS_kmVUZQ`ArV=hg1*3A<&lQ5cgAk(O3{ht**!U9vD&My7!PfqVC9&x<^5Go?*! ziEUDe{hXA|OZsP*>At`7fl=(U@20&mjM;L2k)~IpOaCN(exbf_`5EIg!E<$<%f2=V zT657ddv*Q^UB)aUPeYe?jP5qC7aaW=KG`m%@aBXK@mp9@4nOA3Ex7BwOKAJj-7mi! z`EqNL9aButMLDK2&if(T0^^$W=gCc79r$2ks1ZA}=(OSh$6b>85^q0jczNPs9(QBc zf3Dq^ei`%0si<>4IGv$qkhn{y{iOJDUWH$K&dmF}w&{S(Z#ALM%TDl5uTK4BEXb?y z|Jx(YGpD$>u?rXr`4sdhG9S?JYUTOxrd(#9n&Wo0-a}5E_1ec(OVz@T6*n<(u(NGe zJQcF$;A^RMv#lm-{4ZI5aprkHsZtAv9pWYlH>cFT-Jv^+rLFr@tWCB3^D5TU?&4)y z3-cX*KhgfYZ=Qa2-GTf%fr;6-#2@ij^YK(JSB_zN@c#DSw%N}50h7FVO0%bSw9fNo zXnw^UeMI_h!o7Y^BfVep?@XV`%RG*aWpO$g$Kv&4BS&m9b5d4km&2}*r=KQNoeETX zqPVb2chl!t*DnY;9Oq4ZxKr`Nbk%htPa9JjT^sm6aV4D8s#Ez@I8D7`f0U`vO_!C* z*_lkwFTb5ITXXJ-ncFggoEQ2Wa@~~1SgJPBGqb3^Lihgc(*df za#~G%@&P~bC9bnf1M^{Sp%;Z|i@!JdL@&C@&}WF++cc*higLf3IXTR*KsfuU_{2^rM7tBC}&v{%`a7v`Drv zzG~@DgWHo+Lnhj;4S%TMT3Z+)&XCsQX!ib5|8BQT)(79aD+~gz`y73A=>3(9^@ks* zGE9B`U(rIyB=ECSj9_6e&ws5-!{j`(eG!K{FFr3(czEYE-5#q+tIaBI)c;L*{d%r$XHmq(gf_3R z8<&^ONtkYaYFVLG_NzI8m)sby>|bAZgnw?%#Cc05{FmjN_dn^#%4o08C1zfB2@a}v zr*u|Vtj;)@RdepfmICguyfmwAA&Xrz3Pq=_^O>FTrDvzxO} zuXfffy~9Z};*_7Qkhpc`q`vFytBSW9Hbk+%lAOD)W!bT1H`_GNhxhEg_G5M7gEeXV zU&K8N=jqeD@m6I-)+{5u(^ zCW~0_tA0Ca%L5VT_qWOm50u|d+WhY5X71#}VX=pmAGY6fKe0YX?D8r`*X*~KCr|l* zw)@6xJLZs+|D*Z0_x^d$Kl9K3g6!|!fByS_`e0q;Y`7 z|J(F;?hW>fua@l#xdDqhy6!N{d6+YoqyCmasWO?s>^%tcN?|#~HQ^MgW zpUqy`Ypd?|oL+WtnlAgX2Eq9U8uIfhq)*GflQCueqMN(!ZOWRf>Wd1NCHyyXs?a{` zb$732MW4@+*A1KI_^)I5`fsB5WsXY=bDpd?ul+8t_hN+3>wDhXH9Jb5e9vR|obmIZ zn~e9xo18aiUfK4kZ?(XXH=ko8~3EqFG zHBt1(12K)OxtHB@y+4MZSg;*<`su&fkM3a^ljF(J(qJ1@NLcwm;>H2d3)a;-cu)nkL|s6@pYwm&y&9YpSYFh=M-hFXP23B zT9?FKx#GToe}(oDd9j}Aw(ZtCYT^w7EoJ{raQO6TnaG#(A#puPN9<=#Y@MTGT%LMz z_gX(Qxy^2sQ>zbhDQWw@{q%XpL)PSC%TO7cQ}!;Y8$WKoWM7!y)BWsjFE8iYW140M z`x4H1ymwVlJn;93?oto0de1n%3d@J?|4&uj%#r%Nrax@Q>l?dMw_iT_L;m0sTei6C zE7lb3af=nczsAaRUi2+B_4l*AIqjMn`s0^4q)61xH$FFS?qYUjHM3R)WuB$wN2Qok zAKbNF_=w$e;d$vp|K+NBCF#+FUi;y=o)Gm(Rrt>`usnJue0^a3yQ5AbI(LQlhby) zsS_xFJ;UW$R`IP3CMPerFX2*%eEnL$kKZEuU|%Zd=F+(S(8~BYeySFA=8L#bfX7_n+DVprHVvF4b|LC_mwW$f&Ip+Tu!zBN_a+1x9e?9+`L`B$#bNl*EpB?kSMz;;LSg0o4sP{_9!NczwVpT3`I6pF&*}{A6jfdTb zjObI#R>*968l$SWB;!&b;& z+@Pt}7_VtmV}s1=Q5{>Z^_M2DBt_)m}*hZ-?yvfrTg|S{IcVN z_zrh<(K_)DH;S91Ox4fLVY;)JBc}1yYU_;whDXv)F1VTH9(``fjhAAP+1X0lc5J=v zVlvG{Yyw-)P9?2E&4q~^9KFifRnuRdSl`M%`?~8k0mU-Du9Q{VPwlx_X5`oztHx8@ zsv@-JnU~tqx9wbaJDwD+bz`2?^LvV?Ssbgm@JZ3sA6{>N%O}Tgd$uGcRJ-p&MXz+O z2*2SrXSu(}|9^?s__y(r*7B5}b%!TBxjTDqnD4ygBk>1~_;z+Ueo$29zHRnMS$mH2 zt2mY^Vd9U~7aMAxTD8#n{_B@<7X?lU?OUGmEzi&XB9mWUjgc#Ju=#WYeT|&SFP3a! zTYTc)=GH@dXIF|}TH(p~LUqE`JI-wZJA}A(S-5|n_uRIFBjto{t6NB{pcE_5s;A99 z#|<7dMPASn^{~~jYkI2m@8T|#;2(BVJFUMjSY+Y;TDbfN%aYRvc#3oFkOZmpACmNLiTzPh+X`}&RicTL{i*{RDR zp0xA7r6I5B>O7gCR_&QjZ`eBCjQ@B)E%=3ni(K;b$yS+@E!(a%efzu5r{|k#mi+IV zof12iW?sx!5Z-0DwQu_h%WZQu@(Wh^D>Xk}^p-jG$jA2|Ll)F+%f6fK|1VQq$$anU z;0=6_a#!sM%Zt+fSUImUu3*+W*1nW8 zirSwEpSBxpnX9PI<4g1#GPJV7EqV(KUS;}Ku*qm>%nvS!Nyy6jLDQ-$? zw|0+mzGiuT;)B_FQCD*Ab?^Fq`{D}m*i!|ewr_qH{9d6eedgwM=a1h5^{s5~WVzS> zx_t5C)vKZ0Q}<4Cv3U4!$64_gTUqtDZvFND{JrT7+rvV+#V=~hxfC*_1n{%&)b*Xc z%=y;6)1jA4axa@@&R(L-HAhALWai{KU*_=mK2&OAwtK?qKmR=A5k^)ShY60ZtS(0x zT?N^%9CvnQaXiWx==k@0RQ3DfZznH&iwoHQ|MwgA?Y`&isw1l3ny%kEJM?tffrQ(N z;?q_-4KD&M6v}LACW?$>ii?gb-PF-xSEvftL3+KU)XJ5P8_ikPE|7hu=?`D>t zB}BRZTWsdedcjnA&oz_zPwVS7tOGk#b+j+UFj=*NxZS~_x+7=PHZSvgaqijW)%7lm&%fw?`^mHDz1@4xDc^ctA5~IR zE%jdff7jg!-x&@ZV-Pkw6eiZvRbVA`(_&k4(~Cvd=d^uYa%!sB>KOmnCz)oeMSRj{ zv}VUJa!(8XX4&_$_ICBx`OJCi-r1LS*tgC*lUvI2 z=4$PtD-AKTq9=K3cZzjb_O5s+?mM$DdHZa6p=yEjh*R_00!_E>+&E`-WOBsvdsho0 z#GYP`ygY9rlk|kIe@oxpd~@~Kw0pnea;iIT^M3!7@+HVD^O@+zKT~h-wfuC=*lzW2 z-$R>b?GNA0wEec)#jVj1|BuD3<1^{ee5BvhQN5`8esfXfAN8+_f3zRjFTCMAU*P}E z=8yB|HP#=m|7Tn$@zb%&cf#$pXH&lHsaj^XSmmUB)hC~LyUZ6+m)4d#rZO(-n%?;= zET#PN&r4cy>Zczcl|ELriQkdKdHW-~qZ|C)8&oDwa(&bx>Bcubch>Hl?h8>Xv8TaKdy_%(*uh+bsJFRikuKTYi{Bt#)Cva3< zPvZ4oFy^%ppo#5Ghz$8sOvG^5KSf7Oj>^M?x(%2IqU zI(gjKvT~d0J@wmL{c`L3CPb`yGc(C%{ndSp5*1sN?ZmY`-YgE@f9!FhyTBW#^HU#d z9?H5dSv~i@P^0NmsRGZ&ou@x8XuQU7dw=(%fW7hx>&3TxJCS&Hn%hPzi7kFV^V$=3qO93 z4ZGhLnzz;Gp{bdw;Dq18HBN0edL4E^+OMr6$>9OdANC2@Rl{o#p20DviAif z71pYmK0C5ef0dxHqqKO3sQzn#WtWz?=&X*?ad~-yQ+5C541O13#wj*e^i7XAoV#|c zO0Poujp%#FPhs4RY`hQM`fc~rcr>TKJ$v6r<+Ja7mGfqmlMDQ}E()2w@9ez%OUP&BcLIO) zE6Qt6>)D8@%&|Rp;l$rqasL|+uf(4=-yVM>x5C7CV%XwE@^6j@H17|bxk(}W;dwsySWC zIbLSzLAD$GM;&_IxE~2m=De%4B*^b|)uvFDvwSZOy}f$$+Vw5-FTRNGS@2v|e%h~;vkYeJ3qP;^*lBgbJ$qS%&Wzo6 z@-{C}xw50_VukDWj5%vpCv;vjT&=K``%iD0tNA28uO;FEzJeul=0u%&aWb~(^_dbA zwH2xfdN;PNG`rQMyzSj4iH*U_GoK1)+)X{ZbQx=gc0k&OsjGaOiZaZfTt9X7#p<;@ zYpo7;d%9(Wa<#5;+iGBL6xZ>Q@&1!}J8xt>U$9}*-s;yY*BsdStu#K{Q-7kE&P?%2 zsV2{iYYV2yRkn*tzPa&I&1rA6rl|3idpj>5{nlgoCqr-@i{;Wk`gQB)nKFFli3)Xm zz1p@Tq{)2qsfneA)w#Zr6GCRYPIW6)R_7OH$yC)!znlH_SF!I`BQ5pWA_lR`FZwSp z+28g(;JrtQ*Y=26TN?kJslW3sm-|!S_Z>%y+x|XVS9_FAJTz&ovZHB4_R}95&9WC{ z{?h)gztjKRO6$4ruQY9O6Mbavl(Ssm%{hiYx3*qZwe~t>wnAI8OQ7tcx8TFKyLwI6 zYq{?DyW+;O8}C2P+)%7Oul;hrJ+lby6z0 zFVgti$(t=1_um-)JpJ>;Bca_48_uOyB$QosShjH53xzzsro-lk?rA#4305tzX5Kur z<>r+TPZjBu%WL-LJ;|MGk=vbW=waSg^jEph@pZ3L^6T{Fytj=a5U(tX|W zzKXNeDXY+q)FdA{F1d>fGLLV!U0>*N%$Idh*TUp)6E*&)4z5lNu(@H4v@7Kc$52S*5p-IKYH$-NbH_?GxxBm@ydXgW%HLEymvE&Z_Uv% zwFc82uf6u=S=rU5PJWo#e2mR^M#JLoXIGuIDr7sQer;KrMYM^-b0u$euhkL9S2czN zTdz2kC2A(K-cE{h^74OI_noW%f8~F{|N5>83LUKK|83qT=LG)Wx_|xj`~M%jef|Dz z^Kbd8f3g3+&j0-R*~||&6fXr9nDKM=^}C#SKl$PR=CTR(_KViu^{$gma68r(z3$gD zL;i{U=Y4)V?VC0&`(D+D$)`4-c>Y}E*z_~graiyQ^iGtCy5&Z7d#jubuzv zZuxve(ddesZ_Dppyi|QU{Ez-?%io(DfA{}Aaw|1_U0i+k?d5)dUa}Y8oBheW_L%Ug ze?Ob~_x^FOy~VG8;aySY{F=}4_7zW$-~YUN^_J-VT`vvqpP1TiYgqj3_`IJ@OP#Nm z$rPk~-4o5X>+Qt*b0)sq^>}Ch{ogN7m3r^L_w)G`=JJsD61#5NycS=&I(Yw&XJ6v) zET32T;_3a57434ne%!X-{dbl1`kebe=Y5|h|Lb@3{fp(FPW_#~|Ien0*-r0g{`hxK zy}tV8+)dvvJd(awQ1U*|-M;$a&hy;y#TQ-Q-izID^EchTI_|t~{fu8XRrlBbUtcx< z=eGSU_p8n?p1&jIdieXl`{njU?H-}}Dy z&A-j_u214_zyEKi_N{w`Y0swC|GBbIzw+9_$M^qze0)9T^PKXSiWj`@6>;U?E=l+8 zdGL4nPCwga?_W)hEB1Ngy5Ih%HCh^(G@T-~Pwi>9POb+%5m^b-ya<;{@$}V!w}W-~V^n{QB>^ zug|Z4!5&}#q;Y=jpL^1FRTo{i?|WtZeBO;umkuA>SMcoj72E1R+0$btS^r|au;6L& z@-Q)btM7BCIv@W3vFn2R{r`3TKR>R%GWX;5`^)G3-F?6IO|N)-&BM3*W2`?+Os{>k zBAT!AC=hNcCxen zK3aZOIquI#+2!vtD$f<~`|~cpHv2_rxto7}UzAAj79`itsi?(eN$Y&vlI9ly*?v!7R+-Hz=md05^mJPqN z|M|MZyWahO>3J)OpnmSh?dtlw{$1Cvf3WYk|8B+dQ}=J(dH3{p{f}2ui~ki|HjZa8t~qWTS9Qa= zUHzGSUi8_g|K6WIU-hi@74!G|{}=qWzF&E`^LhPa-}ZeeZx7Ax|NHEzzLG=L+r>Yl z^=!W^mH+*ueBIwa?e>4>wAYvUd^&OY|G(|ta@PMpm{;UW**>`C9{-~1wyyH}`4RtL z?!H%b#M(;Vdd=5;r{gE<^YFd@d$R9#-=3OZm!9VT`8x5oUfk{9FJAsrUmw3#r)8O+ z*8GyRWkyDyez8YK>-Xu!#MY&3|9Rlea_#W{&u`cLoq4|g+57UC|BF69oqxaNw&y+Z zdj*fHpT^5dovHbGPB_n2Hbpa1<=`u^8n=Ko$U z_s*{W@T@*!$N!Cw^>@7c^Z5OHs~0!R*Y1CF^2^2WeGebl_V537eY?!s<8}K4s()AI zop+75{$KBB{k6W|rm8exZ{e@W_JL(}pXTw~m;B;()7!DR;^*Sy`+s(Gm+RO3`!PFi z>+{veHGf9)Tm5FOjuWhY+$}%n&-|;(`ad2z*GKJtU~zu?`}fwbCRqF3D?Z;pZ$kc# zPYX{>{9pWir?Yj`zF$|n{j4AUux~Y&do#!EnElsh-HSF)|5>+UpH=Y>TV`%se!1$u zx9fhKtM>D{Tm1Oy^69Itp1(8M_Uo-F|? z^-ts1Rs3&X7g_!L)cSj0&oI89Y%crd*5mwK>3wgvG}jeec{+dZ=iqiVyOKX^uYQ|0 z(|(^>#EZ9YGNS)P{S2?)`Q-e4i5(x+-}CPMa80~MsqlAw&A&eDWBRs-zfVg4^Y+Sv zc)pza!ynE6ZFAlI;^x(xvgS4w=j7+#|1|C0o5=YU*FFo~|8Z>I>AteF;(s1(pI>|P`}&;^+wOk1 z`TbM>cXa)qP0qjbfBL+9ee?dmu$w>cRqy`)YvJ{u|NeZ4daioD=3CO!kJInXnI0ac zx97>ybea41+lxMH>)ZZrxN>#-`oAyU?e6K1D-Zp5;c&dbj@7AOf4B3?{O@Wni~qI# zP`rJ~rpo($P0yE_zulYp{Js3n=dZ$Js%{(!e7}2L{?*buh4y>j7~AI+`fDF9uYcY6 zrFChNq5ZdM)BjxWo0e|-`f%sMh2?&=+oPl8cbvEV^iaO$`(pFD_h;kkvF?Y~cspHqD9$GZD*AD24snm%>fZt;C}hd38sPs@+^d!Tpw+|m;Z-Rr+Ry6Xkodd0gyW&=Wj;v>|`_T1jqkY!SU&WuFE)1`Ex$pS(_PFYA7yIq%&aGR_ zx8tFI{h!Co?Q_20o|wP?^Of`Szr5Z)zv?R|^Y%aQQ~B%vl#9oj{60MW&wEMsihH-O zm4ABA#?&b5?izlEd4K)w*zmZY%i87tJq^|Go3l@O!(;aO)sN2eXWu#)Z~5fi<>lpp>Ab-t+cSq*`U$f`!ba%Vgj@Kjq-v4#Yf1SnS=k>p{|Nm;e%o*R-IZRG{q1CM-9GKov;1xIf1FOgWN-I#7PFt6?B3tM%U}MlJbv!- z`F+oit-t&4>BZ{0uZvId?$+b{|4UbY{|APb4>Eqt4L)x7|K0qzJHCH3uX!x3UwiH9 z{QDNSZN9&GUZ+(1{L|0v``#~3Tlf6kwAf39y7^Z7K0l0)`+ea{_52XNPW#2nm#($7 zsJLjoUg1E1-kzJag`d>zEAOqh|F?TH|IZ`GlFR=-^?oj2^dNL|`f>RkFNNK0tJ2R; zod4(IjYH4ne#iZ4oa=t~+x`E4kG!hi_a^*%mE5#>dw%R&*!cOIo%On!7ndf>->m)Q z?G^v$=1g|`f3r^SFMhlA`oAyFChJ)}=^XrAJ!u|IDc3D3+djI>7`1}0&`ukr}9v-NE|GD>#m!mV)~E=f#WWdYZ<09bPWH zbXC%m*LI7Io9oUs8N20N2wm~g(aKNmqDWJ++MU;lD+ILiPUYU=oqc`stFD_%r8B>r zyZAiz%ch8L52w64xA6L$8$ZM)_9k|CX!7gj?Ahty)N{uJ z6z6Q@GQ9d?xA_I8HEK`Sh?ze%xp7qFhs63*+G$Sus~^lP@^|_BVY!P`=_LJy+V<6I z>TB)1)oM4ccpW*pWCzzqaaUD&{Z!R^q71XYPFSy&@?rPMYn$~xUCpdo8KEJ%@Zb!` zYi;H15^HQG<^+Y$C>oYjD&KAvJL_HE^bhARpRi!HTtB#HviL+GnYE;W&@@2cf8Pm$R$7Rz6a@c0MB5m-Cl} z?s9eSl$m*%M|Ga~X=`+bdQ2)joPBXmT%-QBh>a&_m8xDfiY#^Qy*+nQT+-DhsiUPA zt&g0j6FY0M{uHC`vWr{Sq@)(f+}Jwxnn6lCx23!C)iaZL(`rjE`hRJ@!Nsl9&AV2A z%{E2d#1|if>}G~mDlZOrW6Ar!Y%;&=eaP5$M!dE>)0mHuv1u!PvH*d0nhvcmsOo?pC}xBvSp#@>KWborHY;xe@wrivr_zO zO{CiY?6!IBlR2;Qnyk3z$gLnC@bvT#{x|)zu5!OP;An32SITe7kDUn99z;jMdiVf=Jf(|R(%rPEPEulMd6}?SkP4Nr4`cz!kSpMYL+g2vMSE#!9Sl9 zx$IdfpXN=e5jynrr;BI&wUf&CH+((HdG*-&xz2OvJsST2LO`X%c zvknHFy*2go`nxrbVY`d`ukYf~bDtA*_ho~+%&CG)3;m`A8F{H?bLrnSnRQS-bL*kx zzCyN037;i=Iz^28+^@)~K9Ib2{b!)(#QwrsS@}fXyxNo**A{JYJD5MY(2JGh^X!gO zGyb>~&b;uVM*R@`GY2-+1M}5mlvg_4nZ|igZHhg^V@8?Q6}s0N8e+~S3o}_AD&g>L z6}uoTBgkY@6?P|Q(~ojZ{t!Fg2${b<))zYCX0FT9JH0RUpV>EC%kBe9cl%9=TGsT> z!9was`zeXpr|Rdui~AI;{Qv8>?_d5!|NEW(=gO}i57KpP)PK9VGf&K{)DhrwsR{mc z=8nNZk(r6!bJHjMJrm3`y*hSNx(Aw;~7S5ZdCp2=@k zt@qdZcSfL7+kJ-cmM+0xEdoAEL!74{pWu`FRMKnd@*4*PdbF3H_!MhVxJRecziR^9 z$tMntq8CkOgzC;ea)yn=z#!H{S8b`P!GFb-vs`)?Pf(aUg=cEVtOCwvr}bwqbV;U& zx#XRm`sqNmr^EdXgyAqmw4B5_og&! zL8IRmC0`>$!4huCsSfe2XM@5P##fwVn#(PB@bnDpv<)0_X1?MI3qlMO-Z-ljU43?1 zOz^#+To1YMbydEn_W@ z{`8Ir73TkwN`2lQS|eZ){9{o_phJZw*Lo-E#QSR>o_x(RB`JvC%eHc>(nFh@ViOND zeBQFFIYD#v{Uv<0v$T9?E!}P8lEQH)31>fa^#rp-a&6?uoS}J9IaF~)bO`6f zNw;kR4heW|d)&Efir;x_F_-5zuU+=L=42MrWT|BPC}rWKi$ao?z6UZMF>T74<-`7R zPUn)Tf>zTvR0_Sk+}m!na_OqRj|Cg;ik6-H=-G5gRGr%=Q)8Yw=c%WYD|DF-Mjg2^ zSF9{U#Gs(FpD{&7#I`BSbBe)K9S=*dz)~3r5!aV>I}(P;i5%?e{>uI#U(le zqK%Xntf*l@Uh^6^r%l_WQO+qcsl?8Y!!~ll>NcZW>M~EwY~!7^e8ulN-O^d~x2C(RYq zCB?kLg<4$a&irH=EYzPKD8B2}E%kS)7p2*ym8{)%@-O0Vx}+o6!xo{mAf!6+k&A}i z3^ON>oGh{ZO41@Mmv)C;a#+@uUh%yYc{ zV8g@@2^Yde;+Agae&~|1jn92nu1n{qi@)X;Pnxj(0QIBK&t3Z)A?&znX`m z8yeW1wRPEYQ&|z4! z^<{TY%BRoHafbyHn6^?^Nj2B_#EM<C(=H8o!GD?j?Z*051H2l2KYRY({N!zz(?(w<(G^^gbB@lox@a&nRI7*GX%f541F6j5N0-!OlAE}f z&1!bosIshCxF~g9)aCGpuS^&GfApp?DLPqcH zXBo2tdvKoWoQ+erWTon_NYj4jqn(q*m?bz-zk%;~W2~yF6N?uMlY*~+s?*7A=`P-H zGxW9xo%6I>SiHF5krwMRzUG|iAzUl?541BS8rE1pkmbCgwD5Nt8z*Ow=BdOTDhZzp z#G4Hotx`>To-uzs;r+@=aN$Q=uT++@*2$NB>Q;!F6*h8h*<|V2!kT9KfcXQL&%A$E zrNTA%vyV0&4f6@fa8)+RWWK-EC}4%uY=ei5mDxVqZaRIxvHM7h+_xJ{MU7Lhgui|` z$Hio!t8c)XOITp?W%~D!=Sg6G0li}*ZgH1VpTxHBhCE5yf zmU2ejsCMCN_71x)taUS^G}d=Ymycx2VtvND%8$ep1SfCRSgs~i6}iyJFmM1)=^}@E_-0BhCk#V+_(c3G@rqhU1%ZI6*)sQ(q!yusJ_}N1rlAPiiR?eD{ zo$!{WF)(tiYq`bpf@h4)T!(}vm|gJR9C=+x!T!r>Q{KB9QtDT^H!N9Z6v|`jw60N0 zBYlz`@7^ir1l$r2PASaa^|&SSgv;&Ivo)t0mTP@Bnmgy5{HaUE%hG#;!(KA{aP{(R z2)wA1GF3}aY9Eu%X@~D8MY^sg&V8|}rJ^8``E9<)=M{?%n)xqOe%822;S6`dVHFE| zj#*PpQ=SHSg?3Fl#=5J))X4H_m%+B)yh@q-mpGRw-Bi(HHPFy>RqK9mh*R6lVfIo# zZNJJVW^EgI%#XNk5I0$-WK^kfa;lkMg0IRnkySU?zhw)joeO*6W9+h>Y26aRbw1|D zHm$nBVl#s&H1Gzy!S6*v3nwJ{E}6UK1N+4HB2P@%{1{U8=RX}f5cW}q#UPj(2 zlO*0~E??z3xo^I~iQr~$=XrXTUQ6vZtczN+KU9Dg+X z$TTQ=U->3-V1K-J($VORq6>s33YTj5x}9HsiSIyizPhErrrSi$+|6`x!dN>hjab9wz(}FB5%Jh z@V9s%*7mEkS*>FcXRDADyXD0>wMs#T3aOie*80r~56#=0Ql~rp%p5bG%Ak^Y-=Ze^ zY=3+tKw6B=IeUg)RLhGX%|psI7t4Fq#7x${ioE2*?s{R8w2azBCBaJ}T`&He`IuKe zE9h;Z>w-(IDc0Poiy3S$7jXP$TybdIU-q1pcjg=~oPIloW#MFpsSj8fCTG1mr+Ikg zlsD7o9-nk>+L|m8fs)w9FTeaE~IZJuo zT(MYrO-?WC%drWmv1l$UE=$l-0H* zKL5%U*mwC#U7kmd|ABxt8CPZ}M9z9xGi9pI)AP%CFI8M!Dg1~{UoceoNXmtZ7!HYL zoB>O(8R}jxa}~HMw#Oo5$F?t#7jL;l#D^`PF8nyIKwsh9MkY6{XZcLh%#KoD*goDg zShzQ>AnmZBu1xb5-&Y3M>icD)$W(?lBot>v6w59cke?#PImjes8h}fF!X1Hr)qBY-;&rwmw zaH)Xj^sU7m(kIG|R8OyaHzVcjGS8fqMeA6ubG+POtfX>c;sM^S)sm{Si#C4ljSrM& zY1v~KtmJdFm0j|Bo40zyiHt&%T!9UbCqCq|+d46je@=^S)aii9-wts;n{j5r+J%wx z--Wwr2Z*`cbBXyHU66c??~=Xi&z3Y5FUzQH2TOKcV03p#N_Pvs+PK(Nif3ih#^7m- zHS~d?C!J$(!q4We&;qetY8)pHHm7U3iK^eY&04||lXc;+%vamMr8Z9dZ!{W? zyYC2HF29_I^Yn$+BG!G0cQ;lZ@Z#(`(&#T7X`Z>UcEXGx&8fH86i!;McXnm!_@#76 zqDm`Ag40d0=*`m*P469*VjtUdEYA9|XY-#D5IkffcdkjS$@ntULk%YpwH6DZt zR_%H!$i&vu`GJSG@}ZCCosPb?h(0Bm&`<-hKm}Eq1O;cN@aNeAHD|vxD25hX@)9q- zrNzJzp;N4z^g-QXZmIG*LAfC79_BP7dH=FmJ}dg&YeY*NxGKB?I&WmDn7Bx|O_JW= z?vnK2ioyw@lUu%Z1ciC@)}<+}&ajN(toqa4ld7mZ`RxgIrlj_xaUq)n^p_Y}{){@y zT(d!6W=riZ^Ip9wszH`n+gv8{m#wPdG=&!;vmE}M3|HN-py1<@oSk`- zeCpZvPdVsX=+?xRvz*O!=@T{Kgb%@wT#9`I!sQtGx9@zr%&@(pqT^RuwbZN|2RyXr zMsLnu`|z6Er5#)nY>RCe)_=14Ej?o*Z&+cl!-NSx92@Ekra7b)&dl(8*5)L(-N80g z;oY(jEz$4YC&V6YlAYWWDG~0xMp*Z6X_MAuvn<}^Y3J68Rw{-)R86aX=w>LGZP^*1 z>h1C%`u25>R;c@ zQjV|yWx*dhrv>gXZS|NLY_KmrIgvq1`|{yG3wp0`RvEeY`JQLaJ)O{cm!}|bW@c-K zb<%3X$cUD|3?fQ=88;kWEZlnXU}h?}@0Q@0M$;lmu}Buhy0)d9+|w)$erA9F*)?#! z;te5B>zQq=69P}}yx<#bx>G!2V{MA5YzW8sWo5-5&Ib!U5IUdCyL$Ty)}y^Dtg4FV zJ4*EYYzwX_{Z8nT(caBe-6aWvWhEX&Y4kr^5C8<_ayF3I+rV4R-_$u&$8osdh-VB zxjpSI$1s z_w|{^Q-P8R`>d-Kgu54q&ad{^8QI@+9MiyV%Z9ot=A z9{g5&YZKFZj>6;GpRO1PhDlhrcJ=vIth^j>=|DnGp@UbC%53IYEk?{Ae$O^?I=FDU zGW+BQe(Ed_6whX72(Fe~6SidGk164~$GkfF4+QP_e^%)aZoxSycX?B=Kps_>!`C!cps+YAEX}lG*^KoIf&=*jS3>9sjmDj2^ zrI_oRx}J}i#&)L^bBPod#mQ3BRs26TaC50{A@Gs?W)ofnW55l`$qhwgM!Q(riYwd zaLuuccS>Mnkx(#CisSRUV)w3Q9AKJZ;Lu_}B3?``R2W-T7Y4wVE4^kLo5g#xy;Rey~48@%o2<_Pdl;?97X9 z?_A^f#LjzxKv<^MjqFx;x#cFWjB>bQnGSV0pIvd|;YpV@W`-)ue!kW#GfUWZXNRU5 z|JAI8=NxJeX<3}!@b{Wpg3(vA*?hT|QkJ{rR%Lm8c_kKVYSmq({%6_4_^=c5?FQeN zjoVeS4;Qhlk_d51ynaCV-4|XbaUN&cZ4S(fE@*D`RXm(<`^cWFVJX{6vu6BkdwGj# zrbkq!_yR?Ra>Wc43Heserg;p0To>OgH00;exv^=wiCt2Gh0M8fC)Tyi(%WTjdM-C# z+9$%5=#kLFZp%}Vq3!4T;DUs8#^+StV9`90-6_{zGUR6HHdcDtV=A!c(pMG+CHph3H4s(uaHGeavi)2aDwsY3MP^wT2eG?)4m z3su;%ixnxj1$frTblWDbnI@3Ln$_=}SITfoFkDx3HR}}7QYlyW^}^egCcN02U@}Aa zd0g_e2X&8yv_w6+=Qf`%5v*_!@e$v`oR>Ar%f(m!W8@{>pz8-a`seUo-05J;nDS`G zf9J(-3X%j8Q#vQ|9*i?C-WTa4pJC9T91&vh%45@0ri51~g*+~cge16bV_R@p_vyy$ zDXa$3_W~k!C{-y0%wmvnUaV$1d!2#jKowEJmKh|JA^{9P)AzjNN z{JzXQ#(p?(`#g!MlanfYLU=_MW*=wcyi}(Y$mDx=ijKNkYCD&_mqn*el8S1S`$BJN zbJgQ{P7&PQYs4z%sNUN6V^vd>j$@y}bZ^yFj>#*$qMbi4D{fOWa{tunaY$O{LBv$4 zO$NL2meNN{aDLZTj<@UJ?Wd0Xi60~$K49us%?9XRrLNmtrLFSoPTEf+Qfth_lz)Z^gYnVt($wRqc{@>-wV zE0Ya=b(meLSTnmy?0}H{;)`XPWk;M$%q+R5Xqnti z*W+F6==Y`C%*MvVm@|G`j*^Y|#a+J|9QcCw&a8J?*D0ANXP|SH?VRCVQTOMwloc<% z;q!mJO{qccVOodqQkxyF7e4Z_d5QfwV4~Y{&0+eR&3}yyr%H!Jx8~=vvoC3uTqRM( zY;o?<(${BZ+q~r|n0EB>g&wY1+#Mg=DyORbmC#DB-gbJeca{yO)uux>0cWlsIjpwA zzh}2~K#$6mhGRV!RLnbs7h72i&dxOIl~|)EwMk~9gi!j*D-T_AYfCNt1+KQu@yoO* z?~K}4Ic1lGbN0{Nl|T9CZ4mBQwCAn=Bn!vul~u-XXYX1q<9Y8eT^|HE z<}K#w(BJ)$YZlv)faK7{7bGIu4i){$)Z$W{Z#lR1w255X@6A({yaSnx+~2QTTeQLR z#k7pMJI(Yvvb*>zYuUGaH|zPm!9BS!+VzIp8JC9(^Y@f>@a+tl^RA1- zZ*pFQfXtDN6K0ytSg^HwhUx*9^x2X(i%%s?Ghw^^@Y=_Hx6M^W&KaoIq*&X#MK}m$ zO}6>N^e6hk=R+ovz1)J+e7o9>i=BOQCp2qT|C%_XYR}f<8@xtp^>3Q!I*(>kYY(1j*{#t}<)u~+@ z6haPeu?^lJ7nLSqC3tj|XtKWQO+T~tT(d1Y7rpFyLJu7)mS&rFJFw^r|Eqh~duPqv zP*P%-Sgo*C_7KNrkr@wi7*kevt}^a0Vm`gNu*c`GgSk`RL#ZtbHW%DHyRzt{iepQE zX8&vMY-Om$Z!A2VLLS)vo3_c(H!>FpCr`L|wbE=g^? z@QZ;{a;Z%6Z3|h)to4F!!7|r=uDa1z=qF`#WkbC7%^4gXR&E=Niw$csI2g388-jMu zQrLDYL-@(fgn0^|WG`$DOPe))O+bK1e!|-~DouYIes`=n5qxtXht84Nl%di%V5 zcqex8!fzq310@p zOpQF?xA|wc8P^;=(c;%?dpq+QUQZ2haesM$>(FMV8%I*q#M^oV1Rn4iEOwH8WR>l4 zf%#yZ`_+jHg)78_I|75|Za%E25pA?@y`F-%g?-iA&Z5G`uGTfi$2ZI^(q^$KoOyax zAk&uhCs+%jIdA9&N;522@#1>I>C`foLna0LnqyZi$f(Tf7kF%EAb<9h#71wkt8Zky zUmUYc*y*F>6FnnDo{gi*kYmLcE_=oN7OkJxGK8di#kOm|mbz?g7Bh&9URn4XtZCk65)w1y$^M$gp(A`AOTh9&|h*DAp*DBC$o=`K!e& zgEw!dP1d-&q$@|=K>o1PMS+XCr7gRZIWwajrm!e*Wv_}Bs%H*gv~t-7vCP+7HyA}r zO%)EBm3T?}iEiqVw7i~c6V}a6;yJ&3>8H%yYma+wy}WfYpOEUage}!uAKcbH6rLm~ zpJK)8UAS!WqGPtR?G9YtW&F(KNkp3M?+~UVYn<7R#fME~UZQ#Bv`)Z>54_n7F&b-@ z{WB>q+?Ql}Y~KZ?^xnB!x;VI_brTxAJ*p>P@A%VFwK6Z1qcO>^FKUvFmgA-!Ym}=b zmq<3;3=og{&-F#GdQql9;*_2I$1~lceRr|+&lQ=g?A0{w^oo+4Xl{(W(}zre1Q7XtEDM|strP>lK9nPeA`d^eYWp6*dLtrOVVWN!H!zXudGx^DGmesi{f9XD5o?5jcW4(8XK@LI?xR!4#1Dc7+LITBaqR!8M^wEk{d^5*fJe$&K5UpnF% zvl&B_tliff5U=6v4vw6%+;iR3%c~zfSU1OSW>Z7-`uM0*E#0b*cIjj`M$Ngo?t+uP z#j?mn>o}(DmuV=S$GYd*EU~qW*IE3l7gf%ba?;iNa-ku52xvf$Tclx~E zPf2i@_*^<5p1X8gc)3&2f>}p&yt3LH)zxF9Ck9RmanwkiEEM%9r;z*3qFHf=zOZN& z3VyVVZmQg9{6PVe(y|$w;H?$r#N}a+_&2puV^djQblFSJu{|%Ky?H6BW5AyO(&|>@^IZONF zB!v~5kC`3xyA>xO{?w~w&4m?`(`9dVu&$b_W81|bd3aM*r|ZK68S4+7sZ%R#Vn6Zp z<=s+!{^`oA71C>MJ}h%fX)#WXEHPAH&?psucuDJ_lD;g%KZcT=mv3l1o_M2DduCa# zleJ(|yT*phNtZRw-O}2h{&a#x)UBfhE*p&XMK72izb=$$lNGe$nBtG%*1p)X3qfLg z(gMP#a9XC6H%tr)oL~R?(Hy3(=L@3)ZWM~_=rowlUU=P$bNVaiWx-odG(K~hrF`~I z(pHB;&xEOoOp4!qE-y8kD`Yiq14Bgbf+Oea7EigymV8-Qx3eugW1P+O8$&Pr+ zzTnluiM~#uy4|lzF3qy!n;Na~*H0y+GPyvTWYR-jpGQ^hxRm#Sc;rq;cdyPaMKuP&ZA#pB89J2pxamuYSG3;e!2Y{@tC zY4L0a8LfijGgPB`91bN(t+>1B@s_h%*3+NcUO#y#;@-Ww_P+42&s@Gwj-2|Wn)ZFU z?vY!Q-^OlXP<~ge&26{daMpzfY+A7qtVWTJ7QnVEJm2=?>WyT;~p$gxZ9x z5cMh*;qgdR5L&gV`9e^$K#1oxpNm|{p?cY`x4IQ{ig?~)b6v%+d_bT{<*Xjs!6mAI2Tl%DCa*@1uqNua2iN|x5oVK=t;F+z~uBz7>bDrKY+|BK=Nkn$jtf;WJ z3Or@67R2pHoF*bQ!S!g`rZan9X-wuiWBQtBm2yo_uF29|CsT$%N4LAYN*>-Z*Ohc< zsJ&SgJlC*R_HTRd3xO`yq>>F;Mo+f}GRk^8Z1&>*m=T|yHz(0!nltmYQ>*`W&I+1q zHaEXPwW6MBu3Z63JLl|0M#euK3iYh3k6N;b{<$E~H!o}T7DnNS+N}+W&Rd=S3OYMa zTO=KNrYAQC#VI`^zac`?B7v)a%g;CN-{oaiv8)@=~{K z(-R)|+;y!bmQ&Y7J#%n8s$;3K=T?@1fts|fuKEIjfRii zRyQbDI!;sdVcD)-mk;u;*Yoify10DWgXGdnRjVGHIZ`~qM7v`e>-#Mie^~maPA@a4 zjWw@qeO7kL<^ZSTvqsH6lOx9Sb4<3Bx$a%h z*4wOM#!=&u#i^|`pa0^Cr(sXOzU5G8ynATJwA>XxcgJq~;n^bEc)=oa&&ti8*lf0* zxl*(=C-CIzEh>V&S5~h+^VERzgOH|8FprMtj*{P|$KqbS`LyU~yYGYx6|CL!ugvPQ z@MXEMi1GZl&d+bGzU*AQ#@nPyW8tB%79x#nM0~s-37*rb>=u|Y=fJ0>vjTjkEj^TX zZP7Ld?wmr|r}lRX@3%60P2=k8Yn#H@KEo~jlZ0vdrx_7vPh7sO>V9R8UzkMNr?Ly{ ze(#fBv^SS?%THd5-}16267t&}yu=a&oL?D*XFO;QI6U{&?rWwVlR~RpcJv=OpqlnJ z=hm-BUnR;roHtEW(QXzgHD4%GXZ>vI1LrE~*$*DB3^r1UUMN(Z-XyoZ$vUqmbB4_B zQl%Yh{TIGS7Zus=#h}p=9m#q*f^SXI)V~{wuHJkbyyk@O;gbPPI@g5q=Dj_?XUnDu zr}AUF4sE_}wllhuU6HfgQ&l#|qF3Od7{}>v8~$r#YT9jGTJvdjPqCBcbSrzQ>9b_a zmu#?j)wcbB$?kcb&#Z$~oi=eFzj3tV=qm+N^PUY))*dtbB%!U8HqpXsNrKAEsgEPo zSv_n%+>Tp4^;cu7)EXvpjrGZSnA3aL^2v4^Z3W{^VD`6CJ7#o5{@7MrDW>}7fj!HHiT5J{CpS30@pDpW@NK->-ZCkM_3VW; zUkb(4vhN$QUT8geX+>y-BB!QLf$+>D@6I1oa z7OoqCN&*Zj4|PAOu1Lvjl`2}5%_>{`^>A0@a(#F!!NJZh@Z>Cwy=_a%*fu>rwkAne%+pNlDtk)QE+y9R z&>b?}eCI=ZbOlfK#rdR^yi<8xyYT1bS<9|u%wpP9d@^WG#@#ZZKauM%trk5wS!Mqg zgV3f!VL>+wFKjE8TcAG8?$BnhgqFE##xE_K`RA^ymCi_dF!6!rBPa1zo^R*wKbWQ- zIAi9aw@;n+Zxv(6n-%%)$c)l)T+iyThguTT)0&>U!KqpSZzNig1Gn@Yc* z`-4Us;hA#XKb-U|&3flEcW-pCP&?0}v@k(OOk!gDbd~#O&V*ceFFlXzMDRY=9s9E{ z%FpOL$^E!-gAMOpeU`$Ic(+gite4vrT?JV=Zq{424$W)gV0v5M5UQ5qoJHRu! zlcmrug^fY=#&*H@6Xs$aTr(Jxz1}G0OqZF<`J?#*d!^rpi3$fqSVJwmu5m?s?@O3_ z!fw;?3(N}Ycil7lWLO*y7`V$AnboNKZ96NsTTb)o%9k1m3rrq$|4f-<_|sOV;o3xp z84O(KXPkdOzk9-GHr)rjMzdeYST-IsxXT-%FZVGa#pO{u&&RSW;&WzoUs00lvGH}_ zh*e_ZvUZGixg9H%vrk4w)%oY~o7+zIE#9NjKY6VL--NV%6MrhmxtsCLOqp=7Wes0- zLcx+7<`-N=y?4wwdgwq#?5Zgz4o~7+l>Ri;@$eeviEmW|R$t-i{hr3wwd2&1g=_d! z%`><}*XEyH^hQ!tFX@sTZ^(3qm=Ns{YpsP+9bWuXXGZ#awMO)iKk6%;tKb<;G_XZH>iJr8KFwAifdeAX|&km-=$uH|(o8xEUTBa+sDqIG(cI`y`im z;oH^588?gfSoQpTdBf_`jNELYb8C(D6Hcx*n3dQi|EBd&f4f(sE~OTm04biCuaeZ^LJJ-V#fzWSYN}@56~J3R%I+ zCN(WR*t2$f@gBp8D>BaUw{7A)Dfi`?LP71PML%z@YWXC-c_sH&)45AGZd8AB=*$9B zPSwJ=W`WSfVcyF!Swm+?K7Z2G*Cv?t^pWYDAl21}4A!WzDrq^do6IohhuwM;jWzYl zJ3VvPyxy^JrRImnnnwB)kKQqfU7xabYZ<6X(mhr+Ki&PW8?`;IZS$w&*=OXMH=W`_b&| zBFl!YOcITS#y`EcJzc-T>#FmMMCnyl<*NgIXN4Ov%g^k18n$Jx@vY+1{~}L1FZ7wm z8@bAGk}!j#<*J&XWf7pm9Nu{?y)^aQ^j#&&Z=Wsnco#mY^QXcZ5urmzHdzNeXbTGY za`uAun~!#E8yuyp@$6n+UhvIy^QnNx<0+;k&?d0D=6y|gG_pB(LBi}Ot@HbLMCokWpl$te#{HdJr8gcp)%oJ-t<_I&u3f)ldkAwGgUTT<)diPV3*0#5 z^ZZ+?#GfShs~N{H*aaAzoVaxRyM>>Hxe_lr70fBRYdKPsp6hWDK%@Bq(-uBceTh|!=-y>tM{Xn2^kKtEjeW~H?$TW zeW7&9_2Sy6r=tQ(^?lCwi*ipgaTaI$?IO}pp_zMgpP)%uW4}pXcIYgobsl2A`V!pl z&MchP`l$S(@4xd?9_bZaH{Nm7aFyfcM-4>>Rw%0Ihl>iC?~Z6%vGASA+oE|cZ@0bP zE}GGD!tdGc=3Cs?o;9ps4(jA&-0mvY%qQBv)``_bn4^W4Rk&$`fK6}6fAiu5`!`OL z{F+v*ezND~&-w!rvoF>Am+xPG-}~kM?dc2Ve`w0{x3Bz{|LgzxAC}CkRxm!yysNr> z-2~%#4!f>#WkqS1Z<*+RH+J5-ykEKtxHNu!zIHk8&pgfrYCoBn)_kyTi~Dd_a>d&% zQT3+s;`2>qr00LoVffU-(L8tWBjY-5ZzthnN7Q?m4@O5ET7Tozytkrpdi^G~`!mXz z-Zy^t|5{ScTlzIZ*2Bqs?S&U6i{3aFI($FLxp{%OV|YQZ?gHcGB36>Gg%lq~yiJ&X zJWg=E4o|aIfY&M;*SqCH5plH zNwyPCg!j0lUR=G_{dAYvve%aPV|HHIE)pAW^6c_d+v+2}+T0ewyN@qS$g%!i{i$oA z>Se7d6=lvZ zr21QJv2?%Vw$L9}I&1Uy&j09PFzXw8cv{u2yGMUGm82<(T=?}n?RmP=>Bg^DFP#V| zOInqzx5Lgna?Kh4A8gv(^JWNsU(@2_pHdR|{YQK9!_@NnWe?Ez3M^JhC4$_mK(IWIctRWMQi=Jd-+U2#8l&3W<6 z&Yi7KuLNiW&d~DHcy{7M(H+~EfXlzPsorMJdwVj==fHyvwxTDj+!PB+Yu+9Cx1BBP z{f1e(Roy3#B-E(A)jlS?GsVzm;?fhe=jA%chv>ZDu04lOC) zb7^(uv+A0xl-!;5id$!V>-qZi*|jfAb5^L&|L~2EuW#Mg^(tbk-Rkv{)}74`e*JD^ z=h7v1S7rTm`*@xeFP1%BSsw7;J#k*S@Qtswp*Jcm-|XSoy>D$|c-;9jp}*DGHivSE z%FBPqO|flF*|feqM`^RoJH2yXLauyB(21YZo4$OyRn6V*k2fBiII`;a+TZD3moH!4 zQthOuwZQP&TmOD_u|Lh1S5A2UvN?9EL!IHvMS?e~PJDdzTIvq}fk<298Ks-nZF79N z%)081_UWiPL(!RX(h-soGi1*0uKs)PPe$tATa)K%EmzN`mGt-AJ zPrq#byz#Ooq{-D;VT_sJU$hL@!8yU&Qc@BDP(t!-x`!#T6wWz5}C z@#58*f9vG*5AI%jlb`pI@$97h_jNf;uP5g){9Y~m`(_n;+O(RZK0Yr`em&#AZu+T< z6Youw++V%smf`h8zuz{&`-B#9tlsIdGUg@6?>~XAu{>wWT8;~UUd()~#Nu_Vx5n$w zdy7>~!vnQe%QDMe?9@EodB1$6!t$pfj2moSw!D8UtlGF|*0qH;9eDv0PkU!P-S^7e zs4_Qt_EPDeYSFGHC*%VE-}@FfQLk|MiT8fDPF|B1?VK9^w{~jZ$q$PJU&$7wi~lJ; z6;W;=7a8mE>oha>y9ZwmO|X03t2Omim1cC@+_m5O&!0PUT4cIZeQnRRN5{3vbSmNqLlF2dEW2qKKSj9&dz?| ztp07=*KaM?#3u`yN}cTSm2{`0)Mtv1@9((obiveDP(<%lU!7J{9}#UwGiw@?dty8L$2w ze>>@-kMpbM$9vx2PHxb1`S+CNE(bLli|HXFhFx8`hIvy5$7&&iul@2&i}@@1F(EpsbTIlYwhU)9yqud}!R zTKsBD{hmKtHYRgCUs1MfQ^58ul9eAPw7%QT@b-nIqgu_oBX-*livIb$FLLvy1<$J!(#UGpi+3L9S9@nqwzIuyeaYHjcefl8Std`uwL>i4JO7aAEN88C>Z|9n9yOR~Y2GG0(fr%pbt)x32UKca zEs@RRT^duiLLl(((t8uGZ?>8itGuezUsF7`CsldlrHGulCk}dR%PxIz=gE^GQ^RZW zdTx(bMa_Bdt9j8fKgA((=Dd|9!W+*U2hV#G%^l8urf&I{P4C%l;&g+yy)@Z&a?7Ru zS^7%JuKT!(a`)d*T6xSn!|;uwOzDZAOC~Jg-^JS2&d(9@RqHm}bK{2Ee!qhD3v+Gw z<##rPK|!Ct%isHFliR|_g%bC_Ij$)9;=ZA`IZZ%DIenhyWVehJH~KytH2?h4cv(_^ z!@CdFyWN7zEldSg=N-AW_u{>*lq4&=x3lFXUVWEp=3KlyxhnO-^Y{K@?AjM+-g_81 zf1Tbp{{+OC1L)7y`~ZiKRBxk z`b?Uw(Zl?k$7^*sli%z75Ha>`Y%gzA=t%YHvHso6oR(B`Am$y%SBb?{ii`YH*}82% zu6)r{ZZXfSD|p$YnS6ZTvqSIyammWaNln~jzEMOv&`GqPI;$5?(*3)Sg&5mxwBv1tefpGv(dHs+ns;&d#5ckZxYTj+J<{A~jF zFYJ*vyg$WycA)VoCz~*zbZ_CAcI62x>Qi(>I-eSOy?;`4WAP8Mn&!@n328OTTWrGT z1)W|!_1}qGS?-2bzpq>nO4@1tSha(*W&5x1;*8sz3$iOxjFUFLUnjgVIk9_!bYg$> zdtbY4o2Gg3^s{X_lu~pt{#~@x%-9dhi#k_6J$Yw=`fQaYSC@+?JzZsJWhU?Mvd8#c z<$V2{_GSX_Ug#a`Ie61FcinD_RVwa#7Ei8y_{b%-D*Do~%dFA0q1kMq7ao3(oxCho z$>w3d-<#BIk8`{gLRV)@(oVJc=W%1cR*!CFch&iXvb{3v&h&ikooaTc@#oZ%Rpmfv{P*Op$^*AEJ8I+GgiN)6*w&1@A8xo;3H1*VjoK zC6`rw7hYEO+^l5ndV%D<(T6LqnQxjHdQ7-EDyh!EBA`!OxLa7%*3x9(h7bvzpVx!t zl`hS<64k3rVix}Vdb4A)p6%WM8UHr-F5{@JlcamI1O3nc>5Ajg6Z)!iWwrF|z{$Q2 zZ8!Di>UcfQ4Bas&mU)Lvd`|dNFEurH@#^qTcNaPZM8;h7u?-5l-YopN@g#3*Zo=Z@ z5%XgFOIsEl3&=5@DSdusaqZ4|IZ0mehGBOWKfZbJPS*cFRT|r$y!H@PUTyp?uC_?t zw(RV~}QGkv0M1}jke$S>;+Cpo#aeavKRh-wxoios&M6=Z8^uP zuexm8zo@c3_{smy8!<|2!ny9b+|*gJJ6z2C)5IH3PI#S@mH41~dGX=TtMb$AMErdE zWR7FJ_OmG)XUTE620QA?&yqa*B&>9?(S{on=fusKKf}~IE%~0}B8Hrn zRVydf7>K-!yfA5AUqQ_(^J^bP7XCBgT%Eah!j`x{|J~N5ysCJ7_{LuO9uX@S6-^cE zjqPhED%nnu|9-Aw@>N0K`4=wR-H$jZq5Y%#^T9hV%M3QZGAsG-W^`{hSK1!A?Jv)r zpRuwd{Cv3c)X@Fy zg#N!eQn!sQt>4nnOfXoi*-`b;$}gG17dObgJNLxdn>o3)#oFHD(sr@0{vjP4=DRk` zkn)&P^RgzPrgld_;MAW-dZNC4PpMYt*7pAQ^{NA$hu{>)V|do^;wM5z}?HztUUToB4Nwgj`==$+xm! zn*)N&Z~tbvSAE`$Va=+!63PFzcv0 zaqGr|3lpk#PMF!>7Cw1#rs+G=#qS*MZ!r|&;TQSM_SbH!kZin9#oy*gQ&S@g%b7;M zPJ74s-u>!I4`ljP_4? z{Zao(+Nqy2=Ou)`kM3(@yR&(6XT~~_SyD6mmUBJ+tC_1{WIoM%SMLpvRfjc1?=D-^ zo3WX()K1Lrcad=8lKiT@zvp{6OB+2*+iwRVQuJ^}BuE zSh{G#x?`ua-K;FP&zn1gPjriDYEfm~QS(F{iR5LstpjD}@J(H%?fl!js@u!>>BY_Q zjQ=kvI<)?I^(9AtQaq>M*Zy_h* zTiM8)A0_YcQ+pm~euA0!{5R5PeZTxz#Ju|D&%o~~36%?{NcpM$y%~`7EK_LXHf5`u znlip~UOE3hyfmpIsQ~lw7lYb6sPj7xAx|`26rg<;8qvMoS&j??>F- zCY-p>W+;;G_i~Gj1V}{?v>b@+RW@=sdUFuy#q~WSVWpCTJ zJia+^=UO)N`F5f4Z)QyJ|M<9U>c4-Nl9KZ5LeHFQbNkHwZ{gNSw{_O*O{qSfbaLLz zD=}%8?phmf-6isECG+BsTRQ_bZohxoe%%`OJy|JP3-Wa`rDla`uFddLUCC43#@(1u z5-qdRk~7yYVxHFDimc}a!7*DFo1LrTN|e6spmukKhFef>TJ`ID_CAmHGD&^hZ@qha zyYt7Olr`3&5_)dO790NGqQ9!jSlRY(^HB#)yH)PX9@U;soNvBIZk9ZMc-5uLou_u# zg{;*2_{J?G`PTLQhFjiA&-&!k79Mu@;UaSp>7Z$5qMzoat!&%emS*>{Ry=cqJA>*U zxBcJEmdxz!oOkC<&SjP{VNGqN(srx$`fHy}U6|sZSXWqAsJ2sNwq#H^+jiBnHoggKI(KHI{l3Xx z&}GXUarAr!>n*klcCq7!Uq5Ef?sk5i{C8(UYOQJTsiR*?%QYl8)t6mXKK83gQtO*Y zjhp}bLlp<5jg)SFIPjvNWXl763E5wNO1{5vh@WpZy^rVH6LqJR8|S^)Cd2W+Ah>2N z)BT>qZ+0smzi8aJ=dtmp8M!<1etCOK7AMsd)FoMj$z3>`vn#(s*?!{`$&1R4`L2S$OI6u-m1OG^D!rXJ>+@#zcYBo30 zS3h0x&F#_Y_m1lNk_T>=T@rk^adzLzRkEpvf^DQsJhz&A-?(>V!q3F`>px;Uc5z3& zS?zI|-}iJ{MzQ9qucUV|U4knfk2{qw?o2dc9$9S=O5(!Jt3St5oB+Zhp0G*^{^RTS9EU zX8pHWJ^OQk&gTU!gp#c<_LXfFAz1mbyy`m-zq3) zdFMgB-_pmGtb#JMZZtg>aa|@>VC(6Vle&!0U9ott?hnJoH#Yyg74~Y)77vq}D0$W3 zMSCswc!6up^U^;U(e@kwVc$Qdz$w_eZ%#9-*q{6ZKSiWFMG{=F=OV|ok7#^b;h9k#YkBI%#MwJP`(ESh;ar(3H96}3 zyH`9n&$ab9^N3t}s#eW7u`<>9jIHWUvp`O^`oxog<=owW+V7S!E~stZ_l0At!@0wM zYdKdlbJr*ny27Ct^JyW+02hT2OByAvJ-?sGdEnQu!Zt$o2~m1^F3G55SrUU$Kk z1mEwE>%5yoc$;fC^}czWa?tF9XyX2)=U-1hz7UiWs}at$UXgd#$>)!F@*1}Wuj&y9 zZCrUO?riXx%4h?(buKd(?26I7@waP1&$%7*bRJZyi%oRhaLZ6P{V7k`UPW!wjb%p9 z)-FxGahlP}>xAagE{#y(2Pf-xi^crXo@FyXTB^qS@Co+|-YWJdS7#YbEZDp9(1T;^ zU*C(#U)Cd`kutYi);(i;S^(4a@|%;sTh7XUW9zkr?eIkjuICvoipn>lG9!#imCORn zH2o_IqH{fsx7W>9KbE=j%Uho#?lV-|PVHWmmKbYft(^Mo?3y%f>6UM7L0Mljm1Bj9 z=A4yLoTW3dbUX9Nlge3#Z=Yidi(x<+0V(6oac`tvhcm`YvTOyVoV> zkbv>({qtUw$Vd8UT<`fhH+Ht9Sbik;)VysvoXusu`&Mjdc74F!X%MbuIJG~iLFQti z`pl|>ygya(dg6bu3~*vrSVjc5t-ZIa?kn)wAnsj{Mr?8cR}}4!z75 zoU%fuivRH&wymp!mhV~nEq7{dK;-qb*zKGbeUmKTZ4`Xk_+pvr$;OGji|?9!+$NI! zgLC2=gCs|Tb-6vc+fFV?ZaJ1&?eQ#$>E5(dUxSM$yB-|&yK(G`ZOgtTF|qDPT&FD0 zp6wQ&f5f^`_s~>ZwNqyoi~3Y-{b%%{?Td**wza|J!raZ=IecqZb3E^76FI0FEggT+ zxj3(ZZPlwLMc<~3{@|o?LDTA9EeCoHfh0C6!vt;?HD#YsIqTg|Ts6Jb-~U1AY5bwzCGM8m#>OE#>|bZ>xOn1;gz~XYo^#V}RkC%ybssTZqIkcExkmNj zpR{L+ocZgo&3)Oirn63>x14M5wV#X~v%{`@j^5FBt>=8Ko>gw6<*$l+4l7pQIDXD% z+B(g{>zDiSJAD3Eu|rq%A=ebn>%pzJ_#+DTc&4vlP0g)3t;*3{e%!J*Y;)Cl(}hQF z&x~4cFH`pN*9E?5tN)1<&ED{g=XG=@d%pY1voGID`YV2$HS5|Q^Gh#QJ^b9c{^@6v z{dLZ3rg>jb%{y=W&U=Mun0qy|a*>VkI`=;{oBX5uOkZdvSLvPflRulEQn9H*YKiumo4+TQj{>E#Xcyo~)HtoEwu-f39jzJYa; zobb{w0gR5zEcs7pZhoViDdt$VX+Mjm#Al!A`>zuF*X0V`-()uFu+OEJDtzYE=CO`t z%p%nfcsO^@J;xSuytZ=l5w#`yPl5`2A6w7&7=ZbzLCz`JbUj2Phw( zf6qj=X6xT1$NzTbZ1-og9Qj?M9{;YSV%_|Uv!9)pyz{7~EBKY%TC?gJ@w7Q}HhXVM z^*xffhHLZRIg-C;Si~4=GpF9FU2nHy=eivQ1}lzfJLTuAWccTueG!>^BfHCb_I9b& z9eoAoij?iP6})Roo7ZvqzMNo%lWM_@%)l)#92TClS}rB1?U%qSx3(}nwtVTV7^6>9 zWuEI_4VJO0)Cmv!cFXFMqqs|2M9nwvt1<`VtUgs_U0B)m`0o99eJk6@)qB&vSJ{SI zve(Gu@t?f(CA4wZ2Wj?sTcmvWqhGA#`}+E?<=Z#QB-3_uWg8bS@?EmULTY7c-{UC} zE>8^`Tl1{4OR9b^G!b`q)|qzc=!pgu!;edLuGc?&n`ymVxp=JLp=fgs9o8dE`ajSA zWjwN6@KNP!okeA58oF$%%mXG!PW7H~Xj9V-<|hE;O&Zk zyYAYGn(eNA^1Xl3@LG6i z@RAnCEsD3ba#U>lqSi;8Y$$rQbMfZH^~bnwMv2Vjm0!rafwySOo{-$7e|J7xc7D#@ z`8U3o?L7LK+pqKap|x?rDR~LK@`v@=54!JM8Wr3axa*aZ(-uP}w#|2?(r1(%-z3R$ z>+qwVHyhh*gO|G)HFonPudOa3*H`N{3KC-$<`O2np1hL%75 zb}_U6@U?xnoz!=AcHL5Qo3;B|Uyw`sqlL+r9(hK@@;-gIVypa)FSDX29Xw;cQtqAf zxp{B2jb)#hxd`bd$vsTt^zXZyo!zhho6mM*=bqS}^Th{P7oD3Fnp~Y%oxNdy=DFM4 zbF(y?6SKFhP858xCVP(mW|noT7OrMl3$MK}(Y*Mk)y99v#!K6>CeO&o7ufUGu=bot zTlcaP-+o?n>bxRe8P@uzOzGVAX9k|!TFN7%|Z{mI%Q*B~Zgd*`k^L+FC?3keT| zHkkyy4O6?=vt#XPZdE@gj)N7K#J(7Ey7w;G7$CGi`nHwQ49zcYpH`N<^YNT?s6|il zba1BM7xfiq-uJAu{j&c+#XsLoZ+5A0yE4y`_p{`qpJ!I~&(~|UnO3#(*t3~8 z3d3IgS+Pj{){~=x0Z#+n{O3(?=fC^6DQ3>x*n*Dbi3|8AU0u4Wt|KsV&tH>sXLkPo z{P67Q6>4jL&(YbvYi4n<OW z?$w7pit~E%Imq>wm#69mJ+EgkwN?2qJ#>BOd~NEd#C5OPRfS}3`{>`RuKDtDb$Is4 zr@`05m+ZOyBz-Secku6Z@-hGRJ1RB)%&?p^uW9k(>B$>4CUCIl*hD{g^x(+u{V`j1 zSA4ngVgJ7?C(;kAOM7T7EZ93y{9Dj*-7O3UZ7#JQci$iTvmr}Ae8JxSgSG2^Zo3v= z@A6PE!?Bvb{5!jYXw9Xbqet@Dz02;)Z}q5@e#q0i_Djy3v~J0rw@r`n-puT&t(X** zvD-B6WUX?CQB~jVXAN=6Znx%L|KfL6=+@nZtfo6F&2wv)tt)?d{r{0p`HF8mhmZLt z%4&Z+^||oJpI0Y!SEs0NIPPbC&ST3hubVzmEhY20UK~u=TYRL@{?4mehDXgUwsShF zH*C&5o%VhARgSnLI-BqR+>y{5yK%q#F&onrt<4izcF$FNwX?{n`{uceCQn7Bjji%L zTl7m#K0o)PrtRLePxXaU5(}#xS9tB7T3=P;vwvIiYs>6IT3_Bw4t86=rpGkjV&W5- zqI-LvpZv{y!vFh+8%I1>Ez2mHKcnJVLcU4o+fy%f!l%tjyd-qGYQeQ^^8qlWm|mlv4+FhN6M@cJKlzMFHG3ld1u*gw&UCPwVZ!k_3XCmO4|mVz=%5` zy>74HU1~d#?IVyod8?&o#>%uqmw2>gexCI-KBW*R_g%C2p77ac=NKy|Ula7s;e2%| z;^EqN4AEQD0+%iMawb~Bk^S1Hf={BM^G^%p=P&;`=b&yul~?H1>X@R#$65|vdn#ou zam@Bqaf+AG_X{<-607c|q`GIUtjOLiTV@q^t;~4V-8I%LHP{wUy!^f~(xc6dXPwNv zr@?N~x7P0Z7FPX6TQYh5iHYYQO6=o2em-=9^6#B*s&`0T6J~r|{<3Jt+}xb0437(# zblvAho(-w~vCsSQ%AJqe^rpCGR8LsgWXF>}=fOeS-h!kRoRj_5nXcV-v24}B8+bgZ5yI#KxYn!5~+cE3l(-fgwTNi1>eF+Y8 zo?D^VQ1nVA;ri>2*@a!#FTCC4mQt*>*=lmn_aCn933FQ2&eujLnkk&!Te0k!wEvGU zQFGXSEL-p?Q*g23*3-ru+CA%^%6VRWXw@Mlw2ZxZ>!nAFt@7QHPe*4~=U`u-- zM{6Oz^V=U4JezdGtm#MCx{m9!^~|@j|K^onZz_2&D|X-A*-;gr!~Xsj=BU-$R2%%~ z|F^UA|Lyp9c3q5v3Cq?CZnD3cHV5B?Xp-P5RUHx`KXf%V?^vh~5x<;H&J#kTnb* zpQbT9mXYPZKV`pE)~>Z|Kl=s5^l#j6ZCH8x;y>F5v$_N{8dkP6EM;hWt0K=}_Fv00 zsObSy&j#Ml{8w^qS(xq?ofQd~%cRLV{lv_cmmWzH#g6{Pzv{`_C3m+rGo(%G92Z=K?M*I>vP8zSsZF){0zPn?om@N#}|@ zGsXF6x=f0P|NDoR4XTc9C@AmxSeMR~DlGr{%+AZ3!`2l)exUEZ`iK0VWsklZchyNRGT1|?7^AL^_^ci}X_HQt2?vkB;02 zEY7(;P=Bx^>chL;$=Ay7|F6*5ytiVHZMD*lynio#9NKO3dxkvQ=B9-CdrRl+Kf3z9 z%)B}akJDZM*Ufm;Z&rND?DFKAU1y7%7H@sL{n6b$$M`K1Ys$k}x_V=FE|4#OfB)&% zJ?FK#_p#UgdOa~kChC#PL;u?koHr(&Ub5}WyX*G~$}tS`zO-{#27{mJfLchZLG zz|Fk-(|y((%zT<6cp`H};D=@g0Uu$5PbVWpn7Hg>3bbrbREGSB{dXe#6I157*iY{? zBpY;=I6M)P(eY>6B*$U-SS!2I<#73f^7oC;_r=}cSDo{E`~7FET>fcm4>ZfOy`FRY ziG+6L+%5My?GO%9uN%6VQ`?`J7FAwm4IB#k8uJ47Fh@Rcw-qm&E}!#({Q=9j8;kCX7(c&Q z$t`DZ(O&ZB&L?ATyYE4-?Ly_tvzWe&4PaiIEbJ&``;JUHfeZzys^Hb~^^c{7cf1DU_;Y!ollP-FbMUz7AMSpBJ zdL93tDT{w*)B0oetN-skp{=q^{`A3ou_cG=!bQ%hA4q)gzif#&zw@_sb0%0OGS)5V zHvC|B25pNFMxox_v{8mVJi$sehga di+9IGzTwY(*#8;?|KI#t|6{4o8-@#P3;-}&tcCyp literal 0 HcmV?d00001 diff --git a/index.js b/index.js index 887294c..42afe72 100755 --- a/index.js +++ b/index.js @@ -4,6 +4,13 @@ const express = require('express'); const fs = require('fs'); const app = express(); +const tsNode = require('ts-node'); + +tsNode.register({ + transpileOnly: true, + files: true +}); + app.use("/getupdates",(req, res) => { const out=fs.statSync(`${__dirname}/webpage`); res.send(out.mtimeMs+""); @@ -16,6 +23,10 @@ app.use('/', (req, res) => { } if(fs.existsSync(`${__dirname}/webpage${req.path}`)) { res.sendFile(`./webpage${req.path}`, {root: __dirname}); + }else if(req.path.endsWith(".js") && fs.existsSync(`${__dirname}/.dist${req.path}`)){ + const dir=`./.dist${req.path}`; + res.sendFile(dir, {root: __dirname}); + return; } else if(fs.existsSync(`${__dirname}/webpage${req.path}.html`)) { res.sendFile(`./webpage${req.path}.html`, {root: __dirname}); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c32748b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,985 @@ +{ + "name": "jankclient", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "jankclient", + "version": "0.1.0", + "license": "GPL-3.0", + "dependencies": { + "express": "latest" + }, + "devDependencies": { + "ts-node": "^10.9.2" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.14.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.8.tgz", + "integrity": "sha512-DO+2/jZinXfROG7j7WKFn/3C6nFwxy2lLpgLjEXJz+0XKphZlTLJ14mo8Vfg8X5BWN6XjyESXq+LcYdT7tR3bA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", + "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", + "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "license": "MIT" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "license": "MIT", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", + "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + } + } +} diff --git a/package.json b/package.json index b377d93..f992135 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,10 @@ "keywords": [], "author": "MathMan05", "license": "GPL-3.0", - "dependencies":{ - "express":"latest" + "dependencies": { + "express": "latest" + }, + "devDependencies": { + "ts-node": "^10.9.2" } } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..7c0d445 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ + { + "compilerOptions": { + "target": "es2022", + "moduleResolution": "Bundler", + "module":"es2022", + "strict": false, + "esModuleInterop": true, + "outDir": "./.dist" + }, + "include": [ + "./webpage/*.ts" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/webpage/audio.js b/webpage/audio.ts similarity index 71% rename from webpage/audio.js rename to webpage/audio.ts index 3a1f160..c006881 100644 --- a/webpage/audio.js +++ b/webpage/audio.ts @@ -1,6 +1,15 @@ -class voice{ - constructor(wave,freq,volume=1){ - this.audioCtx = new (window.AudioContext || window.webkitAudioContext)(); +import {getBulkInfo} from "./login.js"; + +class Voice{ + audioCtx:AudioContext; + info:{wave:string|Function,freq:number}; + playing:boolean; + myArrayBuffer:AudioBuffer; + gainNode:GainNode; + buffer:Float32Array; + source:AudioBufferSourceNode; + constructor(wave:string|Function,freq:number,volume=1){ + this.audioCtx = new (window.AudioContext)(); this.info={wave:wave,freq:freq} this.playing=false; this.myArrayBuffer=this.audioCtx.createBuffer( @@ -16,61 +25,61 @@ class voice{ this.source.buffer = this.myArrayBuffer; this.source.loop=true; this.source.start(); - this.updateWave(freq); + this.updateWave(); } - get wave(){ + get wave():string|Function{ return this.info.wave; } - get freq(){ + get freq():number{ return this.info.freq; } - set wave(wave){ + set wave(wave:string|Function){ this.info.wave=wave; - this.updateWave() + this.updateWave(); } - set freq(freq){ + set freq(freq:number){ this.info.freq=freq; - this.updateWave() + this.updateWave(); } - updateWave(){ + updateWave():void{ const func=this.waveFucnion(); for (let i = 0; i < this.buffer.length; i++) { this.buffer[i]=func(i/this.audioCtx.sampleRate,this.freq); } } - waveFucnion(){ + waveFucnion():Function{ if(typeof this.wave === 'function'){ return this.wave; } switch(this.wave){ case "sin": - return (t,freq)=>{ + return (t:number,freq:number)=>{ return Math.sin(t*Math.PI*2*freq); } case "triangle": - return (t,freq)=>{ + return (t:number,freq:number)=>{ return Math.abs((4*t*freq)%4-2)-1; } case "sawtooth": - return (t,freq)=>{ + return (t:number,freq:number)=>{ return ((t*freq)%1)*2-1; } case "square": - return (t,freq)=>{ + return (t:number,freq:number)=>{ return (t*freq)%2<1?1:-1; } case "white": - return (t,freq)=>{ + return (_t:number,_freq:number)=>{ return Math.random()*2-1; } case "noise": - return (t,freq)=>{ + return (_t:number,_freq:number)=>{ return 0; } } } - play(){ + play():void{ if(this.playing){ return; } @@ -78,16 +87,16 @@ class voice{ this.playing=true; } - stop(){ + stop():void{ if(this.playing){ this.source.disconnect(); this.playing=false; } } - static noises(noise){ + static noises(noise:string):void{ switch(noise){ case "three":{ - const voicy=new voice("sin",800); + const voicy=new Voice("sin",800); voicy.play(); setTimeout(_=>{voicy.freq=1000},50); setTimeout(_=>{voicy.freq=1300},100); @@ -95,7 +104,7 @@ class voice{ break; } case "zip":{ - const voicy=new voice((t,freq)=>{ + const voicy=new Voice((t:number,freq:number)=>{ return Math.sin(((t+2)**(Math.cos(t*4)))*Math.PI*2*freq); },700); voicy.play(); @@ -103,7 +112,7 @@ class voice{ break; } case "square":{ - const voicy=new voice("square",600,.4); + const voicy=new Voice("square",600,.4); voicy.play() setTimeout(_=>{voicy.freq=800},50); setTimeout(_=>{voicy.freq=1000},100); @@ -111,7 +120,7 @@ class voice{ break; } case "beep":{ - const voicy=new voice("sin",800); + const voicy=new Voice("sin",800); voicy.play(); setTimeout(_=>{voicy.stop()},50); setTimeout(_=>{voicy.play();},100); @@ -123,7 +132,7 @@ class voice{ static get sounds(){ return ["three","zip","square","beep"]; } - static setNotificationSound(sound){ + static setNotificationSound(sound:string){ let userinfos=getBulkInfo(); userinfos.preferances.notisound=sound; localStorage.setItem("userinfos",JSON.stringify(userinfos)); @@ -133,3 +142,4 @@ class voice{ return userinfos.preferances.notisound; } } +export {Voice as Voice}; diff --git a/webpage/channel.js b/webpage/channel.ts similarity index 76% rename from webpage/channel.js rename to webpage/channel.ts index 18f8aa8..848c0cd 100644 --- a/webpage/channel.js +++ b/webpage/channel.ts @@ -1,31 +1,69 @@ "use strict" -class channel{ - static contextmenu=new contextmenu("channel menu"); +import { Message } from "./message.js"; +import {Voice} from "./audio.js"; +import {Contextmenu} from "./contextmenu.js"; +import {Fullscreen} from "./fullscreen.js"; +import {markdown} from "./markdown.js"; +import {Guild} from "./guild.js"; +import { Localuser } from "./localuser.js"; +import { Permissions } from "./permissions.js"; +declare global { + interface NotificationOptions { + image?: string + } +} +class Channel{ + editing:Message; + type:number; + owner:Guild; + headers:Localuser["headers"]; + messages:Message[]; + name:string; + id:string; + parent_id:string; + parrent:Channel; + children:Channel[]; + guild_id:string; + messageids:{[key : string]:Message}; + permission_overwrites:{[key:string]:Permissions}; + topic:string; + nsfw:boolean; + position:number; + lastreadmessageid:string; + lastmessageid:string; + mentions:number; + lastpin:string; + move_id:string; + typing:number; + message_notifications:number; + allthewayup:boolean; + static contextmenu=new Contextmenu("channel menu"); static setupcontextmenu(){ - channel.contextmenu.addbutton("Copy channel id",function(){ + Channel.contextmenu.addbutton("Copy channel id",function(){ console.log(this) navigator.clipboard.writeText(this.id); }); - channel.contextmenu.addbutton("Mark as read",function(){ + Channel.contextmenu.addbutton("Mark as read",function(){ console.log(this) this.readbottom(); }); - channel.contextmenu.addbutton("Delete channel",function(){ + Channel.contextmenu.addbutton("Delete channel",function(){ console.log(this) this.deleteChannel(); },null,_=>{console.log(_);return _.isAdmin()}); - channel.contextmenu.addbutton("Edit channel",function(){ - editchannelf(this); + Channel.contextmenu.addbutton("Edit channel",function(){ + this.editChannel(this); },null,_=>{return _.isAdmin()}); } - constructor(JSON,owner){ + constructor(JSON,owner:Guild){ if(JSON===-1){ return; } + this.editing; this.type=JSON.type; this.owner=owner; this.headers=this.owner.headers; @@ -37,7 +75,11 @@ class channel{ this.children=[]; this.guild_id=JSON.guild_id; this.messageids={}; - this.permission_overwrites=JSON.permission_overwrites; + this.permission_overwrites={}; + for(const thing of JSON.permission_overwrites){ + this.permission_overwrites[thing.id]=new Permissions(thing.allow,thing.deny); + } + console.log(this.permission_overwrites) this.topic=JSON.topic; this.nsfw=JSON.nsfw; this.position=JSON.position; @@ -53,6 +95,9 @@ class channel{ get localuser(){ return this.guild.localuser; } + get info(){ + return this.owner.info; + } readStateInfo(json){ this.lastreadmessageid=json.last_message_id; this.mentions=json.mention_count; @@ -63,8 +108,13 @@ class channel{ return this.lastmessageid!==this.lastreadmessageid&&this.type!==4; } get canMessage(){ - for(const thing of this.permission_overwrites){ - if(this.guild.hasRole(thing.id)&&thing.deny&(1<<11)){ + console.log("this should run"); + for(const thing of Object.entries(this.permission_overwrites)){ + const perm=thing[1].getPermision("SEND_MESSAGES"); + if(perm===1){ + return true + } + if(perm===-1){ return false; } } @@ -73,7 +123,7 @@ class channel{ sortchildren(){ this.children.sort((a,b)=>{return a.position-b.position}); } - resolveparent(guild){ + resolveparent(guild:Guild){ this.parrent=guild.channelids[this.parent_id]; this.parrent??=null; if(this.parrent!==null){ @@ -85,7 +135,7 @@ class channel{ let position=-1; let build=[]; for(const thing of this.children){ - const thisthing={id:thing.id} + const thisthing={id:thing.id,position:undefined,parent_id:undefined}; if(thing.position{channel.dragged=[this,div];e.stopImmediatePropagation()}) - div.addEventListener("dragend",()=>{channel.dragged=[]}) + div.addEventListener("dragstart",(e)=>{Channel.dragged=[this,div];e.stopImmediatePropagation()}) + div.addEventListener("dragend",()=>{Channel.dragged=[]}) if(this.type===4){ this.sortchildren(); const caps=document.createElement("div"); @@ -129,17 +179,17 @@ class channel{ addchannel.classList.add("addchannel"); caps.appendChild(addchannel); addchannel.onclick=function(){ - createchannels(this.createChannel.bind(this)); + this.guild.createchannels(this.createChannel.bind(this)); }.bind(this); - this.coatDropDiv(decdiv,this,childrendiv); + this.coatDropDiv(decdiv,childrendiv); } div.appendChild(caps) caps.classList.add("capsflex") decdiv.classList.add("channeleffects"); decdiv.classList.add("channel"); - channel.contextmenu.bind(decdiv,this); - decdiv.all=this; + Channel.contextmenu.bind(decdiv,this); + decdiv["all"]=this; for(const channel of this.children){ @@ -164,9 +214,9 @@ class channel{ if(this.hasunreads){ div.classList.add("cunread"); } - channel.contextmenu.bind(div,this); - if(admin){this.coatDropDiv(div,this);} - div.all=this; + Channel.contextmenu.bind(div,this); + if(admin){this.coatDropDiv(div);} + div["all"]=this; const myhtml=document.createElement("span"); myhtml.textContent=this.name; if(this.type===0){ @@ -188,9 +238,8 @@ class channel{ console.log(this.type) } div.appendChild(myhtml); - div.myinfo=this; - div.onclick=function(){ - this.myinfo.getHTML(); + div.onclick=_=>{ + this.getHTML(); } } return div; @@ -201,9 +250,9 @@ class channel{ return null }else if(this.parrent){ for(const thing of search){ - if(thing.all===this.parrent){ + if(thing["all"]===this.parrent){ for(const thing2 of thing.children[1].children){ - if(thing2.all===this){ + if(thing2["all"]===this){ return thing2; } } @@ -211,7 +260,7 @@ class channel{ } }else{ for(const thing of search){ - if(thing.all===this){ + if(thing["all"]===this){ return thing; } } @@ -222,7 +271,7 @@ class channel{ if(!this.hasunreads){ return; } - fetch(info.api.toString()+"/v9/channels/"+this.id+"/messages/"+this.lastmessageid+"/ack",{ + fetch(this.info.api.toString()+"/v9/channels/"+this.id+"/messages/"+this.lastmessageid+"/ack",{ method:"POST", headers:this.headers, body:JSON.stringify({}) @@ -233,7 +282,7 @@ class channel{ this.myhtml.classList.remove("cunread"); } } - coatDropDiv(div,that,container=false){ + coatDropDiv(div:HTMLDivElement,container:HTMLElement|boolean=false){ div.addEventListener("dragenter", (event) => { console.log("enter") event.preventDefault(); @@ -244,7 +293,7 @@ class channel{ }); div.addEventListener("drop", (event) => { - const that=channel.dragged[0]; + const that=Channel.dragged[0]; event.preventDefault(); if(container){ that.move_id=this.id; @@ -252,10 +301,10 @@ class channel{ that.parrent.children.splice(that.parrent.children.indexOf(that),1); } that.parrent=this; - container.prepend(channel.dragged[1]); + (container as HTMLElement).prepend(Channel.dragged[1]); this.children.unshift(that); }else{ - console.log(this,channel.dragged); + console.log(this,Channel.dragged); that.move_id=this.parent_id; if(that.parrent){ that.parrent.children.splice(that.parrent.children.indexOf(that),1); @@ -282,15 +331,15 @@ class channel{ } this.guild.headchannels=build; } - div.after(channel.dragged[1]); + div.after(Channel.dragged[1]); } this.guild.calculateReorder() }); return div; } - createChannel(name,type){ - fetch(info.api.toString()+"/guilds/"+this.guild.id+"/channels",{ + createChannel(name:string,type:number){ + fetch(this.info.api.toString()+"/guilds/"+this.guild.id+"/channels",{ method:"Post", headers:this.headers, body:JSON.stringify({ @@ -307,14 +356,14 @@ class channel{ let nsfw=this.nsfw; const thisid=this.id; const thistype=this.type; - const full=new fullscreen( + const full=new Fullscreen( ["hdiv", ["vdiv", ["textbox","Channel name:",this.name,function(){name=this.value}], ["mdbox","Channel topic:",this.topic,function(){topic=this.value}], ["checkbox","NSFW Channel",this.nsfw,function(){nsfw=this.checked}], ["button","","submit",function(){ - fetch(info.api.toString()+"/v9/channels/"+thisid,{ + fetch(this.info.api.toString()+"/v9/channels/"+thisid,{ method:"PATCH", headers:this.headers, body:JSON.stringify({ @@ -338,7 +387,7 @@ class channel{ console.log(full) } deleteChannel(){ - fetch(info.api.toString()+"/v9/channels/"+this.id,{ + fetch(this.info.api.toString()+"/v9/channels/"+this.id,{ method:"DELETE", headers:this.headers }) @@ -352,18 +401,18 @@ class channel{ this.putmessages(); history.pushState(null, null,"/channels/"+this.guild_id+"/"+this.id); document.getElementById("channelname").textContent="#"+this.name; + console.log(this); + (document.getElementById("typebox") as HTMLInputElement).disabled=!this.canMessage; } putmessages(){ const out=this; - fetch(info.api.toString()+"/channels/"+this.id+"/messages?limit=100",{ + fetch(this.info.api.toString()+"/channels/"+this.id+"/messages?limit=100",{ method: 'GET', headers: this.headers, }).then((j)=>{return j.json()}).then(responce=>{ - messages.innerHTML = ''; - //responce.reverse() - messagelist=[]; + document.getElementById("messages").innerHTML = ''; for(const thing of responce){ - const messager=new cmessage(thing,this) + const messager=new Message(thing,this) if(out.messageids[messager.id]==undefined){ out.messageids[messager.id]=messager; out.messages.push(messager); @@ -387,25 +436,25 @@ class channel{ } const out=this; - await fetch(info.api.toString()+"/channels/"+this.id+"/messages?before="+this.messages[this.messages.length-1].id+"&limit=100",{ + await fetch(this.info.api.toString()+"/channels/"+this.id+"/messages?before="+this.messages[this.messages.length-1].id+"&limit=100",{ method:"GET", headers:this.headers }).then((j)=>{return j.json()}).then(responce=>{ //messages.innerHTML = ''; //responce.reverse() - let next + let next:Message; if(responce.length===0){ out.allthewayup=true; } for(const i in responce){ - let messager + let messager:Message; if(!next){ - messager=new cmessage(responce[i],this) + messager=new Message(responce[i],this) }else{ messager=next; } if(responce[+i+1]!==undefined){ - next=new cmessage(responce[+i+1],this); + next=new Message(responce[+i+1],this); }else{ next=undefined; console.log("ohno",+i+1) @@ -422,15 +471,15 @@ class channel{ }) return; } - buildmessage(message,next){ + buildmessage(message:Message,next:Message){ const built=message.buildhtml(next); - messages.prepend(built); + document.getElementById("messages").prepend(built); } buildmessages(){ for(const i in this.messages){ const prev=this.messages[(+i)+1]; const built=this.messages[i].buildhtml(prev); - messages.prepend(built); + document.getElementById("messages").prepend(built); } document.getElementById("messagecontainer").scrollTop = document.getElementById("messagecontainer").scrollHeight; @@ -452,7 +501,7 @@ class channel{ return; } this.typing=new Date().getTime()+6000; - fetch(info.api.toString()+"/channels/"+this.id+"/typing",{ + fetch(this.info.api.toString()+"/channels/"+this.id+"/typing",{ method:"POST", headers:this.headers }) @@ -472,8 +521,8 @@ class channel{ return "default"; } } - async sendMessage(content,{attachments=[],embeds=[],replyingto=false}){ - let replyjson=false; + async sendMessage(content:string,{attachments=[],embeds=[],replyingto=null}){ + let replyjson:any; if(replyingto){ replyjson= { @@ -481,17 +530,18 @@ class channel{ "channel_id": replyingto.channel.id, "message_id": replyingto.id, }; - } + }; if(attachments.length===0){ const body={ content:content, - nonce:Math.floor(Math.random()*1000000000) + nonce:Math.floor(Math.random()*1000000000), + message_reference:undefined }; if(replyjson){ body.message_reference=replyjson; } console.log(body) - return await fetch(info.api.toString()+"/channels/"+this.id+"/messages",{ + return await fetch(this.info.api.toString()+"/channels/"+this.id+"/messages",{ method:"POST", headers:this.headers, body:JSON.stringify(body) @@ -501,6 +551,7 @@ class channel{ const body={ content:content, nonce:Math.floor(Math.random()*1000000000), + message_reference:undefined } if(replyjson){ body.message_reference=replyjson; @@ -510,15 +561,15 @@ class channel{ console.log(attachments[i]) formData.append("files["+i+"]",attachments[i]); } - return await fetch(info.api.toString()+"/channels/"+this.id+"/messages", { + return await fetch(this.info.api.toString()+"/channels/"+this.id+"/messages", { method: 'POST', body: formData, headers:{"Authorization":this.headers.Authorization} }); } } - messageCreate(messagep,focus){ - const messagez=new cmessage(messagep.d,this); + messageCreate(messagep:any):void{ + const messagez=new Message(messagep.d,this); this.lastmessageid=messagez.id; if(messagez.author===this.localuser.user){ this.lastreadmessageid=messagez.id; @@ -536,7 +587,7 @@ class channel{ this.messageids[messagez.id]=messagez; if(this.localuser.lookingguild.prevchannel===this){ var shouldScroll=scrolly.scrollTop+scrolly.clientHeight>scrolly.scrollHeight-20; - messages.appendChild(messagez.buildhtml(this.messages[1])); + document.getElementById("messages").appendChild(messagez.buildhtml(this.messages[1])); } if(shouldScroll){ scrolly.scrollTop = scrolly.scrollHeight; @@ -553,11 +604,11 @@ class channel{ this.notify(messagez); } } - notititle(message){ + notititle(message:Message):string{ return message.author.username+" > "+this.guild.properties.name+" > "+this.name; } - notify(message,deep=0){ - voice.noises(voice.getNotificationSound()); + notify(message:Message,deep=0){ + Voice.noises(Voice.getNotificationSound()); if (!("Notification" in window)) { } else if (Notification.permission === "granted") { @@ -584,11 +635,12 @@ class channel{ this.getHTML(); }) } else if (Notification.permission !== "denied") { - Notification.requestPermission().then((permission) => { + Notification.requestPermission().then(() => { if(deep===3){return}; this.notify(message,deep+1); }); } } } -channel.setupcontextmenu(); +Channel.setupcontextmenu(); +export {Channel}; diff --git a/webpage/contextmenu.js b/webpage/contextmenu.ts similarity index 53% rename from webpage/contextmenu.js rename to webpage/contextmenu.ts index 932fc0c..279549b 100644 --- a/webpage/contextmenu.js +++ b/webpage/contextmenu.ts @@ -1,14 +1,29 @@ -class contextmenu{ - constructor(name){ +class Contextmenu{ + static currentmenu; + name:string; + buttons:[string,Function,string,Function,Function][]; + div:HTMLDivElement; + static setup(){ + Contextmenu.currentmenu=""; + document.addEventListener('click', function(event) { + if(Contextmenu.currentmenu==""){ + return; + } + if (!Contextmenu.currentmenu.contains(event.target)) { + Contextmenu.currentmenu.remove(); + Contextmenu.currentmenu=""; + } + }); + } + constructor(name:string){ this.name=name; this.buttons=[] - } - addbutton(text,onclick,img=null,shown=_=>true,enabled=_=>true){ + addbutton(text:string,onclick:Function,img=null,shown=_=>true,enabled=_=>true){ this.buttons.push([text,onclick,img,shown,enabled]) return {}; } - makemenu(x,y,addinfo,obj){ + makemenu(x:number,y:number,addinfo:any,obj:HTMLElement){ const div=document.createElement("table"); div.classList.add("contextmenu"); for(const thing of this.buttons){ @@ -16,7 +31,7 @@ class contextmenu{ const textb=document.createElement("tr"); const intext=document.createElement("button") intext.disabled=!thing[4](); - textb.button=intext; + textb["button"]=intext; intext.classList.add("contextbutton") intext.textContent=thing[0] textb.appendChild(intext) @@ -24,17 +39,17 @@ class contextmenu{ intext.onclick=thing[1].bind(addinfo,obj); div.appendChild(textb); } - if(currentmenu!=""){ - currentmenu.remove(); + if(Contextmenu.currentmenu!=""){ + Contextmenu.currentmenu.remove(); } div.style.top = y+'px'; div.style.left = x+'px'; document.body.appendChild(div); console.log(div) - currentmenu=div; + Contextmenu.currentmenu=div; return this.div; } - bind(obj,addinfo){ + bind(obj:HTMLElement,addinfo:any=undefined){ obj.addEventListener("contextmenu", (event) => { event.preventDefault(); event.stopImmediatePropagation(); @@ -42,3 +57,5 @@ class contextmenu{ }); } } +Contextmenu.setup(); +export {Contextmenu as Contextmenu} diff --git a/webpage/direct.js b/webpage/direct.ts similarity index 79% rename from webpage/direct.js rename to webpage/direct.ts index 68e7a85..f4a5743 100644 --- a/webpage/direct.js +++ b/webpage/direct.ts @@ -1,6 +1,12 @@ -class direct extends guild{ - constructor(JSON,owner){ - super(-1); +import {Guild} from "./guild.js"; +import { Channel } from "./channel.js"; +import { Message } from "./message.js"; +import { Localuser } from "./localuser.js"; +import {User} from "./user.js"; + +class Direct extends Guild{ + constructor(JSON,owner:Localuser){ + super(-1,owner); this.message_notifications=0; console.log(JSON); this.owner=owner; @@ -17,14 +23,14 @@ class direct extends guild{ this.prevchannel=undefined; this.properties.name="Dirrect Messages"; for(const thing of JSON){ - const temp=new group(thing,this); + const temp=new Group(thing,this); this.channels.push(temp); this.channelids[temp.id]=temp; } this.headchannels=this.channels; } createChannelpac(JSON){ - const thischannel=new group(JSON,this); + const thischannel=new Group(JSON,this); this.channelids[JSON.id]=thischannel; this.channels.push(thischannel); this.calculateReorder(); @@ -50,19 +56,20 @@ class direct extends guild{ } unreaddms(){ for(const thing of this.channels){ - thing.unreads(); + (thing as Group).unreads(); } } } -class group extends channel{ - constructor(JSON,owner){ - super(-1); +class Group extends Channel{ + user:User; + constructor(JSON,owner:Direct){ + super(-1,owner); this.owner=owner; this.headers=this.guild.headers; this.messages=[]; this.name=JSON.recipients[0]?.username; if(JSON.recipients[0]){ - this.user=new user(JSON.recipients[0]); + this.user=new User(JSON.recipients[0],this.localuser); }else{ this.user=this.localuser.user; } @@ -73,9 +80,9 @@ class group extends channel{ this.children=[]; this.guild_id="@me"; this.messageids={}; - this.permission_overwrites=[]; + this.permission_overwrites={}; this.lastmessageid=JSON.last_message_id; - this.lastmessageid??=0; + this.lastmessageid??="0"; this.mentions=0; } createguildHTML(){ @@ -85,9 +92,9 @@ class group extends channel{ myhtml.textContent=this.name; div.appendChild(this.user.buildpfp()); div.appendChild(myhtml); - div.myinfo=this; - div.onclick=function(){ - this.myinfo.getHTML(); + div["myinfo"]=this; + div.onclick=_=>{ + this.getHTML(); } return div; } @@ -98,8 +105,8 @@ class group extends channel{ history.pushState(null, null,"/channels/"+this.guild_id+"/"+this.id); document.getElementById("channelname").textContent="@"+this.name; } - messageCreate(messagep,focus){ - const messagez=new cmessage(messagep.d,this); + messageCreate(messagep){ + const messagez=new Message(messagep.d,this); this.lastmessageid=messagez.id; if(messagez.author===this.localuser.user){ this.lastreadmessageid=messagez.id; @@ -109,7 +116,7 @@ class group extends channel{ this.messageids[messagez.id]=messagez; if(this.localuser.lookingguild.prevchannel===this){ var shouldScroll=scrolly.scrollTop+scrolly.clientHeight>scrolly.scrollHeight-20; - messages.appendChild(messagez.buildhtml(this.messages[1])); + document.getElementById("messages").appendChild(messagez.buildhtml(this.messages[1])); } if(shouldScroll){ scrolly.scrollTop = scrolly.scrollHeight; @@ -118,12 +125,10 @@ class group extends channel{ if(this.localuser.lookingguild===this.guild){ const channellist=document.getElementById("channels").children[0] for(const thing of channellist.children){ - if(thing.myinfo===this){ + if(thing["myinfo"]===this){ channellist.prepend(thing); - console.log(thing.myinfo); break; } - console.log(thing.myinfo,this,thing.myinfo===this); } } this.unreads(); @@ -147,8 +152,7 @@ class group extends channel{ const sentdms=document.getElementById("sentdms"); let current=null; for(const thing of sentdms.children){ - console.log(thing.all) - if(thing.all===this){ + if(thing["all"]===this){ current=thing; } } @@ -158,19 +162,19 @@ class group extends channel{ div.classList.add("servernoti"); const noti=document.createElement("div"); noti.classList.add("unread","notiunread","pinged"); - noti.textContent=this.mentions; + noti.textContent=""+this.mentions; console.log(this.mentions) - div.noti=noti; + div["noti"]=noti; div.append(noti) const buildpfp=this.user.buildpfp(); - div.all=this; + div["all"]=this; buildpfp.classList.add("mentioned"); console.log(this); div.append(buildpfp) sentdms.append(div); div.onclick=function(){ - this.all.guild.loadGuild(); - this.all.getHTML(); + this["noti"].guild.loadGuild(); + this["noti"].getHTML(); } }else if(current){ @@ -180,3 +184,4 @@ class group extends channel{ } } } +export {Direct, Group}; diff --git a/webpage/embed.js b/webpage/embed.ts similarity index 94% rename from webpage/embed.js rename to webpage/embed.ts index 09eff97..946a5fe 100644 --- a/webpage/embed.js +++ b/webpage/embed.ts @@ -1,6 +1,10 @@ -class embed{ - constructor(json, owner){ - +import {Fullscreen} from "./fullscreen.js"; +import {Message} from "./message.js"; +class Embed{ + type:string; + owner:Message; + json; + constructor(json, owner:Message){ this.type=this.getType(json); this.owner=owner; this.json=json; @@ -116,7 +120,7 @@ class embed{ const img=document.createElement("img"); img.classList.add("messageimg") img.onclick=function(){ - const full=new fullscreen(["img",img.src,["fit"]]); + const full=new Fullscreen(["img",img.src,["fit"]]); full.show(); } img.src=this.json.thumbnail.proxy_url; @@ -141,7 +145,7 @@ class embed{ if(this.json.thumbnail){ img.classList.add("embedimg"); img.onclick=function(){ - const full=new fullscreen(["img",img.src,["fit"]]); + const full=new Fullscreen(["img",img.src,["fit"]]); full.show(); } img.src=this.json.thumbnail.proxy_url; @@ -184,7 +188,7 @@ class embed{ const img=document.createElement("img"); img.classList.add("bigembedimg"); img.onclick=function(){ - const full=new fullscreen(["img",img.src,["fit"]]); + const full=new Fullscreen(["img",img.src,["fit"]]); full.show(); } img.src=this.json.thumbnail.proxy_url; @@ -194,3 +198,4 @@ class embed{ return colordiv; } } +export {Embed}; diff --git a/webpage/favicon.ico b/webpage/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..f10fd447dc69e9cb7bac707bcf6496b9ac757f1b GIT binary patch literal 2840 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 XdQ8k0&R)d8z`)??>gTe~DWM4fwo6)& literal 0 HcmV?d00001 diff --git a/webpage/fullscreen.js b/webpage/fullscreen.ts similarity index 96% rename from webpage/fullscreen.js rename to webpage/fullscreen.ts index 60cf256..4868b9f 100644 --- a/webpage/fullscreen.js +++ b/webpage/fullscreen.ts @@ -1,4 +1,10 @@ -class fullscreen{ +export {Fullscreen}; +class Fullscreen{ + layout; + onclose: Function; + onopen: Function; + html:HTMLDivElement; + background: HTMLDivElement; constructor(layout,onclose=_=>{},onopen=_=>{}){ this.layout=layout; this.onclose=onclose; @@ -134,8 +140,9 @@ class fullscreen{ if(i===0){ continue; } - if(thing.children[0].children[0].checked){ - array[3](thing.children[0].children[0].value); + const checkbox = thing.children[0].children[0] as HTMLInputElement; + if(checkbox.checked){ + array[3](checkbox.value); } } }); diff --git a/webpage/guild.js b/webpage/guild.ts similarity index 71% rename from webpage/guild.js rename to webpage/guild.ts index 015a2f1..ad6650f 100644 --- a/webpage/guild.js +++ b/webpage/guild.ts @@ -1,30 +1,52 @@ -class guild{ - static contextmenu=new contextmenu("guild menu"); +import { Channel } from "./channel.js"; +import { Localuser } from "./localuser.js"; +import {Contextmenu} from "./contextmenu.js"; +import {Role} from "./role.js"; +import {Fullscreen} from "./fullscreen.js"; +import {Member} from "./member.js"; + +class Guild{ + owner:Localuser; + headers:Localuser["headers"]; + channels:Channel[]; + channelids:{[key:string]:Channel}; + id:string; + properties + roles:Role[]; + roleids:{[key:string]:Role}; + prevchannel:Channel; + message_notifications + headchannels:Channel[]; + position:number; + parent_id:string; + member:Member; + html:HTMLUnknownElement; + static contextmenu=new Contextmenu("guild menu"); static setupcontextmenu(){ - guild.contextmenu.addbutton("Copy Guild id",function(){ + Guild.contextmenu.addbutton("Copy Guild id",function(){ console.log(this) navigator.clipboard.writeText(this.id); }); - guild.contextmenu.addbutton("Mark as read",function(){ + Guild.contextmenu.addbutton("Mark as read",function(){ console.log(this) this.markAsRead(); }); - guild.contextmenu.addbutton("Notifications",function(){ + Guild.contextmenu.addbutton("Notifications",function(){ console.log(this) this.setnotifcation(); }); - guild.contextmenu.addbutton("Leave guild",function(){ + Guild.contextmenu.addbutton("Leave guild",function(){ this.confirmleave(); },null,function(_){return _.properties.owner_id!==_.member.user.id}); - guild.contextmenu.addbutton("Delete guild",function(){ + Guild.contextmenu.addbutton("Delete guild",function(){ this.confirmDelete(); },null,function(_){return _.properties.owner_id===_.member.user.id}); - guild.contextmenu.addbutton("Create invite",function(){ + Guild.contextmenu.addbutton("Create invite",function(){ console.log(this); },null,_=>true,_=>false); /* -----things left for later----- @@ -38,7 +60,7 @@ class guild{ },null,_=>{return thisuser.isAdmin()}) */ } - constructor(JSON,owner){ + constructor(JSON,owner:Localuser){ if(JSON===-1){ return; @@ -57,12 +79,12 @@ class guild{ this.prevchannel=undefined; this.message_notifications=0; for(const roley of JSON.roles){ - const roleh=new role(roley); + const roleh=new Role(roley,this); this.roles.push(roleh) this.roleids[roleh.id]=roleh; } for(const thing of JSON.channels){ - const temp=new channel(thing,this); + const temp=new Channel(thing,this); this.channels.push(temp); this.channelids[temp.id]=temp; } @@ -78,7 +100,7 @@ class guild{ } setnotifcation(){ let noti=this.message_notifications - const notiselect=new fullscreen( + const notiselect=new Fullscreen( ["vdiv", ["radio","select notifications type", ["all","only mentions","none"], @@ -88,7 +110,7 @@ class guild{ noti ], ["button","","submit",_=>{ - fetch(info.api.toString()+"/v9/users/@me/guilds/settings",{ + fetch(this.info.api.toString()+"/v9/users/@me/guilds/settings",{ method:"PATCH", headers:this.headers, body:JSON.stringify({ @@ -105,7 +127,7 @@ class guild{ notiselect.show(); } confirmleave(){ - const full= new fullscreen([ + const full= new Fullscreen([ "vdiv", ["title", "Are you sure you want to leave?" @@ -133,7 +155,7 @@ class guild{ full.show(); } async leave(){ - return fetch(info.api.toString()+"/users/@me/guilds/"+this.id,{ + return fetch(this.info.api.toString()+"/users/@me/guilds/"+this.id,{ method:"DELETE", headers:this.headers }) @@ -152,7 +174,7 @@ class guild{ let position=-1; let build=[]; for(const thing of this.headchannels){ - const thisthing={id:thing.id} + const thisthing={id:thing.id,position:undefined,parent_id:undefined} if(thing.position<=position){ thing.position=(thisthing.position=position+1); } @@ -181,14 +203,14 @@ class guild{ if(serverbug){ for(const thing of build){ console.log(build,thing) - fetch(info.api.toString()+"/v9/guilds/"+this.id+"/channels",{ + fetch(this.info.api.toString()+"/v9/guilds/"+this.id+"/channels",{ method:"PATCH", headers:this.headers, body:JSON.stringify([thing]) }); } }else{ - fetch(info.api.toString()+"/v9/guilds/"+this.id+"/channels",{ + fetch(this.info.api.toString()+"/v9/guilds/"+this.id+"/channels",{ method:"PATCH", headers:this.headers, body:JSON.stringify(build) @@ -199,9 +221,8 @@ class guild{ get localuser(){ return this.owner; } - loadChannel(id){ - this.localuser.channelfocus=this.channelids[id]; - this.channelids[id].getHTML(); + get info(){ + return this.owner.info; } sortchannels(){ this.headchannels.sort((a,b)=>{return a.position-b.position;}); @@ -217,14 +238,14 @@ class guild{ if(this.properties.icon!=null){ const img=document.createElement("img"); img.classList.add("pfp","servericon"); - img.src=info.cdn.toString()+"icons/"+this.properties.id+"/"+this.properties.icon+".png"; + img.src=this.info.cdn.toString()+"icons/"+this.properties.id+"/"+this.properties.icon+".png"; divy.appendChild(img) img.onclick=()=>{ console.log(this.loadGuild) this.loadGuild(); this.loadChannel(); } - guild.contextmenu.bind(img,this); + Guild.contextmenu.bind(img,this); }else{ const div=document.createElement("div"); let build=""; @@ -238,13 +259,13 @@ class guild{ this.loadGuild(); this.loadChannel(); } - guild.contextmenu.bind(div,this) + Guild.contextmenu.bind(div,this) } return divy; } confirmDelete(){ let confirmname=""; - const full= new fullscreen([ + const full= new Fullscreen([ "vdiv", ["title", "Are you sure you want to delete "+this.properties.name+"?" @@ -284,12 +305,12 @@ class guild{ full.show(); } async delete(){ - return fetch(info.api.toString()+"/guilds/"+this.id+"/delete",{ + return fetch(this.info.api.toString()+"/guilds/"+this.id+"/delete",{ method:"POST", headers:this.headers, }) } - unreads(html){ + unreads(html=undefined){ if(html){ this.html=html; }else{ @@ -333,13 +354,13 @@ class guild{ } } this.unreads(); - fetch(info.api.toString()+"/v9/read-states/ack-bulk",{ + fetch(this.info.api.toString()+"/v9/read-states/ack-bulk",{ method:"POST", headers:this.headers, body:JSON.stringify(build) }) } - fillMember(member){ + fillMember(member:Member){ const realroles=[]; for(const thing of member.roles){ realroles.push(this.getRole(thing)); @@ -347,20 +368,21 @@ class guild{ member.roles=realroles; return member; } - giveMember(member){ + giveMember(member:Member){ this.fillMember(member); this.member=member; } getRole(ID){ return this.roleids[ID]; } - hasRole(r){ + hasRole(r:Role|string){ + console.log("this should run"); if((typeof r)!==(typeof "")){ - r=r.id; + r=(r as Role).id; } - return this.member.hasRole(r); + return this.member.hasRole(r as string); } - loadChannel(ID){ + loadChannel(ID=undefined){ if(ID&&this.channelids[ID]){ this.channelids[ID].getHTML(); return; @@ -394,7 +416,7 @@ class guild{ this.printServers(); } createChannelpac(JSON){ - const thischannel=new channel(JSON,this); + const thischannel=new Channel(JSON,this); this.channelids[JSON.id]=thischannel; this.channels.push(thischannel); thischannel.resolveparent(this); @@ -404,26 +426,82 @@ class guild{ this.calculateReorder(); this.printServers(); } + createchannels(func=this.createChannel){ + let name=""; + let category=0; + const channelselect=new Fullscreen( + ["vdiv", + ["radio","select channel type", + ["voice","text","announcement"], + function(e){ + console.log(e) + category={"text":0,"voice":2,"announcement":5,"category":4}[e] + }, + 1 + ], + ["textbox","Name of channel","",function(){ + console.log(this) + name=this.value + }], + ["button","","submit",function(){ + console.log(name,category) + func(name,category); + channelselect.hide(); + }.bind(this)] + ]); + channelselect.show(); + } + createcategory(){ + let name=""; + let category=4; + const channelselect=new Fullscreen( + ["vdiv", + ["textbox","Name of category","",function(){ + console.log(this); + name=this.value; + }], + ["button","","submit",function(){ + console.log(name,category) + this.createChannel(name,category); + channelselect.hide(); + }] + ]); + channelselect.show(); + } delChannel(JSON){ + const channel=this.channelids[JSON.id]; delete this.channelids[JSON.id]; + + this.channels.splice(this.channels.indexOf(channel),1); + const indexy=this.headchannels.indexOf(channel); + if(indexy!==-1){ + this.headchannels.splice(indexy,1); + } + + /* const build=[]; for(const thing of this.channels){ - if(thing.id!==JSON.id){ + console.log(thing.id); + if(thing!==channel){ build.push(thing) }else{ + console.log("fail"); if(thing.parrent){ thing.parrent.delChannel(JSON); } } } this.channels=build; + */ + this.printServers(); } - createChannel(name,type){ - fetch(info.api.toString()+"/guilds/"+this.id+"/channels",{ + createChannel(name:string,type:number){ + fetch(this.info.api.toString()+"/guilds/"+this.id+"/channels",{ method:"Post", headers:this.headers, body:JSON.stringify({name: name, type: type}) }) } } -guild.setupcontextmenu(); +Guild.setupcontextmenu(); +export { Guild }; diff --git a/webpage/index.html b/webpage/index.html index 7e895fd..02b72d3 100644 --- a/webpage/index.html +++ b/webpage/index.html @@ -10,21 +10,28 @@ - - - - - - - - - - - - - - - + +

Jank Client is loading

This shouldn't take long

@@ -58,7 +65,7 @@
-

⚙

+

⚙

@@ -86,6 +93,5 @@ - diff --git a/webpage/index.js b/webpage/index.ts similarity index 63% rename from webpage/index.js rename to webpage/index.ts index a87981b..b04a756 100644 --- a/webpage/index.js +++ b/webpage/index.ts @@ -1,3 +1,16 @@ +import { Localuser } from "./localuser.js"; +import {Contextmenu} from "./contextmenu.js"; +import {mobile, getBulkUsers,setTheme, Specialuser} from "./login.js"; +async function waitforload(){ + let res + new Promise(r=>{res=r}); + document.addEventListener("DOMContentLoaded", function(){ + res(); + }); + await res; +} +await waitforload(); + function setDynamicHeight() { var servertdHeight = document.getElementById('servertd').offsetHeight+document.getElementById('typebox').offsetHeight+document.getElementById('pasteimage').offsetHeight; document.documentElement.style.setProperty('--servertd-height', servertdHeight + 'px'); @@ -18,7 +31,7 @@ var info=users.users[users.currentuser].serverurls; let token=users.users[users.currentuser].token; let READY; -let thisuser=new localuser(users.users[users.currentuser]); +let thisuser=new Localuser(users.users[users.currentuser]); thisuser.initwebsocket().then(_=>{ thisuser.loaduser(); thisuser.init(); @@ -32,7 +45,8 @@ thisuser.initwebsocket().then(_=>{ userinfo.addEventListener("click",function(event){ const table=document.createElement("table"); for(const thing of Object.values(users.users)){ - console.log(thing.pfpsrc) + const specialuser=thing as Specialuser; + console.log(specialuser.pfpsrc) const tr=document.createElement("tr"); const td=document.createElement("td"); @@ -49,14 +63,14 @@ thisuser.initwebsocket().then(_=>{ row.append(usertd); const user=document.createElement("div"); usertd.append(user); - user.append(thing.username); + user.append(specialuser.username); user.append(document.createElement("br")); const span=document.createElement("span"); - span.textContent=thing.serverurls.wellknown.hostname; + span.textContent=specialuser.serverurls.wellknown.hostname; user.append(span); span.classList.add("serverURL") - pfp.src=thing.pfpsrc; + pfp.src=specialuser.pfpsrc; pfp.classList.add("pfp"); td.append(userinfo) @@ -66,9 +80,8 @@ thisuser.initwebsocket().then(_=>{ thisuser.unload(); document.getElementById("loading").classList.remove("doneloading"); document.getElementById("loading").classList.add("loading"); - thisuser=new localuser(thing); - window.info =thing.serverurls; - users.currentuser=thing.uid; + thisuser=new Localuser(specialuser); + users["currentuser"]=specialuser.uid; localStorage.setItem("userinfos",JSON.stringify(users)); thisuser.initwebsocket().then(_=>{ thisuser.loaduser(); @@ -91,178 +104,50 @@ thisuser.initwebsocket().then(_=>{ table.append(tr); } table.classList.add("accountSwitcher"); - if(currentmenu!=""){ - currentmenu.remove(); + if(Contextmenu.currentmenu!=""){ + Contextmenu.currentmenu.remove(); } - currentmenu=table; + Contextmenu.currentmenu=table; console.log(table); userdock.append(table); event.stopImmediatePropagation(); }) } -var currentmenu=""; -document.addEventListener('click', function(event) { - if(currentmenu==""){ - return; - } - if (!currentmenu.contains(event.target)) { - currentmenu.remove(); - currentmenu=""; - } -}); -let replyingto=null; + { - const menu=new contextmenu("create rightclick"); + const menu=new Contextmenu("create rightclick"); menu.addbutton("Create channel",function(){ - createchannels(thisuser.lookingguild.createChannel.bind(thisuser.lookingguild)); + thisuser.lookingguild.createchannels(); },null,_=>{return thisuser.isAdmin()}) menu.addbutton("Create category",function(){ - createcategory(thisuser.lookingguild.createChannel.bind(thisuser.lookingguild)); + thisuser.lookingguild.createcategory(); },null,_=>{return thisuser.isAdmin()}) menu.bind(document.getElementById("channels")) } -function createchannels(fincall){ - let name=""; - let category=0; - console.log(fincall); - channelselect=new fullscreen( - ["vdiv", - ["radio","select channel type", - ["voice","text","announcement"], - function(e){ - console.log(e) - category={"text":0,"voice":2,"announcement":5,"category":4}[e] - }, - 1 - ], - ["textbox","Name of channel","",function(){ - console.log(this) - name=this.value - }], - ["button","","submit",function(){ - console.log(name,category) - fincall(name,category); - channelselect.hide(); - }] - ]); - channelselect.show(); -} -function createcategory(fincall){ - let name=""; - let category=4; - console.log(fincall); - channelselect=new fullscreen( - ["vdiv", - ["textbox","Name of category","",function(){ - console.log(this) - name=this.value - }], - ["button","","submit",function(){ - console.log(name,category) - fincall(name,category); - channelselect.hide(); - }] - ]); - channelselect.show(); -} function editchannelf(channel){channel.editChannel();} -let messagelist=[]; -function buildprofile(x,y,user,type="author"){ - if(currentmenu!=""){ - currentmenu.remove(); - } - let nickname, username, discriminator, bio, bot, pronouns, id, avatar - if(type=="author"){ - console.log(user) - username=nickname=user.username; - bio=user.bio; - id=user.id; - discriminator=user.discriminator; - pronouns=user.pronouns; - bot=user.bot; - avatar=user.avatar; - } - const div=document.createElement("table"); - if(x!==-1){ - div.style.left=x+"px"; - div.style.top=y+"px"; - div.classList.add("profile"); - }else{ - div.classList.add("hypoprofile"); - } - - { - const pfp=user.buildpfp(); - const pfprow=document.createElement("tr"); - div.appendChild(pfprow); - pfprow.appendChild(pfp); - } - { - const userbody=document.createElement("tr"); - userbody.classList.add("infosection"); - div.appendChild(userbody); - const usernamehtml=document.createElement("h2"); - usernamehtml.textContent=nickname; - userbody.appendChild(usernamehtml); - - const discrimatorhtml=document.createElement("h3"); - discrimatorhtml.classList.add("tag"); - discrimatorhtml.textContent=username+"#"+discriminator; - userbody.appendChild(discrimatorhtml) - - const pronounshtml=document.createElement("p"); - pronounshtml.textContent=pronouns; - pronounshtml.classList.add("pronouns"); - userbody.appendChild(pronounshtml) - - const rule=document.createElement("hr"); - userbody.appendChild(rule); - const biohtml=markdown(bio); - userbody.appendChild(biohtml); - } - console.log(div); - if(x!==-1){ - currentmenu=div; - document.body.appendChild(div) - } - return div -} -function profileclick(obj,author){ - obj.onclick=function(e){ - console.log(e.clientX,e.clientY,author); - buildprofile(e.clientX,e.clientY,author); - e.stopPropagation(); - } -} - -var editing=false; -const typebox=document.getElementById("typebox") -typebox.addEventListener("keyup",enter); -typebox.addEventListener("keydown",event=>{ - if(event.key === "Enter"&&!event.shiftKey) event.preventDefault(); -}); -console.log(typebox) -typebox.onclick=console.log; +const pasteimage=document.getElementById("pasteimage"); +let replyingto=null; async function enter(event){ - thisuser.channelfocus.typingstart(); + const channel=thisuser.channelfocus + channel.typingstart(); if(event.key === "Enter"&&!event.shiftKey){ event.preventDefault(); - if(editing){ - editing.edit(typebox.value); - editing=false; + if(channel.editing){ + channel.editing.edit((typebox).value); + channel.editing=null; }else{ let replying=replyingto?.all; if(replyingto){ replyingto.classList.remove("replying"); } replyingto=false; - thisuser.channelfocus.sendMessage(typebox.value,{ + channel.sendMessage(typebox.value,{ attachments:images, replyingto:replying, }) @@ -276,7 +161,17 @@ async function enter(event){ } } -let packets=1; +const typebox=document.getElementById("typebox") as HTMLInputElement; +typebox.addEventListener("keyup",enter); +typebox.addEventListener("keydown",event=>{ + if(event.key === "Enter"&&!event.shiftKey) event.preventDefault(); +}); +console.log(typebox) +typebox.onclick=console.log; + + + + let serverz=0; let serverid=[]; @@ -289,13 +184,13 @@ let cchanel=0; function getguildinfo(){ const path=window.location.pathname.split("/"); const channel=path[3]; - ws.send(JSON.stringify({op: 14, d: {guild_id: path[2], channels: {[channel]: [[0, 99]]}}})); + this.ws.send(JSON.stringify({op: 14, d: {guild_id: path[2], channels: {[channel]: [[0, 99]]}}})); } const images=[]; const imageshtml=[]; -function createunknown(fname,fsize,src){ +function createunknown(fname,fsize){ const div=document.createElement("table"); div.classList.add("unknownfile"); const nametr=document.createElement("tr"); @@ -304,14 +199,9 @@ function createunknown(fname,fsize,src){ nametr.append(fileicon); fileicon.append("🗎"); fileicon.classList.add("fileicon"); - fileicon.rowSpan="2"; + fileicon.rowSpan=2; const nametd=document.createElement("td"); - if(src){ - const a=document.createElement("a"); - a.href=src; - a.textContent=fname; - nametd.append(a); - }else{ + { nametd.textContent=fname; } @@ -362,6 +252,7 @@ setTheme(); function userSettings(){ thisuser.usersettings.show(); } +document.getElementById("settings").onclick=userSettings; let triggered=false; document.getElementById("messagecontainer").addEventListener("scroll",(e)=>{ const messagecontainer=document.getElementById("messagecontainer") @@ -384,13 +275,13 @@ document.getElementById("messagecontainer").addEventListener("scroll",(e)=>{ }) if(mobile){ document.getElementById("channelw").onclick=function(){ - document.getElementById("channels").parentNode.classList.add("collapse"); + (document.getElementById("channels").parentNode as HTMLElement).classList.add("collapse"); document.getElementById("servertd").classList.add("collapse"); document.getElementById("servers").classList.add("collapse"); } document.getElementById("mobileback").textContent="#"; document.getElementById("mobileback").onclick=function(){ - document.getElementById("channels").parentNode.classList.remove("collapse"); + (document.getElementById("channels").parentNode as HTMLElement).classList.remove("collapse"); document.getElementById("servertd").classList.remove("collapse"); document.getElementById("servers").classList.remove("collapse"); } diff --git a/webpage/localuser.js b/webpage/localuser.ts similarity index 78% rename from webpage/localuser.js rename to webpage/localuser.ts index ca3c599..9c7cc8a 100644 --- a/webpage/localuser.js +++ b/webpage/localuser.ts @@ -1,18 +1,48 @@ -class localuser{ - constructor(userinfo){ +import {Guild} from "./guild.js"; +import {Channel} from "./channel.js"; +import {Direct} from "./direct.js"; +import {Voice} from "./audio.js"; +import {User} from "./user.js"; +import {Member} from "./member.js"; +import {markdown} from "./markdown.js"; +import {Fullscreen} from "./fullscreen.js"; +import {setTheme, Specialuser} from "./login.js"; +class Localuser{ + packets:number; + token:string; + userinfo:Specialuser; + serverurls; + initialized:boolean; + info; + headers:{"Content-type":string,Authorization:string}; + usersettings:Fullscreen; + ready; + guilds:Guild[]; + guildids:{ [key: string]: Guild }; + user:User; + status:string; + channelfocus:Channel; + lookingguild:Guild; + guildhtml:Record; + ws:WebSocket; + typing:[string,number][]; + wsinterval:NodeJS.Timeout; + constructor(userinfo:Specialuser){ + this.packets=1; this.token=userinfo.token; this.userinfo=userinfo; this.serverurls=this.userinfo.serverurls; this.initialized=false; + this.info=this.serverurls; this.headers={"Content-type": "application/json; charset=UTF-8",Authorization:this.userinfo.token}; } - gottenReady(ready){ + gottenReady(ready):void{ this.usersettings=null; this.initialized=true; this.ready=ready; this.guilds=[]; this.guildids={}; - this.user=new user(ready.d.user); + this.user=new User(ready.d.user,this); this.userinfo.username=this.user.username; this.userinfo.pfpsrc=this.user.getpfpsrc(); this.status=this.ready.d.user_settings.status; @@ -20,12 +50,12 @@ class localuser{ this.lookingguild=null; this.guildhtml={}; for(const thing of ready.d.guilds){ - const temp=new guild(thing,this); + const temp=new Guild(thing,this); this.guilds.push(temp); this.guildids[temp.id]=temp; } { - const temp=new direct(ready.d.private_channels,this); + const temp=new Direct(ready.d.private_channels,this); this.guilds.push(temp); this.guildids[temp.id]=temp; } @@ -35,11 +65,11 @@ class localuser{ } for(const thing of ready.d.merged_members){ const guild=this.guildids[thing[0].guild_id] - const temp=new member(thing[0],guild); + const temp=new Member(thing[0],guild); guild.giveMember(temp); } for(const thing of ready.d.read_state.entries){ - const guild=this.resolveGuildidFromChannelID(thing.id) + const guild=this.resolveChannelFromID(thing.id).guild; if(guild===undefined){ continue } @@ -48,14 +78,14 @@ class localuser{ } this.typing=[]; } - outoffocus(){ + outoffocus():void{ document.getElementById("servers").textContent=""; document.getElementById("channels").textContent=""; document.getElementById("messages").textContent=""; this.lookingguild=null; this.channelfocus=null; } - unload(){ + unload():void{ this.initialized=false; clearInterval(this.wsinterval); this.outoffocus(); @@ -63,7 +93,7 @@ class localuser{ this.guildids={}; this.ws.close(4000) } - async initwebsocket(){ + async initwebsocket():Promise{ let returny=null const promise=new Promise((res)=>{returny=res}); this.ws = new WebSocket(this.serverurls.gateway.toString()); @@ -106,22 +136,25 @@ class localuser{ break; case "READY": this.gottenReady(temp); - READY=temp; this.genusersettings(); returny(); break; case "MESSAGE_UPDATE": if(this.initialized){ - if(window.location.pathname.split("/")[3]==temp.d.channel_id){ + if(this.channelfocus.id===temp.d.channel_id){ const find=temp.d.id; + const messagelist=document.getElementById("messages").children; for(const message of messagelist){ - if(message.all.id===find){ - message.all.content=temp.d.content; - message.txt.innerHTML=markdown(temp.d.content).innerHTML; + const all = message["all"]; + if(all.id===find){ + all.content=temp.d.content; + message["txt"].innerHTML=markdown(temp.d.content).innerHTML; break; } } - } + }else{ + this.resolveChannelFromID(temp.d.channel_id).messages.find(e=>e.id===temp.d.channel_id).content=temp.d.content; + } } break; case "TYPING_START": @@ -131,7 +164,7 @@ class localuser{ break; case "USER_UPDATE": if(this.initialized){ - const users=user.userids[temp.d.id]; + const users=User.userids[temp.d.id]; console.log(users,temp.d.id) if(users){ users.userupdate(temp.d); @@ -163,7 +196,7 @@ class localuser{ } case "GUILD_CREATE": { - const guildy=new guild(temp.d,this); + const guildy=new Guild(temp.d,this); this.guilds.push(guildy); this.guildids[guildy.id]=guildy; document.getElementById("servers").insertBefore(guildy.generateGuildIcon(),document.getElementById("bottomseperator")); @@ -173,11 +206,11 @@ class localuser{ }else if(temp.op===10){ console.log("heartbeat down") this.wsinterval=setInterval(_=>{ - this.ws.send(JSON.stringify({op:1,d:packets})) + this.ws.send(JSON.stringify({op:1,d:this.packets})) },temp.d.heartbeat_interval) - packets=1; + this.packets=1; }else if(temp.op!=11){ - packets++ + this.packets++ } }catch(error){ console.error(error) @@ -189,13 +222,13 @@ class localuser{ clearInterval(this.wsinterval); console.log('WebSocket closed'); console.warn(event); - if(event.code!==4000&&thisuser===this){ + if(event.code!==4000&&this===this){ this.unload(); document.getElementById("loading").classList.remove("doneloading"); document.getElementById("loading").classList.add("loading"); this.initwebsocket().then(_=>{ - thisuser.loaduser(); - thisuser.init(); + this.loaduser(); + this.init(); document.getElementById("loading").classList.add("doneloading"); document.getElementById("loading").classList.remove("loading"); console.log("done loading") @@ -205,32 +238,34 @@ class localuser{ await promise; return; } - resolveGuildidFromChannelID(ID){ - let resolve=this.guilds.find(guild => guild.channelids[ID]) + resolveChannelFromID(ID:string):Channel{ + console.log(this.guilds.find(guild => guild.channelids[ID]).channelids) + let resolve=this.guilds.find(guild => guild.channelids[ID]).channelids[ID]; resolve??=undefined; return resolve; } - updateChannel(JSON){ + updateChannel(JSON):void{ this.guildids[JSON.guild_id].updateChannel(JSON); if(JSON.guild_id===this.lookingguild.id){ this.loadGuild(JSON.guild_id); } } - createChannel(JSON){ + createChannel(JSON):void{ JSON.guild_id??="@me"; this.guildids[JSON.guild_id].createChannelpac(JSON); if(JSON.guild_id===this.lookingguild.id){ this.loadGuild(JSON.guild_id); } } - delChannel(JSON){ + delChannel(JSON):void{ JSON.guild_id??="@me"; this.guildids[JSON.guild_id].delChannel(JSON); + if(JSON.guild_id===this.lookingguild.id){ this.loadGuild(JSON.guild_id); } } - init(){ + init():void{ const location=window.location.href.split("/"); if(location[3]==="channels"){ const guild=this.loadGuild(location[4]); @@ -239,15 +274,15 @@ class localuser{ } this.buildservers(); } - loaduser(){ - document.getElementById("username").textContent=this.user.username - document.getElementById("userpfp").src=this.user.getpfpsrc() + loaduser():void{ + document.getElementById("username").textContent=this.user.username; + (document.getElementById("userpfp") as HTMLImageElement).src=this.user.getpfpsrc(); document.getElementById("status").textContent=this.status; } - isAdmin(){ + isAdmin():boolean{ return this.lookingguild.isAdmin(); } - loadGuild(id){ + loadGuild(id:string):Guild{ let guild=this.guildids[id]; if(!guild){ guild=this.guildids["@me"]; @@ -259,17 +294,17 @@ class localuser{ document.getElementById("channels").appendChild(guild.getHTML()); return guild; } - buildservers(){ + buildservers():void{ const serverlist=document.getElementById("servers");// const div=document.createElement("div"); div.textContent="⌂"; div.classList.add("home","servericon") - div.all=this.guildids["@me"]; + div["all"]=this.guildids["@me"]; serverlist.appendChild(div) div.onclick=function(){ - this.all.loadGuild(); - this.all.loadChannel(); + this["all"].loadGuild(); + this["all"].loadChannel(); } const sentdms=document.createElement("div"); sentdms.classList.add("sentdms"); @@ -280,8 +315,8 @@ class localuser{ br.classList.add("lightbr"); serverlist.appendChild(br) for(const thing of this.guilds){ - if(thing instanceof direct){ - thing.unreaddms(); + if(thing instanceof Direct){ + (thing as Direct).unreaddms(); continue; } const divy=thing.generateGuildIcon(); @@ -309,7 +344,7 @@ class localuser{ let inviteurl=""; const error=document.createElement("span"); - const full=new fullscreen(["tabs",[ + const full=new Fullscreen(["tabs",[ ["Join using invite",[ "vdiv", ["textbox", @@ -332,7 +367,7 @@ class localuser{ }else{ parsed=inviteurl; } - fetch(info.api.toString()+"/v9/invites/"+parsed,{ + fetch(this.info.api.toString()+"/v9/invites/"+parsed,{ method:"POST", headers:this.headers, }).then(r=>r.json()).then(_=>{ @@ -351,19 +386,19 @@ class localuser{ ]]) full.show(); } - messageCreate(messagep){ + messageCreate(messagep):void{ messagep.d.guild_id??="@me"; - this.guildids[messagep.d.guild_id].channelids[messagep.d.channel_id].messageCreate(messagep,this.channelfocus.id===messagep.d.channel_id); + this.guildids[messagep.d.guild_id].channelids[messagep.d.channel_id].messageCreate(messagep); this.unreads(); } - unreads(){ + unreads():void{ console.log(this.guildhtml) for(const thing of this.guilds){ if(thing.id==="@me"){continue;} thing.unreads(this.guildhtml[thing.id]); } } - typeingStart(typing){ + typeingStart(typing):void{ if(this.channelfocus.id===typing.d.channel_id){ const memb=typing.d.member; let name; @@ -392,12 +427,12 @@ class localuser{ this.rendertyping(); } } - updatepfp(file){ + updatepfp(file:Blob):void{ var reader = new FileReader(); reader.readAsDataURL(file); console.log(this.headers); reader.onload = ()=>{ - fetch(info.api.toString()+"/v9/users/@me",{ + fetch(this.info.api.toString()+"/v9/users/@me",{ method:"PATCH", headers:this.headers, body:JSON.stringify({ @@ -408,8 +443,8 @@ class localuser{ }; } - updatepronouns(pronouns){ - fetch(info.api.toString()+"/v9/users/@me/profile",{ + updatepronouns(pronouns:string):void{ + fetch(this.info.api.toString()+"/v9/users/@me/profile",{ method:"PATCH", headers:this.headers, body:JSON.stringify({ @@ -417,8 +452,8 @@ class localuser{ }) }); } - updatebio(bio){ - fetch(info.api.toString()+"/v9/users/@me/profile",{ + updatebio(bio:string):void{ + fetch(this.info.api.toString()+"/v9/users/@me/profile",{ method:"PATCH", headers:this.headers, body:JSON.stringify({ @@ -426,7 +461,7 @@ class localuser{ }) }); } - rendertyping(){ + rendertyping():void{ const typingtext=document.getElementById("typing") let build=""; const array2=[]; @@ -456,20 +491,20 @@ class localuser{ typingtext.classList.add("hidden"); } } - genusersettings(){ + genusersettings():void{ const hypothetcialprofie=document.createElement("div"); let file=null; let newprouns=null; let newbio=null; - let hypouser=new user(thisuser.user,true); + let hypouser=new User(this.user,this,true); function regen(){ hypothetcialprofie.textContent=""; - const hypoprofile=buildprofile(-1,-1,hypouser); + const hypoprofile=hypouser.buildprofile(-1,-1); hypothetcialprofie.appendChild(hypoprofile) } regen(); - this.usersettings=new fullscreen( + this.usersettings=new Fullscreen( ["hdiv", ["vdiv", ["fileupload","upload pfp:",function(e){ @@ -480,13 +515,13 @@ class localuser{ hypouser.hypotheticalpfp=true; regen(); }], - ["textbox","Pronouns:",thisuser.user.pronouns,function(e){ + ["textbox","Pronouns:",this.user.pronouns,function(e){ console.log(this.value); hypouser.pronouns=this.value; newprouns=this.value; regen(); }], - ["mdbox","Bio:",thisuser.user.bio,function(e){ + ["mdbox","Bio:",this.user.bio,function(e){ console.log(this.value); hypouser.bio=this.value; newbio=this.value; @@ -494,33 +529,35 @@ class localuser{ }], ["button","update user content:","submit",function(){ if(file!==null){ - thisuser.updatepfp(file); + this.updatepfp(file); } if(newprouns!==null){ - thisuser.updatepronouns(newprouns); + this.updatepronouns(newprouns); } if(newbio!==null){ - thisuser.updatebio(newbio); + this.updatebio(newbio); } }], ["select","Theme:",["Dark","Light","WHITE"],e=>{ localStorage.setItem("theme",["Dark","Light","WHITE"][e.target.selectedIndex]); setTheme(); },["Dark","Light","WHITE"].indexOf(localStorage.getItem("theme"))], - ["select","Notification sound:",voice.sounds,e=>{ - voice.setNotificationSound(voice.sounds[e.target.selectedIndex]); - voice.noises(voice.sounds[e.target.selectedIndex]); - },voice.sounds.indexOf(voice.getNotificationSound())] + ["select","Notification sound:",Voice.sounds,e=>{ + Voice.setNotificationSound(Voice.sounds[e.target.selectedIndex]); + Voice.noises(Voice.sounds[e.target.selectedIndex]); + },Voice.sounds.indexOf(Voice.getNotificationSound())] ], ["vdiv", ["html",hypothetcialprofie] ] ],_=>{},function(){ - hypouser=new user(thisuser.user); + console.log(this); + hypouser=new User(this.user,this); regen(); file=null; newprouns=null; newbio=null; - }) + }.bind(this)) } } +export {Localuser}; diff --git a/webpage/login.html b/webpage/login.html index 780d56a..7b528a4 100644 --- a/webpage/login.html +++ b/webpage/login.html @@ -22,5 +22,5 @@ Don't have an account? - + diff --git a/webpage/login.js b/webpage/login.ts similarity index 89% rename from webpage/login.js rename to webpage/login.ts index ad40880..f6d20c0 100644 --- a/webpage/login.js +++ b/webpage/login.ts @@ -1,4 +1,5 @@ -const mobile=isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); +const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); +export {mobile, getBulkUsers,getBulkInfo,setTheme,Specialuser} function setTheme(){ const name=localStorage.getItem("theme"); if(!name){ @@ -11,7 +12,7 @@ setTheme(); function getBulkUsers(){ const json=getBulkInfo() for(const thing in json.users){ - json.users[thing]=new specialuser(json.users[thing]); + json.users[thing]=new Specialuser(json.users[thing]); } return json; } @@ -48,9 +49,14 @@ function setDefaults(){ localStorage.setItem("userinfos",JSON.stringify(userinfos)); } setDefaults(); -class specialuser{ +class Specialuser{ + serverurls; + email:string; + token:string; + loggedin; + json; constructor(json){ - if(json instanceof specialuser){ + if(json instanceof Specialuser){ console.error("specialuser can't construct from another specialuser"); } this.serverurls=json.serverurls; @@ -94,21 +100,23 @@ class specialuser{ } } function adduser(user){ - user=new specialuser(user); + user=new Specialuser(user); const info=getBulkInfo(); info.users[user.uid]=user; info.currentuser=user.uid; localStorage.setItem("userinfos",JSON.stringify(info)); } const instancein=document.getElementById("instancein"); -let timeout=0; +let timeout; +let instanceinfo; async function checkInstance(e){ + const verify=document.getElementById("verify");; try{ verify.textContent="Checking Instance"; - instanceinfo=await setInstance(instancein.value); + const instanceinfo=await setInstance((instancein as HTMLInputElement).value); localStorage.setItem("instanceinfo",JSON.stringify(instanceinfo)); verify.textContent="Instance is all good" - if(checkInstance.alt){checkInstance.alt();} + if(checkInstance["alt"]){checkInstance["alt"]();} setTimeout(_=>{ console.log(verify.textContent) verify.textContent=""; @@ -128,7 +136,7 @@ if(instancein){ timeout=setTimeout(checkInstance,1000); }); if(localStorage.getItem("instanceinfo")){ - instancein.value=JSON.parse(localStorage.getItem("instanceinfo")).wellknown + (instancein as HTMLInputElement).value=JSON.parse(localStorage.getItem("instanceinfo")).wellknown }else{ checkInstance("https://spacebar.chat/"); } @@ -148,7 +156,7 @@ async function login(username, password){ }} try{ const info=JSON.parse(localStorage.getItem("instanceinfo")); - url=new URL(info.login); + const url=new URL(info.login); return await fetch(url.origin+'/api/auth/login',options).then(responce=>responce.json()) .then((response) => { console.log(response,response.message) diff --git a/webpage/markdown.js b/webpage/markdown.ts similarity index 95% rename from webpage/markdown.js rename to webpage/markdown.ts index efc9116..82aa167 100644 --- a/webpage/markdown.js +++ b/webpage/markdown.ts @@ -1,7 +1,10 @@ -"use strict"; -function markdown(txt,keep=false){ - if((typeof txt)===(typeof "")){ - return markdown(txt.split(""),keep); +export {markdown}; +function markdown(text : string|string[],keep=false){ + let txt : string[]; + if((typeof txt)==="string"){ + txt=(text as string).split(""); + }else{ + txt=(text as string[]); } const span=document.createElement("span"); let current=document.createElement("span"); @@ -19,7 +22,7 @@ function markdown(txt,keep=false){ i--; } let element=null; - let keepys=false; + let keepys=""; if(txt[i+1]==="#"){ console.log("test"); @@ -151,7 +154,7 @@ function markdown(txt,keep=false){ if(txt[j]==="*"){ find++; }else{ - build+=txt[j]; + build.push(txt[j]); if(find!==0){ build=build.concat(new Array(find).fill("*")); find=0; @@ -205,7 +208,7 @@ function markdown(txt,keep=false){ if(txt[j]==="_"){ find++; }else{ - build+=txt[j]; + build.push(txt[j]); if(find!==0){ build=build.concat(new Array(find).fill("_")); find=0; @@ -251,7 +254,7 @@ function markdown(txt,keep=false){ if(txt[j]==="~"){ find++; }else{ - build+=txt[j]; + build.push(txt[j]); if(find!==0){ build=build.concat(new Array(find).fill("~")); find=0; @@ -281,7 +284,7 @@ function markdown(txt,keep=false){ if(txt[j]==="|"){ find++; }else{ - build+=txt[j]; + build.push(txt[j]); if(find!==0){ build=build.concat(new Array(find).fill("~")); find=0; @@ -309,8 +312,9 @@ function markdown(txt,keep=false){ appendcurrent(); return span; } -markdown.unspoil=function(e){ +markdown.unspoil=function(e:any) : void{ //console.log("undone") e.target.classList.remove("spoiler") e.target.classList.add("unspoiled") } + diff --git a/webpage/member.js b/webpage/member.ts similarity index 55% rename from webpage/member.js rename to webpage/member.ts index 17f334a..c1c4cb7 100644 --- a/webpage/member.js +++ b/webpage/member.ts @@ -1,6 +1,12 @@ -class member{ +import {User} from "./user.js"; +import {Role} from "./role.js"; +import {Guild} from "./guild.js"; +class Member{ static already={}; - constructor(memberjson,owner){ + owner:Guild; + user:User; + roles:Role[]; + constructor(memberjson,owner:Guild){ if(!owner){console.error("Guild not included in the creation of a member object")} this.owner=owner; let membery=memberjson; @@ -12,7 +18,7 @@ class member{ if(thing==="guild"){continue} this[thing]=membery[thing]; } - this.user=new user(this.user); + this.user=new User(this.user,owner.localuser); } get guild(){ return this.owner; @@ -20,27 +26,32 @@ class member{ get localuser(){ return this.guild.localuser; } - static async resolve(user,guild){ - if(!member.already[guild.id]){ - member.already[guild.id]={}; - }else if(member.already[guild.id][user.id]){ - const memb=member.already[guild.id][user.id] + get info(){ + return this.owner.info; + } + static async resolve(user:User,guild:Guild){ + if(guild.id==="@me"){return null} + if(!Member.already[guild.id]){ + Member.already[guild.id]={}; + }else if(Member.already[guild.id][user.id]){ + const memb=Member.already[guild.id][user.id] if(memb instanceof Promise){ return await memb; } return memb; } - const promoise= fetch(info.api.toString()+"/v9/users/"+user.id+"/profile?with_mutual_guilds=true&with_mutual_friends_count=true&guild_id="+guild.id,{headers:guild.headers}).then(_=>_.json()).then(json=>{ - const memb=new member(json,guild); - member.already[guild.id][user.id]=memb; + const promoise= fetch(guild.info.api.toString()+"/v9/users/"+user.id+"/profile?with_mutual_guilds=true&with_mutual_friends_count=true&guild_id="+guild.id,{headers:guild.headers}).then(_=>_.json()).then(json=>{ + const memb=new Member(json,guild); + Member.already[guild.id][user.id]=memb; guild.fillMember(memb); console.log("resolved") return memb }); - member.already[guild.id][user.id]=promoise; + Member.already[guild.id][user.id]=promoise; return await promoise; } - hasRole(ID){ + hasRole(ID:string){ + console.log(this.roles,ID); for(const thing of this.roles){ if(thing.id===ID){ return true; @@ -62,3 +73,4 @@ class member{ } } +export {Member}; diff --git a/webpage/message.js b/webpage/message.ts similarity index 63% rename from webpage/message.js rename to webpage/message.ts index 2f590a6..aaa5e88 100644 --- a/webpage/message.js +++ b/webpage/message.ts @@ -1,34 +1,56 @@ -class cmessage{ - static contextmenu=new contextmenu("message menu"); +import {Contextmenu} from "./contextmenu.js"; +import {User} from "./user.js"; +import {Member} from "./member.js"; +import {markdown} from "./markdown.js"; +import {Embed} from "./embed.js"; +import {Fullscreen} from "./fullscreen.js"; +import { Channel } from "./channel.js"; +import {Localuser} from "./localuser.js"; +import { Role } from "./role.js"; + +class Message{ + static contextmenu=new Contextmenu("message menu"); + owner:Channel; + headers:Localuser["headers"]; + embeds:Embed[]; + author:User; + mentions:User[]; + mention_roles:Role[]; + attachments; + id:string; + message_reference; + type:number; + timestamp:number; + content; static setupcmenu(){ - cmessage.contextmenu.addbutton("Copy raw text",function(){ + Message.contextmenu.addbutton("Copy raw text",function(){ navigator.clipboard.writeText(this.content); }); - cmessage.contextmenu.addbutton("Reply",function(div){ - if(replyingto){ - replyingto.classList.remove("replying"); + Message.contextmenu.addbutton("Reply",function(div){ + if(this.channel.replyingto){ + this.channel.replyingto.classList.remove("replying"); } - replyingto=div; + this.channel.replyingto=div; console.log(div); - replyingto.classList.add("replying"); + this.channel.replyingto.classList.add("replying"); }); - cmessage.contextmenu.addbutton("Copy message id",function(){ + Message.contextmenu.addbutton("Copy message id",function(){ navigator.clipboard.writeText(this.id); }); - cmessage.contextmenu.addbutton("Copy user id",function(){ + Message.contextmenu.addbutton("Copy user id",function(){ navigator.clipboard.writeText(this.author.id); }); - cmessage.contextmenu.addbutton("Message user",function(){ - fetch(info.api.toString()+"/v9/users/@me/channels", + Message.contextmenu.addbutton("Message user",function(){ + fetch(this.info.api.toString()+"/v9/users/@me/channels", {method:"POST", body:JSON.stringify({"recipients":[this.author.id]}), - headers: {"Content-type": "application/json; charset=UTF-8",Authorization:token} + headers: this.headers }); }) - cmessage.contextmenu.addbutton("Edit",function(){ - editing=this; - document.getElementById("typebox").value=this.content; - },null,_=>{return _.author.id==READY.d.user.id}); + Message.contextmenu.addbutton("Edit",function(){ + this.channel.editing=this; + (document.getElementById("typebox") as HTMLInputElement).value=this.content; + },null,_=>{return _.author.id===_.localuser.user.id}); } constructor(messagejson,owner){ this.owner=owner; @@ -38,11 +60,11 @@ class cmessage{ } for(const thing in this.embeds){ console.log(thing,this.embeds) - this.embeds[thing]=new embed(this.embeds[thing],this); + this.embeds[thing]=new Embed(this.embeds[thing],this); } - this.author=new user(this.author); + this.author=new User(this.author,this.localuser); for(const thing in this.mentions){ - this.mentions[thing]=new user(this.mentions[thing]); + this.mentions[thing]=new User(this.mentions[thing],this.localuser); } if(this.mentions.length||this.mention_roles.length){//currently mention_roles isn't implemented on the spacebar servers console.log(this.mentions,this.mention_roles) @@ -60,14 +82,17 @@ class cmessage{ get localuser(){ return this.owner.localuser; } + get info(){ + return this.owner.info; + } messageevents(obj){ - cmessage.contextmenu.bind(obj,this) + Message.contextmenu.bind(obj,this) obj.classList.add("messagediv") } mentionsuser(userd){ - if(userd instanceof user){ + if(userd instanceof User){ return this.mentions.includes(userd); - }else if(userd instanceof member){ + }else if(userd instanceof Member){ return this.mentions.includes(userd.user); } } @@ -81,7 +106,7 @@ class cmessage{ return build; } async edit(content){ - return await fetch(info.api.toString()+"/channels/"+this.channel.id+"/messages/"+this.id,{ + return await fetch(this.info.api.toString()+"/channels/"+this.channel.id+"/messages/"+this.id,{ method: "PATCH", headers: this.headers, body:JSON.stringify({content:content}) @@ -104,7 +129,7 @@ class cmessage{ const reply=document.createElement("div"); username.classList.add("username"); - member.resolve(this.author,this.guild).then(_=>{ + Member.resolve(this.author,this.guild).then(_=>{ username.style.color=_.getColor(); }); @@ -115,21 +140,20 @@ class cmessage{ line2.classList.add("reply"); line.classList.add("startreply"); replyline.classList.add("replyflex") - fetch(info.api.toString()+"/v9/channels/"+this.message_reference.channel_id+"/messages?limit=1&around="+this.message_reference.message_id,{headers:this.headers}).then(responce=>responce.json()).then(responce=>{ - const author=new user(responce[0].author); + fetch(this.info.api.toString()+"/v9/channels/"+this.message_reference.channel_id+"/messages?limit=1&around="+this.message_reference.message_id,{headers:this.headers}).then(responce=>responce.json()).then(responce=>{ + const author=new User(responce[0].author,this.localuser); reply.appendChild(markdown(responce[0].content)); minipfp.src=author.getpfpsrc() - profileclick(minipfp,author) + author.profileclick(minipfp) username.textContent=author.username; - profileclick(username,author) + author.profileclick(username) }); div.appendChild(replyline); } this.messageevents(div); - messagelist.push(div) build.classList.add("message"); div.appendChild(build); if({0:true,19:true}[this.type]||this.attachments.length!==0){ @@ -145,13 +169,13 @@ class cmessage{ const newt=(new Date(this.timestamp).getTime())/1000; current=(newt-old)>600; } - const combine=(premessage?.userid!=this.author.id&premessage?.author?.id!=this.author.id)||(current)||this.message_reference + const combine=(premessage?.userid!=this.author.id&&premessage?.author?.id!=this.author.id)||(current)||this.message_reference if(combine){ const pfp=this.author.buildpfp(); - profileclick(pfp,this.author); + this.author.profileclick(pfp); pfpRow.appendChild(pfp); }else{ - div.pfpparent=pfpparent; + div["pfpparent"]=pfpparent; } pfpRow.classList.add("pfprow") build.appendChild(pfpRow); @@ -163,8 +187,9 @@ class cmessage{ if(combine){ const username=document.createElement("span"); username.classList.add("username") - profileclick(username,this.author); - member.resolve(this.author,this.guild).then(_=>{ + this.author.profileclick(username); + Member.resolve(this.author,this.guild).then(_=>{ + if(!_){return} username.style.color=_.getColor(); }) username.textContent=this.author.username; @@ -184,28 +209,29 @@ class cmessage{ texttxt.appendChild(userwrap) } const messaged=markdown(this.content); - div.txt=messaged; + div["txt"]=messaged; const messagedwrap=document.createElement("tr") messagedwrap.appendChild(messaged) texttxt.appendChild(messagedwrap) build.appendChild(text) if(this.attachments.length){ + console.log(this.attachments) const attatch = document.createElement("tr") for(const thing of this.attachments){ const array=thing.url.split("/");array.shift();array.shift();array.shift(); - const src=info.cdn.toString()+array.join("/"); + const src=this.info.cdn.toString()+array.join("/"); if(thing.content_type.startsWith('image/')){ const img=document.createElement("img"); img.classList.add("messageimg") img.onclick=function(){ - const full=new fullscreen(["img",img.src,["fit"]]); + const full=new Fullscreen(["img",img.src,["fit"]]); full.show(); } img.src=src; attatch.appendChild(img) }else{ - attatch.appendChild(createunknown(thing.filename,thing.size,src)) + attatch.appendChild(this.createunknown(thing.filename,thing.size,src)) } } @@ -228,7 +254,7 @@ class cmessage{ build.appendChild(text) const messaged=document.createElement("p"); - div.txt=messaged; + div["txt"]=messaged; messaged.textContent="welcome: "+this.author.username; const messagedwrap=document.createElement("tr") messagedwrap.appendChild(messaged); @@ -240,10 +266,44 @@ class cmessage{ texttxt.appendChild(messagedwrap) } - div.userid=this.author.id; - div.all=this; + div["userid"]=this.author.id; + div["all"]=this; return(div) } + createunknown(fname,fsize,src){ + const div=document.createElement("table"); + div.classList.add("unknownfile"); + const nametr=document.createElement("tr"); + div.append(nametr); + const fileicon=document.createElement("td"); + nametr.append(fileicon); + fileicon.append("🗎"); + fileicon.classList.add("fileicon"); + fileicon.rowSpan=2; + const nametd=document.createElement("td"); + if(src){ + const a=document.createElement("a"); + a.href=src; + a.textContent=fname; + nametd.append(a); + }else{ + nametd.textContent=fname; + } + + nametd.classList.add("filename"); + nametr.append(nametd); + const sizetr=document.createElement("tr"); + const size=document.createElement("td"); + sizetr.append(size); + size.textContent="Size:"+this.filesizehuman(fsize); + size.classList.add("filesize"); + div.appendChild(sizetr) + return div; + } + filesizehuman(fsize){ + var i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1024)); + return +((fsize / Math.pow(1024, i)).toFixed(2)) * 1 + ' ' + ['Bytes', 'Kilobytes', 'Megabytes', 'Gigabytes', 'Terabytes'][i]; + } } function formatTime(date) { @@ -268,4 +328,5 @@ function formatTime(date) { return `${date.toLocaleDateString()} at ${formatTime(date)}`; } } -cmessage.setupcmenu(); +Message.setupcmenu(); +export { Message }; diff --git a/webpage/permissions.js b/webpage/permissions.ts similarity index 87% rename from webpage/permissions.js rename to webpage/permissions.ts index d844fce..9144527 100644 --- a/webpage/permissions.js +++ b/webpage/permissions.ts @@ -1,18 +1,24 @@ -class permissions{ - constructor(b){ - this.permissions=BigInt(b); +export {Permissions}; +class Permissions{ + allow:bigint; + deny:bigint; + constructor(allow:string,deny:string=""){ + this.allow=BigInt(allow); + this.deny=BigInt(deny); } - getPermisionbit(b){ - return Boolean((this.permissions>>BigInt(b))&1n); + getPermisionbit(b:number,big:bigint) : boolean{ + return Boolean((big>>BigInt(b))&1n); } - setPermisionbit(b,state){ + setPermisionbit(b:number,state:boolean,big:bigint) : bigint{ const bit=1n<{ - text=await data.clone().text(); + const text=await data.clone().text(); console.log(text,lastcache) if(lastcache!==text){ deleteoldcache(); @@ -80,8 +80,8 @@ async function getfile(event){ return responseFromNetwork; }catch(e){console.error(e)} } -self.addEventListener('fetch', (event) => { +self.addEventListener('fetch', (event:any) => { try{ - event.respondWith(getfile(event)); + event.respondWith(getfile(event)); }catch(e){console.error(e)} }) diff --git a/webpage/user.js b/webpage/user.js deleted file mode 100644 index 0d45200..0000000 --- a/webpage/user.js +++ /dev/null @@ -1,61 +0,0 @@ -//const usercache={}; -class user{ - static userids={}; - static checkuser(userjson){ - if(user.userids[userjson.id]){ - return user.userids[userjson.id]; - }else{ - const tempuser=new user(userjson,true) - user.userids[userjson.id]=tempuser; - return tempuser; - } - } - constructor(userjson,dontclone=false){ - if(dontclone){ - for(const thing of Object.keys(userjson)){ - this[thing]=userjson[thing]; - } - this.hypotheticalpfp=false; - }else{ - return user.checkuser(userjson); - } - } - async resolvemember(guild){ - await member.resolve(this,guild); - } - buildpfp(){ - const pfp=document.createElement('img'); - pfp.src=this.getpfpsrc(this.id,this.avatar); - pfp.classList.add("pfp"); - pfp.classList.add("userid:"+this.id); - return pfp; - } - userupdate(json){ - if(json.avatar!==this.avatar){ - console.log - this.changepfp(json.avatar); - } - } - changepfp(update){ - this.avatar=update; - this.hypotheticalpfp=false; - const src=this.getpfpsrc(); - console.log(src) - for(thing of document.getElementsByClassName("userid:"+this.id)){ - thing.src=src; - } - } - getpfpsrc(){ - if(this.hypotheticalpfp){ - return this.avatar; - } - if(this.avatar!=null){ - return info.cdn.toString()+"avatars/"+this.id+"/"+this.avatar+".png"; - }else{ - return info.cdn.toString()+"embed/avatars/3.png"; - } - } - createjankpromises(){ - new Promise(_=>{}) - } -} diff --git a/webpage/user.ts b/webpage/user.ts new file mode 100644 index 0000000..17ac002 --- /dev/null +++ b/webpage/user.ts @@ -0,0 +1,141 @@ +//const usercache={}; +import {Member} from "./member.js"; +import {markdown} from "./markdown.js"; +import {Contextmenu} from "./contextmenu.js"; +import {Localuser} from "./localuser.js"; +import {Guild} from "./guild.js"; +class User{ + static userids={}; + owner:Localuser; + hypotheticalpfp:boolean; + id:string; + avatar:string; + username:string; + bio:string; + discriminator:string; + pronouns:string; + bot:boolean; + static checkuser(userjson,owner:Localuser){ + if(User.userids[userjson.id]){ + return User.userids[userjson.id]; + }else{ + const tempuser=new User(userjson,owner,true) + User.userids[userjson.id]=tempuser; + return tempuser; + } + } + get info(){ + return this.owner.info; + } + get localuser(){ + return this.owner; + } + constructor(userjson,owner:Localuser,dontclone=false){ + this.owner=owner; + if(!owner){console.error("missing localuser")} + if(dontclone){ + for(const thing of Object.keys(userjson)){ + this[thing]=userjson[thing]; + } + this.hypotheticalpfp=false; + }else{ + return User.checkuser(userjson,owner); + } + } + async resolvemember(guild:Guild){ + await Member.resolve(this,guild); + } + buildpfp(){ + const pfp=document.createElement('img'); + pfp.src=this.getpfpsrc(); + pfp.classList.add("pfp"); + pfp.classList.add("userid:"+this.id); + return pfp; + } + userupdate(json){ + if(json.avatar!==this.avatar){ + console.log + this.changepfp(json.avatar); + } + } + changepfp(update:string){ + this.avatar=update; + this.hypotheticalpfp=false; + const src=this.getpfpsrc(); + console.log(src) + for(const thing of document.getElementsByClassName("userid:"+this.id)){ + (thing as HTMLImageElement).src=src; + } + } + getpfpsrc(){ + if(this.hypotheticalpfp){ + return this.avatar; + } + if(this.avatar!=null){ + return this.info.cdn.toString()+"avatars/"+this.id+"/"+this.avatar+".png"; + }else{ + return this.info.cdn.toString()+"embed/avatars/3.png"; + } + } + createjankpromises(){ + new Promise(_=>{}) + } + buildprofile(x:number,y:number){ + if(Contextmenu.currentmenu!=""){ + Contextmenu.currentmenu.remove(); + } + + + const div=document.createElement("table"); + if(x!==-1){ + div.style.left=x+"px"; + div.style.top=y+"px"; + div.classList.add("profile"); + }else{ + div.classList.add("hypoprofile"); + } + + { + const pfp=this.buildpfp(); + const pfprow=document.createElement("tr"); + div.appendChild(pfprow); + pfprow.appendChild(pfp); + } + { + const userbody=document.createElement("tr"); + userbody.classList.add("infosection"); + div.appendChild(userbody); + const usernamehtml=document.createElement("h2"); + usernamehtml.textContent=this.username; + userbody.appendChild(usernamehtml); + + const discrimatorhtml=document.createElement("h3"); + discrimatorhtml.classList.add("tag"); + discrimatorhtml.textContent=this.username+"#"+this.discriminator; + userbody.appendChild(discrimatorhtml) + + const pronounshtml=document.createElement("p"); + pronounshtml.textContent=this.pronouns; + pronounshtml.classList.add("pronouns"); + userbody.appendChild(pronounshtml) + + const rule=document.createElement("hr"); + userbody.appendChild(rule); + const biohtml=markdown(this.bio); + userbody.appendChild(biohtml); + } + console.log(div); + if(x!==-1){ + Contextmenu.currentmenu=div; + document.body.appendChild(div) + } + return div; + } + profileclick(obj:HTMLElement){ + obj.onclick=e=>{ + this.buildprofile(e.clientX,e.clientY); + e.stopPropagation(); + } + } +} +export {User}; From ac939e5fb6f256e8b549954f4673b11fa55407ee Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 28 Jun 2024 11:13:26 -0500 Subject: [PATCH 2/7] Correcting names, and deleting message There is a known regresion for the MESSAGE_CREATE event while it's not on screen, though I have not been able to replicate it while I'm looking for it. If you see this bug, please let me know the conditions it happens under --- .dist/channel.js | 78 ++++++++++++++++++++---------- .dist/contextmenu.js | 8 +++- .dist/direct.js | 18 +++++-- .dist/guild.js | 22 +++------ .dist/index.js | 3 +- .dist/localuser.js | 19 ++++---- .dist/member.js | 61 +++++++++++++++++++----- .dist/message.js | 96 +++++++++++++++++++++++++++++++++---- webpage/channel.ts | 76 ++++++++++++++++++----------- webpage/contextmenu.ts | 9 +++- webpage/direct.ts | 19 ++++++-- webpage/guild.ts | 25 +++------- webpage/index.ts | 3 +- webpage/localuser.ts | 24 ++++++---- webpage/member.ts | 55 +++++++++++++++++---- webpage/message.ts | 106 ++++++++++++++++++++++++++++++++++------- webpage/style.css | 17 ++++++- 17 files changed, 473 insertions(+), 166 deletions(-) diff --git a/.dist/channel.js b/.dist/channel.js index 1d65758..413c829 100644 --- a/.dist/channel.js +++ b/.dist/channel.js @@ -31,6 +31,7 @@ class Channel { message_notifications; allthewayup; static contextmenu = new Contextmenu("channel menu"); + replyingto; static setupcontextmenu() { Channel.contextmenu.addbutton("Copy channel id", function () { console.log(this); @@ -68,7 +69,6 @@ class Channel { for (const thing of JSON.permission_overwrites) { this.permission_overwrites[thing.id] = new Permissions(thing.allow, thing.deny); } - console.log(this.permission_overwrites); this.topic = JSON.topic; this.nsfw = JSON.nsfw; this.position = JSON.position; @@ -94,20 +94,30 @@ class Channel { this.lastpin = json.last_pin_timestamp; } get hasunreads() { + if (!this.hasPermission("VIEW_CHANNEL")) { + return false; + } return this.lastmessageid !== this.lastreadmessageid && this.type !== 4; } - get canMessage() { - console.log("this should run"); - for (const thing of Object.entries(this.permission_overwrites)) { - const perm = thing[1].getPermision("SEND_MESSAGES"); - if (perm === 1) { + hasPermission(name, member = this.guild.member) { + if (member.isAdmin()) { + return true; + } + for (const thing of member.roles) { + if (this.permission_overwrites[thing.id]) { + let perm = this.permission_overwrites[thing.id].getPermision(name); + if (perm) { + return perm === 1; + } + } + if (thing.permissions.getPermision(name)) { return true; } - if (perm === -1) { - return false; - } } - return true; + return false; + } + get canMessage() { + return this.hasPermission("SEND_MESSAGES"); } sortchildren() { this.children.sort((a, b) => { return a.position - b.position; }); @@ -144,6 +154,17 @@ class Channel { static dragged = []; createguildHTML(admin = false) { const div = document.createElement("div"); + if (!this.hasPermission("VIEW_CHANNEL")) { + let quit = true; + for (const thing of this.children) { + if (thing.hasPermission("VIEW_CHANNEL")) { + quit = false; + } + } + if (quit) { + return div; + } + } div["all"] = this; div.draggable = admin; div.addEventListener("dragstart", (e) => { Channel.dragged = [this, div]; e.stopImmediatePropagation(); }); @@ -383,34 +404,38 @@ class Channel { headers: this.headers }); } - getHTML() { + async getHTML() { if (this.guild !== this.localuser.lookingguild) { this.guild.loadGuild(); } this.guild.prevchannel = this; this.localuser.channelfocus = this; - this.putmessages(); + const prom = Message.wipeChanel(); + await this.putmessages(); + await prom; + this.buildmessages(); history.pushState(null, null, "/channels/" + this.guild_id + "/" + this.id); document.getElementById("channelname").textContent = "#" + this.name; console.log(this); document.getElementById("typebox").disabled = !this.canMessage; } - putmessages() { - const out = this; - fetch(this.info.api.toString() + "/channels/" + this.id + "/messages?limit=100", { + async putmessages() { + if (this.messages.length >= 100) { + return; + } + ; + const j = await fetch(this.info.api.toString() + "/channels/" + this.id + "/messages?limit=100", { method: 'GET', headers: this.headers, - }).then((j) => { return j.json(); }).then(responce => { - document.getElementById("messages").innerHTML = ''; - for (const thing of responce) { - const messager = new Message(thing, this); - if (out.messageids[messager.id] == undefined) { - out.messageids[messager.id] = messager; - out.messages.push(messager); - } - } - out.buildmessages(); }); + const responce = await j.json(); + for (const thing of responce) { + const messager = new Message(thing, this); + if (this.messageids[messager.id] === undefined) { + this.messageids[messager.id] = messager; + this.messages.push(messager); + } + } } delChannel(JSON) { const build = []; @@ -565,6 +590,9 @@ class Channel { } } messageCreate(messagep) { + if (!this.hasPermission("VIEW_CHANNEL")) { + return; + } const messagez = new Message(messagep.d, this); this.lastmessageid = messagez.id; if (messagez.author === this.localuser.user) { diff --git a/.dist/contextmenu.js b/.dist/contextmenu.js index dc7d998..4310b4e 100644 --- a/.dist/contextmenu.js +++ b/.dist/contextmenu.js @@ -52,11 +52,15 @@ class Contextmenu { return this.div; } bind(obj, addinfo = undefined) { - obj.addEventListener("contextmenu", (event) => { + const func = (event) => { event.preventDefault(); event.stopImmediatePropagation(); this.makemenu(event.clientX, event.clientY, addinfo, obj); - }); + }; + obj.addEventListener("contextmenu", func); + return func; + } + static keepOnScreen(obj) { } } Contextmenu.setup(); diff --git a/.dist/direct.js b/.dist/direct.js index 9fcf9f6..bfa5b6e 100644 --- a/.dist/direct.js +++ b/.dist/direct.js @@ -4,7 +4,7 @@ import { Message } from "./message.js"; import { User } from "./user.js"; class Direct extends Guild { constructor(JSON, owner) { - super(-1, owner); + super(-1, owner, null); this.message_notifications = 0; console.log(JSON); this.owner = owner; @@ -97,10 +97,16 @@ class Group extends Channel { }; return div; } - getHTML() { + async getHTML() { + if (this.guild !== this.localuser.lookingguild) { + this.guild.loadGuild(); + } + const prom = Message.wipeChanel(); this.guild.prevchannel = this; this.localuser.channelfocus = this; - this.putmessages(); + await this.putmessages(); + await prom; + this.buildmessages(); history.pushState(null, null, "/channels/" + this.guild_id + "/" + this.id); document.getElementById("channelname").textContent = "@" + this.name; } @@ -186,5 +192,11 @@ class Group extends Channel { else { } } + isAdmin() { + return false; + } + hasPermission(name, member) { + return true; + } } export { Direct, Group }; diff --git a/.dist/guild.js b/.dist/guild.js index 161bd18..dc7b712 100644 --- a/.dist/guild.js +++ b/.dist/guild.js @@ -2,6 +2,7 @@ import { Channel } from "./channel.js"; import { Contextmenu } from "./contextmenu.js"; import { Role } from "./role.js"; import { Fullscreen } from "./fullscreen.js"; +import { Member } from "./member.js"; class Guild { owner; headers; @@ -52,15 +53,12 @@ class Guild { },null,_=>{return thisuser.isAdmin()}) */ } - constructor(JSON, owner) { + constructor(JSON, owner, member) { if (JSON === -1) { return; } this.owner = owner; this.headers = this.owner.headers; - if (!this.owner) { - console.error("localuser was not included, please fix"); - } this.channels = []; this.channelids = {}; this.id = JSON.id; @@ -74,6 +72,7 @@ class Guild { this.roles.push(roleh); this.roleids[roleh.id] = roleh; } + Member.resolve(member, this).then(_ => this.member = _); for (const thing of JSON.channels) { const temp = new Channel(thing, this); this.channels.push(temp); @@ -353,19 +352,10 @@ class Guild { body: JSON.stringify(build) }); } - fillMember(member) { - const realroles = []; - for (const thing of member.roles) { - realroles.push(this.getRole(thing)); - } - member.roles = realroles; - return member; - } - giveMember(member) { - this.fillMember(member); - this.member = member; - } getRole(ID) { + if (!this.roleids[ID]) { + console.error(`role id ${ID} does not exist`, this.roleids); + } return this.roleids[ID]; } hasRole(r) { diff --git a/.dist/index.js b/.dist/index.js index e20cb84..1b98373 100644 --- a/.dist/index.js +++ b/.dist/index.js @@ -128,11 +128,12 @@ async function enter(event) { channel.editing = null; } else { + replyingto = thisuser.channelfocus.replyingto; let replying = replyingto?.all; if (replyingto) { replyingto.classList.remove("replying"); } - replyingto = false; + thisuser.channelfocus.replyingto = null; channel.sendMessage(typebox.value, { attachments: images, replyingto: replying, diff --git a/.dist/localuser.js b/.dist/localuser.js index f708502..1973ac3 100644 --- a/.dist/localuser.js +++ b/.dist/localuser.js @@ -2,7 +2,6 @@ import { Guild } from "./guild.js"; import { Direct } from "./direct.js"; import { Voice } from "./audio.js"; import { User } from "./user.js"; -import { Member } from "./member.js"; import { markdown } from "./markdown.js"; import { Fullscreen } from "./fullscreen.js"; import { setTheme } from "./login.js"; @@ -48,8 +47,12 @@ class Localuser { this.channelfocus = null; this.lookingguild = null; this.guildhtml = {}; + const members = {}; + for (const thing of ready.d.merged_members) { + members[thing[0].guild_id] = thing[0]; + } for (const thing of ready.d.guilds) { - const temp = new Guild(thing, this); + const temp = new Guild(thing, this, members[thing.id]); this.guilds.push(temp); this.guildids[temp.id] = temp; } @@ -62,11 +65,6 @@ class Localuser { for (const thing of ready.d.user_guild_settings.entries) { this.guildids[thing.guild_id].notisetting(thing); } - for (const thing of ready.d.merged_members) { - const guild = this.guildids[thing[0].guild_id]; - const temp = new Member(thing[0], guild); - guild.giveMember(temp); - } for (const thing of ready.d.read_state.entries) { const guild = this.resolveChannelFromID(thing.id).guild; if (guild === undefined) { @@ -130,6 +128,10 @@ class Localuser { this.messageCreate(temp); } break; + case "MESSAGE_DELETE": + console.log(temp.d); + this.guildids[temp.d.guild_id].channelids[temp.d.channel_id].messageids[temp.d.id].deleteEvent(); + break; case "READY": this.gottenReady(temp); this.genusersettings(); @@ -193,7 +195,7 @@ class Localuser { } case "GUILD_CREATE": { - const guildy = new Guild(temp.d, this); + const guildy = new Guild(temp.d, this, this.user); this.guilds.push(guildy); this.guildids[guildy.id] = guildy; document.getElementById("servers").insertBefore(guildy.generateGuildIcon(), document.getElementById("bottomseperator")); @@ -236,7 +238,6 @@ class Localuser { return; } resolveChannelFromID(ID) { - console.log(this.guilds.find(guild => guild.channelids[ID]).channelids); let resolve = this.guilds.find(guild => guild.channelids[ID]).channelids[ID]; resolve ??= undefined; return resolve; diff --git a/.dist/member.js b/.dist/member.js index bd5c19a..74a9c69 100644 --- a/.dist/member.js +++ b/.dist/member.js @@ -1,26 +1,44 @@ import { User } from "./user.js"; +import { Guild } from "./guild.js"; class Member { static already = {}; owner; user; roles; - constructor(memberjson, owner) { - if (!owner) { - console.error("Guild not included in the creation of a member object"); - } + error; + constructor(memberjson, owner, error = false) { + this.error = error; this.owner = owner; let membery = memberjson; - if (memberjson.guild_member) { - membery = memberjson.guild_member; - this.user = memberjson.user; + this.roles = []; + if (!error) { + if (memberjson.guild_member) { + membery = memberjson.guild_member; + this.user = memberjson.user; + } } for (const thing of Object.keys(membery)) { if (thing === "guild") { continue; } + if (thing === "owner") { + continue; + } + if (thing === "roles") { + for (const strrole of membery["roles"]) { + const role = this.guild.getRole(strrole); + this.roles.push(role); + } + continue; + } this[thing] = membery[thing]; } - this.user = new User(this.user, owner.localuser); + if (error) { + this.user = memberjson; + } + else { + this.user = new User(this.user, owner.localuser); + } } get guild() { return this.owner; @@ -31,7 +49,17 @@ class Member { get info() { return this.owner.info; } - static async resolve(user, guild) { + static async resolve(unkown, guild) { + if (!(guild instanceof Guild)) { + console.error(guild); + } + let user; + if (unkown instanceof User) { + user = unkown; + } + else { + return new Member(unkown, guild); + } if (guild.id === "@me") { return null; } @@ -48,12 +76,18 @@ class Member { const promoise = fetch(guild.info.api.toString() + "/v9/users/" + user.id + "/profile?with_mutual_guilds=true&with_mutual_friends_count=true&guild_id=" + guild.id, { headers: guild.headers }).then(_ => _.json()).then(json => { const memb = new Member(json, guild); Member.already[guild.id][user.id] = memb; - guild.fillMember(memb); console.log("resolved"); return memb; }); Member.already[guild.id][user.id] = promoise; - return await promoise; + try { + return await promoise; + } + catch (_) { + const memb = new Member(user, guild, true); + Member.already[guild.id][user.id] = memb; + return memb; + } } hasRole(ID) { console.log(this.roles, ID); @@ -74,6 +108,11 @@ class Member { return ""; } isAdmin() { + for (const role of this.roles) { + if (role.permissions.getPermision("ADMINISTRATOR")) { + return true; + } + } return this.guild.properties.owner_id === this.user.id; } } diff --git a/.dist/message.js b/.dist/message.js index a62498a..f7560d7 100644 --- a/.dist/message.js +++ b/.dist/message.js @@ -12,12 +12,25 @@ class Message { author; mentions; mention_roles; - attachments; + attachments; //probably should be its own class tbh, should be Attachments[] id; message_reference; type; timestamp; content; + static del; + static resolve; + div; + static setup() { + this.del = new Promise(_ => { this.resolve = _; }); + Message.setupcmenu(); + } + static async wipeChanel() { + this.resolve(); + document.getElementById("messages").innerHTML = ""; + await Promise.allSettled([this.resolve]); + this.del = new Promise(_ => { this.resolve = _; }); + } static setupcmenu() { Message.contextmenu.addbutton("Copy raw text", function () { navigator.clipboard.writeText(this.content); @@ -46,6 +59,9 @@ class Message { this.channel.editing = this; document.getElementById("typebox").value = this.content; }, null, _ => { return _.author.id === _.localuser.user.id; }); + Message.contextmenu.addbutton("Delete message", function () { + this.delete(); + }, null, _ => { return _.canDelete(); }); } constructor(messagejson, owner) { this.owner = owner; @@ -68,6 +84,9 @@ class Message { console.log(this); } } + canDelete() { + return this.channel.hasPermission("MANAGE_MESSAGES") || this.author.id === this.localuser.user.id; + } get channel() { return this.owner; } @@ -81,7 +100,12 @@ class Message { return this.owner.info; } messageevents(obj) { - Message.contextmenu.bind(obj, this); + const func = Message.contextmenu.bind(obj, this); + this.div = obj; + Message.del.then(_ => { + obj.removeEventListener("click", func); + this.div = null; + }); obj.classList.add("messagediv"); } mentionsuser(userd) { @@ -108,10 +132,32 @@ class Message { body: JSON.stringify({ content: content }) }); } - buildhtml(premessage) { - //premessage??=messages.lastChild; + delete() { + fetch(`${this.info.api.toString()}/channels/${this.channel.id}/messages/${this.id}`, { + headers: this.headers, + method: "DELETE", + }); + } + deleteEvent() { + if (this.div) { + this.div.innerHTML = ""; + this.div = null; + } + const index = this.channel.messages.indexOf(this); + this.channel.messages.splice(this.channel.messages.indexOf(this), 1); + delete this.channel.messageids[this.id]; + const regen = this.channel.messages[index - 1]; + if (regen) { + regen.generateMessage(); + } + } + generateMessage(premessage = null) { + if (!premessage) { + premessage = this.channel.messages[this.channel.messages.indexOf(this) + 1]; + } + const div = this.div; + div.innerHTML = ""; const build = document.createElement('table'); - const div = document.createElement("div"); if (this.message_reference) { const replyline = document.createElement("div"); const line = document.createElement("hr"); @@ -124,7 +170,23 @@ class Message { const reply = document.createElement("div"); username.classList.add("username"); Member.resolve(this.author, this.guild).then(_ => { + if (!_) { + return; + } + ; + console.log(_.error); + if (_.error) { + username.textContent += "Error"; + alert("Should've gotten here"); + const error = document.createElement("span"); + error.textContent = "!"; + error.classList.add("membererror"); + username.after(error); + return; + } username.style.color = _.getColor(); + }).catch(_ => { + console.log(_); }); reply.classList.add("replytext"); replyline.appendChild(reply); @@ -150,7 +212,6 @@ class Message { const pfpRow = document.createElement('th'); let pfpparent, current; if (premessage != null) { - pfpparent = premessage.pfpparent; pfpparent ??= premessage; let pfpparent2 = pfpparent.all; pfpparent2 ??= pfpparent; @@ -158,7 +219,7 @@ class Message { const newt = (new Date(this.timestamp).getTime()) / 1000; current = (newt - old) > 600; } - const combine = (premessage?.userid != this.author.id && premessage?.author?.id != this.author.id) || (current) || this.message_reference; + const combine = (premessage?.author?.id != this.author.id) || (current) || this.message_reference; if (combine) { const pfp = this.author.buildpfp(); this.author.profileclick(pfp); @@ -181,6 +242,14 @@ class Message { if (!_) { return; } + ; + if (_.error) { + const error = document.createElement("span"); + error.textContent = "!"; + error.classList.add("membererror"); + username.after(error); + return; + } username.style.color = _.getColor(); }); username.textContent = this.author.username; @@ -254,10 +323,19 @@ class Message { messagedwrap.append(time); texttxt.appendChild(messagedwrap); } - div["userid"] = this.author.id; div["all"] = this; return (div); } + buildhtml(premessage) { + if (this.div) { + console.error(`HTML for ${this} already exists, aborting`); + return; + } + //premessage??=messages.lastChild; + const div = document.createElement("div"); + this.div = div; + return this.generateMessage(premessage); + } createunknown(fname, fsize, src) { const div = document.createElement("table"); div.classList.add("unknownfile"); @@ -314,5 +392,5 @@ function formatTime(date) { return `${date.toLocaleDateString()} at ${formatTime(date)}`; } } -Message.setupcmenu(); +Message.setup(); export { Message }; diff --git a/webpage/channel.ts b/webpage/channel.ts index 848c0cd..86e742b 100644 --- a/webpage/channel.ts +++ b/webpage/channel.ts @@ -7,6 +7,7 @@ import {markdown} from "./markdown.js"; import {Guild} from "./guild.js"; import { Localuser } from "./localuser.js"; import { Permissions } from "./permissions.js"; + declare global { interface NotificationOptions { image?: string @@ -38,6 +39,7 @@ class Channel{ message_notifications:number; allthewayup:boolean; static contextmenu=new Contextmenu("channel menu"); + replyingto:HTMLDivElement; static setupcontextmenu(){ Channel.contextmenu.addbutton("Copy channel id",function(){ console.log(this) @@ -79,7 +81,6 @@ class Channel{ for(const thing of JSON.permission_overwrites){ this.permission_overwrites[thing.id]=new Permissions(thing.allow,thing.deny); } - console.log(this.permission_overwrites) this.topic=JSON.topic; this.nsfw=JSON.nsfw; this.position=JSON.position; @@ -104,21 +105,29 @@ class Channel{ this.mentions??=0; this.lastpin=json.last_pin_timestamp; } - get hasunreads(){ + get hasunreads():boolean{ + if(!this.hasPermission("VIEW_CHANNEL")){return false;} return this.lastmessageid!==this.lastreadmessageid&&this.type!==4; } - get canMessage(){ - console.log("this should run"); - for(const thing of Object.entries(this.permission_overwrites)){ - const perm=thing[1].getPermision("SEND_MESSAGES"); - if(perm===1){ - return true + hasPermission(name:string,member=this.guild.member):boolean{ + if(member.isAdmin()){ + return true; + } + for(const thing of member.roles){ + if(this.permission_overwrites[thing.id]){ + let perm=this.permission_overwrites[thing.id].getPermision(name); + if(perm){ + return perm===1; + } } - if(perm===-1){ - return false; + if(thing.permissions.getPermision(name)){ + return true; } } - return true; + return false; + } + get canMessage():boolean{ + return this.hasPermission("SEND_MESSAGES"); } sortchildren(){ this.children.sort((a,b)=>{return a.position-b.position}); @@ -153,8 +162,19 @@ class Channel{ return build; } static dragged=[]; - createguildHTML(admin=false){ + createguildHTML(admin=false):HTMLDivElement{ const div=document.createElement("div"); + if(!this.hasPermission("VIEW_CHANNEL")){ + let quit=true + for(const thing of this.children){ + if(thing.hasPermission("VIEW_CHANNEL")){ + quit=false; + } + } + if(quit){ + return div; + } + } div["all"]=this; div.draggable=admin; div.addEventListener("dragstart",(e)=>{Channel.dragged=[this,div];e.stopImmediatePropagation()}) @@ -392,34 +412,35 @@ class Channel{ headers:this.headers }) } - getHTML(){ + async getHTML(){ if(this.guild!==this.localuser.lookingguild){ this.guild.loadGuild(); } this.guild.prevchannel=this; this.localuser.channelfocus=this; - this.putmessages(); + const prom=Message.wipeChanel(); + await this.putmessages(); + await prom; + this.buildmessages(); history.pushState(null, null,"/channels/"+this.guild_id+"/"+this.id); document.getElementById("channelname").textContent="#"+this.name; console.log(this); (document.getElementById("typebox") as HTMLInputElement).disabled=!this.canMessage; } - putmessages(){ - const out=this; - fetch(this.info.api.toString()+"/channels/"+this.id+"/messages?limit=100",{ + async putmessages(){ + if(this.messages.length>=100){return}; + const j=await fetch(this.info.api.toString()+"/channels/"+this.id+"/messages?limit=100",{ method: 'GET', headers: this.headers, - }).then((j)=>{return j.json()}).then(responce=>{ - document.getElementById("messages").innerHTML = ''; - for(const thing of responce){ - const messager=new Message(thing,this) - if(out.messageids[messager.id]==undefined){ - out.messageids[messager.id]=messager; - out.messages.push(messager); - } - } - out.buildmessages(); }) + const responce=await j.json(); + for(const thing of responce){ + const messager=new Message(thing,this) + if(this.messageids[messager.id]===undefined){ + this.messageids[messager.id]=messager; + this.messages.push(messager); + } + } } delChannel(JSON){ const build=[]; @@ -569,6 +590,7 @@ class Channel{ } } messageCreate(messagep:any):void{ + if(!this.hasPermission("VIEW_CHANNEL")){return} const messagez=new Message(messagep.d,this); this.lastmessageid=messagez.id; if(messagez.author===this.localuser.user){ diff --git a/webpage/contextmenu.ts b/webpage/contextmenu.ts index 279549b..e4ece80 100644 --- a/webpage/contextmenu.ts +++ b/webpage/contextmenu.ts @@ -50,11 +50,16 @@ class Contextmenu{ return this.div; } bind(obj:HTMLElement,addinfo:any=undefined){ - obj.addEventListener("contextmenu", (event) => { + const func=(event) => { event.preventDefault(); event.stopImmediatePropagation(); this.makemenu(event.clientX,event.clientY,addinfo,obj) - }); + } + obj.addEventListener("contextmenu", func); + return func; + } + static keepOnScreen(obj:HTMLElement){ + } } Contextmenu.setup(); diff --git a/webpage/direct.ts b/webpage/direct.ts index f4a5743..98f17b9 100644 --- a/webpage/direct.ts +++ b/webpage/direct.ts @@ -3,10 +3,11 @@ import { Channel } from "./channel.js"; import { Message } from "./message.js"; import { Localuser } from "./localuser.js"; import {User} from "./user.js"; +import { Member } from "./member.js"; class Direct extends Guild{ constructor(JSON,owner:Localuser){ - super(-1,owner); + super(-1,owner,null); this.message_notifications=0; console.log(JSON); this.owner=owner; @@ -98,10 +99,16 @@ class Group extends Channel{ } return div; } - getHTML(){ + async getHTML(){ + if(this.guild!==this.localuser.lookingguild){ + this.guild.loadGuild(); + } + const prom=Message.wipeChanel(); this.guild.prevchannel=this; this.localuser.channelfocus=this; - this.putmessages(); + await this.putmessages(); + await prom; + this.buildmessages(); history.pushState(null, null,"/channels/"+this.guild_id+"/"+this.id); document.getElementById("channelname").textContent="@"+this.name; } @@ -183,5 +190,11 @@ class Group extends Channel{ } } + isAdmin(): boolean { + return false; + } + hasPermission(name: string, member?: Member): boolean { + return true; + } } export {Direct, Group}; diff --git a/webpage/guild.ts b/webpage/guild.ts index ad6650f..ff92329 100644 --- a/webpage/guild.ts +++ b/webpage/guild.ts @@ -15,7 +15,7 @@ class Guild{ roles:Role[]; roleids:{[key:string]:Role}; prevchannel:Channel; - message_notifications + message_notifications:number; headchannels:Channel[]; position:number; parent_id:string; @@ -60,16 +60,13 @@ class Guild{ },null,_=>{return thisuser.isAdmin()}) */ } - constructor(JSON,owner:Localuser){ + constructor(JSON,owner:Localuser,member){ if(JSON===-1){ return; } this.owner=owner; this.headers=this.owner.headers; - if(!this.owner){ - console.error("localuser was not included, please fix") - } this.channels=[]; this.channelids={}; this.id=JSON.id; @@ -83,6 +80,7 @@ class Guild{ this.roles.push(roleh) this.roleids[roleh.id]=roleh; } + Member.resolve(member,this).then(_=>this.member=_); for(const thing of JSON.channels){ const temp=new Channel(thing,this); this.channels.push(temp); @@ -360,19 +358,8 @@ class Guild{ body:JSON.stringify(build) }) } - fillMember(member:Member){ - const realroles=[]; - for(const thing of member.roles){ - realroles.push(this.getRole(thing)); - } - member.roles=realroles; - return member; - } - giveMember(member:Member){ - this.fillMember(member); - this.member=member; - } - getRole(ID){ + getRole(ID:string):Role{ + if(!this.roleids[ID]){console.error(`role id ${ID} does not exist`,this.roleids)} return this.roleids[ID]; } hasRole(r:Role|string){ @@ -382,7 +369,7 @@ class Guild{ } return this.member.hasRole(r as string); } - loadChannel(ID=undefined){ + loadChannel(ID:string=undefined){ if(ID&&this.channelids[ID]){ this.channelids[ID].getHTML(); return; diff --git a/webpage/index.ts b/webpage/index.ts index b04a756..996392d 100644 --- a/webpage/index.ts +++ b/webpage/index.ts @@ -142,11 +142,12 @@ async function enter(event){ channel.editing.edit((typebox).value); channel.editing=null; }else{ + replyingto= thisuser.channelfocus.replyingto; let replying=replyingto?.all; if(replyingto){ replyingto.classList.remove("replying"); } - replyingto=false; + thisuser.channelfocus.replyingto=null; channel.sendMessage(typebox.value,{ attachments:images, replyingto:replying, diff --git a/webpage/localuser.ts b/webpage/localuser.ts index 9c7cc8a..83983df 100644 --- a/webpage/localuser.ts +++ b/webpage/localuser.ts @@ -49,8 +49,13 @@ class Localuser{ this.channelfocus=null; this.lookingguild=null; this.guildhtml={}; + const members={}; + for(const thing of ready.d.merged_members){ + members[thing[0].guild_id]=thing[0]; + } + for(const thing of ready.d.guilds){ - const temp=new Guild(thing,this); + const temp=new Guild(thing,this,members[thing.id]); this.guilds.push(temp); this.guildids[temp.id]=temp; } @@ -59,15 +64,13 @@ class Localuser{ this.guilds.push(temp); this.guildids[temp.id]=temp; } - console.log(ready.d.user_guild_settings.entries) + console.log(ready.d.user_guild_settings.entries); + + for(const thing of ready.d.user_guild_settings.entries){ this.guildids[thing.guild_id].notisetting(thing); } - for(const thing of ready.d.merged_members){ - const guild=this.guildids[thing[0].guild_id] - const temp=new Member(thing[0],guild); - guild.giveMember(temp); - } + for(const thing of ready.d.read_state.entries){ const guild=this.resolveChannelFromID(thing.id).guild; if(guild===undefined){ @@ -134,6 +137,10 @@ class Localuser{ this.messageCreate(temp); } break; + case "MESSAGE_DELETE": + console.log(temp.d); + this.guildids[temp.d.guild_id].channelids[temp.d.channel_id].messageids[temp.d.id].deleteEvent(); + break; case "READY": this.gottenReady(temp); this.genusersettings(); @@ -196,7 +203,7 @@ class Localuser{ } case "GUILD_CREATE": { - const guildy=new Guild(temp.d,this); + const guildy=new Guild(temp.d,this,this.user); this.guilds.push(guildy); this.guildids[guildy.id]=guildy; document.getElementById("servers").insertBefore(guildy.generateGuildIcon(),document.getElementById("bottomseperator")); @@ -239,7 +246,6 @@ class Localuser{ return; } resolveChannelFromID(ID:string):Channel{ - console.log(this.guilds.find(guild => guild.channelids[ID]).channelids) let resolve=this.guilds.find(guild => guild.channelids[ID]).channelids[ID]; resolve??=undefined; return resolve; diff --git a/webpage/member.ts b/webpage/member.ts index c1c4cb7..6b5a3c4 100644 --- a/webpage/member.ts +++ b/webpage/member.ts @@ -6,19 +6,35 @@ class Member{ owner:Guild; user:User; roles:Role[]; - constructor(memberjson,owner:Guild){ - if(!owner){console.error("Guild not included in the creation of a member object")} + error:boolean; + constructor(memberjson,owner:Guild,error=false){ + this.error=error; this.owner=owner; let membery=memberjson; - if(memberjson.guild_member){ - membery=memberjson.guild_member; - this.user=memberjson.user; + this.roles=[]; + if(!error){ + if(memberjson.guild_member){ + membery=memberjson.guild_member; + this.user=memberjson.user; + } } for(const thing of Object.keys(membery)){ if(thing==="guild"){continue} + if(thing==="owner"){continue} + if(thing==="roles"){ + for(const strrole of membery["roles"]){ + const role=this.guild.getRole(strrole); + this.roles.push(role); + } + continue; + } this[thing]=membery[thing]; } - this.user=new User(this.user,owner.localuser); + if(error){ + this.user=memberjson as User; + }else{ + this.user=new User(this.user,owner.localuser); + } } get guild(){ return this.owner; @@ -29,7 +45,16 @@ class Member{ get info(){ return this.owner.info; } - static async resolve(user:User,guild:Guild){ + static async resolve(unkown:User|object,guild:Guild):Promise{ + if(!(guild instanceof Guild)){ + console.error(guild) + } + let user:User; + if(unkown instanceof User){ + user=unkown as User; + }else{ + return new Member(unkown,guild); + } if(guild.id==="@me"){return null} if(!Member.already[guild.id]){ Member.already[guild.id]={}; @@ -43,12 +68,17 @@ class Member{ const promoise= fetch(guild.info.api.toString()+"/v9/users/"+user.id+"/profile?with_mutual_guilds=true&with_mutual_friends_count=true&guild_id="+guild.id,{headers:guild.headers}).then(_=>_.json()).then(json=>{ const memb=new Member(json,guild); Member.already[guild.id][user.id]=memb; - guild.fillMember(memb); console.log("resolved") return memb - }); + }) Member.already[guild.id][user.id]=promoise; - return await promoise; + try{ + return await promoise + }catch(_){ + const memb=new Member(user,guild,true); + Member.already[guild.id][user.id]=memb; + return memb; + } } hasRole(ID:string){ console.log(this.roles,ID); @@ -69,6 +99,11 @@ class Member{ return ""; } isAdmin(){ + for(const role of this.roles){ + if(role.permissions.getPermision("ADMINISTRATOR")){ + return true; + } + } return this.guild.properties.owner_id===this.user.id; } diff --git a/webpage/message.ts b/webpage/message.ts index aaa5e88..c4aab7b 100644 --- a/webpage/message.ts +++ b/webpage/message.ts @@ -16,12 +16,25 @@ class Message{ author:User; mentions:User[]; mention_roles:Role[]; - attachments; + attachments;//probably should be its own class tbh, should be Attachments[] id:string; message_reference; type:number; timestamp:number; - content; + content:string; + static del:Promise; + static resolve:Function; + div:HTMLDivElement; + static setup(){ + this.del=new Promise(_=>{this.resolve=_}); + Message.setupcmenu(); + } + static async wipeChanel(){ + this.resolve(); + document.getElementById("messages").innerHTML=""; + await Promise.allSettled([this.resolve]); + this.del=new Promise(_=>{this.resolve=_}) + } static setupcmenu(){ Message.contextmenu.addbutton("Copy raw text",function(){ navigator.clipboard.writeText(this.content); @@ -51,8 +64,11 @@ class Message{ this.channel.editing=this; (document.getElementById("typebox") as HTMLInputElement).value=this.content; },null,_=>{return _.author.id===_.localuser.user.id}); + Message.contextmenu.addbutton("Delete message",function(){ + this.delete(); + },null,_=>{return _.canDelete()}) } - constructor(messagejson,owner){ + constructor(messagejson,owner:Channel){ this.owner=owner; this.headers=this.owner.headers; for(const thing of Object.keys(messagejson)){ @@ -73,6 +89,9 @@ class Message{ console.log(this); } } + canDelete(){ + return this.channel.hasPermission("MANAGE_MESSAGES")||this.author.id===this.localuser.user.id; + } get channel(){ return this.owner; } @@ -85,11 +104,16 @@ class Message{ get info(){ return this.owner.info; } - messageevents(obj){ - Message.contextmenu.bind(obj,this) - obj.classList.add("messagediv") + messageevents(obj:HTMLDivElement){ + const func=Message.contextmenu.bind(obj,this); + this.div=obj; + Message.del.then(_=>{ + obj.removeEventListener("click",func); + this.div=null; + }) + obj.classList.add("messagediv"); } - mentionsuser(userd){ + mentionsuser(userd:User|Member){ if(userd instanceof User){ return this.mentions.includes(userd); }else if(userd instanceof Member){ @@ -112,11 +136,32 @@ class Message{ body:JSON.stringify({content:content}) }); } - buildhtml(premessage){ - //premessage??=messages.lastChild; + delete(){ + fetch(`${this.info.api.toString()}/channels/${this.channel.id}/messages/${this.id}`,{ + headers:this.headers, + method:"DELETE", + }) + } + deleteEvent(){ + if(this.div){ + this.div.innerHTML=""; + this.div=null; + } + const index=this.channel.messages.indexOf(this); + this.channel.messages.splice(this.channel.messages.indexOf(this),1); + delete this.channel.messageids[this.id]; + const regen=this.channel.messages[index-1] + if(regen){ + regen.generateMessage(); + } + } + generateMessage(premessage:Message=null){ + if(!premessage){ + premessage=this.channel.messages[this.channel.messages.indexOf(this)+1]; + } + const div=this.div; + div.innerHTML=""; const build = document.createElement('table'); - const div=document.createElement("div"); - if(this.message_reference){ const replyline=document.createElement("div"); const line=document.createElement("hr"); @@ -130,7 +175,21 @@ class Message{ username.classList.add("username"); Member.resolve(this.author,this.guild).then(_=>{ + if(!_) {return}; + console.log(_.error); + if(_.error){ + username.textContent+="Error"; + alert("Should've gotten here") + const error=document.createElement("span"); + error.textContent="!"; + error.classList.add("membererror"); + username.after(error); + + return; + } username.style.color=_.getColor(); + }).catch(_=>{ + console.log(_) }); reply.classList.add("replytext"); @@ -161,7 +220,6 @@ class Message{ let pfpparent, current if(premessage!=null){ - pfpparent=premessage.pfpparent; pfpparent??=premessage; let pfpparent2=pfpparent.all; pfpparent2??=pfpparent; @@ -169,7 +227,7 @@ class Message{ const newt=(new Date(this.timestamp).getTime())/1000; current=(newt-old)>600; } - const combine=(premessage?.userid!=this.author.id&&premessage?.author?.id!=this.author.id)||(current)||this.message_reference + const combine=(premessage?.author?.id!=this.author.id)||(current)||this.message_reference if(combine){ const pfp=this.author.buildpfp(); this.author.profileclick(pfp); @@ -188,8 +246,16 @@ class Message{ const username=document.createElement("span"); username.classList.add("username") this.author.profileclick(username); - Member.resolve(this.author,this.guild).then(_=>{ - if(!_){return} + Member.resolve(this.author,this.guild).then(_=>{ + if(!_) {return}; + if(_.error){ + const error=document.createElement("span"); + error.textContent="!"; + error.classList.add("membererror"); + username.after(error); + + return; + } username.style.color=_.getColor(); }) username.textContent=this.author.username; @@ -266,10 +332,16 @@ class Message{ texttxt.appendChild(messagedwrap) } - div["userid"]=this.author.id; div["all"]=this; return(div) } + buildhtml(premessage:Message){ + if(this.div){console.error(`HTML for ${this} already exists, aborting`);return;} + //premessage??=messages.lastChild; + const div=document.createElement("div"); + this.div=div; + return this.generateMessage(premessage); + } createunknown(fname,fsize,src){ const div=document.createElement("table"); div.classList.add("unknownfile"); @@ -328,5 +400,5 @@ function formatTime(date) { return `${date.toLocaleDateString()} at ${formatTime(date)}`; } } -Message.setupcmenu(); +Message.setup(); export { Message }; diff --git a/webpage/style.css b/webpage/style.css index 45c7622..2d64f7b 100644 --- a/webpage/style.css +++ b/webpage/style.css @@ -138,7 +138,7 @@ h2 { .pfp { border-radius: 50%; - width: .5in; + width: 0.5in; height: .5in; user-select: none; cursor: pointer; @@ -599,9 +599,12 @@ textarea { #settings { cursor: pointer; user-select: none; - border-radius: .1in; + border-radius: .3in; transition: background 1s; text-align: center; + font-size: .25in; + width: .3in; + height: .3in; } #settings:hover { @@ -975,3 +978,13 @@ span { .spaceright{ margin-right:.1in; } +.membererror{ + display:inline-block; + background:#656500; + height:.15in; + width:.15in; + border-radius:.1in; + text-align:center; + border:solid black .03in; + margin-left:.025in; +} \ No newline at end of file From d01e3d57a28442289fcd333a1a31687f15a03353 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 28 Jun 2024 13:04:05 -0500 Subject: [PATCH 3/7] Force Menus to stay on screen --- .dist/contextmenu.js | 13 +++++++++++++ .dist/user.js | 1 + webpage/contextmenu.ts | 14 +++++++++++++- webpage/user.ts | 2 ++ 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/.dist/contextmenu.js b/.dist/contextmenu.js index 4310b4e..17afb51 100644 --- a/.dist/contextmenu.js +++ b/.dist/contextmenu.js @@ -47,6 +47,7 @@ class Contextmenu { div.style.top = y + 'px'; div.style.left = x + 'px'; document.body.appendChild(div); + Contextmenu.keepOnScreen(div); console.log(div); Contextmenu.currentmenu = div; return this.div; @@ -61,6 +62,18 @@ class Contextmenu { return func; } static keepOnScreen(obj) { + const html = document.documentElement.getBoundingClientRect(); + const docheight = html.height; + const docwidth = html.width; + const box = obj.getBoundingClientRect(); + console.log(box, docheight, docwidth); + if (box.right > docwidth) { + console.log("test"); + obj.style.left = docwidth - box.width + 'px'; + } + if (box.bottom > docheight) { + obj.style.top = docheight - box.height + 'px'; + } } } Contextmenu.setup(); diff --git a/.dist/user.js b/.dist/user.js index 71e78bd..5830f56 100644 --- a/.dist/user.js +++ b/.dist/user.js @@ -126,6 +126,7 @@ class User { if (x !== -1) { Contextmenu.currentmenu = div; document.body.appendChild(div); + Contextmenu.keepOnScreen(div); } return div; } diff --git a/webpage/contextmenu.ts b/webpage/contextmenu.ts index e4ece80..aaf7a3f 100644 --- a/webpage/contextmenu.ts +++ b/webpage/contextmenu.ts @@ -45,6 +45,7 @@ class Contextmenu{ div.style.top = y+'px'; div.style.left = x+'px'; document.body.appendChild(div); + Contextmenu.keepOnScreen(div); console.log(div) Contextmenu.currentmenu=div; return this.div; @@ -59,7 +60,18 @@ class Contextmenu{ return func; } static keepOnScreen(obj:HTMLElement){ - + const html = document.documentElement.getBoundingClientRect(); + const docheight=html.height + const docwidth=html.width + const box=obj.getBoundingClientRect(); + console.log(box,docheight,docwidth); + if(box.right>docwidth){ + console.log("test") + obj.style.left = docwidth-box.width+'px'; + } + if(box.bottom>docheight){ + obj.style.top = docheight-box.height+'px'; + } } } Contextmenu.setup(); diff --git a/webpage/user.ts b/webpage/user.ts index 17ac002..530b9c7 100644 --- a/webpage/user.ts +++ b/webpage/user.ts @@ -125,9 +125,11 @@ class User{ userbody.appendChild(biohtml); } console.log(div); + if(x!==-1){ Contextmenu.currentmenu=div; document.body.appendChild(div) + Contextmenu.keepOnScreen(div); } return div; } From f6bd7423d37458d2821bcc8138c0c760622ed303 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 28 Jun 2024 14:47:07 -0500 Subject: [PATCH 4/7] fixed replies multi-lined --- .dist/embed.js | 4 ++-- .dist/localuser.js | 2 +- .dist/markdown.js | 37 ++++++++++++++++++++++------------ .dist/message.js | 2 +- webpage/embed.ts | 4 ++-- webpage/localuser.ts | 2 +- webpage/markdown.ts | 36 +++++++++++++++++++++------------ webpage/message.ts | 2 +- webpage/style.css | 47 +++++++++++++++++++++++++++++++++++++++----- 9 files changed, 97 insertions(+), 39 deletions(-) diff --git a/.dist/embed.js b/.dist/embed.js index 67cb7ea..cb0e10e 100644 --- a/.dist/embed.js +++ b/.dist/embed.js @@ -178,14 +178,14 @@ class Embed { const description = document.createElement("p"); description.textContent = this.json.description; div.append(description); - { + if (this.json.thumbnail) { const img = document.createElement("img"); img.classList.add("bigembedimg"); img.onclick = function () { const full = new Fullscreen(["img", img.src, ["fit"]]); full.show(); }; - img.src = this.json.thumbnail.proxy_url; + img.src = this.json.thumbnail.proxy_url || this.json.thumbnail.url; div.append(img); } colordiv.append(div); diff --git a/.dist/localuser.js b/.dist/localuser.js index 1973ac3..5f854cb 100644 --- a/.dist/localuser.js +++ b/.dist/localuser.js @@ -221,7 +221,7 @@ class Localuser { clearInterval(this.wsinterval); console.log('WebSocket closed'); console.warn(event); - if (event.code !== 4000 && this === this) { + if (event.code !== 4000) { this.unload(); document.getElementById("loading").classList.remove("doneloading"); document.getElementById("loading").classList.add("loading"); diff --git a/.dist/markdown.js b/.dist/markdown.js index d756a7e..9cfa553 100644 --- a/.dist/markdown.js +++ b/.dist/markdown.js @@ -1,5 +1,5 @@ export { markdown }; -function markdown(text, keep = false) { +function markdown(text, { keep = false, stdsize = false } = {}) { let txt; if ((typeof txt) === "string") { txt = text.split(""); @@ -55,17 +55,20 @@ function markdown(text, keep = false) { } if (keepys) { appendcurrent(); - if (!first) { + if (!first && !stdsize) { span.appendChild(document.createElement("br")); } const build = []; for (; txt[i] !== "\n" && txt[i] !== undefined; i++) { build.push(txt[i]); } + if (stdsize) { + element = document.createElement("span"); + } if (keep) { element.append(keepys); } - element.appendChild(markdown(build, keep)); + element.appendChild(markdown(build, { keep: keep, stdsize: stdsize })); span.append(element); i--; continue; @@ -76,7 +79,15 @@ function markdown(text, keep = false) { } if (txt[i] === "\n") { appendcurrent(); - span.append(document.createElement("br")); + if (!stdsize) { + span.append(document.createElement("br")); + } + else { + const s = document.createElement("span"); + s.textContent = "..."; + span.append(s); + return span; + } continue; } if (txt[i] === "`") { @@ -121,7 +132,7 @@ function markdown(text, keep = false) { if (keep) { build += "`".repeat(find); } - if (count !== 3) { + if (count !== 3 && !stdsize) { const samp = document.createElement("samp"); samp.textContent = build; span.appendChild(samp); @@ -173,7 +184,7 @@ function markdown(text, keep = false) { if (keep) { i.append(stars); } - i.appendChild(markdown(build, keep)); + i.appendChild(markdown(build, { keep: keep, stdsize: stdsize })); if (keep) { i.append(stars); } @@ -184,7 +195,7 @@ function markdown(text, keep = false) { if (keep) { b.append(stars); } - b.appendChild(markdown(build, keep)); + b.appendChild(markdown(build, { keep: keep, stdsize: stdsize })); if (keep) { b.append(stars); } @@ -196,7 +207,7 @@ function markdown(text, keep = false) { if (keep) { b.append(stars); } - b.appendChild(markdown(build, keep)); + b.appendChild(markdown(build, { keep: keep, stdsize: stdsize })); if (keep) { b.append(stars); } @@ -239,7 +250,7 @@ function markdown(text, keep = false) { if (keep) { i.append(underscores); } - i.appendChild(markdown(build, keep)); + i.appendChild(markdown(build, { keep: keep, stdsize: stdsize })); if (keep) { i.append(underscores); } @@ -250,7 +261,7 @@ function markdown(text, keep = false) { if (keep) { u.append(underscores); } - u.appendChild(markdown(build, keep)); + u.appendChild(markdown(build, { keep: keep, stdsize: stdsize })); if (keep) { u.append(underscores); } @@ -262,7 +273,7 @@ function markdown(text, keep = false) { if (keep) { i.append(underscores); } - i.appendChild(markdown(build, keep)); + i.appendChild(markdown(build, { keep: keep, stdsize: stdsize })); if (keep) { i.append(underscores); } @@ -299,7 +310,7 @@ function markdown(text, keep = false) { if (keep) { s.append(underscores); } - s.appendChild(markdown(build, keep)); + s.appendChild(markdown(build, { keep: keep, stdsize: stdsize })); if (keep) { s.append(underscores); } @@ -334,7 +345,7 @@ function markdown(text, keep = false) { if (keep) { j.append(underscores); } - j.appendChild(markdown(build, keep)); + j.appendChild(markdown(build, { keep: keep, stdsize: stdsize })); j.classList.add("spoiler"); j.onclick = markdown.unspoil; if (keep) { diff --git a/.dist/message.js b/.dist/message.js index f7560d7..73bbe6b 100644 --- a/.dist/message.js +++ b/.dist/message.js @@ -197,7 +197,7 @@ class Message { replyline.classList.add("replyflex"); fetch(this.info.api.toString() + "/v9/channels/" + this.message_reference.channel_id + "/messages?limit=1&around=" + this.message_reference.message_id, { headers: this.headers }).then(responce => responce.json()).then(responce => { const author = new User(responce[0].author, this.localuser); - reply.appendChild(markdown(responce[0].content)); + reply.appendChild(markdown(responce[0].content, { stdsize: true })); minipfp.src = author.getpfpsrc(); author.profileclick(minipfp); username.textContent = author.username; diff --git a/webpage/embed.ts b/webpage/embed.ts index 946a5fe..db6c1ef 100644 --- a/webpage/embed.ts +++ b/webpage/embed.ts @@ -184,14 +184,14 @@ class Embed{ description.textContent=this.json.description; div.append(description); - { + if(this.json.thumbnail){ const img=document.createElement("img"); img.classList.add("bigembedimg"); img.onclick=function(){ const full=new Fullscreen(["img",img.src,["fit"]]); full.show(); } - img.src=this.json.thumbnail.proxy_url; + img.src=this.json.thumbnail.proxy_url||this.json.thumbnail.url; div.append(img); } colordiv.append(div); diff --git a/webpage/localuser.ts b/webpage/localuser.ts index 83983df..cf09acf 100644 --- a/webpage/localuser.ts +++ b/webpage/localuser.ts @@ -229,7 +229,7 @@ class Localuser{ clearInterval(this.wsinterval); console.log('WebSocket closed'); console.warn(event); - if(event.code!==4000&&this===this){ + if(event.code!==4000){ this.unload(); document.getElementById("loading").classList.remove("doneloading"); document.getElementById("loading").classList.add("loading"); diff --git a/webpage/markdown.ts b/webpage/markdown.ts index 82aa167..f03d486 100644 --- a/webpage/markdown.ts +++ b/webpage/markdown.ts @@ -1,5 +1,5 @@ export {markdown}; -function markdown(text : string|string[],keep=false){ +function markdown(text : string|string[],{keep=false,stdsize=false} = {}){ let txt : string[]; if((typeof txt)==="string"){ txt=(text as string).split(""); @@ -53,17 +53,20 @@ function markdown(text : string|string[],keep=false){ } if(keepys){ appendcurrent(); - if(!first){ + if(!first&&!stdsize){ span.appendChild(document.createElement("br")); } const build=[]; for(;txt[i]!=="\n"&&txt[i]!==undefined;i++){ build.push(txt[i]); } + if(stdsize){ + element=document.createElement("span"); + } if(keep){ element.append(keepys); } - element.appendChild(markdown(build,keep)); + element.appendChild(markdown(build,{keep:keep,stdsize:stdsize})); span.append(element); i--; continue; @@ -74,7 +77,14 @@ function markdown(text : string|string[],keep=false){ } if(txt[i]==="\n"){ appendcurrent(); - span.append(document.createElement("br")); + if(!stdsize){ + span.append(document.createElement("br")); + }else{ + const s=document.createElement("span"); + s.textContent="..."; + span.append(s); + return span; + } continue; } if(txt[i]==="`"){ @@ -118,7 +128,7 @@ function markdown(text : string|string[],keep=false){ if(keep){ build+="`".repeat(find); } - if(count!==3){ + if(count!==3&&!stdsize){ const samp=document.createElement("samp"); samp.textContent=build; span.appendChild(samp); @@ -169,20 +179,20 @@ function markdown(text : string|string[],keep=false){ if(count===1){ const i=document.createElement("i"); if(keep){i.append(stars)} - i.appendChild(markdown(build,keep)); + i.appendChild(markdown(build,{keep:keep,stdsize:stdsize})); if(keep){i.append(stars)} span.appendChild(i); }else if(count===2){ const b=document.createElement("b"); if(keep){b.append(stars)} - b.appendChild(markdown(build,keep)); + b.appendChild(markdown(build,{keep:keep,stdsize:stdsize})); if(keep){b.append(stars)} span.appendChild(b); }else{ const b=document.createElement("b"); const i=document.createElement("i"); if(keep){b.append(stars)} - b.appendChild(markdown(build,keep)); + b.appendChild(markdown(build,{keep:keep,stdsize:stdsize})); if(keep){b.append(stars)} i.appendChild(b); span.appendChild(i); @@ -222,20 +232,20 @@ function markdown(text : string|string[],keep=false){ if(count===1){ const i=document.createElement("i"); if(keep){i.append(underscores)} - i.appendChild(markdown(build,keep)); + i.appendChild(markdown(build,{keep:keep,stdsize:stdsize})); if(keep){i.append(underscores)} span.appendChild(i); }else if(count===2){ const u=document.createElement("u"); if(keep){u.append(underscores)} - u.appendChild(markdown(build,keep)); + u.appendChild(markdown(build,{keep:keep,stdsize:stdsize})); if(keep){u.append(underscores)} span.appendChild(u); }else{ const u=document.createElement("u"); const i=document.createElement("i"); if(keep){i.append(underscores)} - i.appendChild(markdown(build,keep)); + i.appendChild(markdown(build,{keep:keep,stdsize:stdsize})); if(keep){i.append(underscores)} u.appendChild(i) span.appendChild(u); @@ -268,7 +278,7 @@ function markdown(text : string|string[],keep=false){ if(count===2){ const s=document.createElement("s"); if(keep){s.append(underscores)} - s.appendChild(markdown(build,keep)); + s.appendChild(markdown(build,{keep:keep,stdsize:stdsize})); if(keep){s.append(underscores)} span.appendChild(s); } @@ -298,7 +308,7 @@ function markdown(text : string|string[],keep=false){ if(count===2){ const j=document.createElement("j"); if(keep){j.append(underscores)} - j.appendChild(markdown(build,keep)); + j.appendChild(markdown(build,{keep:keep,stdsize:stdsize})); j.classList.add("spoiler"); j.onclick=markdown.unspoil; if(keep){j.append(underscores)} diff --git a/webpage/message.ts b/webpage/message.ts index c4aab7b..1df1456 100644 --- a/webpage/message.ts +++ b/webpage/message.ts @@ -202,7 +202,7 @@ class Message{ fetch(this.info.api.toString()+"/v9/channels/"+this.message_reference.channel_id+"/messages?limit=1&around="+this.message_reference.message_id,{headers:this.headers}).then(responce=>responce.json()).then(responce=>{ const author=new User(responce[0].author,this.localuser); - reply.appendChild(markdown(responce[0].content)); + reply.appendChild(markdown(responce[0].content,{stdsize:true})); minipfp.src=author.getpfpsrc() author.profileclick(minipfp) diff --git a/webpage/style.css b/webpage/style.css index 2d64f7b..2f856bb 100644 --- a/webpage/style.css +++ b/webpage/style.css @@ -57,7 +57,12 @@ th { .messagediv:hover { background-color: var(--message-bg-hover); } - +.messagediv{ + overflow: hidden; + max-width:100%; + /* width: 9%; */ + /* display: inline-block; */ +} pre { background-color: var(--code-bg); width: 100%; @@ -225,10 +230,11 @@ img { height: 100%; width: 100%; display: inline-block; + max-width: 100%; } #messages { - width: 100%; + max-width: 100%; } p { @@ -448,15 +454,25 @@ p { } .replyflex { + overflow: hidden; display: flex; align-items: center; + max-width: 100%; + flex-direction: row; + /* width: 00; */ + flex-wrap: nowrap; + justify-content: space-between; } .reply { - display: inline-block; + display: inline; vertical-align: middle; flex-grow: 1; border-color: var(--reply-border); + /* flex: 1; */ + min-width: 0px; + /* max-width: 0px; */ + /* grid-column-end: 1; */ } .startreply { @@ -477,8 +493,20 @@ p { } .replytext { - padding: .05in; + padding: 0 .05in; color: var(--reply-text); + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + max-width: fit-content; + /* display: block; */ + /* flex-grow: 1; */ + flex: 1 1 auto; + width: fit-content; + min-width: 0; + /* display: inline-block !important; */ + width: 25vw; + grid-column: 2; } ::-webkit-scrollbar { @@ -765,6 +793,9 @@ input[type="checkbox"] { span { word-wrap: break-word; word-break: break-word; + /* overflow: clip; */ + /* width: 2in; */ + /* max-width: 100%; */ } #loading { @@ -987,4 +1018,10 @@ span { text-align:center; border:solid black .03in; margin-left:.025in; -} \ No newline at end of file +} +.replyflex span{ + /* display: inline-block; */ + text-overflow:ellipsis; + overflow: hidden; + max-width: 100%; +} From 039491ca874dd318cd0b43436fea8f58246e6073 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 28 Jun 2024 14:49:00 -0500 Subject: [PATCH 5/7] slight reply patch --- .dist/markdown.js | 8 +------- webpage/markdown.ts | 8 ++------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/.dist/markdown.js b/.dist/markdown.js index 9cfa553..f247258 100644 --- a/.dist/markdown.js +++ b/.dist/markdown.js @@ -78,16 +78,10 @@ function markdown(text, { keep = false, stdsize = false } = {}) { } } if (txt[i] === "\n") { - appendcurrent(); if (!stdsize) { + appendcurrent(); span.append(document.createElement("br")); } - else { - const s = document.createElement("span"); - s.textContent = "..."; - span.append(s); - return span; - } continue; } if (txt[i] === "`") { diff --git a/webpage/markdown.ts b/webpage/markdown.ts index f03d486..d6230e4 100644 --- a/webpage/markdown.ts +++ b/webpage/markdown.ts @@ -76,14 +76,10 @@ function markdown(text : string|string[],{keep=false,stdsize=false} = {}){ } } if(txt[i]==="\n"){ - appendcurrent(); + if(!stdsize){ + appendcurrent(); span.append(document.createElement("br")); - }else{ - const s=document.createElement("span"); - s.textContent="..."; - span.append(s); - return span; } continue; } From a05c74bb3c88d3d177b1dcac4ef22818b5187ff0 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 28 Jun 2024 19:15:10 -0500 Subject: [PATCH 6/7] More supported attatchment types and cancel uploads --- .dist/channel.js | 19 ++++++- .dist/file.js | 127 +++++++++++++++++++++++++++++++++++++++++++ .dist/index.js | 10 ++-- .dist/localuser.js | 31 +++++++++-- .dist/message.js | 34 ++++-------- webpage/channel.ts | 18 +++++- webpage/file.ts | 126 ++++++++++++++++++++++++++++++++++++++++++ webpage/guild.ts | 2 +- webpage/index.html | 21 ------- webpage/index.ts | 21 ++++--- webpage/localuser.ts | 33 ++++++++--- webpage/message.ts | 34 +++++------- webpage/style.css | 43 ++++++++++++--- 13 files changed, 416 insertions(+), 103 deletions(-) create mode 100644 .dist/file.js create mode 100644 webpage/file.ts diff --git a/.dist/channel.js b/.dist/channel.js index 413c829..38410c1 100644 --- a/.dist/channel.js +++ b/.dist/channel.js @@ -404,10 +404,24 @@ class Channel { headers: this.headers }); } + async getmessage(id) { + if (this.messageids[id]) { + return this.messageids[id]; + } + else { + const gety = await fetch(this.info.api.toString() + "/v9/channels/" + this.id + "/messages?limit=1&around=" + id, { headers: this.headers }); + const json = await gety.json(); + return new Message(json[0], this); + } + } async getHTML() { if (this.guild !== this.localuser.lookingguild) { this.guild.loadGuild(); } + if (this.localuser.channelfocus && this.localuser.channelfocus.myhtml) { + this.localuser.channelfocus.myhtml.classList.remove("viewChannel"); + } + this.myhtml.classList.add("viewChannel"); this.guild.prevchannel = this; this.localuser.channelfocus = this; const prom = Message.wipeChanel(); @@ -420,7 +434,7 @@ class Channel { document.getElementById("typebox").disabled = !this.canMessage; } async putmessages() { - if (this.messages.length >= 100) { + if (this.messages.length >= 100 || this.allthewayup) { return; } ; @@ -429,6 +443,9 @@ class Channel { headers: this.headers, }); const responce = await j.json(); + if (responce.length !== 100) { + this.allthewayup = true; + } for (const thing of responce) { const messager = new Message(thing, this); if (this.messageids[messager.id] === undefined) { diff --git a/.dist/file.js b/.dist/file.js new file mode 100644 index 0000000..04075fe --- /dev/null +++ b/.dist/file.js @@ -0,0 +1,127 @@ +import { Fullscreen } from "./fullscreen.js"; +class File { + owner; + id; + filename; + content_type; + width; + height; + proxy_url; + url; + size; + constructor(fileJSON, owner) { + console.log(fileJSON); + this.owner = owner; + this.id = fileJSON.id; + this.filename = fileJSON.filename; + this.content_type = fileJSON.content_type; + this.width = fileJSON.width; + this.height = fileJSON.height; + this.url = fileJSON.url; + this.proxy_url = fileJSON.proxy_url; + this.content_type = fileJSON.content_type; + this.size = fileJSON.size; + } + getHTML(temp = false) { + const src = this.proxy_url || this.url; + if (this.content_type.startsWith('image/')) { + const img = document.createElement("img"); + img.classList.add("messageimg"); + img.onclick = function () { + const full = new Fullscreen(["img", img.src, ["fit"]]); + full.show(); + }; + img.src = src; + img.height = this.height; + img.width = this.width; + return img; + } + else if (this.content_type.startsWith('video/')) { + const video = document.createElement("video"); + const source = document.createElement("source"); + source.src = src; + video.append(source); + source.type = this.content_type; + video.controls = !temp; + return video; + } + else if (this.content_type.startsWith('audio/')) { + const audio = document.createElement("audio"); + const source = document.createElement("source"); + source.src = src; + audio.append(source); + source.type = this.content_type; + audio.controls = !temp; + return audio; + } + else { + return this.createunknown(); + } + } + upHTML(files, file) { + const div = document.createElement("div"); + const contained = this.getHTML(true); + div.classList.add("containedFile"); + div.append(contained); + const controls = document.createElement("div"); + const garbage = document.createElement("button"); + garbage.textContent = "🗑"; + garbage.onclick = _ => { + div.remove(); + files.splice(files.indexOf(file), 1); + }; + controls.classList.add("controls"); + div.append(controls); + controls.append(garbage); + return div; + } + static initFromBlob(file) { + return new File({ + filename: file.name, + size: file.size, + id: null, + content_type: file.type, + width: undefined, + height: undefined, + url: URL.createObjectURL(file), + proxy_url: undefined + }, null); + } + createunknown() { + console.log("🗎"); + const src = this.proxy_url || this.url; + const div = document.createElement("table"); + div.classList.add("unknownfile"); + const nametr = document.createElement("tr"); + div.append(nametr); + const fileicon = document.createElement("td"); + nametr.append(fileicon); + fileicon.append("🗎"); + fileicon.classList.add("fileicon"); + fileicon.rowSpan = 2; + const nametd = document.createElement("td"); + if (src) { + const a = document.createElement("a"); + a.href = src; + a.textContent = this.filename; + nametd.append(a); + } + else { + nametd.textContent = this.filename; + } + nametd.classList.add("filename"); + nametr.append(nametd); + const sizetr = document.createElement("tr"); + const size = document.createElement("td"); + sizetr.append(size); + size.textContent = "Size:" + File.filesizehuman(this.size); + size.classList.add("filesize"); + div.appendChild(sizetr); + return div; + } + static filesizehuman(fsize) { + var i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1024)); + return +((fsize / Math.pow(1024, i)).toFixed(2)) * 1 + ' ' + ['Bytes', 'Kilobytes', 'Megabytes', 'Gigabytes', 'Terabytes'][i]; + } +} +export { File }; diff --git a/.dist/index.js b/.dist/index.js index 1b98373..4f3b858 100644 --- a/.dist/index.js +++ b/.dist/index.js @@ -208,15 +208,15 @@ function filetohtml(file) { return createunknownfile(file); } } +import { File } from "./file.js"; document.addEventListener('paste', async (e) => { - Array.from(e.clipboardData.files).forEach(async (file) => { + Array.from(e.clipboardData.files).forEach(async (f) => { + const file = File.initFromBlob(f); e.preventDefault(); - const html = filetohtml(file); + const html = file.upHTML(images, f); pasteimage.appendChild(html); - const blob = URL.createObjectURL(file); - images.push(file); + images.push(f); imageshtml.push(html); - console.log(file.type); }); }); setTheme(); diff --git a/.dist/localuser.js b/.dist/localuser.js index 5f854cb..ea9751c 100644 --- a/.dist/localuser.js +++ b/.dist/localuser.js @@ -66,7 +66,11 @@ class Localuser { this.guildids[thing.guild_id].notisetting(thing); } for (const thing of ready.d.read_state.entries) { - const guild = this.resolveChannelFromID(thing.id).guild; + const channel = this.resolveChannelFromID(thing.id); + if (!channel) { + continue; + } + const guild = channel.guild; if (guild === undefined) { continue; } @@ -238,9 +242,11 @@ class Localuser { return; } resolveChannelFromID(ID) { - let resolve = this.guilds.find(guild => guild.channelids[ID]).channelids[ID]; - resolve ??= undefined; - return resolve; + let resolve = this.guilds.find(guild => guild.channelids[ID]); + if (resolve) { + return resolve.channelids[ID]; + } + return undefined; } updateChannel(JSON) { this.guildids[JSON.guild_id].updateChannel(JSON); @@ -264,12 +270,12 @@ class Localuser { } init() { const location = window.location.href.split("/"); + this.buildservers(); if (location[3] === "channels") { const guild = this.loadGuild(location[4]); guild.loadChannel(location[5]); this.channelfocus = guild.channelids[location[5]]; } - this.buildservers(); } loaduser() { document.getElementById("username").textContent = this.user.username; @@ -284,6 +290,12 @@ class Localuser { if (!guild) { guild = this.guildids["@me"]; } + if (this.lookingguild) { + this.lookingguild.html.classList.remove("serveropen"); + } + if (guild.html) { + guild.html.classList.add("serveropen"); + } this.lookingguild = guild; document.getElementById("serverName").textContent = guild.properties.name; //console.log(this.guildids,id) @@ -293,11 +305,18 @@ class Localuser { } buildservers() { const serverlist = document.getElementById("servers"); // + const outdiv = document.createElement("div"); const div = document.createElement("div"); div.textContent = "⌂"; div.classList.add("home", "servericon"); div["all"] = this.guildids["@me"]; - serverlist.appendChild(div); + this.guildids["@me"].html = outdiv; + const unread = document.createElement("div"); + unread.classList.add("unread"); + outdiv.append(unread); + outdiv.appendChild(div); + outdiv.classList.add("servernoti"); + serverlist.append(outdiv); div.onclick = function () { this["all"].loadGuild(); this["all"].loadChannel(); diff --git a/.dist/message.js b/.dist/message.js index 73bbe6b..19aef46 100644 --- a/.dist/message.js +++ b/.dist/message.js @@ -3,7 +3,7 @@ import { User } from "./user.js"; import { Member } from "./member.js"; import { markdown } from "./markdown.js"; import { Embed } from "./embed.js"; -import { Fullscreen } from "./fullscreen.js"; +import { File } from "./file.js"; class Message { static contextmenu = new Contextmenu("message menu"); owner; @@ -67,6 +67,13 @@ class Message { this.owner = owner; this.headers = this.owner.headers; for (const thing of Object.keys(messagejson)) { + if (thing === "attachments") { + this.attachments = []; + for (const thing of messagejson.attachments) { + this.attachments.push(new File(thing, this)); + } + continue; + } this[thing] = messagejson[thing]; } for (const thing in this.embeds) { @@ -195,9 +202,9 @@ class Message { line2.classList.add("reply"); line.classList.add("startreply"); replyline.classList.add("replyflex"); - fetch(this.info.api.toString() + "/v9/channels/" + this.message_reference.channel_id + "/messages?limit=1&around=" + this.message_reference.message_id, { headers: this.headers }).then(responce => responce.json()).then(responce => { - const author = new User(responce[0].author, this.localuser); - reply.appendChild(markdown(responce[0].content, { stdsize: true })); + this.channel.getmessage(this.message_reference.message_id).then(message => { + const author = message.author; + reply.appendChild(markdown(message.content, { stdsize: true })); minipfp.src = author.getpfpsrc(); author.profileclick(minipfp); username.textContent = author.username; @@ -277,24 +284,7 @@ class Message { console.log(this.attachments); const attatch = document.createElement("tr"); for (const thing of this.attachments) { - const array = thing.url.split("/"); - array.shift(); - array.shift(); - array.shift(); - const src = this.info.cdn.toString() + array.join("/"); - if (thing.content_type.startsWith('image/')) { - const img = document.createElement("img"); - img.classList.add("messageimg"); - img.onclick = function () { - const full = new Fullscreen(["img", img.src, ["fit"]]); - full.show(); - }; - img.src = src; - attatch.appendChild(img); - } - else { - attatch.appendChild(this.createunknown(thing.filename, thing.size, src)); - } + attatch.appendChild(thing.getHTML()); } messagedwrap.appendChild(attatch); } diff --git a/webpage/channel.ts b/webpage/channel.ts index 86e742b..6b4c546 100644 --- a/webpage/channel.ts +++ b/webpage/channel.ts @@ -412,10 +412,23 @@ class Channel{ headers:this.headers }) } + async getmessage(id:string):Promise{ + if(this.messageids[id]){ + return this.messageids[id]; + }else{ + const gety=await fetch(this.info.api.toString()+"/v9/channels/"+this.id+"/messages?limit=1&around="+id,{headers:this.headers}) + const json=await gety.json(); + return new Message(json[0],this); + } + } async getHTML(){ if(this.guild!==this.localuser.lookingguild){ this.guild.loadGuild(); } + if(this.localuser.channelfocus&&this.localuser.channelfocus.myhtml){ + this.localuser.channelfocus.myhtml.classList.remove("viewChannel"); + } + this.myhtml.classList.add("viewChannel") this.guild.prevchannel=this; this.localuser.channelfocus=this; const prom=Message.wipeChanel(); @@ -428,12 +441,15 @@ class Channel{ (document.getElementById("typebox") as HTMLInputElement).disabled=!this.canMessage; } async putmessages(){ - if(this.messages.length>=100){return}; + if(this.messages.length>=100||this.allthewayup){return}; const j=await fetch(this.info.api.toString()+"/channels/"+this.id+"/messages?limit=100",{ method: 'GET', headers: this.headers, }) const responce=await j.json(); + if(responce.length!==100){ + this.allthewayup=true; + } for(const thing of responce){ const messager=new Message(thing,this) if(this.messageids[messager.id]===undefined){ diff --git a/webpage/file.ts b/webpage/file.ts new file mode 100644 index 0000000..cb83dd0 --- /dev/null +++ b/webpage/file.ts @@ -0,0 +1,126 @@ +import { Message } from "./message.js"; +import { Fullscreen } from "./fullscreen.js"; +type filejson= {id:string,filename:string,content_type:string,width:number,height:number,proxy_url:string|undefined,url:string,size:number}; +class File{ + owner:Message; + id:string; + filename:string; + content_type:string; + width:number; + height:number; + proxy_url:string; + url:string; + size:number; + constructor(fileJSON:filejson,owner:Message){ + console.log(fileJSON); + this.owner=owner; + this.id=fileJSON.id; + this.filename=fileJSON.filename; + this.content_type=fileJSON.content_type; + this.width=fileJSON.width; + this.height=fileJSON.height; + this.url=fileJSON.url; + this.proxy_url=fileJSON.proxy_url; + this.content_type=fileJSON.content_type; + this.size=fileJSON.size; + } + getHTML(temp:boolean=false):HTMLElement{ + const src=this.proxy_url||this.url; + if(this.content_type.startsWith('image/')){ + const img=document.createElement("img"); + img.classList.add("messageimg"); + img.onclick=function(){ + const full=new Fullscreen(["img",img.src,["fit"]]); + full.show(); + } + img.src=src; + img.height=this.height; + img.width=this.width; + return img; + }else if(this.content_type.startsWith('video/')){ + const video=document.createElement("video"); + const source=document.createElement("source"); + source.src=src; + video.append(source); + source.type=this.content_type; + video.controls=!temp; + return video; + }else if(this.content_type.startsWith('audio/')){ + const audio=document.createElement("audio"); + const source=document.createElement("source"); + source.src=src; + audio.append(source); + source.type=this.content_type; + audio.controls=!temp; + return audio; + }else{ + return this.createunknown(); + } + } + upHTML(files:Blob[],file:globalThis.File):HTMLElement{ + const div=document.createElement("div"); + const contained=this.getHTML(true); + div.classList.add("containedFile"); + div.append(contained); + const controls=document.createElement("div"); + const garbage=document.createElement("button"); + garbage.textContent="🗑"; + garbage.onclick=_=>{ + div.remove(); + files.splice(files.indexOf(file),1); + } + controls.classList.add("controls"); + div.append(controls); + controls.append(garbage); + return div; + } + static initFromBlob(file:globalThis.File){ + return new File({ + filename:file.name, + size:file.size, + id:null, + content_type:file.type, + width:undefined, + height:undefined, + url:URL.createObjectURL(file), + proxy_url:undefined + },null) + } + createunknown():HTMLElement{ + console.log("🗎") + const src=this.proxy_url||this.url; + const div=document.createElement("table"); + div.classList.add("unknownfile"); + const nametr=document.createElement("tr"); + div.append(nametr); + const fileicon=document.createElement("td"); + nametr.append(fileicon); + fileicon.append("🗎"); + fileicon.classList.add("fileicon"); + fileicon.rowSpan=2; + const nametd=document.createElement("td"); + if(src){ + const a=document.createElement("a"); + a.href=src; + a.textContent=this.filename; + nametd.append(a); + }else{ + nametd.textContent=this.filename; + } + + nametd.classList.add("filename"); + nametr.append(nametd); + const sizetr=document.createElement("tr"); + const size=document.createElement("td"); + sizetr.append(size); + size.textContent="Size:"+File.filesizehuman(this.size); + size.classList.add("filesize"); + div.appendChild(sizetr); + return div; + } + static filesizehuman(fsize:number){ + var i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1024)); + return +((fsize / Math.pow(1024, i)).toFixed(2)) * 1 + ' ' + ['Bytes', 'Kilobytes', 'Megabytes', 'Gigabytes', 'Terabytes'][i]; + } +} +export{File} diff --git a/webpage/guild.ts b/webpage/guild.ts index ff92329..1db660c 100644 --- a/webpage/guild.ts +++ b/webpage/guild.ts @@ -20,7 +20,7 @@ class Guild{ position:number; parent_id:string; member:Member; - html:HTMLUnknownElement; + html:HTMLElement; static contextmenu=new Contextmenu("guild menu"); static setupcontextmenu(){ Guild.contextmenu.addbutton("Copy Guild id",function(){ diff --git a/webpage/index.html b/webpage/index.html index 02b72d3..2dab8f6 100644 --- a/webpage/index.html +++ b/webpage/index.html @@ -10,27 +10,6 @@ -

Jank Client is loading

This shouldn't take long

diff --git a/webpage/index.ts b/webpage/index.ts index 996392d..20ea565 100644 --- a/webpage/index.ts +++ b/webpage/index.ts @@ -189,7 +189,7 @@ function getguildinfo(){ } -const images=[]; +const images:Blob[]=[]; const imageshtml=[]; function createunknown(fname,fsize){ const div=document.createElement("table"); @@ -235,17 +235,16 @@ function filetohtml(file){ return createunknownfile(file); } } +import { File } from "./file.js"; document.addEventListener('paste', async (e) => { - Array.from(e.clipboardData.files).forEach(async (file) => { - e.preventDefault(); - const html=filetohtml(file); - pasteimage.appendChild(html); - const blob = URL.createObjectURL(file); - images.push(file) - imageshtml.push(html); - - console.log(file.type) - }); + Array.from(e.clipboardData.files).forEach(async (f) => { + const file=File.initFromBlob(f); + e.preventDefault(); + const html=file.upHTML(images,f); + pasteimage.appendChild(html); + images.push(f) + imageshtml.push(html); + }); }); setTheme(); diff --git a/webpage/localuser.ts b/webpage/localuser.ts index cf09acf..31e5b27 100644 --- a/webpage/localuser.ts +++ b/webpage/localuser.ts @@ -72,7 +72,9 @@ class Localuser{ } for(const thing of ready.d.read_state.entries){ - const guild=this.resolveChannelFromID(thing.id).guild; + const channel=this.resolveChannelFromID(thing.id); + if(!channel){continue;} + const guild=channel.guild; if(guild===undefined){ continue } @@ -246,9 +248,11 @@ class Localuser{ return; } resolveChannelFromID(ID:string):Channel{ - let resolve=this.guilds.find(guild => guild.channelids[ID]).channelids[ID]; - resolve??=undefined; - return resolve; + let resolve=this.guilds.find(guild => guild.channelids[ID]); + if(resolve){ + return resolve.channelids[ID]; + } + return undefined; } updateChannel(JSON):void{ this.guildids[JSON.guild_id].updateChannel(JSON); @@ -273,12 +277,13 @@ class Localuser{ } init():void{ const location=window.location.href.split("/"); + this.buildservers(); if(location[3]==="channels"){ const guild=this.loadGuild(location[4]); guild.loadChannel(location[5]); this.channelfocus=guild.channelids[location[5]]; } - this.buildservers(); + } loaduser():void{ document.getElementById("username").textContent=this.user.username; @@ -293,6 +298,12 @@ class Localuser{ if(!guild){ guild=this.guildids["@me"]; } + if(this.lookingguild){ + this.lookingguild.html.classList.remove("serveropen"); + } + if(guild.html){ + guild.html.classList.add("serveropen") + } this.lookingguild=guild; document.getElementById("serverName").textContent=guild.properties.name; //console.log(this.guildids,id) @@ -302,12 +313,20 @@ class Localuser{ } buildservers():void{ const serverlist=document.getElementById("servers");// - + const outdiv=document.createElement("div"); const div=document.createElement("div"); + div.textContent="⌂"; div.classList.add("home","servericon") div["all"]=this.guildids["@me"]; - serverlist.appendChild(div) + this.guildids["@me"].html=outdiv; + const unread=document.createElement("div"); + unread.classList.add("unread"); + outdiv.append(unread); + outdiv.appendChild(div); + + outdiv.classList.add("servernoti") + serverlist.append(outdiv); div.onclick=function(){ this["all"].loadGuild(); this["all"].loadChannel(); diff --git a/webpage/message.ts b/webpage/message.ts index 1df1456..10d6d3a 100644 --- a/webpage/message.ts +++ b/webpage/message.ts @@ -7,6 +7,7 @@ import {Fullscreen} from "./fullscreen.js"; import { Channel } from "./channel.js"; import {Localuser} from "./localuser.js"; import { Role } from "./role.js"; +import {File} from "./file.js"; class Message{ static contextmenu=new Contextmenu("message menu"); @@ -16,7 +17,7 @@ class Message{ author:User; mentions:User[]; mention_roles:Role[]; - attachments;//probably should be its own class tbh, should be Attachments[] + attachments:File[];//probably should be its own class tbh, should be Attachments[] id:string; message_reference; type:number; @@ -72,6 +73,13 @@ class Message{ this.owner=owner; this.headers=this.owner.headers; for(const thing of Object.keys(messagejson)){ + if(thing==="attachments"){ + this.attachments=[]; + for(const thing of messagejson.attachments){ + this.attachments.push(new File(thing,this)); + } + continue; + } this[thing]=messagejson[thing]; } for(const thing in this.embeds){ @@ -199,11 +207,9 @@ class Message{ line2.classList.add("reply"); line.classList.add("startreply"); replyline.classList.add("replyflex") - fetch(this.info.api.toString()+"/v9/channels/"+this.message_reference.channel_id+"/messages?limit=1&around="+this.message_reference.message_id,{headers:this.headers}).then(responce=>responce.json()).then(responce=>{ - const author=new User(responce[0].author,this.localuser); - - reply.appendChild(markdown(responce[0].content,{stdsize:true})); - + this.channel.getmessage(this.message_reference.message_id).then(message=>{ + const author=message.author; + reply.appendChild(markdown(message.content,{stdsize:true})); minipfp.src=author.getpfpsrc() author.profileclick(minipfp) username.textContent=author.username; @@ -285,21 +291,7 @@ class Message{ console.log(this.attachments) const attatch = document.createElement("tr") for(const thing of this.attachments){ - const array=thing.url.split("/");array.shift();array.shift();array.shift(); - const src=this.info.cdn.toString()+array.join("/"); - if(thing.content_type.startsWith('image/')){ - const img=document.createElement("img"); - img.classList.add("messageimg") - img.onclick=function(){ - const full=new Fullscreen(["img",img.src,["fit"]]); - full.show(); - } - img.src=src; - attatch.appendChild(img) - }else{ - attatch.appendChild(this.createunknown(thing.filename,thing.size,src)) - } - + attatch.appendChild(thing.getHTML()) } messagedwrap.appendChild(attatch) } diff --git a/webpage/style.css b/webpage/style.css index 2f856bb..b8fa005 100644 --- a/webpage/style.css +++ b/webpage/style.css @@ -8,6 +8,10 @@ body { margin: 0; padding: 0; } +video{ + max-width: 3in; + max-height: 4in; +} .collapse{ width:0px !important; overflow: hidden; @@ -152,11 +156,14 @@ h2 { .servericon { transition: border-radius .2s; position: relative; + margin: .0in 0in 0.03in 0; } .servericon:hover { border-radius: 30%; } - +.serveropen .servericon{ + border-radius: 30%; +} .contextbutton:hover { background-color: var(--primary-bg); } @@ -177,6 +184,7 @@ h2 { vertical-align: top; height: 100dvh; overflow-x: hidden; + padding: 0.02in .05in 0.0in .05in; } #servers::-webkit-scrollbar { @@ -556,8 +564,12 @@ textarea { .channel { user-select: none; cursor: pointer; + transition: font-weight .1s; +} +.viewChannel{ + font-weight:900; + background: color-mix(in srgb, var(--channel-hover) 60%, transparent) } - #servername { margin-top: .1in; margin-bottom: .1in; @@ -845,17 +857,24 @@ span { background-color: var(--primary-text); height: .075in; width: .075in; - transition: transform .2s, background .2s, height .2s, width .2s; - transform: translate(-.20in, .2in); + transition: transform .2s, background .2s, height .3s, width .2s; + transform: translate(-.14in, .2in); z-index: 10; - border-radius: 50%; + border-radius: .2in; border: solid; border-width: .02in; border-color: var(--black); } - +.servernoti:hover .unread{ + transform: translate(-.1in, 0.15in); + height:.2in; +} +.serveropen .unread{ + transform: translate(-.1in, 0.1in) !important; + height:.3in !important; +} .notiunread { - transform: translate(0, .2in); + transform: translate(-.1in, .2in); } .pinged { @@ -1025,3 +1044,13 @@ span { overflow: hidden; max-width: 100%; } +.controls{ + position:absolute; + top:0px; + right:0px; +} +.containedFile{ + position:relative !important; + width: fit-content; + box-shadow:.02in .02in .05in black; +} From e49360fba121ae746b12f7d1829eea18eb4f6674 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 28 Jun 2024 19:53:48 -0500 Subject: [PATCH 7/7] Reply GUI --- .dist/channel.js | 33 +++++++++++++++++++++++++++++++++ .dist/index.js | 9 +++++---- .dist/message.js | 10 ++++------ webpage/channel.ts | 35 ++++++++++++++++++++++++++++++++++- webpage/index.html | 1 + webpage/index.ts | 9 +++++---- webpage/message.ts | 12 +++++------- webpage/style.css | 29 +++++++++++++++++++++++++++++ 8 files changed, 116 insertions(+), 22 deletions(-) diff --git a/.dist/channel.js b/.dist/channel.js index 38410c1..4bc2f27 100644 --- a/.dist/channel.js +++ b/.dist/channel.js @@ -404,6 +404,38 @@ class Channel { headers: this.headers }); } + setReplying(message) { + if (this.replyingto) { + this.replyingto.div.classList.remove("replying"); + } + this.replyingto = message; + console.log(message); + this.replyingto.div.classList.add("replying"); + this.makereplybox(); + } + makereplybox() { + const replybox = document.getElementById("replybox"); + if (this.replyingto) { + replybox.innerHTML = ""; + const span = document.createElement("span"); + span.textContent = "Replying to " + this.replyingto.author.username; + const X = document.createElement("button"); + X.onclick = _ => { + this.replyingto.div.classList.remove("replying"); + replybox.classList.add("hideReplyBox"); + this.replyingto = null; + replybox.innerHTML = ""; + }; + replybox.classList.remove("hideReplyBox"); + X.textContent = "âĻģ"; + X.classList.add("cancelReply"); + replybox.append(span); + replybox.append(X); + } + else { + replybox.classList.add("hideReplyBox"); + } + } async getmessage(id) { if (this.messageids[id]) { return this.messageids[id]; @@ -427,6 +459,7 @@ class Channel { const prom = Message.wipeChanel(); await this.putmessages(); await prom; + this.makereplybox(); this.buildmessages(); history.pushState(null, null, "/channels/" + this.guild_id + "/" + this.id); document.getElementById("channelname").textContent = "#" + this.name; diff --git a/.dist/index.js b/.dist/index.js index 4f3b858..a7aaf5b 100644 --- a/.dist/index.js +++ b/.dist/index.js @@ -11,14 +11,14 @@ async function waitforload() { } await waitforload(); function setDynamicHeight() { - var servertdHeight = document.getElementById('servertd').offsetHeight + document.getElementById('typebox').offsetHeight + document.getElementById('pasteimage').offsetHeight; + var servertdHeight = document.getElementById('servertd').offsetHeight + document.getElementById('typediv').offsetHeight + document.getElementById('pasteimage').offsetHeight; document.documentElement.style.setProperty('--servertd-height', servertdHeight + 'px'); } const resizeObserver = new ResizeObserver(() => { setDynamicHeight(); }); resizeObserver.observe(document.getElementById('servertd')); -resizeObserver.observe(document.getElementById('typebox')); +resizeObserver.observe(document.getElementById('replybox')); resizeObserver.observe(document.getElementById('pasteimage')); setDynamicHeight(); const users = getBulkUsers(); @@ -129,15 +129,16 @@ async function enter(event) { } else { replyingto = thisuser.channelfocus.replyingto; - let replying = replyingto?.all; + let replying = replyingto; if (replyingto) { - replyingto.classList.remove("replying"); + replyingto.div.classList.remove("replying"); } thisuser.channelfocus.replyingto = null; channel.sendMessage(typebox.value, { attachments: images, replyingto: replying, }); + thisuser.channelfocus.makereplybox(); } while (images.length != 0) { images.pop(); diff --git a/.dist/message.js b/.dist/message.js index 19aef46..121a872 100644 --- a/.dist/message.js +++ b/.dist/message.js @@ -36,12 +36,7 @@ class Message { navigator.clipboard.writeText(this.content); }); Message.contextmenu.addbutton("Reply", function (div) { - if (this.channel.replyingto) { - this.channel.replyingto.classList.remove("replying"); - } - this.channel.replyingto = div; - console.log(div); - this.channel.replyingto.classList.add("replying"); + this.channel.setReplying(this); }); Message.contextmenu.addbutton("Copy message id", function () { navigator.clipboard.writeText(this.id); @@ -163,6 +158,9 @@ class Message { premessage = this.channel.messages[this.channel.messages.indexOf(this) + 1]; } const div = this.div; + if (this === this.channel.replyingto) { + div.classList.add("replying"); + } div.innerHTML = ""; const build = document.createElement('table'); if (this.message_reference) { diff --git a/webpage/channel.ts b/webpage/channel.ts index 6b4c546..5c9b330 100644 --- a/webpage/channel.ts +++ b/webpage/channel.ts @@ -39,7 +39,7 @@ class Channel{ message_notifications:number; allthewayup:boolean; static contextmenu=new Contextmenu("channel menu"); - replyingto:HTMLDivElement; + replyingto:Message; static setupcontextmenu(){ Channel.contextmenu.addbutton("Copy channel id",function(){ console.log(this) @@ -412,6 +412,38 @@ class Channel{ headers:this.headers }) } + setReplying(message:Message){ + if(this.replyingto){ + this.replyingto.div.classList.remove("replying"); + } + this.replyingto=message; + console.log(message); + this.replyingto.div.classList.add("replying"); + this.makereplybox(); + + } + makereplybox(){ + const replybox=document.getElementById("replybox"); + if(this.replyingto){ + replybox.innerHTML=""; + const span=document.createElement("span"); + span.textContent="Replying to "+this.replyingto.author.username; + const X=document.createElement("button"); + X.onclick=_=>{ + this.replyingto.div.classList.remove("replying"); + replybox.classList.add("hideReplyBox"); + this.replyingto=null; + replybox.innerHTML=""; + } + replybox.classList.remove("hideReplyBox"); + X.textContent="âĻģ"; + X.classList.add("cancelReply"); + replybox.append(span); + replybox.append(X); + }else{ + replybox.classList.add("hideReplyBox"); + } + } async getmessage(id:string):Promise{ if(this.messageids[id]){ return this.messageids[id]; @@ -434,6 +466,7 @@ class Channel{ const prom=Message.wipeChanel(); await this.putmessages(); await prom; + this.makereplybox(); this.buildmessages(); history.pushState(null, null,"/channels/"+this.guild_id+"/"+this.id); document.getElementById("channelname").textContent="#"+this.name; diff --git a/webpage/index.html b/webpage/index.html index 2dab8f6..8f3689d 100644 --- a/webpage/index.html +++ b/webpage/index.html @@ -57,6 +57,7 @@
+