2
2
using System . Collections . Generic ;
3
3
using System . Globalization ;
4
4
using System . IO ;
5
+ using System . Linq ;
5
6
using System . Security . Cryptography ;
6
7
using System . Text ;
7
8
using System . Text . RegularExpressions ;
@@ -66,34 +67,46 @@ await Http.ResiliencePipeline.ExecuteAsync(
66
67
67
68
internal partial class ExportAssetDownloader
68
69
{
69
- private static string GetUrlHash ( string url )
70
+ private const String CHARSET = "0123456789bcdfghjklmnpqrstvwxyz_" ;
71
+
72
+ private static String Base32 ( byte [ ] data )
70
73
{
71
- // Remove signature parameters from Discord CDN URLs to normalize them
72
- static string NormalizeUrl ( string url )
73
- {
74
- var uri = new Uri ( url ) ;
75
- if ( ! string . Equals ( uri . Host , "cdn.discordapp.com" , StringComparison . OrdinalIgnoreCase ) )
76
- return url ;
74
+ var newString = new StringBuilder ( ) ;
75
+ uint accum = 0 ;
76
+ uint bits = 0 ;
77
77
78
- var query = HttpUtility . ParseQueryString ( uri . Query ) ;
79
- query . Remove ( "ex" ) ;
80
- query . Remove ( "is" ) ;
81
- query . Remove ( "hm" ) ;
78
+ foreach ( byte b in data )
79
+ {
80
+ accum <<= 8 ;
81
+ accum |= b ;
82
+ bits += 8 ;
82
83
83
- return uri . GetLeftPart ( UriPartial . Path ) + query ;
84
+ while ( bits > 5 )
85
+ {
86
+ char ch = CHARSET [ ( int ) ( accum & 0x1F ) ] ;
87
+ accum >>= 5 ;
88
+ bits -= 5 ;
89
+ newString . Append ( ch ) ;
90
+ }
91
+ }
92
+ if ( bits != 0 )
93
+ {
94
+ char ch = CHARSET [ ( int ) ( accum & 0x1F ) ] ;
95
+ newString . Append ( ch ) ;
84
96
}
85
97
86
- return SHA256
87
- . HashData ( Encoding . UTF8 . GetBytes ( NormalizeUrl ( url ) ) )
88
- . ToHex ( )
89
- // 5 chars ought to be enough for anybody
90
- . Truncate ( 5 ) ;
98
+ return newString . ToString ( ) ;
91
99
}
92
100
93
- private static string GetFileNameFromUrl ( string url )
101
+ private static string GetUrlHash ( string url )
94
102
{
95
- var urlHash = GetUrlHash ( url ) ;
103
+ var hash = SHA256 . HashData ( Encoding . UTF8 . GetBytes ( url ) ) ;
104
+ // 12 characters of base32 contains about as much entropy as a Discord snowflake
105
+ return Base32 ( hash ) . Truncate ( 12 ) ;
106
+ }
96
107
108
+ private static string AddHashToUrl ( string url , string urlHash )
109
+ {
97
110
// Try to extract the file name from URL
98
111
var fileName = Regex . Match ( url , @".+/([^?]*)" ) . Groups [ 1 ] . Value ;
99
112
@@ -117,4 +130,56 @@ private static string GetFileNameFromUrl(string url)
117
130
fileNameWithoutExtension . Truncate ( 42 ) + '-' + urlHash + fileExtension
118
131
) ;
119
132
}
133
+
134
+ private static string GetFileNameFromUrl ( string url )
135
+ {
136
+ var uri = new Uri ( url ) ;
137
+
138
+ if ( string . Equals ( uri . Host , "cdn.discordapp.com" ) )
139
+ {
140
+ string [ ] split = uri . AbsolutePath . Split ( "/" ) ;
141
+
142
+ // Attachments
143
+ if ( uri . AbsolutePath . StartsWith ( "/attachments/" ) && split . Length == 5 )
144
+ {
145
+ // use the attachment snowflake for attachments
146
+ if ( ulong . TryParse ( split [ 3 ] , out var snowflake ) )
147
+ return AddHashToUrl ( url , snowflake . ToString ( ) ) ;
148
+ }
149
+
150
+ // Emojis
151
+ if (
152
+ uri . AbsolutePath . StartsWith ( "/emojis/" )
153
+ && split . Length == 3
154
+ && split [ 2 ] . Contains ( "." )
155
+ )
156
+ {
157
+ var nameSplit = split [ 2 ] . Split ( "." , 2 ) ;
158
+ if ( ulong . TryParse ( nameSplit [ 0 ] , out var snowflake ) )
159
+ return $ "emoji-discord-{ snowflake } .{ nameSplit [ 1 ] } ";
160
+ }
161
+
162
+ // Avatars
163
+ if ( uri . AbsolutePath . StartsWith ( "/avatars/" ) && split . Length == 4 )
164
+ {
165
+ return $ "avatar-{ split [ 2 ] } -{ GetUrlHash ( url ) } .{ split [ 3 ] . Split ( "." ) . Last ( ) } ";
166
+ }
167
+ }
168
+
169
+ if ( string . Equals ( uri . Host , "cdn.jsdelivr.net" ) )
170
+ {
171
+ string [ ] split = uri . AbsolutePath . Split ( "/" ) ;
172
+
173
+ // twemoji
174
+ if (
175
+ uri . AbsolutePath . StartsWith ( "/gh/twitter/twemoji@latest/assets/svg/" )
176
+ && split . Length == 7
177
+ )
178
+ {
179
+ return $ "emoji-twemoji-{ split [ 6 ] } ";
180
+ }
181
+ }
182
+
183
+ return AddHashToUrl ( url , GetUrlHash ( url ) ) ;
184
+ }
120
185
}
0 commit comments