@hieuxyz/rpc 1.1.11 → 1.2.0

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,7 +1,7 @@
1
1
  # @hieuxyz/rpc
2
2
 
3
3
  [![NPM Version](https://img.shields.io/npm/v/@hieuxyz/rpc.svg)](https://www.npmjs.com/package/@hieuxyz/rpc)
4
- [![License](https://img.shields.io/npm/l/@hieuxyz/rpc.svg)](https://github.com/hieuxyz/rpc/blob/main/LICENSE)
4
+ [![License](https://img.shields.io/npm/l/@hieuxyz/rpc.svg)](https://github.com/hieuxyz00/hieuxyz_rpc/blob/master/LICENSE)
5
5
  [![Downloads](https://img.shields.io/npm/dt/@hieuxyz/rpc.svg)](https://www.npmjs.com/package/@hieuxyz/rpc)
6
6
 
7
7
  An easy-to-use and powerful Discord Rich Presence (RPC) library built for the Node.js environment using TypeScript. This library is designed to simplify the creation and management of custom RPC states for Discord user accounts.
@@ -1,5 +1,6 @@
1
1
  import { HieuxyzRPC } from './rpc/HieuxyzRPC';
2
2
  import { ClientProperties } from './gateway/entities/identify';
3
+ import { DiscordUser } from './gateway/entities/types';
3
4
  /**
4
5
  * Option to initialize Client.
5
6
  */
@@ -42,6 +43,11 @@ export declare class Client {
42
43
  * Use this to set your Rich Presence state details.
43
44
  */
44
45
  readonly rpc: HieuxyzRPC;
46
+ /**
47
+ * Information about the logged-in user.
48
+ * Populated after run() resolves.
49
+ */
50
+ user: DiscordUser | null;
45
51
  private readonly websocket;
46
52
  private readonly imageService;
47
53
  private readonly token;
@@ -51,15 +57,23 @@ export declare class Client {
51
57
  * @throws {Error} If no token is provided in the options.
52
58
  */
53
59
  constructor(options: ClientOptions);
60
+ /**
61
+ * Displays information about the library.
62
+ */
63
+ private printAbout;
54
64
  /**
55
65
  * Connect to Discord Gateway and prepare the client for RPC updates.
56
66
  * This method must be called before sending any Rich Presence updates.
57
- * @returns {Promise<void>} A promise will be resolved when the client is ready.
67
+ * @returns {Promise<DiscordUser>} A promise will be resolved when the client is ready.
58
68
  */
59
- run(): Promise<void>;
69
+ run(): Promise<DiscordUser>;
70
+ private formatters;
71
+ private formatFlags;
72
+ private printDynamicTree;
73
+ private logUserProfile;
60
74
  /**
61
75
  * Close the connection to Discord Gateway.
62
- * @param {boolean} [force=false] - If true, the client will close permanently and will not attempt to reconnect,
76
+ * @param {boolean} [force=false] - If true, the client closes permanently and will not reconnect.
63
77
  * even if `alwaysReconnect` is enabled. Defaults to false.
64
78
  */
65
79
  close(force?: boolean): void;
@@ -5,6 +5,7 @@ const DiscordWebSocket_1 = require("./gateway/DiscordWebSocket");
5
5
  const HieuxyzRPC_1 = require("./rpc/HieuxyzRPC");
6
6
  const ImageService_1 = require("./rpc/ImageService");
7
7
  const logger_1 = require("./utils/logger");
8
+ const types_1 = require("./gateway/entities/types");
8
9
  /**
9
10
  * The main Client class for interacting with Discord Rich Presence.
10
11
  * This is the starting point for creating and managing your RPC state.
@@ -23,6 +24,11 @@ class Client {
23
24
  * Use this to set your Rich Presence state details.
24
25
  */
25
26
  rpc;
27
+ /**
28
+ * Information about the logged-in user.
29
+ * Populated after run() resolves.
30
+ */
31
+ user = null;
26
32
  websocket;
27
33
  imageService;
28
34
  token;
@@ -43,21 +49,154 @@ class Client {
43
49
  connectionTimeout: options.connectionTimeout,
44
50
  });
45
51
  this.rpc = new HieuxyzRPC_1.HieuxyzRPC(this.websocket, this.imageService);
52
+ this.printAbout();
53
+ }
54
+ /**
55
+ * Displays information about the library.
56
+ */
57
+ printAbout() {
58
+ const version = '1.2.0';
59
+ console.log(`
60
+ _ _
61
+ | |__ (_) ___ _ ___ ___ _ ______
62
+ | '_ \\| |/ _ \\ | | \\ \\/ / | | |_ /
63
+ | | | | | __/ |_| |> <| |_| |/ /
64
+ |_| |_|_|\\___|\\__,_/_/\\_\\\\__, /___|
65
+ |___/
66
+ @hieuxyz/rpc v${version}
67
+ A powerful Discord Rich Presence library.
68
+ Developed by: hieuxyz
69
+ `);
46
70
  }
47
71
  /**
48
72
  * Connect to Discord Gateway and prepare the client for RPC updates.
49
73
  * This method must be called before sending any Rich Presence updates.
50
- * @returns {Promise<void>} A promise will be resolved when the client is ready.
74
+ * @returns {Promise<DiscordUser>} A promise will be resolved when the client is ready.
51
75
  */
52
76
  async run() {
53
77
  this.websocket.connect();
54
78
  logger_1.logger.info('Waiting for Discord session to be ready...');
55
- await this.websocket.readyPromise;
79
+ const user = await this.websocket.readyPromise;
80
+ this.user = user;
81
+ this.logUserProfile(user);
56
82
  logger_1.logger.info('Client is ready to send Rich Presence updates.');
83
+ return user;
84
+ }
85
+ formatters = {
86
+ email: (val) => (val ? `\x1b[90m<Hidden>\x1b[0m` : 'null'),
87
+ phone: (val) => (val ? `\x1b[90m<Hidden>\x1b[0m` : 'null'),
88
+ avatar: (val, parent) => {
89
+ if (!val)
90
+ return 'null';
91
+ const ext = val.startsWith('a_') ? 'gif' : 'png';
92
+ const userId = parent?.id;
93
+ const url = userId ? `https://cdn.discordapp.com/avatars/${userId}/${val}.${ext}` : '';
94
+ return `"${val}" ${url ? `(\x1b[34m${url}\x1b[0m)` : ''}`;
95
+ },
96
+ banner: (val, parent) => {
97
+ if (!val)
98
+ return 'null';
99
+ const ext = val.startsWith('a_') ? 'gif' : 'png';
100
+ const userId = parent?.id;
101
+ const url = userId ? `https://cdn.discordapp.com/banners/${userId}/${val}.${ext}` : '';
102
+ return `"${val}" ${url ? `(\x1b[34m${url}\x1b[0m)` : ''}`;
103
+ },
104
+ asset: (val) => {
105
+ const url = `https://cdn.discordapp.com/avatar-decoration-presets/${val}.png`;
106
+ return `"${val}" (\x1b[34m${url}\x1b[0m)`;
107
+ },
108
+ accent_color: (val) => `${val} (\x1b[33m#${val.toString(16).padStart(6, '0').toUpperCase()}\x1b[0m)`,
109
+ banner_color: (val) => `\x1b[33m${val}\x1b[0m`,
110
+ expires_at: (val) => (val ? `${val} (${new Date(val).toLocaleString()})` : 'Never'),
111
+ premium_type: (val) => {
112
+ const map = { 0: 'None', 1: 'Classic', 2: 'Nitro', 3: 'Basic' };
113
+ return `${val} (\x1b[32m${map[val] || 'Unknown'}\x1b[0m)`;
114
+ },
115
+ flags: (val) => this.formatFlags(val),
116
+ public_flags: (val) => this.formatFlags(val),
117
+ purchased_flags: (val) => `\x1b[33m${val}\x1b[0m`,
118
+ };
119
+ formatFlags(flags) {
120
+ const flagNames = [];
121
+ if (flags & types_1.UserFlags.STAFF)
122
+ flagNames.push('Staff');
123
+ if (flags & types_1.UserFlags.PARTNER)
124
+ flagNames.push('Partner');
125
+ if (flags & types_1.UserFlags.HYPESQUAD)
126
+ flagNames.push('HypeSquad');
127
+ if (flags & types_1.UserFlags.BUG_HUNTER_LEVEL_1)
128
+ flagNames.push('BugHunter I');
129
+ if (flags & types_1.UserFlags.HYPESQUAD_ONLINE_HOUSE_1)
130
+ flagNames.push('Bravery');
131
+ if (flags & types_1.UserFlags.HYPESQUAD_ONLINE_HOUSE_2)
132
+ flagNames.push('Brilliance');
133
+ if (flags & types_1.UserFlags.HYPESQUAD_ONLINE_HOUSE_3)
134
+ flagNames.push('Balance');
135
+ if (flags & types_1.UserFlags.PREMIUM_EARLY_SUPPORTER)
136
+ flagNames.push('EarlySupporter');
137
+ if (flags & types_1.UserFlags.BUG_HUNTER_LEVEL_2)
138
+ flagNames.push('BugHunter II');
139
+ if (flags & types_1.UserFlags.VERIFIED_DEVELOPER)
140
+ flagNames.push('VerifiedDev');
141
+ if (flags & types_1.UserFlags.CERTIFIED_MODERATOR)
142
+ flagNames.push('CertifiedMod');
143
+ if (flags & types_1.UserFlags.ACTIVE_DEVELOPER)
144
+ flagNames.push('ActiveDev');
145
+ return `${flags} \x1b[36m[${flagNames.length > 0 ? flagNames.join(', ') : 'None'}]\x1b[0m`;
146
+ }
147
+ printDynamicTree(obj, prefix = '') {
148
+ const entries = Object.entries(obj);
149
+ entries.forEach(([key, value], index) => {
150
+ const isLastItem = index === entries.length - 1;
151
+ const connector = isLastItem ? '└── ' : '├── ';
152
+ const childPrefix = prefix + (isLastItem ? ' ' : '│ ');
153
+ let displayValue = '';
154
+ let isObject = false;
155
+ if (value === null) {
156
+ displayValue = '\x1b[90mnull\x1b[0m';
157
+ }
158
+ else if (typeof value === 'object' && !Array.isArray(value)) {
159
+ isObject = true;
160
+ console.log(`${prefix}${connector}\x1b[1m${key}\x1b[0m`);
161
+ this.printDynamicTree(value, childPrefix);
162
+ }
163
+ else if (Array.isArray(value)) {
164
+ if (value.length > 0 && typeof value[0] !== 'object') {
165
+ displayValue = `[ ${value.join(', ')} ]`;
166
+ }
167
+ else {
168
+ displayValue = `[Array(${value.length})]`;
169
+ }
170
+ }
171
+ else {
172
+ if (this.formatters[key]) {
173
+ displayValue = this.formatters[key](value, obj);
174
+ }
175
+ else {
176
+ if (typeof value === 'string')
177
+ displayValue = `"\x1b[32m${value}\x1b[0m"`;
178
+ else if (typeof value === 'boolean')
179
+ displayValue = value ? '\x1b[32mtrue\x1b[0m' : '\x1b[31mfalse\x1b[0m';
180
+ else if (typeof value === 'number')
181
+ displayValue = `\x1b[33m${value}\x1b[0m`;
182
+ else
183
+ displayValue = String(value);
184
+ }
185
+ }
186
+ if (!isObject) {
187
+ console.log(`${prefix}${connector}${key}: ${displayValue}`);
188
+ }
189
+ });
190
+ }
191
+ logUserProfile(user) {
192
+ logger_1.logger.info('-> User Data:');
193
+ this.printDynamicTree(user);
194
+ /*logger.info('-> Raw JSON:');
195
+ console.log(JSON.stringify(user));*/
57
196
  }
58
197
  /**
59
198
  * Close the connection to Discord Gateway.
60
- * @param {boolean} [force=false] - If true, the client will close permanently and will not attempt to reconnect,
199
+ * @param {boolean} [force=false] - If true, the client closes permanently and will not reconnect.
61
200
  * even if `alwaysReconnect` is enabled. Defaults to false.
62
201
  */
63
202
  close(force = false) {
@@ -1,5 +1,5 @@
1
1
  import { ClientProperties } from './entities/identify';
2
- import { PresenceUpdatePayload } from './entities/types';
2
+ import { PresenceUpdatePayload, DiscordUser } from './entities/types';
3
3
  interface DiscordWebSocketOptions {
4
4
  alwaysReconnect: boolean;
5
5
  properties?: ClientProperties;
@@ -22,11 +22,15 @@ export declare class DiscordWebSocket {
22
22
  private permanentClose;
23
23
  private connectTimeout;
24
24
  private resolveReady;
25
+ /**
26
+ * Current logged in user info.
27
+ */
28
+ user: DiscordUser | null;
25
29
  /**
26
30
  * A promise will be resolved when the Gateway connection is ready.
27
31
  * and received the READY event.
28
32
  */
29
- readyPromise: Promise<void>;
33
+ readyPromise: Promise<DiscordUser>;
30
34
  /**
31
35
  * Create a DiscordWebSocket instance.
32
36
  * @param {string} token - Discord user token for authentication.
@@ -59,6 +59,10 @@ class DiscordWebSocket {
59
59
  permanentClose = false;
60
60
  connectTimeout = null;
61
61
  resolveReady = () => { };
62
+ /**
63
+ * Current logged in user info.
64
+ */
65
+ user = null;
62
66
  /**
63
67
  * A promise will be resolved when the Gateway connection is ready.
64
68
  * and received the READY event.
@@ -183,12 +187,15 @@ class DiscordWebSocket {
183
187
  if (payload.t === 'READY') {
184
188
  this.sessionId = payload.d.session_id;
185
189
  this.resumeGatewayUrl = payload.d.resume_gateway_url + '/?v=10&encoding=json';
190
+ this.user = payload.d.user;
186
191
  logger_1.logger.info(`Session READY. Session ID: ${this.sessionId}. Resume URL set.`);
187
- this.resolveReady();
192
+ this.resolveReady(this.user);
188
193
  }
189
194
  else if (payload.t === 'RESUMED') {
190
195
  logger_1.logger.info('The session has been successfully resumed.');
191
- this.resolveReady();
196
+ if (this.user) {
197
+ this.resolveReady(this.user);
198
+ }
192
199
  }
193
200
  break;
194
201
  case OpCode_1.OpCode.HEARTBEAT_ACK:
@@ -27,6 +27,88 @@ export interface IdentifyPayload {
27
27
  properties: IdentifyProperties;
28
28
  compress: boolean;
29
29
  }
30
+ export declare enum UserFlags {
31
+ STAFF = 1,
32
+ PARTNER = 2,
33
+ HYPESQUAD = 4,
34
+ BUG_HUNTER_LEVEL_1 = 8,
35
+ HYPESQUAD_ONLINE_HOUSE_1 = 64,// Bravery
36
+ HYPESQUAD_ONLINE_HOUSE_2 = 128,// Brilliance
37
+ HYPESQUAD_ONLINE_HOUSE_3 = 256,// Balance
38
+ PREMIUM_EARLY_SUPPORTER = 512,
39
+ TEAM_PSEUDO_USER = 1024,
40
+ BUG_HUNTER_LEVEL_2 = 16384,
41
+ VERIFIED_BOT = 65536,
42
+ VERIFIED_DEVELOPER = 131072,
43
+ CERTIFIED_MODERATOR = 262144,
44
+ BOT_HTTP_INTERACTIONS = 524288,
45
+ ACTIVE_DEVELOPER = 4194304
46
+ }
47
+ export interface DiscordUser {
48
+ id: string;
49
+ username: string;
50
+ discriminator: string;
51
+ global_name?: string | null;
52
+ avatar?: string | null;
53
+ bot?: boolean;
54
+ system?: boolean;
55
+ mfa_enabled?: boolean;
56
+ banner?: string | null;
57
+ accent_color?: number | null;
58
+ locale?: string;
59
+ verified?: boolean;
60
+ email?: string | null;
61
+ flags?: number;
62
+ premium_type?: number;
63
+ public_flags?: number;
64
+ bio?: string;
65
+ phone?: string | null;
66
+ nsfw_allowed?: boolean;
67
+ pronouns?: string;
68
+ mobile?: boolean;
69
+ desktop?: boolean;
70
+ clan?: {
71
+ tag: string;
72
+ identity_guild_id: string;
73
+ badge: string;
74
+ identity_enabled?: boolean;
75
+ } | null;
76
+ primary_guild?: {
77
+ tag: string;
78
+ identity_guild_id: string;
79
+ badge: string;
80
+ identity_enabled?: boolean;
81
+ } | null;
82
+ purchased_flags?: number;
83
+ premium_usage_flags?: number;
84
+ premium?: boolean;
85
+ premium_state?: {
86
+ premium_subscription_type?: number;
87
+ premium_subscription_group_role?: number;
88
+ premium_source?: number;
89
+ } | null;
90
+ avatar_decoration_data?: {
91
+ asset: string;
92
+ sku_id: string;
93
+ expires_at: number | null;
94
+ } | null;
95
+ collectibles?: {
96
+ nameplate?: {
97
+ asset: string;
98
+ label: string;
99
+ sku_id: string;
100
+ palette?: string;
101
+ expires_at?: number | null;
102
+ };
103
+ } | null;
104
+ display_name_styles?: {
105
+ font_id?: number;
106
+ effect_id?: number;
107
+ colors?: number[];
108
+ } | null;
109
+ banner_color?: string | null;
110
+ age_verification_status?: number;
111
+ }
30
112
  export interface Activity {
31
113
  name: string;
32
114
  type: number;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ActivityType = void 0;
3
+ exports.UserFlags = exports.ActivityType = void 0;
4
4
  var ActivityType;
5
5
  (function (ActivityType) {
6
6
  ActivityType[ActivityType["Playing"] = 0] = "Playing";
@@ -10,3 +10,21 @@ var ActivityType;
10
10
  ActivityType[ActivityType["Custom"] = 4] = "Custom";
11
11
  ActivityType[ActivityType["Competing"] = 5] = "Competing";
12
12
  })(ActivityType || (exports.ActivityType = ActivityType = {}));
13
+ var UserFlags;
14
+ (function (UserFlags) {
15
+ UserFlags[UserFlags["STAFF"] = 1] = "STAFF";
16
+ UserFlags[UserFlags["PARTNER"] = 2] = "PARTNER";
17
+ UserFlags[UserFlags["HYPESQUAD"] = 4] = "HYPESQUAD";
18
+ UserFlags[UserFlags["BUG_HUNTER_LEVEL_1"] = 8] = "BUG_HUNTER_LEVEL_1";
19
+ UserFlags[UserFlags["HYPESQUAD_ONLINE_HOUSE_1"] = 64] = "HYPESQUAD_ONLINE_HOUSE_1";
20
+ UserFlags[UserFlags["HYPESQUAD_ONLINE_HOUSE_2"] = 128] = "HYPESQUAD_ONLINE_HOUSE_2";
21
+ UserFlags[UserFlags["HYPESQUAD_ONLINE_HOUSE_3"] = 256] = "HYPESQUAD_ONLINE_HOUSE_3";
22
+ UserFlags[UserFlags["PREMIUM_EARLY_SUPPORTER"] = 512] = "PREMIUM_EARLY_SUPPORTER";
23
+ UserFlags[UserFlags["TEAM_PSEUDO_USER"] = 1024] = "TEAM_PSEUDO_USER";
24
+ UserFlags[UserFlags["BUG_HUNTER_LEVEL_2"] = 16384] = "BUG_HUNTER_LEVEL_2";
25
+ UserFlags[UserFlags["VERIFIED_BOT"] = 65536] = "VERIFIED_BOT";
26
+ UserFlags[UserFlags["VERIFIED_DEVELOPER"] = 131072] = "VERIFIED_DEVELOPER";
27
+ UserFlags[UserFlags["CERTIFIED_MODERATOR"] = 262144] = "CERTIFIED_MODERATOR";
28
+ UserFlags[UserFlags["BOT_HTTP_INTERACTIONS"] = 524288] = "BOT_HTTP_INTERACTIONS";
29
+ UserFlags[UserFlags["ACTIVE_DEVELOPER"] = 4194304] = "ACTIVE_DEVELOPER";
30
+ })(UserFlags || (exports.UserFlags = UserFlags = {}));
@@ -42,6 +42,11 @@ export declare class HieuxyzRPC {
42
42
  */
43
43
  private resolvedAssetsCache;
44
44
  private renewalInterval;
45
+ /**
46
+ * Cache for Application Assets (Bot Assets).
47
+ * Map<ApplicationID, Map<AssetName, AssetID>>
48
+ */
49
+ private applicationAssetsCache;
45
50
  constructor(websocket: DiscordWebSocket, imageService: ImageService);
46
51
  /**
47
52
  * Returns the URL of the large image asset, if available.
@@ -99,14 +104,14 @@ export declare class HieuxyzRPC {
99
104
  setParty(currentSize: number, maxSize: number): this;
100
105
  /**
101
106
  * Set large image and its caption text.
102
- * @param {string | RpcImage} source - Image source (URL, asset key, or RpcImage object).
107
+ * @param {string | RpcImage} source - Image source (URL, asset key, Asset Name or RpcImage object).
103
108
  * @param {string} [text] - Text displayed when hovering over image.
104
109
  * @returns {this}
105
110
  */
106
111
  setLargeImage(source: string | RpcImage, text?: string): this;
107
112
  /**
108
113
  * Set the small image and its caption text.
109
- * @param {string | RpcImage} source - Image source (URL, asset key, or RpcImage object).
114
+ * @param {string | RpcImage} source - Image source (URL, asset key, Asset Name or RpcImage object).
110
115
  * @param {string} [text] - Text displayed when hovering over image.
111
116
  * @returns {this}
112
117
  */
@@ -176,6 +181,10 @@ export declare class HieuxyzRPC {
176
181
  * Stops the background process that checks for asset renewal.
177
182
  */
178
183
  stopBackgroundRenewal(): void;
184
+ /**
185
+ * Ensure assets are fetched for the current application ID.
186
+ */
187
+ private ensureAppAssetsLoaded;
179
188
  private resolveImage;
180
189
  private buildActivity;
181
190
  /**
@@ -35,6 +35,11 @@ class HieuxyzRPC {
35
35
  */
36
36
  resolvedAssetsCache = new Map();
37
37
  renewalInterval = null;
38
+ /**
39
+ * Cache for Application Assets (Bot Assets).
40
+ * Map<ApplicationID, Map<AssetName, AssetID>>
41
+ */
42
+ applicationAssetsCache = new Map();
38
43
  constructor(websocket, imageService) {
39
44
  this.websocket = websocket;
40
45
  this.imageService = imageService;
@@ -77,7 +82,6 @@ class HieuxyzRPC {
77
82
  if (assetKey.startsWith('twitch:')) {
78
83
  return `https://static-cdn.jtvnw.net/previews-ttv/live_user_${assetKey.substring(7)}.png`;
79
84
  }
80
- // For assets uploaded to a Discord application
81
85
  if (this.applicationId && !assetKey.startsWith('http')) {
82
86
  return `https://cdn.discordapp.com/app-assets/${this.applicationId}/${assetKey}.png`;
83
87
  }
@@ -106,6 +110,9 @@ class HieuxyzRPC {
106
110
  if (source.startsWith('attachments/') || source.startsWith('external/')) {
107
111
  return new RpcImage_1.DiscordImage(source);
108
112
  }
113
+ if (/^[a-zA-Z0-9_]+$/.test(source) && !/^\d{17,20}$/.test(source)) {
114
+ return new RpcImage_1.ApplicationImage(source);
115
+ }
109
116
  return new RpcImage_1.RawImage(source);
110
117
  }
111
118
  cleanupNulls(obj) {
@@ -185,7 +192,7 @@ class HieuxyzRPC {
185
192
  }
186
193
  /**
187
194
  * Set large image and its caption text.
188
- * @param {string | RpcImage} source - Image source (URL, asset key, or RpcImage object).
195
+ * @param {string | RpcImage} source - Image source (URL, asset key, Asset Name or RpcImage object).
189
196
  * @param {string} [text] - Text displayed when hovering over image.
190
197
  * @returns {this}
191
198
  */
@@ -197,7 +204,7 @@ class HieuxyzRPC {
197
204
  }
198
205
  /**
199
206
  * Set the small image and its caption text.
200
- * @param {string | RpcImage} source - Image source (URL, asset key, or RpcImage object).
207
+ * @param {string | RpcImage} source - Image source (URL, asset key, Asset Name or RpcImage object).
201
208
  * @param {string} [text] - Text displayed when hovering over image.
202
209
  * @returns {this}
203
210
  */
@@ -252,8 +259,8 @@ class HieuxyzRPC {
252
259
  * @returns {this}
253
260
  */
254
261
  setApplicationId(id) {
255
- if (!/^\d{18,19}$/.test(id)) {
256
- throw new Error('The app ID must be an 18 or 19 digit number.');
262
+ if (!/^\d{17,20}$/.test(id)) {
263
+ throw new Error('The app ID must be a valid number string (17-20 digits).');
257
264
  }
258
265
  this.applicationId = id;
259
266
  return this;
@@ -375,16 +382,45 @@ class HieuxyzRPC {
375
382
  logger_1.logger.info('Stopped background asset renewal process.');
376
383
  }
377
384
  }
385
+ /**
386
+ * Ensure assets are fetched for the current application ID.
387
+ */
388
+ async ensureAppAssetsLoaded() {
389
+ if (!this.applicationAssetsCache.has(this.applicationId)) {
390
+ logger_1.logger.info(`Fetching assets for Application ID: ${this.applicationId}...`);
391
+ const assets = await this.imageService.fetchApplicationAssets(this.applicationId);
392
+ const assetMap = new Map();
393
+ assets.forEach((asset) => {
394
+ assetMap.set(asset.name, asset.id);
395
+ });
396
+ this.applicationAssetsCache.set(this.applicationId, assetMap);
397
+ logger_1.logger.info(`Loaded ${assets.length} assets for Application ID: ${this.applicationId}.`);
398
+ }
399
+ }
378
400
  async resolveImage(image) {
379
401
  if (!image)
380
402
  return undefined;
381
403
  const cacheKey = image.getCacheKey();
404
+ if (cacheKey.startsWith('app_asset:')) {
405
+ await this.ensureAppAssetsLoaded();
406
+ const assetName = cacheKey.substring('app_asset:'.length);
407
+ const appAssets = this.applicationAssetsCache.get(this.applicationId);
408
+ const assetId = appAssets?.get(assetName);
409
+ if (!assetId) {
410
+ logger_1.logger.warn(`Asset with name "${assetName}" not found for Application ID ${this.applicationId}.`);
411
+ return undefined;
412
+ }
413
+ return assetId;
414
+ }
382
415
  const cachedAsset = this.resolvedAssetsCache.get(cacheKey);
383
416
  if (cachedAsset) {
384
417
  return await this.renewAssetIfNeeded(cacheKey, cachedAsset);
385
418
  }
386
419
  const resolvedAsset = await image.resolve(this.imageService);
387
420
  if (resolvedAsset) {
421
+ if (resolvedAsset.startsWith('app_asset:')) {
422
+ return this.resolveImage(image);
423
+ }
388
424
  this.resolvedAssetsCache.set(cacheKey, resolvedAsset);
389
425
  }
390
426
  return resolvedAsset;
@@ -1,3 +1,8 @@
1
+ export interface DiscordAsset {
2
+ id: string;
3
+ type: number;
4
+ name: string;
5
+ }
1
6
  /**
2
7
  * A service to handle external image proxying and local image uploading.
3
8
  * Interact with a backend API service to manage image assets.
@@ -28,4 +33,10 @@ export declare class ImageService {
28
33
  * @returns {Promise<string | undefined>} The new asset key or undefined if it failed.
29
34
  */
30
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[]>;
31
42
  }
@@ -121,5 +121,22 @@ class ImageService {
121
121
  }
122
122
  return undefined;
123
123
  }
124
+ /**
125
+ * Fetch all assets for a specific Discord Application.
126
+ * @param applicationId - The ID of the application.
127
+ * @returns {Promise<DiscordAsset[]>} List of assets (id, name, type).
128
+ */
129
+ async fetchApplicationAssets(applicationId) {
130
+ try {
131
+ const url = `https://discord.com/api/v9/oauth2/applications/${applicationId}/assets`;
132
+ const response = await axios_1.default.get(url);
133
+ return response.data;
134
+ }
135
+ catch (error) {
136
+ const err = error;
137
+ logger_1.logger.error(`Failed to fetch assets for application ${applicationId}: ${err.message}. Ensure the App ID is correct.`);
138
+ return [];
139
+ }
140
+ }
124
141
  }
125
142
  exports.ImageService = ImageService;
@@ -54,3 +54,16 @@ export declare class RawImage extends RpcImage {
54
54
  resolve(__imageService: ImageService): Promise<string | undefined>;
55
55
  getCacheKey(): string;
56
56
  }
57
+ /**
58
+ * Represents an asset uploaded to the Discord Application (Bot Assets).
59
+ * It will resolve the asset ID by matching the asset name.
60
+ */
61
+ export declare class ApplicationImage extends RpcImage {
62
+ assetName: string;
63
+ /**
64
+ * @param assetName The name of the asset as defined in the Discord Developer Portal.
65
+ */
66
+ constructor(assetName: string);
67
+ resolve(__imageService: ImageService): Promise<string | undefined>;
68
+ getCacheKey(): string;
69
+ }
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.RawImage = exports.LocalImage = exports.ExternalImage = exports.DiscordImage = exports.RpcImage = void 0;
36
+ exports.ApplicationImage = exports.RawImage = exports.LocalImage = exports.ExternalImage = exports.DiscordImage = exports.RpcImage = void 0;
37
37
  const path = __importStar(require("path"));
38
38
  /**
39
39
  * Base abstract class for all RPC image types.
@@ -113,3 +113,24 @@ class RawImage extends RpcImage {
113
113
  }
114
114
  }
115
115
  exports.RawImage = RawImage;
116
+ /**
117
+ * Represents an asset uploaded to the Discord Application (Bot Assets).
118
+ * It will resolve the asset ID by matching the asset name.
119
+ */
120
+ class ApplicationImage extends RpcImage {
121
+ assetName;
122
+ /**
123
+ * @param assetName The name of the asset as defined in the Discord Developer Portal.
124
+ */
125
+ constructor(assetName) {
126
+ super();
127
+ this.assetName = assetName;
128
+ }
129
+ async resolve(__imageService) {
130
+ return `app_asset:${this.assetName}`;
131
+ }
132
+ getCacheKey() {
133
+ return `app_asset:${this.assetName}`;
134
+ }
135
+ }
136
+ exports.ApplicationImage = ApplicationImage;
package/dist/index.d.ts CHANGED
@@ -2,6 +2,6 @@ export { Client, ClientOptions } from './hieuxyz/Client';
2
2
  export { DiscordWebSocket } from './hieuxyz/gateway/DiscordWebSocket';
3
3
  export { HieuxyzRPC } from './hieuxyz/rpc/HieuxyzRPC';
4
4
  export { ImageService } from './hieuxyz/rpc/ImageService';
5
- export { RpcImage, DiscordImage, ExternalImage, LocalImage, RawImage } from './hieuxyz/rpc/RpcImage';
5
+ export { RpcImage, DiscordImage, ExternalImage, LocalImage, RawImage, ApplicationImage } from './hieuxyz/rpc/RpcImage';
6
6
  export { logger } from './hieuxyz/utils/logger';
7
7
  export * from './hieuxyz/gateway/entities/types';
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.logger = exports.RawImage = exports.LocalImage = exports.ExternalImage = exports.DiscordImage = exports.RpcImage = exports.ImageService = exports.HieuxyzRPC = exports.DiscordWebSocket = exports.Client = void 0;
17
+ exports.logger = exports.ApplicationImage = exports.RawImage = exports.LocalImage = exports.ExternalImage = exports.DiscordImage = exports.RpcImage = exports.ImageService = exports.HieuxyzRPC = exports.DiscordWebSocket = exports.Client = void 0;
18
18
  var Client_1 = require("./hieuxyz/Client");
19
19
  Object.defineProperty(exports, "Client", { enumerable: true, get: function () { return Client_1.Client; } });
20
20
  var DiscordWebSocket_1 = require("./hieuxyz/gateway/DiscordWebSocket");
@@ -29,6 +29,7 @@ Object.defineProperty(exports, "DiscordImage", { enumerable: true, get: function
29
29
  Object.defineProperty(exports, "ExternalImage", { enumerable: true, get: function () { return RpcImage_1.ExternalImage; } });
30
30
  Object.defineProperty(exports, "LocalImage", { enumerable: true, get: function () { return RpcImage_1.LocalImage; } });
31
31
  Object.defineProperty(exports, "RawImage", { enumerable: true, get: function () { return RpcImage_1.RawImage; } });
32
+ Object.defineProperty(exports, "ApplicationImage", { enumerable: true, get: function () { return RpcImage_1.ApplicationImage; } });
32
33
  var logger_1 = require("./hieuxyz/utils/logger");
33
34
  Object.defineProperty(exports, "logger", { enumerable: true, get: function () { return logger_1.logger; } });
34
35
  __exportStar(require("./hieuxyz/gateway/entities/types"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hieuxyz/rpc",
3
- "version": "1.1.11",
3
+ "version": "1.2.0",
4
4
  "description": "A Discord Rich Presence library for Node.js",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -32,18 +32,18 @@
32
32
  },
33
33
  "homepage": "https://github.com/hieuxyz00/hieuxyz_rpc#readme",
34
34
  "dependencies": {
35
- "axios": "^1.12.2",
35
+ "axios": "^1.13.2",
36
36
  "ws": "^8.18.3"
37
37
  },
38
38
  "devDependencies": {
39
- "@types/node": "^24.6.2",
39
+ "@types/node": "^25.0.3",
40
40
  "@types/ws": "^8.18.1",
41
- "eslint": "^9.36.0",
41
+ "eslint": "^9.39.2",
42
42
  "eslint-config-prettier": "^10.1.8",
43
43
  "eslint-plugin-prettier": "^5.5.4",
44
- "prettier": "^3.6.2",
44
+ "prettier": "^3.7.4",
45
45
  "ts-node": "^10.9.2",
46
46
  "typescript": "^5.9.3",
47
- "typescript-eslint": "^8.45.0"
47
+ "typescript-eslint": "^8.51.0"
48
48
  }
49
49
  }