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
- if (allEntitlements.length === 0) {
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 === 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) => skuIds.includes(e.skuId));
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
- const entitlements = await this.getUserEntitlements(userId);
87
- const entitlementSkus = entitlements.map((e) => e.skuId);
88
- return skuIds.every((skuId) => entitlementSkus.includes(skuId));
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
- return this.hasEntitlement(userId, skuId);
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
- const requiredRoles = update.requiredRoles || update.$set?.requiredRoles;
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' })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spacecommands",
3
- "version": "3.5.7",
3
+ "version": "3.5.8",
4
4
  "main": "dist/index.js",
5
5
  "types": "./typings.d.ts",
6
6
  "typings": "./typings.d.ts",