basic proxy setup

This commit is contained in:
Daniel Bulant 2021-07-21 19:52:11 +02:00
parent 16664d7154
commit bdb72158fd
12 changed files with 286 additions and 64 deletions

40
index.js Normal file
View file

@ -0,0 +1,40 @@
const discord = require("discord.js");
const proxyHandlers = require("./proxies/handlers");
const Module = require("module");
const StructuresDef = require("./proxies/structures.js");
const original = discord;
const Structures = StructuresDef;
const proxy = new Proxy(discord, {
get(target, property, receiver) {
if(property in proxyHandlers) return proxyHandlers[property];
if(property === "original") return original;
if(property === "Structures") return Structures;
if(property === "hook") return hook;
return Reflect.get(target, property, receiver);
}
});
/**
* Hooks discord.js-structures into discord.js require, which should fix errors in 3rd party libraries trying to use Structures.
* Overwrites global require function
* @see [StackOverflow source](https://stackoverflow.com/a/24602188/8404532)
*/
function hook() {
var origRequire = Module.prototype.require;
var _require = function(context, path) {
return origRequire.call(context, path);
};
Module.prototype.require = function(path) {
if(path === "discord.js") {
return proxy;
}
return _require(this, path);
};
}
module.exports = proxy;

View file

@ -1,31 +0,0 @@
import discord from "discord.js";
import { defaultProxy } from "./proxies/index.mjs";
import Module from "module";
/** The proxied version, in case you like named params */
export const proxy = new Proxy(discord, defaultProxy);
/** The original discord.js, unmodified. Useful when using discord.js-structures hooked */
export const original = discord;
/**
* Hooks discord.js-structures into discord.js require, which should fix errors in 3rd party libraries trying to use Structures.
* Overwrites global require function
* @see [StackOverflow source](https://stackoverflow.com/a/24602188/8404532)
*/
export function hook() {
var origRequire = Module.prototype.require;
var _require = function(context, path) {
return origRequire.call(context, path);
};
Module.prototype.require = function(path) {
if(path === "discord.js") {
return proxy;
}
return _require(this, path);
};
}
export default proxy;

View file

@ -4,12 +4,15 @@
"description": "Adds discord.js structures back into discord.js",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "node test/index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"peerDependencies": {
"discord.js": ">=13.0.0-dev.0"
},
"devDependencies": {
"discord.js": ">=13.0.0-dev.0"
}
}

2
proxies/cache.js Normal file
View file

@ -0,0 +1,2 @@
module.exports = new WeakMap();

9
proxies/client.js Normal file
View file

@ -0,0 +1,9 @@
/**
* @type {ProxyHandler<import("discord.js").Client>}
*/
module.exports = {
get(target, property, receiver) {
return Reflect.get(target, property, receiver);
}
}

81
proxies/eventProxy.js Normal file
View file

@ -0,0 +1,81 @@
module.exports = class EventProxy {
proxies = new Map();
once = new Map();
on = new Map();
targetEvents = new WeakMap();
handlers = new Map();
setProxy(event, handler) {
this.proxies.set(event, handler);
}
handleEvent(event, args) {
if(proxies.has(event)) {
args = proxies.get(event)(...args);
}
for(const handler of (proxy.on.get(event) || [])) {
handler(...args);
}
for(const handler of (proxy.once.get(event) || [])) {
handler(...args);
}
proxy.once.delete(event);
}
get proxy() {
const proxy = this;
/** @type {ProxyHandler} */
const handler = {
get(target, property) {
const events = proxy.targetEvents.get(target) || [];
switch(property) {
case "on":
return (event, handler) => {
const handlers = proxy.on.get(event) || [];
handlers.push(handler);
proxy.on.set(event, handlers);
if(!events.includes(event)) {
const handler = (...args) => proxy.handleEvent(event, args);
proxy.handlers.set(event, handler);
target.on(event, handler);
}
}
case "once":
return (event, handler) => {
const handlers = proxy.once.get(event) || [];
handlers.push(handler);
proxy.once.set(event, handlers);
if(!events.includes(event)) {
const handler = (...args) => proxy.handleEvent(event, args);
proxy.handlers.set(event, handler);
target.on(event, handler);
}
}
case "off":
return (event, handler) => {
const handlers = proxy.on.get(event) || [];
if(handlers.includes(handler)) handlers.splice(handlers.indexOf(handler), 1);
if(handlers.length) {
proxy.on.set(event, handlers);
} else {
proxy.on.delete(event);
}
const handlers2 = proxy.once.get(event) || [];
if(handlers2.includes(handler)) handlers.splice(handlers.indexOf(handler), 1);
if(handlers2.length) {
proxy.once.set(event, handlers2);
} else {
proxy.once.delete(event);
}
if(!handlers.length && !handlers2.length) {
target.off(event, proxy.handlers.get(event));
}
}
}
return Reflect.get(...arguments);
}
}
return handler;
}
}

13
proxies/handlers.js Normal file
View file

