diff --git a/plugins/charcount.js b/plugins/charcount.js
new file mode 100644
index 0000000..ce6bf17
--- /dev/null
+++ b/plugins/charcount.js
@@ -0,0 +1,52 @@
+function setupCharCount() {
+ if (document.getElementById("charcounter")) return;
+ if (!document.querySelector("textarea[class*=\"textAreaEnabled-\"]")) return;
+
+ let charcount = document.createElement("span");
+ charcount.id = "charcounter";
+ charcount.innerHTML = "0/2000";
+ charcount.style.right = "40px";
+ charcount.style.bottom = "4px";
+ charcount.style.opacity = "0.5";
+ charcount.style.position = "absolute";
+ charcount.style.display = "block";
+ charcount.style["font-size"] = "85%";
+
+ document.querySelector("div[class*=\"channelTextAreaEnabled-\"]").appendChild(charcount);
+}
+
+exports = {
+ meta: {
+ author: "Cynosphere",
+ name: "Character Counter",
+ desc: "Counts characters in the chatbox."
+ },
+ start: function(){
+ let charcount_mo = new MutationObserver(setupCharCount);
+ charcount_mo.observe(document.querySelector("div[class*=\"app-\"]"), {
+ childList: true,
+ subtree: true
+ });
+
+ document.addEventListener("keydown", ev => {
+ if (!document.getElementById("charcounter") || !document.querySelector("textarea[class*=\"textAreaEnabled-\"]")) return;
+
+ setTimeout(()=>{
+ let length = document.querySelector("textarea[class*=\"textAreaEnabled-\"]").value.length;
+ document.getElementById("charcounter").innerHTML = `${length}/2000`;
+
+ if (length > 1999) {
+ document.getElementById("charcounter").style.color = "#FF0000";
+ } else if (length > 1899) {
+ document.getElementById("charcounter").style.color = "#FF4500";
+ } else if (length > 1499) {
+ document.getElementById("charcounter").style.color = "#FFA500";
+ } else if (length > 999) {
+ document.getElementById("charcounter").style.color = "#F1C40F";
+ } else {
+ document.getElementById("charcounter").style.color = "#FFFFFF";
+ }
+ }, 50);
+ });
+ }
+}
\ No newline at end of file
diff --git a/plugins/dblclickedit.js b/plugins/dblclickedit.js
new file mode 100644
index 0000000..7722d64
--- /dev/null
+++ b/plugins/dblclickedit.js
@@ -0,0 +1,20 @@
+exports.meta = {
+ author: "Cynosphere, Jiiks",
+ name: "Double Click Edit",
+ desc: "Double click messages to edit them."
+}
+exports.start = function(){
+ document.addEventListener("dblclick", ev => {
+ let target = ev.target;
+ if(target.className.includes("markup")) {
+ let msg = target;
+ let opt = msg.parentNode.querySelector(".btn-option");
+ opt.click();
+ let options = document.querySelectorAll(".option-popout .btn-item");
+ for(i in options){
+ let o = options[i];
+ if(o.innerHTML == "Edit") o.click();
+ }
+ }
+ });
+}
\ No newline at end of file
diff --git a/plugins/fixmentions.js b/plugins/fixmentions.js
new file mode 100644
index 0000000..9b1ec02
--- /dev/null
+++ b/plugins/fixmentions.js
@@ -0,0 +1,14 @@
+exportsexports = {
+ meta: {
+ author: "Cynosphere, Adryd",
+ name: "Mentions Fixer",
+ desc: "See hidden channel mentions and remove #deleted-channel."
+ },
+ replacements: {
+ '/t\\.type===(.+)\\.ChannelTypes\\.GUILD_TEXT&&(.+)\\.default\\.can\\((.+)\\.Permissions\\.VIEW_CHANNEL,n,t\\)/':'true',
+ '/"#deleted-channel"/':'"<#"+e[1]+">"',
+ '/"@deleted-role"/':'"<@&"+e[1]+">"',
+ '/guildId:null!=t\\?t\\.guild_id:null,/':'guildId:null!=t.guild_id?t.guild_id:"@me",',
+ '/"#deleted-channel"/':'"<#"+e[1]+">"'
+ }
+}
\ No newline at end of file
diff --git a/plugins/imgxis.js b/plugins/imgxis.js
new file mode 100644
index 0000000..2ebc5cc
--- /dev/null
+++ b/plugins/imgxis.js
@@ -0,0 +1,452 @@
+/*
+IMGXIS made by YellowAfterlife. http://yellowafterlife.itch.io/imgxis (mirror: https://github.com/Cynosphere/IMGXIS)
+Ported by Jane (maybejane/statefram) and Cynthia (BoxOfFlex/Cynosphere) to a private client mod.
+Re-ported by Cynthia to EndPwn.
+*/
+exports.meta = {
+ author: "Cynosphere, YellowAfterlife",
+ name: "IMGXIS",
+ desc: "A better way to view images."
+}
+exports.start = function(){
+ var globalPanner;
+ var values = {};
+
+ /*function _hook() {
+ const imageWrapper = document.querySelectorAll(
+ 'div[class^="modal-"] div[class^="inner-"] div div[class^="imageWrapper-"]'
+ )[0];
+ const image = imageWrapper.children[0].className == "imgxis-panner" ? imageWrapper.children[0].children[0] : imageWrapper.children[0];
+ if (imageWrapper && image) {
+ onImageOpen(image);
+ } else {
+ onImageClose(globalPanner);
+ }
+ }
+ window._hook = _hook;*/
+
+ function onLoad() {
+ var css = document.createElement("style");
+ css.type = "text/css";
+ css.id = "css-imgxis";
+ var menuButtonWidth = 32; // in pixels
+ css.innerHTML = [
+ "body {",
+ " background-image: none;",
+ " overflow: hidden;",
+ "}",
+ // panner area
+ ".imgxis-panner {",
+ " position: absolute;",
+ " left: 0; width: 100%;",
+ " top: 0; height: 100%;",
+ "}",
+ // cursors
+ ".imgxis-panner, .imgxis-panner img {",
+ " cursor: move;",
+ "}",
+ ".imgxis-panner.colorpick, .imgxis-panner.colorpick img {",
+ " cursor: default;",
+ " cursor: copy;",
+ "}",
+ // disable interpolation:
+ ".imgxis-panner.zoomed, .imgxis-panner.zoomed img {",
+ " -ms-interpolation-mode: nearest-neighbor;",
+ " image-rendering: optimizeSpeed;",
+ " image-rendering: -moz-crisp-edges;",
+ " image-rendering: -webkit-optimize-contrast;",
+ " image-rendering: -o-crisp-edges;",
+ " image-rendering: pixelated;",
+ "}",
+ // actual image:
+ ".imgxis-panner img {",
+ // disable Firefox' centering:
+ " position: relative;",
+ " margin: 0;",
+ "}",
+ // menu pane on top:
+ ".imgxis-menu {",
+ " position: absolute;",
+ " left: 0; width: 100%;",
+ " top: 0; height: 24px;",
+ " background: rgba(0, 0, 0, 0.7);",
+ " padding: 4px;",
+ " color: white;",
+ " font: 16px sans-serif;",
+ " line-height: 24px;",
+ " min-width: 1024px;",
+ "}",
+ // style the buttons in the menu:
+ ".imgxis-menu input {",
+ ` width: ${menuButtonWidth}px;`,
+ " height: 24px;",
+ " padding: 0px;",
+ " margin-right: 2px;",
+ " float: left;",
+ "}"
+ ].join("\n");
+ document.head.appendChild(css);
+ }
+ onLoad();
+
+ function onImageOpen(element) {
+ let wrap = document.querySelector('div[class*="imageWrapper-"]');
+ const openOrig = document.querySelectorAll(
+ 'div a[class^="downloadLink-"]'
+ )[0];
+ openOrig.style.display = "none";
+ const origLink = openOrig.getAttribute("href");
+ var menuColors = 10; // how many color buttons to show in the menu
+ // ensure that the page contains nothing but a single :
+ var q = element;
+ if (q.tagName != "IMG") return;
+ // stylesheet:
+ // the thing is that browser has it's own zoom in controls, and they are
+ // linked to that particular natively. So we need to make a copy of it, strip
+ // it of any unneeded styles, and put it in place of the original:
+ var o = q.cloneNode();
+ //remove size downscaling
+ let reg = /\?width=\d+&height=\d+$/;
+ let os = o.getAttribute("src");
+ let repl = os.replace(reg, "");
+ o.setAttribute("src", repl);
+ //
+ o.removeAttribute("width");
+ o.removeAttribute("height");
+ var panner = document.createElement("div");
+ globalPanner = panner;
+
+ function check() {
+ if (!wrap) {
+ onImageClose(globalPanner);
+ }
+ }
+ var pannerZoomed = false;
+ panner.className = "imgxis-panner";
+ panner.style.height = "100vh";
+ panner.style.width = "100vw";
+ panner.appendChild(o);
+ q.parentNode.appendChild(panner);
+ //q.parentNode.removeChild(q);
+ q.style.display = "none";
+
+ function fixWrap() {
+ let elems = document.querySelectorAll(
+ 'div[class^="modal-"] > div[class^="inner-"] > div > div[class^="imageWrapper-"]'
+ );
+ elems.forEach(el => {
+ fixElem(el);
+ });
+ }
+
+ function fixElem(elemToFix) {
+ if (electron.process.platform == "win32" || electron.process.platform == "darwin") {
+ menu.style.zIndex = 3003;
+ menu.style.top = "22px";
+ menu.style.position = "absolute";
+ menu.style.left = 0;
+ elemToFix.style.top = "22px";
+ elemToFix.style.height = "calc(100vh - 22px)";
+ } else {
+ elemToFix.style.height = "100vh";
+ elemToFix.style.top = 0;
+ }
+
+ elemToFix.style.width = "100vw";
+ elemToFix.style.position = "absolute";
+ elemToFix.style.zIndex = 3002;
+ elemToFix.style.left = 0;
+ }
+
+ var s = o.style;
+ s.transformOrigin = "top left";
+ q = null;
+ var tile = false;
+ //
+ var w = 0,
+ h = 0,
+ x = 0,
+ y = 0,
+ z = 0,
+ m = 1;
+ // pan and zoom:
+ function update() {
+ var pz = m > 1;
+ if (pz != pannerZoomed) {
+ pannerZoomed = pz;
+ var cl = panner.classList;
+ if (pz) cl.add("zoomed");
+ else cl.remove("zoomed");
+ }
+ if (tile) {
+ var p = panner.style;
+ p.backgroundPosition = `${-x}px ${-y}px`;
+ p.backgroundSize = `${o.width * m}px`;
+ }
+ s.transform = `matrix(${m},0,0,${m},${-x},${-y})`;
+ }
+
+ function zoomto(zx, zy, d) {
+ var p = m;
+ m = Math.pow(2, (z += d));
+ var f = m / p;
+ x = (zx + x) * f - zx;
+ y = (zy + y) * f - zy;
+ menu_zoom.innerHTML = (0 | (m * 100)) + "%";
+ update();
+ }
+
+ // menu:
+ var menu_zoom = null,
+ menu;
+ (function() {
+ menu = document.createElement("div");
+ menu.style.zIndex = 2000;
+ menu.className = "imgxis-menu";
+
+ function menubt(s, f, style) {
+ var bt = document.createElement("input");
+ bt.type = "button";
+ bt.value = s;
+ if (style) {
+ bt.style = style;
+ }
+ bt.addEventListener("click", f);
+ menu.appendChild(bt);
+ }
+ var defcolors = [
+ "#6A86B7",
+ "#60C19D",
+ "#AB7680",
+ "#FFFFFF",
+ "#F4F2EC",
+ "#CAC2BD",
+ "#88898E",
+ "#4F556A",
+ "#1D1F2C",
+ "#000000"
+ ];
+
+ function menucl(i) {
+ var bt = document.createElement("input");
+ bt.type = "color";
+ bt.style.zIndex = 1001;
+ bt.value = GM_getValue(`imgxis-color${i}`, defcolors[i]);
+
+ function bt_apply() {
+ let bodyElem = document.querySelector('div[class*="backdrop-"]');
+ bodyElem.style.backgroundColor = bt.value;
+ }
+ bt.addEventListener("click", function(e) {
+ if (!e.shiftKey) {
+ e.preventDefault();
+ bt_apply();
+ }
+ });
+ bt.addEventListener("change", function(e) {
+ GM_setValue(`imgxis-color${i}`, bt.value);
+ bt_apply();
+ });
+ bt.title = `Color ${i + 1}.\nClick to apply.\nShift+click to change.`;
+ menu.appendChild(bt);
+ if (i == 0) bt_apply();
+ }
+ for (var i = 0; i < menuColors; i++) menucl(i);
+ menubt("-", function(_) {
+ zoomto(window.innerWidth / 2, window.innerHeight / 2, -0.5);
+ });
+ menubt("1:1", function(_) {
+ var iw = window.innerWidth,
+ ih = window.innerHeight;
+ zoomto(iw / 2, ih / 2, -z);
+ x = (w - iw) / 2;
+ y = (h - ih) / 2;
+ update();
+ });
+ menubt("+", function(_) {
+ zoomto(window.innerWidth / 2, window.innerHeight / 2, 0.5);
+ });
+ menubt("tile", function(_) {
+ tile = !tile;
+ panner.style.backgroundImage = tile ? `url(${o.src})` : "";
+ update();
+ });
+ menubt(
+ "original",
+ function(_) {
+ window.open(origLink);
+ },
+ "width:50px;"
+ );
+ //
+ menu_zoom = document.createElement("span");
+ menu_zoom.innerHTML = "100%";
+ menu.appendChild(menu_zoom);
+ document
+ .querySelector('div[class*="backdrop-"]')
+ .parentNode.appendChild(menu);
+ })();
+ // mouse controls:
+ function onmousewheel(e) {
+ var d = e.wheelDelta || -e.detail;
+ d = (d < 0 ? -1 : d > 0 ? 1 : 0) * 0.5;
+ zoomto(e.pageX, e.pageY, d);
+ check();
+ }
+ var mx = 0,
+ my = 0,
+ mp = false;
+
+ function onmousemove(e) {
+ var ox = mx;
+ mx = e.pageX;
+ var dx = mx - ox;
+ var oy = my;
+ my = e.pageY;
+ var dy = my - oy;
+ if (mp) {
+ x -= mx - ox;
+ y -= my - oy;
+ update();
+ }
+ check();
+ }
+
+ function onmousedown(e) {
+ onmousemove(e);
+ fixWrap();
+ if (e.which != 3 && !e.altKey) {
+ // not the right click
+ e.preventDefault(); // disable image "grab"
+ mp = true;
+ }
+ }
+
+ function onmouseup(e) {
+ onmousemove(e);
+ fixWrap();
+ mp = false;
+ }
+
+ //
+ var refresh_timer = null;
+
+ function refresh() {
+ var o_src = o.src;
+ // strip the previous timestamp parameter:
+ var pos = o_src.indexOf("imgxis-time");
+ if (pos >= 0) {
+ switch (o_src.charAt(pos - 1)) {
+ case "&":
+ case "?":
+ pos--;
+ break;
+ }
+ o_src = o_src.substring(0, pos);
+ }
+ // add a timestamp parameter:
+ o_src += o_src.indexOf("?") >= 0 ? "&" : "?";
+ o_src += `imgxis-time=${Date.now()}`;
+ //
+ o.src = o_src;
+ if (tile) panner.style.backgroundImage = `url(${o.src})`;
+ }
+
+ //
+ function onkeydown(e) {
+ switch (e.keyCode) {
+ case 27:
+ onImageClose(globalPanner);
+ break;
+ case 116:
+ e.preventDefault();
+ if (e.shiftKey) {
+ if (refresh_timer == null) {
+ var t = parseFloat(prompt("Refresh interval (seconds)", "15"));
+ if (!isNaN(t))
+ refresh_timer = setInterval(refresh, Math.max(100, t * 1000));
+ } else clearInterval(refresh_timer);
+ } else refresh();
+ break;
+ }
+ }
+ window.addEventListener("mousemove", onmousemove);
+ panner.addEventListener("mousedown", onmousedown);
+ window.addEventListener("mouseup", onmouseup);
+ window.addEventListener("mousewheel", onmousewheel);
+ window.addEventListener("DOMMouseScroll", onmousewheel);
+ window.addEventListener("keydown", onkeydown);
+ // consider that the image dimensions may not be available instantly:
+ var onwait_t = 1000;
+ var onwait = function onwait() {
+ w = o.width;
+ h = o.height;
+ if (w > 0 && h > 0) {
+ var iw = window.innerWidth,
+ lw = w;
+ var ih = window.innerHeight,
+ lh = h;
+ if (lw < iw && lh < ih)
+ for (var k = 0; k < 3; k++) {
+ if (lw * 2 < iw && lh * 2 < ih) {
+ z++;
+ lw *= 2;
+ lh *= 2;
+ }
+ }
+ else
+ while (lw > iw || lh > ih) {
+ z--;
+ lw /= 2;
+ lh /= 2;
+ }
+ m = Math.pow(2, z);
+ menu_zoom.innerHTML = (0 | (m * 100)) + "%";
+ x = -(iw - lw) / 2;
+ y = -(ih - lh) / 2;
+ update();
+ fixWrap();
+ } else if (--onwait_t > 0) setTimeout(onwait, 10);
+ };
+ onwait();
+ }
+
+ function onImageClose(panner) {
+ if (panner) {
+ document.querySelectorAll('div[class*="imgxis-menu"]').forEach(el=>el.remove());
+ window.removeEventListener("mousemove", onmousemove);
+ panner.removeEventListener("mousedown", onmousedown);
+ window.removeEventListener("mouseup", onmouseup);
+ window.removeEventListener("mousewheel", onmousewheel);
+ window.removeEventListener("DOMMouseScroll", onmousewheel);
+ window.removeEventListener("keydown", onkeydown);
+ globalPanner = undefined;
+ }
+ }
+
+ function GM_getValue(name, defaultV) {
+ if (values[name]) {
+ return values[name];
+ } else {
+ return defaultV;
+ }
+ }
+
+ function GM_setValue(name, value) {
+ values[name] = value;
+ }
+
+ $api.events.hook("MODAL_PUSH",e=>{
+ setTimeout(_=>{
+ const imageWrapper = document.querySelectorAll('div[class^="modal-"] div[class^="inner-"] div div[class^="imageWrapper-"]')[0];
+ const image = (imageWrapper && imageWrapper.children[0].className == "imgxis-panner") ? imageWrapper.children[0].children[0] : (imageWrapper ? imageWrapper.children[0] : imageWrapper);
+ if (imageWrapper && image) {
+ onImageOpen(image);
+ }
+ },300);
+ });
+
+ $api.events.hook("MODAL_POP",e=>{
+ onImageClose(globalPanner);
+ });
+}
\ No newline at end of file
diff --git a/plugins/localstorage.js b/plugins/localstorage.js
new file mode 100644
index 0000000..583f19b
--- /dev/null
+++ b/plugins/localstorage.js
@@ -0,0 +1,10 @@
+exports = {
+ meta: {
+ author: "Cynosphere, Adryd",
+ name: "Restore Local Storage",
+ desc: "Restore window.localStorage."
+ },
+ replacements: {
+ 'try{delete window.localStorage}catch(e){}':''
+ }
+}
\ No newline at end of file
diff --git a/plugins/notifsound.js b/plugins/notifsound.js
new file mode 100644
index 0000000..c3d79d7
--- /dev/null
+++ b/plugins/notifsound.js
@@ -0,0 +1,20 @@
+exports.meta = {
+ author: "dr1ft",
+ name: "Notification Sound Replacer",
+ desc: "Replaces notification sound with an unused sound thats nicer."
+}
+exports.start = function(){
+ var s = wc.findFunc('playSound')[1].exports;
+ s.playSoundOriginal = s.playSound;
+ s.playSound = function (e) {
+ switch (e) {
+ case "message1":
+ return s.playSoundOriginal('message3');
+ //new Audio(window._sndURL).play();
+ //return s.playSoundOriginal(e, 0);
+ default:
+ return s.playSoundOriginal(e);
+ break;
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/osutyping.js b/plugins/osutyping.js
new file mode 100644
index 0000000..118f1e8
--- /dev/null
+++ b/plugins/osutyping.js
@@ -0,0 +1,31 @@
+exportsexports = {
+ meta: {
+ author: "Cynosphere",
+ name: "osu! Typing",
+ desc: "Adds typing sounds from osu!."
+ },
+ start: function(){
+ var sounds = [];
+ for (var i = 1; i < 4; i++) {
+ sounds.push(new Audio(`https://github.com/statefram/sounds/blob/master/osu_typing_click${i}.wav?raw=true`));
+ }
+ const backspace = new Audio("https://github.com/statefram/sounds/blob/master/osu_typing_erase.wav?raw=true");
+
+ var keys = {};
+ function typingSound(ev) {
+ for (const sound of sounds) {
+ sound.pause();
+ sound.currentTime = 0;
+ }
+ backspace.pause();
+ backspace.currentTime = 0;
+ if (ev.key == "Backspace" || ev.key == "Delete") {
+ backspace.play();
+ } else {
+ sounds[Math.floor(sounds.length * Math.random())].play();
+ }
+ return false;
+ }
+ document.addEventListener("keydown", typingSound);
+ }
+}
\ No newline at end of file
diff --git a/plugins/spotifyspoof.js b/plugins/spotifyspoof.js
new file mode 100644
index 0000000..6457fbf
--- /dev/null
+++ b/plugins/spotifyspoof.js
@@ -0,0 +1,10 @@
+exports = {
+ meta: {
+ author: "Cynosphere",
+ name: "Spotify Premium Spoof",
+ desc: "Spoofs premium check and allows listen along without premium."
+ },
+ replacements: {
+ 'r.isPremium=n':'r.isPremium=true'
+ }
+}
\ No newline at end of file