spacecommands 3.5.7 → 3.5.8
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.
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
Please consult RULES.md before making any changes.
|
|
2
|
+
|
|
1
3
|
# SpaceCommands
|
|
2
4
|
|
|
3
5
|
SpaceCommands is a modern, feature-rich Discord.js command handler library. Built on Discord.js v14, it provides an easy-to-use framework for creating Discord bots with slash commands, permissions, cooldowns, and more.
|
|
@@ -20,7 +20,7 @@ module.exports = async (guild, command, instance, member, user, reply) => {
|
|
|
20
20
|
}
|
|
21
21
|
// Check if user has required entitlements
|
|
22
22
|
if (requiredEntitlements.length > 0) {
|
|
23
|
-
const { hasEntitlement } = await entitlementHandler.hasAnyEntitlement(user.id, requiredEntitlements);
|
|
23
|
+
const { hasEntitlement } = await entitlementHandler.hasAnyEntitlement(user.id, requiredEntitlements, guild?.id);
|
|
24
24
|
if (!hasEntitlement) {
|
|
25
25
|
reply(instance.messageHandler.get(guild, 'MISSING_ENTITLEMENT') ||
|
|
26
26
|
'You need a premium subscription to use this command.').then((message) => {
|
|
@@ -40,7 +40,13 @@ module.exports = async (guild, command, instance, member, user, reply) => {
|
|
|
40
40
|
// If premiumOnly is set, check for any active entitlement
|
|
41
41
|
if (premiumOnly) {
|
|
42
42
|
const allEntitlements = await entitlementHandler.getUserEntitlements(user.id);
|
|
43
|
-
|
|
43
|
+
let hasPremium = allEntitlements.length > 0;
|
|
44
|
+
// Check guild overrides if no user entitlement found
|
|
45
|
+
if (!hasPremium && guild) {
|
|
46
|
+
const overrides = await entitlementHandler.getGuildOverrides(guild.id);
|
|
47
|
+
hasPremium = overrides.length > 0;
|
|
48
|
+
}
|
|
49
|
+
if (!hasPremium) {
|
|
44
50
|
reply(instance.messageHandler.get(guild, 'PREMIUM_ONLY') ||
|
|
45
51
|
'This command is only available to premium subscribers.').then((message) => {
|
|
46
52
|
if (!message) {
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const discord_js_1 = require("discord.js");
|
|
7
|
+
const premium_overrides_1 = __importDefault(require("../models/premium-overrides"));
|
|
3
8
|
/**
|
|
4
9
|
* Handler for Discord premium features and entitlements
|
|
5
10
|
* Supports Discord's monetization system with SKUs and subscriptions
|
|
@@ -8,7 +13,9 @@ class EntitlementHandler {
|
|
|
8
13
|
_client;
|
|
9
14
|
_instance;
|
|
10
15
|
_skus = new Map();
|
|
16
|
+
_hierarchy = new Map(); // childSku -> parentSkus
|
|
11
17
|
_entitlementCache = new Map();
|
|
18
|
+
_overrideCache = new Map(); // guildId -> skuIds
|
|
12
19
|
_cacheTimeout = 5 * 60 * 1000; // 5 minutes
|
|
13
20
|
constructor(instance) {
|
|
14
21
|
this._instance = instance;
|
|
@@ -38,6 +45,26 @@ class EntitlementHandler {
|
|
|
38
45
|
}
|
|
39
46
|
});
|
|
40
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Register a SKU hierarchy
|
|
50
|
+
* @param childSku The SKU that is included in the parent SKUs
|
|
51
|
+
* @param parentSkus The SKUs that include the child SKU
|
|
52
|
+
*/
|
|
53
|
+
registerHierarchy(childSku, parentSkus) {
|
|
54
|
+
const existing = this._hierarchy.get(childSku) || [];
|
|
55
|
+
this._hierarchy.set(childSku, [...new Set([...existing, ...parentSkus])]);
|
|
56
|
+
if (this._instance.debug) {
|
|
57
|
+
console.log(`SpaceCommands > Registered hierarchy: ${parentSkus.join(', ')} includes ${childSku}`);
|
|
58
|
+
}
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Get all SKUs that satisfy the required SKU (including itself and its parents)
|
|
63
|
+
*/
|
|
64
|
+
getSatisfyingSkus(skuId) {
|
|
65
|
+
const parents = this._hierarchy.get(skuId) || [];
|
|
66
|
+
return [skuId, ...parents];
|
|
67
|
+
}
|
|
41
68
|
/**
|
|
42
69
|
* Register a SKU for premium features
|
|
43
70
|
*/
|
|
@@ -60,9 +87,21 @@ class EntitlementHandler {
|
|
|
60
87
|
/**
|
|
61
88
|
* Check if a user has a specific entitlement
|
|
62
89
|
*/
|
|
63
|
-
async hasEntitlement(userId, skuId) {
|
|
90
|
+
async hasEntitlement(userId, skuId, guildId) {
|
|
91
|
+
const validSkuIds = this.getSatisfyingSkus(skuId);
|
|
92
|
+
// First, check for guild overrides if guildId is provided
|
|
93
|
+
if (guildId) {
|
|
94
|
+
for (const validSku of validSkuIds) {
|
|
95
|
+
if (await this.hasGuildOverride(guildId, validSku)) {
|
|
96
|
+
return {
|
|
97
|
+
hasEntitlement: true,
|
|
98
|
+
isOverride: true,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
64
103
|
const entitlements = await this.getUserEntitlements(userId);
|
|
65
|
-
const entitlement = entitlements.find((e) => e.skuId
|
|
104
|
+
const entitlement = entitlements.find((e) => validSkuIds.includes(e.skuId));
|
|
66
105
|
return {
|
|
67
106
|
hasEntitlement: !!entitlement,
|
|
68
107
|
entitlement,
|
|
@@ -71,9 +110,26 @@ class EntitlementHandler {
|
|
|
71
110
|
/**
|
|
72
111
|
* Check if a user has any of the specified entitlements
|
|
73
112
|
*/
|
|
74
|
-
async hasAnyEntitlement(userId, skuIds) {
|
|
113
|
+
async hasAnyEntitlement(userId, skuIds, guildId) {
|
|
114
|
+
// Expand checks to include hierarchy
|
|
115
|
+
const allValidSkuIds = new Set();
|
|
116
|
+
for (const skuId of skuIds) {
|
|
117
|
+
this.getSatisfyingSkus(skuId).forEach(id => allValidSkuIds.add(id));
|
|
118
|
+
}
|
|
119
|
+
const validSkuList = Array.from(allValidSkuIds);
|
|
120
|
+
// First, check for guild overrides if guildId is provided
|
|
121
|
+
if (guildId) {
|
|
122
|
+
for (const skuId of validSkuList) {
|
|
123
|
+
if (await this.hasGuildOverride(guildId, skuId)) {
|
|
124
|
+
return {
|
|
125
|
+
hasEntitlement: true,
|
|
126
|
+
isOverride: true,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
75
131
|
const entitlements = await this.getUserEntitlements(userId);
|
|
76
|
-
const entitlement = entitlements.find((e) =>
|
|
132
|
+
const entitlement = entitlements.find((e) => validSkuList.includes(e.skuId));
|
|
77
133
|
return {
|
|
78
134
|
hasEntitlement: !!entitlement,
|
|
79
135
|
entitlement,
|
|
@@ -82,10 +138,54 @@ class EntitlementHandler {
|
|
|
82
138
|
/**
|
|
83
139
|
* Check if a user has all of the specified entitlements
|
|
84
140
|
*/
|
|
85
|
-
async hasAllEntitlements(userId, skuIds) {
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
141
|
+
async hasAllEntitlements(userId, skuIds, guildId) {
|
|
142
|
+
// Optimisation: Get user entitlements once
|
|
143
|
+
const userEntitlements = await this.getUserEntitlements(userId);
|
|
144
|
+
const userSkuIds = userEntitlements.map((e) => e.skuId);
|
|
145
|
+
for (const skuId of skuIds) {
|
|
146
|
+
const validSkuIds = this.getSatisfyingSkus(skuId);
|
|
147
|
+
// Check if user has ANY of the satisfying SKUs for this requirement
|
|
148
|
+
let hasRequirement = userSkuIds.some(id => validSkuIds.includes(id));
|
|
149
|
+
if (!hasRequirement && guildId) {
|
|
150
|
+
// Check overrides for ANY satisfying SKU
|
|
151
|
+
for (const validSku of validSkuIds) {
|
|
152
|
+
if (await this.hasGuildOverride(guildId, validSku)) {
|
|
153
|
+
hasRequirement = true;
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (!hasRequirement) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Check if a guild has an override for a specific SKU
|
|
166
|
+
*/
|
|
167
|
+
async hasGuildOverride(guildId, skuId, useCache = true) {
|
|
168
|
+
if (!this._instance.isDBConnected()) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
if (useCache && this._overrideCache.has(guildId)) {
|
|
172
|
+
const overrides = this._overrideCache.get(guildId);
|
|
173
|
+
return overrides.includes(skuId);
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
const result = await premium_overrides_1.default.findOne(guildId);
|
|
177
|
+
const skuIds = result?.skuId ? [result.skuId] : [];
|
|
178
|
+
this._overrideCache.set(guildId, skuIds);
|
|
179
|
+
// Clear cache after timeout
|
|
180
|
+
setTimeout(() => {
|
|
181
|
+
this._overrideCache.delete(guildId);
|
|
182
|
+
}, this._cacheTimeout);
|
|
183
|
+
return skuIds.includes(skuId);
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
console.error(`SpaceCommands > Error checking guild override for ${guildId}:`, error);
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
89
189
|
}
|
|
90
190
|
/**
|
|
91
191
|
* Get all active entitlements for a user
|
|
@@ -128,6 +228,32 @@ class EntitlementHandler {
|
|
|
128
228
|
getSKU(skuId) {
|
|
129
229
|
return this._skus.get(skuId);
|
|
130
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* Get all active overrides for a guild
|
|
233
|
+
*/
|
|
234
|
+
async getGuildOverrides(guildId, useCache = true) {
|
|
235
|
+
if (!this._instance.isDBConnected()) {
|
|
236
|
+
return [];
|
|
237
|
+
}
|
|
238
|
+
if (useCache && this._overrideCache.has(guildId)) {
|
|
239
|
+
return this._overrideCache.get(guildId);
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
const result = await premium_overrides_1.default.findOne(guildId);
|
|
243
|
+
const overrideSku = result?.skuId;
|
|
244
|
+
const overrides = overrideSku ? [overrideSku] : [];
|
|
245
|
+
this._overrideCache.set(guildId, overrides);
|
|
246
|
+
// Clear cache after timeout
|
|
247
|
+
setTimeout(() => {
|
|
248
|
+
this._overrideCache.delete(guildId);
|
|
249
|
+
}, this._cacheTimeout);
|
|
250
|
+
return overrides;
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
console.error(`SpaceCommands > Error fetching guild overrides for ${guildId}:`, error);
|
|
254
|
+
return [];
|
|
255
|
+
}
|
|
256
|
+
}
|
|
131
257
|
/**
|
|
132
258
|
* Clear the entitlement cache for a specific user
|
|
133
259
|
*/
|
|
@@ -139,6 +265,17 @@ class EntitlementHandler {
|
|
|
139
265
|
this._entitlementCache.clear();
|
|
140
266
|
}
|
|
141
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* Clear the override cache for a specific guild
|
|
270
|
+
*/
|
|
271
|
+
clearOverrideCache(guildId) {
|
|
272
|
+
if (guildId) {
|
|
273
|
+
this._overrideCache.delete(guildId);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
this._overrideCache.clear();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
142
279
|
/**
|
|
143
280
|
* Set the cache timeout duration (in milliseconds)
|
|
144
281
|
*/
|
|
@@ -151,7 +288,8 @@ class EntitlementHandler {
|
|
|
151
288
|
*/
|
|
152
289
|
async checkAccess(userOrMember, skuId) {
|
|
153
290
|
const userId = userOrMember.id;
|
|
154
|
-
|
|
291
|
+
const guildId = userOrMember instanceof discord_js_1.GuildMember ? userOrMember.guild.id : undefined;
|
|
292
|
+
return this.hasEntitlement(userId, skuId, guildId);
|
|
155
293
|
}
|
|
156
294
|
/**
|
|
157
295
|
* Consume a one-time purchase entitlement
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
// @ts-nocheck
|
|
4
|
+
const supabase_1 = require("../supabase");
|
|
5
|
+
const TABLE_NAME = 'spacecommands_premium_overrides';
|
|
6
|
+
exports.default = {
|
|
7
|
+
/**
|
|
8
|
+
* Find overrides for a specific guild
|
|
9
|
+
*/
|
|
10
|
+
async findOne(guildId) {
|
|
11
|
+
const client = (0, supabase_1.getSupabaseClient)();
|
|
12
|
+
if (!client)
|
|
13
|
+
return null;
|
|
14
|
+
const { data, error } = await client
|
|
15
|
+
.from(TABLE_NAME)
|
|
16
|
+
.select('sku_id')
|
|
17
|
+
.eq('guild_id', guildId)
|
|
18
|
+
.single();
|
|
19
|
+
if (error) {
|
|
20
|
+
if (error.code === 'PGRST116')
|
|
21
|
+
return null;
|
|
22
|
+
console.error('SpaceCommands > Error finding premium override:', error);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
guildId,
|
|
27
|
+
skuId: data.sku_id,
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
/**
|
|
31
|
+
* Set overrides for a guild
|
|
32
|
+
*/
|
|
33
|
+
async setOverride(guildId, skuId) {
|
|
34
|
+
const client = (0, supabase_1.getSupabaseClient)();
|
|
35
|
+
if (!client)
|
|
36
|
+
return null;
|
|
37
|
+
const { data, error } = await client
|
|
38
|
+
.from(TABLE_NAME)
|
|
39
|
+
.upsert({ guild_id: guildId, sku_id: skuId }, { onConflict: 'guild_id' })
|
|
40
|
+
.select()
|
|
41
|
+
.single();
|
|
42
|
+
if (error) {
|
|
43
|
+
console.error('SpaceCommands > Error setting premium override:', error);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
guildId: data.guild_id,
|
|
48
|
+
skuId: data.sku_id,
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
/**
|
|
52
|
+
* Remove overrides for a guild
|
|
53
|
+
*/
|
|
54
|
+
async removeOverrides(guildId) {
|
|
55
|
+
const client = (0, supabase_1.getSupabaseClient)();
|
|
56
|
+
if (!client)
|
|
57
|
+
return false;
|
|
58
|
+
const { error } = await client
|
|
59
|
+
.from(TABLE_NAME)
|
|
60
|
+
.delete()
|
|
61
|
+
.eq('guild_id', guildId);
|
|
62
|
+
if (error) {
|
|
63
|
+
console.error('SpaceCommands > Error removing premium override:', error);
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
@@ -56,7 +56,25 @@ exports.default = {
|
|
|
56
56
|
return null;
|
|
57
57
|
const guildId = filter.guildId;
|
|
58
58
|
const command = filter.command || update.command || update.$set?.command;
|
|
59
|
-
|
|
59
|
+
let requiredRoles = update.requiredRoles || update.$set?.requiredRoles;
|
|
60
|
+
// Handle $addToSet (used by requiredrole command)
|
|
61
|
+
if (update.$addToSet && update.$addToSet.requiredRoles) {
|
|
62
|
+
const roleToAdd = update.$addToSet.requiredRoles;
|
|
63
|
+
// Fetch existing roles first
|
|
64
|
+
const { data: existing } = await client
|
|
65
|
+
.from(TABLE_NAME)
|
|
66
|
+
.select('required_roles')
|
|
67
|
+
.eq('guild_id', guildId)
|
|
68
|
+
.eq('command', command)
|
|
69
|
+
.single();
|
|
70
|
+
const currentRoles = existing ? (existing.required_roles || []) : [];
|
|
71
|
+
// Ensure it's an array
|
|
72
|
+
const rolesArray = Array.isArray(currentRoles) ? currentRoles : [];
|
|
73
|
+
if (!rolesArray.includes(roleToAdd)) {
|
|
74
|
+
rolesArray.push(roleToAdd);
|
|
75
|
+
}
|
|
76
|
+
requiredRoles = rolesArray;
|
|
77
|
+
}
|
|
60
78
|
const { data, error } = await client
|
|
61
79
|
.from(TABLE_NAME)
|
|
62
80
|
.upsert({ guild_id: guildId, command, required_roles: requiredRoles }, { onConflict: 'guild_id,command' })
|