@hieuxyz/rpc 1.2.4 → 1.2.5
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/package.json +10 -9
- package/dist/hieuxyz/Client.d.ts +0 -100
- package/dist/hieuxyz/Client.js +0 -256
- package/dist/hieuxyz/gateway/DiscordWebSocket.d.ts +0 -69
- package/dist/hieuxyz/gateway/DiscordWebSocket.js +0 -328
- package/dist/hieuxyz/gateway/entities/OpCode.d.ts +0 -13
- package/dist/hieuxyz/gateway/entities/OpCode.js +0 -17
- package/dist/hieuxyz/gateway/entities/identify.d.ts +0 -13
- package/dist/hieuxyz/gateway/entities/identify.js +0 -17
- package/dist/hieuxyz/gateway/entities/types.d.ts +0 -151
- package/dist/hieuxyz/gateway/entities/types.js +0 -30
- package/dist/hieuxyz/rpc/HieuxyzRPC.d.ts +0 -237
- package/dist/hieuxyz/rpc/HieuxyzRPC.js +0 -546
- package/dist/hieuxyz/rpc/ImageService.d.ts +0 -42
- package/dist/hieuxyz/rpc/ImageService.js +0 -142
- package/dist/hieuxyz/rpc/RpcImage.d.ts +0 -69
- package/dist/hieuxyz/rpc/RpcImage.js +0 -136
- package/dist/hieuxyz/utils/logger.d.ts +0 -5
- package/dist/hieuxyz/utils/logger.js +0 -8
- package/dist/index.d.ts +0 -7
- package/dist/index.js +0 -35
|
@@ -1,546 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.HieuxyzRPC = exports.ActivityFlags = void 0;
|
|
4
|
-
const types_1 = require("../gateway/entities/types");
|
|
5
|
-
const RpcImage_1 = require("./RpcImage");
|
|
6
|
-
const logger_1 = require("../utils/logger");
|
|
7
|
-
/**
|
|
8
|
-
* Flags for activities, used with `.setFlags()`.
|
|
9
|
-
* @enum {number}
|
|
10
|
-
*/
|
|
11
|
-
var ActivityFlags;
|
|
12
|
-
(function (ActivityFlags) {
|
|
13
|
-
ActivityFlags[ActivityFlags["INSTANCE"] = 1] = "INSTANCE";
|
|
14
|
-
ActivityFlags[ActivityFlags["JOIN"] = 2] = "JOIN";
|
|
15
|
-
ActivityFlags[ActivityFlags["SPECTATE"] = 4] = "SPECTATE";
|
|
16
|
-
ActivityFlags[ActivityFlags["JOIN_REQUEST"] = 8] = "JOIN_REQUEST";
|
|
17
|
-
ActivityFlags[ActivityFlags["SYNC"] = 16] = "SYNC";
|
|
18
|
-
ActivityFlags[ActivityFlags["PLAY"] = 32] = "PLAY";
|
|
19
|
-
})(ActivityFlags || (exports.ActivityFlags = ActivityFlags = {}));
|
|
20
|
-
/**
|
|
21
|
-
* Class built for creating and managing Discord Rich Presence states.
|
|
22
|
-
*/
|
|
23
|
-
class HieuxyzRPC {
|
|
24
|
-
imageService;
|
|
25
|
-
onUpdate;
|
|
26
|
-
activity = {};
|
|
27
|
-
assets = {};
|
|
28
|
-
status = 'online';
|
|
29
|
-
applicationId = '1416676323459469363';
|
|
30
|
-
platform = 'desktop';
|
|
31
|
-
/**
|
|
32
|
-
* Cache for resolved image assets to avoid re-uploading or re-fetching.
|
|
33
|
-
* Key: A unique string from RpcImage.getCacheKey().
|
|
34
|
-
* Value: The resolved asset key (e.g., "mp:attachments/...").
|
|
35
|
-
*/
|
|
36
|
-
resolvedAssetsCache = new Map();
|
|
37
|
-
/**
|
|
38
|
-
* Maximum number of items in cache to prevent memory leaks/bloat over long runtime.
|
|
39
|
-
*/
|
|
40
|
-
MAX_CACHE_SIZE = 50;
|
|
41
|
-
renewalInterval = null;
|
|
42
|
-
/**
|
|
43
|
-
* Cache for Application Assets (Bot Assets).
|
|
44
|
-
* Map<ApplicationID, Map<AssetName, AssetID>>
|
|
45
|
-
*/
|
|
46
|
-
applicationAssetsCache = new Map();
|
|
47
|
-
constructor(imageService, onUpdate) {
|
|
48
|
-
this.imageService = imageService;
|
|
49
|
-
this.onUpdate = onUpdate;
|
|
50
|
-
this.startBackgroundRenewal();
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Returns the URL of the large image asset, if available.
|
|
54
|
-
* @type {string | null}
|
|
55
|
-
* @readonly
|
|
56
|
-
*/
|
|
57
|
-
get largeImageUrl() {
|
|
58
|
-
if (!this.assets.large_image)
|
|
59
|
-
return null;
|
|
60
|
-
const cacheKey = this.assets.large_image.getCacheKey();
|
|
61
|
-
const resolvedAsset = this.resolvedAssetsCache.get(cacheKey);
|
|
62
|
-
return resolvedAsset ? this._resolveAssetUrl(resolvedAsset) : null;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Returns the URL of the small image asset, if available.
|
|
66
|
-
* @type {string | null}
|
|
67
|
-
* @readonly
|
|
68
|
-
*/
|
|
69
|
-
get smallImageUrl() {
|
|
70
|
-
if (!this.assets.small_image)
|
|
71
|
-
return null;
|
|
72
|
-
const cacheKey = this.assets.small_image.getCacheKey();
|
|
73
|
-
const resolvedAsset = this.resolvedAssetsCache.get(cacheKey);
|
|
74
|
-
return resolvedAsset ? this._resolveAssetUrl(resolvedAsset) : null;
|
|
75
|
-
}
|
|
76
|
-
_resolveAssetUrl(assetKey) {
|
|
77
|
-
if (assetKey.startsWith('mp:')) {
|
|
78
|
-
return `https://media.discordapp.net/${assetKey.substring(3)}`;
|
|
79
|
-
}
|
|
80
|
-
if (assetKey.startsWith('spotify:')) {
|
|
81
|
-
return `https://i.scdn.co/image/${assetKey.substring(8)}`;
|
|
82
|
-
}
|
|
83
|
-
if (assetKey.startsWith('youtube:')) {
|
|
84
|
-
return `https://i.ytimg.com/vi/${assetKey.substring(8)}/hqdefault.jpg`;
|
|
85
|
-
}
|
|
86
|
-
if (assetKey.startsWith('twitch:')) {
|
|
87
|
-
return `https://static-cdn.jtvnw.net/previews-ttv/live_user_${assetKey.substring(7)}.png`;
|
|
88
|
-
}
|
|
89
|
-
if (this.applicationId && !assetKey.startsWith('http')) {
|
|
90
|
-
return `https://cdn.discordapp.com/app-assets/${this.applicationId}/${assetKey}.png`;
|
|
91
|
-
}
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
_toRpcImage(source) {
|
|
95
|
-
if (typeof source !== 'string') {
|
|
96
|
-
return source;
|
|
97
|
-
}
|
|
98
|
-
if (source.startsWith('https://') || source.startsWith('http://')) {
|
|
99
|
-
try {
|
|
100
|
-
const url = new URL(source);
|
|
101
|
-
if (url.hostname === 'cdn.discordapp.com' || url.hostname === 'media.discordapp.net') {
|
|
102
|
-
const discordAssetPath = url.pathname.substring(1);
|
|
103
|
-
return new RpcImage_1.DiscordImage(discordAssetPath);
|
|
104
|
-
}
|
|
105
|
-
else {
|
|
106
|
-
return new RpcImage_1.ExternalImage(source);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
logger_1.logger.warn(`Could not parse "${source}" into a valid URL. Treating as RawImage.`);
|
|
111
|
-
return new RpcImage_1.RawImage(source);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
if (source.startsWith('attachments/') || source.startsWith('external/')) {
|
|
115
|
-
return new RpcImage_1.DiscordImage(source);
|
|
116
|
-
}
|
|
117
|
-
if (/^[a-zA-Z0-9_]+$/.test(source) && !/^\d{17,20}$/.test(source)) {
|
|
118
|
-
return new RpcImage_1.ApplicationImage(source);
|
|
119
|
-
}
|
|
120
|
-
return new RpcImage_1.RawImage(source);
|
|
121
|
-
}
|
|
122
|
-
cleanupNulls(obj) {
|
|
123
|
-
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== null && v !== undefined));
|
|
124
|
-
}
|
|
125
|
-
sanitize(str, length = 128) {
|
|
126
|
-
return str.length > length ? str.substring(0, length) : str;
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Name the operation (first line of RPC).
|
|
130
|
-
* @param {string} name - Name to display.
|
|
131
|
-
* @returns {this}
|
|
132
|
-
*/
|
|
133
|
-
setName(name) {
|
|
134
|
-
this.activity.name = this.sanitize(name);
|
|
135
|
-
return this;
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Set details for the operation (second line of RPC).
|
|
139
|
-
* @param {string} details - Details to display.
|
|
140
|
-
* @returns {this}
|
|
141
|
-
*/
|
|
142
|
-
setDetails(details) {
|
|
143
|
-
this.activity.details = this.sanitize(details);
|
|
144
|
-
return this;
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Set the state for the operation (third line of the RPC).
|
|
148
|
-
* @param {string} state - State to display.
|
|
149
|
-
* @returns {this}
|
|
150
|
-
*/
|
|
151
|
-
setState(state) {
|
|
152
|
-
this.activity.state = this.sanitize(state);
|
|
153
|
-
return this;
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Set the activity type.
|
|
157
|
-
* @param {SettableActivityType} type - The type of activity (e.g. 0, 'playing', or ActivityType.Playing).
|
|
158
|
-
* @returns {this}
|
|
159
|
-
*/
|
|
160
|
-
setType(type) {
|
|
161
|
-
if (typeof type === 'string') {
|
|
162
|
-
const typeMap = {
|
|
163
|
-
playing: types_1.ActivityType.Playing,
|
|
164
|
-
streaming: types_1.ActivityType.Streaming,
|
|
165
|
-
listening: types_1.ActivityType.Listening,
|
|
166
|
-
watching: types_1.ActivityType.Watching,
|
|
167
|
-
custom: types_1.ActivityType.Custom,
|
|
168
|
-
competing: types_1.ActivityType.Competing,
|
|
169
|
-
};
|
|
170
|
-
this.activity.type = typeMap[type.toLowerCase()] ?? types_1.ActivityType.Playing;
|
|
171
|
-
}
|
|
172
|
-
else {
|
|
173
|
-
this.activity.type = type;
|
|
174
|
-
}
|
|
175
|
-
return this;
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Set a start and/or end timestamp for the activity.
|
|
179
|
-
* @param {number} [start] - Unix timestamp (milliseconds) for start time.
|
|
180
|
-
* @param {number} [end] - Unix timestamp (milliseconds) for the end time.
|
|
181
|
-
* @returns {this}
|
|
182
|
-
*/
|
|
183
|
-
setTimestamps(start, end) {
|
|
184
|
-
this.activity.timestamps = { start, end };
|
|
185
|
-
return this;
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* Set party information for the activity.
|
|
189
|
-
* @param {number} currentSize - Current number of players.
|
|
190
|
-
* @param {number} maxSize - Maximum number of players.
|
|
191
|
-
* @param {string} [id] - Optional custom party ID. Defaults to 'hieuxyz'.
|
|
192
|
-
* @returns {this}
|
|
193
|
-
*/
|
|
194
|
-
setParty(currentSize, maxSize, id = 'hieuxyz') {
|
|
195
|
-
this.activity.party = { id: id, size: [currentSize, maxSize] };
|
|
196
|
-
return this;
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* Set large image and its caption text.
|
|
200
|
-
* @param {string | RpcImage} source - Image source (URL, asset key, Asset Name or RpcImage object).
|
|
201
|
-
* @param {string} [text] - Text displayed when hovering over image.
|
|
202
|
-
* @returns {this}
|
|
203
|
-
*/
|
|
204
|
-
setLargeImage(source, text) {
|
|
205
|
-
this.assets.large_image = this._toRpcImage(source);
|
|
206
|
-
if (text)
|
|
207
|
-
this.assets.large_text = this.sanitize(text);
|
|
208
|
-
return this;
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Set the small image and its caption text.
|
|
212
|
-
* @param {string | RpcImage} source - Image source (URL, asset key, Asset Name or RpcImage object).
|
|
213
|
-
* @param {string} [text] - Text displayed when hovering over image.
|
|
214
|
-
* @returns {this}
|
|
215
|
-
*/
|
|
216
|
-
setSmallImage(source, text) {
|
|
217
|
-
this.assets.small_image = this._toRpcImage(source);
|
|
218
|
-
if (text)
|
|
219
|
-
this.assets.small_text = this.sanitize(text);
|
|
220
|
-
return this;
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Add a single button to the activity.
|
|
224
|
-
* @param {string} label - The text displayed on the button.
|
|
225
|
-
* @param {string} url - The URL opened when the button is clicked.
|
|
226
|
-
* @returns {this}
|
|
227
|
-
*/
|
|
228
|
-
addButton(label, url) {
|
|
229
|
-
if (!this.activity.buttons) {
|
|
230
|
-
this.activity.buttons = [];
|
|
231
|
-
}
|
|
232
|
-
if (!this.activity.metadata) {
|
|
233
|
-
this.activity.metadata = { button_urls: [] };
|
|
234
|
-
}
|
|
235
|
-
if (!this.activity.metadata.button_urls) {
|
|
236
|
-
this.activity.metadata.button_urls = [];
|
|
237
|
-
}
|
|
238
|
-
if (this.activity.buttons.length >= 2) {
|
|
239
|
-
logger_1.logger.warn('Cannot add more than 2 buttons. Button ignored.');
|
|
240
|
-
return this;
|
|
241
|
-
}
|
|
242
|
-
this.activity.buttons.push(this.sanitize(label, 32));
|
|
243
|
-
this.activity.metadata.button_urls.push(url);
|
|
244
|
-
return this;
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Set clickable buttons for RPC (up to 2).
|
|
248
|
-
* This will overwrite any existing buttons.
|
|
249
|
-
* @param {RpcButton[]} buttons - An array of button objects, each with a `label` and `url`.
|
|
250
|
-
* @returns {this}
|
|
251
|
-
*/
|
|
252
|
-
setButtons(buttons) {
|
|
253
|
-
const validButtons = buttons.slice(0, 2);
|
|
254
|
-
this.activity.buttons = validButtons.map((b) => this.sanitize(b.label, 32));
|
|
255
|
-
this.activity.metadata = { button_urls: validButtons.map((b) => b.url) };
|
|
256
|
-
return this;
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* Set secrets for joining, spectating, and matching games.
|
|
260
|
-
* @param {RpcSecrets} secrets - An object with join, spectate, and/or match secrets.
|
|
261
|
-
* @returns {this}
|
|
262
|
-
*/
|
|
263
|
-
setSecrets(secrets) {
|
|
264
|
-
this.activity.secrets = secrets;
|
|
265
|
-
return this;
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Set the sync_id, typically used for Spotify track synchronization.
|
|
269
|
-
* @param {string} syncId - The synchronization ID.
|
|
270
|
-
* @returns {this}
|
|
271
|
-
*/
|
|
272
|
-
setSyncId(syncId) {
|
|
273
|
-
this.activity.sync_id = syncId;
|
|
274
|
-
return this;
|
|
275
|
-
}
|
|
276
|
-
/**
|
|
277
|
-
* Set activity flags. Use the ActivityFlags enum for convenience.
|
|
278
|
-
* @param {number} flags - A number representing the bitwise flags.
|
|
279
|
-
* @returns {this}
|
|
280
|
-
*/
|
|
281
|
-
setFlags(flags) {
|
|
282
|
-
this.activity.flags = flags;
|
|
283
|
-
return this;
|
|
284
|
-
}
|
|
285
|
-
/**
|
|
286
|
-
* Set custom application ID for RPC.
|
|
287
|
-
* @param {string} id - Discord app ID (must be an 18 or 19 digit number string).
|
|
288
|
-
* @throws {Error} If ID is invalid.
|
|
289
|
-
* @returns {this}
|
|
290
|
-
*/
|
|
291
|
-
setApplicationId(id) {
|
|
292
|
-
if (!/^\d{17,20}$/.test(id)) {
|
|
293
|
-
throw new Error('The app ID must be a valid number string (17-20 digits).');
|
|
294
|
-
}
|
|
295
|
-
this.applicationId = id;
|
|
296
|
-
return this;
|
|
297
|
-
}
|
|
298
|
-
/**
|
|
299
|
-
* Set the user's status (e.g. online, idle, dnd).
|
|
300
|
-
* @param {'online' | 'dnd' | 'idle' | 'invisible' | 'offline'} status - Desired state.
|
|
301
|
-
* @returns {this}
|
|
302
|
-
*/
|
|
303
|
-
setStatus(status) {
|
|
304
|
-
this.status = status;
|
|
305
|
-
return this;
|
|
306
|
-
}
|
|
307
|
-
/**
|
|
308
|
-
* Set the platform on which the activity is running.
|
|
309
|
-
* @param {DiscordPlatform} platform - Platform (e.g. 'desktop', 'xbox').
|
|
310
|
-
* @returns {this}
|
|
311
|
-
*/
|
|
312
|
-
setPlatform(platform) {
|
|
313
|
-
this.platform = platform;
|
|
314
|
-
return this;
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
|
-
* Marks the activity as a joinable instance for the game.
|
|
318
|
-
* @param {boolean} instance - Whether this activity is a specific instance.
|
|
319
|
-
* @returns {this}
|
|
320
|
-
*/
|
|
321
|
-
setInstance(instance) {
|
|
322
|
-
this.activity.instance = instance;
|
|
323
|
-
return this;
|
|
324
|
-
}
|
|
325
|
-
clearDetails() {
|
|
326
|
-
this.activity.details = undefined;
|
|
327
|
-
return this;
|
|
328
|
-
}
|
|
329
|
-
clearState() {
|
|
330
|
-
this.activity.state = undefined;
|
|
331
|
-
return this;
|
|
332
|
-
}
|
|
333
|
-
clearTimestamps() {
|
|
334
|
-
this.activity.timestamps = undefined;
|
|
335
|
-
return this;
|
|
336
|
-
}
|
|
337
|
-
clearParty() {
|
|
338
|
-
this.activity.party = undefined;
|
|
339
|
-
return this;
|
|
340
|
-
}
|
|
341
|
-
clearButtons() {
|
|
342
|
-
this.activity.buttons = undefined;
|
|
343
|
-
this.activity.metadata = undefined;
|
|
344
|
-
return this;
|
|
345
|
-
}
|
|
346
|
-
clearSecrets() {
|
|
347
|
-
this.activity.secrets = undefined;
|
|
348
|
-
return this;
|
|
349
|
-
}
|
|
350
|
-
clearInstance() {
|
|
351
|
-
this.activity.instance = undefined;
|
|
352
|
-
return this;
|
|
353
|
-
}
|
|
354
|
-
clearLargeImage() {
|
|
355
|
-
this.assets.large_image = undefined;
|
|
356
|
-
this.assets.large_text = undefined;
|
|
357
|
-
return this;
|
|
358
|
-
}
|
|
359
|
-
clearSmallImage() {
|
|
360
|
-
this.assets.small_image = undefined;
|
|
361
|
-
this.assets.small_text = undefined;
|
|
362
|
-
return this;
|
|
363
|
-
}
|
|
364
|
-
getExpiryTime(assetKey) {
|
|
365
|
-
if (!assetKey.startsWith('mp:attachments'))
|
|
366
|
-
return null;
|
|
367
|
-
const urlPart = assetKey.substring(3);
|
|
368
|
-
try {
|
|
369
|
-
const parsedUrl = new URL(`https://cdn.discordapp.com/${urlPart}`);
|
|
370
|
-
const expiresTimestamp = parsedUrl.searchParams.get('ex');
|
|
371
|
-
if (expiresTimestamp) {
|
|
372
|
-
return parseInt(expiresTimestamp, 16) * 1000;
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
catch {
|
|
376
|
-
logger_1.logger.error(`Could not parse asset URL for expiry check: ${assetKey}`);
|
|
377
|
-
}
|
|
378
|
-
return null;
|
|
379
|
-
}
|
|
380
|
-
async renewAssetIfNeeded(cacheKey, assetKey) {
|
|
381
|
-
const expiryTimeMs = this.getExpiryTime(assetKey);
|
|
382
|
-
if (expiryTimeMs && expiryTimeMs < Date.now() + 3600000) {
|
|
383
|
-
// logger.info(`Asset ${cacheKey} is expiring soon. Renewing...`);
|
|
384
|
-
const assetId = assetKey.split('mp:attachments/')[1];
|
|
385
|
-
const newAsset = await this.imageService.renewImage(assetId);
|
|
386
|
-
if (newAsset) {
|
|
387
|
-
this.resolvedAssetsCache.set(cacheKey, newAsset);
|
|
388
|
-
return newAsset;
|
|
389
|
-
}
|
|
390
|
-
logger_1.logger.warn(`Failed to renew asset, will use the old one.`);
|
|
391
|
-
}
|
|
392
|
-
return assetKey;
|
|
393
|
-
}
|
|
394
|
-
startBackgroundRenewal() {
|
|
395
|
-
if (this.renewalInterval) {
|
|
396
|
-
clearInterval(this.renewalInterval);
|
|
397
|
-
}
|
|
398
|
-
this.renewalInterval = setInterval(async () => {
|
|
399
|
-
// logger.info('Running background asset renewal check...');
|
|
400
|
-
for (const [cacheKey, assetKey] of this.resolvedAssetsCache.entries()) {
|
|
401
|
-
await this.renewAssetIfNeeded(cacheKey, assetKey);
|
|
402
|
-
}
|
|
403
|
-
}, 600000);
|
|
404
|
-
}
|
|
405
|
-
/**
|
|
406
|
-
* Stops the background process that checks for asset renewal.
|
|
407
|
-
*/
|
|
408
|
-
stopBackgroundRenewal() {
|
|
409
|
-
if (this.renewalInterval) {
|
|
410
|
-
clearInterval(this.renewalInterval);
|
|
411
|
-
this.renewalInterval = null;
|
|
412
|
-
// logger.info('Stopped background asset renewal process.');
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
/**
|
|
416
|
-
* Ensure assets are fetched for the current application ID.
|
|
417
|
-
*/
|
|
418
|
-
async ensureAppAssetsLoaded() {
|
|
419
|
-
if (!this.applicationAssetsCache.has(this.applicationId)) {
|
|
420
|
-
logger_1.logger.info(`Fetching assets for Application ID: ${this.applicationId}...`);
|
|
421
|
-
const assets = await this.imageService.fetchApplicationAssets(this.applicationId);
|
|
422
|
-
const assetMap = new Map();
|
|
423
|
-
assets.forEach((asset) => {
|
|
424
|
-
assetMap.set(asset.name, asset.id);
|
|
425
|
-
});
|
|
426
|
-
this.applicationAssetsCache.set(this.applicationId, assetMap);
|
|
427
|
-
logger_1.logger.info(`Loaded ${assets.length} assets for Application ID: ${this.applicationId}.`);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
async resolveImage(image) {
|
|
431
|
-
if (!image)
|
|
432
|
-
return undefined;
|
|
433
|
-
const cacheKey = image.getCacheKey();
|
|
434
|
-
if (cacheKey.startsWith('app_asset:')) {
|
|
435
|
-
await this.ensureAppAssetsLoaded();
|
|
436
|
-
const assetName = cacheKey.substring('app_asset:'.length);
|
|
437
|
-
const appAssets = this.applicationAssetsCache.get(this.applicationId);
|
|
438
|
-
const assetId = appAssets?.get(assetName);
|
|
439
|
-
if (!assetId) {
|
|
440
|
-
logger_1.logger.warn(`Asset with name "${assetName}" not found for Application ID ${this.applicationId}.`);
|
|
441
|
-
return undefined;
|
|
442
|
-
}
|
|
443
|
-
return assetId;
|
|
444
|
-
}
|
|
445
|
-
if (this.resolvedAssetsCache.size >= this.MAX_CACHE_SIZE && !this.resolvedAssetsCache.has(cacheKey)) {
|
|
446
|
-
const oldestKey = this.resolvedAssetsCache.keys().next().value;
|
|
447
|
-
if (oldestKey) {
|
|
448
|
-
this.resolvedAssetsCache.delete(oldestKey);
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
const cachedAsset = this.resolvedAssetsCache.get(cacheKey);
|
|
452
|
-
if (cachedAsset) {
|
|
453
|
-
return await this.renewAssetIfNeeded(cacheKey, cachedAsset);
|
|
454
|
-
}
|
|
455
|
-
const resolvedAsset = await image.resolve(this.imageService);
|
|
456
|
-
if (resolvedAsset) {
|
|
457
|
-
if (resolvedAsset.startsWith('app_asset:')) {
|
|
458
|
-
return this.resolveImage(image);
|
|
459
|
-
}
|
|
460
|
-
this.resolvedAssetsCache.set(cacheKey, resolvedAsset);
|
|
461
|
-
}
|
|
462
|
-
return resolvedAsset;
|
|
463
|
-
}
|
|
464
|
-
/**
|
|
465
|
-
* Publicly accessible method to build the Activity object.
|
|
466
|
-
* Used by Client to aggregate activities from multiple RPC instances.
|
|
467
|
-
* @returns {Promise<Activity | null>} The constructed activity or null if empty.
|
|
468
|
-
*/
|
|
469
|
-
async buildActivity() {
|
|
470
|
-
if (Object.keys(this.activity).length === 0 && !this.assets.large_image && !this.assets.small_image) {
|
|
471
|
-
return null;
|
|
472
|
-
}
|
|
473
|
-
const large_image = await this.resolveImage(this.assets.large_image);
|
|
474
|
-
const small_image = await this.resolveImage(this.assets.small_image);
|
|
475
|
-
const finalAssets = {
|
|
476
|
-
large_text: this.assets.large_text,
|
|
477
|
-
small_text: this.assets.small_text,
|
|
478
|
-
};
|
|
479
|
-
if (large_image)
|
|
480
|
-
finalAssets.large_image = large_image;
|
|
481
|
-
if (small_image)
|
|
482
|
-
finalAssets.small_image = small_image;
|
|
483
|
-
const finalActivity = { ...this.activity };
|
|
484
|
-
finalActivity.assets = large_image || small_image ? finalAssets : undefined;
|
|
485
|
-
finalActivity.application_id = this.applicationId;
|
|
486
|
-
finalActivity.platform = this.platform;
|
|
487
|
-
if (!finalActivity.name) {
|
|
488
|
-
finalActivity.name = 'hieuxyzRPC';
|
|
489
|
-
}
|
|
490
|
-
if (typeof finalActivity.type === 'undefined') {
|
|
491
|
-
finalActivity.type = types_1.ActivityType.Playing;
|
|
492
|
-
}
|
|
493
|
-
return this.cleanupNulls(finalActivity);
|
|
494
|
-
}
|
|
495
|
-
/**
|
|
496
|
-
* Build the final Rich Presence payload and notify the Client to send it to Discord.
|
|
497
|
-
* @returns {Promise<void>}
|
|
498
|
-
*/
|
|
499
|
-
async build() {
|
|
500
|
-
await this.onUpdate();
|
|
501
|
-
}
|
|
502
|
-
/**
|
|
503
|
-
* Sends an update to an existing RPC.
|
|
504
|
-
* This is essentially an alias for `build()`.
|
|
505
|
-
* @returns {Promise<void>}
|
|
506
|
-
*/
|
|
507
|
-
async updateRPC() {
|
|
508
|
-
await this.build();
|
|
509
|
-
}
|
|
510
|
-
/**
|
|
511
|
-
* Clears the current Rich Presence from the user's profile and resets the builder.
|
|
512
|
-
*/
|
|
513
|
-
clear() {
|
|
514
|
-
this.activity = {};
|
|
515
|
-
this.assets = {};
|
|
516
|
-
this.applicationId = '1416676323459469363'; // Reset to default
|
|
517
|
-
this.platform = 'desktop'; // Reset to default
|
|
518
|
-
logger_1.logger.info('RPC instance cleared.');
|
|
519
|
-
this.onUpdate();
|
|
520
|
-
}
|
|
521
|
-
/**
|
|
522
|
-
* Manually clear the asset cache to free memory.
|
|
523
|
-
*/
|
|
524
|
-
clearCache() {
|
|
525
|
-
this.resolvedAssetsCache.clear();
|
|
526
|
-
this.applicationAssetsCache.clear();
|
|
527
|
-
logger_1.logger.info('RPC Asset cache has been cleared.');
|
|
528
|
-
}
|
|
529
|
-
/**
|
|
530
|
-
* Permanently destroy this RPC instance.
|
|
531
|
-
* Stops renewal timers and clears memory.
|
|
532
|
-
*/
|
|
533
|
-
destroy() {
|
|
534
|
-
this.stopBackgroundRenewal();
|
|
535
|
-
this.clearCache();
|
|
536
|
-
this.activity = {};
|
|
537
|
-
this.assets = {};
|
|
538
|
-
}
|
|
539
|
-
/**
|
|
540
|
-
* Get the current status set for this RPC instance.
|
|
541
|
-
*/
|
|
542
|
-
get currentStatus() {
|
|
543
|
-
return this.status;
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
exports.HieuxyzRPC = HieuxyzRPC;
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
export interface DiscordAsset {
|
|
2
|
-
id: string;
|
|
3
|
-
type: number;
|
|
4
|
-
name: string;
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* A service to handle external image proxying and local image uploading.
|
|
8
|
-
* Interact with a backend API service to manage image assets.
|
|
9
|
-
*/
|
|
10
|
-
export declare class ImageService {
|
|
11
|
-
private apiClient;
|
|
12
|
-
/**
|
|
13
|
-
* Create an ImageService instance.
|
|
14
|
-
* @param apiBaseUrl - The base URL of the image upload/proxy API.
|
|
15
|
-
*/
|
|
16
|
-
constructor(apiBaseUrl?: string);
|
|
17
|
-
/**
|
|
18
|
-
* Get an asset key proxy for an external image URL.
|
|
19
|
-
* @param url - URL of external image.
|
|
20
|
-
* @returns {Promise<string | undefined>} Asset key resolved or undefined if failed.
|
|
21
|
-
*/
|
|
22
|
-
getExternalUrl(url: string): Promise<string | undefined>;
|
|
23
|
-
/**
|
|
24
|
-
* Upload an image from the local file system to the image service.
|
|
25
|
-
* @param filePath - Path to the image file.
|
|
26
|
-
* @param fileName - File name to use when uploading.
|
|
27
|
-
* @returns {Promise<string | undefined>} Asset key resolved or undefined if failed.
|
|
28
|
-
*/
|
|
29
|
-
uploadImage(filePath: string, fileName: string): Promise<string | undefined>;
|
|
30
|
-
/**
|
|
31
|
-
* Requests a new signed URL for an expired or expiring attachment asset.
|
|
32
|
-
* @param assetId The asset ID part of the URL (e.g., "channel_id/message_id/filename.ext...")
|
|
33
|
-
* @returns {Promise<string | undefined>} The new asset key or undefined if it failed.
|
|
34
|
-
*/
|
|
35
|
-
renewImage(assetId: string): Promise<string | undefined>;
|
|
36
|
-
/**
|
|
37
|
-
* Fetch all assets for a specific Discord Application.
|
|
38
|
-
* @param applicationId - The ID of the application.
|
|
39
|
-
* @returns {Promise<DiscordAsset[]>} List of assets (id, name, type).
|
|
40
|
-
*/
|
|
41
|
-
fetchApplicationAssets(applicationId: string): Promise<DiscordAsset[]>;
|
|
42
|
-
}
|