@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 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.44.0",
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
@@ -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