kythia-core 0.9.5-beta → 0.10.1-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,53 +1,94 @@
1
+ /**
2
+ * 🌐 Optimized Discord Client Factory
3
+ *
4
+ * @file src/client/kythiaClient.js
5
+ * @copyright © 2025 kenndeclouv
6
+ * @assistant chaa & graa
7
+ * @version 0.10.0-beta
8
+ *
9
+ * @description
10
+ * Factory function that initializes the Discord.js Client with high-performance
11
+ * memory management strategies tailored for VPS environments.
12
+ *
13
+ * ✨ Optimization Highlights:
14
+ * - Rolling Buffer Cache: Limits GuildMember cache to active users (Rolling Window) to save RAM.
15
+ * - Aggressive Sweepers: Periodically clears stale cache data every hour.
16
+ * - Voice Priority: Always caches members connected to Voice Channels.
17
+ * - Zero-Presence: Disables PresenceManager completely for maximum efficiency.
18
+ */
19
+
1
20
  const { Client, GatewayIntentBits, Partials, Options } = require('discord.js');
2
21
 
3
22
  module.exports = function kythiaClient() {
4
- const client = new Client({
5
- intents: [
6
- GatewayIntentBits.Guilds,
7
- GatewayIntentBits.GuildMessages,
8
- GatewayIntentBits.MessageContent,
9
- GatewayIntentBits.GuildMembers,
10
- GatewayIntentBits.GuildModeration,
11
- GatewayIntentBits.GuildInvites,
12
- GatewayIntentBits.GuildVoiceStates,
13
- GatewayIntentBits.AutoModerationExecution,
14
- GatewayIntentBits.DirectMessages,
15
- GatewayIntentBits.DirectMessageReactions,
16
- GatewayIntentBits.DirectMessageTyping,
17
- GatewayIntentBits.GuildExpressions,
18
- ],
19
-
20
- partials: [Partials.Message, Partials.Channel, Partials.Reaction, Partials.User, Partials.GuildMember],
21
-
22
- makeCache: Options.cacheWithLimits({
23
- MessageManager: 25,
24
- PresenceManager: 0,
25
- GuildMemberManager: {
26
- max: 100,
27
- keepOverLimit: (member) =>
28
- (client.user && member.id === client.user.id) ||
29
- (member.guild && member.id === member.guild.ownerId) ||
30
- (member.voice && member.voice.channelId !== null),
31
- },
32
- ThreadManager: 10,
33
- }),
34
-
35
- sweepers: {
36
- ...Options.DefaultSweeperSettings,
37
- messages: {
38
- interval: 3600,
39
- lifetime: 1800,
40
- },
41
-
42
- threads: {
43
- interval: 3600,
44
- lifetime: 1800,
45
- },
46
- users: {
47
- interval: 3600,
48
- filter: () => (user) => user && !user.bot,
49
- },
50
- },
51
- });
52
- return client;
23
+ const client = new Client({
24
+ intents: [
25
+ GatewayIntentBits.Guilds,
26
+ GatewayIntentBits.GuildMessages,
27
+ GatewayIntentBits.MessageContent,
28
+ GatewayIntentBits.GuildMembers,
29
+ GatewayIntentBits.GuildModeration,
30
+ GatewayIntentBits.GuildInvites,
31
+ GatewayIntentBits.GuildVoiceStates,
32
+ GatewayIntentBits.AutoModerationExecution,
33
+ GatewayIntentBits.DirectMessages,
34
+ GatewayIntentBits.DirectMessageReactions,
35
+ GatewayIntentBits.DirectMessageTyping,
36
+ GatewayIntentBits.GuildExpressions,
37
+ ],
38
+
39
+ partials: [
40
+ Partials.Message,
41
+ Partials.Channel,
42
+ Partials.Reaction,
43
+ Partials.User,
44
+ Partials.GuildMember,
45
+ ],
46
+
47
+ makeCache: Options.cacheWithLimits({
48
+ PresenceManager: 0,
49
+
50
+ ThreadManager: {
51
+ maxSize: 25,
52
+ keepOverLimit: (thread) => thread.isActive(),
53
+ },
54
+
55
+ GuildMemberManager: {
56
+ maxSize: 200,
57
+ keepOverLimit: (member) =>
58
+ (client.user && member.id === client.user.id) ||
59
+ (member.guild && member.id === member.guild.ownerId) ||
60
+ (member.voice && member.voice.channelId !== null) ||
61
+ member.roles.cache.size > 5,
62
+ },
63
+
64
+ UserManager: {
65
+ maxSize: 1000,
66
+ keepOverLimit: (user) => user.id === client.user.id,
67
+ },
68
+ }),
69
+
70
+ sweepers: {
71
+ ...Options.DefaultSweeperSettings,
72
+ messages: {
73
+ interval: 3600,
74
+ lifetime: 1800,
75
+ },
76
+ threads: {
77
+ interval: 3600,
78
+ lifetime: 1800,
79
+ },
80
+
81
+ users: {
82
+ interval: 3600,
83
+
84
+ filter: () => (user) => {
85
+ if (user.bot) return false;
86
+ if (user.id === client.user.id) return false;
87
+ return true;
88
+ },
89
+ },
90
+ },
91
+ });
92
+
93
+ return client;
53
94
  };
