@unavatar/core 3.19.2 → 3.19.3

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.19.2",
5
+ "version": "3.19.3",
6
6
  "main": "src/index.js",
7
7
  "exports": {
8
8
  ".": "./src/index.js",
package/src/index.js CHANGED
@@ -29,16 +29,18 @@ module.exports = ({ constants: userConstants, redis, onFetchHTML } = {}) => {
29
29
  })
30
30
  const createBrowser = require('./util/browserless')(constants)
31
31
  const getHTML = require('./util/html-get')({ createBrowser, got })
32
- const { createHtmlProvider, getOgImage } = require('./util/html-provider')({
33
- ...constants,
34
- getHTML,
35
- onFetchHTML
36
- })
32
+ const { createHtmlProvider, getOgImage, NOT_FOUND } =
33
+ require('./util/html-provider')({
34
+ ...constants,
35
+ getHTML,
36
+ onFetchHTML
37
+ })
37
38
 
38
39
  const providerCtx = {
39
40
  constants,
40
41
  createHtmlProvider,
41
42
  getOgImage,
43
+ NOT_FOUND,
42
44
  got,
43
45
  isReservedIp,
44
46
  itunesSearchCache: cache.itunesSearchCache
@@ -1,8 +1,15 @@
1
1
  'use strict'
2
2
 
3
- module.exports = ({ createHtmlProvider, getOgImage }) =>
3
+ const getAvatarUrl = ({ $, getOgImage, NOT_FOUND }) => {
4
+ const ogImage = getOgImage($)
5
+ return ogImage === undefined ? NOT_FOUND : ogImage
6
+ }
7
+
8
+ module.exports = ({ createHtmlProvider, getOgImage, NOT_FOUND }) =>
4
9
  createHtmlProvider({
5
10
  name: 'psnprofiles',
6
11
  url: input => `https://psnprofiles.com/${input}`,
7
- getter: getOgImage
12
+ getter: $ => getAvatarUrl({ $, getOgImage, NOT_FOUND })
8
13
  })
14
+
15
+ module.exports.getAvatarUrl = getAvatarUrl
@@ -28,22 +28,26 @@ const createProviderError = ({ provider, statusCode, cause, code }) =>
28
28
  message: 'Empty value returned by the provider.'
29
29
  })
30
30
 
