@unavatar/core 3.45.26 → 3.46.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/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.45.26",
5
+ "version": "3.46.0",
6
6
  "main": "src/index.js",
7
7
  "exports": {
8
8
  ".": "./src/index.js",
@@ -128,6 +128,7 @@
128
128
  "@metascraper/helpers": "~5.52.0",
129
129
  "@microlink/mql": "~0.17.0",
130
130
  "@microlink/ping-url": "~1.4.16",
131
+ "bimi-url": "~1.0.0",
131
132
  "browserless": "~13.6.0",
132
133
  "cacheable-lookup": "~6.1.0",
133
134
  "data-uri-regex": "~0.1.4",
@@ -23,14 +23,8 @@ const getInputType = input => {
23
23
  return 'username'
24
24
  }
25
25
 
26
- const factory = ({ constants, providers, providersBy, reachableUrl }) => {
26
+ const factory = ({ constants, providers, providerTiers, reachableUrl }) => {
27
27
  const { REQUEST_TIMEOUT } = constants
28
- const providerEntriesByType = Object.fromEntries(
29
- Object.entries(providersBy).map(([inputType, providerNames]) => [
30
- inputType,
31
- providerNames.map(provider => [provider, providers[provider]])
32
- ])
33
- )
34
28
 
35
29
  const getAvatarContent = provider => async output => {
36
30
  if (typeof output !== 'string' || output === '') {
@@ -68,7 +62,13 @@ const factory = ({ constants, providers, providersBy, reachableUrl }) => {
68
62
  return { type: 'url', data: url, provider }
69
63
  }
70
64
 
71
- const getAvatar = async (fn, provider, input, context) => {
65
+ const getAvatar = async (
66
+ fn,
67
+ provider,
68
+ input,
69
+ context,
70
+ deadline = Date.now() + REQUEST_TIMEOUT
71
+ ) => {
72
72
  const promise = Promise.resolve(fn(input, context))
73
73
  .then(getAvatarContent(provider))
74
74
  .catch(error => {
@@ -79,32 +79,49 @@ const factory = ({ constants, providers, providersBy, reachableUrl }) => {
79
79
  throw error
80
80
  })
81
81
 
82
- return pTimeout(promise, REQUEST_TIMEOUT).catch(error => {
83
- error.provider = provider
84
- throw error
85
- })
82
+ return pTimeout(promise, Math.max(0, deadline - Date.now())).catch(
83
+ error => {
84
+ error.provider = provider
85
+ throw error
86
+ }
87
+ )
86
88
  }
87
89
 
88
- const resolveAutoByType = async (inputType, input, context) => {
89
- let collection = providerEntriesByType[inputType]
90
+ const raceTier = (tier, input, context, deadline) =>
91
+ pAny(
92
+ tier.map(provider =>
93
+ getAvatar(providers[provider], provider, input, context, deadline)
94
+ )
95
+ )
96
+
97
+ const auto = inputType => async (input, context) => {
98
+ const tierKey =
99
+ inputType === 'email' && isHash(input) ? 'emailHash' : inputType
100
+ const tiers = providerTiers[tierKey]
90
101
 
91
- if (inputType === 'email' && isHash(input)) {
92
- collection = collection.filter(([provider]) => provider === 'gravatar')
102
+ if (!tiers?.length) {
103
+ throw new ExtendableError({
104
+ message: `No providers declared for \`${tierKey}\`.`,
105
+ statusCode: httpStatus.NOT_FOUND
106
+ })
93
107
  }
94
108
 
95
- const promises = new Array(collection.length)
109
+ const deadline = Date.now() + REQUEST_TIMEOUT
110
+
111
+ let firstError
96
112
 
97
- for (let index = 0; index < collection.length; index++) {
98
- const [provider, fn] = collection[index]
99
- promises[index] = getAvatar(fn, provider, input, context)
113
+ for (const tier of tiers) {
114
+ try {
115
+ return await raceTier(tier, input, context, deadline)
116
+ } catch (error) {
117
+ firstError ??= error
118
+ if (Date.now() >= deadline) break
119
+ }
100
120
  }
101
121
 
102
- return pAny(promises)
122
+ throw firstError
103
123
  }
104
124
 
105
- const auto = inputType => (input, context) =>
106
- resolveAutoByType(inputType, input, context)
107
-
108
125
  return { auto, getInputType, getAvatar }
109
126
  }
110
127
 
package/src/index.js CHANGED
@@ -22,9 +22,11 @@ module.exports = ({
22
22
  redis
23
23
  })
24
24
  const cache = require('./util/cache')({ createMultiCache, createRedisCache })
25
+ const dnsResolver = require('./util/dns-resolver')(constants)
25
26
  const cacheableLookup = require('./util/cacheable-lookup')({
26
27
  ...constants,
27
- cache: cache.dnsCache
28
+ cache: cache.dnsCache,
29
+ resolver: dnsResolver
28
30
  })
29
31
  const isReservedIp = require('./util/is-reserved-ip')({ cacheableLookup })
30
32
  const got = require('./util/got')({ cacheableLookup })
@@ -34,13 +36,16 @@ module.exports = ({
34
36
  })
35
37
  const createBrowser = require('./util/browserless')(constants)
36
38
  const getHTML = require('./util/html-get')({ createBrowser, got })
37
- const { createHtmlProvider, getOgImage, NOT_FOUND } =
38
- require('./util/html-provider')({
39
- ...constants,
40
- getHTML,
41
- onFetchHTML,
42
- userAgent
43
- })
39
+ const {
40
+ createHtmlProvider,
41
+ getOgImage,
42
+ NOT_FOUND
43
+ } = require('./util/html-provider')({
44
+ ...constants,
45
+ getHTML,
46
+ onFetchHTML,
47
+ userAgent
48
+ })
44
49
 
45
50
  const providerCtx = {
46
51
  constants,
@@ -50,15 +55,19 @@ module.exports = ({
50
55
  got,
51
56
  reachableUrl,
52
57
  isReservedIp,
58
+ dnsResolver,
59
+ bimiCache: cache.bimiCache,
53
60
  githubSearchCache: cache.githubSearchCache,
54
61
  itunesSearchCache: cache.itunesSearchCache
55
62
  }
56
- const { providers, providersBy } = require('./providers')(providerCtx)
63
+ const { providers, providerTiers, providersBy } = require('./providers')(
64
+ providerCtx
65
+ )
57
66
 
58
67
  const { auto, getInputType, getAvatar } = require('./avatar/auto')({
59
68
  constants,
60
69
  providers,
61
- providersBy,
70
+ providerTiers,
62
71
  reachableUrl
63
72
  })
64
73
 
@@ -0,0 +1,22 @@
1
+ 'use strict'
2
+
3
+ const createGetLogo = require('bimi-url')
4
+
5
+ const parseInput = input => input.slice(input.lastIndexOf('@') + 1)
6
+
7
+ module.exports = ({ bimiCache, dnsResolver, got, isReservedIp }) => {
8
+ const getLogo = createGetLogo({
9
+ gotOpts: got.gotOpts,
10
+ keyvOpts: bimiCache,
11
+ resolveTxt: hostname => dnsResolver.resolveTxt(hostname)
12
+ })
13
+
14
+ return async function bimi (input) {
15
+ const logoUrl = await getLogo(parseInput(input))
16
+ if (!logoUrl) return undefined
17
+ if (await isReservedIp(new URL(logoUrl).hostname)) return undefined
18
+ return logoUrl
19
+ }
20
+ }
21
+
22
+ module.exports.parseInput = parseInput
@@ -1,86 +1,99 @@
1
1
  'use strict'
2
2
 
3
- const providersBy = {
4
- email: ['gravatar', 'github'],
3
+ const providerTiers = {
4
+ email: [['gravatar', 'github'], ['bimi']],
5
+ emailHash: [['gravatar']],
5
6
  username: [
6
- 'apple-music',
7
- 'apple-store',
8
- 'behance',
9
- 'bilibili',
10
- 'bluesky',
11
- 'buymeacoffee',
12
- 'cashapp',
13
- 'codepen',
14
- 'cults3d',
15
- 'cursor',
16
- 'deezer',
17
- 'deviantart',
18
- 'discord',
19
- 'dockerhub',
20
- 'dribbble',
21
- 'facebook',
22
- 'flickr',
23
- 'gitee',
24
- 'github',
25
- 'gitlab',
26
- 'google-play',
27
- 'huggingface',
28
- 'hevy',
29
- 'instagram',
30
- 'juejin',
31
- 'ko-fi',
32
- 'linkedin',
33
- 'mastodon',
34
- 'medium',
35
- 'netease-music',
36
- 'npm',
37
- 'onlyfans',
38
- 'openstreetmap',
39
- 'patreon',
40
- 'paypal',
41
- 'pinterest',
42
- 'printables',
43
- 'primal',
44
- 'producthunt',
45
- 'psnprofiles',
46
- 'qq',
47
- 'raycast',
48
- 'reddit',
49
- 'revolut',
50
- 'snapchat',
51
- 'soundcloud',
52
- 'spotify',
53
- 'stackoverflow',
54
- 'steam',
55
- 'strava',
56
- 'substack',
57
- 'telegram',
58
- 'thingiverse',
59
- 'threads',
60
- 'tidal',
61
- 'tieba',
62
- 'tiktok',
63
- 'tumblr',
64
- 'twitch',
65
- 'venmo',
66
- 'vimeo',
67
- 'weibo',
68
- 'whatsapp',
69
- 'wise',
70
- 'x',
71
- 'xboxgamertag',
72
- 'youtube',
73
- 'zhihu'
7
+ [
8
+ 'apple-music',
9
+ 'apple-store',
10
+ 'behance',
11
+ 'bilibili',
12
+ 'bluesky',
13
+ 'buymeacoffee',
14
+ 'cashapp',
15
+ 'codepen',
16
+ 'cults3d',
17
+ 'cursor',
18
+ 'deezer',
19
+ 'deviantart',
20
+ 'discord',
21
+ 'dockerhub',
22
+ 'dribbble',
23
+ 'facebook',
24
+ 'flickr',
25
+ 'gitee',
26
+ 'github',
27
+ 'gitlab',
28
+ 'google-play',
29
+ 'huggingface',
30
+ 'hevy',
31
+ 'instagram',
32
+ 'juejin',
33
+ 'ko-fi',
34
+ 'linkedin',
35
+ 'mastodon',
36
+ 'medium',
37
+ 'netease-music',
38
+ 'npm',
39
+ 'onlyfans',
40
+ 'openstreetmap',
41
+ 'patreon',
42
+ 'paypal',
43
+ 'pinterest',
44
+ 'printables',
45
+ 'primal',
46
+ 'producthunt',
47
+ 'psnprofiles',
48
+ 'qq',
49
+ 'raycast',
50
+ 'reddit',
51
+ 'revolut',
52
+ 'snapchat',
53
+ 'soundcloud',
54
+ 'spotify',
55
+ 'stackoverflow',
56
+ 'steam',
57
+ 'strava',
58
+ 'substack',
59
+ 'telegram',
60
+ 'thingiverse',
61
+ 'threads',
62
+ 'tidal',
63
+ 'tieba',
64
+ 'tiktok',
65
+ 'tumblr',
66
+ 'twitch',
67
+ 'venmo',
68
+ 'vimeo',
69
+ 'weibo',
70
+ 'whatsapp',
71
+ 'wise',
72
+ 'x',
73
+ 'xboxgamertag',
74
+ 'youtube',
75
+ 'zhihu'
76
+ ]
74
77
  ],
75
- domain: ['duckduckgo', 'google', 'microlink']
78
+ domain: [['bimi'], ['duckduckgo', 'google', 'microlink']]
76
79
  }
77
80
 
81
+ const { emailHash, ...publicTiers } = providerTiers
82
+
83
+ const providersBy = Object.fromEntries(
84
+ Object.entries(publicTiers).map(([inputType, tiers]) => [
85
+ inputType,
86
+ tiers.flat()
87
+ ])
88
+ )
89
+
78
90
  module.exports = ctx => {
79
91
  const providers = {
80
92
  'apple-music': require('./apple-music')(ctx),
81
93
  'apple-store': require('./apple-store')(ctx),
82
94
  behance: require('./behance')(ctx),
83
95
  bilibili: require('./bilibili')(ctx),
96
+ bimi: require('./bimi')(ctx),
84
97
  bluesky: require('./bluesky')(ctx),
85
98
  buymeacoffee: require('./buymeacoffee')(ctx),
86
99
  cashapp: require('./cashapp')(ctx),
@@ -151,5 +164,5 @@ module.exports = ctx => {
151
164
  zhihu: require('./zhihu')(ctx)
152
165
  }
153
166
 
154
- return { providers, providersBy }
167
+ return { providers, providerTiers, providersBy }
155
168
  }
package/src/util/cache.js CHANGED
@@ -3,11 +3,21 @@
3
3
  const ms = require('ms')
4
4
 
5
5
  module.exports = ({ createMultiCache, createRedisCache }) => ({
6
- dnsCache: createMultiCache(createRedisCache({ namespace: 'dns', ttl: ms('1d') })),
6
+ bimiCache: createMultiCache(
7
+ createRedisCache({ namespace: 'bimi', ttl: ms('1d') })
8
+ ),
9
+ dnsCache: createMultiCache(
10
+ createRedisCache({ namespace: 'dns', ttl: ms('1d') })
11
+ ),
7
12
  githubSearchCache: createRedisCache({
8
13
  namespace: 'github-search',
9
14
  ttl: ms('1d')
10
15
  }),
11
- pingCache: createMultiCache(createRedisCache({ namespace: 'ping', ttl: ms('1d') })),
12
- itunesSearchCache: createRedisCache({ namespace: 'itunes-search', ttl: ms('7d') })
16
+ pingCache: createMultiCache(
17
+ createRedisCache({ namespace: 'ping', ttl: ms('1d') })
18
+ ),
19
+ itunesSearchCache: createRedisCache({
20
+ namespace: 'itunes-search',
21
+ ttl: ms('7d')
22
+ })
13
23
  })
@@ -1,22 +1,10 @@
1
1
  'use strict'
2
2
 
3
3
  const CacheableLookup = require('cacheable-lookup')
4
- const Tangerine = require('tangerine')
5
4
 
6
- module.exports = ({ TTL_DEFAULT, DNS_TIMEOUT, DNS_SERVERS, cache }) =>
5
+ module.exports = ({ TTL_DEFAULT, cache, resolver }) =>
7
6
  new CacheableLookup({
8
7
  maxTtl: TTL_DEFAULT,
9
8
  cache,
10
- resolver: new Tangerine(
11
- {
12
- cache: false,
13
- timeout: DNS_TIMEOUT,
14
- servers: DNS_SERVERS
15
- },
16
- require('got').extend({
17
- responseType: 'buffer',
18
- decompress: false,
19
- retry: 0
20
- })
21
- )
9
+ resolver
22
10
  })
@@ -0,0 +1,18 @@
1
+ 'use strict'
2
+
3
+ const Tangerine = require('tangerine')
4
+ const got = require('got')
5
+
6
+ module.exports = ({ DNS_TIMEOUT, DNS_SERVERS }) =>
7
+ new Tangerine(
8
+ {
9
+ cache: false,
10
+ timeout: DNS_TIMEOUT,
11
+ servers: DNS_SERVERS
12
+ },
13
+ got.extend({
14
+ responseType: 'buffer',
15
+ decompress: false,
16
+ retry: 0
17
+ })
18
+ )