@unavatar/core 3.23.0 → 3.24.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 +5 -4
- package/package.json +1 -1
- package/src/index.js +5 -0
- package/src/providers/apple-music.js +14 -21
- package/src/providers/apple-store.js +126 -0
- package/src/providers/flickr.js +14 -5
- package/src/providers/index.js +2 -0
- package/src/util/itunes-search.js +24 -0
package/README.md
CHANGED
|
@@ -68,13 +68,14 @@ It's proudly powered by [microlink.io](https://microlink.io/), the headless brow
|
|
|
68
68
|
|
|
69
69
|
The service is exposed in **unavatar.io** via provider endpoints:
|
|
70
70
|
|
|
71
|
-
- an **email
|
|
71
|
+
- an **email**: [unavatar.io/email/hello@microlink.io](https://unavatar.io/email/hello@microlink.io)
|
|
72
|
+
- a **domain**: [unavatar.io/domain/reddit.com](https://unavatar.io/domain/reddit.com)
|
|
72
73
|
- an **email** via Gravatar: [unavatar.io/gravatar/hello@microlink.io](https://unavatar.io/gravatar/hello@microlink.io)
|
|
73
74
|
- an **email** via GitHub: [unavatar.io/github/sindresorhus@gmail.com](https://unavatar.io/github/sindresorhus@gmail.com)
|
|
74
75
|
- an **username**: [unavatar.io/github/kikobeats](https://unavatar.io/github/kikobeats)
|
|
75
76
|
- a **domain**: [unavatar.io/google/reddit.com](https://unavatar.io/google/reddit.com)
|
|
76
77
|
|
|
77
|
-
Use
|
|
78
|
+
Use `/email/:key` and `/domain/:key` for type-based resolution. You can read more in [Email avatars](https://unavatar.io/email) and [providers](https://unavatar.io/docs#providers).
|
|
78
79
|
|
|
79
80
|
## Authentication
|
|
80
81
|
|
|
@@ -651,7 +652,7 @@ Available inputs:
|
|
|
651
652
|
|
|
652
653
|
### YouTube
|
|
653
654
|
|
|
654
|
-
Get any YouTube channel's thumbnail by their handle,
|
|
655
|
+
Get any YouTube channel's thumbnail by their handle, username, or channel ID.
|
|
655
656
|
|
|
656
657
|
e.g., [unavatar.io/youtube/casey](https://unavatar.io/youtube/casey)
|
|
657
658
|
|
|
@@ -745,4 +746,4 @@ Expected errors are known operational cases returned with stable codes.
|
|
|
745
746
|
|
|
746
747
|
## Contact
|
|
747
748
|
|
|
748
|
-
If you have any suggestion or bug to report, please contact to ust mailing to [hello@unavatar.io](mailto:hello@unavatar.io).
|
|
749
|
+
If you have any suggestion or bug to report, please contact to ust mailing to [hello@unavatar.io](mailto:hello@unavatar.io).
|
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.24.1",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/index.js",
|
package/src/index.js
CHANGED
|
@@ -61,6 +61,11 @@ module.exports = ({ constants: userConstants, redis, onFetchHTML } = {}) => {
|
|
|
61
61
|
unavatar[name] = input => getAvatar(providers[name], name, input, {})
|
|
62
62
|
})
|
|
63
63
|
|
|
64
|
+
const createTypedAutoResolver = inputType => input => auto(inputType)(input, {})
|
|
65
|
+
|
|
66
|
+
unavatar.email = createTypedAutoResolver('email')
|
|
67
|
+
unavatar.domain = createTypedAutoResolver('domain')
|
|
68
|
+
|
|
64
69
|
unavatar.auto = auto
|
|
65
70
|
unavatar.getInputType = getInputType
|
|
66
71
|
unavatar.getAvatar = getAvatar
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { $jsonld } = require('@metascraper/helpers')
|
|
4
|
-
const
|
|
4
|
+
const { createGetSearchResults } = require('../util/itunes-search')
|
|
5
5
|
|
|
6
6
|
const APPLE_MUSIC_ID_REGEX = /^\d+$/
|
|
7
7
|
const APPLE_MUSIC_STOREFRONT = 'us'
|
|
@@ -36,26 +36,19 @@ const parseInput = input => {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
module.exports = ({ createHtmlProvider, itunesSearchCache, got }) => {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
)}&entity=${entity}&limit=1`
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const entityId = body?.results?.[0]?.[idKey]
|
|
54
|
-
return isNumericId(entityId) ? String(entityId) : null
|
|
55
|
-
},
|
|
56
|
-
itunesSearchCache,
|
|
57
|
-
{ key: ({ query, type }) => `${type}:${query.trim().toLowerCase()}` }
|
|
58
|
-
)
|
|
39
|
+
const getSearchResults = createGetSearchResults({ got, itunesSearchCache })
|
|
40
|
+
|
|
41
|
+
const searchEntityId = async ({ query, type }) => {
|
|
42
|
+
const entityConfig = APPLE_MUSIC_ENTITY_TYPES[type]
|
|
43
|
+
if (!entityConfig) return
|
|
44
|
+
const { entity, idKey } = entityConfig
|
|
45
|
+
|
|
46
|
+
const [result] = await getSearchResults(
|
|
47
|
+
`term=${encodeURIComponent(query)}&entity=${entity}&limit=1`
|
|
48
|
+
)
|
|
49
|
+
const entityId = result?.[idKey]
|
|
50
|
+
return isNumericId(entityId) ? String(entityId) : null
|
|
51
|
+
}
|
|
59
52
|
|
|
60
53
|
const appleMusicURI = async input => {
|
|
61
54
|
const { hasExplicitType, type, id } = parseInput(input)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { createGetSearchResults } = require('../util/itunes-search')
|
|
4
|
+
|
|
5
|
+
const ITUNES_LOOKUP_URL = 'https://itunes.apple.com/lookup'
|
|
6
|
+
const COUNTRY_CODE_REGEX = /^[a-z]{2}$/i
|
|
7
|
+
|
|
8
|
+
const getArtworkUrl = result =>
|
|
9
|
+
result?.artworkUrl512 || result?.artworkUrl100 || result?.artworkUrl60
|
|
10
|
+
|
|
11
|
+
const withCountry = ({ query, country }) =>
|
|
12
|
+
country ? `${query}&country=${encodeURIComponent(country)}` : query
|
|
13
|
+
|
|
14
|
+
const parseInput = input => {
|
|
15
|
+
const separatorIndex = input.indexOf(':')
|
|
16
|
+
if (separatorIndex === -1) return { type: 'app', value: input }
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
type: input.slice(0, separatorIndex),
|
|
20
|
+
value: input.slice(separatorIndex + 1)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const parseCountry = value => {
|
|
25
|
+
const separatorIndex = value.lastIndexOf('@')
|
|
26
|
+
if (separatorIndex === -1) return { value, country: undefined }
|
|
27
|
+
|
|
28
|
+
const country = value.slice(separatorIndex + 1)
|
|
29
|
+
if (!COUNTRY_CODE_REGEX.test(country)) return { value, country: undefined }
|
|
30
|
+
|
|
31
|
+
const nextValue = value.slice(0, separatorIndex)
|
|
32
|
+
if (!nextValue) return { value, country: undefined }
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
value: nextValue,
|
|
36
|
+
country: country.toLowerCase()
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const getLookupResults = async ({ got, query }) => {
|
|
41
|
+
const body = await got(`${ITUNES_LOOKUP_URL}?${query}`, {
|
|
42
|
+
responseType: 'json',
|
|
43
|
+
resolveBodyOnly: true
|
|
44
|
+
})
|
|
45
|
+
return body?.results ?? []
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const getAppAvatar = async ({ got, id, country }) => {
|
|
49
|
+
const query = withCountry({
|
|
50
|
+
query: `id=${encodeURIComponent(id)}&entity=software&limit=1`,
|
|
51
|
+
country
|
|
52
|
+
})
|
|
53
|
+
const [result] = await getLookupResults({ got, query })
|
|
54
|
+
return getArtworkUrl(result)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const getDeveloperAvatar = async ({ got, id, country }) => {
|
|
58
|
+
const query = withCountry({
|
|
59
|
+
query: `id=${encodeURIComponent(id)}&entity=software&limit=200`,
|
|
60
|
+
country
|
|
61
|
+
})
|
|
62
|
+
const results = await getLookupResults({ got, query })
|
|
63
|
+
const softwareResult = results.find(result => result?.kind === 'software')
|
|
64
|
+
return getArtworkUrl(softwareResult)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const getBundleAvatar = async ({ got, bundleId, country }) => {
|
|
68
|
+
const query = withCountry({
|
|
69
|
+
query: `bundleId=${encodeURIComponent(bundleId)}&entity=software&limit=1`,
|
|
70
|
+
country
|
|
71
|
+
})
|
|
72
|
+
const [result] = await getLookupResults({ got, query })
|
|
73
|
+
return getArtworkUrl(result)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const getAppNameAvatar = async ({ got, name, country, searchResults }) => {
|
|
77
|
+
const getSearchResults = searchResults || createGetSearchResults({ got })
|
|
78
|
+
const query = withCountry({
|
|
79
|
+
query: `term=${encodeURIComponent(name)}&entity=software&limit=1`,
|
|
80
|
+
country
|
|
81
|
+
})
|
|
82
|
+
const [result] = await getSearchResults(query)
|
|
83
|
+
return getArtworkUrl(result)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const getDeveloperNameAvatar = async ({ got, name, country, searchResults }) => {
|
|
87
|
+
const getSearchResults = searchResults || createGetSearchResults({ got })
|
|
88
|
+
const query = withCountry({
|
|
89
|
+
query: `term=${encodeURIComponent(
|
|
90
|
+
name
|
|
91
|
+
)}&entity=software&attribute=softwareDeveloper&limit=1`,
|
|
92
|
+
country
|
|
93
|
+
})
|
|
94
|
+
const [result] = await getSearchResults(query)
|
|
95
|
+
return getArtworkUrl(result)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = ({ got, itunesSearchCache }) => {
|
|
99
|
+
const searchResults = createGetSearchResults({ got, itunesSearchCache })
|
|
100
|
+
|
|
101
|
+
return async function appleStore (input) {
|
|
102
|
+
const { type, value: rawValue } = parseInput(input)
|
|
103
|
+
const { value, country } = parseCountry(rawValue)
|
|
104
|
+
|
|
105
|
+
switch (type) {
|
|
106
|
+
case 'app':
|
|
107
|
+
return getAppAvatar({ got, id: value, country })
|
|
108
|
+
case 'dev':
|
|
109
|
+
return getDeveloperAvatar({ got, id: value, country })
|
|
110
|
+
case 'bundle':
|
|
111
|
+
return getBundleAvatar({ got, bundleId: value, country })
|
|
112
|
+
case 'app-name':
|
|
113
|
+
return getAppNameAvatar({ got, name: value, country, searchResults })
|
|
114
|
+
case 'dev-name':
|
|
115
|
+
return getDeveloperNameAvatar({ got, name: value, country, searchResults })
|
|
116
|
+
default:
|
|
117
|
+
throw new Error(`Unsupported Apple Store type: ${type}`)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports.getAppAvatar = getAppAvatar
|
|
123
|
+
module.exports.getDeveloperAvatar = getDeveloperAvatar
|
|
124
|
+
module.exports.getBundleAvatar = getBundleAvatar
|
|
125
|
+
module.exports.getAppNameAvatar = getAppNameAvatar
|
|
126
|
+
module.exports.getDeveloperNameAvatar = getDeveloperNameAvatar
|
package/src/providers/flickr.js
CHANGED
|
@@ -4,24 +4,33 @@ const BUDDY_ICON_URL_PATTERN = /(?:https?:)?\/\/[^"'()\s]*\/buddyicons\/[^"'()\s
|
|
|
4
4
|
const CSS_URL_PATTERN = /url\((['"]?)([^)'"]+)\1\)/i
|
|
5
5
|
const unescapePathSlashes = value => value?.replace(/\\\//g, '/')
|
|
6
6
|
|
|
7
|
+
const parseInput = input => {
|
|
8
|
+
const [first, second] = input.split(':')
|
|
9
|
+
return {
|
|
10
|
+
type: second ? first : 'user',
|
|
11
|
+
id: second ?? first
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
7
15
|
const getBuddyIconFromText = value => {
|
|
8
16
|
const normalizedValue = unescapePathSlashes(value)
|
|
9
17
|
if (typeof normalizedValue !== 'string') return
|
|
10
18
|
return normalizedValue.match(BUDDY_ICON_URL_PATTERN)?.[0]
|
|
11
19
|
}
|
|
12
20
|
|
|
21
|
+
const getUserProfileUrl = userId => `https://www.flickr.com/photos/${userId}/`
|
|
22
|
+
const getGroupProfileUrl = groupId => `https://www.flickr.com/groups/${groupId}/`
|
|
23
|
+
|
|
13
24
|
const getAvatarUrl = input => {
|
|
14
|
-
const
|
|
15
|
-
const type = second ? first : 'user'
|
|
16
|
-
const id = second ?? first
|
|
25
|
+
const { type, id } = parseInput(input)
|
|
17
26
|
|
|
18
27
|
switch (type) {
|
|
19
28
|
case 'user':
|
|
20
29
|
case 'photos':
|
|
21
|
-
return
|
|
30
|
+
return getUserProfileUrl(id)
|
|
22
31
|
case 'group':
|
|
23
32
|
case 'groups':
|
|
24
|
-
return
|
|
33
|
+
return getGroupProfileUrl(id)
|
|
25
34
|
default:
|
|
26
35
|
throw new Error(`Unsupported Flickr type: ${type}`)
|
|
27
36
|
}
|
package/src/providers/index.js
CHANGED
|
@@ -4,6 +4,7 @@ const providersBy = {
|
|
|
4
4
|
email: ['gravatar', 'github'],
|
|
5
5
|
username: [
|
|
6
6
|
'apple-music',
|
|
7
|
+
'apple-store',
|
|
7
8
|
'behance',
|
|
8
9
|
'bluesky',
|
|
9
10
|
'buymeacoffee',
|
|
@@ -49,6 +50,7 @@ const providersBy = {
|
|
|
49
50
|
module.exports = ctx => {
|
|
50
51
|
const providers = {
|
|
51
52
|
'apple-music': require('./apple-music')(ctx),
|
|
53
|
+
'apple-store': require('./apple-store')(ctx),
|
|
52
54
|
behance: require('./behance')(ctx),
|
|
53
55
|
bluesky: require('./bluesky')(ctx),
|
|
54
56
|
buymeacoffee: require('./buymeacoffee')(ctx),
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const memoize = require('@keyvhq/memoize')
|
|
4
|
+
|
|
5
|
+
const ITUNES_SEARCH_URL = 'https://itunes.apple.com/search'
|
|
6
|
+
|
|
7
|
+
const getSearchResults = async ({ got, query }) => {
|
|
8
|
+
const body = await got(`${ITUNES_SEARCH_URL}?${query}`, {
|
|
9
|
+
responseType: 'json',
|
|
10
|
+
resolveBodyOnly: true
|
|
11
|
+
})
|
|
12
|
+
return body?.results ?? []
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const createGetSearchResults = ({ got, itunesSearchCache }) => {
|
|
16
|
+
const searchResults = query => getSearchResults({ got, query })
|
|
17
|
+
if (!itunesSearchCache) return searchResults
|
|
18
|
+
|
|
19
|
+
return memoize(searchResults, itunesSearchCache, {
|
|
20
|
+
key: query => query.trim().toLowerCase()
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = { createGetSearchResults }
|