@@ -0,0 +1,116 @@
1
+ /**
2
+ * 🚜 Addon-based Database Migration Manager
3
+ *
4
+ * @file src/database/KythiaMigrator.js
5
+ * @copyright © 2025 kenndeclouv
6
+ * @assistant chaa & graa
7
+ * @version 0.10.0-beta
8
+ *
9
+ * @description
10
+ * Scans 'addons' folder for migration files and executes them using Umzug.
11
+ * It integrates with KythiaStorage (Laravel-style) to track migration batches.
12
+ *
13
+ * ✨ Core Features:
14
+ * - Auto-Discovery: Recursively finds migrations in `addons/{AddonName}/database/migrations`.
15
+ * - Sorting: Ensures migrations run in correct order (YYYYMMDD format).
16
+ * - Custom Logger: Clean, non-intrusive logging for the bot console.
17
+ * - Batch Integration: Uses KythiaStorage to support rollback by batch.
18
+ */
19
+
20
+ const KythiaStorage = require('./KythiaStorage');
21
+ const { DataTypes } = require('sequelize');
22
+ const { Umzug } = require('umzug');
23
+ const path = require('node:path');
24
+ const fs = require('node:fs');
25
+
26
+ function getMigrationFiles(rootDir) {
27
+ const addonsDir = path.join(rootDir, 'addons');
28
+ if (!fs.existsSync(addonsDir)) return [];
29
+
30
+ const migrationFiles = [];
31
+
32
+ const addonFolders = fs
33
+ .readdirSync(addonsDir)
34
+ .filter((f) => fs.statSync(path.join(addonsDir, f)).isDirectory());
35
+
36
+ for (const addon of addonFolders) {
37
+ const migrationDir = path.join(addonsDir, addon, 'database', 'migrations');
38
+ if (fs.existsSync(migrationDir)) {
39
+ const files = fs
40
+ .readdirSync(migrationDir)
41
+ .filter((f) => f.endsWith('.js'))
42
+ .map((f) => ({
43
+ name: f,
44
+ path: path.join(migrationDir, f),
45
+ folder: migrationDir,
46
+ }));
47
+ migrationFiles.push(...files);
48
+ }
49
+ }
50
+
51
+ return migrationFiles.sort((a, b) => a.name.localeCompare(b.name));
52
+ }
53
+
54
+ async function KythiaMigrator({ sequelize, container, logger }) {
55
+ const rootDir = container.appRoot;
56
+ const migrations = getMigrationFiles(rootDir);
57
+
58
+ if (migrations.length === 0) {
59
+ logger.info('📭 No migrations found in addons.');
60
+ return;
61
+ }
62
+
63
+ const umzugLogger = {
64
+ info: (event) => {
65
+ if (typeof event === 'object') {
66
+ if (event.event === 'migrating') {
67
+ } else if (event.event === 'migrated') {
68
+ logger.info(`✅ Migrated: ${event.name}`);
69
+ }
70
+ } else {
71
+ logger.info(event);
72
+ }
73
+ },
74
+ warn: (msg) => logger.warn(msg),
75
+ error: (msg) => logger.error(msg),
76
+ };
77
+
78
+ const umzug = new Umzug({
79
+ migrations: migrations.map((m) => ({
80
+ name: m.name,
81
+ path: m.path,
82
+ up: async ({ context }) => {
83
+ const migration = require(m.path);
84
+ return migration.up(context, DataTypes);
85
+ },
86
+ down: async ({ context }) => {
87
+ const migration = require(m.path);
88
+ return migration.down(context, DataTypes);
89
+ },
90
+ })),
91
+ context: sequelize.getQueryInterface(),
92
+
93
+ storage: new KythiaStorage({ sequelize }),
94
+ logger: umzugLogger,
95
+ });
96
+
97
+ logger.info(`🚜 Checking migrations for ${migrations.length} files...`);
98
+
99
+ try {
100
+ const pending = await umzug.pending();
101
+
102
+ if (pending.length > 0) {
103
+ const executed = await umzug.up();
104
+ if (executed.length > 0) {
105
+ logger.info(`✨ Successfully applied ${executed.length} migrations.`);
106
+ }
107
+ } else {
108
+ logger.info('✨ Database is already up to date.');
109
+ }
110
+ } catch (error) {
111
+ logger.error('🔥 Migration Failed:', error);
112
+ process.exit(1);
113
+ }
114
+ }
115
+
116
+ module.exports = KythiaMigrator;