@unavatar/core 3.7.61 → 3.7.63

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
@@ -17,6 +17,8 @@
17
17
  - [Google](#google)
18
18
  - [Gravatar](#gravatar)
19
19
  - [Instagram](#instagram)
20
+ - [LinkedIn](#linkedin)
21
+ - [Mastodon](#mastodon)
20
22
  - [Microlink](#microlink)
21
23
  - [OnlyFans](#onlyfans)
22
24
  - [OpenStreetMap](#openstreetmap)
@@ -223,6 +225,20 @@ It resolves user avatar against **instagram.com**.
223
225
 
224
226
  e.g., [unavatar.io/instagram/willsmith](https://unavatar.io/instagram/willsmith)
225
227
 
228
+ ### LinkedIn
229
+
230
+ It resolves user avatar against **linkedin.com**.
231
+
232
+ e.g., [unavatar.io/linkedin/kikobeats](https://unavatar.io/linkedin/kikobeats)
233
+
234
+ ### Mastodon
235
+
236
+ It resolves user avatar from any **Mastodon** instance using the public account lookup API.
237
+
238
+ Because Mastodon is federated, the input must include both the username and the server. The following formats are supported:
239
+
240
+ - `user@server`: [unavatar.io/mastodon/kpwags@hachyderm.io](https://unavatar.io/mastodon/kpwags@hachyderm.io)
241
+
226
242
  ### Microlink
227
243
 
228
244
  It resolves user avatar using **microlink.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.7.61",
5
+ "version": "3.7.63",
6
6
  "main": "src/index.js",
7
7
  "exports": {
8
8
  ".": "./src/index.js",
@@ -34,6 +34,10 @@
34
34
  "name": "Alexander Schlindwein",
35
35
  "email": "alexander.schlindwein@hotmail.de"
36
36
  },
37
+ {
38
+ "name": "Wes Bos",
39
+ "email": "wesbos@gmail.com"
40
+ },
37
41
  {
38
42
  "name": "Zadkiel",
39
43
  "email": "hello@zadkiel.fr"
@@ -134,7 +138,7 @@
134
138
  "p-any": "~3.0.0",
135
139
  "p-cancelable": "2.1.1",
136
140
  "p-timeout": "~4.1.0",
137
- "puppeteer": "~24.39.0",
141
+ "puppeteer": "~24.40.0",
138
142
  "re2": "~1.23.3",
139
143
  "srcset": "~4.0.0",
140
144
  "stable-regex": "~1.0.0",
package/src/index.js CHANGED
@@ -15,6 +15,7 @@ module.exports = ({ constants: userConstants, redis, onFetchHTML } = {}) => {
15
15
  const { createMultiCache, createRedisCache } = require('./util/keyv')({ ...constants, redis })
16
16
  const cache = require('./util/cache')({ createMultiCache, createRedisCache })
17
17
  const cacheableLookup = require('./util/cacheable-lookup')({ ...constants, cache: cache.dnsCache })
18
+ const isReservedIp = require('./util/is-reserved-ip')({ cacheableLookup })
18
19
  const got = require('./util/got')({ cacheableLookup })
19
20
  const reachableUrl = require('./util/reachable-url')({ got, pingCache: cache.pingCache })
20
21
  const createBrowser = require('./util/browserless')(constants)
@@ -25,7 +26,14 @@ module.exports = ({ constants: userConstants, redis, onFetchHTML } = {}) => {
25
26
  onFetchHTML
26
27
  })
27
28
 
28
- const providerCtx = { constants, createHtmlProvider, getOgImage, got, itunesSearchCache: cache.itunesSearchCache }
29
+ const providerCtx = {
30
+ constants,
31
+ createHtmlProvider,
32
+ getOgImage,
33
+ got,
34
+ isReservedIp,
35
+ itunesSearchCache: cache.itunesSearchCache
36
+ }
29
37
  const { providers, providersBy } = require('./providers')(providerCtx)
30
38
 
31
39
  const { auto, getInputType, getAvatar } = require('./avatar/auto')({
@@ -10,6 +10,8 @@ const providersBy = {
10
10
  'github',
11
11
  'gitlab',
12
12
  'instagram',
13
+ 'linkedin',
14
+ 'mastodon',
13
15
  'onlyfans',
14
16
  'openstreetmap',
15
17
  'patreon',
@@ -39,6 +41,8 @@ module.exports = ctx => {
39
41
  google: require('./google')(ctx),
40
42
  gravatar: require('./gravatar')(ctx),
41
43
  instagram: require('./instagram')(ctx),
44
+ linkedin: require('./linkedin')(ctx),
45
+ mastodon: require('./mastodon')(ctx),
42
46
  microlink: require('./microlink')(ctx),
43
47
  onlyfans: require('./onlyfans')(ctx),
44
48
  openstreetmap: require('./openstreetmap')(ctx),
@@ -0,0 +1,8 @@
1
+ 'use strict'
2
+
3
+ module.exports = ({ createHtmlProvider, getOgImage }) =>
4
+ createHtmlProvider({
5
+ name: 'linkedin',
6
+ url: input => `https://www.linkedin.com/in/${input}`,
7
+ getter: getOgImage
8
+ })
@@ -0,0 +1,58 @@
1
+ 'use strict'
2
+
3
+ const isValidServer = server => {
4
+ try {
5
+ const url = new URL(`https://${server}`)
6
+ return (
7
+ Boolean(url.hostname) &&
8
+ url.username === '' &&
9
+ url.password === '' &&
10
+ url.pathname === '/' &&
11
+ url.search === '' &&
12
+ url.hash === '' &&
13
+ url.host.toLowerCase() === server.toLowerCase()
14
+ )
15
+ } catch {
16
+ return false
17
+ }
18
+ }
19
+
20
+ const parseMastodonInput = input => {
21
+ if (typeof input !== 'string') return null
22
+
23
+ const cleaned = input.startsWith('@') ? input.slice(1) : input
24
+ const parts = cleaned.split('@')
25
+ if (parts.length !== 2) return null
26
+
27
+ const [username, server] = parts
28
+ if (!username || !server) return null
29
+ if (!isValidServer(server)) return null
30
+
31
+ return {
32
+ username,
33
+ server
34
+ }
35
+ }
36
+
37
+ module.exports = ({ got, isReservedIp }) => {
38
+ const mastodon = async function ({ input }) {
39
+ const parsed = parseMastodonInput(input)
40
+ if (!parsed) return undefined
41
+
42
+ const { username, server } = parsed
43
+
44
+ if (await isReservedIp(server)) return undefined
45
+
46
+ const { body } = await got(
47
+ `https://${server}/api/v1/accounts/lookup?acct=${encodeURIComponent(
48
+ username
49
+ )}`,
50
+ { responseType: 'json' }
51
+ )
52
+
53
+ return body?.avatar
54
+ }
55
+
56
+ mastodon.parseMastodonInput = parseMastodonInput
57
+ return mastodon
58
+ }
@@ -0,0 +1,23 @@
1
+ 'use strict'
2
+
3
+ const ip = require('ipaddr.js')
4
+
5
+ module.exports = ({ cacheableLookup }) => {
6
+ const getIpAddress = async hostname => {
7
+ if (ip.IPv4.isIPv4(hostname)) return hostname
8
+ if (
9
+ hostname.startsWith('[') &&
10
+ hostname.endsWith(']') &&
11
+ ip.IPv6.isIPv6(hostname.slice(1, -1))
12
+ ) {
13
+ return hostname.slice(1, -1)
14
+ }
15
+ const { address } = await cacheableLookup.lookupAsync(hostname)
16
+ return address
17
+ }
18
+
19
+ return async hostname => {
20
+ const ipAddress = await getIpAddress(hostname)
21
+ return ip.process(ipAddress).range() !== 'unicast'
22
+ }
23
+ }