Skip to content

Commit c0841b5

Browse files
committed
Added DiscordCdnHelper
1 parent 78cc3cb commit c0841b5

File tree

3 files changed

+401
-1
lines changed

3 files changed

+401
-1
lines changed

lib/src/discord_cdn_helper.dart

Lines changed: 366 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,366 @@
1+
import 'package:discord_api/src/models/discord_image_format.dart';
2+
3+
class DiscordCdnHelper {
4+
static final cndUrl = Uri(scheme: "https", host: "cdn.discordapp.com");
5+
6+
/// Available formats : PNG, JPEG, WebP, GIF
7+
static final formatList1 = [
8+
DiscordImageFormat.png,
9+
DiscordImageFormat.jpeg,
10+
DiscordImageFormat.webp,
11+
DiscordImageFormat.gif,
12+
];
13+
14+
/// Available formats : PNG, JPEG, WebP
15+
static final formatList2 = [
16+
DiscordImageFormat.png,
17+
DiscordImageFormat.jpeg,
18+
DiscordImageFormat.webp,
19+
];
20+
21+
/// Available formats : PNG, Lottie
22+
static final formatList3 = [
23+
DiscordImageFormat.png,
24+
DiscordImageFormat.lottie,
25+
];
26+
27+
/// Checks whether or not the int x is a valid size for the Discord CDN query parameter `size`.
28+
///
29+
/// The size is only valid if it's a power of 2,
30+
/// between 16 and 4096.
31+
bool isValidSize(int x) {
32+
return (x >= 16) && (x <= 4096) && ((x & (x - 1)) == 0);
33+
}
34+
35+
/// Returns the CDN URL for a given path, as a String.
36+
///
37+
/// Probably shouldn't be used, as specialized functions exists.
38+
String getString(String path) => getUrl(path).toString();
39+
40+
/// Returns the CDN URL for a given path, as an URI.
41+
///
42+
/// Probably shouldn't be used, as specialized functions exists.
43+
Uri getUrl(String path) => cndUrl.replace(pathSegments: path.split('/'));
44+
45+
/// Tries to create the right URL slug for an emoji, given its emoji_id, in a given format
46+
/// (defaults to PNG), with a given size (defaults to 128).
47+
///
48+
/// Returns null if the format is not available or the size is not valid.
49+
String? getCustomEmoji({
50+
required String emojiId,
51+
DiscordImageFormat format = DiscordImageFormat.png,
52+
int size = 128,
53+
}) {
54+
if (!formatList1.contains(format) || !isValidSize(size)) {
55+
return null;
56+
}
57+
return getString('/emojis/$emojiId.${format.fileExtension}?size=$size');
58+
}
59+
60+
/// Tries to create the right URL slug for a guild icon, given its guild_id and icon hash,
61+
/// in a given format (defaults to PNG), with a given size (defaults to 128).
62+
///
63+
/// Returns null if the format is not available, if the size is not valid,
64+
/// or if the format is GIF, and the hash doesn't begin with "a_".
65+
String? getGuildIcon({
66+
required String guildId,
67+
required String guildIconHash,
68+
DiscordImageFormat format = DiscordImageFormat.png,
69+
int size = 128,
70+
}) {
71+
if (!formatList1.contains(format) || !isValidSize(size)) {
72+
return null;
73+
}
74+
if (format == DiscordImageFormat.gif && !guildIconHash.startsWith('a_')) {
75+
return null;
76+
}
77+
return getString(
78+
'/icons/$guildId/$guildIconHash.${format.fileExtension}?size=$size');
79+
}
80+
81+
/// Tries to create the right URL slug for a guild splash, given its guild_id and splash hash,
82+
/// in a given format (defaults to PNG), with a given size (defaults to 128).
83+
///
84+
/// Returns null if the format is not available or the size is not valid.
85+
String? getGuildSplash({
86+
required String guildId,
87+
required String guildSplashHash,
88+
DiscordImageFormat format = DiscordImageFormat.png,
89+
int size = 128,
90+
}) {
91+
if (!formatList2.contains(format) || !isValidSize(size)) {
92+
return null;
93+
}
94+
return getString(
95+
'/splashes/$guildId/$guildSplashHash.${format.fileExtension}?size=$size');
96+
}
97+
98+
/// Tries to create the right URL slug for a guild discovery splash, given its guild_id and discovery splash hash,
99+
/// in a given format (defaults to PNG), with a given size (defaults to 128).
100+
///
101+
/// Returns null if the format is not available or the size is not valid.
102+
String? getGuildDiscoverySplash({
103+
required String guildId,
104+
required String guildDiscoverySplashHash,
105+
DiscordImageFormat format = DiscordImageFormat.png,
106+
int size = 128,
107+
}) {
108+
if (!formatList2.contains(format) || !isValidSize(size)) {
109+
return null;
110+
}
111+
return getString(
112+
'/discovery-splashes/$guildId/$guildDiscoverySplashHash.${format.fileExtension}?size=$size');
113+
}
114+
115+
/// Tries to create the right URL slug for a guild banner, given its guild_id and banner hash,
116+
/// in a given format (defaults to PNG), with a given size (defaults to 128).
117+
///
118+
/// Returns null if the format is not available or the size is not valid.
119+
String? getGuildBanner({
120+
required String guildId,
121+
required String guildBannerHash,
122+
DiscordImageFormat format = DiscordImageFormat.png,
123+
int size = 128,
124+
}) {
125+
if (!formatList2.contains(format) || !isValidSize(size)) {
126+
return null;
127+
}
128+
return getString(
129+
'/banners/$guildId/$guildBannerHash.${format.fileExtension}?size=$size');
130+
}
131+
132+
/// Tries to create the right URL slug for a user banner, given its user_id and banner hash,
133+
/// in a given format (defaults to PNG), with a given size (defaults to 128).
134+
///
135+
/// Returns null if the format is not available, if the size is not valid,
136+
/// or if the format is GIF, and the hash doesn't begin with "a_".
137+
String? getUserBanner({
138+
required String userId,
139+
required String userBannerHash,
140+
DiscordImageFormat format = DiscordImageFormat.png,
141+
int size = 128,
142+
}) {
143+
if (!formatList1.contains(format) || !isValidSize(size)) {
144+
return null;
145+
}
146+
if (format == DiscordImageFormat.gif && !userBannerHash.startsWith('a_')) {
147+
return null;
148+
}
149+
return getString(
150+
'/banners/$userId/$userBannerHash.${format.fileExtension}?size=$size');
151+
}
152+
153+
/// Tries to create the right URL slug for the default user avatar of a user,
154+
/// given it's discriminator.
155+
///
156+
/// This endpoint can only give back a PNG image, and ignores the size query parameter.
157+
String? getDefaultUserAvatar(String discriminator) {
158+
final discr = int.tryParse(discriminator);
159+
if (discr != null) {
160+
return getString('/embed/avatars/${discr % 5}.png');
161+
}
162+
return null;
163+
}
164+
165+
/// Tries to create the right URL slug for a user avatar, given its user_id and avatar hash,
166+
/// in a given format (defaults to PNG), with a given size (defaults to 128).
167+
///
168+
/// Returns null if the format is not available, if the size is not valid,
169+
/// or if the format is GIF, and the hash doesn't begin with "a_".
170+
String? getUserAvatar({
171+
required String userId,
172+
required String userAvatarHash,
173+
DiscordImageFormat format = DiscordImageFormat.png,
174+
int size = 128,
175+
}) {
176+
if (!formatList1.contains(format) || !isValidSize(size)) {
177+
return null;
178+
}
179+
if (format == DiscordImageFormat.gif && !userAvatarHash.startsWith('a_')) {
180+
return null;
181+
}
182+
return getString(
183+
'/avatars/$userId/$userAvatarHash.${format.fileExtension}?size=$size');
184+
}
185+
186+
/// Tries to create the right URL slug for a guild member avatar, given its guild_id, user_id
187+
/// and member avatar hash, in a given format (defaults to PNG), with a given size (defaults to 128).
188+
///
189+
/// Returns null if the format is not available, if the size is not valid,
190+
/// or if the format is GIF, and the hash doesn't begin with "a_".
191+
String? getGuildMemberAvatar({
192+
required String guildId,
193+
required String userId,
194+
required String memberAvatarHash,
195+
DiscordImageFormat format = DiscordImageFormat.png,
196+
int size = 128,
197+
}) {
198+
if (!formatList1.contains(format) || !isValidSize(size)) {
199+
return null;
200+
}
201+
if (format == DiscordImageFormat.gif &&
202+
!memberAvatarHash.startsWith('a_')) {
203+
return null;
204+
}
205+
return getString(
206+
'/guilds/$guildId/users/$userId/avatars/$memberAvatarHash.${format.fileExtension}?size=$size');
207+
}
208+
209+
/// Tries to create the right URL slug for an application icon, given its application_id and icon hash,
210+
/// in a given format (defaults to PNG), with a given size (defaults to 128).
211+
///
212+
/// Returns null if the format is not available or the size is not valid.
213+
String? getApplicationIcon({
214+
required String applicationId,
215+
required String applicationIconHash,
216+
DiscordImageFormat format = DiscordImageFormat.png,
217+
int size = 128,
218+
}) {
219+
if (!formatList2.contains(format) || !isValidSize(size)) {
220+
return null;
221+
}
222+
return getString(
223+
'/app-icons/$applicationId/$applicationIconHash.${format.fileExtension}?size=$size');
224+
}
225+
226+
/// Tries to create the right URL slug for an application cover, given its application_id and cover image hash,
227+
/// in a given format (defaults to PNG), with a given size (defaults to 128).
228+
///
229+
/// Returns null if the format is not available or the size is not valid.
230+
String? getApplicationCover({
231+
required String applicationId,
232+
required String applicationCoverHash,
233+
DiscordImageFormat format = DiscordImageFormat.png,
234+
int size = 128,
235+
}) {
236+
if (!formatList2.contains(format) || !isValidSize(size)) {
237+
return null;
238+
}
239+
return getString(
240+
'/app-icons/$applicationId/$applicationCoverHash.${format.fileExtension}?size=$size');
241+
}
242+
243+
/// Tries to create the right URL slug for an application asset, given its application_id and asset hash,
244+
/// in a given format (defaults to PNG), with a given size (defaults to 128).
245+
///
246+
/// Returns null if the format is not available or the size is not valid.
247+
String? getApplicationAsset({
248+
required String applicationId,
249+
required String applicationAssetHash,
250+
DiscordImageFormat format = DiscordImageFormat.png,
251+
int size = 128,
252+
}) {
253+
if (!formatList2.contains(format) || !isValidSize(size)) {
254+
return null;
255+
}
256+
return getString(
257+
'/app-assets/$applicationId/$applicationAssetHash.${format.fileExtension}?size=$size');
258+
}
259+
260+
/// Tries to create the right URL slug for an application's achievement icon,
261+
/// given its application_id, achievement_id and icon hash,
262+
/// in a given format (defaults to PNG), with a given size (defaults to 128).
263+
///
264+
/// Returns null if the format is not available or the size is not valid.
265+
String? getApplicationAchievementIcon({
266+
required String applicationId,
267+
required String achievementId,
268+
required String achievementIconHash,
269+
DiscordImageFormat format = DiscordImageFormat.png,
270+
int size = 128,
271+
}) {
272+
if (!formatList2.contains(format) || !isValidSize(size)) {
273+
return null;
274+
}
275+
return getString(
276+
'/app-assets/$applicationId/achievements/$achievementId/icons/$achievementIconHash.${format.fileExtension}?size=$size');
277+
}
278+
279+
/// Tries to create the right URL slug for a sticker pack banner, given its asset_id,
280+
/// in a given format (defaults to PNG), with a given size (defaults to 128).
281+
///
282+
/// Returns null if the format is not available or the size is not valid.
283+
String? getStickerPackBanner({
284+
required String stickerPackBannerId,
285+
DiscordImageFormat format = DiscordImageFormat.png,
286+
int size = 128,
287+
}) {
288+
if (!formatList2.contains(format) || !isValidSize(size)) {
289+
return null;
290+
}
291+
return getString(
292+
'/app-assets/710982414301790216/store/$stickerPackBannerId.${format.fileExtension}?size=$size');
293+
}
294+
295+
/// Tries to create the right URL slug for a team icon, given its team_id and team icon hash,
296+
/// in a given format (defaults to PNG), with a given size (defaults to 128).
297+
///
298+
/// Returns null if the format is not available or the size is not valid.
299+
String? getTeamIcon({
300+
required String teamId,
301+
required String teamIconHash,
302+
DiscordImageFormat format = DiscordImageFormat.png,
303+
int size = 128,
304+
}) {
305+
if (!formatList2.contains(format) || !isValidSize(size)) {
306+
return null;
307+
}
308+
return getString(
309+
'/team-icons/$teamId/$teamIconHash.${format.fileExtension}?size=$size');
310+
}
311+
312+
/// Tries to create the right URL slug for a sticker, given its sticker_id,
313+
/// in a given format (defaults to PNG).
314+
///
315+
/// The size query parameter is ignored on this endpoint.
316+
///
317+
/// **NOTE**: The sticker will be available as PNG if its `format_type` is `PNG` or `APNG`,
318+
/// and as [Lottie](https://airbnb.io/lottie/#/) if its `format_type`| is `LOTTIE`. This method
319+
/// cannot check for those values, so you should ensure which type you need beforehand.
320+
///
321+
/// Returns null if the format is not available or the size is not valid.
322+
String? getSticker({
323+
required String stickerId,
324+
DiscordImageFormat format = DiscordImageFormat.png,
325+
}) {
326+
if (!formatList3.contains(format)) {
327+
return null;
328+
}
329+
return getString('/stickers/$stickerId.${format.fileExtension}');
330+
}
331+
332+
/// Tries to create the right URL slug for a role icon, given its role_id and role icon hash,
333+
/// in a given format (defaults to PNG), with a given size (defaults to 128).
334+
///
335+
/// Returns null if the format is not available or the size is not valid.
336+
String? getRoleIcon({
337+
required String roleId,
338+
required String roleIconHash,
339+
DiscordImageFormat format = DiscordImageFormat.png,
340+
int size = 128,
341+
}) {
342+
if (!formatList2.contains(format) || !isValidSize(size)) {
343+
return null;
344+
}
345+
return getString(
346+
'/role-icons/$roleId/$roleIconHash.${format.fileExtension}?size=$size');
347+
}
348+
349+
/// Tries to create the right URL slug for a guild scheduled event cover,
350+
/// given its scheduled_event_id and schedule event cover image hash,
351+
/// in a given format (defaults to PNG), with a given size (defaults to 128).
352+
///
353+
/// Returns null if the format is not available or the size is not valid.
354+
String? getGuildScheduledEventCover({
355+
required String scheduledEventId,
356+
required String scheduledEventCoverHash,
357+
DiscordImageFormat format = DiscordImageFormat.png,
358+
int size = 128,
359+
}) {
360+
if (!formatList2.contains(format) || !isValidSize(size)) {
361+
return null;
362+
}
363+
return getString(
364+
'/guild-events/$scheduledEventId/$scheduledEventCoverHash.${format.fileExtension}?size=$size');
365+
}
366+
}

lib/src/discord_client.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import 'dart:async';
22

3+
import 'package:discord_api/src/discord_cdn_helper.dart';
4+
35
import 'exceptions/discord_api_exceptions.dart';
46

57
import 'models/discord_api_scope.dart';
@@ -17,13 +19,16 @@ class DiscordClient {
1719
static const apiPath = "api";
1820
static const versionPath = "v$apiVersion";
1921

20-
static final baseUrl = Uri(scheme: 'https', host: 'discord.com');
22+
static final baseUrl = Uri(scheme: "https", host: "discord.com");
2123

2224
final String redirectUri;
2325
final String clientId;
2426
final String clientSecret;
2527
final DiscordHttpClient discordHttpClient;
2628

29+
/// Contains all methods necessary to build elements from Discord's CDN endpoints.
30+
final DiscordCdnHelper cdn = DiscordCdnHelper();
31+
2732
/// If you need it, you can get the [DiscordDioProvider] from the discord_api_dio_provider package.
2833
/// Otherwise, you'll have to implement `discordHttpClient` yourself.
2934
DiscordClient({

0 commit comments

Comments
 (0)