@unavatar/core 3.43.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,21 +19,26 @@
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)
26
28
  - [Cults3D](#cults3d)
27
29
  - [Cursor](#cursor)
30
+ - [Deezer](#deezer)
28
31
  - [DeviantArt](#deviantart)
29
32
  - [Discord](#discord)
30
33
  - [Docker Hub](#docker-hub)
31
34
  - [Domain](#domain)
35
+ - [Douban](#douban)
32
36
  - [Dribbble](#dribbble)
33
37
  - [DuckDuckGo](#duckduckgo)
34
38
  - [Email](#email)
35
39
  - [Facebook](#facebook)
36
40
  - [Flickr](#flickr)
41
+ - [Gitee](#gitee)
37
42
  - [GitHub](#github)
38
43
  - [GitLab](#gitlab)
39
44
  - [Google](#google)
@@ -42,11 +47,13 @@
42
47
  - [Hevy](#hevy)
43
48
  - [Hugging Face](#hugging-face)
44
49
  - [Instagram](#instagram)
50
+ - [Juejin](#juejin)
45
51
  - [Ko-fi](#ko-fi)
46
52
  - [LinkedIn](#linkedin)
47
53
  - [Mastodon](#mastodon)
48
54
  - [Medium](#medium)
49
55
  - [Microlink](#microlink)
56
+ - [NetEase Music](#netease-music)
50
57
  - [npm](#npm)
51
58
  - [OnlyFans](#onlyfans)
52
59
  - [OpenStreetMap](#openstreetmap)
@@ -56,6 +63,7 @@
56
63
  - [Printables](#printables)
57
64
  - [Product Hunt](#product-hunt)
58
65
  - [PSN Profiles](#psn-profiles)
66
+ - [QQ](#qq)
59
67
  - [Raycast](#raycast)
60
68
  - [Reddit](#reddit)
61
69
  - [Snapchat](#snapchat)
@@ -73,10 +81,12 @@
73
81
  - [Tumblr](#tumblr)
74
82
  - [Twitch](#twitch)
75
83
  - [Vimeo](#vimeo)
84
+ - [Weibo](#weibo)
76
85
  - [WhatsApp](#whatsapp)
77
86
  - [X/Twitter](#xtwitter)
78
87
  - [Xbox Gamertag](#xbox-gamertag)
79
88
  - [YouTube](#youtube)
89
+ - [Zhihu](#zhihu)
80
90
  - [Response Format](#response-format)
81
91
  - [Response Headers](#response-headers)
82
92
  - [Response Errors](#response-errors)
@@ -557,12 +567,24 @@ Available inputs:
557
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)
558
568
  - `name`: [unavatar.io/apple-store/name:pokemon%20go](https://unavatar.io/apple-store/name:pokemon%20go)
559
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
+
560
576
  ### Behance
561
577
 
562
578
  Get any Behance user's profile picture by their username.
563
579
 
564
580
  e.g., [unavatar.io/behance/vitormatosinhos](https://unavatar.io/behance/vitormatosinhos)
565
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
+
566
588
  ### Bluesky
567
589
 
568
590
  Get any Bluesky user's profile picture by their handle. Domain-style handles are supported.
@@ -596,6 +618,21 @@ Get any Cursor user's profile picture by their username.
596
618
 
597
619
  e.g., [unavatar.io/cursor/leerob](https://unavatar.io/cursor/leerob)
598
620
 
621
+ ### Deezer
622
+
623
+ Get artwork for any Deezer entity — artists, albums, playlists, or tracks. Look up by Deezer ID.
624
+
625
+ By default the input is treated as an artist:
626
+
627
+ e.g., [unavatar.io/deezer/27](https://unavatar.io/deezer/27)
628
+
629
+ The input also supports a URI format `type:id`:
630
+
631
+ - `artist` (default): [unavatar.io/deezer/artist:27](https://unavatar.io/deezer/artist:27)
632
+ - `album`: [unavatar.io/deezer/album:302127](https://unavatar.io/deezer/album:302127)
633
+ - `playlist`: [unavatar.io/deezer/playlist:908622995](https://unavatar.io/deezer/playlist:908622995)
634
+ - `track`: [unavatar.io/deezer/track:3135556](https://unavatar.io/deezer/track:3135556)
635
+
599
636
  ### DeviantArt
600
637
 
601
638
  Get any DeviantArt user's profile picture by their username.
@@ -623,6 +660,12 @@ Retrieve logos and favicons from any hostname with a single URL. Domain-shaped p
623
660
 
624
661
  e.g., [unavatar.io/domain/microlink.io](https://unavatar.io/domain/microlink.io)
625
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
+
626
669
  ### Dribbble
627
670
 
628
671
  Get any Dribbble designer's profile picture by their username.
@@ -679,6 +722,12 @@ Available inputs:
679
722
  - `user` (default): [unavatar.io/flickr/user:elizabethgadd](https://unavatar.io/flickr/user:elizabethgadd)
680
723
  - `group`: [unavatar.io/flickr/group:allthingsearthy](https://unavatar.io/flickr/group:allthingsearthy)
681
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
+
682
731
  ### GitHub
683
732
 
684
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.
@@ -752,6 +801,12 @@ Get any Instagram user's profile picture by their username. No authentication or
752
801
 
753
802
  e.g., [unavatar.io/instagram/willsmith](https://unavatar.io/instagram/willsmith)
754
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
+
755
810
  ### Ko-fi
756
811
 
757
812
  Get any Ko-fi page's profile picture by the creator username.
@@ -787,6 +842,12 @@ Extract the logo or representative image from any URL. The page is rendered and
787
842
 
788
843
  e.g., [unavatar.io/microlink/microlink.io](https://unavatar.io/microlink/microlink.io)
789
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
+
790
851
  ### npm
791
852
 
792
853
  Get any npm user's profile picture by their username, including scoped handles.
@@ -851,6 +912,14 @@ Get any PlayStation Network user's profile picture by their PSN username.
851
912
 
852
913
  e.g., [unavatar.io/psnprofiles/Duff85](https://unavatar.io/psnprofiles/Duff85)
853
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
+
854
923
  ### Raycast
855
924
 
856
925
  Get any Raycast user's profile picture by their username.
@@ -986,6 +1055,12 @@ Get any Vimeo user's profile picture by their username.
986
1055
 
987
1056
  e.g., [unavatar.io/vimeo/ladieswithlenses](https://unavatar.io/vimeo/ladieswithlenses)
988
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
+
989
1064
  ### WhatsApp
990
1065
 
991
1066
  Get the profile picture for a WhatsApp channel or chat by ID.
@@ -1025,6 +1100,12 @@ Available inputs:
1025
1100
  - `username`: [unavatar.io/youtube/casey](https://unavatar.io/youtube/casey)
1026
1101
  - `channel`: [unavatar.io/youtube/UC_x5XG1OV2P6uZZ5FSM9Ttw](https://unavatar.io/youtube/UC_x5XG1OV2P6uZZ5FSM9Ttw)
1027
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
+
1028
1109
  ## Response Format
1029
1110
 
1030
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.43.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,32 @@
1
+ 'use strict'
2
+
3
+ // Entities without artwork keep an empty image hash
4
+ // (cdn-images.dzcdn.net/images/artist//500x500.jpg), which resolves to a
5
+ // generic placeholder, so any empty hash segment is treated as a miss.
6
+ const isArtwork = url => !/\/images\/[^/]+\/\/\d/.test(url)
7
+
8
+ const getAvatarUrl = input => {
9
+ const [first, second] = input.split(':')
10
+ const type = second ? first : 'artist'
11
+ const id = second ?? first
12
+ return `https://www.deezer.com/en/${type}/${id}`
13
+ }
14
+
15
+ // Deezer server-renders og:image for missing entities as well, so the empty
16
+ // placeholder is filtered out and everything else is returned as-is.
17
+ const getAvatar = ({ $, getOgImage, NOT_FOUND }) => {
18
+ const image = getOgImage($)
19
+ return image && isArtwork(image) ? image : NOT_FOUND
20
+ }
21
+
22
+ const factory = ({ createHtmlProvider, getOgImage, NOT_FOUND }) =>
23
+ createHtmlProvider({
24
+ name: 'deezer',
25
+ url: getAvatarUrl,
26
+ getter: $ => getAvatar({ $, getOgImage, NOT_FOUND })
27
+ })
28
+
29
+ factory.getAvatarUrl = getAvatarUrl
30
+ factory.getAvatar = getAvatar
31
+
32
+ module.exports = factory
@@ -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,28 +6,34 @@ const providersBy = {
6
6
  'apple-music',
7
7
  'apple-store',
8
8
  'behance',
9
+ 'bilibili',
9
10
  'bluesky',
10
11
  'buymeacoffee',
11
12
  'cashapp',
12
13
  'codepen',
13
14
  'cults3d',
14
15
  'cursor',
16
+ 'deezer',
15
17
  'deviantart',
16
18
  'discord',
17
19
  'dockerhub',
20
+ 'douban',
18
21
  'dribbble',
19
22
  'facebook',
20
23
  'flickr',
24
+ 'gitee',
21
25
  'github',
22
26
  'gitlab',
23
27
  'google-play',
24
28
  'huggingface',
25
29
  'hevy',
26
30
  'instagram',
31
+ 'juejin',
27
32
  'ko-fi',
28
33
  'linkedin',
29
34
  'mastodon',
30
35
  'medium',
36
+ 'netease-music',
31
37
  'npm',
32
38
  'onlyfans',
33
39
  'openstreetmap',
@@ -52,15 +58,18 @@ const providersBy = {
52
58
  'thingiverse',
53
59
  'threads',
54
60
  'tidal',
61
+ 'tieba',
55
62
  'tiktok',
56
63
  'tumblr',
57
64
  'twitch',
58
65
  'venmo',
59
66
  'vimeo',
67
+ 'weibo',
60
68
  'wise',
61
69
  'x',
62
70
  'xboxgamertag',
63
- 'youtube'
71
+ 'youtube',
72
+ 'zhihu'
64
73
  ],
65
74
  domain: ['microlink']
66
75
  }
@@ -70,19 +79,23 @@ module.exports = ctx => {
70
79
  'apple-music': require('./apple-music')(ctx),
71
80
  'apple-store': require('./apple-store')(ctx),
72
81
  behance: require('./behance')(ctx),
82
+ bilibili: require('./bilibili')(ctx),
73
83
  bluesky: require('./bluesky')(ctx),
74
84
  buymeacoffee: require('./buymeacoffee')(ctx),
75
85
  cashapp: require('./cashapp')(ctx),
76
86
  codepen: require('./codepen')(ctx),
77
87
  cults3d: require('./cults3d')(ctx),
78
88
  cursor: require('./cursor')(ctx),
89
+ deezer: require('./deezer')(ctx),
79
90
  deviantart: require('./deviantart')(ctx),
80
91
  discord: require('./discord')(ctx),
81
92
  dockerhub: require('./dockerhub')(ctx),
93
+ douban: require('./douban')(ctx),
82
94
  dribbble: require('./dribbble')(ctx),
83
95
  duckduckgo: require('./duckduckgo')(ctx),
84
96
  facebook: require('./facebook')(ctx),
85
97
  flickr: require('./flickr')(ctx),
98
+ gitee: require('./gitee')(ctx),
86
99
  github: require('./github')(ctx),
87
100
  gitlab: require('./gitlab')(ctx),
88
101
  google: require('./google')(ctx),
@@ -91,11 +104,13 @@ module.exports = ctx => {
91
104
  huggingface: require('./huggingface')(ctx),
92
105
  hevy: require('./hevy')(ctx),
93
106
  instagram: require('./instagram')(ctx),
107
+ juejin: require('./juejin')(ctx),
94
108
  'ko-fi': require('./ko-fi')(ctx),
95
109
  linkedin: require('./linkedin')(ctx),
96
110
  mastodon: require('./mastodon')(ctx),
97
111
  medium: require('./medium')(ctx),
98
112
  microlink: require('./microlink')(ctx),
113
+ 'netease-music': require('./netease-music')(ctx),
99
114
  npm: require('./npm')(ctx),
100
115
  onlyfans: require('./onlyfans')(ctx),
101
116
  openstreetmap: require('./openstreetmap')(ctx),
@@ -106,6 +121,7 @@ module.exports = ctx => {
106
121
  primal: require('./primal')(ctx),
107
122
  producthunt: require('./producthunt')(ctx),
108
123
  psnprofiles: require('./psnprofiles')(ctx),
124
+ qq: require('./qq')(ctx),
109
125
  raycast: require('./raycast')(ctx),
110
126
  reddit: require('./reddit')(ctx),
111
127
  revolut: require('./revolut')(ctx),
@@ -120,16 +136,19 @@ module.exports = ctx => {
120
136
  thingiverse: require('./thingiverse')(ctx),
121
137
  threads: require('./threads')(ctx),
122
138
  tidal: require('./tidal')(ctx),
139
+ tieba: require('./tieba')(ctx),
123
140
  tiktok: require('./tiktok')(ctx),
124
141
  tumblr: require('./tumblr')(ctx),
125
142
  twitch: require('./twitch')(ctx),
126
143
  venmo: require('./venmo')(ctx),
127
144
  vimeo: require('./vimeo')(ctx),
145
+ weibo: require('./weibo')(ctx),
128
146
  whatsapp: require('./whatsapp')(ctx),
129
147
  wise: require('./wise')(ctx),
130
148
  x: require('./x')(ctx),
131
149
  xboxgamertag: require('./xboxgamertag')(ctx),
132
- youtube: require('./youtube')(ctx)
150
+ youtube: require('./youtube')(ctx),
151
+ zhihu: require('./zhihu')(ctx)
133
152
  }
134
153
 
135
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