@unavatar/core 0.0.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 +407 -0
- package/bin/index.js +163 -0
- package/bin/unavatar +3 -0
- package/bin/unavatar-dev +3 -0
- package/package.json +192 -0
- package/src/avatar/auto.js +94 -0
- package/src/constant.js +18 -0
- package/src/index.js +51 -0
- package/src/providers/apple-music.js +97 -0
- package/src/providers/bluesky.js +10 -0
- package/src/providers/deviantart.js +8 -0
- package/src/providers/dribbble.js +8 -0
- package/src/providers/duckduckgo.js +6 -0
- package/src/providers/github.js +10 -0
- package/src/providers/gitlab.js +8 -0
- package/src/providers/google.js +6 -0
- package/src/providers/gravatar.js +15 -0
- package/src/providers/index.js +60 -0
- package/src/providers/instagram.js +8 -0
- package/src/providers/microlink.js +15 -0
- package/src/providers/onlyfans.js +20 -0
- package/src/providers/openstreetmap.js +20 -0
- package/src/providers/patreon.js +10 -0
- package/src/providers/reddit.js +9 -0
- package/src/providers/soundcloud.js +17 -0
- package/src/providers/spotify.js +18 -0
- package/src/providers/substack.js +35 -0
- package/src/providers/telegram.js +8 -0
- package/src/providers/tiktok.js +26 -0
- package/src/providers/twitch.js +8 -0
- package/src/providers/vimeo.js +8 -0
- package/src/providers/whatsapp.js +31 -0
- package/src/providers/x.js +35 -0
- package/src/providers/youtube.js +8 -0
- package/src/util/browserless.js +38 -0
- package/src/util/cacheable-lookup.js +22 -0
- package/src/util/error.js +6 -0
- package/src/util/got.js +32 -0
- package/src/util/html-get.js +25 -0
- package/src/util/html-provider.js +190 -0
- package/src/util/http-status.js +17 -0
- package/src/util/is-iterable.js +9 -0
- package/src/util/keyv.js +27 -0
- package/src/util/reachable-url.js +21 -0
- package/src/util/stringify.js +5 -0
package/package.json
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@unavatar/core",
|
|
3
|
+
"description": "Get unified user avatar from social networks, including Instagram, SoundCloud, Telegram, Twitter, YouTube & more.",
|
|
4
|
+
"homepage": "https://unavatar.io",
|
|
5
|
+
"version": "0.0.1",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"unavatar": "bin/unavatar",
|
|
9
|
+
"unavatar-dev": "bin/unavatar-dev"
|
|
10
|
+
},
|
|
11
|
+
"author": {
|
|
12
|
+
"email": "josefrancisco.verdu@gmail.com",
|
|
13
|
+
"name": "Kiko Beats",
|
|
14
|
+
"url": "https://kikobeats.com"
|
|
15
|
+
},
|
|
16
|
+
"contributors": [
|
|
17
|
+
{
|
|
18
|
+
"name": "Ben Croker",
|
|
19
|
+
"email": "57572400+bencroker@users.noreply.github.com"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "David Refoua",
|
|
23
|
+
"email": "David@Refoua.me"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "Alexander Schlindwein",
|
|
27
|
+
"email": "alexander.schlindwein@hotmail.de"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "Zadkiel",
|
|
31
|
+
"email": "hello@zadkiel.fr"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "Omid Nikrah",
|
|
35
|
+
"email": "omidnikrah@gmail.com"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "Nicolas Hedger",
|
|
39
|
+
"email": "649677+nhedger@users.noreply.github.com"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "Angel M De Miguel",
|
|
43
|
+
"email": "angel@bitnami.com"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "Tom Witkowski",
|
|
47
|
+
"email": "dev.gummibeer@gmail.com"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "Terence Eden",
|
|
51
|
+
"email": "edent@users.noreply.github.com"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "Hameed Rahamathullah",
|
|
55
|
+
"email": "hameedraha@gmail.com"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "Jahir Fiquitiva",
|
|
59
|
+
"email": "jahir.fiquitiva@gmail.com"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"name": "Mads Hougesen",
|
|
63
|
+
"email": "mads@mhouge.dk"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"name": "ぶーと / Yoshiaki Ueda",
|
|
67
|
+
"email": "oh@bootjp.me"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"name": "Reed Jones",
|
|
71
|
+
"email": "reedjones@reedjones.com"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"name": "Rodrigo Reis",
|
|
75
|
+
"email": "rodrigoreis22@yahoo.com.br"
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
"repository": {
|
|
79
|
+
"type": "git",
|
|
80
|
+
"url": "git+https://github.com/Kikobeats/unavatar.git"
|
|
81
|
+
},
|
|
82
|
+
"bugs": {
|
|
83
|
+
"url": "https://github.com/Kikobeats/unavatar/issues"
|
|
84
|
+
},
|
|
85
|
+
"keywords": [
|
|
86
|
+
"avatar",
|
|
87
|
+
"domain",
|
|
88
|
+
"email",
|
|
89
|
+
"github",
|
|
90
|
+
"gravatar",
|
|
91
|
+
"instagram",
|
|
92
|
+
"microlink",
|
|
93
|
+
"telegram",
|
|
94
|
+
"twitter",
|
|
95
|
+
"youtube"
|
|
96
|
+
],
|
|
97
|
+
"dependencies": {
|
|
98
|
+
"@keyvhq/compress": "~2.1.15",
|
|
99
|
+
"@keyvhq/core": "~2.1.15",
|
|
100
|
+
"@keyvhq/memoize": "~2.1.16",
|
|
101
|
+
"@keyvhq/multi": "~2.1.15",
|
|
102
|
+
"@keyvhq/redis": "~2.1.15",
|
|
103
|
+
"@kikobeats/got-scraping": "~4.2.0",
|
|
104
|
+
"@metascraper/helpers": "~5.49.24",
|
|
105
|
+
"@microlink/mql": "~0.14.2",
|
|
106
|
+
"@microlink/ping-url": "~1.4.16",
|
|
107
|
+
"browserless": "~10.10.0",
|
|
108
|
+
"cacheable-lookup": "~6.1.0",
|
|
109
|
+
"cheerio": "~1.2.0",
|
|
110
|
+
"data-uri-regex": "~0.1.4",
|
|
111
|
+
"debug-logfmt": "~1.4.7",
|
|
112
|
+
"got": "~11.8.6",
|
|
113
|
+
"html-get": "~2.21.21",
|
|
114
|
+
"https-tls": "~1.0.24",
|
|
115
|
+
"is-absolute-url": "~3.0.3",
|
|
116
|
+
"is-email-like": "~1.0.0",
|
|
117
|
+
"lodash": "~4.17.23",
|
|
118
|
+
"ms": "~2.1.3",
|
|
119
|
+
"p-any": "~3.0.0",
|
|
120
|
+
"p-cancelable": "2.1.1",
|
|
121
|
+
"p-timeout": "~4.1.0",
|
|
122
|
+
"puppeteer": "~24.37.5",
|
|
123
|
+
"srcset": "~4.0.0",
|
|
124
|
+
"tangerine": "~2.0.2",
|
|
125
|
+
"top-crawler-agents": "~1.0.34",
|
|
126
|
+
"top-user-agents": "~2.1.95",
|
|
127
|
+
"ua-hints": "~1.0.1",
|
|
128
|
+
"unique-random-array": "~2.0.0",
|
|
129
|
+
"url-regex": "~5.0.0"
|
|
130
|
+
},
|
|
131
|
+
"devDependencies": {
|
|
132
|
+
"@commitlint/cli": "latest",
|
|
133
|
+
"@commitlint/config-conventional": "latest",
|
|
134
|
+
"@ksmithut/prettier-standard": "latest",
|
|
135
|
+
"ava": "latest",
|
|
136
|
+
"c8": "latest",
|
|
137
|
+
"ci-publish": "latest",
|
|
138
|
+
"conventional-changelog-cli": "latest",
|
|
139
|
+
"finepack": "latest",
|
|
140
|
+
"git-authors-cli": "latest",
|
|
141
|
+
"github-generate-release": "latest",
|
|
142
|
+
"nano-staged": "latest",
|
|
143
|
+
"proxyquire": "latest",
|
|
144
|
+
"simple-git-hooks": "latest",
|
|
145
|
+
"sinon": "latest",
|
|
146
|
+
"standard": "latest",
|
|
147
|
+
"standard-version": "latest"
|
|
148
|
+
},
|
|
149
|
+
"engines": {
|
|
150
|
+
"node": "24"
|
|
151
|
+
},
|
|
152
|
+
"files": [
|
|
153
|
+
"bin",
|
|
154
|
+
"src"
|
|
155
|
+
],
|
|
156
|
+
"license": "MIT",
|
|
157
|
+
"commitlint": {
|
|
158
|
+
"extends": [
|
|
159
|
+
"@commitlint/config-conventional"
|
|
160
|
+
],
|
|
161
|
+
"rules": {
|
|
162
|
+
"body-max-line-length": [
|
|
163
|
+
0
|
|
164
|
+
]
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
"nano-staged": {
|
|
168
|
+
"*.js": [
|
|
169
|
+
"prettier-standard",
|
|
170
|
+
"standard --fix"
|
|
171
|
+
],
|
|
172
|
+
"package.json": [
|
|
173
|
+
"finepack"
|
|
174
|
+
]
|
|
175
|
+
},
|
|
176
|
+
"simple-git-hooks": {
|
|
177
|
+
"commit-msg": "npx commitlint --edit",
|
|
178
|
+
"pre-commit": "npx nano-staged"
|
|
179
|
+
},
|
|
180
|
+
"scripts": {
|
|
181
|
+
"contributors": "(git-authors-cli && finepack && git add package.json && git commit -m 'build: contributors' --no-verify) || true",
|
|
182
|
+
"postrelease": "pnpm release:tags && pnpm release:github && (ci-publish || pnpm publish --access=public)",
|
|
183
|
+
"release": "pnpm release:version && pnpm release:changelog && pnpm release:commit && pnpm release:tag",
|
|
184
|
+
"release:changelog": "conventional-changelog -p conventionalcommits -i CHANGELOG.md -s",
|
|
185
|
+
"release:commit": "git add package.json CHANGELOG.md && git commit -m \"chore(release): $(node -p \"require('./package.json').version\")\"",
|
|
186
|
+
"release:github": "github-generate-release",
|
|
187
|
+
"release:tag": "git tag -a v$(node -p \"require('./package.json').version\") -m \"v$(node -p \"require('./package.json').version\")\"",
|
|
188
|
+
"release:tags": "git push origin HEAD:master --follow-tags",
|
|
189
|
+
"release:version": "standard-version --skip.changelog --skip.commit --skip.tag",
|
|
190
|
+
"test": "c8 ava"
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const isAbsoluteUrl = require('is-absolute-url')
|
|
4
|
+
const dataUriRegex = require('data-uri-regex')
|
|
5
|
+
const isEmail = require('is-email-like')
|
|
6
|
+
const pTimeout = require('p-timeout')
|
|
7
|
+
const urlRegex = require('url-regex')
|
|
8
|
+
const pAny = require('p-any')
|
|
9
|
+
|
|
10
|
+
const httpStatus = require('../util/http-status')
|
|
11
|
+
const isIterable = require('../util/is-iterable')
|
|
12
|
+
const ExtendableError = require('../util/error')
|
|
13
|
+
|
|
14
|
+
const getInputType = input => {
|
|
15
|
+
if (isEmail(input)) return 'email'
|
|
16
|
+
if (urlRegex({ strict: false }).test(input)) return 'domain'
|
|
17
|
+
return 'username'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const factory = ({ constants, providers, providersBy, reachableUrl }) => {
|
|
21
|
+
const { REQUEST_TIMEOUT } = constants
|
|
22
|
+
|
|
23
|
+
const getAvatarContent = provider => async output => {
|
|
24
|
+
if (typeof output !== 'string' || output === '') {
|
|
25
|
+
const message = output === undefined ? 'not found' : `\`${output}\` is invalid`
|
|
26
|
+
const statusCode = output === undefined ? httpStatus.NOT_FOUND : httpStatus.UNPROCESSABLE_ENTITY
|
|
27
|
+
throw new ExtendableError({ provider, message, statusCode })
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (dataUriRegex().test(output)) {
|
|
31
|
+
return { type: 'buffer', data: output }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!isAbsoluteUrl(output)) {
|
|
35
|
+
throw new ExtendableError({
|
|
36
|
+
message: 'The URL must to be absolute.',
|
|
37
|
+
provider,
|
|
38
|
+
statusCode: 400
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const { statusCode, url } = await reachableUrl(output)
|
|
43
|
+
|
|
44
|
+
if (!reachableUrl.isReachable({ statusCode })) {
|
|
45
|
+
throw new ExtendableError({
|
|
46
|
+
message: httpStatus(statusCode),
|
|
47
|
+
provider,
|
|
48
|
+
statusCode
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { type: 'url', data: url, provider }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const getAvatar = async (fn, provider, args) => {
|
|
56
|
+
const promise = Promise.resolve(fn(args))
|
|
57
|
+
.then(getAvatarContent(provider))
|
|
58
|
+
.catch(error => {
|
|
59
|
+
isIterable.forEach(error, error => {
|
|
60
|
+
error.statusCode = error.statusCode ?? error.response?.statusCode
|
|
61
|
+
error.provider = provider
|
|
62
|
+
})
|
|
63
|
+
throw error
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
return pTimeout(promise, REQUEST_TIMEOUT).catch(error => {
|
|
67
|
+
error.provider = provider
|
|
68
|
+
throw error
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const resolveAutoByType = async (inputType, args) => {
|
|
73
|
+
const collection = providersBy[inputType]
|
|
74
|
+
const promises = collection.map(provider =>
|
|
75
|
+
pTimeout(getAvatar(providers[provider], provider, args), REQUEST_TIMEOUT)
|
|
76
|
+
)
|
|
77
|
+
return pAny(promises)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const auto = inputTypeOrArgs => {
|
|
81
|
+
if (typeof inputTypeOrArgs === 'string') {
|
|
82
|
+
return args => resolveAutoByType(inputTypeOrArgs, args)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const args = inputTypeOrArgs
|
|
86
|
+
return resolveAutoByType(getInputType(args.input), args)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return { auto, getInputType, getAvatar }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
factory.getInputType = getInputType
|
|
93
|
+
|
|
94
|
+
module.exports = factory
|
package/src/constant.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { existsSync } = require('fs')
|
|
4
|
+
const ms = require('ms')
|
|
5
|
+
|
|
6
|
+
const DEFAULTS = {
|
|
7
|
+
AVATAR_SIZE: 400,
|
|
8
|
+
REQUEST_TIMEOUT: 25000,
|
|
9
|
+
TTL_DEFAULT: ms('28d'),
|
|
10
|
+
TMP_FOLDER: existsSync('/dev/shm') ? '/dev/shm' : '/tmp',
|
|
11
|
+
DEBUG_HTML_TO_FILE: false,
|
|
12
|
+
PUPPETEER_RANDOM_DIR: false
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
DEFAULTS.PROXY_TIMEOUT = Math.floor(DEFAULTS.REQUEST_TIMEOUT * (1 / 3))
|
|
16
|
+
DEFAULTS.DNS_TIMEOUT = Math.floor(DEFAULTS.REQUEST_TIMEOUT * (1 / 5))
|
|
17
|
+
|
|
18
|
+
module.exports = DEFAULTS
|
package/src/index.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const DEFAULTS = require('./constant')
|
|
4
|
+
|
|
5
|
+
module.exports = ({ constants: userConstants } = {}) => {
|
|
6
|
+
const constants = { ...DEFAULTS, ...userConstants }
|
|
7
|
+
|
|
8
|
+
if (userConstants?.REQUEST_TIMEOUT && !userConstants?.PROXY_TIMEOUT) {
|
|
9
|
+
constants.PROXY_TIMEOUT = Math.floor(constants.REQUEST_TIMEOUT * (1 / 3))
|
|
10
|
+
}
|
|
11
|
+
if (userConstants?.REQUEST_TIMEOUT && !userConstants?.DNS_TIMEOUT) {
|
|
12
|
+
constants.DNS_TIMEOUT = Math.floor(constants.REQUEST_TIMEOUT * (1 / 5))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { createMemoryCache, createRedisCache } = require('./util/keyv')(constants)
|
|
16
|
+
const cacheableLookup = require('./util/cacheable-lookup')({ ...constants, createMemoryCache })
|
|
17
|
+
const got = require('./util/got')({ cacheableLookup })
|
|
18
|
+
const reachableUrl = require('./util/reachable-url')({ got, createMemoryCache })
|
|
19
|
+
const createBrowser = require('./util/browserless')(constants)
|
|
20
|
+
const getHTML = require('./util/html-get')({ createBrowser, got })
|
|
21
|
+
const { createHtmlProvider, getOgImage } = require('./util/html-provider')({ ...constants, getHTML })
|
|
22
|
+
|
|
23
|
+
const providerCtx = { constants, createHtmlProvider, getOgImage, got, createRedisCache }
|
|
24
|
+
const { providers, providersBy } = require('./providers')(providerCtx)
|
|
25
|
+
|
|
26
|
+
const { auto, getInputType, getAvatar } = require('./avatar/auto')({
|
|
27
|
+
constants,
|
|
28
|
+
providers,
|
|
29
|
+
providersBy,
|
|
30
|
+
reachableUrl
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const normalizeArgs = input =>
|
|
34
|
+
typeof input === 'string' ? { input } : input
|
|
35
|
+
|
|
36
|
+
const unavatar = input => auto(normalizeArgs(input))
|
|
37
|
+
|
|
38
|
+
Object.keys(providers).forEach(name => {
|
|
39
|
+
unavatar[name] = input => getAvatar(providers[name], name, normalizeArgs(input))
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
unavatar.auto = auto
|
|
43
|
+
unavatar.getInputType = getInputType
|
|
44
|
+
unavatar.getAvatar = getAvatar
|
|
45
|
+
unavatar.providers = providers
|
|
46
|
+
unavatar.providersBy = providersBy
|
|
47
|
+
unavatar.reachableUrl = reachableUrl
|
|
48
|
+
unavatar.got = got
|
|
49
|
+
|
|
50
|
+
return unavatar
|
|
51
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { $jsonld } = require('@metascraper/helpers')
|
|
4
|
+
const memoize = require('@keyvhq/memoize')
|
|
5
|
+
const ms = require('ms')
|
|
6
|
+
|
|
7
|
+
const APPLE_MUSIC_ID_REGEX = /^\d+$/
|
|
8
|
+
const APPLE_MUSIC_STOREFRONT = 'us'
|
|
9
|
+
const APPLE_MUSIC_ENTITY_TYPES = {
|
|
10
|
+
album: { entity: 'album', idKey: 'collectionId' },
|
|
11
|
+
artist: { entity: 'musicArtist', idKey: 'artistId' },
|
|
12
|
+
song: { entity: 'song', idKey: 'trackId' }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const APPLE_MUSIC_SEARCH_TYPES = ['artist', 'song', 'album']
|
|
16
|
+
const ITUNES_SEARCH_CACHE_TTL = ms('7d')
|
|
17
|
+
|
|
18
|
+
const isNumericId = value => {
|
|
19
|
+
if (typeof value === 'number') {
|
|
20
|
+
return Number.isSafeInteger(value) && value > 0
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (typeof value === 'string' && APPLE_MUSIC_ID_REGEX.test(value)) {
|
|
24
|
+
const parsed = Number.parseInt(value, 10)
|
|
25
|
+
return Number.isSafeInteger(parsed) && parsed > 0
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return false
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const parseInput = input => {
|
|
32
|
+
const separatorIndex = input.indexOf(':')
|
|
33
|
+
const hasExplicitType = separatorIndex !== -1
|
|
34
|
+
const type = hasExplicitType ? input.slice(0, separatorIndex) : undefined
|
|
35
|
+
const id = hasExplicitType ? input.slice(separatorIndex + 1) : input
|
|
36
|
+
|
|
37
|
+
return { hasExplicitType, type, id }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = ({ createHtmlProvider, createRedisCache, got }) => {
|
|
41
|
+
const itunesSearchCache = createRedisCache({
|
|
42
|
+
namespace: 'itunes-search',
|
|
43
|
+
ttl: ITUNES_SEARCH_CACHE_TTL
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const searchEntityId = memoize(
|
|
47
|
+
async ({ query, type }) => {
|
|
48
|
+
const entityConfig = APPLE_MUSIC_ENTITY_TYPES[type]
|
|
49
|
+
if (!entityConfig) return null
|
|
50
|
+
const { entity, idKey } = entityConfig
|
|
51
|
+
|
|
52
|
+
const url = `https://itunes.apple.com/search?term=${encodeURIComponent(
|
|
53
|
+
query
|
|
54
|
+
)}&entity=${entity}&limit=1`
|
|
55
|
+
const body = await got(url, {
|
|
56
|
+
responseType: 'json',
|
|
57
|
+
resolveBodyOnly: true
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const entityId = body?.results?.[0]?.[idKey]
|
|
61
|
+
return isNumericId(entityId) ? String(entityId) : null
|
|
62
|
+
},
|
|
63
|
+
itunesSearchCache,
|
|
64
|
+
{
|
|
65
|
+
key: ({ query, type }) => `${type}:${query.trim().toLowerCase()}`,
|
|
66
|
+
ttl: ITUNES_SEARCH_CACHE_TTL
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
const appleMusicURI = async input => {
|
|
71
|
+
const { hasExplicitType, type, id } = parseInput(input)
|
|
72
|
+
|
|
73
|
+
if (!hasExplicitType) {
|
|
74
|
+
for (const searchType of APPLE_MUSIC_SEARCH_TYPES) {
|
|
75
|
+
const entityId = await searchEntityId({ query: id, type: searchType })
|
|
76
|
+
if (entityId) return `${APPLE_MUSIC_STOREFRONT}/${searchType}/${entityId}`
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return `${APPLE_MUSIC_STOREFRONT}/search?term=${encodeURIComponent(id)}`
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!APPLE_MUSIC_ENTITY_TYPES[type]) {
|
|
83
|
+
return `${type}/${isNumericId(id) ? id : encodeURIComponent(id)}`
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (isNumericId(id)) return `${APPLE_MUSIC_STOREFRONT}/${type}/${id}`
|
|
87
|
+
|
|
88
|
+
const entityId = await searchEntityId({ query: id, type })
|
|
89
|
+
return `${APPLE_MUSIC_STOREFRONT}/${type}/${entityId || encodeURIComponent(id)}`
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return createHtmlProvider({
|
|
93
|
+
name: 'apple-music',
|
|
94
|
+
url: async input => `https://music.apple.com/${await appleMusicURI(input)}`,
|
|
95
|
+
getter: $ => $jsonld('image')($)
|
|
96
|
+
})
|
|
97
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { $jsonld } = require('@metascraper/helpers')
|
|
4
|
+
|
|
5
|
+
module.exports = ({ createHtmlProvider }) =>
|
|
6
|
+
createHtmlProvider({
|
|
7
|
+
name: 'bluesky',
|
|
8
|
+
url: input => `https://bsky.app/profile/${input}`,
|
|
9
|
+
getter: $ => $jsonld('mainEntity.image')($)
|
|
10
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto')
|
|
4
|
+
|
|
5
|
+
const stringify = require('../util/stringify')
|
|
6
|
+
|
|
7
|
+
const md5 = str => crypto.createHash('md5').update(str).digest('hex')
|
|
8
|
+
|
|
9
|
+
module.exports = ({ constants }) =>
|
|
10
|
+
function gravatar ({ input }) {
|
|
11
|
+
return `https://gravatar.com/avatar/${md5(input.trim().toLowerCase())}?${stringify({
|
|
12
|
+
size: constants.AVATAR_SIZE,
|
|
13
|
+
d: '404'
|
|
14
|
+
})}`
|
|
15
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const providersBy = {
|
|
4
|
+
email: ['gravatar'],
|
|
5
|
+
username: [
|
|
6
|
+
'apple-music',
|
|
7
|
+
'bluesky',
|
|
8
|
+
'deviantart',
|
|
9
|
+
'dribbble',
|
|
10
|
+
'github',
|
|
11
|
+
'gitlab',
|
|
12
|
+
'instagram',
|
|
13
|
+
'onlyfans',
|
|
14
|
+
'openstreetmap',
|
|
15
|
+
'patreon',
|
|
16
|
+
'reddit',
|
|
17
|
+
'soundcloud',
|
|
18
|
+
'spotify',
|
|
19
|
+
'substack',
|
|
20
|
+
'telegram',
|
|
21
|
+
'tiktok',
|
|
22
|
+
'twitch',
|
|
23
|
+
'vimeo',
|
|
24
|
+
'x',
|
|
25
|
+
'youtube'
|
|
26
|
+
],
|
|
27
|
+
domain: ['microlink']
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = ctx => {
|
|
31
|
+
const providers = {
|
|
32
|
+
'apple-music': require('./apple-music')(ctx),
|
|
33
|
+
bluesky: require('./bluesky')(ctx),
|
|
34
|
+
deviantart: require('./deviantart')(ctx),
|
|
35
|
+
dribbble: require('./dribbble')(ctx),
|
|
36
|
+
duckduckgo: require('./duckduckgo')(ctx),
|
|
37
|
+
github: require('./github')(ctx),
|
|
38
|
+
gitlab: require('./gitlab')(ctx),
|
|
39
|
+
google: require('./google')(ctx),
|
|
40
|
+
gravatar: require('./gravatar')(ctx),
|
|
41
|
+
instagram: require('./instagram')(ctx),
|
|
42
|
+
microlink: require('./microlink')(ctx),
|
|
43
|
+
onlyfans: require('./onlyfans')(ctx),
|
|
44
|
+
openstreetmap: require('./openstreetmap')(ctx),
|
|
45
|
+
patreon: require('./patreon')(ctx),
|
|
46
|
+
reddit: require('./reddit')(ctx),
|
|
47
|
+
soundcloud: require('./soundcloud')(ctx),
|
|
48
|
+
spotify: require('./spotify')(ctx),
|
|
49
|
+
substack: require('./substack')(ctx),
|
|
50
|
+
telegram: require('./telegram')(ctx),
|
|
51
|
+
tiktok: require('./tiktok')(ctx),
|
|
52
|
+
twitch: require('./twitch')(ctx),
|
|
53
|
+
vimeo: require('./vimeo')(ctx),
|
|
54
|
+
whatsapp: require('./whatsapp')(ctx),
|
|
55
|
+
x: require('./x')(ctx),
|
|
56
|
+
youtube: require('./youtube')(ctx)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { providers, providersBy }
|
|
60
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const PCancelable = require('p-cancelable')
|
|
4
|
+
const mql = require('@microlink/mql')
|
|
5
|
+
const { get } = require('lodash')
|
|
6
|
+
|
|
7
|
+
module.exports = ({ constants }) =>
|
|
8
|
+
PCancelable.fn(async function microlink ({ input, req = {} }, onCancel) {
|
|
9
|
+
const promise = mql(`https://${input}`, {
|
|
10
|
+
apiKey: req.isPro ? constants.MICROLINK_API_KEY : req.headers?.['x-api-key']
|
|
11
|
+
})
|
|
12
|
+
onCancel(() => promise.onCancel())
|
|
13
|
+
const { data } = await promise
|
|
14
|
+
return get(data, 'logo.url')
|
|
15
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { get } = require('lodash')
|
|
4
|
+
|
|
5
|
+
const getAvatarUrl = $ => {
|
|
6
|
+
const text = $('script[type="application/ld+json"]').contents().text()
|
|
7
|
+
if (!text) return
|
|
8
|
+
return get(JSON.parse(text), 'mainEntity.image')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module.exports = ({ createHtmlProvider }) =>
|
|
12
|
+
createHtmlProvider({
|
|
13
|
+
name: 'onlyfans',
|
|
14
|
+
url: input => `https://onlyfans.com/${input}`,
|
|
15
|
+
getter: getAvatarUrl,
|
|
16
|
+
htmlOpts: () => ({
|
|
17
|
+
prerender: true,
|
|
18
|
+
puppeteerOpts: { waitUntil: 'networkidle2', abortTypes: ['other', 'image', 'font'] }
|
|
19
|
+
})
|
|
20
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const OPENSTREETMAP_USER_ID_REGEX = /^\d+$/
|
|
4
|
+
|
|
5
|
+
module.exports = ({ got, createHtmlProvider }) => {
|
|
6
|
+
const fromID = userId =>
|
|
7
|
+
got(`https://api.openstreetmap.org/api/0.6/user/${userId}.json`, {
|
|
8
|
+
responseType: 'json'
|
|
9
|
+
}).then(({ body }) => body?.user?.img?.href)
|
|
10
|
+
|
|
11
|
+
const fromUsername = createHtmlProvider({
|
|
12
|
+
name: 'openstreetmap',
|
|
13
|
+
url: username => `https://www.openstreetmap.org/user/${encodeURIComponent(username)}`,
|
|
14
|
+
getter: $ => $('img.user_image').attr('src')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
return function openstreetmap ({ input }) {
|
|
18
|
+
return OPENSTREETMAP_USER_ID_REGEX.test(input) ? fromID(input) : fromUsername(...arguments)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { $jsonld } = require('@metascraper/helpers')
|
|
4
|
+
|
|
5
|
+
module.exports = ({ createHtmlProvider }) =>
|
|
6
|
+
createHtmlProvider({
|
|
7
|
+
name: 'patreon',
|
|
8
|
+
url: username => `https://www.patreon.com/${username}`,
|
|
9
|
+
getter: $ => $jsonld('mainEntity.image.thumbnailUrl')($)
|
|
10
|
+
})
|