@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.
Files changed (45) hide show
  1. package/README.md +407 -0
  2. package/bin/index.js +163 -0
  3. package/bin/unavatar +3 -0
  4. package/bin/unavatar-dev +3 -0
  5. package/package.json +192 -0
  6. package/src/avatar/auto.js +94 -0
  7. package/src/constant.js +18 -0
  8. package/src/index.js +51 -0
  9. package/src/providers/apple-music.js +97 -0
  10. package/src/providers/bluesky.js +10 -0
  11. package/src/providers/deviantart.js +8 -0
  12. package/src/providers/dribbble.js +8 -0
  13. package/src/providers/duckduckgo.js +6 -0
  14. package/src/providers/github.js +10 -0
  15. package/src/providers/gitlab.js +8 -0
  16. package/src/providers/google.js +6 -0
  17. package/src/providers/gravatar.js +15 -0
  18. package/src/providers/index.js +60 -0
  19. package/src/providers/instagram.js +8 -0
  20. package/src/providers/microlink.js +15 -0
  21. package/src/providers/onlyfans.js +20 -0
  22. package/src/providers/openstreetmap.js +20 -0
  23. package/src/providers/patreon.js +10 -0
  24. package/src/providers/reddit.js +9 -0
  25. package/src/providers/soundcloud.js +17 -0
  26. package/src/providers/spotify.js +18 -0
  27. package/src/providers/substack.js +35 -0
  28. package/src/providers/telegram.js +8 -0
  29. package/src/providers/tiktok.js +26 -0
  30. package/src/providers/twitch.js +8 -0
  31. package/src/providers/vimeo.js +8 -0
  32. package/src/providers/whatsapp.js +31 -0
  33. package/src/providers/x.js +35 -0
  34. package/src/providers/youtube.js +8 -0
  35. package/src/util/browserless.js +38 -0
  36. package/src/util/cacheable-lookup.js +22 -0
  37. package/src/util/error.js +6 -0
  38. package/src/util/got.js +32 -0
  39. package/src/util/html-get.js +25 -0
  40. package/src/util/html-provider.js +190 -0
  41. package/src/util/http-status.js +17 -0
  42. package/src/util/is-iterable.js +9 -0
  43. package/src/util/keyv.js +27 -0
  44. package/src/util/reachable-url.js +21 -0
  45. 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
@@ -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,8 @@
1
+ 'use strict'
2
+
3
+ module.exports = ({ createHtmlProvider, getOgImage }) =>
4
+ createHtmlProvider({
5
+ name: 'deviantart',
6
+ url: input => `https://www.deviantart.com/${input}`,
7
+ getter: getOgImage
8
+ })
@@ -0,0 +1,8 @@
1
+ 'use strict'
2
+
3
+ module.exports = ({ createHtmlProvider }) =>
4
+ createHtmlProvider({
5
+ name: 'dribbble',
6
+ url: input => `https://dribbble.com/${input}`,
7
+ getter: $ => $('.profile-avatar').attr('src')
8
+ })
@@ -0,0 +1,6 @@
1
+ 'use strict'
2
+
3
+ module.exports = () =>
4
+ function duckduckgo ({ input }) {
5
+ return `https://icons.duckduckgo.com/ip3/${input}.ico`
6
+ }
@@ -0,0 +1,10 @@
1
+ 'use strict'
2
+
3
+ const { stringify } = require('querystring')
4
+
5
+ module.exports = ({ constants }) =>
6
+ async function github ({ input }) {
7
+ return `https://github.com/${input}.png?${stringify({
8
+ size: constants.AVATAR_SIZE
9
+ })}`
10
+ }
@@ -0,0 +1,8 @@
1
+ 'use strict'
2
+
3
+ module.exports = ({ createHtmlProvider, getOgImage }) =>
4
+ createHtmlProvider({
5
+ name: 'gitlab',
6
+ url: input => `https://gitlab.com/${input}`,
7
+ getter: getOgImage
8
+ })
@@ -0,0 +1,6 @@
1
+ 'use strict'
2
+
3
+ module.exports = () =>
4
+ function google ({ input }) {
5
+ return `https://www.google.com/s2/favicons?domain_url=${input}&sz=128`
6
+ }
@@ -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,8 @@
1
+ 'use strict'
2
+
3
+ module.exports = ({ createHtmlProvider, getOgImage }) =>
4
+ createHtmlProvider({
5
+ name: 'instagram',
6
+ url: input => `https://www.instagram.com/${input}`,
7
+ getter: getOgImage
8
+ })
@@ -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
+ })