do some patching
This commit is contained in:
parent
c56710569b
commit
73ee66e7d6
50
js/main.js
50
js/main.js
File diff suppressed because one or more lines are too long
|
@ -1,5 +1,5 @@
|
|||
import Builtin from "../structs/builtin";
|
||||
import {Utilities, DiscordModules} from "modules";
|
||||
import {DiscordModules} from "modules";
|
||||
|
||||
export default new class TwentyFourHour extends Builtin {
|
||||
get name() {return "24Hour";}
|
||||
|
@ -11,24 +11,19 @@ export default new class TwentyFourHour extends Builtin {
|
|||
}
|
||||
|
||||
disabled() {
|
||||
if (!this.cancel24Hour) return;
|
||||
this.cancel24Hour();
|
||||
delete this.cancel24Hour;
|
||||
this.unpatchAll();
|
||||
}
|
||||
|
||||
inject24Hour() {
|
||||
if (this.cancel24Hour) return;
|
||||
|
||||
const twelveHour = new RegExp(`([0-9]{1,2}):([0-9]{1,2})\\s(AM|PM)`);
|
||||
const convert = (data) => {
|
||||
const matched = data.returnValue.match(twelveHour);
|
||||
const convert = (thisObject, args, returnValue) => {
|
||||
const matched = returnValue.match(twelveHour);
|
||||
if (!matched || matched.length !== 4) return;
|
||||
if (matched[3] === "AM") return data.returnValue = data.returnValue.replace(matched[0], `${matched[1] === "12" ? "00" : matched[1].padStart(2, "0")}:${matched[2]}`);
|
||||
return data.returnValue = data.returnValue.replace(matched[0], `${matched[1] === "12" ? "12" : parseInt(matched[1]) + 12}:${matched[2]}`);
|
||||
if (matched[3] === "AM") return returnValue = returnValue.replace(matched[0], `${matched[1] === "12" ? "00" : matched[1].padStart(2, "0")}:${matched[2]}`);
|
||||
return returnValue = returnValue.replace(matched[0], `${matched[1] === "12" ? "12" : parseInt(matched[1]) + 12}:${matched[2]}`);
|
||||
};
|
||||
|
||||
const cancelCozy = Utilities.monkeyPatch(DiscordModules.TimeFormatter, "calendarFormat", {after: convert}); // Called in Cozy mode
|
||||
const cancelCompact = Utilities.monkeyPatch(DiscordModules.TimeFormatter, "dateFormat", {after: convert}); // Called in Compact mode
|
||||
this.cancel24Hour = () => {cancelCozy(); cancelCompact();}; // Cancel both
|
||||
this.after(DiscordModules.TimeFormatter, "calendarFormat", convert); // Called in Cozy mode
|
||||
this.after(DiscordModules.TimeFormatter, "dateFormat", convert); // Called in Compact mode
|
||||
}
|
||||
};
|
|
@ -1,5 +1,5 @@
|
|||
import Builtin from "../structs/builtin";
|
||||
import {Utilities, WebpackModules} from "modules";
|
||||
import {WebpackModules} from "modules";
|
||||
|
||||
const MessageContent = WebpackModules.getModule(m => m.defaultProps && m.defaultProps.hasOwnProperty("disableButtons"));
|
||||
|
||||
|
@ -13,22 +13,17 @@ export default new class ColoredText extends Builtin {
|
|||
}
|
||||
|
||||
disabled() {
|
||||
if (!this.cancelColoredText) return;
|
||||
this.cancelColoredText();
|
||||
delete this.cancelColoredText;
|
||||
this.unpatchAll();
|
||||
}
|
||||
|
||||
injectColoredText() {
|
||||
if (this.cancelColoredText) return;
|
||||
|
||||
this.cancelColoredText = Utilities.monkeyPatch(MessageContent.prototype, "render", {after: (data) => {
|
||||
Utilities.monkeyPatch(data.returnValue.props, "children", {silent: true, after: ({returnValue}) => {
|
||||
this.after(MessageContent.prototype, "render", (thisObject, args, retVal) => {
|
||||
this.after(retVal.props, "children", {silent: true, after: ({returnValue}) => {
|
||||
const markup = returnValue.props.children[1];
|
||||
const roleColor = data.thisObject.props.message.colorString;
|
||||
const roleColor = thisObject.props.message.colorString;
|
||||
if (markup && roleColor) markup.props.style = {color: roleColor};
|
||||
return returnValue;
|
||||
}});
|
||||
}});
|
||||
});
|
||||
}
|
||||
|
||||
removeColoredText() {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Builtin from "../structs/builtin";
|
||||
import {Settings, DataStore, React, Utilities, WebpackModules, Events, DOMManager} from "modules";
|
||||
import {Settings, DataStore, React, WebpackModules, Events, DOMManager} from "modules";
|
||||
import CSSEditor from "../ui/customcss/csseditor";
|
||||
import FloatingWindowContainer from "../ui/floating/container";
|
||||
import SettingsTitle from "../ui/settings/title";
|
||||
|
@ -26,7 +26,7 @@ export default new class CustomCSS extends Builtin {
|
|||
|
||||
async enabled() {
|
||||
if (!window.ace) {
|
||||
Utilities.injectJs("https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.9/ace.js").then(() => {
|
||||
DOMManager.injectScript("ace-script", "https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.9/ace.js").then(() => {
|
||||
if (window.require.original) window.require = window.require.original;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -84,8 +84,8 @@ export default new class EmoteModule extends Builtin {
|
|||
|
||||
patchMessageContent() {
|
||||
if (this.cancelEmoteRender) return;
|
||||
this.cancelEmoteRender = Utilities.monkeyPatch(this.MessageContentComponent.prototype, "render", {after: ({retVal}) => {
|
||||
Utilities.monkeyPatch(retVal.props, "children", {silent: true, after: ({returnValue}) => {
|
||||
this.cancelEmoteRender = this.after(this.MessageContentComponent.prototype, "render", (thisObj, args, retVal) => {
|
||||
this.after(retVal.props, "children", (t, a, returnValue) => {
|
||||
if (this.categories.length == 0) return;
|
||||
const markup = returnValue.props.children[1];
|
||||
if (!markup.props.children) return;
|
||||
|
@ -146,8 +146,8 @@ export default new class EmoteModule extends Builtin {
|
|||
if (node.type.name == "BDEmote") node.props.jumboable = true;
|
||||
else if (node.props && node.props.children && node.props.children.props && node.props.children.props.emojiName) node.props.children.props.jumboable = true;
|
||||
}
|
||||
}});
|
||||
}});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async loadEmoteData(emoteInfo) {
|
||||
|
|
|
@ -14,22 +14,22 @@ export default new class MinimalMode extends Builtin {
|
|||
}
|
||||
|
||||
enabled() {
|
||||
$("body").addClass("bd-minimal");
|
||||
document.body.classList.add("bd-minimal");
|
||||
if (this.hideChannels) this.enableHideChannels();
|
||||
this.hideChannelCancel = this.registerSetting(this.hideChannelsID, this.enableHideChannels, this.disableHideChannels);
|
||||
}
|
||||
|
||||
disabled() {
|
||||
$("body").removeClass("bd-minimal");
|
||||
document.body.classList.remove("bd-minimal");
|
||||
if (this.hideChannels) this.disableHideChannels();
|
||||
if (this.hideChannelCancel) this.hideChannelCancel();
|
||||
}
|
||||
|
||||
enableHideChannels() {
|
||||
$("body").addClass("bd-minimal-chan");
|
||||
document.body.classList.add("bd-minimal-chan");
|
||||
}
|
||||
|
||||
disableHideChannels() {
|
||||
$("body").removeClass("bd-minimal-chan");
|
||||
document.body.classList.remove("bd-minimal-chan");
|
||||
}
|
||||
};
|
|
@ -6,20 +6,14 @@ export default new class VoiceMode extends Builtin {
|
|||
get id() {return "voiceMode";}
|
||||
|
||||
enabled() {
|
||||
$(".scroller.guild-channels ul").first().css("display", "none");
|
||||
$(".scroller.guild-channels header").first().css("display", "none");
|
||||
$(".app.flex-vertical, .app-2rEoOp").first().css("overflow", "hidden");
|
||||
$(".chat-3bRxxu").first().css("visibility", "hidden").css("min-width", "0px");
|
||||
$(".flex-vertical.channels-wrap").first().css("flex-grow", "100000");
|
||||
$(".guild-header .btn.btn-hamburger").first().css("visibility", "hidden");
|
||||
document.querySelector(".chat-3bRxxu").style.setProperty("visibility", "hidden");
|
||||
document.querySelector(".chat-3bRxxu").style.setProperty("min-width", "0px");
|
||||
document.querySelector(".channels-Ie2l6A").style.setProperty("flex-grow", "100000");
|
||||
}
|
||||
|
||||
disabled() {
|
||||
$(".scroller.guild-channels ul").first().css("display", "");
|
||||
$(".scroller.guild-channels header").first().css("display", "");
|
||||
$(".app.flex-vertical, .app-2rEoOp").first().css("overflow", "");
|
||||
$(".chat-3bRxxu").first().css("visibility", "").css("min-width", "");
|
||||
$(".flex-vertical.channels-wrap").first().css("flex-grow", "");
|
||||
$(".guild-header .btn.btn-hamburger").first().css("visibility", "");
|
||||
document.querySelector(".chat-3bRxxu").style.setProperty("visibility", "");
|
||||
document.querySelector(".chat-3bRxxu").style.setProperty("min-width", "");
|
||||
document.querySelector(".channels-Ie2l6A").style.setProperty("flex-grow", "");
|
||||
}
|
||||
};
|
|
@ -9,6 +9,7 @@ import Settings from "./modules/settingsmanager";
|
|||
import DataStore from "./modules/datastore";
|
||||
import EmoteModule from "./builtins/emotes";
|
||||
import DomManager from "./modules/dommanager";
|
||||
import Utilities from "./modules/utilities";
|
||||
|
||||
// Perform some setup
|
||||
// proxyLocalStorage();
|
||||
|
@ -35,7 +36,7 @@ window.DataStore = DataStore;
|
|||
|
||||
|
||||
window.DomManager = DomManager;
|
||||
|
||||
window.utils = Utilities;
|
||||
|
||||
window.BDEvents = Events;
|
||||
window.bdConfig = Config;
|
||||
|
|
|
@ -40,7 +40,7 @@ export default new class {
|
|||
if (this.guildListItemsPatch) return;
|
||||
const listItemClass = this.guildClasses.listItem.split(" ")[0];
|
||||
const blobClass = this.guildClasses.blobContainer.split(" ")[0];
|
||||
const reactInstance = Utilities.getInternalInstance(document.querySelector(`.${listItemClass} .${blobClass}`).parentElement);
|
||||
const reactInstance = Utilities.getReactInstance(document.querySelector(`.${listItemClass} .${blobClass}`).parentElement);
|
||||
const GuildComponent = reactInstance.return.type;
|
||||
if (!GuildComponent) return;
|
||||
this.guildListItemsPatch = Utilities.monkeyPatch(GuildComponent.prototype, "render", {after: (data) => {
|
||||
|
|
|
@ -15,6 +15,13 @@ Module.globalPaths.push(path.resolve(require("electron").remote.app.getAppPath()
|
|||
const splitRegex = /[^\S\r\n]*?\n[^\S\r\n]*?\*[^\S\r\n]?/;
|
||||
const escapedAtRegex = /^\\@/;
|
||||
|
||||
const stripBOM = function(content) {
|
||||
if (content.charCodeAt(0) === 0xFEFF) {
|
||||
content = content.slice(1);
|
||||
}
|
||||
return content;
|
||||
};
|
||||
|
||||
export default class ContentManager {
|
||||
|
||||
get name() {return "";}
|
||||
|
@ -136,7 +143,7 @@ export default class ContentManager {
|
|||
const possiblePath = path.resolve(self.contentFolder, path.basename(filename));
|
||||
if (!fs.existsSync(possiblePath) || filename !== fs.realpathSync(possiblePath)) return Reflect.apply(originalRequire, this, arguments);
|
||||
let content = fs.readFileSync(filename, "utf8");
|
||||
content = Utilities.stripBOM(content);
|
||||
content = stripBOM(content);
|
||||
const meta = self.extractMeta(content);
|
||||
meta.id = meta.name;
|
||||
meta.filename = path.basename(filename);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import BDV2 from "./bdv2";
|
||||
import Utilities from "./utilities";
|
||||
import Logger from "./logger";
|
||||
import {Config} from "data";
|
||||
// import EmoteModule from "./emotes";
|
||||
|
|
|
@ -1,22 +1,28 @@
|
|||
export default class DOMManager {
|
||||
|
||||
static get bdHead() { return this.getElement("bd-head"); }
|
||||
static get bdBody() { return this.getElement("bd-body"); }
|
||||
static get bdStyles() { return this.getElement("bd-styles"); }
|
||||
static get bdThemes() { return this.getElement("bd-themes"); }
|
||||
static get bdCustomCSS() { return this.getElement("#customcss"); }
|
||||
static get bdTooltips() { return this.getElement("bd-tooltips") || this.createElement("bd-tooltips").appendTo(this.bdBody); }
|
||||
static get bdModals() { return this.getElement("bd-modals") || this.createElement("bd-modals").appendTo(this.bdBody); }
|
||||
static get bdToasts() { return this.getElement("bd-toasts") || this.createElement("bd-toasts").appendTo(this.bdBody); }
|
||||
static get bdHead() {return this.getElement("bd-head");}
|
||||
static get bdBody() {return this.getElement("bd-body");}
|
||||
static get bdScripts() {return this.getElement("bd-scripts");}
|
||||
static get bdStyles() {return this.getElement("bd-styles");}
|
||||
static get bdThemes() {return this.getElement("bd-themes");}
|
||||
static get bdCustomCSS() {return this.getElement("#customcss");}
|
||||
// static get bdTooltips() { return this.getElement("bd-tooltips") || this.createElement("bd-tooltips").appendTo(this.bdBody); }
|
||||
// static get bdModals() { return this.getElement("bd-modals") || this.createElement("bd-modals").appendTo(this.bdBody); }
|
||||
// static get bdToasts() { return this.getElement("bd-toasts") || this.createElement("bd-toasts").appendTo(this.bdBody); }
|
||||
|
||||
static initialize() {
|
||||
this.createElement("bd-head", {target: document.head});
|
||||
this.createElement("bd-body", {target: document.body});
|
||||
this.createElement("bd-scripts", {target: this.bdHead});
|
||||
this.createElement("bd-styles", {target: this.bdHead});
|
||||
this.createElement("bd-themes", {target: this.bdHead});
|
||||
this.createElement("style", {id: "customcss", target: this.bdHead});
|
||||
}
|
||||
|
||||
static escapeID(id) {
|
||||
return id.replace(/^[^a-z]+|[^\w-]+/gi, "-");
|
||||
}
|
||||
|
||||
static getElement(e, baseElement = document) {
|
||||
if (e instanceof Node) return e;
|
||||
return baseElement.querySelector(e);
|
||||
|
@ -32,22 +38,26 @@ export default class DOMManager {
|
|||
}
|
||||
|
||||
static removeStyle(id) {
|
||||
id = this.escapeID(id);
|
||||
const exists = this.getElement(`#${id}`, this.bdStyles);
|
||||
if (exists) exists.remove();
|
||||
}
|
||||
|
||||
static injectStyle(id, css) {
|
||||
id = this.escapeID(id);
|
||||
const style = this.getElement(`#${id}`, this.bdStyles) || this.createElement("style", {id});
|
||||
style.textContent = css;
|
||||
this.bdStyles.append(style);
|
||||
}
|
||||
|
||||
static removeTheme(id) {
|
||||
id = this.escapeID(id);
|
||||
const exists = this.getElement(`#${id}`, this.bdThemes);
|
||||
if (exists) exists.remove();
|
||||
}
|
||||
|
||||
static injectTheme(id, css) {
|
||||
id = this.escapeID(id);
|
||||
const style = this.getElement(`#${id}`, this.bdThemes) || this.createElement("style", {id});
|
||||
style.textContent = css;
|
||||
this.bdThemes.append(style);
|
||||
|
@ -56,4 +66,20 @@ export default class DOMManager {
|
|||
static updateCustomCSS(css) {
|
||||
this.bdCustomCSS.textContent = css;
|
||||
}
|
||||
|
||||
static removeScript(id) {
|
||||
id = this.escapeID(id);
|
||||
const exists = this.getElement(`#${id}`, this.bdScripts);
|
||||
if (exists) exists.remove();
|
||||
}
|
||||
|
||||
static injectScript(id, url) {
|
||||
id = this.escapeID(id);
|
||||
return new Promise(resolve => {
|
||||
const script = this.getElement(`#${id}`, this.bdScripts) || this.createElement("script", {id});
|
||||
script.src = url;
|
||||
script.onload = resolve;
|
||||
this.bdScripts.append(script);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -19,10 +19,11 @@ import ThemeManager from "./thememanager";
|
|||
import Settings from "./settingsmanager";
|
||||
import DOMManager from "./dommanager";
|
||||
import Logger from "./logger";
|
||||
import Patcher from "./patcher";
|
||||
|
||||
export const React = DiscordModules.React;
|
||||
export const ReactDOM = DiscordModules.ReactDOM;
|
||||
|
||||
export {BDV2, BdApi, Core, ContentManager, DataStore, Logger,
|
||||
Events, PluginManager, DOMManager, ThemeManager,
|
||||
Events, PluginManager, DOMManager, ThemeManager, Patcher,
|
||||
Utilities, WebpackModules, DiscordModules, Settings};
|
||||
|
|
|
@ -46,28 +46,26 @@ BdApi.setWindowPreference = function(key, value) {
|
|||
//id = id of element
|
||||
//css = custom css
|
||||
BdApi.injectCSS = function (id, css) {
|
||||
DOMManager.injectStyle(Utilities.escapeID(id), css);
|
||||
// $("head").append($("<style>", {id: Utilities.escapeID(id), text: css}));
|
||||
DOMManager.injectStyle(id, css);
|
||||
};
|
||||
|
||||
//Clear css/remove any element
|
||||
//id = id of element
|
||||
BdApi.clearCSS = function (id) {
|
||||
DOMManager.removeStyle(Utilities.escapeID(id));
|
||||
// $("#" + Utilities.escapeID(id)).remove();
|
||||
DOMManager.removeStyle(id);
|
||||
};
|
||||
|
||||
//Inject CSS to document head
|
||||
//id = id of element
|
||||
//css = custom css
|
||||
BdApi.linkJS = function (id, url) {
|
||||
$("head").append($("<script>", {id: Utilities.escapeID(id), src: url, type: "text/javascript"}));
|
||||
return DOMManager.injectScript(id, url);
|
||||
};
|
||||
|
||||
//Clear css/remove any element
|
||||
//id = id of element
|
||||
BdApi.unlinkJS = function (id) {
|
||||
$("#" + Utilities.escapeID(id)).remove();
|
||||
DOMManager.removeScript(id);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -135,7 +133,7 @@ BdApi.findModuleByDisplayName = function(name) {
|
|||
BdApi.getInternalInstance = function(node) {
|
||||
if (!(node instanceof window.jQuery) && !(node instanceof Element)) return undefined;
|
||||
if (node instanceof jQuery) node = node[0];
|
||||
return Utilities.getInternalInstance(node);
|
||||
return Utilities.getReactInstance(node);
|
||||
};
|
||||
|
||||
// Gets data
|
||||
|
|
|
@ -137,7 +137,7 @@ export default new class SettingsManager {
|
|||
forceUpdate() {
|
||||
const viewClass = WebpackModules.getByProps("standardSidebarView").standardSidebarView.split(" ")[0];
|
||||
const node = document.querySelector(`.${viewClass}`);
|
||||
Utilities.getInternalInstance(node).return.return.return.return.return.return.stateNode.forceUpdate();
|
||||
Utilities.getReactInstance(node).return.return.return.return.return.return.stateNode.forceUpdate();
|
||||
}
|
||||
|
||||
getUserSettings() {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import {Config} from "data";
|
||||
import ContentManager from "./contentmanager";
|
||||
import Utilities from "./utilities";
|
||||
import {Modals} from "ui";
|
||||
import Settings from "./settingsmanager";
|
||||
import DOMManager from "./dommanager";
|
||||
|
@ -55,19 +54,12 @@ export default new class ThemeManager extends ContentManager {
|
|||
addTheme(idOrContent) {
|
||||
const content = typeof(idOrContent) == "string" ? this.contentList.find(p => p.id == idOrContent) : idOrContent;
|
||||
if (!content) return;
|
||||
// const style = document.createElement("style");
|
||||
// style.id = Utilities.escapeID(content.id);
|
||||
// style.textContent = unescape(content.css);
|
||||
// document.head.append(style);
|
||||
// content.element = style;
|
||||
DOMManager.injectTheme(Utilities.escapeID(content.id), unescape(content.css));
|
||||
DOMManager.injectTheme(content.id, content.css);
|
||||
}
|
||||
|
||||
removeTheme(idOrContent) {
|
||||
const content = typeof(idOrContent) == "string" ? this.contentList.find(p => p.id == idOrContent) : idOrContent;
|
||||
if (!content) return;
|
||||
// const element = content.element || document.getElementById(Utilities.escapeID(content.id));
|
||||
// if (element) element.remove();
|
||||
DOMManager.removeTheme(Utilities.escapeID(content.id));
|
||||
DOMManager.removeTheme(content.id);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,248 @@
|
|||
/**
|
||||
* Functions that check for and update existing plugins.
|
||||
* @module PluginUpdater
|
||||
* @version 0.1.2
|
||||
*/
|
||||
|
||||
import PluginUtilities from "./pluginutilities";
|
||||
import DOMTools from "./domtools";
|
||||
import Logger from "./logger";
|
||||
import DiscordClasses from "./discordclasses";
|
||||
import {EmulatedTooltip, Toasts} from "ui";
|
||||
|
||||
/**
|
||||
* Function that gets the remote version from the file contents.
|
||||
* @param {string} fileContent - the content of the remote file
|
||||
* @returns {string} - remote version
|
||||
* @callback module:PluginUpdater~versioner
|
||||
*/
|
||||
|
||||
/**
|
||||
* Comparator that takes the current version and the remote version,
|
||||
* then compares them returning `true` if there is an update and `false` otherwise.
|
||||
* @param {string} currentVersion - the current version of the plugin
|
||||
* @param {string} remoteVersion - the remote version of the plugin
|
||||
* @returns {boolean} - whether the plugin has an update or not
|
||||
* @callback module:PluginUpdater~comparator
|
||||
*/
|
||||
|
||||
export default class PluginUpdater {
|
||||
|
||||
static get CSS() { return require("../styles/updates.css"); }
|
||||
|
||||
/**
|
||||
* Checks for updates for the specified plugin at the specified link. The final
|
||||
* parameter should link to the raw text of the plugin and will compare semantic
|
||||
* versions.
|
||||
* @param {string} pluginName - name of the plugin
|
||||
* @param {string} currentVersion - current version (semantic versioning only)
|
||||
* @param {string} updateURL - url to check for update
|
||||
* @param {module:PluginUpdater~versioner} [versioner] - versioner that finds the remote version. If not provided uses {@link module:PluginUpdater.defaultVersioner}.
|
||||
* @param {module:PluginUpdater~comparator} [comparator] - comparator that determines if there is an update. If not provided uses {@link module:PluginUpdater.defaultComparator}.
|
||||
*/
|
||||
static checkForUpdate(pluginName, currentVersion, updateURL, versioner, comparator) {
|
||||
let updateLink = "https://raw.githubusercontent.com/rauenzi/BetterDiscordAddons/master/Plugins/" + pluginName + "/" + pluginName + ".plugin.js";
|
||||
if (updateURL) updateLink = updateURL;
|
||||
if (typeof(versioner) != "function") versioner = this.defaultVersioner;
|
||||
if (typeof(comparator) != "function") comparator = this.defaultComparator;
|
||||
|
||||
if (typeof window.PluginUpdates === "undefined") {
|
||||
window.PluginUpdates = {
|
||||
plugins: {},
|
||||
checkAll: function() {
|
||||
for (const key in this.plugins) {
|
||||
const plugin = this.plugins[key];
|
||||
if (!plugin.versioner) plugin.versioner = PluginUpdater.defaultVersioner;
|
||||
if (!plugin.comparator) plugin.comparator = PluginUpdater.defaultComparator;
|
||||
PluginUpdater.processUpdateCheck(plugin.name, plugin.raw);
|
||||
}
|
||||
},
|
||||
interval: setInterval(() => {
|
||||
window.PluginUpdates.checkAll();
|
||||
}, 7200000)
|
||||
};
|
||||
this.patchPluginList();
|
||||
}
|
||||
|
||||
window.PluginUpdates.plugins[updateLink] = {name: pluginName, raw: updateLink, version: currentVersion, versioner: versioner, comparator: comparator};
|
||||
PluginUpdater.processUpdateCheck(pluginName, updateLink);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will check for updates and automatically show or remove the update notice
|
||||
* bar based on the internal result. Better not to call this directly and to
|
||||
* instead use {@link module:PluginUpdater.checkForUpdate}.
|
||||
* @param {string} pluginName - name of the plugin to check
|
||||
* @param {string} updateLink - link to the raw text version of the plugin
|
||||
*/
|
||||
static processUpdateCheck(pluginName, updateLink) {
|
||||
const request = require("request");
|
||||
request(updateLink, (error, response, result) => {
|
||||
if (error) return;
|
||||
const remoteVersion = window.PluginUpdates.plugins[updateLink].versioner(result);
|
||||
const hasUpdate = window.PluginUpdates.plugins[updateLink].comparator(window.PluginUpdates.plugins[updateLink].version, remoteVersion);
|
||||
if (hasUpdate) this.showUpdateNotice(pluginName, updateLink);
|
||||
else this.removeUpdateNotice(pluginName);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The default versioner used as {@link module:PluginUpdater~versioner} for {@link module:PluginUpdater.checkForUpdate}.
|
||||
* This works on basic semantic versioning e.g. "1.0.0". You do not need to provide this as a versioner if your plugin adheres
|
||||
* to this style as this will be used as default.
|
||||
* @param {string} currentVersion
|
||||
* @param {string} content
|
||||
*/
|
||||
static defaultVersioner(content) {
|
||||
const remoteVersion = content.match(/['"][0-9]+\.[0-9]+\.[0-9]+['"]/i);
|
||||
if (!remoteVersion) return "0.0.0";
|
||||
return remoteVersion.toString().replace(/['"]/g, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* The default comparator used as {@link module:PluginUpdater~comparator} for {@link module:PluginUpdater.checkForUpdate}.
|
||||
* This works on basic semantic versioning e.g. "1.0.0". You do not need to provide this as a comparator if your plugin adheres
|
||||
* to this style as this will be used as default.
|
||||
* @param {string} currentVersion
|
||||
* @param {string} content
|
||||
*/
|
||||
static defaultComparator(currentVersion, remoteVersion) {
|
||||
currentVersion = currentVersion.split(".").map((e) => {return parseInt(e);});
|
||||
remoteVersion = remoteVersion.split(".").map((e) => {return parseInt(e);});
|
||||
|
||||
if (remoteVersion[0] > currentVersion[0]) return true;
|
||||
else if (remoteVersion[0] == currentVersion[0] && remoteVersion[1] > currentVersion[1]) return true;
|
||||
else if (remoteVersion[0] == currentVersion[0] && remoteVersion[1] == currentVersion[1] && remoteVersion[2] > currentVersion[2]) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static patchPluginList() {
|
||||
// Patcher.after("ZeresLibrary", V2C_ContentColumn.prototype, "componentDidMount", (self) => {
|
||||
// if (self._reactInternalFiber.key != "pcolumn") return;
|
||||
// const column = DiscordModules.ReactDOM.findDOMNode(self);
|
||||
// if (!column) return;
|
||||
// const button = column.getElementsByClassName("bd-pfbtn")[0];
|
||||
// if (!button || button.nextElementSibling.classList.contains("bd-updatebtn")) return;
|
||||
// button.after(PluginUpdater.createUpdateButton());
|
||||
// });
|
||||
// const button = document.getElementsByClassName("bd-pfbtn")[0];
|
||||
// if (!button || !button.textContent.toLowerCase().includes("plugin") || button.nextElementSibling.classList.contains("bd-updatebtn")) return;
|
||||
// button.after(PluginUpdater.createUpdateButton());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the update button found in the plugins page of BetterDiscord
|
||||
* settings. Returned button will already have listeners to create the tooltip.
|
||||
* @returns {HTMLElement} check for update button
|
||||
*/
|
||||
static createUpdateButton() {
|
||||
const updateButton = DOMTools.parseHTML(`<button class="bd-pfbtn bd-updatebtn" style="left: 220px;">Check for Updates</button>`);
|
||||
updateButton.onclick = function () {
|
||||
window.PluginUpdates.checkAll();
|
||||
};
|
||||
const tooltip = new EmulatedTooltip(updateButton, "Checks for updates of plugins that support this feature. Right-click for a list.");
|
||||
updateButton.oncontextmenu = function () {
|
||||
if (!window.PluginUpdates || !window.PluginUpdates.plugins) return;
|
||||
tooltip.label = Object.values(window.PluginUpdates.plugins).map(p => p.name).join(", ");
|
||||
tooltip.side = "bottom";
|
||||
tooltip.show();
|
||||
updateButton.onmouseout = function() {
|
||||
tooltip.label = "Checks for updates of plugins that support this feature. Right-click for a list.";
|
||||
tooltip.side = "top";
|
||||
};
|
||||
};
|
||||
return updateButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will download the latest version and replace the the old plugin version.
|
||||
* Will also update the button in the update bar depending on if the user
|
||||
* is using RestartNoMore plugin by square {@link https://github.com/Inve1951/BetterDiscordStuff/blob/master/plugins/restartNoMore.plugin.js}
|
||||
* @param {string} pluginName - name of the plugin to download
|
||||
* @param {string} updateLink - link to the raw text version of the plugin
|
||||
*/
|
||||
static downloadPlugin(pluginName, updateLink) {
|
||||
const request = require("request");
|
||||
const fileSystem = require("fs");
|
||||
const path = require("path");
|
||||
request(updateLink, async (error, response, body) => {
|
||||
if (error) return Logger.warn("PluginUpdates", "Unable to get update for " + pluginName);
|
||||
const remoteVersion = window.PluginUpdates.plugins[updateLink].versioner(body);
|
||||
let filename = updateLink.split("/");
|
||||
filename = filename[filename.length - 1];
|
||||
const file = path.join(PluginUtilities.getPluginsFolder(), filename);
|
||||
await new Promise(r => fileSystem.writeFile(file, body, r));
|
||||
Toasts.success(`${pluginName} ${window.PluginUpdates.plugins[updateLink].version} has been replaced by ${pluginName} ${remoteVersion}`);
|
||||
this.removeUpdateNotice(pluginName);
|
||||
|
||||
const oldRNM = window.bdplugins["Restart-No-More"] && window.pluginCookie["Restart-No-More"];
|
||||
const newRNM = window.bdplugins["Restart No More"] && window.pluginCookie["Restart No More"];
|
||||
const BBDLoader = window.settingsCookie["fork-ps-5"];
|
||||
if (oldRNM || newRNM || BBDLoader) return;
|
||||
if (!window.PluginUpdates.downloaded) {
|
||||
window.PluginUpdates.downloaded = [];
|
||||
const button = DOMTools.parseHTML(`<button class="btn btn-reload ${DiscordClasses.Notices.btn} ${DiscordClasses.Notices.button}">Reload</button>`);
|
||||
const tooltip = new EmulatedTooltip(button, window.PluginUpdates.downloaded.join(", "), {side: "top"});
|
||||
button.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
window.location.reload(false);
|
||||
});
|
||||
button.addEventListener("mouseenter", () => {
|
||||
tooltip.label = window.PluginUpdates.downloaded.join(", ");
|
||||
});
|
||||
document.getElementById("pluginNotice").append(button);
|
||||
}
|
||||
window.PluginUpdates.plugins[updateLink].version = remoteVersion;
|
||||
window.PluginUpdates.downloaded.push(pluginName);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Will show the update notice top bar seen in Discord. Better not to call
|
||||
* this directly and to instead use {@link module:PluginUpdater.checkForUpdate}.
|
||||
* @param {string} pluginName - name of the plugin
|
||||
* @param {string} updateLink - link to the raw text version of the plugin
|
||||
*/
|
||||
static showUpdateNotice(pluginName, updateLink) {
|
||||
if (!document.getElementById("pluginNotice")) {
|
||||
const noticeElement = DOMTools.parseHTML(`<div class="${DiscordClasses.Notices.notice} ${DiscordClasses.Notices.noticeInfo}" id="pluginNotice">
|
||||
<div class="${DiscordClasses.Notices.dismiss}" id="pluginNoticeDismiss"></div>
|
||||
<span class="notice-message">The following plugins have updates:</span> <strong id="outdatedPlugins"></strong>
|
||||
</div>`);
|
||||
DOMTools.query("[class*='app-'] > [class*='app-']").prepend(noticeElement);
|
||||
noticeElement.querySelector("#pluginNoticeDismiss").addEventListener("click", async () => {
|
||||
noticeElement.classList.add("closing");
|
||||
await new Promise(resolve => setTimeout(resolve, 400));
|
||||
noticeElement.remove();
|
||||
});
|
||||
}
|
||||
const pluginNoticeID = pluginName + "-notice";
|
||||
if (document.getElementById(pluginNoticeID)) return;
|
||||
const pluginNoticeElement = DOMTools.parseHTML(`<span id="${pluginNoticeID}">${pluginName}</span>`);
|
||||
pluginNoticeElement.addEventListener("click", () => {
|
||||
this.downloadPlugin(pluginName, updateLink);
|
||||
});
|
||||
if (document.getElementById("outdatedPlugins").querySelectorAll("span").length) document.getElementById("outdatedPlugins").append(DOMTools.createElement("<span class='separator'>, </span>"));
|
||||
document.getElementById("outdatedPlugins").append(pluginNoticeElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will remove the plugin from the update notice top bar seen in Discord.
|
||||
* Better not to call this directly and to instead use {@link module:PluginUpdater.checkForUpdate}.
|
||||
* @param {string} pluginName - name of the plugin
|
||||
*/
|
||||
static removeUpdateNotice(pluginName) {
|
||||
if (!document.getElementById("outdatedPlugins")) return;
|
||||
const notice = document.getElementById(pluginName + "-notice");
|
||||
if (notice) {
|
||||
if (notice.nextElementSibling && notice.nextElementSibling.matches(".separator")) notice.nextElementSibling.remove();
|
||||
else if (notice.previousElementSibling && notice.previousElementSibling.matches(".separator")) notice.previousElementSibling.remove();
|
||||
notice.remove();
|
||||
}
|
||||
|
||||
if (!document.getElementById("outdatedPlugins").querySelectorAll("span").length) {
|
||||
if (document.querySelector("#pluginNotice .btn-reload")) document.querySelector("#pluginNotice .notice-message").textContent = "To finish updating you need to reload.";
|
||||
else document.getElementById("pluginNoticeDismiss").click();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,21 +2,10 @@
|
|||
|
||||
export default class Utilities {
|
||||
|
||||
static stripBOM(content) {
|
||||
if (content.charCodeAt(0) === 0xFEFF) {
|
||||
content = content.slice(1);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
static getTextArea() {
|
||||
return $(".channelTextArea-1LDbYG textarea");
|
||||
}
|
||||
|
||||
static getInternalInstance(node) {
|
||||
return node[Object.keys(node).find(k => k.startsWith("__reactInternalInstance"))] || null;
|
||||
}
|
||||
|
||||
static insertText(textarea, text) {
|
||||
textarea.focus();
|
||||
textarea.selectionStart = 0;
|
||||
|
@ -24,28 +13,6 @@ export default class Utilities {
|
|||
document.execCommand("insertText", false, text);
|
||||
}
|
||||
|
||||
static injectCss(uri) {
|
||||
$("<link/>", {
|
||||
type: "text/css",
|
||||
rel: "stylesheet",
|
||||
href: uri
|
||||
}).appendTo($("head"));
|
||||
}
|
||||
|
||||
static injectJs(uri) {
|
||||
return new Promise(resolve => {
|
||||
$("<script/>", {
|
||||
type: "text/javascript",
|
||||
src: uri,
|
||||
onload: resolve
|
||||
}).appendTo($("body"));
|
||||
});
|
||||
}
|
||||
|
||||
static escapeID(id) {
|
||||
return id.replace(/^[^a-z]+|[^\w-]+/gi, "-");
|
||||
}
|
||||
|
||||
static escape(s) {
|
||||
return s.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
||||
}
|
||||
|
@ -162,4 +129,127 @@ export default class Utilities {
|
|||
|
||||
return proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format strings with placeholders (`{{placeholder}}`) into full strings.
|
||||
* Quick example: `PluginUtilities.formatString("Hello, {{user}}", {user: "Zerebos"})`
|
||||
* would return "Hello, Zerebos".
|
||||
* @param {string} string - string to format
|
||||
* @param {object} values - object literal of placeholders to replacements
|
||||
* @returns {string} the properly formatted string
|
||||
*/
|
||||
static formatString(string, values) {
|
||||
for (const val in values) {
|
||||
let replacement = values[val];
|
||||
if (Array.isArray(replacement)) replacement = JSON.stringify(replacement);
|
||||
if (typeof(replacement) === "object" && replacement !== null) replacement = replacement.toString();
|
||||
string = string.replace(new RegExp(`{{${val}}}`, "g"), replacement);
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a value, subobject, or array from a tree that matches a specific filter.
|
||||
* @param {object} tree Tree that should be walked
|
||||
* @param {callable} searchFilter Filter to check against each object and subobject
|
||||
* @param {object} options Additional options to customize the search
|
||||
* @param {Array<string>|null} [options.walkable=null] Array of strings to use as keys that are allowed to be walked on. Null value indicates all keys are walkable
|
||||
* @param {Array<string>} [options.ignore=[]] Array of strings to use as keys to exclude from the search, most helpful when `walkable = null`.
|
||||
*/
|
||||
static findInTree(tree, searchFilter, {walkable = null, ignore = []} = {}) {
|
||||
if (typeof searchFilter === "string") {
|
||||
if (tree.hasOwnProperty(searchFilter)) return tree[searchFilter];
|
||||
}
|
||||
else if (searchFilter(tree)) {
|
||||
return tree;
|
||||
}
|
||||
|
||||
if (typeof tree !== "object" || tree == null) return undefined;
|
||||
|
||||
let tempReturn = undefined;
|
||||
if (tree instanceof Array) {
|
||||
for (const value of tree) {
|
||||
tempReturn = this.findInTree(value, searchFilter, {walkable, ignore});
|
||||
if (typeof tempReturn != "undefined") return tempReturn;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const toWalk = walkable == null ? Object.keys(tree) : walkable;
|
||||
for (const key of toWalk) {
|
||||
if (!tree.hasOwnProperty(key) || ignore.includes(key)) continue;
|
||||
tempReturn = this.findInTree(tree[key], searchFilter, {walkable, ignore});
|
||||
if (typeof tempReturn != "undefined") return tempReturn;
|
||||
}
|
||||
}
|
||||
return tempReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a nested property (if it exists) safely. Path should be something like `prop.prop2.prop3`.
|
||||
* Numbers can be used for arrays as well like `prop.prop2.array.0.id`.
|
||||
* @param {Object} obj - object to get nested property of
|
||||
* @param {string} path - representation of the property to obtain
|
||||
*/
|
||||
static getNestedProp(obj, path) {
|
||||
return path.split(/\s?\.\s?/).reduce(function(currentObj, prop) {
|
||||
return currentObj && currentObj[prop];
|
||||
}, obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a value, subobject, or array from a tree that matches a specific filter. Great for patching render functions.
|
||||
* @param {object} tree React tree to look through. Can be a rendered object or an internal instance.
|
||||
* @param {callable} searchFilter Filter function to check subobjects against.
|
||||
*/
|
||||
static findInRenderTree(tree, searchFilter, {walkable = ["props", "children", "child", "sibling"], ignore = []} = {}) {
|
||||
return this.findInTree(tree, searchFilter, {walkable, ignore});
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a value, subobject, or array from a tree that matches a specific filter. Great for patching render functions.
|
||||
* @param {object} tree React tree to look through. Can be a rendered object or an internal instance.
|
||||
* @param {callable} searchFilter Filter function to check subobjects against.
|
||||
*/
|
||||
static findInReactTree(tree, searchFilter) {
|
||||
return this.findInTree(tree, searchFilter, {walkable: ["props", "children", "return", "stateNode"]});
|
||||
}
|
||||
|
||||
static getReactInstance(node) {
|
||||
if (node.__reactInternalInstance$) return node.__reactInternalInstance$;
|
||||
return node[Object.keys(node).find(k => k.startsWith("__reactInternalInstance"))] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grabs a value from the react internal instance. Allows you to grab
|
||||
* long depth values safely without accessing no longer valid properties.
|
||||
* @param {HTMLElement} node - node to obtain react instance of
|
||||
* @param {object} options - options for the search
|
||||
* @param {array} [options.include] - list of items to include from the search
|
||||
* @param {array} [options.exclude=["Popout", "Tooltip", "Scroller", "BackgroundFlash"]] - list of items to exclude from the search
|
||||
* @param {callable} [options.filter=_=>_] - filter to check the current instance with (should return a boolean)
|
||||
* @return {(*|null)} the owner instance or undefined if not found.
|
||||
*/
|
||||
static getOwnerInstance(node, {include, exclude = ["Popout", "Tooltip", "Scroller", "BackgroundFlash"], filter = _ => _} = {}) {
|
||||
if (node === undefined) return undefined;
|
||||
const excluding = include === undefined;
|
||||
const nameFilter = excluding ? exclude : include;
|
||||
function getDisplayName(owner) {
|
||||
const type = owner.type;
|
||||
if (!type) return null;
|
||||
return type.displayName || type.name || null;
|
||||
}
|
||||
function classFilter(owner) {
|
||||
const name = getDisplayName(owner);
|
||||
return (name !== null && !!(nameFilter.includes(name) ^ excluding));
|
||||
}
|
||||
|
||||
let curr = this.getReactInstance(node);
|
||||
for (curr = curr && curr.return; curr !== null; curr = curr.return) {
|
||||
if (curr === null) continue;
|
||||
const owner = curr.stateNode;
|
||||
if (curr !== null && !(owner instanceof HTMLElement) && classFilter(curr) && filter(owner)) return owner;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import Logger from "../modules/logger";
|
||||
import Events from "../modules/emitter";
|
||||
import Settings from "../modules/settingsmanager";
|
||||
import Patcher from "../modules/patcher";
|
||||
|
||||
export default class BuiltinModule {
|
||||
|
||||
|
@ -81,4 +82,12 @@ export default class BuiltinModule {
|
|||
stacktrace(message, error) {
|
||||
Logger.stacktrace(this.name, message, error);
|
||||
}
|
||||
|
||||
after(object, func, callback) {
|
||||
return Patcher.after(this.name, object, func, callback);
|
||||
}
|
||||
|
||||
unpatchAll() {
|
||||
return Patcher.unpatchAll(this.name);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import {React, Utilities} from "modules";
|
||||
import {React, Utilities, Patcher} from "modules";
|
||||
|
||||
import FloatingWindow from "./window";
|
||||
|
||||
|
@ -11,7 +11,6 @@ class FloatingWindowContainer extends React.Component {
|
|||
|
||||
render() {
|
||||
return this.state.windows.map(window =>
|
||||
// <FloatingWindow onResize={window.onResize} close={this.close.bind(this, window.id)} title={window.title} id={window.id} height={window.height} width={window.width} center={window.center} resizable={window.resizable}>
|
||||
<FloatingWindow {...window} close={this.close.bind(this, window.id)}>
|
||||
{window.children}
|
||||
</FloatingWindow>
|
||||
|
@ -22,8 +21,6 @@ class FloatingWindowContainer extends React.Component {
|
|||
this.setState({
|
||||
windows: [...this.state.windows, window]
|
||||
});
|
||||
// this.windows.push(window);
|
||||
// this.forceUpdate();
|
||||
}
|
||||
|
||||
close(id) {
|
||||
|
@ -33,10 +30,6 @@ class FloatingWindowContainer extends React.Component {
|
|||
return w.id != id;
|
||||
})
|
||||
});
|
||||
// const index = this.windows.findIndex(w => w.id == id);
|
||||
// if (index < 0) return;
|
||||
// this.windows.splice(index, 1);
|
||||
// this.forceUpdate();
|
||||
}
|
||||
|
||||
static get id() {return "floating-windows";}
|
||||
|
@ -51,17 +44,10 @@ class FloatingWindowContainer extends React.Component {
|
|||
|
||||
const containerRef = React.createRef();
|
||||
const container = <FloatingWindowContainer ref={containerRef} />;
|
||||
// ReactDOM.render(container, FloatingWindowContainer.root);
|
||||
const App = document.querySelector(".app-19_DXt").__reactInternalInstance$.return.return.return.return.return.return.return.return.return.return.return;
|
||||
Utilities.monkeyPatch(App.type.prototype, "render", {after: (data) => {
|
||||
//returnValue.props.children.props.children.props.children.props.children[4].props.children.props.children[1].props.children
|
||||
data.returnValue.props.children.props.children.props.children.props.children[4].props.children.props.children[1].props.children.push(container);
|
||||
}});
|
||||
const App = Utilities.findInReactTree(Utilities.getReactInstance(document.querySelector(".app-19_DXt")), m => m && m.type && m.type.displayName && m.type.displayName == "App");
|
||||
Patcher.after("FloatingContainer", App.type.prototype, "render", (thisObject, args, returnValue) => {
|
||||
const group = Utilities.findInRenderTree(returnValue, m => m && m[5] && m[5].type && m[5].type.displayName == "LayerContainer", {walkable: ["children", "props"]});
|
||||
group.push(container);
|
||||
});
|
||||
App.stateNode.forceUpdate();
|
||||
export default containerRef.current;
|
||||
|
||||
// patch App component
|
||||
//
|
||||
//document.querySelector(".app-19_DXt").__reactInternalInstance$.return.return.return.return.return.return.return.return.return.type
|
||||
//props.children.props.children.props.children.props.children[4].props.children[1].props.children[""0""].push( SELF )
|
||||
// forceupdate app component
|
||||
export default containerRef.current;
|
|
@ -246,11 +246,12 @@ export default class PublicServers extends React.Component {
|
|||
const categories = this.categoryButtons.map(name => {
|
||||
const section = {
|
||||
section: name,//.toLowerCase().replace(" ", "_"),
|
||||
label: name
|
||||
label: name,
|
||||
//element: () => name == "All" ? this.content : null
|
||||
};
|
||||
|
||||
if (name == "All") section.element = () => this.content;
|
||||
else section.onClick = () => this.changeCategory(name);
|
||||
// else section.onClick = () => this.changeCategory(name);
|
||||
return section;
|
||||
});
|
||||
return React.createElement(SettingsView, {
|
||||
|
|
Loading…
Reference in New Issue