html2rss 0.30.0 → 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.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/lib/html2rss/article/deduplicator.rb +2 -4
- data/lib/html2rss/article.rb +16 -39
- data/lib/html2rss/auto_source/README.md +1 -1
- data/lib/html2rss/auto_source/cleanup.rb +15 -6
- data/lib/html2rss/auto_source/scraper/html.rb +5 -0
- data/lib/html2rss/auto_source/scraper/json_state.rb +5 -0
- data/lib/html2rss/auto_source/scraper/meta_oembed.rb +8 -1
- data/lib/html2rss/auto_source/scraper/microdata.rb +8 -0
- data/lib/html2rss/auto_source/scraper/microformats2.rb +8 -0
- data/lib/html2rss/auto_source/scraper/native_feed.rb +3 -0
- data/lib/html2rss/auto_source/scraper/schema/thing.rb +4 -5
- data/lib/html2rss/auto_source/scraper/schema.rb +8 -0
- data/lib/html2rss/auto_source/scraper/semantic_html.rb +5 -0
- data/lib/html2rss/auto_source/scraper/sitemap.rb +3 -0
- data/lib/html2rss/auto_source/scraper/wordpress_api.rb +3 -0
- data/lib/html2rss/auto_source/scraper.rb +4 -14
- data/lib/html2rss/auto_source/segment.rb +7 -4
- data/lib/html2rss/auto_source/segmenter/cluster.rb +22 -23
- data/lib/html2rss/auto_source/segmenter/list.rb +54 -31
- data/lib/html2rss/auto_source/segmenter.rb +5 -5
- data/lib/html2rss/auto_source.rb +2 -2
- data/lib/html2rss/batch.rb +3 -1
- data/lib/html2rss/capture/README.md +6 -4
- data/lib/html2rss/capture.rb +67 -35
- data/lib/html2rss/channel.rb +3 -3
- data/lib/html2rss/cli/probe_view.rb +5 -3
- data/lib/html2rss/cli/render.rb +6 -5
- data/lib/html2rss/cli/validate.rb +28 -27
- data/lib/html2rss/cli.rb +1 -1
- data/lib/html2rss/config/issue_mapper.rb +108 -0
- data/lib/html2rss/config/request_headers.rb +2 -3
- data/lib/html2rss/config/schema.rb +16 -1
- data/lib/html2rss/config/selectors_validator.rb +92 -70
- data/lib/html2rss/config/validation_issue.rb +60 -0
- data/lib/html2rss/config/validation_report.rb +55 -0
- data/lib/html2rss/config/validator.rb +10 -6
- data/lib/html2rss/config.rb +35 -113
- data/lib/html2rss/defaults.rb +42 -30
- data/lib/html2rss/doctor/botasaurus.rb +6 -4
- data/lib/html2rss/doctor.rb +8 -0
- data/lib/html2rss/error.rb +1 -1
- data/lib/html2rss/feed_builder/rss.rb +9 -14
- data/lib/html2rss/feed_pipeline/runtime_policy.rb +14 -10
- data/lib/html2rss/feed_pipeline/strategy_plan.rb +6 -2
- data/lib/html2rss/feed_resolution/candidate_generator.rb +1 -4
- data/lib/html2rss/feed_resolution/diag.rb +3 -1
- data/lib/html2rss/feed_resolution/options.rb +3 -1
- data/lib/html2rss/feed_resolution/policy.rb +2 -2
- data/lib/html2rss/html/article_extractor/date_extractor.rb +1 -1
- data/lib/html2rss/html/article_extractor.rb +2 -4
- data/lib/html2rss/html/feed_link.rb +3 -1
- data/lib/html2rss/html/navigator/text_extractor.rb +1 -1
- data/lib/html2rss/html/navigator.rb +10 -10
- data/lib/html2rss/html/rendering/description_builder.rb +1 -1
- data/lib/html2rss/html/sst_article_extractor.rb +8 -5
- data/lib/html2rss/link_destination/destination_facts.rb +4 -2
- data/lib/html2rss/link_destination/noise_policy.rb +10 -1
- data/lib/html2rss/link_destination/path_classifier.rb +45 -38
- data/lib/html2rss/mcp/README.md +2 -0
- data/lib/html2rss/mcp/config_argument.rb +3 -1
- data/lib/html2rss/mcp/contract.rb +6 -20
- data/lib/html2rss/mcp/outcome/playbook.rb +2 -2
- data/lib/html2rss/mcp/outcome.rb +36 -43
- data/lib/html2rss/mcp/server/tools.rb +111 -131
- data/lib/html2rss/mcp/server.rb +26 -18
- data/lib/html2rss/page_recon/README.md +5 -5
- data/lib/html2rss/page_recon/diagnostics.rb +71 -81
- data/lib/html2rss/page_recon.rb +9 -5
- data/lib/html2rss/recon.rb +19 -30
- data/lib/html2rss/request_service/botasaurus_contract.rb +11 -12
- data/lib/html2rss/request_service/botasaurus_strategy.rb +1 -1
- data/lib/html2rss/request_service/compressed_body.rb +4 -1
- data/lib/html2rss/request_service/httpx_strategy.rb +39 -28
- data/lib/html2rss/request_service/network_guard.rb +7 -5
- data/lib/html2rss/request_service/response.rb +1 -2
- data/lib/html2rss/request_service/strategy.rb +3 -8
- data/lib/html2rss/scoring/container_assessor.rb +3 -1
- data/lib/html2rss/scoring/engine.rb +8 -8
- data/lib/html2rss/scoring/observation.rb +5 -3
- data/lib/html2rss/scoring/ranked_segment.rb +3 -1
- data/lib/html2rss/scrape_target.rb +3 -1
- data/lib/html2rss/selector_candidates.rb +234 -0
- data/lib/html2rss/selectors/README.md +64 -0
- data/lib/html2rss/selectors/extractors/attribute.rb +13 -15
- data/lib/html2rss/selectors/extractors/href.rb +15 -16
- data/lib/html2rss/selectors/extractors/html.rb +8 -9
- data/lib/html2rss/selectors/extractors/static.rb +12 -14
- data/lib/html2rss/selectors/extractors/text.rb +8 -9
- data/lib/html2rss/selectors/extractors.rb +33 -20
- data/lib/html2rss/selectors/item_env.rb +31 -0
- data/lib/html2rss/selectors/{object_to_xml_converter.rb → json_xml.rb} +11 -12
- data/lib/html2rss/selectors/option_spec.rb +140 -0
- data/lib/html2rss/selectors/post_processors/base.rb +79 -31
- data/lib/html2rss/selectors/post_processors/gsub.rb +103 -39
- data/lib/html2rss/selectors/post_processors/html_transformers/transform_urls_to_absolute_ones.rb +4 -4
- data/lib/html2rss/selectors/post_processors/markdown_to_html.rb +10 -11
- data/lib/html2rss/selectors/post_processors/parse_time.rb +50 -26
- data/lib/html2rss/selectors/post_processors/parse_uri.rb +11 -7
- data/lib/html2rss/selectors/post_processors/sanitize_html.rb +46 -32
- data/lib/html2rss/selectors/post_processors/substring.rb +14 -25
- data/lib/html2rss/selectors/post_processors/template.rb +23 -19
- data/lib/html2rss/selectors/post_processors.rb +6 -5
- data/lib/html2rss/selectors/{schema_doc.rb → schema_export.rb} +12 -31
- data/lib/html2rss/selectors/step_env.rb +24 -0
- data/lib/html2rss/selectors.rb +231 -142
- data/lib/html2rss/sst/attrs.rb +5 -3
- data/lib/html2rss/sst/document.rb +3 -1
- data/lib/html2rss/sst/index.rb +3 -1
- data/lib/html2rss/sst/node.rb +29 -10
- data/lib/html2rss/sst/normalizer.rb +12 -16
- data/lib/html2rss/sst/tags.rb +7 -7
- data/lib/html2rss/status.rb +13 -8
- data/lib/html2rss/surface_category.rb +14 -32
- data/lib/html2rss/syndication/parser.rb +2 -2
- data/lib/html2rss/test/enhance_audit.rb +1 -3
- data/lib/html2rss/test/policy.rb +106 -0
- data/lib/html2rss/test.rb +126 -173
- data/lib/html2rss/url.rb +25 -4
- data/lib/html2rss/version.rb +1 -1
- data/lib/html2rss.rb +21 -13
- data/lib/tasks/config_schema.rake +2 -2
- data/schema/html2rss-config.schema.json +11 -8
- metadata +15 -6
- data/lib/html2rss/selectors/item_scope.rb +0 -30
|
@@ -7,47 +7,54 @@ module Html2rss
|
|
|
7
7
|
attr_reader :segments
|
|
8
8
|
|
|
9
9
|
# Soft utility segments excluded from high-confidence junk (still utility_path).
|
|
10
|
-
SOFT_UTILITY =
|
|
11
|
-
for-you join member members membership newsletter newsletters
|
|
12
|
-
plans plus premium pricing recommended
|
|
13
|
-
].
|
|
10
|
+
SOFT_UTILITY = Set[
|
|
11
|
+
'for-you', 'join', 'member', 'members', 'membership', 'newsletter', 'newsletters',
|
|
12
|
+
'plans', 'plus', 'premium', 'pricing', 'recommended'
|
|
13
|
+
].freeze
|
|
14
14
|
|
|
15
15
|
# Segment groups used to classify article, taxonomy, utility, and vanity routes.
|
|
16
16
|
# Utility = taxonomy ∪ chrome ∪ soft utility (do not re-list taxonomy tokens).
|
|
17
17
|
SEGMENT_SETS = begin
|
|
18
|
-
content =
|
|
19
|
-
article articles blog blogs changelog changelogs insight insights
|
|
20
|
-
launch launches news post posts release releases story stories
|
|
21
|
-
artikel beitrag beitraege nachrichten neuigkeiten
|
|
22
|
-
articulo articulos noticia noticias entrada entradas
|
|
23
|
-
actualite actualites nouvelle nouvelles
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
18
|
+
content = Set[
|
|
19
|
+
'article', 'articles', 'blog', 'blogs', 'changelog', 'changelogs', 'insight', 'insights',
|
|
20
|
+
'launch', 'launches', 'news', 'post', 'posts', 'release', 'releases', 'story', 'stories',
|
|
21
|
+
'update', 'updates', 'artikel', 'beitrag', 'beitraege', 'nachrichten', 'neuigkeiten',
|
|
22
|
+
'aktuelles', 'articulo', 'articulos', 'noticia', 'noticias', 'entrada', 'entradas',
|
|
23
|
+
'publicacion', 'publicaciones', 'actualite', 'actualites', 'nouvelle', 'nouvelles',
|
|
24
|
+
'teaser', 'teasers', 'card', 'cards'
|
|
25
|
+
].freeze
|
|
26
|
+
taxonomy = Set[
|
|
27
|
+
'category', 'categories', 'tag', 'tags', 'topic', 'topics',
|
|
28
|
+
'kategorie', 'kategorien', 'schlagwort', 'schlagworte', 'thema', 'themen',
|
|
29
|
+
'categoria', 'categorias', 'etiqueta', 'etiquetas', 'tema', 'temas',
|
|
30
|
+
'categorie', 'etiquette', 'etiquettes', 'sujet', 'sujets', 'theme', 'themes'
|
|
31
|
+
].freeze
|
|
32
|
+
vanity = Set[
|
|
33
|
+
'join', 'membership', 'plus', 'premium', 'pricing', 'plans', 'subscribe', 'signup',
|
|
34
|
+
'abonnieren', 'abo', 'suscribirse', 'boletin', 's-abonner', 'saboner'
|
|
35
|
+
].freeze
|
|
36
|
+
chrome = Set[
|
|
37
|
+
'about', 'account', 'archive', 'archives', 'author', 'authors', 'comment', 'comments',
|
|
38
|
+
'contact', 'feedback', 'help', 'login', 'logout', 'notification', 'notifications',
|
|
39
|
+
'preference', 'preferences', 'profile', 'register', 'search', 'settings', 'share',
|
|
40
|
+
'signup', 'subscribe', 'feed', 'feeds', 'comment-feed', 'comments-feed', 'privacy',
|
|
41
|
+
'terms', 'cookie', 'cookies', 'user', 'users', 'autor', 'autoren', 'archiv', 'ueber-uns',
|
|
42
|
+
'ueber', 'ueberuns', 'profil', 'kontakt', 'impressum', 'suche', 'hilfe', 'anmelden',
|
|
43
|
+
'registrieren', 'konto', 'registrierung', 'anmeldung', 'abonnieren', 'abo', 'datenschutz',
|
|
44
|
+
'nutzungsbedingungen', 'agb', 'autores', 'archivos', 'sobre-nosotros', 'sobre',
|
|
45
|
+
'quienes-somos', 'buscar', 'busqueda', 'ayuda', 'entrar', 'ingresar', 'registrarse',
|
|
46
|
+
'registro', 'cuenta', 'suscribirse', 'boletin', 'privacidad', 'condiciones', 'auteur',
|
|
47
|
+
'auteurs', 'a-propos', 'apropos', 'recherche', 'rechercher', 'aide', 'connexion',
|
|
48
|
+
's-inscrire', 'sinscrire', 'inscription', 'compte', 's-abonner', 'saboner',
|
|
49
|
+
'lettre-information', 'confidentialite', 'mentions-legales', 'cgu', 'menu', 'sidebar',
|
|
50
|
+
'widget', 'social', 'modal', 'popup', 'banner', 'promo', 'ad', 'ads', 'related',
|
|
51
|
+
'recommendation', 'recommendations', 'pagination', 'pager', 'dating', 'jobs', 'job',
|
|
52
|
+
'career', 'careers', 'deals', 'deal', 'shopping', 'shop', 'trading', 'broker',
|
|
53
|
+
'versicherung', 'tierversicherung', 'insurance', 'vergleich', 'comparison',
|
|
54
|
+
'partnerboerse', 'singleboerse', 'krypto', 'crypto', 'casinos', 'casino',
|
|
55
|
+
'kreditkarten', 'kreditkarte', 'kredit', 'echtgeld', 'vpn', 'games', 'kaufberater',
|
|
56
|
+
'leasing'
|
|
57
|
+
].freeze
|
|
51
58
|
utility = (taxonomy | chrome | SOFT_UTILITY).freeze
|
|
52
59
|
{
|
|
53
60
|
content:,
|
|
@@ -55,7 +62,7 @@ module Html2rss
|
|
|
55
62
|
high_confidence_junk: (utility - SOFT_UTILITY).freeze,
|
|
56
63
|
taxonomy:,
|
|
57
64
|
vanity:,
|
|
58
|
-
deep_post_context:
|
|
65
|
+
deep_post_context: Set['press', 'newsroom', 'presse', 'pressemitteilungen', 'prensa'].freeze
|
|
59
66
|
}.freeze
|
|
60
67
|
end
|
|
61
68
|
# Path segment that begins with a year-like publishing marker.
|
data/lib/html2rss/mcp/README.md
CHANGED
|
@@ -24,6 +24,8 @@ Batch: `batch_inspect`, `batch_recon`, `batch_scrape`.
|
|
|
24
24
|
2. **Reusable feed YAML?** → `capture` → `test` → `apply`. `capture` returns YAML in `payload.yaml`. Strive `enhance: true` (false only when chrome leaks). `test` runs schema + live extraction; optional `compare_enhance` compares enhance off vs on. `apply` is the ship gate (`isError` on zero items). Both `test` and `apply` may include `quality_report.enhance_gains` when `selectors.items.enhance` is true.
|
|
25
25
|
3. **Weak scrape/capture or recon?** → `inspect` (or `batch_inspect`). When alternates warrant it, follow `next_step` to `recon`.
|
|
26
26
|
4. **Config already in hand?** → `validate` (schema only) → `test` → `apply`.
|
|
27
|
+
- `validate` failures return `payload.issues` (array of `{ path, code, message, expected, actual }`) — not a nested `errors` hash.
|
|
28
|
+
- `test` schema failures surface the same shape under `payload.validation_issues`.
|
|
27
29
|
|
|
28
30
|
Prefer `capture` for durable configs; `scrape` / `batch_scrape` for one-shot extraction. Follow envelope `next_step` and `guidance`; do not parse scrape text as a raw item array.
|
|
29
31
|
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
module Html2rss
|
|
4
4
|
module MCP
|
|
5
|
+
ConfigArgument = Data.define(:config)
|
|
6
|
+
|
|
5
7
|
# Feed config supplied over MCP as a hash XOR a YAML string.
|
|
6
8
|
#
|
|
7
9
|
# Config does not know MCP. YAML parsing stays on {Html2rss::Config.from_yaml}.
|
|
8
|
-
ConfigArgument
|
|
10
|
+
class ConfigArgument
|
|
9
11
|
class << self
|
|
10
12
|
##
|
|
11
13
|
# @param config [Hash, nil] feed configuration hash
|
|
@@ -16,7 +16,7 @@ module Html2rss
|
|
|
16
16
|
ALL_ACCEPTED_STRATEGIES = (STRATEGIES + MIGRATION_STRATEGIES).freeze
|
|
17
17
|
|
|
18
18
|
# Bump when tool names, required inputs, or envelope semantics change (independent of gem +VERSION+).
|
|
19
|
-
MCP_CONTRACT_VERSION =
|
|
19
|
+
MCP_CONTRACT_VERSION = 3
|
|
20
20
|
public_constant :MCP_CONTRACT_VERSION
|
|
21
21
|
|
|
22
22
|
# Raised when apply/validate config uses an unpublished MCP request adapter.
|
|
@@ -201,26 +201,12 @@ module Html2rss
|
|
|
201
201
|
# Tool annotations for +validate+ (closed world).
|
|
202
202
|
ANNOTATIONS_VALIDATE = ANNOTATIONS_OPEN_WORLD.merge(open_world_hint: false).freeze
|
|
203
203
|
|
|
204
|
-
# Human titles for +tools/list+.
|
|
205
|
-
TITLES = {
|
|
206
|
-
scrape: 'Scrape',
|
|
207
|
-
inspect: 'Inspect',
|
|
208
|
-
recon: 'Recon',
|
|
209
|
-
batch_scrape: 'Batch scrape',
|
|
210
|
-
batch_inspect: 'Batch inspect',
|
|
211
|
-
batch_recon: 'Batch recon',
|
|
212
|
-
capture: 'Capture',
|
|
213
|
-
validate: 'Validate',
|
|
214
|
-
apply: 'Apply',
|
|
215
|
-
test: 'Test'
|
|
216
|
-
}.freeze
|
|
217
|
-
|
|
218
204
|
# @api private
|
|
219
205
|
CATALOG_ENTRY_LINE = lambda do |entry|
|
|
220
|
-
schema = entry.
|
|
206
|
+
schema = entry.input_schema
|
|
221
207
|
required = Array(schema[:required]).sort.join(',')
|
|
222
208
|
one_of = Array(schema[:oneOf]).map { |branch| Array(branch[:required]).sort.join('+') }.sort.join('|')
|
|
223
|
-
[entry.
|
|
209
|
+
[entry.name, required, one_of].reject(&:empty?).join(':')
|
|
224
210
|
end.freeze
|
|
225
211
|
|
|
226
212
|
class << self
|
|
@@ -229,7 +215,7 @@ module Html2rss
|
|
|
229
215
|
#
|
|
230
216
|
# @return [Array<String>]
|
|
231
217
|
def catalog_tools
|
|
232
|
-
Server::Tools::TOOLS.map
|
|
218
|
+
Server::Tools::TOOLS.map(&:name).sort
|
|
233
219
|
end
|
|
234
220
|
|
|
235
221
|
##
|
|
@@ -239,7 +225,7 @@ module Html2rss
|
|
|
239
225
|
#
|
|
240
226
|
# @return [String] 16-char hex digest prefix
|
|
241
227
|
def catalog_fingerprint
|
|
242
|
-
lines = Server::Tools::TOOLS.sort_by
|
|
228
|
+
lines = Server::Tools::TOOLS.sort_by(&:name).map(&CATALOG_ENTRY_LINE)
|
|
243
229
|
Digest::SHA256.hexdigest(lines.join("\n")).slice(0, 16)
|
|
244
230
|
end
|
|
245
231
|
|
|
@@ -254,7 +240,7 @@ module Html2rss
|
|
|
254
240
|
required: %w[ok next_step guidance payload],
|
|
255
241
|
properties: {
|
|
256
242
|
ok: { type: 'boolean' },
|
|
257
|
-
next_step: { type: 'string', enum: Outcome::NextStep::
|
|
243
|
+
next_step: { type: 'string', enum: Outcome::NextStep::ALL.keys.map(&:to_s) },
|
|
258
244
|
guidance: { type: 'string' },
|
|
259
245
|
payload: { type: 'object' }
|
|
260
246
|
}
|
|
@@ -8,7 +8,7 @@ module Html2rss
|
|
|
8
8
|
# and prompt bodies. {Server} delegates here — do not duplicate prose in
|
|
9
9
|
# +server.rb+.
|
|
10
10
|
class Playbook
|
|
11
|
-
# Default guidance copy
|
|
11
|
+
# Default guidance copy; {Outcome::NextStep::ALL} is built from these keys.
|
|
12
12
|
GUIDANCE = {
|
|
13
13
|
done: 'Done. Read payload for the result.',
|
|
14
14
|
inspect: 'Call inspect next. Read payload for diagnostics (final_url, status, ' \
|
|
@@ -16,7 +16,7 @@ module Html2rss
|
|
|
16
16
|
recon: 'Call recon next. Read payload for verdict and native_feed preference.',
|
|
17
17
|
validate: 'Call validate with payload.yaml or a config hash (XOR, not both).',
|
|
18
18
|
apply: 'Call apply next. Confirm payload.item_count before shipping.',
|
|
19
|
-
scrape: 'Call scrape for articles now. strategy auto already runs
|
|
19
|
+
scrape: 'Call scrape for articles now. strategy auto already runs default (HTTPX) then Botasaurus ' \
|
|
20
20
|
'and promotes native RSS/Atom when present.',
|
|
21
21
|
capture: 'Call capture for a reusable YAML draft, then follow next_step.',
|
|
22
22
|
read_runtime: 'Read html2rss://runtime. Compare mcp_contract_version and catalog_fingerprint ' \
|
data/lib/html2rss/mcp/outcome.rb
CHANGED
|
@@ -13,24 +13,16 @@ module Html2rss
|
|
|
13
13
|
NextStep = Data.define(:name, :guidance)
|
|
14
14
|
|
|
15
15
|
##
|
|
16
|
-
# Closed set of agent next actions.
|
|
16
|
+
# Closed set of agent next actions. Names are owned by {Playbook::GUIDANCE}.
|
|
17
17
|
class NextStep
|
|
18
|
-
#
|
|
19
|
-
|
|
18
|
+
# Frozen name → instance table built from {Playbook::GUIDANCE}.
|
|
19
|
+
ALL = Playbook::GUIDANCE.to_h { |name, guidance| [name, new(name:, guidance:)] }.freeze
|
|
20
|
+
private_class_method :new
|
|
20
21
|
|
|
21
22
|
##
|
|
22
23
|
# @param name [Symbol, String]
|
|
23
|
-
# @
|
|
24
|
-
def
|
|
25
|
-
step = name.to_sym
|
|
26
|
-
raise ArgumentError, "unknown next_step: #{name.inspect}" unless NAMES.include?(step)
|
|
27
|
-
|
|
28
|
-
super(name: step, guidance: (guidance || Playbook::GUIDANCE.fetch(step)).freeze)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
class << self
|
|
32
|
-
NAMES.each { |step| define_method(step) { new(name: step) } }
|
|
33
|
-
end
|
|
24
|
+
# @return [NextStep]
|
|
25
|
+
def self.[](name) = ALL.fetch(name.to_sym) { raise ArgumentError, "unknown next_step: #{name.inspect}" }
|
|
34
26
|
end
|
|
35
27
|
|
|
36
28
|
##
|
|
@@ -107,12 +99,13 @@ module Html2rss
|
|
|
107
99
|
end
|
|
108
100
|
|
|
109
101
|
##
|
|
110
|
-
# @param
|
|
102
|
+
# @param report [Html2rss::Config::ValidationReport]
|
|
111
103
|
# @return [Outcome]
|
|
112
|
-
def validate(
|
|
113
|
-
ok =
|
|
114
|
-
next_step = ok ? NextStep
|
|
115
|
-
|
|
104
|
+
def validate(report:)
|
|
105
|
+
ok = report.success?
|
|
106
|
+
next_step = ok ? NextStep[:test] : NextStep[:validate]
|
|
107
|
+
payload = ok ? {} : { issues: report.issues.map(&:to_h) }
|
|
108
|
+
new(ok:, next_step:, guidance: next_step.guidance, payload:)
|
|
116
109
|
end
|
|
117
110
|
|
|
118
111
|
##
|
|
@@ -131,17 +124,17 @@ module Html2rss
|
|
|
131
124
|
##
|
|
132
125
|
# @param batch_result [Html2rss::Batch::BatchResult]
|
|
133
126
|
# @return [Outcome]
|
|
134
|
-
def batch_scrape(batch_result) = batch(batch_result, NextStep
|
|
127
|
+
def batch_scrape(batch_result) = batch(batch_result, NextStep[:scrape])
|
|
135
128
|
|
|
136
129
|
##
|
|
137
130
|
# @param batch_result [Html2rss::Batch::BatchResult]
|
|
138
131
|
# @return [Outcome]
|
|
139
|
-
def batch_inspect(batch_result) = batch(batch_result, NextStep
|
|
132
|
+
def batch_inspect(batch_result) = batch(batch_result, NextStep[:inspect])
|
|
140
133
|
|
|
141
134
|
##
|
|
142
135
|
# @param batch_result [Html2rss::Batch::BatchResult]
|
|
143
136
|
# @return [Outcome]
|
|
144
|
-
def batch_recon(batch_result) = batch(batch_result, NextStep
|
|
137
|
+
def batch_recon(batch_result) = batch(batch_result, NextStep[:recon])
|
|
145
138
|
|
|
146
139
|
##
|
|
147
140
|
# @param rss [String]
|
|
@@ -151,7 +144,7 @@ module Html2rss
|
|
|
151
144
|
# @return [Outcome]
|
|
152
145
|
def apply(rss:, item_count:, empty: item_count.zero?, quality_report: nil)
|
|
153
146
|
ok = !empty
|
|
154
|
-
next_step = ok ? NextStep
|
|
147
|
+
next_step = ok ? NextStep[:done] : NextStep[:inspect]
|
|
155
148
|
payload = { rss:, item_count: }
|
|
156
149
|
payload[:quality_report] = quality_report if quality_report
|
|
157
150
|
new(ok:, next_step:, guidance: next_step.guidance, payload:)
|
|
@@ -169,15 +162,15 @@ module Html2rss
|
|
|
169
162
|
private
|
|
170
163
|
|
|
171
164
|
def batch(batch_result, failure_step)
|
|
172
|
-
step = batch_result.successful.positive? ? NextStep
|
|
165
|
+
step = batch_result.successful.positive? ? NextStep[:done] : failure_step
|
|
173
166
|
new(ok: true, next_step: step, guidance: step.guidance, payload: batch_result.to_h)
|
|
174
167
|
end
|
|
175
168
|
|
|
176
169
|
def scrape_next_step(empty, botasaurus_configured:)
|
|
177
|
-
return NextStep
|
|
178
|
-
return NextStep
|
|
170
|
+
return NextStep[:done] unless empty
|
|
171
|
+
return NextStep[:read_runtime] unless botasaurus_configured
|
|
179
172
|
|
|
180
|
-
NextStep
|
|
173
|
+
NextStep[:inspect]
|
|
181
174
|
end
|
|
182
175
|
|
|
183
176
|
def scrape_payload(items:, requested_strategy:, channel_title:, admission_drops:)
|
|
@@ -188,33 +181,33 @@ module Html2rss
|
|
|
188
181
|
end
|
|
189
182
|
|
|
190
183
|
def inspect_next_step(report)
|
|
191
|
-
return NextStep
|
|
192
|
-
return NextStep
|
|
184
|
+
return NextStep[:recon] if report.alternate_feeds?
|
|
185
|
+
return NextStep[:capture] if report.articles_count.positive?
|
|
193
186
|
|
|
194
|
-
NextStep
|
|
187
|
+
NextStep[:scrape]
|
|
195
188
|
end
|
|
196
189
|
|
|
197
190
|
def recon_next_step(result)
|
|
198
|
-
return NextStep
|
|
199
|
-
return NextStep
|
|
191
|
+
return NextStep[:done] if result.defer?
|
|
192
|
+
return NextStep[:capture] if result.build?
|
|
200
193
|
|
|
201
|
-
NextStep
|
|
194
|
+
NextStep[:scrape]
|
|
202
195
|
end
|
|
203
196
|
|
|
204
197
|
def capture_next_step(articles_count:, has_selectors:, native_feed: nil)
|
|
205
|
-
return NextStep
|
|
198
|
+
return NextStep[:done] if native_feed
|
|
206
199
|
|
|
207
|
-
articles_count.positive? && has_selectors ? NextStep
|
|
200
|
+
articles_count.positive? && has_selectors ? NextStep[:test] : NextStep[:inspect]
|
|
208
201
|
end
|
|
209
202
|
|
|
210
203
|
def test_next_step(test_result)
|
|
211
|
-
return NextStep
|
|
204
|
+
return NextStep[:apply] if test_result.success
|
|
212
205
|
|
|
213
206
|
kind = test_result.failure_kind
|
|
214
|
-
return NextStep
|
|
215
|
-
return NextStep
|
|
207
|
+
return NextStep[:validate] if kind&.schema?
|
|
208
|
+
return NextStep[:capture] if kind&.execution? || kind&.min_items? || kind&.quality?
|
|
216
209
|
|
|
217
|
-
NextStep
|
|
210
|
+
NextStep[:capture]
|
|
218
211
|
end
|
|
219
212
|
|
|
220
213
|
def test_guidance(test_result, next_step)
|
|
@@ -244,15 +237,15 @@ module Html2rss
|
|
|
244
237
|
|
|
245
238
|
def next_step_for_error(error)
|
|
246
239
|
case error
|
|
247
|
-
when RequestService::BotasaurusConfigurationError then NextStep
|
|
248
|
-
when Contract::UnpublishedRequestError then NextStep
|
|
240
|
+
when RequestService::BotasaurusConfigurationError then NextStep[:read_runtime]
|
|
241
|
+
when Contract::UnpublishedRequestError then NextStep[:validate]
|
|
249
242
|
when ArgumentError then argument_error_next_step(error)
|
|
250
|
-
else NextStep
|
|
243
|
+
else NextStep[:inspect]
|
|
251
244
|
end
|
|
252
245
|
end
|
|
253
246
|
|
|
254
247
|
def argument_error_next_step(error)
|
|
255
|
-
XOR_ERROR.match?(error.message) ? NextStep
|
|
248
|
+
XOR_ERROR.match?(error.message) ? NextStep[:validate] : NextStep[:inspect]
|
|
256
249
|
end
|
|
257
250
|
end
|
|
258
251
|
end
|