@ -0,0 +1,13 @@
const Structures = require("./structures.js");
const discord = require("discord.js");
const EventProxy = require("./eventProxy.js");
module.exports = {
Client: class Client {
constructor(...params) {
var proxy = new EventProxy();
return new Proxy(new Proxy(new discord.Client(...params), require("./client.js")), proxy.proxy);
}
},
Structures: Structures
};

View file

@ -1,12 +0,0 @@
const proxyHandlers = {};
/**
* @type {ProxyHandler<import("discord.js")>}
*/
export const defaultProxy = {
get(target, property, receiver) {
if(property in proxyHandlers) return proxyHandlers[property];
return Reflect.get(target, property, receiver);
}
}

112
proxies/structures.js Normal file
View file

@ -0,0 +1,112 @@
const discord = require("discord.js");
/**
* An extendable structure:
* * **`GuildEmoji`**
* * **`DMChannel`**
* * **`TextChannel`**
* * **`VoiceChannel`**
* * **`CategoryChannel`**
* * **`NewsChannel`**
* * **`StoreChannel`**
* * **`GuildMember`**
* * **`Guild`**
* * **`Message`**
* * **`MessageReaction`**
* * **`Presence`**
* * **`ClientPresence`**
* * **`VoiceState`**
* * **`Role`**
* * **`User`**
* @typedef {string} ExtendableStructure
*/
/**
* Allows for the extension of built-in Discord.js structures that are instantiated by {@link BaseManager Managers}.
*/
class Structures {
constructor() {
throw new Error(`The ${this.constructor.name} class may not be instantiated.`);
}
/**
* Retrieves a structure class.
* @param {string} structure Name of the structure to retrieve
* @returns {Function}
*/
static get(structure) {
if (typeof structure === 'string') return structures[structure];
throw new TypeError(`"structure" argument must be a string (received ${typeof structure})`);
}
/**
* Extends a structure.
* <warn> Make sure to extend all structures before instantiating your client.
* Extending after doing so may not work as expected. </warn>
* @param {ExtendableStructure} structure Name of the structure class to extend
* @param {Function} extender Function that takes the base class to extend as its only parameter and returns the
* extended class/prototype
* @returns {Function} Extended class/prototype returned from the extender
* @example
* const { Structures } = require('discord.js');
*
* Structures.extend('Guild', Guild => {
* class CoolGuild extends Guild {
* constructor(client, data) {
* super(client, data);
* this.cool = true;
* }
* }
*
* return CoolGuild;
* });
*/
static extend(structure, extender) {
if (!structures[structure]) throw new RangeError(`"${structure}" is not a valid extensible structure.`);
if (typeof extender !== 'function') {
const received = `(received ${typeof extender})`;
throw new TypeError(
`"extender" argument must be a function that returns the extended structure class/prototype ${received}.`,
);
}
const extended = extender(structures[structure]);
if (typeof extended !== 'function') {
const received = `(received ${typeof extended})`;
throw new TypeError(`The extender function must return the extended structure class/prototype ${received}.`);
}
if (!(extended.prototype instanceof structures[structure])) {
const prototype = Object.getPrototypeOf(extended);
const received = `${extended.name || 'unnamed'}${prototype.name ? ` extends ${prototype.name}` : ''}`;
throw new Error(
'The class/prototype returned from the extender function must extend the existing structure class/prototype' +
` (received function ${received}; expected extension of ${structures[structure].name}).`,
);
}
structures[structure] = extended;
return extended;
}
}
const structures = {
GuildEmoji: discord.GuildEmoji,
DMChannel: discord.DMChannel,
TextChannel: discord.TextChannel,
VoiceChannel: discord.VoiceChannel,
CategoryChannel: discord.CategoryChannel,
NewsChannel: discord.NewsChannel,
StoreChannel: discord.StoreChannel,
GuildMember: discord.GuildMember,
Guild: discord.Guild,
Message: discord.Message,
MessageReaction: discord.MessageReaction,
Presence: discord.Presence,
ClientPresence: discord.ClientPresence,
VoiceState: discord.VoiceState,
Role: discord.Role,
User: discord.User,
};
module.exports = Structures;

View file

25
test/index.js Normal file
View file

@ -0,0 +1,25 @@
const discord = require("../index");
const config = require("./config.json");
discord.Structures.extend('Message', Message => {
class BetterMessage extends Message {
constructor(client, data) {
super(client, data);
this.cool = true;
}
}
return BetterMessage;
});
const client = new discord.Client({
intents: ["GUILDS", "GUILD_MESSAGES"]
});
client.login(config.token);
client.on("ready", () => {
console.log("Ready as", client.user.tag);
})
client.on("message", (msg) => {
console.log(msg);
});

View file

@ -1,20 +0,0 @@
import discord from "../index.mjs";
import config from "./config.json";
discord.Structures.extend('Message', Message => {
class BetterMessage extends Message {
constructor(client, data) {
super(client, data);
this.cool = true;
}
}
return BetterMessage;
});
const client = new discord.Client();
client.login(config.token);
client.on("message", (msg) => {
console.log(msg);
});