@unavatar/core 3.44.0 → 3.45.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 +0 -33
- package/package.json +1 -1
- package/src/providers/bilibili.js +27 -0
- package/src/providers/douban.js +15 -0
- package/src/providers/gitee.js +22 -0
- package/src/providers/index.js +19 -2
- package/src/providers/juejin.js +26 -0
- package/src/providers/netease-music.js +26 -0
- package/src/providers/qq.js +13 -0
- package/src/providers/tieba.js +36 -0
- package/src/providers/weibo.js +36 -0
- package/src/providers/zhihu.js +28 -0
package/README.md
CHANGED
|
@@ -25,7 +25,6 @@
|
|
|
25
25
|
- [CodePen](#codepen)
|
|
26
26
|
- [Cults3D](#cults3d)
|
|
27
27
|
- [Cursor](#cursor)
|
|
28
|
-
- [Deezer](#deezer)
|
|
29
28
|
- [DeviantArt](#deviantart)
|
|
30
29
|
- [Discord](#discord)
|
|
31
30
|
- [Docker Hub](#docker-hub)
|
|
@@ -69,7 +68,6 @@
|
|
|
69
68
|
- [Telegram](#telegram)
|
|
70
69
|
- [Thingiverse](#thingiverse)
|
|
71
70
|
- [Threads](#threads)
|
|
72
|
-
- [TIDAL](#tidal)
|
|
73
71
|
- [TikTok](#tiktok)
|
|
74
72
|
- [Tumblr](#tumblr)
|
|
75
73
|
- [Twitch](#twitch)
|
|
@@ -597,21 +595,6 @@ Get any Cursor user's profile picture by their username.
|
|
|
597
595
|
|
|
598
596
|
e.g., [unavatar.io/cursor/leerob](https://unavatar.io/cursor/leerob)
|
|
599
597
|
|
|
600
|
-
### Deezer
|
|
601
|
-
|
|
602
|
-
Get artwork for any Deezer entity — artists, albums, playlists, or tracks. Look up by Deezer ID.
|
|
603
|
-
|
|
604
|
-
By default the input is treated as an artist:
|
|
605
|
-
|
|
606
|
-
e.g., [unavatar.io/deezer/27](https://unavatar.io/deezer/27)
|
|
607
|
-
|
|
608
|
-
The input also supports a URI format `type:id`:
|
|
609
|
-
|
|
610
|
-
- `artist` (default): [unavatar.io/deezer/artist:27](https://unavatar.io/deezer/artist:27)
|
|
611
|
-
- `album`: [unavatar.io/deezer/album:302127](https://unavatar.io/deezer/album:302127)
|
|
612
|
-
- `playlist`: [unavatar.io/deezer/playlist:908622995](https://unavatar.io/deezer/playlist:908622995)
|
|
613
|
-
- `track`: [unavatar.io/deezer/track:3135556](https://unavatar.io/deezer/track:3135556)
|
|
614
|
-
|
|
615
598
|
### DeviantArt
|
|
616
599
|
|
|
617
600
|
Get any DeviantArt user's profile picture by their username.
|
|
@@ -962,22 +945,6 @@ Get any Threads user's profile picture by their username.
|
|
|
962
945
|
|
|
963
946
|
e.g., [unavatar.io/threads/zuck](https://unavatar.io/threads/zuck)
|
|
964
947
|
|
|
965
|
-
### TIDAL
|
|
966
|
-
|
|
967
|
-
Get artwork for any TIDAL entity — artists, albums, playlists, tracks, or videos. Look up by TIDAL ID.
|
|
968
|
-
|
|
969
|
-
By default the input is treated as an artist:
|
|
970
|
-
|
|
971
|
-
e.g., [unavatar.io/tidal/1566](https://unavatar.io/tidal/1566)
|
|
972
|
-
|
|
973
|
-
The input also supports a URI format `type:id`:
|
|
974
|
-
|
|
975
|
-
- `artist` (default): [unavatar.io/tidal/artist:1566](https://unavatar.io/tidal/artist:1566)
|
|
976
|
-
- `album`: [unavatar.io/tidal/album:1765857](https://unavatar.io/tidal/album:1765857)
|
|
977
|
-
- `playlist`: [unavatar.io/tidal/playlist:c3c18106-c4f5-4021-bb18-108255c1f450](https://unavatar.io/tidal/playlist:c3c18106-c4f5-4021-bb18-108255c1f450)
|
|
978
|
-
- `track`: [unavatar.io/tidal/track:113655761](https://unavatar.io/tidal/track:113655761)
|
|
979
|
-
- `video`: [unavatar.io/tidal/video:45323542](https://unavatar.io/tidal/video:45323542)
|
|
980
|
-
|
|
981
948
|
### TikTok
|
|
982
949
|
|
|
983
950
|
Get any TikTok user's profile picture by their username. No authentication or API tokens needed — just pass the username.
|
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.45.1",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/index.js",
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const API_URL = 'https://api.bilibili.com/x/web-interface/card'
|
|
4
|
+
|
|
5
|
+
const getAvatarUrl = input => `${API_URL}?mid=${encodeURIComponent(input)}`
|
|
6
|
+
|
|
7
|
+
// Users without a custom avatar get the generic `noface.jpg` placeholder.
|
|
8
|
+
const getAvatar = body => {
|
|
9
|
+
if (body?.code !== 0) return
|
|
10
|
+
const face = body.data?.card?.face
|
|
11
|
+
return face && !face.endsWith('/noface.jpg') ? face : undefined
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = ({ got }) =>
|
|
15
|
+
async function bilibili (input) {
|
|
16
|
+
const { body, statusCode } = await got(getAvatarUrl(input), {
|
|
17
|
+
responseType: 'json',
|
|
18
|
+
throwHttpErrors: false
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
if (statusCode >= 400) return
|
|
22
|
+
|
|
23
|
+
return getAvatar(body)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports.getAvatarUrl = getAvatarUrl
|
|
27
|
+
module.exports.getAvatar = getAvatar
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const getAvatarUrl = input => `https://www.douban.com/people/${input}/`
|
|
4
|
+
|
|
5
|
+
const getAvatar = $ => $('img.userface').attr('src')
|
|
6
|
+
|
|
7
|
+
module.exports = ({ createHtmlProvider }) =>
|
|
8
|
+
createHtmlProvider({
|
|
9
|
+
name: 'douban',
|
|
10
|
+
url: getAvatarUrl,
|
|
11
|
+
getter: getAvatar
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
module.exports.getAvatarUrl = getAvatarUrl
|
|
15
|
+
module.exports.getAvatar = getAvatar
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const API_URL = 'https://gitee.com/api/v5/users'
|
|
4
|
+
|
|
5
|
+
const getAvatarUrl = input => `${API_URL}/${encodeURIComponent(input)}`
|
|
6
|
+
|
|
7
|
+
const getAvatar = body => body?.avatar_url || undefined
|
|
8
|
+
|
|
9
|
+
module.exports = ({ got }) =>
|
|
10
|
+
async function gitee (input) {
|
|
11
|
+
const { body, statusCode } = await got(getAvatarUrl(input), {
|
|
12
|
+
responseType: 'json',
|
|
13
|
+
throwHttpErrors: false
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
if (statusCode >= 400) return
|
|
17
|
+
|
|
18
|
+
return getAvatar(body)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports.getAvatarUrl = getAvatarUrl
|
|
22
|
+
module.exports.getAvatar = getAvatar
|
package/src/providers/index.js
CHANGED
|
@@ -6,6 +6,7 @@ const providersBy = {
|
|
|
6
6
|
'apple-music',
|
|
7
7
|
'apple-store',
|
|
8
8
|
'behance',
|
|
9
|
+
'bilibili',
|
|
9
10
|
'bluesky',
|
|
10
11
|
'buymeacoffee',
|
|
11
12
|
'cashapp',
|
|
@@ -16,19 +17,23 @@ const providersBy = {
|
|
|
16
17
|
'deviantart',
|
|
17
18
|
'discord',
|
|
18
19
|
'dockerhub',
|
|
20
|
+
'douban',
|
|
19
21
|
'dribbble',
|
|
20
22
|
'facebook',
|
|
21
23
|
'flickr',
|
|
24
|
+
'gitee',
|
|
22
25
|
'github',
|
|
23
26
|
'gitlab',
|
|
24
27
|
'google-play',
|
|
25
28
|
'huggingface',
|
|
26
29
|
'hevy',
|
|
27
30
|
'instagram',
|
|
31
|
+
'juejin',
|
|
28
32
|
'ko-fi',
|
|
29
33
|
'linkedin',
|
|
30
34
|
'mastodon',
|
|
31
35
|
'medium',
|
|
36
|
+
'netease-music',
|
|
32
37
|
'npm',
|
|
33
38
|
'onlyfans',
|
|
34
39
|
'openstreetmap',
|
|
@@ -53,15 +58,18 @@ const providersBy = {
|
|
|
53
58
|
'thingiverse',
|
|
54
59
|
'threads',
|
|
55
60
|
'tidal',
|
|
61
|
+
'tieba',
|
|
56
62
|
'tiktok',
|
|
57
63
|
'tumblr',
|
|
58
64
|
'twitch',
|
|
59
65
|
'venmo',
|
|
60
66
|
'vimeo',
|
|
67
|
+
'weibo',
|
|
61
68
|
'wise',
|
|
62
69
|
'x',
|
|
63
70
|
'xboxgamertag',
|
|
64
|
-
'youtube'
|
|
71
|
+
'youtube',
|
|
72
|
+
'zhihu'
|
|
65
73
|
],
|
|
66
74
|
domain: ['microlink']
|
|
67
75
|
}
|
|
@@ -71,6 +79,7 @@ module.exports = ctx => {
|
|
|
71
79
|
'apple-music': require('./apple-music')(ctx),
|
|
72
80
|
'apple-store': require('./apple-store')(ctx),
|
|
73
81
|
behance: require('./behance')(ctx),
|
|
82
|
+
bilibili: require('./bilibili')(ctx),
|
|
74
83
|
bluesky: require('./bluesky')(ctx),
|
|
75
84
|
buymeacoffee: require('./buymeacoffee')(ctx),
|
|
76
85
|
cashapp: require('./cashapp')(ctx),
|
|
@@ -81,10 +90,12 @@ module.exports = ctx => {
|
|
|
81
90
|
deviantart: require('./deviantart')(ctx),
|
|
82
91
|
discord: require('./discord')(ctx),
|
|
83
92
|
dockerhub: require('./dockerhub')(ctx),
|
|
93
|
+
douban: require('./douban')(ctx),
|
|
84
94
|
dribbble: require('./dribbble')(ctx),
|
|
85
95
|
duckduckgo: require('./duckduckgo')(ctx),
|
|
86
96
|
facebook: require('./facebook')(ctx),
|
|
87
97
|
flickr: require('./flickr')(ctx),
|
|
98
|
+
gitee: require('./gitee')(ctx),
|
|
88
99
|
github: require('./github')(ctx),
|
|
89
100
|
gitlab: require('./gitlab')(ctx),
|
|
90
101
|
google: require('./google')(ctx),
|
|
@@ -93,11 +104,13 @@ module.exports = ctx => {
|
|
|
93
104
|
huggingface: require('./huggingface')(ctx),
|
|
94
105
|
hevy: require('./hevy')(ctx),
|
|
95
106
|
instagram: require('./instagram')(ctx),
|
|
107
|
+
juejin: require('./juejin')(ctx),
|
|
96
108
|
'ko-fi': require('./ko-fi')(ctx),
|
|
97
109
|
linkedin: require('./linkedin')(ctx),
|
|
98
110
|
mastodon: require('./mastodon')(ctx),
|
|
99
111
|
medium: require('./medium')(ctx),
|
|
100
112
|
microlink: require('./microlink')(ctx),
|
|
113
|
+
'netease-music': require('./netease-music')(ctx),
|
|
101
114
|
npm: require('./npm')(ctx),
|
|
102
115
|
onlyfans: require('./onlyfans')(ctx),
|
|
103
116
|
openstreetmap: require('./openstreetmap')(ctx),
|
|
@@ -108,6 +121,7 @@ module.exports = ctx => {
|
|
|
108
121
|
primal: require('./primal')(ctx),
|
|
109
122
|
producthunt: require('./producthunt')(ctx),
|
|
110
123
|
psnprofiles: require('./psnprofiles')(ctx),
|
|
124
|
+
qq: require('./qq')(ctx),
|
|
111
125
|
raycast: require('./raycast')(ctx),
|
|
112
126
|
reddit: require('./reddit')(ctx),
|
|
113
127
|
revolut: require('./revolut')(ctx),
|
|
@@ -122,16 +136,19 @@ module.exports = ctx => {
|
|
|
122
136
|
thingiverse: require('./thingiverse')(ctx),
|
|
123
137
|
threads: require('./threads')(ctx),
|
|
124
138
|
tidal: require('./tidal')(ctx),
|
|
139
|
+
tieba: require('./tieba')(ctx),
|
|
125
140
|
tiktok: require('./tiktok')(ctx),
|
|
126
141
|
tumblr: require('./tumblr')(ctx),
|
|
127
142
|
twitch: require('./twitch')(ctx),
|
|
128
143
|
venmo: require('./venmo')(ctx),
|
|
129
144
|
vimeo: require('./vimeo')(ctx),
|
|
145
|
+
weibo: require('./weibo')(ctx),
|
|
130
146
|
whatsapp: require('./whatsapp')(ctx),
|
|
131
147
|
wise: require('./wise')(ctx),
|
|
132
148
|
x: require('./x')(ctx),
|
|
133
149
|
xboxgamertag: require('./xboxgamertag')(ctx),
|
|
134
|
-
youtube: require('./youtube')(ctx)
|
|
150
|
+
youtube: require('./youtube')(ctx),
|
|
151
|
+
zhihu: require('./zhihu')(ctx)
|
|
135
152
|
}
|
|
136
153
|
|
|
137
154
|
return { providers, providersBy }
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const API_URL = 'https://api.juejin.cn/user_api/v1/user/get'
|
|
4
|
+
|
|
5
|
+
const getAvatarUrl = input => `${API_URL}?user_id=${encodeURIComponent(input)}`
|
|
6
|
+
|
|
7
|
+
// Unknown ids still respond `err_no: 0`, but with an empty profile.
|
|
8
|
+
const getAvatar = body => {
|
|
9
|
+
if (body?.err_no !== 0) return
|
|
10
|
+
return body.data?.avatar_large || undefined
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = ({ got }) =>
|
|
14
|
+
async function juejin (input) {
|
|
15
|
+
const { body, statusCode } = await got(getAvatarUrl(input), {
|
|
16
|
+
responseType: 'json',
|
|
17
|
+
throwHttpErrors: false
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
if (statusCode >= 400) return
|
|
21
|
+
|
|
22
|
+
return getAvatar(body)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports.getAvatarUrl = getAvatarUrl
|
|
26
|
+
module.exports.getAvatar = getAvatar
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const API_URL = 'https://music.163.com/api/v1/user/detail'
|
|
4
|
+
|
|
5
|
+
const getAvatarUrl = input => `${API_URL}/${encodeURIComponent(input)}`
|
|
6
|
+
|
|
7
|
+
// The API returns avatar URLs over plain HTTP; the CDN supports HTTPS.
|
|
8
|
+
const getAvatar = body => {
|
|
9
|
+
const avatarUrl = body?.profile?.avatarUrl
|
|
10
|
+
return avatarUrl ? avatarUrl.replace(/^http:/, 'https:') : undefined
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = ({ got }) =>
|
|
14
|
+
async function neteaseMusic (input) {
|
|
15
|
+
const { body, statusCode } = await got(getAvatarUrl(input), {
|
|
16
|
+
responseType: 'json',
|
|
17
|
+
throwHttpErrors: false
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
if (statusCode >= 400) return
|
|
21
|
+
|
|
22
|
+
return getAvatar(body)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports.getAvatarUrl = getAvatarUrl
|
|
26
|
+
module.exports.getAvatar = getAvatar
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// qlogo.cn serves a default placeholder for unknown numbers (gravatar-like),
|
|
4
|
+
// so the URL always resolves and misses can't be detected upfront.
|
|
5
|
+
const getAvatarUrl = input =>
|
|
6
|
+
`https://q1.qlogo.cn/g?b=qq&nk=${encodeURIComponent(input)}&s=640`
|
|
7
|
+
|
|
8
|
+
module.exports = () =>
|
|
9
|
+
function qq (input) {
|
|
10
|
+
return getAvatarUrl(input)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports.getAvatarUrl = getAvatarUrl
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const PORTRAIT_URL = 'https://himg.bdimg.com/sys/portrait/item'
|
|
4
|
+
|
|
5
|
+
const getAvatarUrl = input =>
|
|
6
|
+
`https://tieba.baidu.com/i/sys/user_json?un=${encodeURIComponent(
|
|
7
|
+
input
|
|
8
|
+
)}&ie=utf-8`
|
|
9
|
+
|
|
10
|
+
const getAvatar = body => {
|
|
11
|
+
const portrait = body?.creator?.portrait
|
|
12
|
+
return portrait ? `${PORTRAIT_URL}/${portrait}` : undefined
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = ({ got }) =>
|
|
16
|
+
async function tieba (input) {
|
|
17
|
+
// Unknown users respond with an empty body, which is not valid JSON.
|
|
18
|
+
const { body, statusCode } = await got(getAvatarUrl(input), {
|
|
19
|
+
responseType: 'text',
|
|
20
|
+
throwHttpErrors: false
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
if (statusCode >= 400 || !body) return
|
|
24
|
+
|
|
25
|
+
let json
|
|
26
|
+
try {
|
|
27
|
+
json = JSON.parse(body)
|
|
28
|
+
} catch {
|
|
29
|
+
return undefined
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return getAvatar(json)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports.getAvatarUrl = getAvatarUrl
|
|
36
|
+
module.exports.getAvatar = getAvatar
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const getAvatarUrl = input =>
|
|
4
|
+
/^\d+$/.test(input)
|
|
5
|
+
? `https://m.weibo.cn/u/${input}`
|
|
6
|
+
: `https://m.weibo.cn/n/${encodeURIComponent(input)}`
|
|
7
|
+
|
|
8
|
+
// The profile avatar is served as a signed, expiring crop
|
|
9
|
+
// (`/crop.0.0.W.H.180/file?ssig=…`); rewriting it to `/large/file` yields a
|
|
10
|
+
// stable, unsigned, full-resolution URL. Default avatars live under
|
|
11
|
+
// `/default/`, which is treated as a miss.
|
|
12
|
+
const getAvatar = ({ $, NOT_FOUND }) => {
|
|
13
|
+
const src = $('.m-avatar-box img').attr('src')
|
|
14
|
+
if (!src) return
|
|
15
|
+
|
|
16
|
+
const url = new URL(src, 'https://m.weibo.cn')
|
|
17
|
+
if (url.pathname.includes('/default/')) return NOT_FOUND
|
|
18
|
+
|
|
19
|
+
const filename = url.pathname.slice(url.pathname.lastIndexOf('/'))
|
|
20
|
+
return `${url.origin}/large${filename}`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// The page sits behind Weibo's visitor gate (a JS cookie-setting redirect)
|
|
24
|
+
// and the avatar is injected after hydration, so it must be prerendered.
|
|
25
|
+
module.exports = ({ createHtmlProvider, NOT_FOUND }) =>
|
|
26
|
+
createHtmlProvider({
|
|
27
|
+
name: 'weibo',
|
|
28
|
+
url: getAvatarUrl,
|
|
29
|
+
getter: $ => getAvatar({ $, NOT_FOUND }),
|
|
30
|
+
htmlOpts: () => ({
|
|
31
|
+
prerender: true
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
module.exports.getAvatarUrl = getAvatarUrl
|
|
36
|
+
module.exports.getAvatar = getAvatar
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const API_URL = 'https://www.zhihu.com/api/v4/members'
|
|
4
|
+
|
|
5
|
+
const getAvatarUrl = input =>
|
|
6
|
+
`${API_URL}/${encodeURIComponent(input)}?include=avatar_url`
|
|
7
|
+
|
|
8
|
+
// Misses (and bot challenges) come back as an `error` payload rather than
|
|
9
|
+
// a plain 404, and default avatars are flagged via `use_default_avatar`.
|
|
10
|
+
const getAvatar = body => {
|
|
11
|
+
if (!body || body.error || body.use_default_avatar) return
|
|
12
|
+
return body.avatar_url || undefined
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = ({ got }) =>
|
|
16
|
+
async function zhihu (input) {
|
|
17
|
+
const { body, statusCode } = await got(getAvatarUrl(input), {
|
|
18
|
+
responseType: 'json',
|
|
19
|
+
throwHttpErrors: false
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
if (statusCode >= 400) return
|
|
23
|
+
|
|
24
|
+
return getAvatar(body)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports.getAvatarUrl = getAvatarUrl
|
|
28
|
+
module.exports.getAvatar = getAvatar
|