Re"structuring" pluginCertifier

This commit is contained in:
Jean Ouina 2020-06-12 23:44:01 +02:00
parent 4ed3c4b78b
commit 93a735193b
12 changed files with 343 additions and 327 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -78,6 +78,7 @@ export const settings = {
/* Content */
"Content Error Modal": {id: "fork-ps-1", info: "Shows a modal with plugin/theme errors", implemented: true, hidden: false, cat: "core", category: "content manager"},
"Scan Plugins": {id: "fork-ps-6", info: "Scan plugins for any threat that can be hidden inside.", implemented: true, hidden: false, cat: "core", category: "content manager"},
"Show Toasts": {id: "fork-ps-2", info: "Shows a small notification for important information", implemented: true, hidden: false, cat: "core", category: "content manager"},
"Scroll To Settings": {id: "fork-ps-3", info: "Auto-scrolls to a plugin's settings when the button is clicked (only if out of view)", implemented: true, hidden: false, cat: "core", category: "content manager"},
"Automatic Loading": {id: "fork-ps-5", info: "Automatically loads, reloads, and unloads plugins and themes", implemented: true, hidden: false, cat: "core", category: "content manager"},
@ -129,6 +130,7 @@ export const defaultCookie = {
"fork-ps-3": true,
"fork-ps-4": true,
"fork-ps-5": true,
"fork-ps-6": true,
"fork-es-2": false,
"fork-es-3": true,
"fork-wp-1": false,

View File

@ -1,8 +1,8 @@
const __non_webpack_require__ = window.require
import {bdConfig, bdplugins, bdthemes} from "../0globals";
import pluginModule from "./pluginModule";
import themeModule from "./themeModule";
import Utils from "./utils";
import * as crypto from "crypto"
const path = require("path");
const fs = require("fs");
@ -171,9 +171,9 @@ export default new class ContentManager {
if (typeof(filename) === "undefined" || typeof(type) === "undefined") return;
const isPlugin = type === "plugin";
const baseFolder = isPlugin ? this.pluginsFolder : this.themesFolder;
try {__non_webpack_require__(path.resolve(baseFolder, filename));}
try {window.require(path.resolve(baseFolder, filename));}
catch (error) {return {name: filename, file: filename, message: "Could not be compiled.", error: {message: error.message, stack: error.stack}};}
const content = __non_webpack_require__(path.resolve(baseFolder, filename));
const content = window.require(path.resolve(baseFolder, filename));
if(!content.name)return {name: filename, file: filename, message: "Cannot escape the ID.", error: {message: "Cannot read property 'replace' of undefined", stack: "Cannot read property 'replace' of undefined"}}
content.id = Utils.escapeID(content.name);
if (isPlugin) {
@ -189,14 +189,14 @@ export default new class ContentManager {
delete bdthemes[content.name];
bdthemes[content.name] = content;
}
}
}
unloadContent(filename, type) {
if (typeof(filename) === "undefined" || typeof(type) === "undefined") return;
const isPlugin = type === "plugin";
const baseFolder = isPlugin ? this.pluginsFolder : this.themesFolder;
try {
delete __non_webpack_require__.cache[__non_webpack_require__.resolve(path.resolve(baseFolder, filename))];
delete window.require.cache[window.require.resolve(path.resolve(baseFolder, filename))];
}
catch (err) {return {name: filename, file: filename, message: "Could not be unloaded.", error: {message: err.message, stack: err.stack}};}
}
@ -204,7 +204,7 @@ export default new class ContentManager {
isLoaded(filename, type) {
const isPlugin = type === "plugin";
const baseFolder = isPlugin ? this.pluginsFolder : this.themesFolder;
try {__non_webpack_require__.cache[__non_webpack_require__.resolve(path.resolve(baseFolder, filename))];}
try {window.require.cache[window.require.resolve(path.resolve(baseFolder, filename))];}
catch (err) {return false;}
return true;
}

View File

@ -47,6 +47,7 @@ Core.prototype.init = async function() {
return;
}
/*
const latestLocalVersion = bdConfig.updater ? bdConfig.updater.LatestVersion : bdConfig.latestVersion;
if (latestLocalVersion > bdConfig.version) {
Utils.showConfirmationModal("Update Available", [`There is an update available for BandagedBD's Injector (${latestLocalVersion}).`, "You can either update and restart now, or later."], {
@ -66,7 +67,7 @@ Core.prototype.init = async function() {
}
}
});
}
}*/
Utils.log("Startup", "Initializing Settings");
this.initSettings();
@ -107,7 +108,7 @@ Core.prototype.init = async function() {
DataStore.setBDData("version", bbdVersion);
}
const emojiModule = EmojiModule
EmojiModule.start()
Utils.suppressErrors(this.patchSocial.bind(this), "BD Social Patch")();
Utils.suppressErrors(this.patchGuildPills.bind(this), "BD Guild Pills Patch")();
@ -125,6 +126,7 @@ Core.prototype.init = async function() {
}
const logo = document.querySelector("#app-mount > div.typeWindows-1za-n7.withFrame-haYltI.titleBar-AC4pGV.horizontalReverse-3tRjY7.flex-1O1GKY.directionRowReverse-m8IjIq.justifyStart-2NDFzi.alignStretch-DpGPf3.da-typeWindows.da-withFrame.da-titleBar.da-horizontalReverse.da-flex.da-directionRowReverse.da-justifyStart.da-alignStretch > div.wordmarkWindows-1v0lYD.wordmark-2iDDfm.da-wordmarkWindows.da-wordmark")
if(logo){
logo.style.top = "3px"
logo.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" height="10" width="70" viewBox="0 0 72.54 10" style="margin-left: -5px, margin-top: 10px">
<path fill="currentColor" d="M44.81,9.67V6.33c0-1.21,2.13-1.49,2.78-.28l2-.81A3.53,3.53,0,0,0,46.2,3c-1.92,0-3.81,1.13-3.81,3.33V9.67c0,2.21,1.89,3.33,3.77,3.33a3.87,3.87,0,0,0,3.45-2.18l-2.12-1C47,11.17,44.81,10.85,44.81,9.67ZM10.68,12.89h2.41V3.17H10.68ZM71.76,3.14H68.19V7.23L70.57,9.4v-4h1.27c.81,0,1.21.41,1.21,1v3c0,.63-.38,1.05-1.21,1.05H68.18v2.31h3.57c1.92,0,3.72-1,3.72-3.2V6.39C75.48,4.13,73.68,3.14,71.76,3.14ZM54.22,3c-2,0-4,1.1-4,3.34V9.66c0,2.23,2,3.34,4,3.34s3.95-1.11,3.95-3.34V6.34C58.19,4.11,56.2,3,54.22,3Zm1.55,6.66c0,.7-.78,1.06-1.54,1.06s-1.55-.35-1.55-1.06V6.34c0-.72.75-1.1,1.5-1.1s1.59.35,1.59,1.1ZM66.84,6.34c0-2.29-1.58-3.2-3.55-3.2H59.46v9.73h2.45V9.77h.43l2.22,3.09h3L65,9.52C66.13,9.15,66.84,8.14,66.84,6.34ZM63.33,7.65H61.91V5.43h1.42A1.11,1.11,0,1,1,63.33,7.65ZM29.83,13h2.42V3.06H29.83V6.73l-3,0V3.09H24.7v9.78h2.14V8.68l3,0ZM17.16,9.76V6.42c0-1.21,2.13-1.49,2.78-.28l2-.81a3.55,3.55,0,0,0-3.36-2.24c-1.92,0-3.81,1.13-3.81,3.33V9.76c0,2.21,2,3.15,3.9,3.15s3.58-1,3.58-3V7.58H18.79l0,1.36H20.3v.77C20.3,10.92,17.16,10.94,17.16,9.76Z" transform="translate(-2.93 -3)"/>
<polygon fill="currentColor" points="35.91 0.06 38.43 0.06 38.43 1.84 35.92 1.81 35.97 10 33.55 10 33.49 1.75 30.98 1.74 30.98 0.06 33.49 0.06 35.91 0.06"/>
@ -176,9 +178,13 @@ Core.prototype.initSettings = function () {
}
};
let classNameLayer
let classNameSocialLinks
let classNameModal
Core.prototype.initObserver = function () {
const mainObserver = new MutationObserver((mutations) => {
for (let i = 0, mlen = mutations.length; i < mlen; i++) {
const mutation = mutations[i];
if (typeof pluginModule !== "undefined") pluginModule.rawObserver(mutation);
@ -188,13 +194,9 @@ Core.prototype.initObserver = function () {
const node = mutation.addedNodes[0];
let [
classNameLayer,
classNameSocialLinks
] = [
BDModules.get((e) => e.layer && typeof e.layer === "string" && e.animating)[0].layer,
BDModules.get((e) => e.socialLinks && typeof e.socialLinks === "string")[0].socialLinks
]
if(!classNameLayer)classNameLayer = BDModules.get((e) => e.layer && typeof e.layer === "string" && e.animating)[0].layer
if(!classNameSocialLinks)classNameSocialLinks = BDModules.get((e) => e.socialLinks && typeof e.socialLinks === "string")[0].socialLinks
if(!classNameModal)classNameModal = BDModules.get((e) => e.modal && typeof e.modal === "string" && e.inner && typeof e.inner === "string" && !e.responsiveWidthMobile)[0].modal
if (node.classList.contains(classNameLayer)) {
if (node.getElementsByClassName("guild-settings-base-section").length) node.setAttribute("layer-id", "server-settings");
@ -208,6 +210,28 @@ Core.prototype.initObserver = function () {
if (node.parentElement == document.body && node.querySelector("#ace_settingsmenu")) node.id = "ace_settingsmenu_container";
/*
if(node.classList.contains(classNameModal)){
try{
/**
* @type {Element}
*//*
const UserProfile = Utils.getNestedProp(node, "childNodes.0.childNodes.0")
let user = BDV2.getInternalInstance(node).pendingProps.children.props.children.props.user
console.log(user)
if(UserProfile && UserProfile.childNodes.length === 2){
let header = UserProfile.childNodes[0].childNodes[0]
let children = BDV2.getInternalInstance(header).return.pendingProps.children[1]
console.log(children)
children.push(BDV2.react.createElement("p", {}, "sltsv"))
}
}catch(e){
console.error("An error occured in Badge Rendering:", e)
}
}*/
// Emoji Picker
//node.getElementsByClassName("emojiPicker-3m1S-j").length && !node.querySelector(".emojiPicker-3m1S-j").parentElement.classList.contains("animatorLeft-1EQxU0")
//if (node.classList.contains(classNameLayer2) && node.getElementsByClassName(classNameEmojiPicker).length && !node.querySelector("."+classNameEmojiPicker).parentElement.classList.contains(classNameAnimatorLeft)) quickEmoteMenu.obsCallback(node);
@ -481,7 +505,7 @@ Core.prototype.patchMemberList = function() {
children.push(
BDV2.React.createElement(TooltipWrap, {color: "black", side: "top", text: "Lightcord Developer"},
BDV2.React.createElement(Anchor, {className: "bd-member-badge", href: "https://github.com/Lightcord/Lightcord", title: "Lightcord", target: "_blank"},
BDV2.React.createElement(LightcordLogo, {size: "32px", className: "bd-logo"})
BDV2.React.createElement(LightcordLogo, {size: "16px", className: "bd-logo"})
)
)
);

View File

@ -180,6 +180,10 @@ export default new class EmojiModule {
this.cancelEmoteRender();
this.cancelEmoteRender = null;
}
start(){
}
}
function D(e, t, n, r, o, i, s, u, l, c) {

View File

@ -4,16 +4,11 @@ import * as crypto from "crypto"
import BDV2 from "./v2"
import tooltipWrap from "../ui/tooltipWrap"
import Utils from "./utils"
import { createReadStream } from "fs"
import { basename } from "path"
const debug = true
const cache = {}
const cache2 = {}
/*
const PinnedModule = BDModules.get(e => e.default && e.default.getPinnedMessages)[0].default
const ChannelModule = BDModules.get(e => e.default && e.default.getChannelId)[0].default
const fetchMessagesModule = BDModules.get(e => e.default && e.default.fetchMessages)[0].default
const fetchMessagesModule2 = BDModules.get(e => e.default && e.default.fetchMessages)[1].default
const getMessagesModule = BDModules.get(e => e.default && e.default.getMessages)[0].default*/
export default new class PluginCertifier {
constructor(){}
@ -25,103 +20,15 @@ export default new class PluginCertifier {
}
start(){
/*const dispatcher = window.Lightcord.DiscordModules.dispatcher
const constants = window.Lightcord.DiscordModules.constants
const originalFetchMessages = fetchMessagesModule.fetchMessages
fetchMessagesModule.fetchMessages = function(){
const returnValue = originalFetchMessages.apply(this, arguments)
if(returnValue instanceof Promise){
returnValue
.then(() => {
const ev = getMessagesModule.getMessages(ChannelModule.getChannelId())
process.nextTick(() => {
for(const message of ev._array){
const attachments = message.attachments || []
if(attachments.length === 0)continue // no attachments
attachments.forEach(attachment => {
processAttachment(attachment)
})
}
})
})
}
return returnValue
}
}
const originalFetchMessages2 = fetchMessagesModule2.fetchMessages
fetchMessagesModule2.fetchMessages = function(){
const returnValue = originalFetchMessages2.apply(this, arguments)
if(returnValue instanceof Promise){
returnValue
.then(() => {
const ev = getMessagesModule.getMessages(ChannelModule.getChannelId())
process.nextTick(() => {
for(const message of ev._array){
const attachments = message.attachments || []
if(attachments.length === 0)continue // no attachments
attachments.forEach(attachment => {
processAttachment(attachment)
})
}
})
})
}
return returnValue
}
const alreadyUsed = {}
const originalGetPinnedMessages = PinnedModule.getPinnedMessages.bind(PinnedModule)
PinnedModule.getPinnedMessages = function(){
const pinned = originalGetPinnedMessages(...arguments)
if(!pinned || alreadyUsed[pinned.id])return pinned
alreadyUsed[pinned.id] = true
setTimeout(() => {
delete alreadyUsed[pinned.id]
for(const message of pinned.messages){
const attachments = message.attachments || []
if(attachments.length === 0)continue // no attachments
attachments.forEach(attachment => {
processAttachment(attachment)
})
}
}, 50);
return pinned
}
dispatcher.subscribe(constants.ActionTypes.MESSAGE_CREATE, (ev) => {
const message = ev.message
if(message.channel_id !== ChannelModule.getChannelId())return
process.nextTick(() => {
const attachments = message.attachments || []
if(attachments.length === 0)return // no attachments
attachments.forEach(attachment => {
processAttachment(attachment)
})
})
})
const messages = getMessagesModule.getMessages(ChannelModule.getChannelId())
process.nextTick(() => {
for(const message of messages._array){
const attachments = message.attachments || []
if(attachments.length === 0)continue // no attachments
attachments.forEach(attachment => {
processAttachment(attachment)
})
}
})*/
isTrusted(hash){
return cache[hash] && !cache[hash].suspect
}
}
function checkViruses(hash, data, id){
export function checkViruses(hash, data, resultCallback, removeCallback){
data = data.toString("utf8").split(/[^\w\d]+/g)
let isHarmful = false
for(let keyword of data){
@ -161,8 +68,8 @@ function checkViruses(hash, data, id){
/_0x\w{4}\('0x[\dabcdef]+'\)/g,
/_0x\w{4}\('0x[\dabcdef]+'[, ]+'[^']{4}'\)/g, // _0x8db7('0x0', 'x1]f')
/** mangled */
/\w+\('0x[\dabcdef]+'\)/g,
/\w+\('0x[\dabcdef]+'[, ]+'[^']{4}'\)/g, // _0x8db7('0x0', 'x1]f')
/\w+\('0x[\dabcdef]+'\)/g, // b('0x0')
/\w+\('0x[\dabcdef]+'[, ]+'[^']{4}'\)/g, // b('0x0', 'x1]f')
]
for(let regex of regexps){
if(isHarmful)break
@ -171,7 +78,7 @@ function checkViruses(hash, data, id){
}
}
if(!isHarmful)return
if(!isHarmful)return removeCallback()
cache[hash] = {
suspect: true,
name: hashToUrl[hash].split("/").pop(),
@ -181,39 +88,57 @@ function checkViruses(hash, data, id){
console.log(`Found potentially dangerous ${cache[hash].type.toLowerCase()}: ${cache[hash].name}`)
renderToElements(id, cache[hash], cache[hash].name)
resultCallback(cache[hash])
}
const hashToUrl = {}
function processAttachment(attachment, id){
export function checkHash(hash, data, filename, resultCallback, removeCallback){
console.log(`File: ${filename} hash: ${hash}`)
if(!cache[hash]){
nodeFetch("https://cdn.jsdelivr.net/gh/Lightcord/filehashes@master/hashes/"+hash, { // Using node-fetch to bypass cors
headers: {
"User-Agent": electron.remote.getCurrentWebContents().userAgent // have to set user-agent
}
}).then(async res => {
if(res.status !== 200){
if(filename.endsWith(".theme.css"))return removeCallback()
return checkViruses(hash, data, resultCallback, wrongCallback)
}
const result = await res.json()
cache[hash] = result
resultCallback(result)
}).catch(()=>{})
}else{
const result = cache[hash]
resultCallback(result)
}
}
export function processFile(__path, resultCallback, removeCallback){
const hash = crypto.createHash("sha256")
let data = Buffer.alloc(0)
createReadStream(__path).on("data", chunk => {
data = Buffer.concat([data, chunk])
hash.update(chunk)
}).on("end", () => {
const hashResult = hash.digest("hex")
hashToUrl[hashResult] = __path
checkHash(hashResult, data, basename(__path), resultCallback, removeCallback)
})
}
export function processAttachment(attachment, id){
if(!document.getElementById(id))return
if(!attachment.url.startsWith("https://cdn.discordapp.com/"))return document.getElementById(id).remove()
if(!attachment.filename.endsWith(".plugin.js") && !attachment.filename.endsWith(".theme.css"))return document.getElementById(id).remove()
let nextHash = (hash, data) => {
if(!cache[hash]){
nodeFetch("https://cdn.jsdelivr.net/gh/Lightcord/filehashes@master/hashes/"+hash, { // Using node-fetch to bypass cors
headers: {
"User-Agent": electron.remote.getCurrentWebContents().userAgent // have to set user-agent
}
}).then(async res => {
if(res.status !== 200)return checkViruses(hash, data, id)
const result = await res.json()
cache[hash] = result
renderToElements(id, result, attachment.filename)
}).catch(()=>{})
}else{
const result = cache[hash]
renderToElements(id, result, attachment.filename)
}
}
if(cache2[attachment.url])return nextHash(cache2[attachment.url])
nodeFetch(attachment.url, {
headers: {
"User-Agent": electron.remote.getCurrentWebContents().userAgent
@ -232,7 +157,11 @@ function processAttachment(attachment, id){
cache2[attachment.url] = hashResult
hashToUrl[hashResult] = attachment.url
nextHash(hashResult, data)
checkHash(hashResult, data, attachment.filename, (result) => {
renderToElements(id, result, attachment.filename)
}, () => {
document.getElementById(id).remove()
})
})
}).catch(()=>{})
}
@ -252,7 +181,6 @@ function renderToElements(id, result, filename){
if(!flowerStarModule)flowerStarModule = BDModules.get(e => e.flowerStarContainer)[0]
if(!childModule)childModule = BDModules.get(e => e.childContainer)[0]
console.log(result)
if(result.suspect){
try{

View File

@ -107,7 +107,7 @@ PluginModule.prototype.loadPlugin = async function(filename) {
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${filename} could not be loaded.`, {type: "error"});
return Utils.err("ContentManager", `${filename} could not be loaded.`, error);
}
const plugin = Object.values(bdplugins).find(p => p.filename == filename).plugin;
const plugin = Object.values(bdplugins).find(p => (console.log(p.filename, filename)) || p.filename == filename).plugin;
try { if (plugin.load && typeof(plugin.load) == "function") plugin.load();}
catch (err) {if (settingsCookie["fork-ps-1"]) Utils.showContentErrors({plugins: [err]});}
Utils.log("ContentManager", `${plugin.getName()} v${plugin.getVersion()} was loaded.`);

View File

@ -9,6 +9,9 @@ import EditIcon from "./icons/edit";
import DeleteIcon from "./icons/delete";
import Switch from "./components/switch";
import TooltipWrap from "./tooltipWrap";
import { processFile } from "../modules/pluginCertifier";
import contentManager from "../modules/contentManager";
import { resolve } from "path";
const React = BDV2.React;
const anchorClasses = BDV2.anchorClasses;
@ -32,7 +35,8 @@ export default class V2C_PluginCard extends BDV2.reactComponent {
this.state = {
checked: this.props.enabled,
settings: false,
reloads: 0
reloads: 0,
trusted: false
};
}
@ -135,6 +139,10 @@ export default class V2C_PluginCard extends BDV2.reactComponent {
</TooltipWrap>;
}
componentWillUnmount(){
this.unmounted = true
}
get links() {
const links = [];
const addon = this.props.addon;
@ -182,7 +190,21 @@ export default class V2C_PluginCard extends BDV2.reactComponent {
if (this.state.settings) return this.settingsComponent;
const {authorId, authorLink} = this.props.addon;
return BDV2.react.createElement("div", {className: "bd-card bd-addon-card settings-closed ui-switch-item"},
const style = {}
if(!this.isScanning){
this.isScanning = true
processFile(resolve(this.props.addon.filename.endsWith(".plugin.js") ? contentManager.pluginsFolder : contentManager.themesFolder, this.props.addon.filename), (result) => {
if(this.unmounted)return
this.setState({
isTrusted: !result.suspect
}, () => {})
})
}else{
if(this.state.isTrusted){
style.borderColor = "#4087ed"
}
}
return BDV2.react.createElement("div", {className: "bd-card bd-addon-card settings-closed ui-switch-item", style},
BDV2.react.createElement("div", {className: "bd-addon-header bda-header"},
BDV2.react.createElement("div", {className: "bd-card-title bda-header-title"}, this.buildTitle(this.name, this.version, {name: this.author, id: authorId, link: authorLink})),
BDV2.react.createElement("div", {className: "bd-addon-controls bda-controls"},

View File

@ -95,7 +95,8 @@ export default class CardList extends BDV2.reactComponent {
toggle: this.manager.toggle.bind(this.manager),
//edit: this.edit.bind(this),
remove: this.delete.bind(this),
addon: addon
addon: addon,
hash: addon.hash
};
}

BIN
assets/logo-wide.psd Normal file

Binary file not shown.

View File

@ -109,8 +109,6 @@ async function privateInit(){
fs.writeFileSync(ZeresPluginLibraryPath, content)
})
// Should we download 1XenoLib and BDFDB too ? response: No
BetterDiscordConfig.haveInstalledDefault = true // Inform User about what we just did
}
if(!fs.existsSync(themePath)){