31
+ const createErrorCause = ({ html, headers, statusCode }) => ({
32
+ html,
33
+ headers,
34
+ statusCode
35
+ })
36
+
31
37
  module.exports = ({ PROXY_TIMEOUT, getHTML, onFetchHTML }) => {
32
38
  /**
33
39
  * @param {object} opts
34
40
  * @param {string} opts.name - Provider identifier used in logs and metrics.
35
41
  * @param {(input: string) => string | Promise<string>} opts.url - Builds the URL to fetch for a given input.
36
- * @param {($: cheerio.CheerioAPI) => string | undefined} opts.getter
42
+ * @param {($: cheerio.CheerioAPI) => string | symbol | undefined} opts.getter
37
43
  * Extracts the avatar URL from the fetched HTML.
38
- * - `string` — avatar URL found (success).
44
+ * - `string` — avatar URL found (success).
45
+ * - `NOT_FOUND` — provider-level miss.
39
46
  * - `undefined` — avatar not found (normal failure, no retry).
40
- * @param {(context: { $: cheerio.CheerioAPI, statusCode: number }) => boolean} [opts.isBlocked]
41
- * Optional provider-specific blocked-page detector, checked after the
42
- * default `is-antibot` check when getter returns empty/undefined.
43
47
  * @param {() => object} [opts.htmlOpts] - Returns extra options merged into the fetch call.
44
48
  */
45
- const createHtmlProvider = ({ name, url, getter, isBlocked, htmlOpts }) => {
46
- const provider = async function (input, context) {
49
+ const createHtmlProvider = ({ name, url, getter, htmlOpts }) => {
50
+ async function provider (input, context) {
47
51
  const providerUrl = await url(input)
48
52
 
49
53
  const attempt = async gotOpts => {
@@ -56,7 +60,8 @@ module.exports = ({ PROXY_TIMEOUT, getHTML, onFetchHTML }) => {
56
60
  },
57
61
  timeout: PROXY_TIMEOUT
58
62
  }
59
- const fetchOpts = gotOpts ? { ...defaultOpts, ...gotOpts } : defaultOpts
63
+ const fetchOpts = { ...defaultOpts, ...gotOpts }
64
+ const userAgent = fetchOpts.headers['user-agent']
60
65
  const tier = fetchOpts.tier ?? 'origin'
61
66
 
62
67
  const log = debug.duration({ provider: name, input, providerUrl, tier })
@@ -73,6 +78,11 @@ module.exports = ({ PROXY_TIMEOUT, getHTML, onFetchHTML }) => {
73
78
  : undefined
74
79
  attempt.lastHeaders = responseHeaders
75
80
  attempt.lastStatusCode = statusCode
81
+ const errorCause = createErrorCause({
82
+ html: attempt.lastHtml,
83
+ headers: responseHeaders,
84
+ statusCode
85
+ })
76
86
 
77
87
  if (isStatusCodeMissing(statusCode)) {
78
88
  const code = EMPTY_PROVIDER_VALUE_CODE.MISSING_STATUS_CODE
@@ -80,67 +90,87 @@ module.exports = ({ PROXY_TIMEOUT, getHTML, onFetchHTML }) => {
80
90
  throw createProviderError({
81
91
  provider: name,
82
92
  statusCode,
83
- cause: {
84
- html: attempt.lastHtml,
85
- headers: responseHeaders,
86
- statusCode
87
- },
93
+ cause: errorCause,
88
94
  code
89
95
  })
90
96
  }
91
97
 
98
+ const result = getter($)
92
99
  if (statusCode === httpStatus.NOT_FOUND) {
93
100
  log.error({ statusCode, status: 'not_found' })
94
101
  return NOT_FOUND
95
102
  }
96
103
 
97
- const result = getter($)
98
- if (typeof result !== 'string' || result === '') {
99
- const error = createProviderError({
104
+ function createEmptyGetterResultError () {
105
+ return createProviderError({
100
106
  provider: name,
101
107
  statusCode,
102
108
  code: EMPTY_PROVIDER_VALUE_CODE.EMPTY_GETTER_RESULT,
103
- cause: {
104
- html: attempt.lastHtml,
105
- headers: responseHeaders,
106
- statusCode
107
- }
109
+ cause: errorCause
108
110
  })
111
+ }
109
112
 
113
+ function getBlockedStatus () {
110
114
  const isRateLimited = statusCode === httpStatus.TOO_MANY_REQUESTS
111
- const providerBlocked = isBlocked?.({ $, statusCode })
115
+ if (isRateLimited) return { isBlocked: true, antibotProvider: null }
112
116
 
113
117
  const { detected: antibotDetected, provider: antibotProvider } =
114
- isRateLimited || providerBlocked
115
- ? { detected: false, provider: null }
116
- : isAntibot({
117
- url: providerUrl,
118
- statusCode,
119
- headers: responseHeaders,
120
- body: attempt.lastHtml
121
- })
122
-
123
- if (isRateLimited || providerBlocked || antibotDetected) {
124
- error.blocked = true
118
+ isAntibot({
119
+ url: providerUrl,
120
+ statusCode,
121
+ headers: responseHeaders,
122
+ body: attempt.lastHtml
123
+ })
124
+
125
+ return {
126
+ isBlocked: antibotDetected,
127
+ antibotProvider
125
128
  }
129
+ }
130
+
131
+ if (typeof result === 'string' && result !== '') {
132
+ const normalizedResult = normalizeUrl(providerUrl, result)
133
+ log({
134
+ statusCode,
135
+ status: 'success',
136
+ result: normalizedResult
137
+ })
138
+ return normalizedResult
139
+ }
140
+
141
+ // Some providers encode not-found via getter output. Check antibot
142
+ // first so challenge pages are retried as blocked, not treated as 404.
143
+ const { isBlocked: shouldMarkBlocked, antibotProvider } =
144
+ getBlockedStatus()
145
+
146
+ if (shouldMarkBlocked) {
147
+ const error = createEmptyGetterResultError()
148
+ error.blocked = true
126
149
 
127
150
  log.error({
128
151
  statusCode,
129
- status: error.blocked ? 'blocked' : undefined,
152
+ status: 'blocked',
130
153
  antibot: antibotProvider ?? undefined,
131
- userAgent: fetchOpts.headers['user-agent']
154
+ userAgent
132
155
  })
133
156
 
134
157
  throw error
135
158
  }
136
159
 
137
- const normalizedResult = normalizeUrl(providerUrl, result)
138
- log({
160
+ if (result === NOT_FOUND) {
161
+ log.error({ statusCode, status: 'not_found' })
162
+ return NOT_FOUND
163
+ }
164
+
165
+ const error = createEmptyGetterResultError()
166
+
167
+ log.error({
139
168
  statusCode,
140
- status: 'success',
141
- result: normalizedResult
169
+ antibot: antibotProvider ?? undefined,
170
+ userAgent
142
171
  })
143
- return normalizedResult
172
+
173
+ throw error
144
174
  }
145
175
 
146
176
  if (typeof onFetchHTML === 'function') {
@@ -148,8 +178,7 @@ module.exports = ({ PROXY_TIMEOUT, getHTML, onFetchHTML }) => {
148
178
  }
149
179
 
150
180
  const result = await attempt()
151
- if (result === NOT_FOUND) return
152
- return result
181
+ return result === NOT_FOUND ? undefined : result
153
182
  }
154
183
 
155
184
  provider.getUrl = url