html2rss 0.28.0 → 0.30.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.
- checksums.yaml +4 -4
- data/README.md +84 -27
- data/html2rss.gemspec +1 -4
- data/lib/html2rss/article.rb +1 -2
- data/lib/html2rss/auto_source/README.md +8 -8
- data/lib/html2rss/auto_source/cleanup.rb +92 -1
- data/lib/html2rss/auto_source/scraper/json_state/document_scanner.rb +3 -5
- data/lib/html2rss/auto_source/scraper/meta_oembed.rb +15 -5
- data/lib/html2rss/auto_source/scraper/microdata.rb +8 -6
- data/lib/html2rss/auto_source/scraper/native_feed.rb +5 -2
- data/lib/html2rss/auto_source/scraper/schema.rb +39 -21
- data/lib/html2rss/auto_source/scraper.rb +2 -3
- data/lib/html2rss/auto_source/segmenter/cluster.rb +2 -3
- data/lib/html2rss/auto_source/segmenter/primary_link.rb +1 -2
- data/lib/html2rss/auto_source/segmenter.rb +1 -2
- data/lib/html2rss/auto_source.rb +25 -11
- data/lib/html2rss/batch.rb +169 -0
- data/lib/html2rss/capture/README.md +15 -6
- data/lib/html2rss/capture.rb +159 -16
- data/lib/html2rss/channel.rb +1 -2
- data/lib/html2rss/cli/probe_view.rb +66 -0
- data/lib/html2rss/cli/render.rb +124 -0
- data/lib/html2rss/cli/validate.rb +91 -0
- data/lib/html2rss/cli.rb +318 -193
- data/lib/html2rss/config/request_headers.rb +9 -16
- data/lib/html2rss/config/schema.rb +7 -5
- data/lib/html2rss/config/validator.rb +1 -0
- data/lib/html2rss/config.rb +68 -19
- data/lib/html2rss/doctor/botasaurus.rb +138 -0
- data/lib/html2rss/feed_builder/rss.rb +1 -2
- data/lib/html2rss/feed_pipeline/README.md +11 -11
- data/lib/html2rss/feed_pipeline/auto_fallback.rb +30 -11
- data/lib/html2rss/feed_pipeline/strategy_plan.rb +1 -1
- data/lib/html2rss/feed_pipeline.rb +27 -16
- data/lib/html2rss/feed_resolution/README.md +11 -11
- data/lib/html2rss/feed_resolution.rb +6 -12
- data/lib/html2rss/html/article_extractor/heading_extractor.rb +24 -14
- data/lib/html2rss/html/article_extractor.rb +4 -9
- data/lib/html2rss/html/feed_link.rb +4 -2
- data/lib/html2rss/html/navigator.rb +4 -39
- data/lib/html2rss/html/probe.rb +82 -0
- data/lib/html2rss/html/sst_article_extractor.rb +1 -2
- data/lib/html2rss/link_destination/noise_policy.rb +1 -2
- data/lib/html2rss/link_destination/path_classifier.rb +20 -19
- data/lib/html2rss/mcp/README.md +80 -0
- data/lib/html2rss/mcp/contract.rb +144 -18
- data/lib/html2rss/mcp/outcome/playbook.rb +122 -0
- data/lib/html2rss/mcp/outcome.rb +112 -42
- data/lib/html2rss/mcp/runtime.rb +45 -0
- data/lib/html2rss/mcp/server/tools.rb +277 -0
- data/lib/html2rss/mcp/server.rb +92 -233
- data/lib/html2rss/mcp.rb +3 -0
- data/lib/html2rss/page_recon/README.md +69 -0
- data/lib/html2rss/page_recon/diagnostics.rb +211 -0
- data/lib/html2rss/page_recon.rb +56 -2
- data/lib/html2rss/recon.rb +252 -0
- data/lib/html2rss/request_service/blocked_surface.rb +1 -0
- data/lib/html2rss/request_service/botasaurus_strategy.rb +32 -28
- data/lib/html2rss/request_service/compressed_body.rb +13 -8
- data/lib/html2rss/request_service/httpx_strategy.rb +228 -0
- data/lib/html2rss/request_service/policy.rb +1 -2
- data/lib/html2rss/request_service/response.rb +3 -4
- data/lib/html2rss/request_service/response_guard.rb +0 -16
- data/lib/html2rss/request_service/strategy.rb +20 -5
- data/lib/html2rss/request_service.rb +48 -85
- data/lib/html2rss/request_session.rb +1 -2
- data/lib/html2rss/scrape_target.rb +1 -1
- data/lib/html2rss/selectors/post_processors/sanitize_html.rb +2 -4
- data/lib/html2rss/selectors/post_processors.rb +0 -1
- data/lib/html2rss/selectors.rb +33 -10
- data/lib/html2rss/sst/attrs.rb +1 -2
- data/lib/html2rss/sst/normalizer.rb +49 -11
- data/lib/html2rss/status.rb +3 -6
- data/lib/html2rss/syndication/README.md +6 -6
- data/lib/html2rss/syndication/discovery.rb +2 -4
- data/lib/html2rss/test/enhance_audit.rb +192 -0
- data/lib/html2rss/test.rb +459 -0
- data/lib/html2rss/url.rb +1 -2
- data/lib/html2rss/version.rb +1 -1
- data/lib/html2rss.rb +157 -35
- data/schema/html2rss-config.schema.json +9 -29
- metadata +21 -56
- data/lib/html2rss/mcp/inspect.rb +0 -138
- data/lib/html2rss/request_service/faraday_strategy.rb +0 -229
- data/lib/html2rss/selectors/post_processors/html_to_markdown.rb +0 -59
data/lib/html2rss.rb
CHANGED
|
@@ -16,7 +16,7 @@ require 'html2rss/defaults'
|
|
|
16
16
|
|
|
17
17
|
##
|
|
18
18
|
# The Html2rss namespace.
|
|
19
|
-
module Html2rss
|
|
19
|
+
module Html2rss # rubocop:disable Metrics/ModuleLength
|
|
20
20
|
##
|
|
21
21
|
# The logger instance.
|
|
22
22
|
module Log
|
|
@@ -28,6 +28,160 @@ module Html2rss
|
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
##
|
|
32
|
+
# Cheap diagnostics for a URL (final URL, status, alternates, surface).
|
|
33
|
+
# Golden path step 1 (optional); use {.recon} for verdict and native_feed.
|
|
34
|
+
#
|
|
35
|
+
# @param url [String] source page URL
|
|
36
|
+
# @param strategy [Symbol] request strategy (:auto, :default, :botasaurus)
|
|
37
|
+
# @param deep [Boolean] when true and strategy is :auto, one Botasaurus hop if configured
|
|
38
|
+
# @return [Html2rss::PageRecon::Diagnostics::Report]
|
|
39
|
+
def self.inspect(url, strategy: :auto, deep: false, **)
|
|
40
|
+
PageRecon::Diagnostics.call(url:, strategy:, deep:, **)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
##
|
|
44
|
+
# Curation verdict and native_feed preference for a URL.
|
|
45
|
+
# Golden path step 2 (optional); adds verdict beyond {.inspect}.
|
|
46
|
+
#
|
|
47
|
+
# @param url [String, Html2rss::Url] source page URL
|
|
48
|
+
# @param strategy [Symbol] request strategy (:auto, :default, :botasaurus)
|
|
49
|
+
# @return [Html2rss::Recon::Result]
|
|
50
|
+
def self.recon(url, strategy: :auto, **)
|
|
51
|
+
Recon.call(url, strategy:, **)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
##
|
|
55
|
+
# Derives a reusable YAML-ready feed config from a URL.
|
|
56
|
+
# Golden path step 3.
|
|
57
|
+
#
|
|
58
|
+
# @param url [String] source page URL
|
|
59
|
+
# @param strategy [Symbol] request strategy (+:auto+, +:default+, +:botasaurus+)
|
|
60
|
+
# @option options [String, nil] :items_selector optional CSS selector hint for items
|
|
61
|
+
# @option options [Array<String>, nil] :topics optional directory topics override
|
|
62
|
+
# @option options [String, nil] :title optional title override
|
|
63
|
+
# @option options [String, nil] :summary optional summary override
|
|
64
|
+
# @option options [Boolean] :force whether to ignore native feed detection
|
|
65
|
+
# @option options [Boolean, nil] :enhance whether to force enhance: true
|
|
66
|
+
# @option options [Integer, nil] :max_redirects optional redirect limit override
|
|
67
|
+
# @option options [Integer, nil] :max_requests optional request budget override
|
|
68
|
+
# @option options [Integer, nil] :limit max articles to keep
|
|
69
|
+
# @option options [String, nil] :local_file_path optional local HTML file path
|
|
70
|
+
# @return [Html2rss::Capture::CaptureResult]
|
|
71
|
+
def self.capture(url, strategy: :auto, **)
|
|
72
|
+
Capture.build(url, strategy:, **)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
##
|
|
76
|
+
# Validates a config hash, YAML string, or file path against the schema.
|
|
77
|
+
# Side door: schema-only check without live extraction.
|
|
78
|
+
#
|
|
79
|
+
# @param config_input [Hash, String]
|
|
80
|
+
# @param feed_name [String, nil]
|
|
81
|
+
# @param params [Hash]
|
|
82
|
+
# @return [Dry::Validation::Result, Html2rss::Config::ValidationResult]
|
|
83
|
+
def self.validate(config_input, feed_name = nil, params: {})
|
|
84
|
+
_raw, validation = Config.resolve_and_validate(config_input, feed_name:, params:)
|
|
85
|
+
validation
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
##
|
|
89
|
+
# Validates schema and asserts live item extraction.
|
|
90
|
+
# Golden path step 4.
|
|
91
|
+
#
|
|
92
|
+
# @param config_input [Hash, String] config hash, YAML string, or file path
|
|
93
|
+
# @param feed_name [String, nil] optional feed name in multi-feed file
|
|
94
|
+
# @param min_items [Integer] minimum required items (default: 1)
|
|
95
|
+
# @param params [Hash] dynamic feed params
|
|
96
|
+
# @param strategy [Symbol, nil] optional strategy override
|
|
97
|
+
# @param strict_quality [Boolean] when true, fail on ship-quality audit thresholds
|
|
98
|
+
# @param compare_enhance [Boolean] diagnostic enhance off vs on comparison on cached HTML
|
|
99
|
+
# @return [Html2rss::Test::Result]
|
|
100
|
+
def self.test(config_input, feed_name = nil, min_items: 1, params: {}, strategy: nil, strict_quality: false, # rubocop:disable Metrics/ParameterLists
|
|
101
|
+
compare_enhance: false)
|
|
102
|
+
Test.call(config_input, feed_name, min_items:, params:, strategy:, strict_quality:, compare_enhance:)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
##
|
|
106
|
+
# Ships RSS from a validated config (user-facing verb for {.feed_result}).
|
|
107
|
+
# Golden path step 5.
|
|
108
|
+
#
|
|
109
|
+
# @param raw_config [Hash{Symbol => Object}] feed configuration
|
|
110
|
+
# @return [Html2rss::FeedResult]
|
|
111
|
+
def self.apply(raw_config)
|
|
112
|
+
feed_result(raw_config)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# rubocop:disable Metrics/ParameterLists
|
|
116
|
+
|
|
117
|
+
##
|
|
118
|
+
# One-shot auto-source scrape from a URL (user-facing verb for {.auto_feed_result}).
|
|
119
|
+
#
|
|
120
|
+
# @param url [String] source page URL
|
|
121
|
+
# @param strategy [Symbol] request strategy to use
|
|
122
|
+
# @param items_selector [String, nil] optional selector hint for item extraction
|
|
123
|
+
# @param max_redirects [Integer, nil] optional redirect limit override
|
|
124
|
+
# @param max_requests [Integer] optional request budget override (default: 4 for sitemap sub-fetches)
|
|
125
|
+
# @param local_file_path [String, nil] optional local HTML file path
|
|
126
|
+
# @param limit [Integer, nil] max articles to keep (default: {AutoSource::DEFAULT_LIMIT})
|
|
127
|
+
# @return [Html2rss::FeedResult]
|
|
128
|
+
def self.scrape(url,
|
|
129
|
+
strategy: :auto,
|
|
130
|
+
items_selector: nil,
|
|
131
|
+
max_redirects: nil,
|
|
132
|
+
max_requests: 4,
|
|
133
|
+
local_file_path: nil,
|
|
134
|
+
limit: nil)
|
|
135
|
+
auto_feed_result(url, strategy:, items_selector:, max_redirects:, max_requests:,
|
|
136
|
+
local_file_path:, limit:)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
##
|
|
140
|
+
# Scrapes multiple URLs in parallel using auto-source article discovery.
|
|
141
|
+
#
|
|
142
|
+
# @param urls [Enumerable<String>] list of URLs to scrape
|
|
143
|
+
# @param strategy [Symbol] request strategy (:auto, :default, :botasaurus)
|
|
144
|
+
# @param limit [Integer] max articles to keep per URL (default: 10)
|
|
145
|
+
# @param concurrency [Integer] max worker threads (default: 5)
|
|
146
|
+
# @return [Html2rss::Batch::BatchResult]
|
|
147
|
+
def self.batch_scrape(urls, strategy: :auto, limit: 10, concurrency: Batch::DEFAULT_CONCURRENCY)
|
|
148
|
+
Batch.batch_scrape(urls:, strategy:, limit:, concurrency:)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
##
|
|
152
|
+
# Inspects multiple URLs in parallel with per-URL error isolation.
|
|
153
|
+
#
|
|
154
|
+
# @param urls [Enumerable<String>] list of URLs to inspect
|
|
155
|
+
# @param strategy [Symbol] request strategy (:auto, :default, :botasaurus)
|
|
156
|
+
# @param concurrency [Integer] max worker threads (default: 5)
|
|
157
|
+
# @return [Html2rss::Batch::BatchResult]
|
|
158
|
+
def self.batch_inspect(urls, strategy: :auto, concurrency: Batch::DEFAULT_CONCURRENCY)
|
|
159
|
+
Batch.batch_inspect(urls:, strategy:, concurrency:)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
##
|
|
163
|
+
# Runs recon across multiple URLs in parallel with per-URL error isolation.
|
|
164
|
+
#
|
|
165
|
+
# @param urls [Enumerable<String>] list of URLs to recon
|
|
166
|
+
# @param strategy [Symbol] request strategy (:auto, :default, :botasaurus)
|
|
167
|
+
# @param concurrency [Integer] max worker threads (default: 5)
|
|
168
|
+
# @option options [String, nil] :cache_dir optional HTML cache directory
|
|
169
|
+
# @return [Html2rss::Batch::BatchResult]
|
|
170
|
+
def self.batch_recon(urls, strategy: :auto, concurrency: Batch::DEFAULT_CONCURRENCY, **)
|
|
171
|
+
Batch.batch_recon(urls:, strategy:, concurrency:, **)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# rubocop:enable Metrics/ParameterLists
|
|
175
|
+
|
|
176
|
+
##
|
|
177
|
+
# Exports the configuration JSON Schema as JSON string.
|
|
178
|
+
#
|
|
179
|
+
# @param pretty [Boolean] whether to pretty-print JSON
|
|
180
|
+
# @return [String]
|
|
181
|
+
def self.schema_json(pretty: true)
|
|
182
|
+
Config.json_schema_json(pretty:)
|
|
183
|
+
end
|
|
184
|
+
|
|
31
185
|
##
|
|
32
186
|
# Loads a feed configuration from YAML.
|
|
33
187
|
#
|
|
@@ -143,39 +297,9 @@ module Html2rss
|
|
|
143
297
|
local_file_path:, limit:).to_json_feed
|
|
144
298
|
end
|
|
145
299
|
|
|
146
|
-
##
|
|
147
|
-
# Analyzes a URL and produces a reusable YAML-ready feed config hash.
|
|
148
|
-
#
|
|
149
|
-
# Uses auto-source discovery to extract articles, then derives CSS selectors
|
|
150
|
-
# from the structural analysis.
|
|
151
|
-
#
|
|
152
|
-
# @param url [String] source page URL
|
|
153
|
-
# @param strategy [Symbol] request strategy (+:auto+, +:faraday+, +:botasaurus+)
|
|
154
|
-
# @param items_selector [String, nil] optional CSS selector hint for items
|
|
155
|
-
# @param max_redirects [Integer, nil] optional redirect limit override
|
|
156
|
-
# @param max_requests [Integer, nil] optional request budget override
|
|
157
|
-
# @param limit [Integer, nil] max articles to keep
|
|
158
|
-
# @param local_file_path [String, nil] optional local HTML file path
|
|
159
|
-
# @return [Hash] feed config hash with +:channel+ and +:selectors+
|
|
160
|
-
def self.capture(url,
|
|
161
|
-
strategy: :auto,
|
|
162
|
-
items_selector: nil,
|
|
163
|
-
max_redirects: nil,
|
|
164
|
-
max_requests: nil,
|
|
165
|
-
limit: nil,
|
|
166
|
-
local_file_path: nil)
|
|
167
|
-
Capture.build(url,
|
|
168
|
-
strategy:,
|
|
169
|
-
items_selector:,
|
|
170
|
-
max_redirects:,
|
|
171
|
-
max_requests:,
|
|
172
|
-
limit:,
|
|
173
|
-
local_file_path:).config
|
|
174
|
-
end
|
|
175
|
-
|
|
176
300
|
# rubocop:enable Metrics/ParameterLists
|
|
177
301
|
|
|
178
|
-
# rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
302
|
+
# rubocop:disable-next ThreadSafety/ClassInstanceVariable
|
|
179
303
|
class << self
|
|
180
304
|
##
|
|
181
305
|
# @return [Html2rss::Defaults] the global defaults instance
|
|
@@ -217,12 +341,11 @@ module Html2rss
|
|
|
217
341
|
logger.level = defaults.log_level if logger.respond_to?(:level=)
|
|
218
342
|
end
|
|
219
343
|
end
|
|
220
|
-
# rubocop:enable ThreadSafety/ClassInstanceVariable
|
|
221
344
|
|
|
222
345
|
class << self
|
|
223
346
|
private
|
|
224
347
|
|
|
225
|
-
# rubocop:disable Metrics/ParameterLists, Metrics/MethodLength
|
|
348
|
+
# rubocop:disable-next Metrics/ParameterLists, Metrics/MethodLength
|
|
226
349
|
def build_auto_source_config(url:, strategy:, items_selector:, max_redirects:, max_requests:,
|
|
227
350
|
local_file_path: nil, limit: nil)
|
|
228
351
|
config = Config.auto_source_config(
|
|
@@ -237,7 +360,6 @@ module Html2rss
|
|
|
237
360
|
end
|
|
238
361
|
config
|
|
239
362
|
end
|
|
240
|
-
# rubocop:enable Metrics/ParameterLists, Metrics/MethodLength
|
|
241
363
|
end
|
|
242
364
|
|
|
243
365
|
logger.level = defaults.log_level if logger.respond_to?(:level=)
|
|
@@ -97,7 +97,12 @@
|
|
|
97
97
|
"consumer",
|
|
98
98
|
"civic",
|
|
99
99
|
"product",
|
|
100
|
-
"research"
|
|
100
|
+
"research",
|
|
101
|
+
"health",
|
|
102
|
+
"culture",
|
|
103
|
+
"education",
|
|
104
|
+
"rights",
|
|
105
|
+
"transport"
|
|
101
106
|
]
|
|
102
107
|
},
|
|
103
108
|
"minItems": 1
|
|
@@ -443,7 +448,8 @@
|
|
|
443
448
|
"type": "boolean",
|
|
444
449
|
"not": {
|
|
445
450
|
"type": "null"
|
|
446
|
-
}
|
|
451
|
+
},
|
|
452
|
+
"description": "List-card enrichment: run Html::ArticleExtractor on each matched item node to fill missing fields from the card HTML."
|
|
447
453
|
},
|
|
448
454
|
"pagination": {
|
|
449
455
|
"description": "Pagination configuration or maximum page count integer.",
|
|
@@ -504,7 +510,7 @@
|
|
|
504
510
|
"required": [
|
|
505
511
|
"selector"
|
|
506
512
|
],
|
|
507
|
-
"description": "Defines the items selector and
|
|
513
|
+
"description": "Defines the items selector and list-card enhance settings."
|
|
508
514
|
},
|
|
509
515
|
"enclosure": {
|
|
510
516
|
"$schema": "http://json-schema.org/draft-06/schema#",
|
|
@@ -546,9 +552,6 @@
|
|
|
546
552
|
{
|
|
547
553
|
"$ref": "#/$defs/post_processors/gsub"
|
|
548
554
|
},
|
|
549
|
-
{
|
|
550
|
-
"$ref": "#/$defs/post_processors/html_to_markdown"
|
|
551
|
-
},
|
|
552
555
|
{
|
|
553
556
|
"$ref": "#/$defs/post_processors/markdown_to_html"
|
|
554
557
|
},
|
|
@@ -638,9 +641,6 @@
|
|
|
638
641
|
{
|
|
639
642
|
"$ref": "#/$defs/post_processors/gsub"
|
|
640
643
|
},
|
|
641
|
-
{
|
|
642
|
-
"$ref": "#/$defs/post_processors/html_to_markdown"
|
|
643
|
-
},
|
|
644
644
|
{
|
|
645
645
|
"$ref": "#/$defs/post_processors/markdown_to_html"
|
|
646
646
|
},
|
|
@@ -875,26 +875,6 @@
|
|
|
875
875
|
],
|
|
876
876
|
"additionalProperties": true
|
|
877
877
|
},
|
|
878
|
-
"html_to_markdown": {
|
|
879
|
-
"type": "object",
|
|
880
|
-
"title": "html_to_markdown",
|
|
881
|
-
"description": "Sanitize HTML then convert it to Markdown (via ReverseMarkdown).",
|
|
882
|
-
"examples": [
|
|
883
|
-
{
|
|
884
|
-
"name": "html_to_markdown"
|
|
885
|
-
}
|
|
886
|
-
],
|
|
887
|
-
"properties": {
|
|
888
|
-
"name": {
|
|
889
|
-
"type": "string",
|
|
890
|
-
"const": "html_to_markdown"
|
|
891
|
-
}
|
|
892
|
-
},
|
|
893
|
-
"required": [
|
|
894
|
-
"name"
|
|
895
|
-
],
|
|
896
|
-
"additionalProperties": true
|
|
897
|
-
},
|
|
898
878
|
"markdown_to_html": {
|
|
899
879
|
"type": "object",
|
|
900
880
|
"title": "markdown_to_html",
|
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.
|
|
4
|
+
version: 0.30.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:
|
|
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: '
|
|
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: '
|
|
67
|
+
version: '1.8'
|
|
102
68
|
- !ruby/object:Gem::Dependency
|
|
103
69
|
name: kramdown
|
|
104
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -203,20 +169,6 @@ dependencies:
|
|
|
203
169
|
- - ">="
|
|
204
170
|
- !ruby/object:Gem::Version
|
|
205
171
|
version: '0'
|
|
206
|
-
- !ruby/object:Gem::Dependency
|
|
207
|
-
name: reverse_markdown
|
|
208
|
-
requirement: !ruby/object:Gem::Requirement
|
|
209
|
-
requirements:
|
|
210
|
-
- - "~>"
|
|
211
|
-
- !ruby/object:Gem::Version
|
|
212
|
-
version: '3.0'
|
|
213
|
-
type: :runtime
|
|
214
|
-
prerelease: false
|
|
215
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
216
|
-
requirements:
|
|
217
|
-
- - "~>"
|
|
218
|
-
- !ruby/object:Gem::Version
|
|
219
|
-
version: '3.0'
|
|
220
172
|
- !ruby/object:Gem::Dependency
|
|
221
173
|
name: rss
|
|
222
174
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -351,10 +303,14 @@ files:
|
|
|
351
303
|
- lib/html2rss/auto_source/segmenter/list.rb
|
|
352
304
|
- lib/html2rss/auto_source/segmenter/primary_link.rb
|
|
353
305
|
- lib/html2rss/auto_source/segmenter/semantic.rb
|
|
306
|
+
- lib/html2rss/batch.rb
|
|
354
307
|
- lib/html2rss/capture.rb
|
|
355
308
|
- lib/html2rss/capture/README.md
|
|
356
309
|
- lib/html2rss/channel.rb
|
|
357
310
|
- lib/html2rss/cli.rb
|
|
311
|
+
- lib/html2rss/cli/probe_view.rb
|
|
312
|
+
- lib/html2rss/cli/render.rb
|
|
313
|
+
- lib/html2rss/cli/validate.rb
|
|
358
314
|
- lib/html2rss/config.rb
|
|
359
315
|
- lib/html2rss/config/auto_source_contract.rb
|
|
360
316
|
- lib/html2rss/config/dynamic_params.rb
|
|
@@ -365,6 +321,7 @@ files:
|
|
|
365
321
|
- lib/html2rss/config/selectors_validator.rb
|
|
366
322
|
- lib/html2rss/config/validator.rb
|
|
367
323
|
- lib/html2rss/defaults.rb
|
|
324
|
+
- lib/html2rss/doctor/botasaurus.rb
|
|
368
325
|
- lib/html2rss/error.rb
|
|
369
326
|
- lib/html2rss/feed_builder.rb
|
|
370
327
|
- lib/html2rss/feed_builder/item_presentation.rb
|
|
@@ -405,6 +362,7 @@ files:
|
|
|
405
362
|
- lib/html2rss/html/feed_link.rb
|
|
406
363
|
- lib/html2rss/html/navigator.rb
|
|
407
364
|
- lib/html2rss/html/navigator/text_extractor.rb
|
|
365
|
+
- lib/html2rss/html/probe.rb
|
|
408
366
|
- lib/html2rss/html/rendering.rb
|
|
409
367
|
- lib/html2rss/html/rendering/audio_renderer.rb
|
|
410
368
|
- lib/html2rss/html/rendering/description_builder.rb
|
|
@@ -420,12 +378,18 @@ files:
|
|
|
420
378
|
- lib/html2rss/link_destination/path_classifier.rb
|
|
421
379
|
- lib/html2rss/link_destination/text_classifier.rb
|
|
422
380
|
- lib/html2rss/mcp.rb
|
|
381
|
+
- lib/html2rss/mcp/README.md
|
|
423
382
|
- lib/html2rss/mcp/config_argument.rb
|
|
424
383
|
- lib/html2rss/mcp/contract.rb
|
|
425
|
-
- lib/html2rss/mcp/inspect.rb
|
|
426
384
|
- lib/html2rss/mcp/outcome.rb
|
|
385
|
+
- lib/html2rss/mcp/outcome/playbook.rb
|
|
386
|
+
- lib/html2rss/mcp/runtime.rb
|
|
427
387
|
- lib/html2rss/mcp/server.rb
|
|
388
|
+
- lib/html2rss/mcp/server/tools.rb
|
|
428
389
|
- lib/html2rss/page_recon.rb
|
|
390
|
+
- lib/html2rss/page_recon/README.md
|
|
391
|
+
- lib/html2rss/page_recon/diagnostics.rb
|
|
392
|
+
- lib/html2rss/recon.rb
|
|
429
393
|
- lib/html2rss/request_service.rb
|
|
430
394
|
- lib/html2rss/request_service/blocked_surface.rb
|
|
431
395
|
- lib/html2rss/request_service/botasaurus_contract.rb
|
|
@@ -433,7 +397,7 @@ files:
|
|
|
433
397
|
- lib/html2rss/request_service/budget.rb
|
|
434
398
|
- lib/html2rss/request_service/compressed_body.rb
|
|
435
399
|
- lib/html2rss/request_service/context.rb
|
|
436
|
-
- lib/html2rss/request_service/
|
|
400
|
+
- lib/html2rss/request_service/httpx_strategy.rb
|
|
437
401
|
- lib/html2rss/request_service/local_file_strategy.rb
|
|
438
402
|
- lib/html2rss/request_service/network_guard.rb
|
|
439
403
|
- lib/html2rss/request_service/policy.rb
|
|
@@ -469,7 +433,6 @@ files:
|
|
|
469
433
|
- lib/html2rss/selectors/post_processors.rb
|
|
470
434
|
- lib/html2rss/selectors/post_processors/base.rb
|
|
471
435
|
- lib/html2rss/selectors/post_processors/gsub.rb
|
|
472
|
-
- lib/html2rss/selectors/post_processors/html_to_markdown.rb
|
|
473
436
|
- lib/html2rss/selectors/post_processors/html_transformers/transform_urls_to_absolute_ones.rb
|
|
474
437
|
- lib/html2rss/selectors/post_processors/html_transformers/wrap_img_in_a.rb
|
|
475
438
|
- lib/html2rss/selectors/post_processors/markdown_to_html.rb
|
|
@@ -494,6 +457,8 @@ files:
|
|
|
494
457
|
- lib/html2rss/syndication/candidate_catalog.rb
|
|
495
458
|
- lib/html2rss/syndication/discovery.rb
|
|
496
459
|
- lib/html2rss/syndication/parser.rb
|
|
460
|
+
- lib/html2rss/test.rb
|
|
461
|
+
- lib/html2rss/test/enhance_audit.rb
|
|
497
462
|
- lib/html2rss/url.rb
|
|
498
463
|
- lib/html2rss/version.rb
|
|
499
464
|
- lib/tasks/config_schema.rake
|
|
@@ -503,7 +468,7 @@ licenses:
|
|
|
503
468
|
- MIT
|
|
504
469
|
metadata:
|
|
505
470
|
allowed_push_host: https://rubygems.org
|
|
506
|
-
changelog_uri: https://github.com/html2rss/html2rss/releases/tag/v0.
|
|
471
|
+
changelog_uri: https://github.com/html2rss/html2rss/releases/tag/v0.30.0
|
|
507
472
|
rubygems_mfa_required: 'true'
|
|
508
473
|
rdoc_options: []
|
|
509
474
|
require_paths:
|
data/lib/html2rss/mcp/inspect.rb
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Html2rss
|
|
4
|
-
module MCP
|
|
5
|
-
##
|
|
6
|
-
# Diagnostic inspect path (not Capture ownership). Fetches once, then delegates
|
|
7
|
-
# shared recon to {Html2rss::PageRecon}; adds MCP-only scraper/XHR diagnostics.
|
|
8
|
-
module Inspect
|
|
9
|
-
module_function
|
|
10
|
-
|
|
11
|
-
##
|
|
12
|
-
# @param url [String]
|
|
13
|
-
# @param strategy [String, Symbol]
|
|
14
|
-
# @return [Hash]
|
|
15
|
-
def call(url:, strategy: :auto)
|
|
16
|
-
resolved = FeedPipeline::StrategyPlan.concrete_for_diagnostic(strategy)
|
|
17
|
-
response = fetch_response(url, resolved)
|
|
18
|
-
recon = PageRecon.call(response:, url:, strategy: resolved)
|
|
19
|
-
|
|
20
|
-
result = recon.to_h.merge(
|
|
21
|
-
strategy: resolved,
|
|
22
|
-
scraper_eligibility: scraper_info(safe_parsed_body(response))
|
|
23
|
-
)
|
|
24
|
-
result[:xhr_capture] = xhr_capture_info(response) if resolved == :botasaurus
|
|
25
|
-
result
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
##
|
|
29
|
-
# @param response [Html2rss::RequestService::Response]
|
|
30
|
-
# @return [Hash] redacted XHR capture diagnostics (no query strings)
|
|
31
|
-
def xhr_capture_info(response)
|
|
32
|
-
captured = response.captured_responses
|
|
33
|
-
{
|
|
34
|
-
count: captured.size,
|
|
35
|
-
sample_endpoints: captured.first(5).filter_map { |entry| redacted_endpoint(entry) },
|
|
36
|
-
candidate_articles: captured.any? { |entry| xhr_candidate_articles?(entry) }
|
|
37
|
-
}
|
|
38
|
-
end
|
|
39
|
-
module_function :xhr_capture_info
|
|
40
|
-
|
|
41
|
-
##
|
|
42
|
-
# @param entry [Hash] captured response hash
|
|
43
|
-
# @return [String, nil] scheme+host+path only
|
|
44
|
-
def redacted_endpoint(entry)
|
|
45
|
-
raw = entry['url'] || entry[:url]
|
|
46
|
-
return unless raw
|
|
47
|
-
|
|
48
|
-
uri = URI.parse(raw.to_s)
|
|
49
|
-
return unless uri.scheme && uri.host
|
|
50
|
-
|
|
51
|
-
"#{uri.scheme}://#{uri.host}#{uri.path}"
|
|
52
|
-
rescue URI::InvalidURIError
|
|
53
|
-
nil
|
|
54
|
-
end
|
|
55
|
-
module_function :redacted_endpoint
|
|
56
|
-
|
|
57
|
-
##
|
|
58
|
-
# @param entry [Hash] captured response hash
|
|
59
|
-
# @return [Boolean]
|
|
60
|
-
def xhr_candidate_articles?(entry)
|
|
61
|
-
body = entry['body'] || entry[:body]
|
|
62
|
-
return false unless body.is_a?(String)
|
|
63
|
-
|
|
64
|
-
document = JSON.parse(body, symbolize_names: true)
|
|
65
|
-
AutoSource::Scraper::JsonState::CandidateDetector.candidate_array?(document)
|
|
66
|
-
rescue JSON::ParserError
|
|
67
|
-
false
|
|
68
|
-
end
|
|
69
|
-
module_function :xhr_candidate_articles?
|
|
70
|
-
|
|
71
|
-
##
|
|
72
|
-
# @param url [String]
|
|
73
|
-
# @param strategy [Symbol]
|
|
74
|
-
# @return [Html2rss::RequestService::Response]
|
|
75
|
-
def fetch_response(url, strategy) # rubocop:disable Metrics/MethodLength -- session construction
|
|
76
|
-
raw_config = Config.auto_source_config(
|
|
77
|
-
url:,
|
|
78
|
-
request_controls: Config::RequestControls.from_shortcut(strategy:)
|
|
79
|
-
)
|
|
80
|
-
raw_config[:strategy] = strategy
|
|
81
|
-
config = Config.from_hash(raw_config)
|
|
82
|
-
resources = FeedPipeline::RuntimePolicy.resources_for(config)
|
|
83
|
-
session = RequestSession.build(
|
|
84
|
-
config:,
|
|
85
|
-
strategy: config.strategy,
|
|
86
|
-
budget: resources.budget,
|
|
87
|
-
policy: resources.policy
|
|
88
|
-
)
|
|
89
|
-
session.fetch_initial_response
|
|
90
|
-
end
|
|
91
|
-
module_function :fetch_response
|
|
92
|
-
|
|
93
|
-
##
|
|
94
|
-
# @param parsed [Object] parsed response body
|
|
95
|
-
# @return [Array<String>, Hash]
|
|
96
|
-
def scraper_info(parsed)
|
|
97
|
-
return { error: 'Response is not HTML' } unless parsed.is_a?(Nokogiri::HTML::Document)
|
|
98
|
-
|
|
99
|
-
begin
|
|
100
|
-
Html2rss::AutoSource::Scraper.from(parsed).map(&:name)
|
|
101
|
-
rescue Html2rss::AutoSource::Scraper::NoScraperFound => error
|
|
102
|
-
{ none_found: error.category.to_s }
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
module_function :scraper_info
|
|
106
|
-
|
|
107
|
-
##
|
|
108
|
-
# @param response [Html2rss::RequestService::Response]
|
|
109
|
-
# @return [Hash, nil]
|
|
110
|
-
def sst_stats_from(response)
|
|
111
|
-
return nil unless response.html_response?
|
|
112
|
-
|
|
113
|
-
recon = PageRecon.call(response:, url: response.url)
|
|
114
|
-
return nil unless recon.sst
|
|
115
|
-
|
|
116
|
-
{ node_count: recon.sst[:node_count], degraded: recon.sst[:degraded] }
|
|
117
|
-
end
|
|
118
|
-
module_function :sst_stats_from
|
|
119
|
-
|
|
120
|
-
##
|
|
121
|
-
# @param sst [Html2rss::SST::Document]
|
|
122
|
-
# @param url [String]
|
|
123
|
-
# @return [Array]
|
|
124
|
-
def discover_segments(sst, url) = PageRecon.discover_segments(sst, url)
|
|
125
|
-
module_function :discover_segments
|
|
126
|
-
|
|
127
|
-
def safe_parsed_body(response)
|
|
128
|
-
return unless response.html_response?
|
|
129
|
-
|
|
130
|
-
response.parsed_body
|
|
131
|
-
rescue RequestService::UnsupportedResponseContentType
|
|
132
|
-
nil
|
|
133
|
-
end
|
|
134
|
-
module_function :safe_parsed_body
|
|
135
|
-
private_class_method :safe_parsed_body
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
end
|