@unavatar/core 3.7.62 → 3.7.64
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 +153 -92
- package/package.json +5 -1
- package/src/index.js +9 -1
- package/src/providers/index.js +4 -0
- package/src/providers/linkedin.js +8 -0
- package/src/providers/mastodon.js +58 -0
- package/src/util/is-reserved-ip.js +23 -0
package/README.md
CHANGED
|
@@ -14,10 +14,12 @@
|
|
|
14
14
|
- [DuckDuckGo](#duckduckgo)
|
|
15
15
|
- [GitHub](#github)
|
|
16
16
|
- [GitLab](#gitlab)
|
|
17
|
+
- [LinkedIn](#linkedin)
|
|
17
18
|
- [Google](#google)
|
|
18
19
|
- [Gravatar](#gravatar)
|
|
19
20
|
- [Instagram](#instagram)
|
|
20
21
|
- [Microlink](#microlink)
|
|
22
|
+
- [Mastodon](#mastodon)
|
|
21
23
|
- [OnlyFans](#onlyfans)
|
|
22
24
|
- [OpenStreetMap](#openstreetmap)
|
|
23
25
|
- [Patreon](#patreon)
|
|
@@ -43,13 +45,13 @@ Welcome to **unavatar.io**, the ultimate avatar service that offers everything y
|
|
|
43
45
|
|
|
44
46
|
- **Versatile**: A wide range of platforms and services including [TikTok](#tiktok), [Instagram](#instagram), [YouTube](#youtube), [X/Twitter](#xtwitter), [Gravatar](#gravatar), etc., meaning you can rule all of them just querying against unavatar.
|
|
45
47
|
|
|
46
|
-
- **Speed**: Designed to be fast and efficient
|
|
48
|
+
- **Speed**: Designed to be fast and efficient with a 97% cache hit rate, serving 24.3 TB of data across 522M requests.
|
|
47
49
|
|
|
48
50
|
- **Optimize**: All the images are not only compressed on-the-fly to reduce their size and save bandwith, but also optimized to maintain a high-quality ratio. They are ready for immediate use, enhancing the overall optimization of your website or application.
|
|
49
51
|
|
|
50
52
|
- **Integration**: The service seamlessly incorporates into your current applications or websites with ease. We offer straightforward documentation and comprehensive support to ensure a quick and effortless onboarding experience.
|
|
51
53
|
|
|
52
|
-
It's proudly powered by [microlink.io](https://microlink.io), the headless browser API that handles all the heavy lifting behind the scenes to ensure your avatars are always ready.
|
|
54
|
+
It's proudly powered by [microlink.io](https://microlink.io/), the headless browser API that handles all the heavy lifting behind the scenes to ensure your avatars are always ready.
|
|
53
55
|
|
|
54
56
|
## Quick start
|
|
55
57
|
|
|
@@ -65,8 +67,8 @@ Use the `/:provider/:key` format for all lookups. You can read more about availa
|
|
|
65
67
|
|
|
66
68
|
### TTL
|
|
67
69
|
|
|
68
|
-
Type: `number
|
|
69
|
-
Default: `'24h'`<br
|
|
70
|
+
Type: `number` or `string`<br>
|
|
71
|
+
Default: `'24h'`<br>
|
|
70
72
|
Range: from `'1h'` to `'28d'`
|
|
71
73
|
|
|
72
74
|
It determines the maximum quantity of time an avatar is considered fresh.
|
|
@@ -79,7 +81,7 @@ The same resource will continue to be used until reach TTL expiration. After tha
|
|
|
79
81
|
|
|
80
82
|
### Fallback
|
|
81
83
|
|
|
82
|
-
Type: `string
|
|
84
|
+
Type: `string` or `boolean`
|
|
83
85
|
|
|
84
86
|
When it can't be possible to get a user avatar, a fallback image is returned instead, and it can be personalized to fit better with your website or application style.
|
|
85
87
|
|
|
@@ -113,7 +115,7 @@ e.g., [unavatar.io/github/kikobeats?json](https://unavatar.io/github/kikobeats?j
|
|
|
113
115
|
|
|
114
116
|
## Pricing
|
|
115
117
|
|
|
116
|
-
The service is **FREE** for everyone, no registration required, with a daily rate limit of **50 requests** per IP address.
|
|
118
|
+
The service is **FREE** for everyone, no registration required, with a daily rate limit of **50 requests** per IP address.
|
|
117
119
|
|
|
118
120
|
For preventing abusive usage, the service has associated a daily rate limit based on requests IP address.
|
|
119
121
|
|
|
@@ -123,25 +125,23 @@ You can verify for your rate limit state checking the following headers in the r
|
|
|
123
125
|
- `x-rate-limit-remaining`: The number of requests remaining in the current rate limit window.
|
|
124
126
|
- `x-rate-limit-reset`: The time at which the current rate limit window resets in UTC epoch seconds.
|
|
125
127
|
|
|
126
|
-
For higher usage, the
|
|
128
|
+
For higher usage, the plan is a usage-based plan billed monthly that removes rate limits and unlocks custom TTL.
|
|
127
129
|
|
|
128
|
-
Every request has a cost in tokens (
|
|
130
|
+
Every request has a cost in tokens (**\$0.001 per token**) based on the proxy tier needed to resolve the avatar:
|
|
129
131
|
|
|
130
|
-
| Proxy tier | Tokens |
|
|
131
|
-
|
|
132
|
-
| Origin |
|
|
133
|
-
| Datacenter |
|
|
134
|
-
| Residential |
|
|
132
|
+
| Proxy tier | Tokens | Cost |
|
|
133
|
+
|-------------|--------|---------|
|
|
134
|
+
| Origin | 1 | \$0.001 |
|
|
135
|
+
| Datacenter | +2 | \$0.003 |
|
|
136
|
+
| Residential | +4 | \$0.007 |
|
|
135
137
|
|
|
136
138
|
The proxy tier used is returned in the `x-proxy-tier` response header, and the total cost in the `x-unavatar-cost` header.
|
|
137
139
|
|
|
138
|
-
|
|
139
|
-
$ curl -I -H "x-api-key: YOUR_API_KEY" https://unavatar.io/instagram/kikobeats
|
|
140
|
+
$ curl -I -H "x-api-key: YOUR_API_KEY" https://unavatar.io/instagram/kikobeats
|
|
140
141
|
|
|
141
|
-
x-pricing-tier: pro
|
|
142
|
-
x-proxy-tier: origin
|
|
143
|
-
x-unavatar-cost: 1
|
|
144
|
-
```
|
|
142
|
+
x-pricing-tier: pro
|
|
143
|
+
x-proxy-tier: origin
|
|
144
|
+
x-unavatar-cost: 1
|
|
145
145
|
|
|
146
146
|
To upgrade, visit [unavatar.io/checkout](https://unavatar.io/checkout). After completing the payment, you'll receive an API key.
|
|
147
147
|
|
|
@@ -149,19 +149,19 @@ To upgrade, visit [unavatar.io/checkout](https://unavatar.io/checkout). After co
|
|
|
149
149
|
|
|
150
150
|
### Apple Music
|
|
151
151
|
|
|
152
|
-
|
|
152
|
+
Get artwork for any Apple Music artist, album, or song. Search by name or look up directly by numeric Apple Music ID.
|
|
153
153
|
|
|
154
154
|
e.g., [unavatar.io/apple-music/daft%20punk](https://unavatar.io/apple-music/daft%20punk)
|
|
155
155
|
|
|
156
|
-
The endpoint supports
|
|
156
|
+
The endpoint supports explicit type as part of the input.
|
|
157
157
|
|
|
158
158
|
If explicit type is not provided, it searches `artist` and `song` (in that order).
|
|
159
159
|
|
|
160
|
-
Available
|
|
160
|
+
Available URI format inputs:
|
|
161
161
|
|
|
162
162
|
- artist
|
|
163
|
-
-
|
|
164
|
-
-
|
|
163
|
+
- by artist name: [unavatar.io/apple-music/artist:daft%20punk](https://unavatar.io/apple-music/artist:daft%20punk)
|
|
164
|
+
- by numeric artist ID: [unavatar.io/apple-music/artist:5468295](https://unavatar.io/apple-music/artist:5468295)
|
|
165
165
|
- album
|
|
166
166
|
- by album name: [unavatar.io/apple-music/album:discovery](https://unavatar.io/apple-music/album:discovery)
|
|
167
167
|
- by album ID: [unavatar.io/apple-music/album:78691923](https://unavatar.io/apple-music/album:78691923)
|
|
@@ -171,108 +171,155 @@ Available types:
|
|
|
171
171
|
|
|
172
172
|
### Bluesky
|
|
173
173
|
|
|
174
|
-
|
|
174
|
+
Get any Bluesky user's profile picture by their handle. Domain-style handles are supported.
|
|
175
175
|
|
|
176
|
-
|
|
176
|
+
Available inputs:
|
|
177
|
+
|
|
178
|
+
- User handle, e.g., [unavatar.io/bluesky/pfrazee.com](https://unavatar.io/bluesky/pfrazee.com)
|
|
179
|
+
- Domain handle, e.g., [unavatar.io/bluesky/bsky.app](https://unavatar.io/bluesky/bsky.app)
|
|
177
180
|
|
|
178
181
|
### DeviantArt
|
|
179
182
|
|
|
180
|
-
|
|
183
|
+
Get any DeviantArt user's profile picture by their username.
|
|
184
|
+
|
|
185
|
+
Available inputs:
|
|
181
186
|
|
|
182
|
-
e.g., [unavatar.io/deviantart/spyed](https://unavatar.io/deviantart/spyed)
|
|
187
|
+
- Username, e.g., [unavatar.io/deviantart/spyed](https://unavatar.io/deviantart/spyed)
|
|
183
188
|
|
|
184
189
|
### Dribbble
|
|
185
190
|
|
|
186
|
-
|
|
191
|
+
Get any Dribbble designer's profile picture by their username.
|
|
187
192
|
|
|
188
|
-
|
|
193
|
+
Available inputs:
|
|
194
|
+
|
|
195
|
+
- Username, e.g., [unavatar.io/dribbble/omidnikrah](https://unavatar.io/dribbble/omidnikrah)
|
|
189
196
|
|
|
190
197
|
### DuckDuckGo
|
|
191
198
|
|
|
192
|
-
|
|
199
|
+
Get the favicon or logo for any domain via DuckDuckGo's icon service. Useful as a fallback when a domain doesn't expose its favicon directly.
|
|
193
200
|
|
|
194
|
-
|
|
201
|
+
Available inputs:
|
|
202
|
+
|
|
203
|
+
- Domain, e.g., [unavatar.io/duckduckgo/gummibeer.dev](https://unavatar.io/duckduckgo/gummibeer.dev)
|
|
195
204
|
|
|
196
205
|
### GitHub
|
|
197
206
|
|
|
198
|
-
|
|
207
|
+
Get any GitHub user or organization's profile picture by their username.
|
|
208
|
+
|
|
209
|
+
Available inputs:
|
|
199
210
|
|
|
200
|
-
e.g., [unavatar.io/github/mdo](https://unavatar.io/github/mdo)
|
|
211
|
+
- User, e.g., [unavatar.io/github/mdo](https://unavatar.io/github/mdo)
|
|
212
|
+
- Organization, e.g., [unavatar.io/github/vercel](https://unavatar.io/github/vercel)
|
|
201
213
|
|
|
202
214
|
### GitLab
|
|
203
215
|
|
|
204
|
-
|
|
216
|
+
Get any GitLab user or group's profile picture by their username.
|
|
217
|
+
|
|
218
|
+
Available inputs:
|
|
219
|
+
|
|
220
|
+
- User, e.g., [unavatar.io/gitlab/sytses](https://unavatar.io/gitlab/sytses)
|
|
221
|
+
- Group, e.g., [unavatar.io/gitlab/inkscape](https://unavatar.io/gitlab/inkscape)
|
|
205
222
|
|
|
206
|
-
|
|
223
|
+
### LinkedIn
|
|
224
|
+
|
|
225
|
+
Get any LinkedIn user's profile picture by their public profile slug.
|
|
226
|
+
|
|
227
|
+
Available inputs:
|
|
228
|
+
|
|
229
|
+
- Profile slug, e.g., [unavatar.io/linkedin/kikobeats](https://unavatar.io/linkedin/kikobeats)
|
|
207
230
|
|
|
208
231
|
### Google
|
|
209
232
|
|
|
210
|
-
|
|
233
|
+
Get the favicon or logo for any domain using Google's favicon service.
|
|
211
234
|
|
|
212
|
-
|
|
235
|
+
Available inputs:
|
|
236
|
+
|
|
237
|
+
- Domain, e.g., [unavatar.io/google/netflix.com](https://unavatar.io/google/netflix.com)
|
|
213
238
|
|
|
214
239
|
### Gravatar
|
|
215
240
|
|
|
216
|
-
|
|
241
|
+
Get any user's avatar by their email address via Gravatar. The most widely used global avatar service — if your users have a Gravatar set up, this is the fastest way to retrieve it.
|
|
217
242
|
|
|
218
|
-
|
|
243
|
+
Available inputs:
|
|
244
|
+
|
|
245
|
+
- Email address, e.g., [unavatar.io/gravatar/hello@microlink.io](https://unavatar.io/gravatar/hello@microlink.io)
|
|
219
246
|
|
|
220
247
|
### Instagram
|
|
221
248
|
|
|
222
|
-
|
|
249
|
+
Get any Instagram user's profile picture by their username. No authentication or API tokens needed — just pass the username.
|
|
250
|
+
|
|
251
|
+
Available inputs:
|
|
223
252
|
|
|
224
|
-
e.g., [unavatar.io/instagram/willsmith](https://unavatar.io/instagram/willsmith)
|
|
253
|
+
- Username, e.g., [unavatar.io/instagram/willsmith](https://unavatar.io/instagram/willsmith)
|
|
225
254
|
|
|
226
255
|
### Microlink
|
|
227
256
|
|
|
228
|
-
|
|
257
|
+
Extract the logo or representative image from any URL. The page is rendered and the best available image is selected — useful for getting brand logos from any website.
|
|
258
|
+
|
|
259
|
+
Available inputs:
|
|
260
|
+
|
|
261
|
+
- Domain, e.g., [unavatar.io/microlink/microlink.io](https://unavatar.io/microlink/microlink.io)
|
|
229
262
|
|
|
230
|
-
|
|
263
|
+
### Mastodon
|
|
264
|
+
|
|
265
|
+
Get any Mastodon user's profile picture from any instance using the public account lookup API. Pass the handle as `user@server` so the account resolves on the correct home instance.
|
|
266
|
+
|
|
267
|
+
Available inputs:
|
|
268
|
+
|
|
269
|
+
- user@server, e.g., [unavatar.io/mastodon/kpwags@hachyderm.io](https://unavatar.io/mastodon/kpwags@hachyderm.io)
|
|
231
270
|
|
|
232
271
|
### OnlyFans
|
|
233
272
|
|
|
234
|
-
|
|
273
|
+
Get any OnlyFans creator's profile picture by their username.
|
|
235
274
|
|
|
236
|
-
|
|
275
|
+
Available inputs:
|
|
276
|
+
|
|
277
|
+
- Username, e.g., [unavatar.io/onlyfans/amandaribas](https://unavatar.io/onlyfans/amandaribas)
|
|
237
278
|
|
|
238
279
|
### OpenStreetMap
|
|
239
280
|
|
|
240
|
-
|
|
281
|
+
Get any OpenStreetMap contributor's profile picture. Accepts either a numeric user ID or a username.
|
|
241
282
|
|
|
242
|
-
|
|
283
|
+
Available inputs:
|
|
243
284
|
|
|
244
285
|
- Numeric user ID, e.g., [unavatar.io/openstreetmap/98672](https://unavatar.io/openstreetmap/98672)
|
|
245
|
-
- Username e.g., [unavatar.io/openstreetmap/Terence%20Eden](https://unavatar.io/openstreetmap/Terence%20Eden)
|
|
286
|
+
- Username, e.g., [unavatar.io/openstreetmap/Terence%20Eden](https://unavatar.io/openstreetmap/Terence%20Eden)
|
|
246
287
|
|
|
247
288
|
### Patreon
|
|
248
289
|
|
|
249
|
-
|
|
290
|
+
Get any Patreon creator's profile picture by their username.
|
|
250
291
|
|
|
251
|
-
|
|
292
|
+
Available inputs:
|
|
293
|
+
|
|
294
|
+
- Username, e.g., [unavatar.io/patreon/kikobeats](https://unavatar.io/patreon/kikobeats)
|
|
252
295
|
|
|
253
296
|
### Reddit
|
|
254
297
|
|
|
255
|
-
|
|
298
|
+
Get any Reddit user's avatar by their username.
|
|
299
|
+
|
|
300
|
+
Available inputs:
|
|
256
301
|
|
|
257
|
-
e.g., [unavatar.io/reddit/kikobeats](https://unavatar.io/reddit/kikobeats)
|
|
302
|
+
- Username, e.g., [unavatar.io/reddit/kikobeats](https://unavatar.io/reddit/kikobeats)
|
|
258
303
|
|
|
259
304
|
### SoundCloud
|
|
260
305
|
|
|
261
|
-
|
|
306
|
+
Get any SoundCloud artist's profile picture by their username.
|
|
307
|
+
|
|
308
|
+
Available inputs:
|
|
262
309
|
|
|
263
|
-
e.g., [unavatar.io/soundcloud/gorillaz](https://unavatar.io/soundcloud/gorillaz)
|
|
310
|
+
- Username, e.g., [unavatar.io/soundcloud/gorillaz](https://unavatar.io/soundcloud/gorillaz)
|
|
264
311
|
|
|
265
312
|
### Spotify
|
|
266
313
|
|
|
267
|
-
|
|
314
|
+
Get artwork for any Spotify entity — users, artists, albums, playlists, shows, episodes, or tracks. Look up by username or Spotify ID.
|
|
268
315
|
|
|
269
316
|
e.g., [unavatar.io/spotify/kikobeats](https://unavatar.io/spotify/kikobeats)
|
|
270
317
|
|
|
271
|
-
The endpoint supports
|
|
318
|
+
The endpoint supports explicit type as part of the input.
|
|
272
319
|
|
|
273
320
|
If explicit type is not provided, it defaults to `user`.
|
|
274
321
|
|
|
275
|
-
Available
|
|
322
|
+
Available URI format inputs:
|
|
276
323
|
|
|
277
324
|
- `user`: [unavatar.io/spotify/kikobeats](https://unavatar.io/spotify/kikobeats)
|
|
278
325
|
- `artist`: [unavatar.io/spotify/artist:6sFIWsNpZYqbRiDnNOkZCA](https://unavatar.io/spotify/artist:6sFIWsNpZYqbRiDnNOkZCA)
|
|
@@ -284,40 +331,52 @@ Available types:
|
|
|
284
331
|
|
|
285
332
|
### Substack
|
|
286
333
|
|
|
287
|
-
|
|
334
|
+
Get any Substack author's profile picture by their publication username.
|
|
288
335
|
|
|
289
|
-
|
|
336
|
+
Available inputs:
|
|
337
|
+
|
|
338
|
+
- Publication username, e.g., [unavatar.io/substack/bankless](https://unavatar.io/substack/bankless)
|
|
290
339
|
|
|
291
340
|
### Telegram
|
|
292
341
|
|
|
293
|
-
|
|
342
|
+
Get any Telegram user's profile picture by their username.
|
|
343
|
+
|
|
344
|
+
Available inputs:
|
|
294
345
|
|
|
295
|
-
e.g., [unavatar.io/telegram/drsdavidsoft](https://unavatar.io/telegram/drsdavidsoft)
|
|
346
|
+
- Username, e.g., [unavatar.io/telegram/drsdavidsoft](https://unavatar.io/telegram/drsdavidsoft)
|
|
296
347
|
|
|
297
348
|
### TikTok
|
|
298
349
|
|
|
299
|
-
|
|
350
|
+
Get any TikTok user's profile picture by their username. No authentication or API tokens needed — just pass the username.
|
|
300
351
|
|
|
301
|
-
|
|
352
|
+
Available inputs:
|
|
353
|
+
|
|
354
|
+
- Username, e.g., [unavatar.io/tiktok/carlosazaustre](https://unavatar.io/tiktok/carlosazaustre)
|
|
302
355
|
|
|
303
356
|
### Twitch
|
|
304
357
|
|
|
305
|
-
|
|
358
|
+
Get any Twitch streamer's profile picture by their username.
|
|
306
359
|
|
|
307
|
-
|
|
360
|
+
Available inputs:
|
|
361
|
+
|
|
362
|
+
- Username, e.g., [unavatar.io/twitch/midudev](https://unavatar.io/twitch/midudev)
|
|
308
363
|
|
|
309
364
|
### Vimeo
|
|
310
365
|
|
|
311
|
-
|
|
366
|
+
Get any Vimeo user's profile picture by their username.
|
|
367
|
+
|
|
368
|
+
Available inputs:
|
|
312
369
|
|
|
313
|
-
e.g., [unavatar.io/vimeo/staff](https://unavatar.io/vimeo/staff)
|
|
370
|
+
- Username, e.g., [unavatar.io/vimeo/staff](https://unavatar.io/vimeo/staff)
|
|
314
371
|
|
|
315
372
|
### WhatsApp
|
|
316
373
|
|
|
317
|
-
|
|
374
|
+
Get the profile picture for a WhatsApp phone number, channel, chat, or group.
|
|
318
375
|
|
|
319
376
|
The input supports a URI format `type:id`. When no type is provided, it defaults to `phone`.
|
|
320
377
|
|
|
378
|
+
Available URI format inputs:
|
|
379
|
+
|
|
321
380
|
- `phone` (default): [unavatar.io/whatsapp/34612345678](https://unavatar.io/whatsapp/34612345678)
|
|
322
381
|
- `channel`: [unavatar.io/whatsapp/channel:0029VaABC1234abcDEF56789](https://unavatar.io/whatsapp/channel:0029VaABC1234abcDEF56789)
|
|
323
382
|
- `chat`: [unavatar.io/whatsapp/chat:ABC1234DEFghi](https://unavatar.io/whatsapp/chat:ABC1234DEFghi)
|
|
@@ -325,13 +384,15 @@ The input supports a URI format `type:id`. When no type is provided, it defaults
|
|
|
325
384
|
|
|
326
385
|
### X/Twitter
|
|
327
386
|
|
|
328
|
-
|
|
387
|
+
Get any X (formerly Twitter) user's profile picture by their username.
|
|
388
|
+
|
|
389
|
+
Available inputs:
|
|
329
390
|
|
|
330
|
-
e.g., [unavatar.io/x/kikobeats](https://unavatar.io/x/kikobeats)
|
|
391
|
+
- Username, e.g., [unavatar.io/x/kikobeats](https://unavatar.io/x/kikobeats)
|
|
331
392
|
|
|
332
393
|
### YouTube
|
|
333
394
|
|
|
334
|
-
|
|
395
|
+
Get any YouTube channel's thumbnail by their handle, legacy username, or channel ID.
|
|
335
396
|
|
|
336
397
|
e.g., [unavatar.io/youtube/casey](https://unavatar.io/youtube/casey)
|
|
337
398
|
|
|
@@ -352,29 +413,29 @@ However, you can get a [json](#json) as response payload.
|
|
|
352
413
|
|
|
353
414
|
When an endpoint returns JSON, the shape is predictable so you can parse it reliably in your app:
|
|
354
415
|
|
|
355
|
-
| Field
|
|
356
|
-
|
|
357
|
-
| `status`
|
|
358
|
-
| `message` | `string`
|
|
359
|
-
| `data`
|
|
360
|
-
| `code`
|
|
361
|
-
| `more`
|
|
362
|
-
| `report`
|
|
416
|
+
| Field | Type | Present in | Description |
|
|
417
|
+
|----|----|----|----|
|
|
418
|
+
| `status` | `string` | all JSON responses | One of: `success`, `fail`, `error`. |
|
|
419
|
+
| `message` | `string` | all JSON responses | Human-readable summary for display/logging. |
|
|
420
|
+
| `data` | `object` | `success` | Response payload for successful requests. |
|
|
421
|
+
| `code` | `string` | `fail`, `error` | Stable machine-readable error code. |
|
|
422
|
+
| `more` | `string (URL)` | most `fail`/`error` responses | Documentation URL with troubleshooting details. |
|
|
423
|
+
| `report` | `string` | some `error` responses | Support contact channel (for example `mailto:`). |
|
|
363
424
|
|
|
364
425
|
## Response Headers
|
|
365
426
|
|
|
366
427
|
These headers help you understand pricing, limits, and request diagnostics.
|
|
367
428
|
|
|
368
|
-
| Header
|
|
369
|
-
|
|
370
|
-
| `x-pricing-tier`
|
|
371
|
-
| `x-timestamp`
|
|
372
|
-
| `x-unavatar-cost`
|
|
373
|
-
| `x-proxy-tier`
|
|
374
|
-
| `x-rate-limit-limit`
|
|
375
|
-
| `x-rate-limit-remaining` | Remaining requests in current window (free tier only)
|
|
376
|
-
| `x-rate-limit-reset`
|
|
377
|
-
| `retry-after`
|
|
429
|
+
| Header | Purpose |
|
|
430
|
+
|----|----|
|
|
431
|
+
| `x-pricing-tier` | `free` or `pro` — the plan used for this request |
|
|
432
|
+
| `x-timestamp` | Server timestamp when request was received |
|
|
433
|
+
| `x-unavatar-cost` | Token cost of the request (avatar routes only) |
|
|
434
|
+
| `x-proxy-tier` | Proxy tier used: `origin`, `datacenter`, or `residential` |
|
|
435
|
+
| `x-rate-limit-limit` | Maximum requests allowed per window (free tier only) |
|
|
436
|
+
| `x-rate-limit-remaining` | Remaining requests in current window (free tier only) |
|
|
437
|
+
| `x-rate-limit-reset` | UTC epoch seconds when window resets (free tier only) |
|
|
438
|
+
| `retry-after` | Seconds until rate limit resets (only on 429 responses) |
|
|
378
439
|
|
|
379
440
|
## Response Errors
|
|
380
441
|
|
|
@@ -389,7 +450,7 @@ Expected errors are known operational cases returned with stable codes.
|
|
|
389
450
|
- `report` (when present) indicates how to contact support for server errors.
|
|
390
451
|
|
|
391
452
|
| HTTP | Code | Typical trigger |
|
|
392
|
-
|
|
453
|
+
|------|----------------------|---------------------------------------------|
|
|
393
454
|
| 400 | `ESESSIONID` | Missing `session_id` in `/checkout/success` |
|
|
394
455
|
| 400 | `ESESSION` | Checkout session not paid or not found |
|
|
395
456
|
| 400 | `ESIGNATURE` | Missing `stripe-signature` header |
|
|
@@ -413,4 +474,4 @@ Expected errors are known operational cases returned with stable codes.
|
|
|
413
474
|
|
|
414
475
|
## Contact
|
|
415
476
|
|
|
416
|
-
If you have any suggestion or bug to report, please contact to ust mailing to hello@unavatar.io.
|
|
477
|
+
If you have any suggestion or bug to report, please contact to ust mailing to [hello@unavatar.io](mailto:hello@unavatar.io).
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@unavatar/core",
|
|
3
3
|
"description": "Get unified user avatar from social networks, including Instagram, SoundCloud, Telegram, Twitter, YouTube & more.",
|
|
4
4
|
"homepage": "https://unavatar.io",
|
|
5
|
-
"version": "3.7.
|
|
5
|
+
"version": "3.7.64",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/index.js",
|
|
@@ -34,6 +34,10 @@
|
|
|
34
34
|
"name": "Alexander Schlindwein",
|
|
35
35
|
"email": "alexander.schlindwein@hotmail.de"
|
|
36
36
|
},
|
|
37
|
+
{
|
|
38
|
+
"name": "Wes Bos",
|
|
39
|
+
"email": "wesbos@gmail.com"
|
|
40
|
+
},
|
|
37
41
|
{
|
|
38
42
|
"name": "Zadkiel",
|
|
39
43
|
"email": "hello@zadkiel.fr"
|
package/src/index.js
CHANGED
|
@@ -15,6 +15,7 @@ module.exports = ({ constants: userConstants, redis, onFetchHTML } = {}) => {
|
|
|
15
15
|
const { createMultiCache, createRedisCache } = require('./util/keyv')({ ...constants, redis })
|
|
16
16
|
const cache = require('./util/cache')({ createMultiCache, createRedisCache })
|
|
17
17
|
const cacheableLookup = require('./util/cacheable-lookup')({ ...constants, cache: cache.dnsCache })
|
|
18
|
+
const isReservedIp = require('./util/is-reserved-ip')({ cacheableLookup })
|
|
18
19
|
const got = require('./util/got')({ cacheableLookup })
|
|
19
20
|
const reachableUrl = require('./util/reachable-url')({ got, pingCache: cache.pingCache })
|
|
20
21
|
const createBrowser = require('./util/browserless')(constants)
|
|
@@ -25,7 +26,14 @@ module.exports = ({ constants: userConstants, redis, onFetchHTML } = {}) => {
|
|
|
25
26
|
onFetchHTML
|
|
26
27
|
})
|
|
27
28
|
|
|
28
|
-
const providerCtx = {
|
|
29
|
+
const providerCtx = {
|
|
30
|
+
constants,
|
|
31
|
+
createHtmlProvider,
|
|
32
|
+
getOgImage,
|
|
33
|
+
got,
|
|
34
|
+
isReservedIp,
|
|
35
|
+
itunesSearchCache: cache.itunesSearchCache
|
|
36
|
+
}
|
|
29
37
|
const { providers, providersBy } = require('./providers')(providerCtx)
|
|
30
38
|
|
|
31
39
|
const { auto, getInputType, getAvatar } = require('./avatar/auto')({
|
package/src/providers/index.js
CHANGED
|
@@ -10,6 +10,8 @@ const providersBy = {
|
|
|
10
10
|
'github',
|
|
11
11
|
'gitlab',
|
|
12
12
|
'instagram',
|
|
13
|
+
'linkedin',
|
|
14
|
+
'mastodon',
|
|
13
15
|
'onlyfans',
|
|
14
16
|
'openstreetmap',
|
|
15
17
|
'patreon',
|
|
@@ -39,6 +41,8 @@ module.exports = ctx => {
|
|
|
39
41
|
google: require('./google')(ctx),
|
|
40
42
|
gravatar: require('./gravatar')(ctx),
|
|
41
43
|
instagram: require('./instagram')(ctx),
|
|
44
|
+
linkedin: require('./linkedin')(ctx),
|
|
45
|
+
mastodon: require('./mastodon')(ctx),
|
|
42
46
|
microlink: require('./microlink')(ctx),
|
|
43
47
|
onlyfans: require('./onlyfans')(ctx),
|
|
44
48
|
openstreetmap: require('./openstreetmap')(ctx),
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const isValidServer = server => {
|
|
4
|
+
try {
|
|
5
|
+
const url = new URL(`https://${server}`)
|
|
6
|
+
return (
|
|
7
|
+
Boolean(url.hostname) &&
|
|
8
|
+
url.username === '' &&
|
|
9
|
+
url.password === '' &&
|
|
10
|
+
url.pathname === '/' &&
|
|
11
|
+
url.search === '' &&
|
|
12
|
+
url.hash === '' &&
|
|
13
|
+
url.host.toLowerCase() === server.toLowerCase()
|
|
14
|
+
)
|
|
15
|
+
} catch {
|
|
16
|
+
return false
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const parseMastodonInput = input => {
|
|
21
|
+
if (typeof input !== 'string') return null
|
|
22
|
+
|
|
23
|
+
const cleaned = input.startsWith('@') ? input.slice(1) : input
|
|
24
|
+
const parts = cleaned.split('@')
|
|
25
|
+
if (parts.length !== 2) return null
|
|
26
|
+
|
|
27
|
+
const [username, server] = parts
|
|
28
|
+
if (!username || !server) return null
|
|
29
|
+
if (!isValidServer(server)) return null
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
username,
|
|
33
|
+
server
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = ({ got, isReservedIp }) => {
|
|
38
|
+
const mastodon = async function ({ input }) {
|
|
39
|
+
const parsed = parseMastodonInput(input)
|
|
40
|
+
if (!parsed) return undefined
|
|
41
|
+
|
|
42
|
+
const { username, server } = parsed
|
|
43
|
+
|
|
44
|
+
if (await isReservedIp(server)) return undefined
|
|
45
|
+
|
|
46
|
+
const { body } = await got(
|
|
47
|
+
`https://${server}/api/v1/accounts/lookup?acct=${encodeURIComponent(
|
|
48
|
+
username
|
|
49
|
+
)}`,
|
|
50
|
+
{ responseType: 'json' }
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
return body?.avatar
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
mastodon.parseMastodonInput = parseMastodonInput
|
|
57
|
+
return mastodon
|
|
58
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const ip = require('ipaddr.js')
|
|
4
|
+
|
|
5
|
+
module.exports = ({ cacheableLookup }) => {
|
|
6
|
+
const getIpAddress = async hostname => {
|
|
7
|
+
if (ip.IPv4.isIPv4(hostname)) return hostname
|
|
8
|
+
if (
|
|
9
|
+
hostname.startsWith('[') &&
|
|
10
|
+
hostname.endsWith(']') &&
|
|
11
|
+
ip.IPv6.isIPv6(hostname.slice(1, -1))
|
|
12
|
+
) {
|
|
13
|
+
return hostname.slice(1, -1)
|
|
14
|
+
}
|
|
15
|
+
const { address } = await cacheableLookup.lookupAsync(hostname)
|
|
16
|
+
return address
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return async hostname => {
|
|
20
|
+
const ipAddress = await getIpAddress(hostname)
|
|
21
|
+
return ip.process(ipAddress).range() !== 'unicast'
|
|
22
|
+
}
|
|
23
|
+
}
|