ask-web-fetch 0.5.5 → 0.5.7

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: db21a4a35495ff89b8b91b769d4a15743da85beb961a5ddbbc430bdde37988f5
4
- data.tar.gz: 61fb64631bbfe1d478b3b6ed6587de8e5b9b3890dd5348924ca574929f85acb7
3
+ metadata.gz: '08275e5b901ca9aaba313cac4cd3f776efb2c16b93aa709645c0ad367a1ff720'
4
+ data.tar.gz: 0eaba632b36081a0bc3fbf7dd1ff2b463cadb613e44d8a280d0546aeeda40676
5
5
  SHA512:
6
- metadata.gz: f53d538cf613c02e1411c9f09901c3b93db98c51c33d0d3dfca2d7ea58420f9e463f074c1f2e9a6c97da59023037abcfce59d757ef3ab689eada87fc6155939d
7
- data.tar.gz: fde5f3000a2ec77294d1bceec3484d9d4ec68b3b3d0b91d8e29083b3bb711f31b391092a1cd544b329858ffb510e116d39c39ed4a23323c110af133db6cef928
6
+ metadata.gz: 3c82f4b9abfb154fb06b9a5feaaf2254ddb6caa1cac99892a034311b4113ab1775114ad7c5aa126db88682c3ecf4cdfbee13786b61a06a00f2f30d6b838a2cdf
7
+ data.tar.gz: f3d14810e581699b5d2f8717fa329052ad8b81f03611d637d99fde979f1b324d2939ee79217ee812018327596f07ae0659c987b2e42f197df60af4bff30c8a7b
@@ -59,6 +59,18 @@ module Ask
59
59
  # Wikipedia embeds an hcaptcha edit-config flag on every page).
60
60
  CHALLENGE_RE = /just a moment|checking your browser|cf-chl/i
61
61
 
62
+ # Registrar parking-page markers: the page is an ad for a parked
63
+ # (for-sale) domain, not the site's content. A content company must
64
+ # never store these as if they were the site. Observed live on the
65
+ # CC list, three shapes: (a) GoDaddy's parking-lander JS app
66
+ # (ap:"parking" flag, parking-lander asset, LANDER_SYSTEM="PW")
67
+ # served at /lander, (b) Namecheap's parking app (utm_campaign=
68
+ # nc_market + parkingpage links), and (c) static registrar pages
69
+ # ("is parked free, courtesy of GoDaddy.com", "is registered at
70
+ # Namecheap"). Deliberately specific — generic terms like "domain"
71
+ # or "for sale" appear on real pages.
72
+ PARKED_DOMAIN_MARKERS = /ap:"parking"|parking-lander|LANDER_SYSTEM="PW"|utm_campaign=nc_market|utm_source=parkingpage|is parked free, courtesy of GoDaddy|is available on GoDaddy Auctions|is registered at Namecheap/i
73
+
62
74
  def self.backend_name
63
75
  name.split('::').last
64
76
  end
@@ -118,6 +130,10 @@ module Ask
118
130
  body.to_s.match?(CHALLENGE_RE)
119
131
  end
120
132
 
133
+ def parked_domain?(body)
134
+ body.to_s.match?(PARKED_DOMAIN_MARKERS)
135
+ end
136
+
121
137
  def usable_content?(content)
122
138
  content.to_s.strip.length >= MIN_CONTENT_LENGTH
123
139
  end
@@ -199,6 +199,10 @@ module Ask
199
199
 
200
200
  return fetch_attempt(page, url, warmed: true)
201
201
  end
202
+ # Browser renders the parked page a JS redirect lands on (the
203
+ # server shell hands /lander to JS) — Local never sees it. The
204
+ # shared parked-domain detector catches it here.
205
+ raise EmptyContentError, "parked domain at #{url} — registrar parking page, not site content" if parked_domain?(body)
202
206
 
203
207
  result = Markdown.generate(body, base_url: url, filter: self.class.content_filter)
204
208
  result[:outlinks] = outlink_urls(body, url)
@@ -45,7 +45,32 @@ module Ask
45
45
 
46
46
  page = to_markdown(body, url)
47
47
  page[:redirected] = redirect
