@unavatar/core 3.44.0 → 3.45.0

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
@@ -19,7 +19,9 @@
19
19
  - [Providers](#providers)
20
20
  - [Apple Music](#apple-music)
21
21
  - [Apple Store](#apple-store)
22
+ - [Baidu Tieba](#baidu-tieba)
22
23
  - [Behance](#behance)
24
+ - [Bilibili](#bilibili)
23
25
  - [Bluesky](#bluesky)
24
26
  - [Buy Me a Coffee](#buy-me-a-coffee)
25
27
  - [CodePen](#codepen)
@@ -30,11 +32,13 @@
30
32
  - [Discord](#discord)
31
33
  - [Docker Hub](#docker-hub)
32
34
  - [Domain](#domain)
35
+ - [Douban](#douban)
33
36
  - [Dribbble](#dribbble)
34
37
  - [DuckDuckGo](#duckduckgo)
35
38
  - [Email](#email)
36
39
  - [Facebook](#facebook)
37
40
  - [Flickr](#flickr)
41
+ - [Gitee](#gitee)
38
42
  - [GitHub](#github)
39
43
  - [GitLab](#gitlab)
40
44
  - [Google](#google)
@@ -43,11 +47,13 @@
43
47
  - [Hevy](#hevy)
44
48
  - [Hugging Face](#hugging-face)
45
49
  - [Instagram](#instagram)
50
+ - [Juejin](#juejin)
46
51
  - [Ko-fi](#ko-fi)
47
52
  - [LinkedIn](#linkedin)
48
53
  - [Mastodon](#mastodon)
49
54
  - [Medium](#medium)
50
55
  - [Microlink](#microlink)
56
+ - [NetEase Music](#netease-music)
51
57
  - [npm](#npm)
52
58
  - [OnlyFans](#onlyfans)
53
59
  - [OpenStreetMap](#openstreetmap)
@@ -57,6 +63,7 @@
57
63
  - [Printables](#printables)
58
64
  - [Product Hunt](#product-hunt)
59
65
  - [PSN Profiles](#psn-profiles)
66
+ - [QQ](#qq)
60
67
  - [Raycast](#raycast)
61
68
  - [Reddit](#reddit)
62
69
  - [Snapchat](#snapchat)
@@ -74,10 +81,12 @@
74
81
  - [Tumblr](#tumblr)
75
82
  - [Twitch](#twitch)
76
83
  - [Vimeo](#vimeo)
84
+ - [Weibo](#weibo)
77
85
  - [WhatsApp](#whatsapp)
78
86
  - [X/Twitter](#xtwitter)
79
87
  - [Xbox Gamertag](#xbox-gamertag)
80
88
  - [YouTube](#youtube)
89
+ - [Zhihu](#zhihu)
81
90
  - [Response Format](#response-format)
82
91
  - [Response Headers](#response-headers)
83
92
  - [Response Errors](#response-errors)
@@ -558,12 +567,24 @@ Available inputs:
558
567
  - `id` (default): [unavatar.io/apple-store/id:529479190](https://unavatar.io/apple-store/id:529479190) or [unavatar.io/apple-store/id:6474323148@es](https://unavatar.io/apple-store/id:6474323148@es)
559
568
  - `name`: [unavatar.io/apple-store/name:pokemon%20go](https://unavatar.io/apple-store/name:pokemon%20go)
560
569
 
570
+ ### Baidu Tieba
571
+
572
+ Get any Baidu Tieba user's profile picture by their username.
573
+
574
+ e.g., [unavatar.io/tieba/Mojang](https://unavatar.io/tieba/Mojang)
575
+
561
576
  ### Behance
562
577
 
563
578
  Get any Behance user's profile picture by their username.
564
579
 
565
580
  e.g., [unavatar.io/behance/vitormatosinhos](https://unavatar.io/behance/vitormatosinhos)
566
581
 
582
+ ### Bilibili
583
+
584
+ Get any Bilibili user's profile picture by their numeric uid.
585
+
586
+ e.g., [unavatar.io/bilibili/2](https://unavatar.io/bilibili/2)
587
+
567
588
  ### Bluesky
568
589
 
569
590
  Get any Bluesky user's profile picture by their handle. Domain-style handles are supported.
@@ -639,6 +660,12 @@ Retrieve logos and favicons from any hostname with a single URL. Domain-shaped p
639
660
 
640
661
  e.g., [unavatar.io/domain/microlink.io](https://unavatar.io/domain/microlink.io)
641
662
 
663
+ ### Douban
664
+
665
+ Get any Douban user's profile picture by their username or numeric ID.
666
+
667
+ e.g., [unavatar.io/douban/ahbei](https://unavatar.io/douban/ahbei)
668
+
642
669
  ### Dribbble
643
670
 
644
671
  Get any Dribbble designer's profile picture by their username.
@@ -695,6 +722,12 @@ Available inputs:
695
722
  - `user` (default): [unavatar.io/flickr/user:elizabethgadd](https://unavatar.io/flickr/user:elizabethgadd)
696
723
  - `group`: [unavatar.io/flickr/group:allthingsearthy](https://unavatar.io/flickr/group:allthingsearthy)
697
724
 
725
+ ### Gitee
726
+
727
+ Get any Gitee user or organization's profile picture by their username.
728
+
729
+ e.g., [unavatar.io/gitee/y_project](https://unavatar.io/gitee/y_project)
730
+
698
731
  ### GitHub
699
732
 
700
733
  Get any GitHub user or organization's profile picture by username, or resolve an avatar from a public email via GitHub search when the address matches a profile or commit history.
@@ -768,6 +801,12 @@ Get any Instagram user's profile picture by their username. No authentication or
768
801
 
769
802
  e.g., [unavatar.io/instagram/willsmith](https://unavatar.io/instagram/willsmith)
770
803
 
804
+ ### Juejin
805
+
806
+ Get any Juejin user's profile picture by their numeric user ID.
807
+
808
+ e.g., [unavatar.io/juejin/1556564194374926](https://unavatar.io/juejin/1556564194374926)
809
+
771
810
  ### Ko-fi
772
811
 
773
812
  Get any Ko-fi page's profile picture by the creator username.
@@ -803,6 +842,12 @@ Extract the logo or representative image from any URL. The page is rendered and
803
842
 
804
843
  e.g., [unavatar.io/microlink/microlink.io](https://unavatar.io/microlink/microlink.io)
805
844
 
845
+ ### NetEase Music
846
+
847
+ Get any NetEase Cloud Music user's profile picture by their numeric user ID.
848
+
849
+ e.g., [unavatar.io/netease-music/32953014](https://unavatar.io/netease-music/32953014)
850
+
806
851
  ### npm
807
852
 
808
853
  Get any npm user's profile picture by their username, including scoped handles.
@@ -867,6 +912,14 @@ Get any PlayStation Network user's profile picture by their PSN username.
867
912
 
868
913
  e.g., [unavatar.io/psnprofiles/Duff85](https://unavatar.io/psnprofiles/Duff85)
869
914
 
915
+ ### QQ
916
+
917
+ Get any QQ user's profile picture by their QQ number.
918
+
919
+ e.g., [unavatar.io/qq/10001](https://unavatar.io/qq/10001)
920
+
921
+ Note: unknown numbers resolve to QQ's default avatar, similar to Gravatar.
922
+
870
923
  ### Raycast
871
924
 
872
925
  Get any Raycast user's profile picture by their username.
@@ -1002,6 +1055,12 @@ Get any Vimeo user's profile picture by their username.
1002
1055
 
1003
1056
  e.g., [unavatar.io/vimeo/ladieswithlenses](https://unavatar.io/vimeo/ladieswithlenses)
1004
1057
 
1058
+ ### Weibo
1059
+
1060
+ Get any Weibo user's profile picture by their numeric uid or nickname.
1061
+
1062
+ e.g., [unavatar.io/weibo/2803301701](https://unavatar.io/weibo/2803301701)
1063
+
1005
1064
  ### WhatsApp
1006
1065
 
1007
1066
  Get the profile picture for a WhatsApp channel or chat by ID.
@@ -1041,6 +1100,12 @@ Available inputs:
1041
1100
  - `username`: [unavatar.io/youtube/casey](https://unavatar.io/youtube/casey)
1042
1101
  - `channel`: [unavatar.io/youtube/UC_x5XG1OV2P6uZZ5FSM9Ttw](https://unavatar.io/youtube/UC_x5XG1OV2P6uZZ5FSM9Ttw)
1043
1102
 
1103
+ ### Zhihu
1104
+
1105
+ Get any Zhihu user's profile picture by their URL token (the handle in the profile URL).
1106
+
1107
+ e.g., [unavatar.io/zhihu/kaifulee](https://unavatar.io/zhihu/kaifulee)
1108
+
1044
1109
  ## Response Format
1045
1110
 
1046
1111
  A response is returning the user avatar by default.
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.0",
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