html2rss 0.32.4 → 0.34.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 +4 -2
- data/lib/html2rss/article/deduplicator.rb +5 -2
- data/lib/html2rss/article/enclosure.rb +185 -25
- data/lib/html2rss/article/title.rb +203 -0
- data/lib/html2rss/article.rb +79 -13
- data/lib/html2rss/auto_source/README.md +16 -8
- data/lib/html2rss/auto_source/cleanup.rb +127 -53
- data/lib/html2rss/auto_source/corroboration.rb +167 -0
- data/lib/html2rss/auto_source/page_links/anchor_claims.rb +82 -0
- data/lib/html2rss/auto_source/page_links/card_units.rb +77 -0
- data/lib/html2rss/auto_source/page_links/label_binding.rb +79 -0
- data/lib/html2rss/auto_source/page_links/sponsored_cards.rb +147 -0
- data/lib/html2rss/auto_source/page_links.rb +164 -0
- data/lib/html2rss/auto_source/scraper/html.rb +11 -45
- data/lib/html2rss/auto_source/scraper/json_document.rb +3 -6
- data/lib/html2rss/auto_source/scraper/json_state/article_normalizer.rb +202 -125
- data/lib/html2rss/auto_source/scraper/json_state/candidate_detector.rb +27 -38
- data/lib/html2rss/auto_source/scraper/json_state/document_scanner.rb +123 -123
- data/lib/html2rss/auto_source/scraper/json_state/value_finder.rb +27 -35
- data/lib/html2rss/auto_source/scraper/json_state.rb +4 -9
- data/lib/html2rss/auto_source/scraper/meta_oembed.rb +27 -5
- data/lib/html2rss/auto_source/scraper/microdata.rb +233 -248
- data/lib/html2rss/auto_source/scraper/microformats2.rb +10 -4
- data/lib/html2rss/auto_source/scraper/native_feed.rb +28 -18
- data/lib/html2rss/auto_source/scraper/schema/category_extractor.rb +7 -2
- data/lib/html2rss/auto_source/scraper/schema/item_list.rb +5 -3
- data/lib/html2rss/auto_source/scraper/schema/thing.rb +140 -22
- data/lib/html2rss/auto_source/scraper/schema.rb +52 -8
- data/lib/html2rss/auto_source/scraper/semantic_html/entry_deduplicator.rb +3 -9
- data/lib/html2rss/auto_source/scraper/semantic_html.rb +12 -20
- data/lib/html2rss/auto_source/scraper/sitemap/parser.rb +1 -4
- data/lib/html2rss/auto_source/scraper/sitemap.rb +7 -14
- data/lib/html2rss/auto_source/scraper/wordpress_api/page_scope.rb +2 -2
- data/lib/html2rss/auto_source/scraper/wordpress_api/posts_endpoint.rb +1 -3
- data/lib/html2rss/auto_source/scraper/wordpress_api.rb +7 -7
- data/lib/html2rss/auto_source/scraper/xhr_articles.rb +2 -4
- data/lib/html2rss/auto_source/scraper.rb +10 -13
- data/lib/html2rss/auto_source/segment.rb +19 -5
- data/lib/html2rss/auto_source/segmenter/cluster.rb +18 -22
- data/lib/html2rss/auto_source/segmenter/list.rb +35 -59
- data/lib/html2rss/auto_source/segmenter/primary_link.rb +29 -4
- data/lib/html2rss/auto_source/segmenter/semantic.rb +23 -19
- data/lib/html2rss/auto_source/segmenter.rb +4 -8
- data/lib/html2rss/auto_source.rb +81 -26
- data/lib/html2rss/batch.rb +24 -32
- data/lib/html2rss/capture/README.md +6 -3
- data/lib/html2rss/capture.rb +51 -59
- data/lib/html2rss/channel.rb +30 -12
- data/lib/html2rss/cli/render.rb +97 -96
- data/lib/html2rss/cli/validate.rb +2 -4
- data/lib/html2rss/cli.rb +55 -38
- data/lib/html2rss/config/auto_source_contract.rb +52 -45
- data/lib/html2rss/config/issue_mapper.rb +4 -1
- data/lib/html2rss/config/request_controls.rb +16 -5
- data/lib/html2rss/config/schema.rb +23 -15
- data/lib/html2rss/config/selectors_validator.rb +3 -2
- data/lib/html2rss/config/validation_issue.rb +4 -1
- data/lib/html2rss/config/validator.rb +14 -3
- data/lib/html2rss/config.rb +54 -28
- data/lib/html2rss/contract_error.rb +9 -0
- data/lib/html2rss/defaults.rb +25 -38
- data/lib/html2rss/doctor/botasaurus.rb +18 -22
- data/lib/html2rss/error.rb +17 -3
- data/lib/html2rss/error_kind.rb +39 -0
- data/lib/html2rss/feed_builder/item_presentation.rb +22 -9
- data/lib/html2rss/feed_builder/json_feed/item.rb +2 -1
- data/lib/html2rss/feed_builder/json_feed.rb +2 -2
- data/lib/html2rss/feed_builder/rss/stylesheet.rb +3 -3
- data/lib/html2rss/feed_builder/rss.rb +50 -7
- data/lib/html2rss/feed_builder.rb +2 -1
- data/lib/html2rss/feed_pipeline/README.md +1 -1
- data/lib/html2rss/feed_pipeline/auto_fallback.rb +16 -34
- data/lib/html2rss/feed_pipeline/strategy_plan.rb +3 -3
- data/lib/html2rss/feed_pipeline.rb +40 -27
- data/lib/html2rss/feed_resolution/README.md +1 -1
- data/lib/html2rss/feed_resolution/candidate_generator.rb +6 -8
- data/lib/html2rss/feed_resolution/options.rb +3 -7
- data/lib/html2rss/feed_resolution/policy.rb +29 -33
- data/lib/html2rss/feed_resolution/probe.rb +6 -4
- data/lib/html2rss/feed_resolution/scorer.rb +29 -29
- data/lib/html2rss/feed_resolution.rb +16 -21
- data/lib/html2rss/feed_result.rb +11 -6
- data/lib/html2rss/hash_util.rb +91 -90
- data/lib/html2rss/html/article_extractor/category_extractor.rb +7 -10
- data/lib/html2rss/html/article_extractor/date_extractor.rb +1 -1
- data/lib/html2rss/html/article_extractor/enclosure_extractor.rb +12 -33
- data/lib/html2rss/html/article_extractor/heading_extractor.rb +2 -3
- data/lib/html2rss/html/article_extractor/id_generator.rb +7 -10
- data/lib/html2rss/html/article_extractor/image_extractor.rb +9 -1
- data/lib/html2rss/html/article_extractor.rb +19 -47
- data/lib/html2rss/html/article_rules/category.rb +3 -3
- data/lib/html2rss/html/article_rules/description.rb +6 -15
- data/lib/html2rss/html/article_rules/enclosure.rb +70 -42
- data/lib/html2rss/html/article_rules/image.rb +30 -16
- data/lib/html2rss/html/feed_link.rb +7 -9
- data/lib/html2rss/html/navigator/text_extractor.rb +26 -16
- data/lib/html2rss/html/navigator.rb +39 -9
- data/lib/html2rss/html/probe.rb +55 -51
- data/lib/html2rss/html/rendering/description_builder.rb +46 -32
- data/lib/html2rss/html/rendering.rb +2 -9
- data/lib/html2rss/html/sst_article_extractor.rb +55 -145
- data/lib/html2rss/html/title_pieces/kicker.rb +147 -0
- data/lib/html2rss/html/title_pieces/nodes.rb +107 -0
- data/lib/html2rss/html/title_pieces/nokogiri_nodes.rb +70 -0
- data/lib/html2rss/html/title_pieces/sst_nodes.rb +71 -0
- data/lib/html2rss/html/title_pieces.rb +169 -0
- data/lib/html2rss/html/visibility.rb +56 -0
- data/lib/html2rss/html.rb +2 -0
- data/lib/html2rss/link_destination/destination_facts.rb +17 -1
- data/lib/html2rss/link_destination/noise_policy.rb +11 -10
- data/lib/html2rss/link_destination/path_classifier.rb +111 -42
- data/lib/html2rss/link_destination/sponsorship/label_text.rb +82 -0
- data/lib/html2rss/link_destination/sponsorship.rb +184 -0
- data/lib/html2rss/link_destination/text_classifier.rb +104 -8
- data/lib/html2rss/mcp/README.md +13 -1
- data/lib/html2rss/mcp/config_argument.rb +14 -4
- data/lib/html2rss/mcp/contract.rb +20 -9
- data/lib/html2rss/mcp/outcome.rb +18 -17
- data/lib/html2rss/mcp/runtime.rb +53 -23
- data/lib/html2rss/mcp/server/tools.rb +9 -9
- data/lib/html2rss/mcp/server.rb +13 -4
- data/lib/html2rss/option.rb +48 -0
- data/lib/html2rss/page_recon/README.md +1 -1
- data/lib/html2rss/page_recon/diagnostics.rb +9 -9
- data/lib/html2rss/page_recon.rb +0 -4
- data/lib/html2rss/recon.rb +19 -23
- data/lib/html2rss/request_service/blocked_surface.rb +5 -8
- data/lib/html2rss/request_service/botasaurus_contract.rb +25 -3
- data/lib/html2rss/request_service/botasaurus_strategy.rb +44 -12
- data/lib/html2rss/request_service/budget.rb +3 -24
- data/lib/html2rss/request_service/compressed_body.rb +36 -48
- data/lib/html2rss/request_service/context.rb +16 -17
- data/lib/html2rss/request_service/cookie_jar.rb +89 -0
- data/lib/html2rss/request_service/cookie_store.rb +79 -0
- data/lib/html2rss/request_service/feed_advertisement.rb +72 -0
- data/lib/html2rss/request_service/httpx_strategy.rb +7 -3
- data/lib/html2rss/request_service/local_file_strategy.rb +2 -3
- data/lib/html2rss/request_service/network_guard.rb +3 -2
- data/lib/html2rss/request_service/policy.rb +55 -36
- data/lib/html2rss/request_service/replay_breaker.rb +56 -0
- data/lib/html2rss/request_service/response.rb +33 -19
- data/lib/html2rss/request_service/response_cache.rb +109 -0
- data/lib/html2rss/request_service/response_caching.rb +95 -0
- data/lib/html2rss/request_service/strategy.rb +5 -11
- data/lib/html2rss/request_service.rb +71 -12
- data/lib/html2rss/request_session/pager/base.rb +3 -10
- data/lib/html2rss/request_session/pager/custom_selector.rb +4 -4
- data/lib/html2rss/request_session/pager/offset.rb +1 -7
- data/lib/html2rss/request_session/pager/rel_next.rb +1 -1
- data/lib/html2rss/request_session/pager/url_template.rb +1 -7
- data/lib/html2rss/request_session/pager.rb +1 -1
- data/lib/html2rss/request_session.rb +6 -2
- data/lib/html2rss/scoring/cluster_scorer.rb +1 -1
- data/lib/html2rss/scoring/container_assessor.rb +12 -13
- data/lib/html2rss/scoring/engine.rb +10 -38
- data/lib/html2rss/scoring/link_resolver.rb +2 -7
- data/lib/html2rss/scoring/observation.rb +2 -2
- data/lib/html2rss/selector_candidates.rb +19 -8
- data/lib/html2rss/selectors/README.md +1 -1
- data/lib/html2rss/selectors/extractors/href.rb +2 -4
- data/lib/html2rss/selectors/json_xml.rb +33 -34
- data/lib/html2rss/selectors/option_spec.rb +4 -1
- data/lib/html2rss/selectors/post_processors/base.rb +0 -8
- data/lib/html2rss/selectors/post_processors/gsub.rb +9 -10
- data/lib/html2rss/selectors/post_processors/html_transformers/keep_playable_media.rb +50 -0
- data/lib/html2rss/selectors/post_processors/html_transformers/transform_urls_to_absolute_ones.rb +1 -3
- data/lib/html2rss/selectors/post_processors/parse_time.rb +5 -6
- data/lib/html2rss/selectors/post_processors/parse_uri.rb +1 -1
- data/lib/html2rss/selectors/post_processors/sanitize_html.rb +18 -5
- data/lib/html2rss/selectors/post_processors/substring.rb +1 -1
- data/lib/html2rss/selectors/schema_export.rb +60 -60
- data/lib/html2rss/selectors.rb +67 -30
- data/lib/html2rss/sst/attrs.rb +2 -2
- data/lib/html2rss/sst/document.rb +3 -3
- data/lib/html2rss/sst/index.rb +30 -11
- data/lib/html2rss/sst/node.rb +42 -29
- data/lib/html2rss/sst/normalizer.rb +47 -34
- data/lib/html2rss/sst/tags.rb +113 -2
- data/lib/html2rss/sst/text.rb +94 -58
- data/lib/html2rss/sst.rb +2 -0
- data/lib/html2rss/status.rb +27 -31
- data/lib/html2rss/surface_category.rb +1 -1
- data/lib/html2rss/syndication/README.md +12 -6
- data/lib/html2rss/syndication/alternate_links.rb +39 -0
- data/lib/html2rss/syndication/channel_facts.rb +87 -0
- data/lib/html2rss/syndication/discovery.rb +171 -187
- data/lib/html2rss/syndication/feed.rb +20 -0
- data/lib/html2rss/syndication/item_media.rb +103 -0
- data/lib/html2rss/syndication/parser.rb +117 -116
- data/lib/html2rss/test/enhance_audit.rb +15 -17
- data/lib/html2rss/test/item_sample.rb +43 -0
- data/lib/html2rss/test/policy.rb +9 -11
- data/lib/html2rss/test.rb +66 -57
- data/lib/html2rss/url.rb +163 -62
- data/lib/html2rss/version.rb +1 -1
- data/lib/html2rss/word_count.rb +53 -0
- data/lib/html2rss.rb +19 -9
- data/schema/html2rss-config.schema.json +56 -41
- metadata +33 -4
- data/lib/html2rss/html/rendering/media_renderer.rb +0 -39
- data/lib/html2rss/html/rendering/pdf_renderer.rb +0 -30
data/lib/html2rss/test.rb
CHANGED
|
@@ -5,8 +5,6 @@ module Html2rss
|
|
|
5
5
|
# Service that runs schema validation and live feed extraction on a configuration,
|
|
6
6
|
# enforcing minimum item thresholds and capturing sample output.
|
|
7
7
|
module Test # rubocop:disable Metrics/ModuleLength -- Result + FailureKind nest with owner
|
|
8
|
-
module_function
|
|
9
|
-
|
|
10
8
|
FailureKind = Data.define(:name)
|
|
11
9
|
|
|
12
10
|
##
|
|
@@ -19,7 +17,7 @@ module Html2rss
|
|
|
19
17
|
##
|
|
20
18
|
# @param name [Symbol, String]
|
|
21
19
|
# @return [FailureKind]
|
|
22
|
-
def self.[](name) = ALL.fetch(name.to_sym) { raise
|
|
20
|
+
def self.[](name) = ALL.fetch(name.to_sym) { raise InvalidOption, "unknown failure kind: #{name.inspect}" }
|
|
23
21
|
|
|
24
22
|
##
|
|
25
23
|
# @return [Boolean]
|
|
@@ -80,6 +78,7 @@ module Html2rss
|
|
|
80
78
|
:success,
|
|
81
79
|
:item_count,
|
|
82
80
|
:sample_items,
|
|
81
|
+
:items,
|
|
83
82
|
:channel_title,
|
|
84
83
|
:channel_url,
|
|
85
84
|
:strategy_used,
|
|
@@ -102,6 +101,8 @@ module Html2rss
|
|
|
102
101
|
# @param success [Boolean]
|
|
103
102
|
# @param item_count [Integer]
|
|
104
103
|
# @param sample_items [Array<Hash>]
|
|
104
|
+
# @param items [Array<Hash>] up to +sample_limit+ items (title, url, published_at, description, image,
|
|
105
|
+
# categories) built from extracted articles regardless of the quality gate
|
|
105
106
|
# @param channel_title [String, nil]
|
|
106
107
|
# @param channel_url [String, nil]
|
|
107
108
|
# @param strategy_used [Symbol, nil]
|
|
@@ -115,7 +116,7 @@ module Html2rss
|
|
|
115
116
|
# @param response_body [String, nil] fetched page HTML copied from the pipeline response
|
|
116
117
|
def initialize(success:, item_count:, sample_items:, channel_title:, channel_url:, # rubocop:disable Metrics/ParameterLists
|
|
117
118
|
strategy_used:, duration_seconds:, validation_issues:, error_message:,
|
|
118
|
-
failure_kind:, rss:, quality_report: nil, enhance_compare: nil, response_body: nil)
|
|
119
|
+
failure_kind:, rss:, items: [], quality_report: nil, enhance_compare: nil, response_body: nil)
|
|
119
120
|
super
|
|
120
121
|
end
|
|
121
122
|
|
|
@@ -138,6 +139,7 @@ module Html2rss
|
|
|
138
139
|
success:,
|
|
139
140
|
item_count:,
|
|
140
141
|
sample_items:,
|
|
142
|
+
items:,
|
|
141
143
|
channel_title:,
|
|
142
144
|
channel_url:,
|
|
143
145
|
strategy_used:,
|
|
@@ -162,28 +164,31 @@ module Html2rss
|
|
|
162
164
|
# @param strategy [Symbol, nil] optional strategy override
|
|
163
165
|
# @param strict_quality [Boolean] when true, fail on ship-quality audit thresholds
|
|
164
166
|
# @param compare_enhance [Boolean] diagnostic enhance off vs on comparison on cached HTML
|
|
167
|
+
# @param sample_limit [Integer] maximum number of {Result#items} (default 3)
|
|
165
168
|
# @return [Html2rss::Test::Result]
|
|
166
|
-
def call(config_input, feed_name = nil, min_items: 1, params: {}, strategy: nil, # rubocop:disable Metrics/ParameterLists
|
|
167
|
-
|
|
169
|
+
def self.call(config_input, feed_name = nil, min_items: 1, params: {}, strategy: nil, # rubocop:disable Metrics/ParameterLists
|
|
170
|
+
strict_quality: false, compare_enhance: false, sample_limit: 3)
|
|
168
171
|
raw_config, validation = Config.resolve_and_validate(config_input, feed_name:, params:)
|
|
169
172
|
return validation_failure_result(validation, raw_config) unless validation.success?
|
|
170
173
|
|
|
171
174
|
config = build_test_config(raw_config, strategy, params)
|
|
172
175
|
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
173
|
-
execute_timed_pipeline(raw_config, config, started, min_items:, strict_quality:, compare_enhance:)
|
|
174
|
-
rescue
|
|
176
|
+
execute_timed_pipeline(raw_config, config, started, min_items:, strict_quality:, compare_enhance:, sample_limit:)
|
|
177
|
+
rescue Html2rss::ContractError
|
|
178
|
+
raise
|
|
179
|
+
rescue Html2rss::Error => error
|
|
175
180
|
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started
|
|
176
181
|
execution_failure_result(error, raw_config, duration)
|
|
177
182
|
end
|
|
178
183
|
|
|
179
|
-
def build_test_config(raw_config, strategy, params)
|
|
184
|
+
def self.build_test_config(raw_config, strategy, params)
|
|
180
185
|
raw_config[:strategy] = strategy.to_sym if strategy
|
|
181
186
|
raw_config[:params] = params if params&.any?
|
|
182
187
|
Config.from_hash(raw_config)
|
|
183
188
|
end
|
|
184
189
|
private_class_method :build_test_config
|
|
185
190
|
|
|
186
|
-
def compile_quality_report(raw_config, config, feed_result, pipeline_outcome)
|
|
191
|
+
def self.compile_quality_report(raw_config, config, feed_result, pipeline_outcome)
|
|
187
192
|
quality_report_for(
|
|
188
193
|
feed_result.to_rss.items,
|
|
189
194
|
channel_url: raw_config.dig(:channel, :url).to_s,
|
|
@@ -194,20 +199,21 @@ module Html2rss
|
|
|
194
199
|
end
|
|
195
200
|
private_class_method :compile_quality_report
|
|
196
201
|
|
|
197
|
-
def execute_timed_pipeline(raw_config, config, started, min_items:, strict_quality:, # rubocop:disable Metrics/ParameterLists
|
|
198
|
-
|
|
202
|
+
def self.execute_timed_pipeline(raw_config, config, started, min_items:, strict_quality:, # rubocop:disable Metrics/ParameterLists
|
|
203
|
+
compare_enhance:, sample_limit:)
|
|
199
204
|
feed_result, pipeline_outcome = extract_feed(raw_config)
|
|
200
205
|
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started
|
|
201
206
|
quality_report = compile_quality_report(raw_config, config, feed_result, pipeline_outcome)
|
|
202
207
|
enhance_compare = build_enhance_compare(config, pipeline_outcome) if compare_enhance
|
|
203
208
|
|
|
204
209
|
build_test_result(raw_config, feed_result, duration, quality_report, enhance_compare,
|
|
205
|
-
min_items:, strict_quality:, response_body: pipeline_outcome
|
|
210
|
+
min_items:, strict_quality:, response_body: pipeline_outcome.response.body,
|
|
211
|
+
items: ItemSample.call(pipeline_outcome.articles, limit: sample_limit))
|
|
206
212
|
end
|
|
207
213
|
private_class_method :execute_timed_pipeline
|
|
208
214
|
|
|
209
|
-
def build_test_result(raw_config, feed_result, duration, quality_report, enhance_compare, # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/ParameterLists
|
|
210
|
-
|
|
215
|
+
def self.build_test_result(raw_config, feed_result, duration, quality_report, enhance_compare, # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/ParameterLists
|
|
216
|
+
min_items:, strict_quality:, response_body:, items:)
|
|
211
217
|
rss_doc = feed_result.to_rss
|
|
212
218
|
outcome = Policy.evaluate(item_count: rss_doc.items.size, min_items:, strict_quality:, quality_report:)
|
|
213
219
|
strategy_used = feed_result.status.selected_strategy || raw_config[:strategy] ||
|
|
@@ -217,6 +223,7 @@ module Html2rss
|
|
|
217
223
|
success: outcome.passed,
|
|
218
224
|
item_count: rss_doc.items.size,
|
|
219
225
|
sample_items: extract_samples(rss_doc.items),
|
|
226
|
+
items:,
|
|
220
227
|
channel_title: feed_result.channel_title,
|
|
221
228
|
channel_url: raw_config.dig(:channel, :url).to_s,
|
|
222
229
|
strategy_used:,
|
|
@@ -232,27 +239,14 @@ module Html2rss
|
|
|
232
239
|
end
|
|
233
240
|
private_class_method :build_test_result
|
|
234
241
|
|
|
235
|
-
def extract_samples(items, limit: 3)
|
|
242
|
+
def self.extract_samples(items, limit: 3)
|
|
236
243
|
items.first(limit).map do |item|
|
|
237
|
-
article =
|
|
238
|
-
{
|
|
239
|
-
title: article.title.to_s.strip,
|
|
240
|
-
url: article.url.to_s,
|
|
241
|
-
published_at: article.published_at
|
|
242
|
-
}
|
|
244
|
+
article = Article.new(title: item.title, url: item.link, published_at: item.pubDate)
|
|
245
|
+
{ title: article.title.to_s.strip, url: article.url.to_s, published_at: article.published_at }
|
|
243
246
|
end
|
|
244
247
|
end
|
|
245
248
|
private_class_method :extract_samples
|
|
246
249
|
|
|
247
|
-
def feed_item_as_article(item)
|
|
248
|
-
case item
|
|
249
|
-
when Article then item
|
|
250
|
-
else
|
|
251
|
-
Article.new(title: item.title, url: item.link, published_at: item.pubDate)
|
|
252
|
-
end
|
|
253
|
-
end
|
|
254
|
-
private_class_method :feed_item_as_article
|
|
255
|
-
|
|
256
250
|
##
|
|
257
251
|
# Builds a ship-quality report for RSS items (shared by test and MCP apply).
|
|
258
252
|
#
|
|
@@ -261,32 +255,33 @@ module Html2rss
|
|
|
261
255
|
# @param config [Html2rss::Config]
|
|
262
256
|
# @param feed_result [Html2rss::FeedResult]
|
|
263
257
|
# @param pipeline_outcome [Html2rss::FeedPipeline::PipelineOutcome]
|
|
264
|
-
# @param probe_native_feed [Boolean] when false, skip syndication discovery (apply ship gate)
|
|
258
|
+
# @param probe_native_feed [Boolean] when false, skip syndication discovery (apply ship gate);
|
|
259
|
+
# when true, discovery reads the page in +pipeline_outcome.response+ (no extra page fetch)
|
|
265
260
|
# @return [QualityReport]
|
|
266
|
-
def quality_report_for(items, channel_url:, config:, feed_result:, pipeline_outcome:, # rubocop:disable Metrics/ParameterLists
|
|
267
|
-
|
|
261
|
+
def self.quality_report_for(items, channel_url:, config:, feed_result:, pipeline_outcome:, # rubocop:disable Metrics/ParameterLists
|
|
262
|
+
probe_native_feed: true)
|
|
268
263
|
audit = AutoSource::Cleanup.audit_feed_items(items)
|
|
269
|
-
native_feed =
|
|
264
|
+
native_feed = probe_native_feed_url(channel_url, config, pipeline_outcome.response)&.to_s if probe_native_feed
|
|
270
265
|
report = QualityReport.from_audit(audit, native_feed:)
|
|
271
266
|
report = append_url_mismatch_warning(report, channel_url, feed_result)
|
|
267
|
+
report = append_items_without_url_warning(report, pipeline_outcome)
|
|
272
268
|
merge_enhance_audit(report, config, pipeline_outcome)
|
|
273
269
|
end
|
|
274
270
|
|
|
275
|
-
def extract_feed(raw_config)
|
|
271
|
+
def self.extract_feed(raw_config)
|
|
276
272
|
outcome, feed_result = FeedPipeline.new(raw_config).to_outcome_and_result
|
|
277
273
|
[feed_result, outcome]
|
|
278
274
|
end
|
|
279
275
|
private_class_method :extract_feed
|
|
280
276
|
|
|
281
|
-
def enhance_enabled?(config)
|
|
277
|
+
def self.enhance_enabled?(config)
|
|
282
278
|
return false unless config.selectors
|
|
283
279
|
|
|
284
280
|
!!config.selectors.dig(:items, :enhance)
|
|
285
281
|
end
|
|
286
282
|
private_class_method :enhance_enabled?
|
|
287
283
|
|
|
288
|
-
def build_enhance_compare(config, pipeline_outcome)
|
|
289
|
-
return nil unless pipeline_outcome
|
|
284
|
+
def self.build_enhance_compare(config, pipeline_outcome)
|
|
290
285
|
return nil unless config.selectors
|
|
291
286
|
|
|
292
287
|
EnhanceAudit.compare(
|
|
@@ -297,9 +292,8 @@ module Html2rss
|
|
|
297
292
|
end
|
|
298
293
|
private_class_method :build_enhance_compare
|
|
299
294
|
|
|
300
|
-
def merge_enhance_audit(report, config, pipeline_outcome) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength -- warning merge + metrics
|
|
301
|
-
return report unless
|
|
302
|
-
return report unless config.selectors
|
|
295
|
+
def self.merge_enhance_audit(report, config, pipeline_outcome) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength -- warning merge + metrics
|
|
296
|
+
return report unless enhance_enabled?(config)
|
|
303
297
|
|
|
304
298
|
slice = EnhanceAudit.probe(
|
|
305
299
|
response: pipeline_outcome.response,
|
|
@@ -320,7 +314,7 @@ module Html2rss
|
|
|
320
314
|
end
|
|
321
315
|
private_class_method :merge_enhance_audit
|
|
322
316
|
|
|
323
|
-
def enhance_gains_to_h(enhance_gains)
|
|
317
|
+
def self.enhance_gains_to_h(enhance_gains)
|
|
324
318
|
{
|
|
325
319
|
items_probed: enhance_gains.items_probed,
|
|
326
320
|
keys_added: enhance_gains.keys_added,
|
|
@@ -330,7 +324,22 @@ module Html2rss
|
|
|
330
324
|
end
|
|
331
325
|
private_class_method :enhance_gains_to_h
|
|
332
326
|
|
|
333
|
-
|
|
327
|
+
# Selector items dropped for having no URL ({Selectors#admission_drops}) leave nothing for the audit to
|
|
328
|
+
# see, so the warning names the +url+ selector as the place to look.
|
|
329
|
+
def self.append_items_without_url_warning(report, pipeline_outcome)
|
|
330
|
+
dropped = pipeline_outcome.admission_drops.fetch(Selectors::MISSING_URL_REASON, 0)
|
|
331
|
+
return report unless dropped.positive?
|
|
332
|
+
|
|
333
|
+
QualityReport.new(
|
|
334
|
+
warnings: (report.warnings + [:items_without_url]).freeze,
|
|
335
|
+
metrics: report.metrics.merge(items_without_url: dropped),
|
|
336
|
+
native_feed: report.native_feed,
|
|
337
|
+
defer_reason: report.defer_reason
|
|
338
|
+
)
|
|
339
|
+
end
|
|
340
|
+
private_class_method :append_items_without_url_warning
|
|
341
|
+
|
|
342
|
+
def self.append_url_mismatch_warning(report, channel_url, feed_result)
|
|
334
343
|
return report unless url_mismatch?(channel_url, feed_result)
|
|
335
344
|
|
|
336
345
|
warnings = report.warnings.dup
|
|
@@ -344,36 +353,36 @@ module Html2rss
|
|
|
344
353
|
end
|
|
345
354
|
private_class_method :append_url_mismatch_warning
|
|
346
355
|
|
|
347
|
-
def url_mismatch?(channel_url, feed_result)
|
|
348
|
-
status = feed_result
|
|
349
|
-
return false unless status
|
|
350
|
-
|
|
356
|
+
def self.url_mismatch?(channel_url, feed_result)
|
|
357
|
+
status = feed_result.status
|
|
351
358
|
configured = channel_url.to_s
|
|
352
359
|
final = status.scrape_url.to_s
|
|
353
360
|
final = status.entry_url.to_s if final.empty?
|
|
354
361
|
return false if configured.empty? || final.empty?
|
|
355
362
|
|
|
356
363
|
!Url.from_absolute(configured).same_document?(Url.from_absolute(final))
|
|
357
|
-
rescue
|
|
364
|
+
rescue Html2rss::Url::InvalidUrlError
|
|
358
365
|
false
|
|
359
366
|
end
|
|
360
367
|
private_class_method :url_mismatch?
|
|
361
368
|
|
|
362
|
-
|
|
369
|
+
# The page the pipeline fetched (where any redirect landed) is the page probed, as in Recon and Capture: no
|
|
370
|
+
# extra page fetch.
|
|
371
|
+
def self.probe_native_feed_url(channel_url, config, response)
|
|
363
372
|
return nil if channel_url.to_s.empty?
|
|
364
373
|
|
|
365
374
|
resources = FeedPipeline::RuntimePolicy.resources_for(config)
|
|
366
375
|
strategy = FeedPipeline::StrategyPlan.concrete_for_diagnostic(config.strategy)
|
|
367
|
-
session = RequestSession.build(
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
rescue
|
|
376
|
+
session = RequestSession.build(config:, strategy:, budget: resources.budget, policy: resources.policy)
|
|
377
|
+
Syndication::Discovery.native_feed_url(response:, request_session: session)
|
|
378
|
+
rescue Html2rss::ContractError
|
|
379
|
+
raise
|
|
380
|
+
rescue Html2rss::Error
|
|
372
381
|
nil
|
|
373
382
|
end
|
|
374
383
|
private_class_method :probe_native_feed_url
|
|
375
384
|
|
|
376
|
-
def validation_failure_result(report, raw_config) # rubocop:disable Metrics/MethodLength
|
|
385
|
+
def self.validation_failure_result(report, raw_config) # rubocop:disable Metrics/MethodLength
|
|
377
386
|
Result.new(
|
|
378
387
|
success: false,
|
|
379
388
|
item_count: 0,
|
|
@@ -391,7 +400,7 @@ module Html2rss
|
|
|
391
400
|
end
|
|
392
401
|
private_class_method :validation_failure_result
|
|
393
402
|
|
|
394
|
-
def execution_failure_result(error, raw_config, duration) # rubocop:disable Metrics/MethodLength
|
|
403
|
+
def self.execution_failure_result(error, raw_config, duration) # rubocop:disable Metrics/MethodLength
|
|
395
404
|
Result.new(
|
|
396
405
|
success: false,
|
|
397
406
|
item_count: 0,
|