48
+ # Parked-domain pages are not content: the domain owner parked it
49
+ # with a registrar and the page is an ad for buying the domain
50
+ # (GoDaddy/Namecheap/Sedo parking). A content company must never
51
+ # store these as if they were the site. Checked BEFORE the
52
+ # content-minimum — a parking page can render as "content" above
53
+ # the minimum (puncta.ai: 395c of Namecheap auction ads), and
54
+ # must still be rejected. Detectable from the server HTML —
55
+ # parked pages are fully server-rendered, so both Local and
56
+ # Browser see the same ad.
57
+ if parked_domain?(body)
58
+ raise EmptyContentError, "parked domain at #{url} — registrar parking page, not site content"
59
+ end
48
60
  raise EmptyContentError, "no readable content at #{url}" unless usable_content?(page[:content])
61
+ # The completeness signal: a JS-app shell whose server HTML
62
+ # renders little is a TRUNCATED page, not a complete one — the
63
+ # real content awaits client-side JS that Local cannot run.
64
+ # Storing the shell as success would silently under-deliver
65
+ # (airbnb: 613KB server HTML -> 143 chars of markdown); failing
66
+ # through lets the chain prefer a rendering backend (Browser),
67
+ # and keeps a partial page from ever being stored as the real
68
+ # thing. Two detectors: known framework markers, or a large
69
+ # HTML page with almost no server-rendered text.
70
+ if js_app_shell?(body) && page[:content].length < SHELL_CONTENT_THRESHOLD
71
+ raise EmptyContentError,
72
+ "JS-app shell at #{url} — server HTML renders only #{page[:content].length} chars; a rendering backend is required"
73
+ end
49
74
 
50
75
  page
51
76
  rescue Errno::ECONNREFUSED, Errno::ECONNRESET, SocketError, URI::InvalidURIError => e
@@ -55,6 +80,40 @@ module Ask
55
80
  raise TimeoutError, "#{e.class}: #{e.message}"
56
81
  end
57
82
 
83
+ # Client-rendered framework markers: the server HTML is (at least
84
+ # partly) a shell awaiting JS. Deliberately narrow — generic terms
85
+ # like "script" or "root" appear on every page; these are the
86
+ # specific footprints of React/Vue/Next/Nuxt app shells.
87
+ JS_APP_SHELL_MARKERS = /id=["'](?:root|app|__next|site-content)["']|__NEXT_DATA__|window\.__NUXT__|ng-app|data-reactroot/
88
+
89
+ # Markdown below this from a JS-app shell is "server sent a shell",
90
+ # not "page is genuinely short" — a real page (even a short one)
91
+ # is usually server-rendered above this. Tunable; the chain turns
92
+ # the signal into "prefer Browser for this URL".
93
+ SHELL_CONTENT_THRESHOLD = 4_000
94
+
95
+ # A page whose server HTML is large but yields almost no text is a
96
+ # shell whatever framework it uses (airbnb: 613KB HTML -> 143 chars
97
+ # of markdown). Framework markers miss these; the size ratio
98
+ # catches them. nytimes (1.4MB -> 7.5k text) stays above the text
99
+ # floor and is correctly left to Local.
100
+ SHELL_HTML_BYTES = 20_000
101
+
102
+ def js_app_shell?(body)
103
+ body.to_s.match?(JS_APP_SHELL_MARKERS) ||
104
+ (body.to_s.bytesize > SHELL_HTML_BYTES && markdown_visible_chars(body) < SHELL_CONTENT_THRESHOLD)
105
+ end
106
+
107
+ # A cheap text estimate from the raw HTML (tags stripped) — the
108
+ # "how much did the server actually render" number. Deliberately
109
+ # rough: it only feeds a shell-vs-page heuristic.
110
+ def markdown_visible_chars(body)
111
+ body.to_s.gsub(/<script[\s\S]*?<\/script>/i, "")
112
+ .gsub(/<style[\s\S]*?<\/style>/i, "")
113
+ .gsub(/<[^>]+>/, " ")
114
+ .gsub(/\s+/, " ").strip.length
115
+ end
116
+
58
117
  # Parses +html+ and returns { title:, description:, content:,
59
118
  # licenses:, outlinks: } where content is clean markdown (pruned by
60
119
  # the ContentFilter), licenses are the page's declared license
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ask
4
4
  module WebFetch
5
- VERSION = '0.5.5'
5
+ VERSION = '0.5.7'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ask-web-fetch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.5
4
+ version: 0.5.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto