html2rss 0.29.1 → 0.31.0

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 (132) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -8
  3. data/html2rss.gemspec +1 -3
  4. data/lib/html2rss/article/deduplicator.rb +2 -4
  5. data/lib/html2rss/article.rb +16 -39
  6. data/lib/html2rss/auto_source/README.md +1 -1
  7. data/lib/html2rss/auto_source/cleanup.rb +15 -6
  8. data/lib/html2rss/auto_source/scraper/html.rb +5 -0
  9. data/lib/html2rss/auto_source/scraper/json_state.rb +5 -0
  10. data/lib/html2rss/auto_source/scraper/meta_oembed.rb +8 -1
  11. data/lib/html2rss/auto_source/scraper/microdata.rb +8 -0
  12. data/lib/html2rss/auto_source/scraper/microformats2.rb +8 -0
  13. data/lib/html2rss/auto_source/scraper/native_feed.rb +3 -0
  14. data/lib/html2rss/auto_source/scraper/schema/thing.rb +4 -5
  15. data/lib/html2rss/auto_source/scraper/schema.rb +8 -0
  16. data/lib/html2rss/auto_source/scraper/semantic_html.rb +5 -0
  17. data/lib/html2rss/auto_source/scraper/sitemap.rb +3 -0
  18. data/lib/html2rss/auto_source/scraper/wordpress_api.rb +3 -0
  19. data/lib/html2rss/auto_source/scraper.rb +4 -14
  20. data/lib/html2rss/auto_source/segment.rb +7 -4
  21. data/lib/html2rss/auto_source/segmenter/cluster.rb +22 -23
  22. data/lib/html2rss/auto_source/segmenter/list.rb +54 -31
  23. data/lib/html2rss/auto_source/segmenter.rb +5 -5
  24. data/lib/html2rss/auto_source.rb +2 -2
  25. data/lib/html2rss/batch.rb +6 -4
  26. data/lib/html2rss/capture/README.md +7 -5
  27. data/lib/html2rss/capture.rb +68 -36
  28. data/lib/html2rss/channel.rb +3 -3
  29. data/lib/html2rss/cli/probe_view.rb +5 -3
  30. data/lib/html2rss/cli/render.rb +6 -5
  31. data/lib/html2rss/cli/validate.rb +28 -27
  32. data/lib/html2rss/cli.rb +1 -1
  33. data/lib/html2rss/config/issue_mapper.rb +108 -0
  34. data/lib/html2rss/config/request_headers.rb +6 -4
  35. data/lib/html2rss/config/schema.rb +16 -1
  36. data/lib/html2rss/config/selectors_validator.rb +92 -70
  37. data/lib/html2rss/config/validation_issue.rb +60 -0
  38. data/lib/html2rss/config/validation_report.rb +55 -0
  39. data/lib/html2rss/config/validator.rb +10 -6
  40. data/lib/html2rss/config.rb +35 -101
  41. data/lib/html2rss/defaults.rb +42 -30
  42. data/lib/html2rss/doctor/botasaurus.rb +19 -9
  43. data/lib/html2rss/doctor.rb +8 -0
  44. data/lib/html2rss/error.rb +1 -1
  45. data/lib/html2rss/feed_builder/rss.rb +9 -14
  46. data/lib/html2rss/feed_pipeline/README.md +8 -8
  47. data/lib/html2rss/feed_pipeline/auto_fallback.rb +1 -1
  48. data/lib/html2rss/feed_pipeline/runtime_policy.rb +14 -10
  49. data/lib/html2rss/feed_pipeline/strategy_plan.rb +7 -3
  50. data/lib/html2rss/feed_resolution/candidate_generator.rb +1 -4
  51. data/lib/html2rss/feed_resolution/diag.rb +3 -1
  52. data/lib/html2rss/feed_resolution/options.rb +3 -1
  53. data/lib/html2rss/feed_resolution/policy.rb +2 -2
  54. data/lib/html2rss/html/article_extractor/date_extractor.rb +1 -1
  55. data/lib/html2rss/html/article_extractor.rb +2 -4
  56. data/lib/html2rss/html/feed_link.rb +3 -1
  57. data/lib/html2rss/html/navigator/text_extractor.rb +1 -1
  58. data/lib/html2rss/html/navigator.rb +10 -10
  59. data/lib/html2rss/html/rendering/description_builder.rb +1 -1
  60. data/lib/html2rss/html/sst_article_extractor.rb +8 -5
  61. data/lib/html2rss/link_destination/destination_facts.rb +4 -2
  62. data/lib/html2rss/link_destination/noise_policy.rb +10 -1
  63. data/lib/html2rss/link_destination/path_classifier.rb +45 -38
  64. data/lib/html2rss/mcp/README.md +5 -3
  65. data/lib/html2rss/mcp/config_argument.rb +3 -1
  66. data/lib/html2rss/mcp/contract.rb +15 -25
  67. data/lib/html2rss/mcp/outcome/playbook.rb +7 -7
  68. data/lib/html2rss/mcp/outcome.rb +36 -43
  69. data/lib/html2rss/mcp/server/tools.rb +112 -132
  70. data/lib/html2rss/mcp/server.rb +27 -19
  71. data/lib/html2rss/page_recon/README.md +5 -5
  72. data/lib/html2rss/page_recon/diagnostics.rb +71 -81
  73. data/lib/html2rss/page_recon.rb +9 -5
  74. data/lib/html2rss/recon.rb +20 -31
  75. data/lib/html2rss/request_service/botasaurus_contract.rb +11 -12
  76. data/lib/html2rss/request_service/botasaurus_strategy.rb +32 -28
  77. data/lib/html2rss/request_service/compressed_body.rb +17 -9
  78. data/lib/html2rss/request_service/httpx_strategy.rb +239 -0
  79. data/lib/html2rss/request_service/network_guard.rb +7 -5
  80. data/lib/html2rss/request_service/response.rb +1 -2
  81. data/lib/html2rss/request_service/response_guard.rb +0 -16
  82. data/lib/html2rss/request_service/strategy.rb +22 -11
  83. data/lib/html2rss/request_service.rb +46 -85
  84. data/lib/html2rss/scoring/container_assessor.rb +3 -1
  85. data/lib/html2rss/scoring/engine.rb +8 -8
  86. data/lib/html2rss/scoring/observation.rb +5 -3
  87. data/lib/html2rss/scoring/ranked_segment.rb +3 -1
  88. data/lib/html2rss/scrape_target.rb +3 -1
  89. data/lib/html2rss/selector_candidates.rb +234 -0
  90. data/lib/html2rss/selectors/README.md +64 -0
  91. data/lib/html2rss/selectors/extractors/attribute.rb +13 -15
  92. data/lib/html2rss/selectors/extractors/href.rb +15 -16
  93. data/lib/html2rss/selectors/extractors/html.rb +8 -9
  94. data/lib/html2rss/selectors/extractors/static.rb +12 -14
  95. data/lib/html2rss/selectors/extractors/text.rb +8 -9
  96. data/lib/html2rss/selectors/extractors.rb +33 -20
  97. data/lib/html2rss/selectors/item_env.rb +31 -0
  98. data/lib/html2rss/selectors/{object_to_xml_converter.rb → json_xml.rb} +11 -12
  99. data/lib/html2rss/selectors/option_spec.rb +140 -0
  100. data/lib/html2rss/selectors/post_processors/base.rb +79 -31
  101. data/lib/html2rss/selectors/post_processors/gsub.rb +103 -39
  102. data/lib/html2rss/selectors/post_processors/html_transformers/transform_urls_to_absolute_ones.rb +4 -4
  103. data/lib/html2rss/selectors/post_processors/markdown_to_html.rb +10 -11
  104. data/lib/html2rss/selectors/post_processors/parse_time.rb +50 -26
  105. data/lib/html2rss/selectors/post_processors/parse_uri.rb +11 -7
  106. data/lib/html2rss/selectors/post_processors/sanitize_html.rb +46 -32
  107. data/lib/html2rss/selectors/post_processors/substring.rb +14 -25
  108. data/lib/html2rss/selectors/post_processors/template.rb +23 -19
  109. data/lib/html2rss/selectors/post_processors.rb +6 -5
  110. data/lib/html2rss/selectors/{schema_doc.rb → schema_export.rb} +12 -31
  111. data/lib/html2rss/selectors/step_env.rb +24 -0
  112. data/lib/html2rss/selectors.rb +231 -142
  113. data/lib/html2rss/sst/attrs.rb +5 -3
  114. data/lib/html2rss/sst/document.rb +3 -1
  115. data/lib/html2rss/sst/index.rb +3 -1
  116. data/lib/html2rss/sst/node.rb +29 -10
  117. data/lib/html2rss/sst/normalizer.rb +12 -16
  118. data/lib/html2rss/sst/tags.rb +7 -7
  119. data/lib/html2rss/status.rb +13 -8
  120. data/lib/html2rss/surface_category.rb +14 -32
  121. data/lib/html2rss/syndication/parser.rb +2 -2
  122. data/lib/html2rss/test/enhance_audit.rb +1 -3
  123. data/lib/html2rss/test/policy.rb +106 -0
  124. data/lib/html2rss/test.rb +126 -172
  125. data/lib/html2rss/url.rb +25 -4
  126. data/lib/html2rss/version.rb +1 -1
  127. data/lib/html2rss.rb +27 -19
  128. data/lib/tasks/config_schema.rake +2 -2
  129. data/schema/html2rss-config.schema.json +11 -8
  130. metadata +19 -44
  131. data/lib/html2rss/request_service/faraday_strategy.rb +0 -233
  132. data/lib/html2rss/selectors/item_scope.rb +0 -30
@@ -434,7 +434,8 @@
434
434
  "properties": {
435
435
  "selector": {
436
436
  "type": "string",
437
- "minLength": 1
437
+ "minLength": 1,
438
+ "default": "a[href]"
438
439
  },
439
440
  "order": {
440
441
  "enum": [
@@ -507,9 +508,7 @@
507
508
  ]
508
509
  }
509
510
  },
510
- "required": [
511
- "selector"
512
- ],
511
+ "required": [],
513
512
  "description": "Defines the items selector and list-card enhance settings."
514
513
  },
515
514
  "enclosure": {
@@ -848,7 +847,7 @@
848
847
  "gsub": {
849
848
  "type": "object",
850
849
  "title": "gsub",
851
- "description": "Replace matches of `pattern` in the extracted string with `replacement` (Ruby String#gsub; pattern may be a regexp-like string).",
850
+ "description": "Replace matches of `pattern` in the extracted string with `replacement` (Ruby String#gsub; pattern may be a regexp-like string). Patterns over 256 characters as written, or with nested quantifiers, are rejected.",
852
851
  "examples": [
853
852
  {
854
853
  "name": "gsub",
@@ -862,10 +861,14 @@
862
861
  "const": "gsub"
863
862
  },
864
863
  "pattern": {
865
- "type": "string"
864
+ "type": "string",
865
+ "maxLength": 256
866
866
  },
867
867
  "replacement": {
868
- "type": "string"
868
+ "type": [
869
+ "string",
870
+ "object"
871
+ ]
869
872
  }
870
873
  },
871
874
  "required": [
@@ -1024,7 +1027,7 @@
1024
1027
  "type": "string",
1025
1028
  "const": "href",
1026
1029
  "title": "href",
1027
- "description": "Return the absolute URL from the selected element's `href` attribute (relative hrefs are resolved against the channel URL).",
1030
+ "description": "Return the absolute URL from the selected element's `href` attribute (relative hrefs are resolved against the page base URL).",
1028
1031
  "examples": [
1029
1032
  "href"
1030
1033
  ]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: html2rss
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.29.1
4
+ version: 0.31.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gil Desmarais
@@ -52,53 +52,19 @@ dependencies:
52
52
  - !ruby/object:Gem::Version
53
53
  version: '0'
54
54
  - !ruby/object:Gem::Dependency
55
- name: faraday
56
- requirement: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - ">"
59
- - !ruby/object:Gem::Version
60
- version: 2.0.1
61
- - - "<"
62
- - !ruby/object:Gem::Version
63
- version: '3.0'
64
- type: :runtime
65
- prerelease: false
66
- version_requirements: !ruby/object:Gem::Requirement
67
- requirements:
68
- - - ">"
69
- - !ruby/object:Gem::Version
70
- version: 2.0.1
71
- - - "<"
72
- - !ruby/object:Gem::Version
73
- version: '3.0'
74
- - !ruby/object:Gem::Dependency
75
- name: faraday-follow_redirects
76
- requirement: !ruby/object:Gem::Requirement
77
- requirements:
78
- - - ">="
79
- - !ruby/object:Gem::Version
80
- version: '0'
81
- type: :runtime
82
- prerelease: false
83
- version_requirements: !ruby/object:Gem::Requirement
84
- requirements:
85
- - - ">="
86
- - !ruby/object:Gem::Version
87
- version: '0'
88
- - !ruby/object:Gem::Dependency
89
- name: faraday-gzip
55
+ name: httpx
90
56
  requirement: !ruby/object:Gem::Requirement
91
57
  requirements:
92
58
  - - "~>"
93
59
  - !ruby/object:Gem::Version
94
- version: '3'
60
+ version: '1.8'
95
61
  type: :runtime
96
62
  prerelease: false
97
63
  version_requirements: !ruby/object:Gem::Requirement
98
64
  requirements:
99
65
  - - "~>"
100
66
  - !ruby/object:Gem::Version
101
- version: '3'
67
+ version: '1.8'
102
68
  - !ruby/object:Gem::Dependency
103
69
  name: kramdown
104
70
  requirement: !ruby/object:Gem::Requirement
@@ -348,13 +314,17 @@ files:
348
314
  - lib/html2rss/config.rb
349
315
  - lib/html2rss/config/auto_source_contract.rb
350
316
  - lib/html2rss/config/dynamic_params.rb
317
+ - lib/html2rss/config/issue_mapper.rb
351
318
  - lib/html2rss/config/multiple_feeds_config.rb
352
319
  - lib/html2rss/config/request_controls.rb
353
320
  - lib/html2rss/config/request_headers.rb
354
321
  - lib/html2rss/config/schema.rb
355
322
  - lib/html2rss/config/selectors_validator.rb
323
+ - lib/html2rss/config/validation_issue.rb
324
+ - lib/html2rss/config/validation_report.rb
356
325
  - lib/html2rss/config/validator.rb
357
326
  - lib/html2rss/defaults.rb
327
+ - lib/html2rss/doctor.rb
358
328
  - lib/html2rss/doctor/botasaurus.rb
359
329
  - lib/html2rss/error.rb
360
330
  - lib/html2rss/feed_builder.rb
@@ -431,7 +401,7 @@ files:
431
401
  - lib/html2rss/request_service/budget.rb
432
402
  - lib/html2rss/request_service/compressed_body.rb
433
403
  - lib/html2rss/request_service/context.rb
434
- - lib/html2rss/request_service/faraday_strategy.rb
404
+ - lib/html2rss/request_service/httpx_strategy.rb
435
405
  - lib/html2rss/request_service/local_file_strategy.rb
436
406
  - lib/html2rss/request_service/network_guard.rb
437
407
  - lib/html2rss/request_service/policy.rb
@@ -455,15 +425,18 @@ files:
455
425
  - lib/html2rss/scoring/ranked_segment.rb
456
426
  - lib/html2rss/scoring/score.rb
457
427
  - lib/html2rss/scrape_target.rb
428
+ - lib/html2rss/selector_candidates.rb
458
429
  - lib/html2rss/selectors.rb
430
+ - lib/html2rss/selectors/README.md
459
431
  - lib/html2rss/selectors/extractors.rb
460
432
  - lib/html2rss/selectors/extractors/attribute.rb
461
433
  - lib/html2rss/selectors/extractors/href.rb
462
434
  - lib/html2rss/selectors/extractors/html.rb
463
435
  - lib/html2rss/selectors/extractors/static.rb
464
436
  - lib/html2rss/selectors/extractors/text.rb
465
- - lib/html2rss/selectors/item_scope.rb
466
- - lib/html2rss/selectors/object_to_xml_converter.rb
437
+ - lib/html2rss/selectors/item_env.rb
438
+ - lib/html2rss/selectors/json_xml.rb
439
+ - lib/html2rss/selectors/option_spec.rb
467
440
  - lib/html2rss/selectors/post_processors.rb
468
441
  - lib/html2rss/selectors/post_processors/base.rb
469
442
  - lib/html2rss/selectors/post_processors/gsub.rb
@@ -475,7 +448,8 @@ files:
475
448
  - lib/html2rss/selectors/post_processors/sanitize_html.rb
476
449
  - lib/html2rss/selectors/post_processors/substring.rb
477
450
  - lib/html2rss/selectors/post_processors/template.rb
478
- - lib/html2rss/selectors/schema_doc.rb
451
+ - lib/html2rss/selectors/schema_export.rb
452
+ - lib/html2rss/selectors/step_env.rb
479
453
  - lib/html2rss/sst.rb
480
454
  - lib/html2rss/sst/attrs.rb
481
455
  - lib/html2rss/sst/document.rb
@@ -493,6 +467,7 @@ files:
493
467
  - lib/html2rss/syndication/parser.rb
494
468
  - lib/html2rss/test.rb
495
469
  - lib/html2rss/test/enhance_audit.rb
470
+ - lib/html2rss/test/policy.rb
496
471
  - lib/html2rss/url.rb
497
472
  - lib/html2rss/version.rb
498
473
  - lib/tasks/config_schema.rake
@@ -502,7 +477,7 @@ licenses:
502
477
  - MIT
503
478
  metadata:
504
479
  allowed_push_host: https://rubygems.org
505
- changelog_uri: https://github.com/html2rss/html2rss/releases/tag/v0.29.1
480
+ changelog_uri: https://github.com/html2rss/html2rss/releases/tag/v0.31.0
506
481
  rubygems_mfa_required: 'true'
507
482
  rdoc_options: []
508
483
  require_paths:
@@ -518,7 +493,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
518
493
  - !ruby/object:Gem::Version
519
494
  version: '0'
520
495
  requirements: []
521
- rubygems_version: 4.0.16
496
+ rubygems_version: 4.0.20
522
497
  specification_version: 4
523
498
  summary: Generates RSS feeds from websites by scraping a URL and using CSS selectors
524
499
  to extract item.
@@ -1,233 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'brotli'
4
- require 'faraday'
5
- require 'faraday/follow_redirects'
6
- require 'faraday/gzip'
7
-
8
- module Html2rss
9
- class RequestService
10
- ##
11
- # Strategy to use Faraday for the request.
12
- # @see https://rubygems.org/gems/faraday
13
- # rubocop:disable-next Metrics/ClassLength -- terminal redirect retry colocated with Faraday transport
14
- class FaradayStrategy < Strategy
15
- ##
16
- # Restores buffered streamed bytes so response middleware can process them.
17
- class StreamingBodyMiddleware < Faraday::Middleware
18
- # Request-context key used to store streamed chunks before middleware completion.
19
- STREAM_BUFFER_KEY = :html2rss_stream_buffer
20
-
21
- # @param env [Faraday::Env] completed response environment
22
- # @return [void]
23
- def on_complete(env)
24
- buffer = env.request.context&.delete(STREAM_BUFFER_KEY)
25
- return if buffer.nil? || buffer.empty?
26
-
27
- env.body = buffer
28
- end
29
- end
30
-
31
- ##
32
- # @return [ResponseGuard]
33
- attr_reader :response_guard
34
-
35
- # Executes the request with runtime policy enforcement, returning the normalized response.
36
- #
37
- # @return [Response] normalized response
38
- def perform_execute
39
- deadline = request_deadline
40
- @response_guard = ResponseGuard.new(policy: ctx.policy)
41
- reset_redirect_tracking!
42
- raw_response = request_with_terminal_redirect_retry(response_guard, deadline:)
43
- raw_response = retry_without_streaming(response_guard, deadline:) if retry_without_streaming?(raw_response)
44
- build_response(raw_response)
45
- end
46
-
47
- private
48
-
49
- def request_deadline
50
- monotonic_now + ctx.budget.effective_timeout_seconds(fallback: ctx.policy.total_timeout_seconds)
51
- end
52
-
53
- def reset_redirect_tracking!
54
- @last_redirect_to = nil
55
- @terminal_redirect_retried = false
56
- @request_url_override = nil
57
- end
58
-
59
- def request_with_terminal_redirect_retry(response_guard, deadline:)
60
- faraday_request(response_guard, deadline:, streaming_buffer: true)
61
- rescue Faraday::FollowRedirects::RedirectLimitReached => error
62
- raise RedirectLimitReached, error.message unless terminal_redirect_retryable?
63
-
64
- begin
65
- retry_from_terminal_redirect!(response_guard, deadline:)
66
- rescue Faraday::FollowRedirects::RedirectLimitReached => retry_error
67
- raise RedirectLimitReached, retry_error.message
68
- end
69
- end
70
-
71
- def terminal_redirect_retryable?
72
- return false if @terminal_redirect_retried || @last_redirect_to.nil?
73
-
74
- @last_redirect_to.to_s != request_url.to_s
75
- end
76
-
77
- def retry_from_terminal_redirect!(response_guard, deadline:)
78
- terminal_url = @last_redirect_to
79
- @terminal_redirect_retried = true
80
- Log.debug("#{self.class}: redirect limit reached; retrying once from #{terminal_url}")
81
- begin_terminal_url_request!(terminal_url)
82
- faraday_request(response_guard, deadline:, streaming_buffer: true, consume_budget: false)
83
- end
84
-
85
- def begin_terminal_url_request!(terminal_url)
86
- ctx.policy.validate_request!(url: terminal_url, origin_url: ctx.origin_url, relation: ctx.relation)
87
- @request_url_override = terminal_url
88
- @client = nil
89
- @last_redirect_to = nil
90
- end
91
-
92
- def request_url
93
- @request_url_override || ctx.url
94
- end
95
-
96
- def build_response(response)
97
- Response.new(body: CompressedBody.decode(response.body, headers: response.headers),
98
- headers: response.headers, url: response_url(response),
99
- status: response.status)
100
- end
101
-
102
- def faraday_request(response_guard, deadline:, streaming_buffer:, consume_budget: true)
103
- preflight!(consume_budget:)
104
-
105
- client.get do |req|
106
- apply_timeouts(req, deadline:)
107
- next unless streaming_buffer
108
-
109
- buffer = prepare_stream_buffer(req)
110
- req.options.on_data = on_data_callback(response_guard, buffer)
111
- end
112
- end
113
-
114
- def retry_without_streaming(response_guard, deadline:)
115
- faraday_request(response_guard, deadline:, streaming_buffer: false, consume_budget: false)
116
- end
117
-
118
- ##
119
- # Validates the remote socket peer IP during Net::HTTP start.
120
- module PeerIpValidator
121
- module_function
122
-
123
- # @param http [Net::HTTP] connection to configure
124
- # @param policy [Policy] request policy
125
- # @return [void]
126
- def install!(http, policy:)
127
- orig_start = http.method(:start)
128
- http.define_singleton_method(:start) do |&block|
129
- orig_start.call do |opened_http|
130
- PeerIpValidator.validate!(opened_http, policy:)
131
- block.call(opened_http)
132
- end
133
- end
134
- end
135
-
136
- # @param opened_http [Net::HTTP] active connection
137
- # @param policy [Policy] request policy
138
- # @return [void]
139
- def validate!(opened_http, policy:)
140
- scheme = opened_http.use_ssl? ? 'https' : 'http'
141
- url = Html2rss::Url.from_absolute("#{scheme}://#{opened_http.address}:#{opened_http.port}")
142
- policy.validate_remote_ip!(ip: peer_ip_for(opened_http), url:)
143
- end
144
-
145
- # @param opened_http [Net::HTTP]
146
- # @return [String, nil]
147
- def peer_ip_for(opened_http)
148
- sock = opened_http.instance_variable_get(:@socket)
149
- io = sock.respond_to?(:io) ? sock.io : sock
150
- peeraddr = io.respond_to?(:peeraddr) ? io.peeraddr : nil
151
- peeraddr&.[](3) || peeraddr&.[](2)
152
- end
153
- end
154
-
155
- # rubocop:disable-next Metrics/AbcSize
156
- def client
157
- @client ||= Faraday.new(url: request_url.to_s, headers: ctx.headers) do |faraday|
158
- faraday.use Faraday::FollowRedirects::Middleware, limit: ctx.policy.max_redirects, callback: redirect_callback
159
- faraday.request :gzip
160
- faraday.use StreamingBodyMiddleware
161
- faraday.adapter Faraday.default_adapter do |http|
162
- PeerIpValidator.install!(http, policy: ctx.policy)
163
- end
164
- end
165
- end
166
-
167
- def apply_timeouts(request, deadline:)
168
- remaining_timeout = remaining_timeout_seconds(deadline)
169
- request.options.timeout = remaining_timeout
170
- request.options.open_timeout = [ctx.policy.connect_timeout_seconds, remaining_timeout].min
171
- request.options.read_timeout = [ctx.policy.read_timeout_seconds, remaining_timeout].min
172
- end
173
-
174
- def prepare_stream_buffer(request)
175
- request.options.context ||= {}
176
- request.options.context[StreamingBodyMiddleware::STREAM_BUFFER_KEY] = +''
177
- end
178
-
179
- def on_data_callback(response_guard, buffer)
180
- proc do |chunk, total_bytes, env|
181
- response_guard.inspect_chunk!(total_bytes:, headers: env&.response_headers)
182
- buffer&.<< chunk
183
- end
184
- end
185
-
186
- def remaining_timeout_seconds(deadline)
187
- remaining = deadline - monotonic_now
188
- raise RequestTimedOut, 'Request timed out' if remaining <= 0
189
-
190
- remaining
191
- end
192
-
193
- def retry_without_streaming?(response)
194
- return false if response.body.to_s.empty? == false
195
- return false unless response_success?(response)
196
-
197
- final_url = response.env&.url
198
- return false unless final_url
199
-
200
- final_url.to_s != ctx.url.to_s
201
- end
202
-
203
- def response_success?(response)
204
- return true if response.status.nil?
205
-
206
- response.status >= 200 && response.status < 300
207
- end
208
-
209
- def response_url(response)
210
- return ctx.url unless (url = response.env&.url)
211
-
212
- Html2rss::Url.from_absolute(url.to_s)
213
- end
214
-
215
- def redirect_callback
216
- lambda do |old_env, new_env|
217
- from_url = normalize_url(old_env[:url])
218
- to_url = normalize_url(new_env[:url])
219
- @last_redirect_to = to_url
220
- ctx.policy.validate_redirect!(from_url:, to_url:, origin_url: ctx.origin_url, relation: ctx.relation)
221
- end
222
- end
223
-
224
- def normalize_url(url)
225
- Html2rss::Url.from_absolute(url.to_s)
226
- end
227
-
228
- def monotonic_now
229
- Process.clock_gettime(Process::CLOCK_MONOTONIC)
230
- end
231
- end
232
- end
233
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Html2rss
4
- class Selectors
5
- ##
6
- # Per-item extraction scope: owns the article node and page +base_url+ for one
7
- # extraction pass. Channel hashes are injected (and cached) by {Selectors}.
8
- #
9
- # Distinct from {Context}, which is the post-processor invocation bag (+options+).
10
- # Post-processor +config+ is derived from {#channel} — not a parallel bag.
11
- ItemScope = Data.define(:item, :base_url, :scraper, :channel) do
12
- ##
13
- # Selects an attribute using this scope's item and base_url.
14
- #
15
- # @param name [Symbol, String]
16
- # @return [Object, Array<Object>]
17
- def select(name) = scraper.select_in_scope(name, self)
18
-
19
- ##
20
- # Builds a post-processor {Context} carrying this scope for nested selects.
21
- #
22
- # @param options [Hash] post-processor options from the selector config
23
- # @option options [String] :name post-processor name
24
- # @return [Context]
25
- def context_for(options:)
26
- Context.new(options:, item:, config: { channel: }, scraper:, item_scope: self)
27
- end
28
- end
29
- end
30
- end