Skip to content

Commit 75b30ab

Browse files
TheArmaganErdemGKSL
andcommitted
[BETA] Work on Plugin System!
Co-Authored-By: Erdem <[email protected]>
1 parent 999422a commit 75b30ab

File tree

4 files changed

+157
-16
lines changed

4 files changed

+157
-16
lines changed

index.js

Lines changed: 113 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,25 @@ globalThis.Underline = config.globalObjects;
66
const Discord = require("discord.js");
77
const chillout = require("chillout");
88
const path = require("path");
9+
const fs = require("fs");
910
const readdirRecursive = require("recursive-readdir");
1011
const { makeSureFolderExists } = require("stuffs");
1112
const client = new Discord.Client(config.clientOptions);
1213
const interactions = new Discord.Collection();
1314
const events = new Discord.Collection();
1415
const locales = new Discord.Collection();
16+
const { quickMap, quickForEach } = require("async-and-quick");
1517
let interactionFiles;
1618
let eventFiles;
1719
let localeFiles;
20+
let pluginFiles;
21+
let onFunctions = {
22+
onInteractionBeforeChecks: [config.onInteractionBeforeChecks],
23+
onInteraction: [config.onInteraction],
24+
onAfterInteraction: [config.onAfterInteraction],
25+
onEvent: [config.onEvent],
26+
onAfterEvent: [config.onAfterEvent]
27+
};
1828
globalThis.Underline = {
1929
...config.globalObjects,
2030
config,
@@ -24,6 +34,7 @@ globalThis.Underline = {
2434
events,
2535
locales,
2636
utils,
37+
plugins: {},
2738
_references: new Discord.Collection(),
2839
Interaction: require('./types/Interaction'),
2940
Event: require('./types/Event'),
@@ -35,6 +46,36 @@ globalThis.Underline = {
3546
Locale: require("./types/Locale"),
3647
}
3748

49+
const extractZip = require("extract-zip");
50+
51+
async function getPluginFilePaths() {
52+
let pluginsPath = path.resolve("./plugins");
53+
await makeSureFolderExists(pluginsPath);
54+
let folderOrZips = await fs.promises.readdir(pluginsPath, { withFileTypes: true });
55+
let result = [];
56+
for (let i = 0; i < folderOrZips.length; i++) {
57+
const folderOrZip = folderOrZips[i];
58+
if (folderOrZip.isDirectory()) {
59+
if (folderOrZip.name.startsWith("-")) continue;
60+
result.push(path.resolve(pluginsPath, folderOrZip.name, "index.js"));
61+
} else if (folderOrZip.name.endsWith(".up.js")) {
62+
if (folderOrZip.name.startsWith("-")) continue;
63+
result.push(path.resolve(pluginsPath, folderOrZip.name));
64+
} else if (folderOrZip.name.endsWith(".up.zip")) {
65+
let folderPath = path.resolve(pluginsPath, folderOrZip.name.replace(".up.zip", ".up"));
66+
let zipPath = path.resolve(pluginsPath, folderOrZip.name);
67+
68+
await fs.promises.rmdir(folderPath, {recursive: true}).catch(() => {});
69+
await makeSureFolderExists(folderPath);
70+
await extractZip(zipPath, { dir: folderPath });
71+
fs.promises.unlink(zipPath).catch(() => null);
72+
if (folderOrZip.name.startsWith("-")) continue;
73+
result.push(path.resolve(folderPath, "index.js"));
74+
}
75+
}
76+
return result;
77+
}
78+
3879
async function getEventFilePaths() {
3980
let eventsPath = path.resolve("./events");
4081
await makeSureFolderExists(eventsPath);
@@ -72,6 +113,15 @@ async function getLocaleFilePaths() {
72113
let eventListeners = [];
73114

74115
async function load() {
116+
117+
onFunctions = {
118+
onInteractionBeforeChecks: [config.onInteractionBeforeChecks],
119+
onInteraction: [config.onInteraction],
120+
onAfterInteraction: [config.onAfterInteraction],
121+
onEvent: [config.onEvent],
122+
onAfterEvent: [config.onAfterEvent]
123+
};
124+
75125
let loadStart = Date.now();
76126
console.debug(`[HATA AYIKLAMA] Yüklemeye başlandı!`);
77127

@@ -93,6 +143,56 @@ async function load() {
93143
console.info(`[BİLGİ] "${locale.locale}" dili yüklendi. (${Date.now() - start}ms sürdü.)`);
94144
})
95145

146+
pluginFiles = await getPluginFilePaths();
147+
await chillout.forEach(pluginFiles, (pluginFile) => {
148+
let start = Date.now();
149+
let rltPath = path.relative(__dirname, pluginFile);
150+
console.info(`[BİLGİ] "${rltPath}" konumundaki plugin yükleniyor..`)
151+
/** @type {import("./types/Plugin")} */
152+
let plugin = require(pluginFile);
153+
let isReady = false;
154+
155+
if (plugin._type != "plugin")
156+
return console.warn(`[UYARI] "${rltPath}" plugin dosyası boş. Atlanıyor..`);
157+
158+
if (Underline.plugins[plugin.namespace])
159+
return console.warn(`[UYARI] ${plugin.name} plugini zaten yüklenmiş. Atlanıyor..`);
160+
161+
const pluginApi = {};
162+
163+
pluginApi.ready = async () => {
164+
if (isReady) throw new Error("Plugin already ready!")
165+
isReady = true;
166+
}
167+
168+
Underline.plugins[plugin.namespace] = {};
169+
170+
pluginApi.define = (name, value) => {
171+
Underline.plugins[plugin.namespace][name] = value;
172+
}
173+
174+
pluginApi.emit = (name, ...args) => {
175+
client.emit(`${plugin.namespace}:${name}`, ...args);
176+
}
177+
178+
pluginApi.onInteractionBeforeChecks = onFunctions.onInteractionBeforeChecks.push;
179+
pluginApi.onInteraction = onFunctions.onInteraction.push;
180+
pluginApi.onAfterInteraction = onFunctions.onAfterInteraction.push;
181+
182+
pluginApi.onEvent = onFunctions.onEvent.push;
183+
pluginApi.onAfterEvent = onFunctions.onAfterEvent.push;
184+
185+
pluginApi.client = client;
186+
187+
plugin.onLoad(pluginApi);
188+
await chillout.waitUntil(() => {
189+
if (isReady) return chillout.StopIteration;
190+
})
191+
192+
console.info(`[BİLGİ] "${plugin.name}" plugini yüklendi. (${Date.now() - start}ms sürdü.)`);
193+
})
194+
195+
96196

97197
interactionFiles = await getInteractionFilePaths();
98198
await chillout.forEach(interactionFiles, (interactionFile) => {
@@ -239,16 +339,16 @@ async function load() {
239339

240340
let other = {};
241341

242-
let before = await Underline.config.onEvent(eventName, args, other);
243-
if (!before) return;
342+
let before = (await quickMap(onFunctions.onEvent, async (func) => { return await func(eventName, args, other); })).findIndex(v => v === false);
343+
if (before != -1) return;
244344
args.push(other);
245345
chillout.forEach(events,
246346
/** @param {import("./types/Event")} event */
247347
(event) => {
248348
if (!event.disabled) {
249349
try {
250350
event.onEvent(...args);
251-
Underline.config.onAfterEvent(eventName, args, other);
351+
quickForEach(onFunctions.onAfterEvent, async (func) => { func(...args); });
252352
} catch (err) {
253353
if (Underline.config.debugLevel >= 1) {
254354
console.error(`[HATA] "${event.id}" idli ve "${eventName}" isimli olayda bir hata oluştu!`);
@@ -290,6 +390,9 @@ async function unload() {
290390
console.debug(`[HATA AYIKLAMA] Önbellek temizle işlemi başladı.`);
291391
let unloadStart = Date.now();
292392

393+
console.info(`[BILGI] Plugin listesi temizleniyor..`);
394+
Underline.plugins = {};
395+
293396
console.info(`[BILGI] İnteraksiyon listesi temizleniyor..`);
294397
Underline.interactions.clear();
295398

@@ -305,7 +408,7 @@ async function unload() {
305408
console.info(`[BILGI] Dil listesi temizleniyor..`);
306409
Underline.locales.clear();
307410

308-
let pathsToUnload = [...interactionFiles, ...eventFiles, ...localeFiles];
411+
let pathsToUnload = [...interactionFiles, ...eventFiles, ...localeFiles, ...pluginFiles];
309412

310413
await chillout.forEach(pathsToUnload, async (pathToUnload) => {
311414
console.info(`[BILGI] Modül "${path.relative(__dirname, pathToUnload)}" önbellekten kaldırılıyor!`);
@@ -421,8 +524,8 @@ client.on("interactionCreate", async (interaction) => {
421524
}
422525

423526
{
424-
let shouldRun1 = await config.onInteractionBeforeChecks(uInter, interaction, other);
425-
if (!shouldRun1) return;
527+
let shouldRun1 = (await quickMap(onFunctions.onInteractionBeforeChecks, async (func) => { return await func(uInter, interaction, other); })).findIndex(v => v === false);
528+
if (shouldRun1 != -1) return;
426529
}
427530

428531
if (uInter.disabled) {
@@ -483,6 +586,7 @@ client.on("interactionCreate", async (interaction) => {
483586
await interaction.update().catch(Underline.config.debugLevel >= 2 ? console.error : () => { });
484587
// await utils.nullDefer(interaction);
485588
interaction.deferReply = newDefer;
589+
let key = converter[k];
486590
interaction.reply = interaction.editReply = interaction.followUp = () => {
487591
if (Underline.config.debugLevel >= 1) console.warn(`[UYARI] "${uInter.name[0]}" adlı interaksiyon için "reply" umursanmadı, interaksiyona zaten otomatik olarak boş cevap verilmiş.`);
488592
};
@@ -508,7 +612,6 @@ client.on("interactionCreate", async (interaction) => {
508612
let now = Date.now();
509613

510614
for (let k in converter) {
511-
let key = converter[k];
512615
let keyCooldown = uInter.coolDowns.get(key);
513616
if (now < keyCooldown) {
514617
config.userErrors.coolDown(interaction, uInter, keyCooldown - now, k, other);
@@ -540,15 +643,14 @@ client.on("interactionCreate", async (interaction) => {
540643
(async () => {
541644

542645
{
543-
let shouldRun2 = await config.onInteraction(uInter, interaction, other);
544-
if (!shouldRun2) return;
646+
let shouldRun2 = (await quickMap(onFunctions.onInteraction, async (func) => { try {return await func(uInter, interaction, other);} catch (e) { onfig.debugLevel > 2 ? console.error(e) : null; } })).findIndex(v => v === false);
647+
if (shouldRun2 != -1) return;
545648
}
546649

547650
try {
548651

549652
await uInter.onInteraction(interaction, other);
550-
551-
await config.onAfterInteraction(uInter, interaction, other);
653+
quickForEach(onFunctions.onAfterInteraction, async (func) => { try { func(uInter, interaction, other)?.catch(config.debugLevel > 2 ? console.error : () => null); } catch (err) { (config.debugLevel > 2 ? console.error : () => null)(err) } })
552654

553655
} catch (err) {
554656
if (Underline.config.debugLevel >= 1) {

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
{
22
"dependencies": {
33
"@discordjs/rest": "^0.1.0-canary.0",
4+
"async-and-quick": "^1.0.1",
45
"chalk": "^4.1.1",
56
"chillout": "^5.0.0",
67
"discord-api-types": "^0.23.1",
78
"discord.js": "^13.6.0",
89
"enquirer": "^2.3.6",
10+
"extract-zip": "^2.0.1",
911
"lodash": "^4.17.21",
1012
"plsargs": "^0.1.6",
1113
"recursive-readdir": "^2.2.2",
@@ -18,7 +20,7 @@
1820
"@types/recursive-readdir": "^2.2.0"
1921
},
2022
"name": "armagan-basit-altyapi",
21-
"version": "1.9.9",
23+
"version": "2.0.0",
2224
"description": "Kullanımı basit ancak bir yandanda içinde birçek özellik barındıran discord bot altyapısı.",
2325
"main": "index.js",
2426
"repository": {

types/Config.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,13 @@ class Config {
4242
/** @type {(client:import("discord.js").Client)=>void} */
4343
onReady = () => { };
4444

45-
/** @type {(interaction:Command, interaction: Discord.CommandInteraction, other: {[key:string|number]: any})=>boolean} */
45+
/** @type {(unInter: Interaction, interaction: Discord.CommandInteraction, other: {[key:string|number]: any})=>boolean} */
4646
onInteractionBeforeChecks = async () => { return true; };
4747

48-
/** @type {(interaction:Command, interaction: Discord.CommandInteraction, other: {setCoolDown(duration:number): void, [key:string|number]: any})=>boolean} */
48+
/** @type {(unInter: Interaction, interaction: Discord.CommandInteraction, other: {setCoolDown(duration:number): void, [key:string|number]: any})=>boolean} */
4949
onInteraction = async () => { return true; };
5050

51-
/** @type {(interaction:Command, interaction: Discord.CommandInteraction, other: {setCoolDown(duration:number): void, [key:string|number]: any})=>boolean} */
51+
/** @type {(unInter: Interaction, interaction: Discord.CommandInteraction, other: {setCoolDown(duration:number): void, [key:string|number]: any})=>boolean} */
5252
onAfterInteraction = async () => { return true; };
5353

5454
/** @type {(eventName: "applicationCommandCreate" | "applicationCommandDelete" | "applicationCommandUpdate" | "channelCreate" | "channelDelete" | "channelPinsUpdate" | "channelUpdate" | "debug" | "emojiCreate" | "emojiDelete" | "emojiUpdate" | "error" | "guildBanAdd" | "guildBanRemove" | "guildCreate" | "guildDelete" | "guildIntegrationsUpdate" | "guildMemberAdd" | "guildMemberAvailable" | "guildMemberRemove" | "guildMembersChunk" | "guildMemberUpdate" | "guildUnavailable" | "guildUpdate" | "interaction" | "interactionCreate" | "invalidated" | "invalidRequestWarning" | "inviteCreate" | "inviteDelete" | "message" | "messageCreate" | "messageDelete" | "messageDeleteBulk" | "messageReactionAdd" | "messageReactionRemove" | "messageReactionRemoveAll" | "messageReactionRemoveEmoji" | "messageUpdate" | "presenceUpdate" | "rateLimit" | "ready" | "roleCreate" | "roleDelete" | "roleUpdate" | "shardDisconnect" | "shardError" | "shardReady" | "shardReconnecting" | "shardResume" | "stageInstanceCreate" | "stageInstanceDelete" | "stageInstanceUpdate" | "stickerCreate" | "stickerDelete" | "stickerUpdate" | "threadCreate" | "threadDelete" | "threadListSync" | "threadMembersUpdate" | "threadMemberUpdate" | "threadUpdate" | "typingStart" | "userUpdate" | "voiceStateUpdate" | "warn" | "webhookUpdate", args: [], other: {[key:string|number]: any})=>boolean} */
@@ -66,7 +66,7 @@ class Config {
6666
constructor(arg = {}) {
6767
this.debugLevel = arg.debugLevel ?? 0;
6868

69-
if (!(typeof arg.clientToken == "string" && arg.clientToken.length != 0)) {
69+
if (typeof arg.clientToken != "string" || arg.clientToken.length == 0) {
7070
console.error("[HATA] Ayarlar dosayasında geçersiz bot tokeni girişi yapılmış.");
7171
process.exit(-1);
7272
};

types/Plugin.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
class Plugin {
2+
_type = "plugin";
3+
constructor(obj = {}) {
4+
5+
if (!obj.name) {
6+
console.error("[HATA] Plugin dosyasında isim alanı boş bırakılamaz!");
7+
process.exit(-1);
8+
}
9+
this.name = obj.name;
10+
this.version = (obj.version || "0.0.1").replace("v", "");
11+
if (!obj.namespace) {
12+
console.error("[HATA] Plugin dosyasında namespace alanı boş bırakılamaz!");
13+
process.exit(-1);
14+
}
15+
this.namespace = obj.namespace;
16+
this.requires = obj.requires;
17+
if (obj.requires?.modules) {
18+
let names = Object.keys(obj.requires.modules);
19+
for (let i = 0; i < names.length; i++) {
20+
const moduleName = names[i];
21+
try {
22+
require(moduleName)
23+
} catch (e) {
24+
console.error(`[HATA] "${obj.name}" adlı plugin, "${moduleName}" (${obj.requires.modules[moduleName]}) adlı modülü istiyor!`);
25+
process.exit(-1);
26+
}
27+
}
28+
}
29+
30+
this.implements = obj.implements;
31+
this.onLoad = obj.onLoad;
32+
}
33+
}
34+
35+
36+
37+
module.exports = Plugin;

0 commit comments

Comments
 (0)