Fix waitForModule and meta handling
This commit is contained in:
parent
9e5c090c6e
commit
81474344f0
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -2,6 +2,19 @@
|
|||
|
||||
This changelog starts with the restructured 1.0.0 release that happened after context isolation changes. The changelogs here should more-or-less mirror the ones that get shown in the client but probably with less formatting and pizzazz.
|
||||
|
||||
## 1.6.1
|
||||
|
||||
### Added
|
||||
|
||||
### Removed
|
||||
|
||||
### Changed
|
||||
|
||||
### Fixed
|
||||
- Fixed an issue where `waitForModule` would not return the found module.
|
||||
- Fixed an issue where broken addon METAs could prevent BD from fully loading.
|
||||
- Fixed an issue where developer badges stopped rendering.
|
||||
|
||||
## 1.6.0
|
||||
|
||||
### Added
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "betterdiscord",
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1",
|
||||
"description": "Enhances Discord by adding functionality and themes.",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
|
|
|
@ -1,26 +1,14 @@
|
|||
// fixed, improved, added, progress
|
||||
export default {
|
||||
description: "Big things are coming soon, this is just the tip of the iceberg!",
|
||||
description: "Big things are coming soon, please be patient!",
|
||||
changes: [
|
||||
{
|
||||
title: "What's New?",
|
||||
type: "added",
|
||||
title: "Fixes",
|
||||
type: "fixed",
|
||||
items: [
|
||||
"Better handling and fallback when the editor fails to load. (Thanks Qb)",
|
||||
"Now able to sort addons by whether they're enabled. (Thanks TheGreenPig)",
|
||||
"New `Webpack` API added for plugin developers to take advantage of. Please see the docs for details!",
|
||||
"New developer docs (still work-in-progress) available at https://docs.betterdiscord.app"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Improvements",
|
||||
type: "improved",
|
||||
items: [
|
||||
"Addons should now load faster, use less memory, and be a bit more consistent!",
|
||||
"Addon error modal should work a little better. (Thanks Qb)",
|
||||
"Plugin and startup errors should make more sense now.",
|
||||
"The crash dialog has more information and more buttons.",
|
||||
"Minor speed and memory improvements."
|
||||
"Fixed an issue where `waitForModule` would not return the found module.",
|
||||
"Fixed an issue where broken addon METAs could prevent BD from fully loading.",
|
||||
"Fixed an issue where developer badges stopped rendering."
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -4,7 +4,6 @@ import Settings from "./settingsmanager";
|
|||
import Events from "./emitter";
|
||||
import DataStore from "./datastore";
|
||||
import AddonError from "../structs/addonerror";
|
||||
import MetaError from "../structs/metaerror";
|
||||
import Toasts from "../ui/toasts";
|
||||
import DiscordModules from "./discordmodules";
|
||||
import Strings from "./strings";
|
||||
|
@ -121,21 +120,21 @@ export default class AddonManager {
|
|||
Logger.log(this.name, `No longer watching ${this.prefix} addons.`);
|
||||
}
|
||||
|
||||
extractMeta(fileContent) {
|
||||
extractMeta(fileContent, filename) {
|
||||
const firstLine = fileContent.split("\n")[0];
|
||||
const hasOldMeta = firstLine.includes("//META");
|
||||
if (hasOldMeta) return this.parseOldMeta(fileContent);
|
||||
const hasOldMeta = firstLine.includes("//META") && firstLine.includes("*//");
|
||||
if (hasOldMeta) return this.parseOldMeta(fileContent, filename);
|
||||
const hasNewMeta = firstLine.includes("/**");
|
||||
if (hasNewMeta) return this.parseNewMeta(fileContent);
|
||||
throw new MetaError(Strings.Addons.metaNotFound);
|
||||
throw new AddonError(filename, filename, Strings.Addons.metaNotFound, {message: "", stack: fileContent}, this.prefix);
|
||||
}
|
||||
|
||||
parseOldMeta(fileContent) {
|
||||
parseOldMeta(fileContent, filename) {
|
||||
const meta = fileContent.split("\n")[0];
|
||||
const metaData = meta.substring(meta.lastIndexOf("//META") + 6, meta.lastIndexOf("*//"));
|
||||
const parsed = Utilities.testJSON(metaData);
|
||||
if (!parsed) throw new MetaError(Strings.Addons.metaError);
|
||||
if (!parsed.name) throw new MetaError(Strings.Addons.missingNameData);
|
||||
if (!parsed) throw new AddonError(filename, filename, Strings.Addons.metaError, {message: "", stack: meta}, this.prefix);
|
||||
if (!parsed.name) throw new AddonError(filename, filename, Strings.Addons.missingNameData, {message: "", stack: meta}, this.prefix);
|
||||
parsed.format = "json";
|
||||
return parsed;
|
||||
}
|
||||
|
@ -168,12 +167,12 @@ export default class AddonManager {
|
|||
let fileContent = fs.readFileSync(filename, "utf8");
|
||||
fileContent = stripBOM(fileContent);
|
||||
const stats = fs.statSync(filename);
|
||||
const addon = this.extractMeta(fileContent);
|
||||
const addon = this.extractMeta(fileContent, path.basename(filename));
|
||||
if (!addon.author) addon.author = Strings.Addons.unknownAuthor;
|
||||
if (!addon.version) addon.version = "???";
|
||||
if (!addon.description) addon.description = Strings.Addons.noDescription;
|
||||
// if (!addon.name || !addon.author || !addon.description || !addon.version) return new AddonError(addon.name || path.basename(filename), filename, "Addon is missing name, author, description, or version", {message: "Addon must provide name, author, description, and version.", stack: ""}, this.prefix);
|
||||
addon.id = addon.name;
|
||||
addon.id = addon.name || path.basename(filename);
|
||||
addon.slug = path.basename(filename).replace(this.extension, "").replace(/ /g, "-");
|
||||
addon.filename = path.basename(filename);
|
||||
addon.added = stats.atimeMs;
|
||||
|
@ -186,9 +185,13 @@ export default class AddonManager {
|
|||
// Subclasses should use the return (if not AddonError) and push to this.addonList
|
||||
loadAddon(filename, shouldToast = false) {
|
||||
if (typeof(filename) === "undefined") return;
|
||||
|
||||
const addon = this.requireAddon(path.resolve(this.addonFolder, filename));
|
||||
if (addon instanceof AddonError) return addon;
|
||||
let addon;
|
||||
try {
|
||||
addon = this.requireAddon(path.resolve(this.addonFolder, filename));
|
||||
}
|
||||
catch (e) {
|
||||
return e;
|
||||
}
|
||||
if (this.addonList.find(c => c.id == addon.id)) return new AddonError(addon.name, filename, Strings.Addons.alreadyExists.format({type: this.prefix, name: addon.name}), this.prefix);
|
||||
|
||||
const error = this.initializeAddon(addon);
|
||||
|
|
|
@ -148,29 +148,45 @@ export default new class ComponentPatcher {
|
|||
|
||||
patchMessageHeader() {
|
||||
if (this.messageHeaderPatch) return;
|
||||
const MessageTimestamp = WebpackModules.getModule(m => m?.default?.toString().indexOf("showTimestampOnHover") > -1);
|
||||
this.messageHeaderPatch = Patcher.after("ComponentPatcher", MessageTimestamp, "default", (_, [{message}], returnValue) => {
|
||||
const userId = Utilities.getNestedProp(message, "author.id");
|
||||
if (Developers.indexOf(userId) < 0) return;
|
||||
const children = Utilities.getNestedProp(returnValue, "props.children.1.props.children");
|
||||
if (!Array.isArray(children)) return;
|
||||
// const MessageTimestamp = WebpackModules.getModule(m => m?.default?.toString().indexOf("showTimestampOnHover") > -1);
|
||||
// this.messageHeaderPatch = Patcher.after("ComponentPatcher", MessageTimestamp, "default", (_, [{message}], returnValue) => {
|
||||
// const userId = Utilities.getNestedProp(message, "author.id");
|
||||
// if (Developers.indexOf(userId) < 0) return;
|
||||
// if (!returnValue?.type) return;
|
||||
// const orig = returnValue.type;
|
||||
// returnValue.type = function() {
|
||||
// const retVal = Reflect.apply(orig, this, arguments);
|
||||
|
||||
children.splice(2, 0,
|
||||
React.createElement(DeveloperBadge, {
|
||||
type: "chat"
|
||||
})
|
||||
);
|
||||
});
|
||||
// const children = Utilities.getNestedProp(retVal, "props.children.1.props.children");
|
||||
// if (!Array.isArray(children)) return;
|
||||
|
||||
// children.splice(3, 0,
|
||||
// React.createElement(DeveloperBadge, {
|
||||
// type: "chat"
|
||||
// })
|
||||
// );
|
||||
|
||||
// return retVal;
|
||||
// };
|
||||
|
||||
// });
|
||||
}
|
||||
|
||||
patchMemberList() {
|
||||
async patchMemberList() {
|
||||
if (this.memberListPatch) return;
|
||||
const MemberListItem = WebpackModules.findByDisplayName("MemberListItem");
|
||||
const memo = WebpackModules.find(m => m?.type?.toString().includes("useGlobalHasAvatarDecorations"));
|
||||
if (!memo?.type) return;
|
||||
const MemberListItem = await new Promise(resolve => {
|
||||
const cancelFindListItem = Patcher.after("ComponentPatcher", memo, "type", (_, props, returnValue) => {
|
||||
cancelFindListItem();
|
||||
resolve(returnValue?.type);
|
||||
});
|
||||
});
|
||||
if (!MemberListItem?.prototype?.renderDecorators) return;
|
||||
this.memberListPatch = Patcher.after("ComponentPatcher", MemberListItem.prototype, "renderDecorators", (thisObject, args, returnValue) => {
|
||||
const user = Utilities.getNestedProp(thisObject, "props.user");
|
||||
const children = Utilities.getNestedProp(returnValue, "props.children");
|
||||
if (!children || Developers.indexOf(user.id) < 0) return;
|
||||
if (!children || Developers.indexOf(user?.id) < 0) return;
|
||||
if (!Array.isArray(children)) return;
|
||||
children.push(
|
||||
React.createElement(DeveloperBadge, {
|
||||
|
@ -182,19 +198,21 @@ export default new class ComponentPatcher {
|
|||
|
||||
patchProfile() {
|
||||
if (this.profilePatch) return;
|
||||
const UserProfileBadgeList = WebpackModules.getModule(m => m?.default?.displayName === "UserProfileBadgeList");
|
||||
this.profilePatch = Patcher.after("ComponentPatcher", UserProfileBadgeList, "default", (_, [{user}], res) => {
|
||||
if (Developers.indexOf(user?.id) < 0) return;
|
||||
const children = Utilities.getNestedProp(res, "props.children");
|
||||
if (!Array.isArray(children)) return;
|
||||
const UserProfileBadgeLists = WebpackModules.getModule(m => m?.default?.displayName === "UserProfileBadgeList", {first: false});
|
||||
for (const UserProfileBadgeList of UserProfileBadgeLists) {
|
||||
this.profilePatch = Patcher.after("ComponentPatcher", UserProfileBadgeList, "default", (_, [{user}], res) => {
|
||||
if (Developers.indexOf(user?.id) < 0) return;
|
||||
const children = Utilities.getNestedProp(res, "props.children");
|
||||
if (!Array.isArray(children)) return;
|
||||
|
||||
children.unshift(
|
||||
React.createElement(DeveloperBadge, {
|
||||
type: "profile",
|
||||
size: 18
|
||||
})
|
||||
);
|
||||
});
|
||||
children.unshift(
|
||||
React.createElement(DeveloperBadge, {
|
||||
type: "profile",
|
||||
size: 18
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
};
|
|
@ -1,6 +1,5 @@
|
|||
import {Config} from "data";
|
||||
import Logger from "common/logger";
|
||||
import DOM from "./domtools";
|
||||
|
||||
export default class Utilities {
|
||||
|
||||
|
@ -8,36 +7,6 @@ export default class Utilities {
|
|||
return `https://cdn.staticaly.com/gh/BetterDiscord/BetterDiscord/${Config.hash}/${path}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string of HTML and returns the results. If the second parameter is true,
|
||||
* the parsed HTML will be returned as a document fragment {@see https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment}.
|
||||
* This is extremely useful if you have a list of elements at the top level, they can then be appended all at once to another node.
|
||||
*
|
||||
* If the second parameter is false, then the return value will be the list of parsed
|
||||
* nodes and there were multiple top level nodes, otherwise the single node is returned.
|
||||
* @param {string} html - HTML to be parsed
|
||||
* @param {boolean} [fragment=false] - Whether or not the return should be the raw `DocumentFragment`
|
||||
* @returns {(DocumentFragment|NodeList|HTMLElement)} - The result of HTML parsing
|
||||
*/
|
||||
static parseHTML(html, fragment = false) {
|
||||
const template = document.createElement("template");
|
||||
template.innerHTML = html;
|
||||
const node = template.content.cloneNode(true);
|
||||
if (fragment) return node;
|
||||
return node.childNodes.length > 1 ? node.childNodes : node.childNodes[0];
|
||||
}
|
||||
|
||||
static getTextArea() {
|
||||
return DOM.query(".channelTextArea-1LDbYG textarea");
|
||||
}
|
||||
|
||||
static insertText(textarea, text) {
|
||||
textarea.focus();
|
||||
textarea.selectionStart = 0;
|
||||
textarea.selectionEnd = textarea.value.length;
|
||||
document.execCommand("insertText", false, text);
|
||||
}
|
||||
|
||||
static escape(s) {
|
||||
return s.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
||||
}
|
||||
|
@ -148,25 +117,6 @@ export default class Utilities {
|
|||
return proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Protects prototypes from external assignment.
|
||||
*
|
||||
* Needs some work before full usage
|
||||
* @param {Class} Component - component with prototype to protect
|
||||
*/
|
||||
static protectPrototype(Component) {
|
||||
const descriptors = Object.getOwnPropertyDescriptors(Component.prototype);
|
||||
for (const name in descriptors) {
|
||||
const descriptor = descriptors[name];
|
||||
descriptor.configurable = false;
|
||||
descriptor.enumerable = false;
|
||||
if (Object.prototype.hasOwnProperty.call(descriptor, "get")) descriptor.set = () => Logger.warn("protectPrototype", "Addon policy for plugins #5 https://github.com/BetterDiscord/BetterDiscord/wiki/Addon-Policies#plugins");
|
||||
if (Object.prototype.hasOwnProperty.call(descriptor, "value") && typeof(descriptor.value) === "function") descriptor.value.bind(Component.prototype);
|
||||
if (Object.prototype.hasOwnProperty.call(descriptor, "writable")) descriptor.writable = false;
|
||||
}
|
||||
Object.defineProperties(Component.prototype, descriptors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep extends an object with a set of other objects. Objects later in the list
|
||||
* of `extenders` have priority, that is to say if one sets a key to be a primitive,
|
||||
|
|
|
@ -142,8 +142,8 @@ export default class WebpackModules {
|
|||
|
||||
/**
|
||||
* Finds a module using a filter function.
|
||||
* @param {Function} filter A function to use to filter modules
|
||||
* @param {object} [options] Whether to return only the first matching module
|
||||
* @param {function} filter A function to use to filter modules
|
||||
* @param {object} [options] Set of options to customize the search
|
||||
* @param {Boolean} [options.first=true] Whether to return only the first matching module
|
||||
* @param {Boolean} [options.defaultExport=true] Whether to return default export when matching the default export
|
||||
* @return {Any}
|
||||
|
@ -312,7 +312,7 @@ export default class WebpackModules {
|
|||
/**
|
||||
* Finds a module that lazily loaded.
|
||||
* @param {(m) => boolean} filter A function to use to filter modules.
|
||||
* @param {object} [options] Whether to return only the first matching module
|
||||
* @param {object} [options] Set of options to customize the search
|
||||
* @param {AbortSignal} [options.signal] AbortSignal of an AbortController to cancel the promise
|
||||
* @param {Boolean} [options.defaultExport=true] Whether to return default export when matching the default export
|
||||
* @returns {Promise<any>}
|
||||
|
@ -320,25 +320,25 @@ export default class WebpackModules {
|
|||
static getLazy(filter, options = {}) {
|
||||
/** @type {AbortSignal} */
|
||||
const abortSignal = options.signal;
|
||||
const defaultExport = options.defaultExport;
|
||||
const defaultExport = options.defaultExport ?? true;
|
||||
const fromCache = this.getModule(filter);
|
||||
if (fromCache) return Promise.resolve(fromCache);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const cancel = () => {this.removeListener(listener);};
|
||||
const listener = function (m) {
|
||||
const directMatch = filter(m);
|
||||
const listener = function (mod) {
|
||||
const directMatch = filter(mod);
|
||||
|
||||
if (directMatch) {
|
||||
cancel();
|
||||
return resolve(directMatch);
|
||||
}
|
||||
|
||||
const defaultMatch = filter(m.default);
|
||||
const defaultMatch = filter(mod.default);
|
||||
if (!defaultMatch) return;
|
||||
|
||||
cancel();
|
||||
resolve(defaultExport ? m.default : defaultExport);
|
||||
resolve(defaultExport ? mod.default : mod);
|
||||
};
|
||||
|
||||
this.addListener(listener);
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
export default class MetaError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = "MetaError";
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue