Modifying LightcordApi

Adding category in settings for lightcordapi
making module alias
Fixing a little bit the minimal mode
This commit is contained in:
Jean Ouina 2020-07-15 01:50:02 +02:00
parent d3494666a2
commit 22ff405d40
31 changed files with 3635 additions and 2392 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -6,6 +6,7 @@ export const minimumDiscordVersion = "0.0.306";
export const currentDiscordVersion = (window.DiscordNative && window.DiscordNative.remoteApp && window.DiscordNative.remoteApp.getVersion && window.DiscordNative.remoteApp.getVersion()) || "0.0.306";
export const minSupportedVersion = "0.3.0";
export const bbdVersion = "0.3.4";
/*
export const LCChanelog = {
description: "Lightcord's changelog",
changes: [
@ -36,7 +37,7 @@ export const LCChanelog = {
const supportLink = Anchor ? BDV2.React.createElement(Anchor, {onClick: joinSupportServer}, "Join our Discord Server.") : BDV2.React.createElement("a", {className: `${AnchorClasses.anchor} ${AnchorClasses.anchorUnderlineOnHover}`, onClick: joinSupportServer}, "Join our Discord Server.");
return BDV2.React.createElement(TextElement, {size: TextElement.Sizes.SMALL, color: TextElement.Colors.STANDARD}, "Need support? ", supportLink);
})()
}
}*/
export const bbdChangelog = {
description: "BBD's changelog.",
changes: [
@ -91,7 +92,7 @@ export const settings = {
"Disable BetterDiscord": {id: "bd-disable", info: "Disable Betterdiscord (plugins, themes, etc) (Not implemented).", implemented: false, hidden: false, cat: "lightcord", category: "Lightcord"},
"Blur Personal Information": {id: "lightcord-6", info: "Blur sensitive informations like email, payment infos and more.", implemented: true, hidden: false, cat: "lightcord", category: "Lightcord"},
"Calling Ring Beat": {id: "lightcord-2", info: "Enable Discord's special calling beat.", implemented: true, hidden: false, cat: "lightcord", category: "Lightcord"},
"Developer Options": {id: "lightcord-1", info: "Enable Discord's & Lightcord's Internal Developer Options. This allow the \"Experiments\" tab, the \"Developer Options\" tab and the \"Api Components\" tab.", implemented: true, hidden: false, cat: "lightcord", category: "Lightcord"},
"Developer Options": {id: "lightcord-1", info: "Enable Discord's & Lightcord's Internal Developer Options. This allow the \"Experiments\" tab, the \"Developer Options\" tab and the \"Lightcord Api\" section.", implemented: true, hidden: false, cat: "lightcord", category: "Lightcord"},
"Ad Block": {id: "lightcord-4", info: "Block any BOT that dms you with an invite link. Even in an embed.", implemented: true, hidden: false, cat: "lightcord", category: "Lightcord"},
"Enable Lightcord Servers": {id: "lightcord-5", info: "Enable Lightcord's servers. Disabling this will disable custom badges.", implemented: true, hidden: false, cat: "lightcord", category: "Lightcord"},
"Disable typing": {id: "lightcord-7", info: "Don't let other see you're typing.", implemented: true, hidden: false, cat: "lightcord", category: "Lightcord"},

View File

@ -54,8 +54,9 @@ window.BdApi = BdApi;
import Core from "./modules/core";
deprecateGlobal("mainCore", Core);
export default class CoreWrapper {
constructor(bdConfig) {
constructor(bdConfig, methods) {
Core.setConfig(bdConfig);
Core.setMethods(methods);
}
init() {

View File

@ -25,10 +25,16 @@ function Core() {
// this.init();
}
let methods
Core.prototype.setConfig = function(config) {
Object.assign(bdConfig, config);
};
Core.prototype.setMethods = function(m) {
methods = m
};
Core.prototype.init = async function() {
if (!Array.prototype.flat) {
Utils.alert("Not Supported", "BetterDiscord v" + bbdVersion + " does not support this old version (" + currentDiscordVersion + ") of Discord. Please update your Discord installation before proceeding.");

View File

@ -47,6 +47,8 @@ class BDSidebarHeader extends React.PureComponent {
}
}
let isClearingCache = false
export default new class V2_SettingsPanel {
constructor() {
@ -331,7 +333,7 @@ export default new class V2_SettingsPanel {
return BDV2.react.createElement(SectionedSettingsPanel, {key: "cspanel", onChange: this.onChange, sections: this.coreSettings})
}
lightcordComponent(sidebar) {
lightcordComponent(sidebar, forceUpdate) {
let appSettings = remote.getGlobal("appSettings")
return [
this.lightcordSettings.map((section, i) => {
@ -379,7 +381,38 @@ export default new class V2_SettingsPanel {
remote.app.quit()
},
wrapper: true
}, "Relaunch without BetterDiscord")
}, "Relaunch without BetterDiscord"),
React.createElement(Lightcord.Api.Components.inputs.Button, {
color: "yellow",
look: "ghost",
size: "medium",
hoverColor: "red",
onClick: () => {
if(isClearingCache)return
isClearingCache = true
Utils.showToast("Clearing cache...", {
type: "info"
})
forceUpdate()
remote.getCurrentWebContents().session.clearCache()
.then(() => {
Utils.showToast("Cache is cleared !", {
type: "success"
})
isClearingCache = false
forceUpdate()
}).catch(err => {
console.error(err)
Utils.showToast("An error occured. Check console for more informations.", {
type: "error"
})
isClearingCache = false
forceUpdate()
})
},
wrapper: true,
disabled: isClearingCache
}, "Clear cache")
]
}
@ -444,7 +477,7 @@ export default new class V2_SettingsPanel {
function makeComponent(children){
class SettingComponent extends React.Component {
render(){
return children(sidebar)
return children(sidebar, () => this.forceUpdate())
}
}
let sidebar

View File

@ -41,10 +41,25 @@ export default class V2_SettingsPanel_Sidebar {
id: "accountinfo"
}
]
if(window.Lightcord.Settings.devMode)items.push({
text: "Api Components Preview",
id: "lcapipreview"
})
return items
}
get LCDevItems(){
let items = []
if(!window.Lightcord.Settings.devMode)return items
items.push(...[
{
section: "DIVIDER"
},
{
section: "HEADER",
label: "Lightcord Api"
},
{
text: "Components Preview",
id: "lcapipreview"
}
])
return items
}
@ -61,6 +76,14 @@ export default class V2_SettingsPanel_Sidebar {
element: this.getComponent(e.id, sidebar)
}
}),
...this.LCDevItems.map(e => {
if(e.section)return e
return {
section: e.id,
label: e.text,
element: this.getComponent(e.id, sidebar)
}
}),
{
section: "DIVIDER"
},

File diff suppressed because one or more lines are too long

View File

@ -1101,6 +1101,16 @@
}
}
},
"@types/bandagedbd__bdapi": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@types/bandagedbd__bdapi/-/bandagedbd__bdapi-0.2.2.tgz",
"integrity": "sha512-igNQMnEticrue5LYXkKZGP6pGoN/mQTzUDJ2Jjr5T7wghgmkePTyk8r09N9Dm7fTjHH1aEmQmSSRv9s4Ce35bQ==",
"dev": true,
"requires": {
"@types/react": "*",
"@types/react-dom": "*"
}
},
"@types/json-schema": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz",

View File

@ -18,6 +18,7 @@
"@babel/preset-env": "^7.3.4",
"@babel/preset-react": "^7.0.0",
"@babel/register": "^7.0.0",
"@types/bandagedbd__bdapi": "^0.2.2",
"@types/react": "^16.9.36",
"@types/react-dom": "^16.9.8",
"@types/uuid": "^8.0.0",

View File

@ -1,39 +1,35 @@
/**
* @name LightcordApiExemple
*/
module.exports = class LightcordApiExemple {
getName() {return "LightcordApiExemple";} // Name of your plugin to show on the plugins page
getDescription() {return "Describe the basic functions. Maybe a support server link.";} // Description to show on the plugins page
getVersion() {return "0.0.1";} // Current version. I recommend following semantic versioning <http://semver.org/> (e.g. 0.0.1)
getAuthor() {return "Not Thomiz";} // Your name
load() {} // Called when the plugin is loaded in to memory
start() {
if(!("Lightcord" in window) || !("Api" in window.Lightcord)){
bdApi.showToast("This plugin only works in Lightcord.")
return
}
console.log(`LightcordAPI is availaible !`)
} // Called when the plugin is activated (including after reloads)
stop() {} // Called when the plugin is deactivated
observer(changes) {} // Observer for the `document`. Better documentation than I can provide is found here: <https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver>
getSettingsPanel(){
let settings = [
{
component: "inputs.Button",
props: {
children: [
"sltsv"
],
color: "red"
}
}
]
return windows.Lightcord.Api.Utils.PluginUtils.renderSettings(settings)
}
}
/**
* @name LightcordApiExemple
*/
module.exports = class LightcordApiExemple {
getName() {return "LightcordApiExemple";} // Name of your plugin to show on the plugins page
getDescription() {return "Describe the basic functions. Maybe a support server link.";} // Description to show on the plugins page
getVersion() {return "0.0.1";} // Current version. I recommend following semantic versioning <http://semver.org/> (e.g. 0.0.1)
getAuthor() {return "Not Thomiz";} // Your name
load() {} // Called when the plugin is loaded in to memory
start() {
if(!("Lightcord" in window) || !("Api" in window.Lightcord)){
bdApi.showToast("This plugin only works in Lightcord.")
return
}
console.log(`LightcordAPI is availaible !`)
} // Called when the plugin is activated (including after reloads)
stop() {} // Called when the plugin is deactivated
observer(changes) {} // Observer for the `document`. Better documentation than I can provide is found here: <https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver>
getSettingsPanel(){
const Markdown = BDModules.get(e => e.default && e.default.displayName === "Markdown")[0].default
let settings = Markdown.prototype.render.call({
props: Object.assign({
className: "",
children: "> warn\n> sltsv"
}, Markdown.defaultProps)
})
return window.Lightcord.Api.Utils.ReactToHTML(settings)
}
}

View File

@ -40,6 +40,11 @@ type ColorPickerProps = {
let ColorPickerModules
let isFetching = null
/**
* This componennt needs to be loaded. As a result, you may experience 100-300ms loading the first time.
* Render will return `null` before loaded.
*/
export default class ColorPicker extends React.PureComponent<ColorPickerProps, {value?:string,lastColor:any}> {
constructor(props:ColorPickerProps){
super(props)
@ -50,6 +55,13 @@ export default class ColorPicker extends React.PureComponent<ColorPickerProps, {
this.onChange = this.onChange.bind(this)
}
/** Preload the component. */
static preload(){
if(ColorPicker.prototype.modules[0])return
if(isFetching)return
new ColorPicker({}).render()
}
onChange(val){
this.props.onChange(val)
this.setState({
@ -79,7 +91,10 @@ export default class ColorPicker extends React.PureComponent<ColorPickerProps, {
] = this.modules
if(!ColorPickerComponent){
if(isFetching)isFetching.then(e => this.forceUpdate()) // support for multiple color picker
if(isFetching){ // support for multiple color picker
isFetching.then(() => this.forceUpdate())
return null
}
ColorPickerModules = null
let resolve
isFetching = new Promise(res => (resolve = res))
@ -160,7 +175,8 @@ export default class ColorPicker extends React.PureComponent<ColorPickerProps, {
}
static help = {
info: "To convert hex colors to decimal, you can do `Lightcord.Api.Utils.HexColorToDecimal('#yourcolor')` and go back with `Lightcord.Api.Utils.DecimalColorToHex(7506394)`"
info: "To convert hex colors to decimal, you can do `Lightcord.Api.Utils.HexColorToDecimal('#yourcolor')` and go back with `Lightcord.Api.Utils.DecimalColorToHex(7506394)`",
warn: "The component may not appear instantly. The component need to be loaded, so you could experience 50-300ms loading time depending on your internet connection."
}
}
let AllPreviews

View File

@ -0,0 +1,51 @@
import WebpackLoader from "../../modules/WebpackLoader"
import { notice, notices, events } from "./Notices"
import NOOP from "../../modules/noop"
let NoticeModules
export const defaultNotice:notice = {
text: "",
id: "unknown id",
onClick: NOOP,
buttonText: null,
type: "default"
}
export default class Notice extends React.Component<notice> {
static displayName = "LightcordNotice"
static defaultProps:notice = defaultNotice
get modules(){
return NoticeModules || (NoticeModules = [
WebpackLoader.find(e => e.noticeInfo)
])
}
render(){
const [
noticeClasses
] = this.modules
const className = noticeClasses["notice"+this.props.type.slice(0, 1).toUpperCase()+this.props.type.slice(1)]
if(!className){
notices.pop()
setImmediate(() => {
events.emit("noticeUpdate")
})
return null
}
const button = this.props.buttonText ? <button className={noticeClasses.button} onClick={() => {
notices.pop()
this.props.onClick()
events.emit("noticeUpdate")
}}>{this.props.buttonText}</button> : null
return <div className={className}>
<div className={noticeClasses.dismiss} role="button" tabIndex={0} onClick={() => {
notices.pop()
events.emit("noticeUpdate")
}} />
{this.props.text}
{button}
</div>
}
}

View File

@ -0,0 +1,50 @@
import Notice from "./Notice"
import uuid from "../../modules/uuid"
import { EventEmitter } from "events"
export const events = new EventEmitter()
export default class Notices extends React.Component<{container: any}> {
static displayName = "LightcordNotices"
static defaultProps = {}
constructor(props: Readonly<{ container: any }>){
super(props)
this.noticeHandler = this.noticeHandler.bind(this)
}
noticeHandler(){
this.forceUpdate()
}
componentWillMount(){
events.on("noticeUpdate", this.noticeHandler)
}
componentWillUnmount(){
events.off("noticeUpdate", this.noticeHandler)
}
render(){
if(!this.hasNotice)return null
const notice = notices[0]
return <Notice {...notice}></Notice>
}
get hasNotice(){
return notices.length > 0
}
}
export const notices:notice[] = []
export type noticeWithoutID = {
text: string,
buttonText?: string,
onClick?: () => void,
type: "default"|"info"|"success"|"danger"|"streamerMode"|"download"|"notification"|"premium"|"richPresence"|"premiumTier1"|"premiumTier2"|"facebook"|"brand"|"survey"|"spotify"
}
export type notice = {
id: string
} & noticeWithoutID

View File

@ -1,65 +1,69 @@
import WebpackLoader from "./modules/WebpackLoader"
import Components from "./components/components"
import uuid from "./modules/uuid"
import Utils from "./modules/Utils"
const LightcordApi = {
WebpackLoader: WebpackLoader,
Components: Components,
uuid: uuid,
Utils: Utils
}
declare global {
var React:typeof import("react")
interface Window {
Lightcord: LightcordGlobal,
BDModules: {
modules:any[],
get(filter:(mod:any)=>boolean, modules?:any[]):any[],
get(id:number, modules?:any[]):any,
get(ids: [number|((mod:any)=>boolean)], modules?:any[]):any
}
}
var Lightcord:LightcordGlobal
}
export default LightcordApi
Object.assign(window.Lightcord.Api, LightcordApi)
/**
* The main Lightcord exports. Can be accessed with `window.Lightcord`
*/
export interface LightcordGlobal {
DiscordModules: {
/**
* Internal Discord's dispatcher - can be used to subscribe to gateway events / client events.
*/
dispatcher: import("./types/DiscordDispatcherTypes").default,
constants: import("./types/DiscordConstantsTypes").default
},
Settings: {
devMode: boolean,
callRingingBeat: boolean
},
Api: LightcordApiGlobal
}
/**
* The main Api. Can be accessed with `window.Lightcord.Api`
*/
type LightcordApiGlobal = lightcordApiMainExports & typeof LightcordApi
type lightcordApiMainExports = {
/**
* Waits until the first module that match the filter gets exported
* @param filter The filter that specifies the module to match.
*/
ensureExported(filter: (mod:any) => boolean):Promise<any>,
/**
* Recreate the object without the `__proto__` and `prototype` properties - usefull for better formatting in console.
* @param obj The object to recreate
*/
cloneNullProto<Obj = any>(obj:Obj):Obj
import WebpackLoader from "./modules/WebpackLoader"
import Components from "./components/components"
import uuid from "./modules/uuid"
import Utils from "./modules/Utils"
import DiscordTools from "./modules/DiscordTools"
import * as patchers from "./modules/patchers"
patchers.patch()
const LightcordApi = {
WebpackLoader: WebpackLoader,
Components: Components,
uuid: uuid,
Utils: Utils,
DiscordTools: DiscordTools
}
declare global {
var React:typeof import("react")
interface Window {
Lightcord: LightcordGlobal,
BDModules: {
modules:any[],
get(filter:(mod:any)=>boolean, modules?:any[]):any[],
get(id:number, modules?:any[]):any,
get(ids: [number|((mod:any)=>boolean)], modules?:any[]):any
}
}
var Lightcord:LightcordGlobal
}
export default LightcordApi
Object.assign(window.Lightcord.Api, LightcordApi)
/**
* The main Lightcord exports. Can be accessed with `window.Lightcord`
*/
export interface LightcordGlobal {
DiscordModules: {
/**
* Internal Discord's dispatcher - can be used to subscribe to gateway events / client events.
*/
dispatcher: import("./types/DiscordDispatcherTypes").default,
constants: import("./types/DiscordConstantsTypes").default
},
Settings: {
devMode: boolean,
callRingingBeat: boolean
},
Api: LightcordApiGlobal
}
/**
* The main Api. Can be accessed with `window.Lightcord.Api`
*/
type LightcordApiGlobal = lightcordApiMainExports & typeof LightcordApi
type lightcordApiMainExports = {
/**
* Waits until the first module that match the filter gets exported
* @param filter The filter that specifies the module to match.
*/
ensureExported(filter: (mod:any) => boolean):Promise<any>,
/**
* Recreate the object without the `__proto__` and `prototype` properties - usefull for better formatting in console.
* @param obj The object to recreate
*/
cloneNullProto<Obj = any>(obj:Obj):Obj
}

View File

@ -0,0 +1,163 @@
import { notices, noticeWithoutID, notice, events as noticeEvents } from "../components/private/Notices";
import Utils from "./Utils";
import uuid from "./uuid";
import cloneNullProto from "./cloneNullProto";
import { EventEmitter } from "events";
import { defaultNotice } from "../components/private/Notice";
import excludeProperties from "./excludeProperties";
import NOOP from "./noop";
import WebpackLoader, { WebpackLoaderError } from "./WebpackLoader";
let soundModule
export default new class DiscordTools {
showNotice(data:NoticeData):Notice{
if(typeof data !== "object" || typeof data.text !== "string")throw new Error(`This notice is not valid. Given: ${Utils.formatJSObject(data)}`)
let newData = cloneNullProto(Object.assign({}, defaultNotice, data)) as notice
newData.id = uuid()
notices.push(newData)
noticeEvents.emit("noticeUpdate")
const notice = new Notice(newData)
return notice
}
get notices():Notice[]{
return notices.map(data => new Notice(data))
}
/**
* Quickly send notification (Even when no focused.)
* @param data The notification. Be sure to include all properties except functions cause they're optional.
* Notifications have a timeout of 3-5 seconds.
* They look like this: https://i.imgur.com/jzuxKKu.png
*/
showNotification(data:NotificationData):Notification{
const notification = new window.Notification(data.title, excludeProperties(data, [
"title",
"onClick",
"onClose",
"onShow"
]))
notification.onclick = data.onClick || NOOP
notification.onshow = data.onShow || NOOP
notification.onclose = data.onClose || NOOP
return notification
}
playSound(sound:Sound){
soundModule = soundModule || WebpackLoader.findByUniqueProperties(["createSound"])
if(!soundModule)throw new WebpackLoaderError("Couldn't find soundModule here.")
const created = soundModule.createSound(sound)
created.play()
return created
}
}
export type Sound = "call_calling"|"call_ringing"|"call_ringing_beat"|"ddr-down"|"ddr-left"|"ddr-right"|"ddr-up"|"deafen"|"discodo"|"disconnect"|"human_man"|"mention1"|"mention2"|"mention3"|"message1"|"message2"|"message3"|"mute"|"overlayunlock"|"ptt_start"|"ptt_stop"|"reconnect"|"robot_man"|"stream_ended"|"stream_started"|"stream_user_joined"|"stream_user_left"|"undeafen"|"unmute"|"user_join"|"user_leave"|"user_moved"
export type NotificationData = {
title: string,
body: string,
icon: string,
onShow?: () => void,
onClick?: () => void,
onClose?: () => void
}
export type NoticeData = noticeWithoutID
const EventHandler = function(){
if(this.removed !== this.state.removed){
if(this.removed){
this.emit("removed")
}
}
if(this.showing !== this.state.showing){
if(this.showing){
this.emit("showing", true)
}else{
this.emit("showing", false)
}
}
if(this.index !== this.state.index){
this.emit("index", this.index)
}
}
/** A notice interface for modifying it and subscribing to events. */
export class Notice extends EventEmitter {
constructor(data){
super()
this.data = data
this.state = {
removed: this.removed,
showing: this.showing,
index: this.index
}
let eventFunc = EventHandler.bind(this)
noticeEvents.on("noticeUpdate", eventFunc)
this.on("removed", () => {
noticeEvents.off("noticeUpdate", eventFunc)
})
}
state:{
removed:boolean,
showing:boolean,
index:number
}
get removed():boolean{
return !notices.find(e => e.id === this.id)
}
get showing():boolean{
return this.index === 0
}
get index():number{
return notices.findIndex(e => e.id === this.id)
}
get id(){
return this.data.id
}
get text(){
return this.data.text
}
set text(text){
this.data.text = text
noticeEvents.emit("noticeUpdate")
}
get type(){
return this.data.type
}
set type(type){
this.data.type = type
noticeEvents.emit("noticeUpdate")
}
get buttonText(){
return this.data.buttonText
}
set buttonText(buttonText:string){
this.data.buttonText = buttonText
noticeEvents.emit("noticeUpdate")
}
get onClick(){
return this.data.onClick
}
set onClick(onClick){
this.data.onClick = onClick
noticeEvents.emit("noticeUpdate")
}
remove(){
if(this.removed)return
notices.splice(this.index, 1)
noticeEvents.emit("noticeUpdate")
}
data:notice
}

View File

@ -31,4 +31,80 @@ export default new class Utils {
if(isNaN(res))throw new Error(`Invalid color: ${color}`)
return res
}
removeDa(className:string):string{
if(!className)return className
return className.split(" ").filter(e => !e.startsWith("da-")).join(" ")
}
FindReact(dom:Element, traverseUp:number = 0):React.Component|React.PureComponent{
/** took from https://stackoverflow.com/questions/29321742/react-getting-a-component-from-a-dom-element-for-debugging */
const key = Object.keys(dom).find(key=>key.startsWith("__reactInternalInstance$"));
const domFiber = dom[key];
if (domFiber == null) return null;
// react <16
if (domFiber._currentElement) {
let compFiber = domFiber._currentElement._owner;
for (let i = 0; i < traverseUp; i++) {
compFiber = compFiber._currentElement._owner;
}
return compFiber._instance;
}
// react 16+
const GetCompFiber = fiber=>{
//return fiber._debugOwner; // this also works, but is __DEV__ only
let parentFiber = fiber.return;
while (typeof parentFiber.type == "string") {
parentFiber = parentFiber.return;
}
return parentFiber;
};
let compFiber = GetCompFiber(domFiber);
for (let i = 0; i < traverseUp; i++) {
compFiber = GetCompFiber(compFiber);
}
return compFiber.stateNode;
}
hasClass(classNames:string, className:string):boolean{
if(!classNames || !className)return false
const classnames = classNames.split(" ")
for(let classname of this.removeDa(className).split(" ")){
if(!classnames.includes(classname))return false
}
return true
}
formatJSObject(obj:any):string{
if(["string", "number", "boolean", "bigint", "undefined"].includes(typeof obj))return JSON.stringify(obj)
if(obj === null)return "null"
if(typeof obj === "function")return String(obj)
if(typeof obj === "symbol")return String(obj)
if(Array.isArray(obj)){
if(!obj.length)return "[]"
return `[\n ${obj.map(e => this.formatJSObject(e)).join(",\n ")}\n]`
}else{
const keys = Object.keys(obj)
if(keys.length === 0)return "{}"
return `{\n ${keys.map(key => {
let original = key
if(typeof key === "symbol")key = "["+String(key)+"]"
else{
if(typeof key === "number")key = String(key)
else{
console.log(key)
if(isNaN(parseInt(key[0]))){
key = this.formatJSObject(key)
}else if(/[^\w\d_$]/g.test(key)){
key = this.formatJSObject(key)
}
}
}
return `${key}: ${this.formatJSObject(obj[original])}`
})}\n}`
}
}
}

View File

@ -46,4 +46,12 @@ export default new class WebpackLoader {
return true
})
}
}
export class WebpackLoaderError extends Error {
constructor(message:string = ""){
message += "\n\tThis error is related to Lightcord not being able to find a WebpackModule. \n\tPlease show this error and a few lines of logs above this error. \n\tOpen an issue on https://github.com/Lightcord/Lightcord or in their discord server."
super(message)
this.name = "WebpackLoaderError"
}
}

View File

@ -0,0 +1,61 @@
import Utils from "./Utils"
import Notices, { notices } from "../components/private/Notices"
export function patch(){
/** START NOTICE */
getModule(e => e.default && e.default.displayName === "ConnectedAppView")
.then(async (mod) => {
const appClasses = await getModule(e => e.hasNotice);
const buildRender = original => {
return function render(){
const returnValue = original.call(this, ...arguments)
const newchildren = []
let children = returnValue.props.children[1].props.children
if(!Array.isArray(children))children = [children]
newchildren.push(children[0])
newchildren.push(React.createElement(Notices, {container: this}))
newchildren.push(children[1])
returnValue.props.children[1].props.children = newchildren
returnValue.props.children[1].props.children[2].props.children[0].props.render = buildRenderChannelSidebar(returnValue.props.children[1].props.children[2].props.children[0].props.render)
return returnValue
}
}
const buildRenderChannelSidebar = original => {
return function renderChannelSidebar(){
const returnValue = original.call(this, ...arguments)
const hasNotice = notices.length > 0
if(!hasNotice)return returnValue
if(!Utils.hasClass(returnValue.props.className, appClasses.hasNotice)){
returnValue.props.className += " "+Utils.removeDa(appClasses.hasNotice)
}
return returnValue
}
}
mod.default.prototype.render = buildRender(mod.default.prototype.render);
(async function(){
const base = document.querySelector("."+Utils.removeDa(appClasses.base))
if(!base)throw new Error(`Could not find base here`)
const elem = Utils.FindReact(base) as any
elem.render = buildRender(elem.render)
elem.forceUpdate()
})()
})
/** END NOTICE */
}
function getModule(filter: (mod:any) => boolean):Promise<any>{
return new Promise((resolve) => {
window.Lightcord.Api.ensureExported(filter)
.then(resolve)
.catch(err => {
console.error("[LIGHTCORD]", err, filter)
})
})
}

View File

@ -0,0 +1,3 @@
module.exports = {
}

View File

@ -47,22 +47,11 @@ async function privateInit(){
ModuleLoader.get(e => e.getCurrentHub)[0].getCurrentHub().getClient().getOptions().enabled = false
// setting react in require cache
try{
window.React = require("react")
}catch(e){
const React = ModuleLoader.get(e => !["Component", "PureComponent", "Children", "createElement", "cloneElement"].map(c => !!e[c]).includes(false))[0]
window.React = React
require.cache["react"] = React
}
const React = ModuleLoader.get(e => !["Component", "PureComponent", "Children", "createElement", "cloneElement"].map(c => !!e[c]).includes(false))[0]
window.React = React
try{
window.ReactDOM = require("react-dom")
}catch(e){
const ReactDOM = ModuleLoader.get(e => e.findDOMNode)[0]
window.ReactDOM = ReactDOM
require.cache["react-dom"] = ReactDOM
}
const ReactDOM = ModuleLoader.get(e => e.findDOMNode)[0]
window.ReactDOM = ReactDOM
//stop here if betterdiscord is disabled.
if(electron.remote.process.argv.includes("--disable-betterdiscord")){
@ -290,7 +279,7 @@ async function privateInit(){
DiscordNative.ipc.send("UPDATE_THEME", data.settings.theme)
})
require("../../../../../LightcordApi/js/main.js")
require("lightcordapi/js/main.js")
/*
if(shouldShowPrompt){
@ -363,7 +352,7 @@ async function privateInit(){
dispatcher.subscribe(constants.ActionTypes.CONNECTION_OPEN || "CONNECTION_OPEN", onConn)
}*/
const BetterDiscord = window.BetterDiscord = window.mainCore = new(require("../../../../../BetterDiscordApp/js/main.js").default)(BetterDiscordConfig)
const BetterDiscord = window.BetterDiscord = window.mainCore = new(require("../../../../../BetterDiscordApp/js/main.js").default)(BetterDiscordConfig, require("./betterdiscord"))
const Utils = window.Lightcord.BetterDiscord.Utils
const DOMTools = window.Lightcord.BetterDiscord.DOM
@ -1247,6 +1236,10 @@ require.extensions[".jsbr"] = (m, filename) => {
fs.writeFileSync(tmpFile.name+".js", zlib.brotliDecompressSync(fs.readFileSync(filename)))
return require.extensions[".js"](m, tmpFile.name+".js")
}
require.extensions[".txt"] = (m, filename) => {
m.exports = fs.readFileSync(filename, "utf8")
return m.exports
}
const LightcordBDFolder = path.join(electron.remote.app.getPath("appData"), "Lightcord_BD")
@ -1337,7 +1330,7 @@ path.originalResolve = originalResolve
let blacklist
function isBlacklisted(id){
if(!blacklist)blacklist = fs.readFileSync(path.join(__dirname, "blacklist.txt"), "utf8").split(/[\n\r]+/g).map((line, index, lines) => {
if(!blacklist)blacklist = require("./blacklist.txt").split(/[\n\r]+/g).map((line, index, lines) => {
let id = ""
let comment = ""
line.split("#").forEach((idOrComment, index, array) => {

View File

@ -0,0 +1,253 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2018, Nick Gavrilov
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* From https://github.com/ilearnio/module-alias
*/
'use strict'
var BuiltinModule = require('module')
// Guard against poorly mocked module constructors
var Module = module.constructor.length > 1
? module.constructor
: BuiltinModule
var nodePath = require('path')
var modulePaths = []
var moduleAliases = {}
var moduleAliasNames = []
var oldNodeModulePaths = Module._nodeModulePaths
Module._nodeModulePaths = function (from) {
var paths = oldNodeModulePaths.call(this, from)
// Only include the module path for top-level modules
// that were not installed:
if (from.indexOf('node_modules') === -1) {
paths = modulePaths.concat(paths)
}
return paths
}
var oldResolveFilename = Module._resolveFilename
Module._resolveFilename = function (request, parentModule, isMain, options) {
for (var i = moduleAliasNames.length; i-- > 0;) {
var alias = moduleAliasNames[i]
if (isPathMatchesAlias(request, alias)) {
var aliasTarget = moduleAliases[alias]
// Custom function handler
if (typeof moduleAliases[alias] === 'function') {
var fromPath = parentModule.filename
aliasTarget = moduleAliases[alias](fromPath, request, alias)
if (!aliasTarget || typeof aliasTarget !== 'string') {
throw new Error('[module-alias] Expecting custom handler function to return path.')
}
}
request = nodePath.join(aliasTarget, request.substr(alias.length))
// Only use the first match
break
}
}
return oldResolveFilename.call(this, request, parentModule, isMain, options)
}
function isPathMatchesAlias (path, alias) {
// Matching /^alias(\/|$)/
if (path.indexOf(alias) === 0) {
if (path.length === alias.length) return true
if (path[alias.length] === '/') return true
}
return false
}
function addPathHelper (path, targetArray) {
path = nodePath.normalize(path)
if (targetArray && targetArray.indexOf(path) === -1) {
targetArray.unshift(path)
}
}
function removePathHelper (path, targetArray) {
if (targetArray) {
var index = targetArray.indexOf(path)
if (index !== -1) {
targetArray.splice(index, 1)
}
}
}
function addPath (path) {
var parent
path = nodePath.normalize(path)
if (modulePaths.indexOf(path) === -1) {
modulePaths.push(path)
// Enable the search path for the current top-level module
var mainModule = getMainModule()
if (mainModule) {
addPathHelper(path, mainModule.paths)
}
parent = module.parent
// Also modify the paths of the module that was used to load the
// app-module-paths module and all of it's parents
while (parent && parent !== mainModule) {
addPathHelper(path, parent.paths)
parent = parent.parent
}
}
}
function addAliases (aliases) {
for (var alias in aliases) {
addAlias(alias, aliases[alias])
}
}
function addAlias (alias, target) {
moduleAliases[alias] = target
// Cost of sorting is lower here than during resolution
moduleAliasNames = Object.keys(moduleAliases)
moduleAliasNames.sort()
}
/**
* Reset any changes maded (resets all registered aliases
* and custom module directories)
* The function is undocumented and for testing purposes only
*/
function reset () {
var mainModule = getMainModule()
// Reset all changes in paths caused by addPath function
modulePaths.forEach(function (path) {
if (mainModule) {
removePathHelper(path, mainModule.paths)
}
// Delete from require.cache if the module has been required before.
// This is required for node >= 11
Object.getOwnPropertyNames(require.cache).forEach(function (name) {
if (name.indexOf(path) !== -1) {
delete require.cache[name]
}
})
var parent = module.parent
while (parent && parent !== mainModule) {
removePathHelper(path, parent.paths)
parent = parent.parent
}
})
modulePaths = []
moduleAliases = {}
moduleAliasNames = []
}
/**
* Import aliases from package.json
* @param {object} options
*/
function init (options) {
if (typeof options === 'string') {
options = { base: options }
}
options = options || {}
var candidatePackagePaths
if (options.base) {
candidatePackagePaths = [nodePath.resolve(options.base.replace(/\/package\.json$/, ''))]
} else {
// There is probably 99% chance that the project root directory in located
// above the node_modules directory,
// Or that package.json is in the node process' current working directory (when
// running a package manager script, e.g. `yarn start` / `npm run start`)
candidatePackagePaths = [nodePath.join(__dirname, '../..'), process.cwd()]
}
var npmPackage
var base
for (var i in candidatePackagePaths) {
try {
base = candidatePackagePaths[i]
npmPackage = require(nodePath.join(base, 'package.json'))
break
} catch (e) {
// noop
}
}
if (typeof npmPackage !== 'object') {
var pathString = candidatePackagePaths.join(',\n')
throw new Error('Unable to find package.json in any of:\n[' + pathString + ']')
}
//
// Import aliases
//
var aliases = npmPackage._moduleAliases || {}
for (var alias in aliases) {
if (aliases[alias][0] !== '/') {
aliases[alias] = nodePath.join(base, aliases[alias])
}
}
addAliases(aliases)
//
// Register custom module directories (like node_modules)
//
if (npmPackage._moduleDirectories instanceof Array) {
npmPackage._moduleDirectories.forEach(function (dir) {
if (dir === 'node_modules') return
var modulePath = nodePath.join(base, dir)
addPath(modulePath)
})
}
}
function getMainModule () {
return require.main._simulateRepl ? undefined : require.main
}
module.exports = init
module.exports.addPath = addPath
module.exports.addAlias = addAlias
module.exports.addAliases = addAliases
module.exports.isPathMatchesAlias = isPathMatchesAlias
module.exports.reset = reset
module.exports.setMain = function(main){
require.main = main
}

View File

@ -0,0 +1 @@
An alias for react-dom

View File

@ -0,0 +1 @@
module.exports = window.ReactDOM

View File

@ -0,0 +1,11 @@
{
"name": "react-dom",
"version": "1.0.0",
"description": "An alias for react-dom",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

View File

@ -0,0 +1 @@
An alias for the react module.

View File

@ -0,0 +1 @@
module.exports = window.React

View File

@ -0,0 +1,11 @@
{
"name": "react",
"version": "1.0.0",
"description": "An alias for the react module.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

View File

@ -7,8 +7,16 @@ process.on("uncaughtException", console.error)
const ipcRenderer = require('./discord_native/ipc');
const electron = require("electron")
const moduleAlias = require("./BetterDiscord/loaders/module-alias")
const path = require("path")
electron.remote.getCurrentWindow().setBackgroundColor("#2f3136")
moduleAlias.setMain(module)
moduleAlias.addAlias("@lightcord/api", path.join(__dirname, "../../../../LightcordApi"))
moduleAlias.addAlias("lightcordapi", path.join(__dirname, "../../../../LightcordApi"))
moduleAlias.addPath(path.join(__dirname, "BetterDiscord", "modules"))
const TRACK_ANALYTICS_EVENT = 'TRACK_ANALYTICS_EVENT';
const TRACK_ANALYTICS_EVENT_COMMIT = 'TRACK_ANALYTICS_EVENT_COMMIT';

73
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "lightcord",
"version": "0.1.1",
"version": "0.1.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -721,12 +721,6 @@
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
},
"jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
@ -789,15 +783,6 @@
"dev": true,
"optional": true
},
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dev": true,
"requires": {
"js-tokens": "^3.0.0 || ^4.0.0"
}
},
"lowercase-keys": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
@ -884,12 +869,6 @@
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"dev": true
},
"object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
@ -962,17 +941,6 @@
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"dev": true
},
"prop-types": {
"version": "15.7.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
"dev": true,
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.8.1"
}
},
"proto-list": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
@ -1005,35 +973,6 @@
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
},
"react": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz",
"integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==",
"dev": true,
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2"
}
},
"react-dom": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz",
"integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==",
"dev": true,
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"scheduler": "^0.19.1"
}
},
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true
},
"readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
@ -1127,16 +1066,6 @@
"truncate-utf8-bytes": "^1.0.0"
}
},
"scheduler": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
"integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
"dev": true,
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
}
},
"semver": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",

View File

@ -40,8 +40,6 @@
"@types/yauzl": "^2.9.1",
"cross-spawn": "^7.0.3",
"electron": "^8.4.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"terser": "^4.7.0",
"yazl": "^2.5.1"
}