@unavatar/core 3.8.0 → 3.9.1
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 +106 -74
- package/package.json +2 -2
- package/src/avatar/auto.js +6 -12
- package/src/index.js +14 -8
- package/src/providers/duckduckgo.js +1 -1
- package/src/providers/github.js +1 -1
- package/src/providers/google.js +1 -1
- package/src/providers/gravatar.js +4 -2
- package/src/providers/mastodon.js +1 -1
- package/src/providers/microlink.js +7 -7
- package/src/providers/openstreetmap.js +4 -4
- package/src/util/html-provider.js +3 -4
package/README.md
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|

|
|
2
2
|
|
|
3
|
+
- [Introduction](#introduction)
|
|
3
4
|
- [Quick start](#quick-start)
|
|
4
5
|
- [Query parameters](#query-parameters)
|
|
5
6
|
- [TTL](#ttl)
|
|
@@ -16,13 +17,15 @@
|
|
|
16
17
|
- [GitLab](#gitlab)
|
|
17
18
|
- [LinkedIn](#linkedin)
|
|
18
19
|
- [Google](#google)
|
|
19
|
-
- [Gravatar](#gravatar)
|
|
20
20
|
- [Instagram](#instagram)
|
|
21
|
+
- [Ko-fi](#ko-fi)
|
|
22
|
+
- [Medium](#medium)
|
|
21
23
|
- [Microlink](#microlink)
|
|
22
24
|
- [Mastodon](#mastodon)
|
|
23
25
|
- [OnlyFans](#onlyfans)
|
|
24
26
|
- [OpenStreetMap](#openstreetmap)
|
|
25
27
|
- [Patreon](#patreon)
|
|
28
|
+
- [Printables](#printables)
|
|
26
29
|
- [Reddit](#reddit)
|
|
27
30
|
- [SoundCloud](#soundcloud)
|
|
28
31
|
- [Spotify](#spotify)
|
|
@@ -32,18 +35,17 @@
|
|
|
32
35
|
- [Twitch](#twitch)
|
|
33
36
|
- [Vimeo](#vimeo)
|
|
34
37
|
- [WhatsApp](#whatsapp)
|
|
35
|
-
- [X/Twitter](#xtwitter)
|
|
36
38
|
- [YouTube](#youtube)
|
|
37
39
|
- [Response Format](#response-format)
|
|
38
40
|
- [Response Headers](#response-headers)
|
|
39
|
-
- [Response Errors](#response-errors)
|
|
40
|
-
- [Contact](#contact)
|
|
41
41
|
|
|
42
42
|
---
|
|
43
43
|
|
|
44
|
+
## Introduction
|
|
45
|
+
|
|
44
46
|
Welcome to **unavatar.io**, the ultimate avatar service that offers everything you need to easily retrieve user avatars:
|
|
45
47
|
|
|
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.
|
|
48
|
+
- **Versatile**: A wide range of platforms and services including [TikTok](https://unavatar.io/docs#tiktok), [Instagram](https://unavatar.io/docs#instagram), [YouTube](https://unavatar.io/docs#youtube), [X/Twitter](https://unavatar.io/docs#xtwitter), [Gravatar](https://unavatar.io/docs#gravatar), etc., meaning you can rule all of them just querying against unavatar.
|
|
47
49
|
|
|
48
50
|
- **Speed**: Designed to be fast and efficient with a 97% cache hit rate, serving 24.3 TB of data across 522M requests.
|
|
49
51
|
|
|
@@ -61,14 +63,14 @@ The service is exposed in **unavatar.io** via provider endpoints:
|
|
|
61
63
|
- an **username**: [unavatar.io/github/kikobeats](https://unavatar.io/github/kikobeats)
|
|
62
64
|
- a **domain**: [unavatar.io/google/reddit.com](https://unavatar.io/google/reddit.com)
|
|
63
65
|
|
|
64
|
-
Use the `/:provider/:key` format for all lookups. You can read more about available providers in [providers](#providers).
|
|
66
|
+
Use the `/:provider/:key` format for all lookups. You can read more about available providers in [providers](https://unavatar.io/docs#providers).
|
|
65
67
|
|
|
66
68
|
## Query parameters
|
|
67
69
|
|
|
68
70
|
### TTL
|
|
69
71
|
|
|
70
|
-
Type: `number` or `string
|
|
71
|
-
Default: `'24h'
|
|
72
|
+
Type: `number` or `string`\
|
|
73
|
+
Default: `'24h'`\
|
|
72
74
|
Range: from `'1h'` to `'28d'`
|
|
73
75
|
|
|
74
76
|
It determines the maximum quantity of time an avatar is considered fresh.
|
|
@@ -125,23 +127,25 @@ You can verify for your rate limit state checking the following headers in the r
|
|
|
125
127
|
- `x-rate-limit-remaining`: The number of requests remaining in the current rate limit window.
|
|
126
128
|
- `x-rate-limit-reset`: The time at which the current rate limit window resets in UTC epoch seconds.
|
|
127
129
|
|
|
128
|
-
For higher usage, the plan is a usage-based plan billed monthly that removes rate limits and unlocks custom TTL.
|
|
130
|
+
For higher usage, the **[PRO](https://unavatar.io/checkout)** plan is a usage-based plan billed monthly that removes rate limits and unlocks custom TTL.
|
|
129
131
|
|
|
130
132
|
Every request has a cost in tokens (**\$0.001 per token**) based on the proxy tier needed to resolve the avatar:
|
|
131
133
|
|
|
132
134
|
| Proxy tier | Tokens | Cost |
|
|
133
|
-
|
|
135
|
+
| ----------- | ------ | ------- |
|
|
134
136
|
| Origin | 1 | \$0.001 |
|
|
135
137
|
| Datacenter | +2 | \$0.003 |
|
|
136
138
|
| Residential | +4 | \$0.007 |
|
|
137
139
|
|
|
138
140
|
The proxy tier used is returned in the `x-proxy-tier` response header, and the total cost in the `x-unavatar-cost` header.
|
|
139
141
|
|
|
140
|
-
|
|
142
|
+
``` bash
|
|
143
|
+
$ curl -I -H "x-api-key: YOUR_API_KEY" https://[unavatar.io/instagram/kikobeats](https://unavatar.io/instagram/kikobeats)
|
|
141
144
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
+
x-pricing-tier: pro
|
|
146
|
+
x-proxy-tier: origin
|
|
147
|
+
x-unavatar-cost: 1
|
|
148
|
+
```
|
|
145
149
|
|
|
146
150
|
To upgrade, visit [unavatar.io/checkout](https://unavatar.io/checkout). After completing the payment, you'll receive an API key.
|
|
147
151
|
|
|
@@ -151,7 +155,7 @@ To upgrade, visit [unavatar.io/checkout](https://unavatar.io/checkout). After co
|
|
|
151
155
|
|
|
152
156
|
Get artwork for any Apple Music artist, album, or song. Search by name or look up directly by numeric Apple Music ID.
|
|
153
157
|
|
|
154
|
-
e.g., [unavatar.io/apple-music/daft%20punk](https://unavatar.io/apple-music/daft%20punk)
|
|
158
|
+
e.g., [unavatar.io/apple-music/artist:daft%20punk](https://unavatar.io/apple-music/artist:daft%20punk)
|
|
155
159
|
|
|
156
160
|
The endpoint supports explicit type as part of the input.
|
|
157
161
|
|
|
@@ -184,7 +188,7 @@ Get any DeviantArt user's profile picture by their username.
|
|
|
184
188
|
|
|
185
189
|
Available inputs:
|
|
186
190
|
|
|
187
|
-
-
|
|
191
|
+
- slug, e.g., [unavatar.io/deviantart/spyed](https://unavatar.io/deviantart/spyed)
|
|
188
192
|
|
|
189
193
|
### Dribbble
|
|
190
194
|
|
|
@@ -192,7 +196,7 @@ Get any Dribbble designer's profile picture by their username.
|
|
|
192
196
|
|
|
193
197
|
Available inputs:
|
|
194
198
|
|
|
195
|
-
-
|
|
199
|
+
- slug, e.g., [unavatar.io/dribbble/omidnikrah](https://unavatar.io/dribbble/omidnikrah)
|
|
196
200
|
|
|
197
201
|
### DuckDuckGo
|
|
198
202
|
|
|
@@ -200,7 +204,7 @@ Get the favicon or logo for any domain via DuckDuckGo's icon service. Useful as
|
|
|
200
204
|
|
|
201
205
|
Available inputs:
|
|
202
206
|
|
|
203
|
-
- Domain, e.g., [unavatar.io/duckduckgo/
|
|
207
|
+
- Domain, e.g., [unavatar.io/duckduckgo/microsoft.com](https://unavatar.io/duckduckgo/microsoft.com)
|
|
204
208
|
|
|
205
209
|
### GitHub
|
|
206
210
|
|
|
@@ -213,20 +217,27 @@ Available inputs:
|
|
|
213
217
|
|
|
214
218
|
### GitLab
|
|
215
219
|
|
|
216
|
-
Get any GitLab user or
|
|
220
|
+
Get any GitLab user or organization's profile picture by their username.
|
|
217
221
|
|
|
218
222
|
Available inputs:
|
|
219
223
|
|
|
220
224
|
- User, e.g., [unavatar.io/gitlab/sytses](https://unavatar.io/gitlab/sytses)
|
|
221
|
-
-
|
|
225
|
+
- Organization, e.g., [unavatar.io/gitlab/inkscape](https://unavatar.io/gitlab/inkscape)
|
|
222
226
|
|
|
223
227
|
### LinkedIn
|
|
224
228
|
|
|
225
|
-
Get any LinkedIn user
|
|
229
|
+
Get any LinkedIn user or company profile picture by their public profile slug.
|
|
226
230
|
|
|
227
|
-
|
|
231
|
+
e.g., [unavatar.io/linkedin/user:wesbos](https://unavatar.io/linkedin/user:wesbos)
|
|
228
232
|
|
|
229
|
-
|
|
233
|
+
The input supports a URI format `type:id`.
|
|
234
|
+
|
|
235
|
+
When no type is provided, it defaults to `user` (user profile).
|
|
236
|
+
|
|
237
|
+
Available URI format inputs:
|
|
238
|
+
|
|
239
|
+
- `user` (default): [unavatar.io/linkedin/user:wesbos](https://unavatar.io/linkedin/user:wesbos)
|
|
240
|
+
- `company`: [unavatar.io/linkedin/company:microlinkhq](https://unavatar.io/linkedin/company:microlinkhq)
|
|
230
241
|
|
|
231
242
|
### Google
|
|
232
243
|
|
|
@@ -234,9 +245,7 @@ Get the favicon or logo for any domain using Google's favicon service.
|
|
|
234
245
|
|
|
235
246
|
Available inputs:
|
|
236
247
|
|
|
237
|
-
- Domain, e.g., [unavatar.io/google/
|
|
238
|
-
|
|
239
|
-
### Gravatar
|
|
248
|
+
- Domain, e.g., [unavatar.io/google/stremio.com](https://unavatar.io/google/stremio.com)
|
|
240
249
|
|
|
241
250
|
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.
|
|
242
251
|
|
|
@@ -250,7 +259,23 @@ Get any Instagram user's profile picture by their username. No authentication or
|
|
|
250
259
|
|
|
251
260
|
Available inputs:
|
|
252
261
|
|
|
253
|
-
-
|
|
262
|
+
- slug, e.g., [unavatar.io/instagram/willsmith](https://unavatar.io/instagram/willsmith)
|
|
263
|
+
|
|
264
|
+
### Ko-fi
|
|
265
|
+
|
|
266
|
+
Get any Ko-fi page's profile picture by their public creator page slug.
|
|
267
|
+
|
|
268
|
+
Available inputs:
|
|
269
|
+
|
|
270
|
+
- Page slug, e.g., [unavatar.io/ko-fi/geekshock](https://unavatar.io/ko-fi/geekshock)
|
|
271
|
+
|
|
272
|
+
### Medium
|
|
273
|
+
|
|
274
|
+
Get any Medium author's profile picture by their username.
|
|
275
|
+
|
|
276
|
+
Available inputs:
|
|
277
|
+
|
|
278
|
+
- slug, e.g., [unavatar.io/medium/juancalmaraz](https://unavatar.io/medium/juancalmaraz)
|
|
254
279
|
|
|
255
280
|
### Microlink
|
|
256
281
|
|
|
@@ -274,7 +299,7 @@ Get any OnlyFans creator's profile picture by their username.
|
|
|
274
299
|
|
|
275
300
|
Available inputs:
|
|
276
301
|
|
|
277
|
-
-
|
|
302
|
+
- slug, e.g., [unavatar.io/onlyfans/amandaribas](https://unavatar.io/onlyfans/amandaribas)
|
|
278
303
|
|
|
279
304
|
### OpenStreetMap
|
|
280
305
|
|
|
@@ -283,7 +308,7 @@ Get any OpenStreetMap contributor's profile picture. Accepts either a numeric us
|
|
|
283
308
|
Available inputs:
|
|
284
309
|
|
|
285
310
|
- Numeric user ID, e.g., [unavatar.io/openstreetmap/98672](https://unavatar.io/openstreetmap/98672)
|
|
286
|
-
-
|
|
311
|
+
- slug, e.g., [unavatar.io/openstreetmap/Terence%20Eden](https://unavatar.io/openstreetmap/Terence%20Eden)
|
|
287
312
|
|
|
288
313
|
### Patreon
|
|
289
314
|
|
|
@@ -291,7 +316,15 @@ Get any Patreon creator's profile picture by their username.
|
|
|
291
316
|
|
|
292
317
|
Available inputs:
|
|
293
318
|
|
|
294
|
-
-
|
|
319
|
+
- slug, e.g., [unavatar.io/patreon/gametestro](https://unavatar.io/patreon/gametestro)
|
|
320
|
+
|
|
321
|
+
### Printables
|
|
322
|
+
|
|
323
|
+
Get any Printables user's profile picture by their username.
|
|
324
|
+
|
|
325
|
+
Available inputs:
|
|
326
|
+
|
|
327
|
+
- slug, e.g., [unavatar.io/printables/DukeDoks](https://unavatar.io/printables/DukeDoks)
|
|
295
328
|
|
|
296
329
|
### Reddit
|
|
297
330
|
|
|
@@ -299,7 +332,7 @@ Get any Reddit user's avatar by their username.
|
|
|
299
332
|
|
|
300
333
|
Available inputs:
|
|
301
334
|
|
|
302
|
-
-
|
|
335
|
+
- slug, e.g., [unavatar.io/reddit/kikobeats](https://unavatar.io/reddit/kikobeats)
|
|
303
336
|
|
|
304
337
|
### SoundCloud
|
|
305
338
|
|
|
@@ -307,13 +340,13 @@ Get any SoundCloud artist's profile picture by their username.
|
|
|
307
340
|
|
|
308
341
|
Available inputs:
|
|
309
342
|
|
|
310
|
-
-
|
|
343
|
+
- slug, e.g., [unavatar.io/soundcloud/gorillaz](https://unavatar.io/soundcloud/gorillaz)
|
|
311
344
|
|
|
312
345
|
### Spotify
|
|
313
346
|
|
|
314
347
|
Get artwork for any Spotify entity — users, artists, albums, playlists, shows, episodes, or tracks. Look up by username or Spotify ID.
|
|
315
348
|
|
|
316
|
-
e.g., [unavatar.io/spotify/
|
|
349
|
+
e.g., [unavatar.io/spotify/album:7I9Wh2IgvI3Nnr8Z1ZSWby](https://unavatar.io/spotify/album:7I9Wh2IgvI3Nnr8Z1ZSWby)
|
|
317
350
|
|
|
318
351
|
The endpoint supports explicit type as part of the input.
|
|
319
352
|
|
|
@@ -321,13 +354,13 @@ If explicit type is not provided, it defaults to `user`.
|
|
|
321
354
|
|
|
322
355
|
Available URI format inputs:
|
|
323
356
|
|
|
324
|
-
- `
|
|
325
|
-
- `artist`: [unavatar.io/spotify/artist:
|
|
326
|
-
- `
|
|
327
|
-
- `
|
|
328
|
-
- `show`: [unavatar.io/spotify/show:
|
|
329
|
-
- `
|
|
330
|
-
- `
|
|
357
|
+
- `album`: [unavatar.io/spotify/album:7I9Wh2IgvI3Nnr8Z1ZSWby](https://unavatar.io/spotify/album:7I9Wh2IgvI3Nnr8Z1ZSWby)
|
|
358
|
+
- `artist`: [unavatar.io/spotify/artist:1vCWHaC5f2uS3yhpwWbIA6](https://unavatar.io/spotify/artist:1vCWHaC5f2uS3yhpwWbIA6)
|
|
359
|
+
- `episode`: [unavatar.io/spotify/episode:1YNm34Q8ofC2CDTYYLaFMj](https://unavatar.io/spotify/episode:1YNm34Q8ofC2CDTYYLaFMj)
|
|
360
|
+
- `playlist`: [unavatar.io/spotify/playlist:37i9dQZF1DZ06evO3KIUZW](https://unavatar.io/spotify/playlist:37i9dQZF1DZ06evO3KIUZW)
|
|
361
|
+
- `show`: [unavatar.io/spotify/show:0iykbhPkRz53QF8LR2UyNO](https://unavatar.io/spotify/show:0iykbhPkRz53QF8LR2UyNO)
|
|
362
|
+
- `track`: [unavatar.io/spotify/track:4OROzZUy6gOWN4UGQVaZMF](https://unavatar.io/spotify/track:4OROzZUy6gOWN4UGQVaZMF)
|
|
363
|
+
- `user` (default): [unavatar.io/spotify/user:kikobeats](https://unavatar.io/spotify/user:kikobeats)
|
|
331
364
|
|
|
332
365
|
### Substack
|
|
333
366
|
|
|
@@ -343,7 +376,7 @@ Get any Telegram user's profile picture by their username.
|
|
|
343
376
|
|
|
344
377
|
Available inputs:
|
|
345
378
|
|
|
346
|
-
-
|
|
379
|
+
- slug, e.g., [unavatar.io/telegram/drsdavidsoft](https://unavatar.io/telegram/drsdavidsoft)
|
|
347
380
|
|
|
348
381
|
### TikTok
|
|
349
382
|
|
|
@@ -351,7 +384,7 @@ Get any TikTok user's profile picture by their username. No authentication or AP
|
|
|
351
384
|
|
|
352
385
|
Available inputs:
|
|
353
386
|
|
|
354
|
-
-
|
|
387
|
+
- slug, e.g., [unavatar.io/tiktok/carlosazaustre](https://unavatar.io/tiktok/carlosazaustre)
|
|
355
388
|
|
|
356
389
|
### Twitch
|
|
357
390
|
|
|
@@ -359,7 +392,7 @@ Get any Twitch streamer's profile picture by their username.
|
|
|
359
392
|
|
|
360
393
|
Available inputs:
|
|
361
394
|
|
|
362
|
-
-
|
|
395
|
+
- slug, e.g., [unavatar.io/twitch/midudev](https://unavatar.io/twitch/midudev)
|
|
363
396
|
|
|
364
397
|
### Vimeo
|
|
365
398
|
|
|
@@ -367,28 +400,29 @@ Get any Vimeo user's profile picture by their username.
|
|
|
367
400
|
|
|
368
401
|
Available inputs:
|
|
369
402
|
|
|
370
|
-
-
|
|
403
|
+
- slug, e.g., [unavatar.io/vimeo/ladieswithlenses](https://unavatar.io/vimeo/ladieswithlenses)
|
|
371
404
|
|
|
372
405
|
### WhatsApp
|
|
373
406
|
|
|
374
407
|
Get the profile picture for a WhatsApp phone number, channel, chat, or group.
|
|
375
408
|
|
|
376
|
-
|
|
409
|
+
e.g., [unavatar.io/whatsapp/phone:34660021551](https://unavatar.io/whatsapp/phone:34660021551)
|
|
377
410
|
|
|
378
|
-
|
|
411
|
+
The input supports a URI format `type:id`.
|
|
379
412
|
|
|
380
|
-
|
|
381
|
-
- `channel`: [unavatar.io/whatsapp/channel:0029VaABC1234abcDEF56789](https://unavatar.io/whatsapp/channel:0029VaABC1234abcDEF56789)
|
|
382
|
-
- `chat`: [unavatar.io/whatsapp/chat:ABC1234DEFghi](https://unavatar.io/whatsapp/chat:ABC1234DEFghi)
|
|
383
|
-
- `group`: [unavatar.io/whatsapp/group:ABC1234DEFghi](https://unavatar.io/whatsapp/group:ABC1234DEFghi)
|
|
413
|
+
When no type is provided, it defaults to `phone`.
|
|
384
414
|
|
|
385
|
-
|
|
415
|
+
Available URI format inputs:
|
|
416
|
+
|
|
417
|
+
- `phone` (default): [unavatar.io/whatsapp/phone:34660021551](https://unavatar.io/whatsapp/phone:34660021551)
|
|
418
|
+
- `channel`: [unavatar.io/whatsapp/channel:0029VaARuQ7KwqSXh9fiMc0m](https://unavatar.io/whatsapp/channel:0029VaARuQ7KwqSXh9fiMc0m)
|
|
419
|
+
- `chat`: [unavatar.io/whatsapp/chat:D2FFycjQXrEIKG8qQjbwZz](https://unavatar.io/whatsapp/chat:D2FFycjQXrEIKG8qQjbwZz)
|
|
386
420
|
|
|
387
421
|
Get any X (formerly Twitter) user's profile picture by their username.
|
|
388
422
|
|
|
389
423
|
Available inputs:
|
|
390
424
|
|
|
391
|
-
-
|
|
425
|
+
- slug, e.g., [unavatar.io/x/kikobeats](https://unavatar.io/x/kikobeats)
|
|
392
426
|
|
|
393
427
|
### YouTube
|
|
394
428
|
|
|
@@ -409,35 +443,33 @@ Available inputs:
|
|
|
409
443
|
|
|
410
444
|
A response is returning the user avatar by default.
|
|
411
445
|
|
|
412
|
-
However, you can get a [json](#json) as response payload.
|
|
446
|
+
However, you can get a [json](https://unavatar.io/docs#json) as response payload.
|
|
413
447
|
|
|
414
448
|
When an endpoint returns JSON, the shape is predictable so you can parse it reliably in your app:
|
|
415
449
|
|
|
416
|
-
| Field
|
|
417
|
-
|
|
418
|
-
| `status`
|
|
419
|
-
| `message` | `string`
|
|
420
|
-
| `data`
|
|
421
|
-
| `code`
|
|
422
|
-
| `more`
|
|
423
|
-
| `report`
|
|
450
|
+
| Field | Type | Present in | Description |
|
|
451
|
+
| --------- | -------------- | ------------------------------- | ------------------------------------------------ |
|
|
452
|
+
| `status` | `string` | all JSON responses | One of: `success`, `fail`, `error`. |
|
|
453
|
+
| `message` | `string` | all JSON responses | Human-readable summary for display/logging. |
|
|
454
|
+
| `data` | `object` | `success` | Response payload for successful requests. |
|
|
455
|
+
| `code` | `string` | `fail`, `error` | Stable machine-readable error code. |
|
|
456
|
+
| `more` | `string (URL)` | most `fail` / `error` responses | Documentation URL with troubleshooting details. |
|
|
457
|
+
| `report` | `string` | some `error` responses | Support contact channel (for example `mailto:`). |
|
|
424
458
|
|
|
425
459
|
## Response Headers
|
|
426
460
|
|
|
427
461
|
These headers help you understand pricing, limits, and request diagnostics.
|
|
428
462
|
|
|
429
|
-
| Header
|
|
430
|
-
|
|
431
|
-
| `x-pricing-tier`
|
|
432
|
-
| `x-timestamp`
|
|
433
|
-
| `x-unavatar-cost`
|
|
434
|
-
| `x-proxy-tier`
|
|
435
|
-
| `x-rate-limit-limit`
|
|
436
|
-
| `x-rate-limit-remaining` | Remaining requests in current window (free tier only)
|
|
437
|
-
| `x-rate-limit-reset`
|
|
438
|
-
| `retry-after`
|
|
439
|
-
|
|
440
|
-
## Response Errors
|
|
463
|
+
| Header | Purpose |
|
|
464
|
+
| ------------------------ | --------------------------------------------------------- |
|
|
465
|
+
| `x-pricing-tier` | `free` or `pro` — the plan used for this request |
|
|
466
|
+
| `x-timestamp` | Server timestamp when request was received |
|
|
467
|
+
| `x-unavatar-cost` | Token cost of the request (avatar routes only) |
|
|
468
|
+
| `x-proxy-tier` | Proxy tier used: `origin`, `datacenter`, or `residential` |
|
|
469
|
+
| `x-rate-limit-limit` | Maximum requests allowed per window (free tier only) |
|
|
470
|
+
| `x-rate-limit-remaining` | Remaining requests in current window (free tier only) |
|
|
471
|
+
| `x-rate-limit-reset` | UTC epoch seconds when window resets (free tier only) |
|
|
472
|
+
| `retry-after` | Seconds until rate limit resets (only on 429 responses) |
|
|
441
473
|
|
|
442
474
|
Expected errors are known operational cases returned with stable codes.
|
|
443
475
|
|
|
@@ -450,7 +482,7 @@ Expected errors are known operational cases returned with stable codes.
|
|
|
450
482
|
- `report` (when present) indicates how to contact support for server errors.
|
|
451
483
|
|
|
452
484
|
| HTTP | Code | Typical trigger |
|
|
453
|
-
|
|
485
|
+
| ---- | -------------------- | ------------------------------------------- |
|
|
454
486
|
| 400 | `ESESSIONID` | Missing `session_id` in `/checkout/success` |
|
|
455
487
|
| 400 | `ESESSION` | Checkout session not paid or not found |
|
|
456
488
|
| 400 | `ESIGNATURE` | Missing `stripe-signature` header |
|
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.
|
|
5
|
+
"version": "3.9.1",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/index.js",
|
|
@@ -131,13 +131,13 @@
|
|
|
131
131
|
"got": "~11.8.6",
|
|
132
132
|
"html-get": "~2.22.0",
|
|
133
133
|
"https-tls": "~1.0.24",
|
|
134
|
+
"ipaddr.js": "~2.3.0",
|
|
134
135
|
"is-absolute-url": "~3.0.3",
|
|
135
136
|
"is-antibot": "~1.3.0",
|
|
136
137
|
"is-email-like": "~1.0.0",
|
|
137
138
|
"lodash": "~4.17.23",
|
|
138
139
|
"ms": "~2.1.3",
|
|
139
140
|
"p-any": "~3.0.0",
|
|
140
|
-
"p-cancelable": "2.1.1",
|
|
141
141
|
"p-timeout": "~4.1.0",
|
|
142
142
|
"puppeteer": "~24.40.0",
|
|
143
143
|
"re2": "~1.23.3",
|
package/src/avatar/auto.js
CHANGED
|
@@ -66,8 +66,8 @@ const factory = ({ constants, providers, providersBy, reachableUrl }) => {
|
|
|
66
66
|
return { type: 'url', data: url, provider }
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
const getAvatar = async (fn, provider,
|
|
70
|
-
const promise = Promise.resolve(fn(
|
|
69
|
+
const getAvatar = async (fn, provider, input, context) => {
|
|
70
|
+
const promise = Promise.resolve(fn(input, context))
|
|
71
71
|
.then(getAvatarContent(provider))
|
|
72
72
|
.catch(error => {
|
|
73
73
|
isIterable.forEach(error, error => {
|
|
@@ -83,26 +83,20 @@ const factory = ({ constants, providers, providersBy, reachableUrl }) => {
|
|
|
83
83
|
})
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
const resolveAutoByType = async (inputType,
|
|
86
|
+
const resolveAutoByType = async (inputType, input, context) => {
|
|
87
87
|
const collection = providerEntriesByType[inputType]
|
|
88
88
|
const promises = new Array(collection.length)
|
|
89
89
|
|
|
90
90
|
for (let index = 0; index < collection.length; index++) {
|
|
91
91
|
const [provider, fn] = collection[index]
|
|
92
|
-
promises[index] = getAvatar(fn, provider,
|
|
92
|
+
promises[index] = getAvatar(fn, provider, input, context)
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
return pAny(promises)
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
const auto =
|
|
99
|
-
|
|
100
|
-
return args => resolveAutoByType(inputTypeOrArgs, args)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const args = inputTypeOrArgs
|
|
104
|
-
return resolveAutoByType(getInputType(args.input), args)
|
|
105
|
-
}
|
|
98
|
+
const auto = inputType => (input, context) =>
|
|
99
|
+
resolveAutoByType(inputType, input, context)
|
|
106
100
|
|
|
107
101
|
return { auto, getInputType, getAvatar }
|
|
108
102
|
}
|
package/src/index.js
CHANGED
|
@@ -12,12 +12,21 @@ module.exports = ({ constants: userConstants, redis, onFetchHTML } = {}) => {
|
|
|
12
12
|
constants.DNS_TIMEOUT = Math.floor(constants.REQUEST_TIMEOUT * (1 / 5))
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
const { createMultiCache, createRedisCache } = require('./util/keyv')({
|
|
15
|
+
const { createMultiCache, createRedisCache } = require('./util/keyv')({
|
|
16
|
+
...constants,
|
|
17
|
+
redis
|
|
18
|
+
})
|
|
16
19
|
const cache = require('./util/cache')({ createMultiCache, createRedisCache })
|
|
17
|
-
const cacheableLookup = require('./util/cacheable-lookup')({
|
|
20
|
+
const cacheableLookup = require('./util/cacheable-lookup')({
|
|
21
|
+
...constants,
|
|
22
|
+
cache: cache.dnsCache
|
|
23
|
+
})
|
|
18
24
|
const isReservedIp = require('./util/is-reserved-ip')({ cacheableLookup })
|
|
19
25
|
const got = require('./util/got')({ cacheableLookup })
|
|
20
|
-
const reachableUrl = require('./util/reachable-url')({
|
|
26
|
+
const reachableUrl = require('./util/reachable-url')({
|
|
27
|
+
got,
|
|
28
|
+
pingCache: cache.pingCache
|
|
29
|
+
})
|
|
21
30
|
const createBrowser = require('./util/browserless')(constants)
|
|
22
31
|
const getHTML = require('./util/html-get')({ createBrowser, got })
|
|
23
32
|
const { createHtmlProvider, getOgImage } = require('./util/html-provider')({
|
|
@@ -43,13 +52,10 @@ module.exports = ({ constants: userConstants, redis, onFetchHTML } = {}) => {
|
|
|
43
52
|
reachableUrl
|
|
44
53
|
})
|
|
45
54
|
|
|
46
|
-
const
|
|
47
|
-
typeof input === 'string' ? { input } : input
|
|
48
|
-
|
|
49
|
-
const unavatar = input => auto(normalizeArgs(input))
|
|
55
|
+
const unavatar = input => auto(getInputType(input))(input, {})
|
|
50
56
|
|
|
51
57
|
Object.keys(providers).forEach(name => {
|
|
52
|
-
unavatar[name] = input => getAvatar(providers[name], name,
|
|
58
|
+
unavatar[name] = input => getAvatar(providers[name], name, input, {})
|
|
53
59
|
})
|
|
54
60
|
|
|
55
61
|
unavatar.auto = auto
|
package/src/providers/github.js
CHANGED
package/src/providers/google.js
CHANGED
|
@@ -7,8 +7,10 @@ const stringify = require('../util/stringify')
|
|
|
7
7
|
const md5 = str => crypto.createHash('md5').update(str).digest('hex')
|
|
8
8
|
|
|
9
9
|
module.exports = ({ constants }) =>
|
|
10
|
-
function gravatar (
|
|
11
|
-
return `https://gravatar.com/avatar/${md5(
|
|
10
|
+
function gravatar (input) {
|
|
11
|
+
return `https://gravatar.com/avatar/${md5(
|
|
12
|
+
input.trim().toLowerCase()
|
|
13
|
+
)}?${stringify({
|
|
12
14
|
size: constants.AVATAR_SIZE,
|
|
13
15
|
d: '404'
|
|
14
16
|
})}`
|
|
@@ -35,7 +35,7 @@ const parseMastodonInput = input => {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
module.exports = ({ got, isReservedIp }) => {
|
|
38
|
-
const mastodon = async function (
|
|
38
|
+
const mastodon = async function (input) {
|
|
39
39
|
const parsed = parseMastodonInput(input)
|
|
40
40
|
if (!parsed) return undefined
|
|
41
41
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const PCancelable = require('p-cancelable')
|
|
4
3
|
const mql = require('@microlink/mql')
|
|
5
4
|
const { get } = require('lodash')
|
|
6
5
|
|
|
7
6
|
module.exports = ({ constants }) =>
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
7
|
+
async function microlink (input, context) {
|
|
8
|
+
const req = context?.req
|
|
9
|
+
const { data } = await mql(`https://${input}`, {
|
|
10
|
+
apiKey: req?.isPro
|
|
11
|
+
? constants.MICROLINK_API_KEY
|
|
12
|
+
: req?.headers?.['x-api-key']
|
|
11
13
|
})
|
|
12
|
-
onCancel(() => promise.onCancel())
|
|
13
|
-
const { data } = await promise
|
|
14
14
|
return get(data, 'logo.url')
|
|
15
|
-
}
|
|
15
|
+
}
|
|
@@ -12,12 +12,12 @@ module.exports = ({ got, createHtmlProvider }) => {
|
|
|
12
12
|
getter: $ => $('img.user_image').attr('src')
|
|
13
13
|
})
|
|
14
14
|
|
|
15
|
-
return function openstreetmap (
|
|
16
|
-
return OPENSTREETMAP_USER_ID_REGEX.test(
|
|
17
|
-
? got(`${OPENSTREETMAP_API_URL}/${
|
|
15
|
+
return function openstreetmap (input, context) {
|
|
16
|
+
return OPENSTREETMAP_USER_ID_REGEX.test(input)
|
|
17
|
+
? got(`${OPENSTREETMAP_API_URL}/${input}.json`, {
|
|
18
18
|
responseType: 'json'
|
|
19
19
|
}).then(({ body }) => body?.user?.img?.href)
|
|
20
|
-
: fromUsername(
|
|
20
|
+
: fromUsername(input, context)
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -39,9 +39,8 @@ module.exports = ({ PROXY_TIMEOUT, getHTML, onFetchHTML }) => {
|
|
|
39
39
|
* @param {() => object} [opts.htmlOpts] - Returns extra options merged into the fetch call.
|
|
40
40
|
*/
|
|
41
41
|
const createHtmlProvider = ({ name, url, getter, isBlocked, htmlOpts }) => {
|
|
42
|
-
const provider = async function (
|
|
42
|
+
const provider = async function (input, context) {
|
|
43
43
|
const providerUrl = await url(input)
|
|
44
|
-
const context = { provider: name, input, providerUrl }
|
|
45
44
|
|
|
46
45
|
const attempt = async gotOpts => {
|
|
47
46
|
const providerOpts = htmlOpts?.() ?? {}
|
|
@@ -56,7 +55,7 @@ module.exports = ({ PROXY_TIMEOUT, getHTML, onFetchHTML }) => {
|
|
|
56
55
|
const fetchOpts = gotOpts ? { ...defaultOpts, ...gotOpts } : defaultOpts
|
|
57
56
|
const tier = fetchOpts.tier ?? 'origin'
|
|
58
57
|
|
|
59
|
-
const log = debug.duration({
|
|
58
|
+
const log = debug.duration({ provider: name, input, providerUrl, tier })
|
|
60
59
|
|
|
61
60
|
const {
|
|
62
61
|
$,
|
|
@@ -116,7 +115,7 @@ module.exports = ({ PROXY_TIMEOUT, getHTML, onFetchHTML }) => {
|
|
|
116
115
|
}
|
|
117
116
|
|
|
118
117
|
if (typeof onFetchHTML === 'function') {
|
|
119
|
-
return onFetchHTML({ attempt, provider: name, providerUrl
|
|
118
|
+
return onFetchHTML({ ...context, attempt, provider: name, providerUrl })
|
|
120
119
|
}
|
|
121
120
|
|
|
122
121
|
const result = await attempt()
|