html2rss 0.25.0 → 0.27.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 (89) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +99 -2
  3. data/html2rss.gemspec +4 -1
  4. data/lib/html2rss/auto_source/README.md +57 -0
  5. data/lib/html2rss/auto_source/cleanup.rb +140 -49
  6. data/lib/html2rss/auto_source/scraper/json_state.rb +31 -22
  7. data/lib/html2rss/auto_source/scraper/schema/item_list.rb +2 -14
  8. data/lib/html2rss/auto_source/scraper/xhr_articles.rb +69 -0
  9. data/lib/html2rss/auto_source/scraper.rb +47 -15
  10. data/lib/html2rss/auto_source/segmenter/list.rb +3 -1
  11. data/lib/html2rss/auto_source/segmenter/primary_link.rb +6 -7
  12. data/lib/html2rss/auto_source/segmenter.rb +30 -1
  13. data/lib/html2rss/auto_source.rb +41 -10
  14. data/lib/html2rss/capture/README.md +61 -0
  15. data/lib/html2rss/capture.rb +318 -0
  16. data/lib/html2rss/cli.rb +94 -17
  17. data/lib/html2rss/config/auto_source_contract.rb +3 -1
  18. data/lib/html2rss/config/schema.rb +79 -26
  19. data/lib/html2rss/config/selectors_validator.rb +60 -21
  20. data/lib/html2rss/config/validator.rb +46 -32
  21. data/lib/html2rss/config.rb +28 -0
  22. data/lib/html2rss/error.rb +31 -1
  23. data/lib/html2rss/feed_pipeline/README.md +42 -0
  24. data/lib/html2rss/feed_pipeline/auto_fallback.rb +38 -15
  25. data/lib/html2rss/feed_pipeline/runtime_policy.rb +3 -43
  26. data/lib/html2rss/feed_pipeline/strategy_plan.rb +11 -0
  27. data/lib/html2rss/feed_pipeline.rb +40 -15
  28. data/lib/html2rss/hash_util.rb +17 -0
  29. data/lib/html2rss/html/article_extractor/category_extractor.rb +36 -22
  30. data/lib/html2rss/html/article_extractor/date_extractor.rb +5 -3
  31. data/lib/html2rss/html/article_extractor.rb +95 -17
  32. data/lib/html2rss/html/article_rules/category.rb +28 -11
  33. data/lib/html2rss/html/article_rules/date.rb +60 -6
  34. data/lib/html2rss/html/article_rules/description.rb +122 -0
  35. data/lib/html2rss/html/card_walk.rb +42 -0
  36. data/lib/html2rss/html/feed_link.rb +34 -0
  37. data/lib/html2rss/html/navigator.rb +18 -0
  38. data/lib/html2rss/html/sst_article_extractor.rb +147 -31
  39. data/lib/html2rss/link_destination/noise_policy.rb +7 -18
  40. data/lib/html2rss/link_destination/path_classifier.rb +49 -32
  41. data/lib/html2rss/mcp/config_argument.rb +42 -0
  42. data/lib/html2rss/mcp/contract.rb +173 -0
  43. data/lib/html2rss/mcp/inspect.rb +241 -0
  44. data/lib/html2rss/mcp/outcome.rb +188 -0
  45. data/lib/html2rss/mcp/server.rb +421 -0
  46. data/lib/html2rss/mcp.rb +21 -0
  47. data/lib/html2rss/request_service/blocked_surface.rb +24 -1
  48. data/lib/html2rss/request_service/botasaurus_contract.rb +261 -90
  49. data/lib/html2rss/request_service/botasaurus_strategy.rb +18 -9
  50. data/lib/html2rss/request_service/budget.rb +7 -35
  51. data/lib/html2rss/request_service/compressed_body.rb +109 -0
  52. data/lib/html2rss/request_service/context.rb +0 -6
  53. data/lib/html2rss/request_service/faraday_strategy.rb +48 -3
  54. data/lib/html2rss/request_service/policy.rb +2 -2
  55. data/lib/html2rss/request_service/response.rb +72 -7
  56. data/lib/html2rss/request_service/strategy.rb +1 -2
  57. data/lib/html2rss/request_service.rb +10 -10
  58. data/lib/html2rss/scoring/container_assessor.rb +9 -15
  59. data/lib/html2rss/scoring/engine.rb +49 -5
  60. data/lib/html2rss/scoring/link_resolver.rb +12 -0
  61. data/lib/html2rss/scoring/ranked_segment.rb +0 -12
  62. data/lib/html2rss/scoring/score.rb +1 -20
  63. data/lib/html2rss/scoring.rb +0 -25
  64. data/lib/html2rss/selectors/extractors/attribute.rb +15 -0
  65. data/lib/html2rss/selectors/extractors/href.rb +12 -0
  66. data/lib/html2rss/selectors/extractors/html.rb +12 -0
  67. data/lib/html2rss/selectors/extractors/static.rb +14 -0
  68. data/lib/html2rss/selectors/extractors/text.rb +11 -0
  69. data/lib/html2rss/selectors/post_processors/gsub.rb +18 -0
  70. data/lib/html2rss/selectors/post_processors/html_to_markdown.rb +11 -0
  71. data/lib/html2rss/selectors/post_processors/markdown_to_html.rb +12 -0
  72. data/lib/html2rss/selectors/post_processors/parse_time.rb +11 -0
  73. data/lib/html2rss/selectors/post_processors/parse_uri.rb +11 -0
  74. data/lib/html2rss/selectors/post_processors/sanitize_html.rb +13 -0
  75. data/lib/html2rss/selectors/post_processors/substring.rb +21 -0
  76. data/lib/html2rss/selectors/post_processors/template.rb +20 -0
  77. data/lib/html2rss/selectors/schema_doc.rb +99 -0
  78. data/lib/html2rss/selectors.rb +2 -1
  79. data/lib/html2rss/status.rb +27 -11
  80. data/lib/html2rss/url.rb +31 -7
  81. data/lib/html2rss/version.rb +1 -1
  82. data/lib/html2rss.rb +62 -7
  83. data/schema/html2rss-config.schema.json +428 -94
  84. metadata +65 -12
  85. data/lib/html2rss/request_service/browserless_strategy.rb +0 -132
  86. data/lib/html2rss/request_service/puppet_commander/navigation_guards.rb +0 -148
  87. data/lib/html2rss/request_service/puppet_commander/preload_runner.rb +0 -86
  88. data/lib/html2rss/request_service/puppet_commander.rb +0 -95
  89. data/lib/html2rss/scoring/anchor_score.rb +0 -34
@@ -63,6 +63,37 @@
63
63
  "url"
64
64
  ]
65
65
  },
66
+ "directory": {
67
+ "type": "object",
68
+ "properties": {
69
+ "topics": {
70
+ "type": "array",
71
+ "items": {
72
+ "minLength": 1,
73
+ "type": "string",
74
+ "enum": [
75
+ "sports",
76
+ "energy",
77
+ "tech",
78
+ "science",
79
+ "news",
80
+ "entertainment",
81
+ "jobs",
82
+ "finance",
83
+ "security",
84
+ "travel",
85
+ "environment",
86
+ "consumer",
87
+ "civic",
88
+ "product",
89
+ "research"
90
+ ]
91
+ },
92
+ "minItems": 1
93
+ }
94
+ },
95
+ "required": []
96
+ },
66
97
  "headers": {
67
98
  "type": "object",
68
99
  "description": "HTTP headers applied to every request.",
@@ -201,6 +232,18 @@
201
232
  },
202
233
  "required": []
203
234
  },
235
+ "xhr_articles": {
236
+ "type": "object",
237
+ "properties": {
238
+ "enabled": {
239
+ "type": "boolean",
240
+ "not": {
241
+ "type": "null"
242
+ }
243
+ }
244
+ },
245
+ "required": []
246
+ },
204
247
  "meta_oembed": {
205
248
  "type": "object",
206
249
  "properties": {
@@ -274,13 +317,6 @@
274
317
  "not": {
275
318
  "type": "null"
276
319
  }
277
- },
278
- "min_words_title": {
279
- "type": "integer",
280
- "not": {
281
- "type": "null"
282
- },
283
- "exclusiveMinimum": 0
284
320
  }
285
321
  },
286
322
  "required": []
@@ -310,6 +346,9 @@
310
346
  "json_state": {
311
347
  "enabled": true
312
348
  },
349
+ "xhr_articles": {
350
+ "enabled": true
351
+ },
313
352
  "meta_oembed": {
314
353
  "enabled": true
315
354
  },
@@ -325,8 +364,7 @@
325
364
  }
326
365
  },
327
366
  "cleanup": {
328
- "keep_different_domain": false,
329
- "min_words_title": 3
367
+ "keep_different_domain": false
330
368
  }
331
369
  }
332
370
  },
@@ -422,8 +460,24 @@
422
460
  "type": "object",
423
461
  "properties": {
424
462
  "extractor": {
425
- "type": "string",
426
- "minLength": 1
463
+ "description": "Extractor used to pull a value from the selected element.",
464
+ "oneOf": [
465
+ {
466
+ "$ref": "#/$defs/extractors/attribute"
467
+ },
468
+ {
469
+ "$ref": "#/$defs/extractors/href"
470
+ },
471
+ {
472
+ "$ref": "#/$defs/extractors/html"
473
+ },
474
+ {
475
+ "$ref": "#/$defs/extractors/static"
476
+ },
477
+ {
478
+ "$ref": "#/$defs/extractors/text"
479
+ }
480
+ ]
427
481
  },
428
482
  "attribute": {
429
483
  "type": "string",
@@ -435,8 +489,34 @@
435
489
  },
436
490
  "post_process": {
437
491
  "type": "array",
492
+ "description": "Ordered transforms applied to the extracted value.",
438
493
  "items": {
439
- "type": "object"
494
+ "oneOf": [
495
+ {
496
+ "$ref": "#/$defs/post_processors/gsub"
497
+ },
498
+ {
499
+ "$ref": "#/$defs/post_processors/html_to_markdown"
500
+ },
501
+ {
502
+ "$ref": "#/$defs/post_processors/markdown_to_html"
503
+ },
504
+ {
505
+ "$ref": "#/$defs/post_processors/parse_time"
506
+ },
507
+ {
508
+ "$ref": "#/$defs/post_processors/parse_uri"
509
+ },
510
+ {
511
+ "$ref": "#/$defs/post_processors/sanitize_html"
512
+ },
513
+ {
514
+ "$ref": "#/$defs/post_processors/substring"
515
+ },
516
+ {
517
+ "$ref": "#/$defs/post_processors/template"
518
+ }
519
+ ]
440
520
  }
441
521
  },
442
522
  "content_type": {
@@ -472,8 +552,24 @@
472
552
  "type": "object",
473
553
  "properties": {
474
554
  "extractor": {
475
- "type": "string",
476
- "minLength": 1
555
+ "description": "Extractor used to pull a value from the selected element.",
556
+ "oneOf": [
557
+ {
558
+ "$ref": "#/$defs/extractors/attribute"
559
+ },
560
+ {
561
+ "$ref": "#/$defs/extractors/href"
562
+ },
563
+ {
564
+ "$ref": "#/$defs/extractors/html"
565
+ },
566
+ {
567
+ "$ref": "#/$defs/extractors/static"
568
+ },
569
+ {
570
+ "$ref": "#/$defs/extractors/text"
571
+ }
572
+ ]
477
573
  },
478
574
  "attribute": {
479
575
  "type": "string",
@@ -485,8 +581,34 @@
485
581
  },
486
582
  "post_process": {
487
583
  "type": "array",
584
+ "description": "Ordered transforms applied to the extracted value.",
488
585
  "items": {
489
- "type": "object"
586
+ "oneOf": [
587
+ {
588
+ "$ref": "#/$defs/post_processors/gsub"
589
+ },
590
+ {
591
+ "$ref": "#/$defs/post_processors/html_to_markdown"
592
+ },
593
+ {
594
+ "$ref": "#/$defs/post_processors/markdown_to_html"
595
+ },
596
+ {
597
+ "$ref": "#/$defs/post_processors/parse_time"
598
+ },
599
+ {
600
+ "$ref": "#/$defs/post_processors/parse_uri"
601
+ },
602
+ {
603
+ "$ref": "#/$defs/post_processors/sanitize_html"
604
+ },
605
+ {
606
+ "$ref": "#/$defs/post_processors/substring"
607
+ },
608
+ {
609
+ "$ref": "#/$defs/post_processors/template"
610
+ }
611
+ ]
490
612
  }
491
613
  }
492
614
  },
@@ -520,77 +642,18 @@
520
642
  },
521
643
  "exclusiveMinimum": 0
522
644
  },
523
- "browserless": {
524
- "type": "object",
525
- "properties": {
526
- "preload": {
527
- "type": "object",
528
- "properties": {
529
- "wait_after_ms": {
530
- "type": "integer",
531
- "not": {
532
- "type": "null"
533
- },
534
- "minimum": 0
535
- },
536
- "click_selectors": {
537
- "type": "array",
538
- "items": {
539
- "type": "object",
540
- "properties": {
541
- "selector": {
542
- "type": "string",
543
- "minLength": 1
544
- },
545
- "max_clicks": {
546
- "type": "integer",
547
- "not": {
548
- "type": "null"
549
- },
550
- "exclusiveMinimum": 0
551
- },
552
- "wait_after_ms": {
553
- "type": "integer",
554
- "not": {
555
- "type": "null"
556
- },
557
- "minimum": 0
558
- }
559
- },
560
- "required": [
561
- "selector"
562
- ]
563
- }
564
- },
565
- "scroll_down": {
566
- "type": "object",
567
- "properties": {
568
- "iterations": {
569
- "type": "integer",
570
- "not": {
571
- "type": "null"
572
- },
573
- "exclusiveMinimum": 0
574
- },
575
- "wait_after_ms": {
576
- "type": "integer",
577
- "not": {
578
- "type": "null"
579
- },
580
- "minimum": 0
581
- }
582
- },
583
- "required": []
584
- }
585
- },
586
- "required": []
587
- }
588
- },
589
- "required": []
590
- },
591
645
  "botasaurus": {
592
646
  "type": "object",
593
647
  "properties": {
648
+ "execution_mode": {
649
+ "type": "string",
650
+ "minLength": 1,
651
+ "enum": [
652
+ "auto",
653
+ "request",
654
+ "browser"
655
+ ]
656
+ },
594
657
  "navigation_mode": {
595
658
  "type": "string",
596
659
  "minLength": 1,
@@ -598,7 +661,8 @@
598
661
  "auto",
599
662
  "get",
600
663
  "google_get",
601
- "google_get_bypass"
664
+ "google_get_bypass",
665
+ "organic_get"
602
666
  ]
603
667
  },
604
668
  "max_retries": {
@@ -620,7 +684,14 @@
620
684
  "not": {
621
685
  "type": "null"
622
686
  },
623
- "exclusiveMinimum": 0
687
+ "minimum": 1,
688
+ "maximum": 20
689
+ },
690
+ "scroll": {
691
+ "type": "boolean",
692
+ "not": {
693
+ "type": "null"
694
+ }
624
695
  },
625
696
  "block_images": {
626
697
  "type": "boolean",
@@ -634,6 +705,12 @@
634
705
  "type": "null"
635
706
  }
636
707
  },
708
+ "block_trackers": {
709
+ "type": "boolean",
710
+ "not": {
711
+ "type": "null"
712
+ }
713
+ },
637
714
  "wait_for_complete_page_load": {
638
715
  "type": "boolean",
639
716
  "not": {
@@ -655,20 +732,42 @@
655
732
  "minLength": 1
656
733
  },
657
734
  "window_size": {
658
- "type": "array",
659
- "items": {
660
- "minLength": 2,
661
- "maxLength": 2,
662
- "type": "integer",
663
- "exclusiveMinimum": 0
664
- }
735
+ "type": "object",
736
+ "properties": {
737
+ "width": {
738
+ "type": "integer",
739
+ "not": {
740
+ "type": "null"
741
+ },
742
+ "exclusiveMinimum": 0
743
+ },
744
+ "height": {
745
+ "type": "integer",
746
+ "not": {
747
+ "type": "null"
748
+ },
749
+ "exclusiveMinimum": 0
750
+ }
751
+ },
752
+ "required": [
753
+ "width",
754
+ "height"
755
+ ],
756
+ "additionalProperties": false
665
757
  },
666
758
  "lang": {
667
759
  "type": "string",
668
760
  "minLength": 1
761
+ },
762
+ "cookies": {
763
+ "type": "object"
764
+ },
765
+ "headers": {
766
+ "type": "object"
669
767
  }
670
768
  },
671
- "required": []
769
+ "required": [],
770
+ "additionalProperties": false
672
771
  },
673
772
  "local_file_path": {
674
773
  "type": "string",
@@ -692,5 +791,240 @@
692
791
  "auto_source"
693
792
  ]
694
793
  }
695
- ]
794
+ ],
795
+ "$defs": {
796
+ "post_processors": {
797
+ "gsub": {
798
+ "type": "object",
799
+ "title": "gsub",
800
+ "description": "Replace matches of `pattern` in the extracted string with `replacement` (Ruby String#gsub; pattern may be a regexp-like string).",
801
+ "examples": [
802
+ {
803
+ "name": "gsub",
804
+ "pattern": "boo",
805
+ "replacement": "baz"
806
+ }
807
+ ],
808
+ "properties": {
809
+ "name": {
810
+ "type": "string",
811
+ "const": "gsub"
812
+ },
813
+ "pattern": {
814
+ "type": "string"
815
+ },
816
+ "replacement": {
817
+ "type": "string"
818
+ }
819
+ },
820
+ "required": [
821
+ "name",
822
+ "pattern",
823
+ "replacement"
824
+ ],
825
+ "additionalProperties": true
826
+ },
827
+ "html_to_markdown": {
828
+ "type": "object",
829
+ "title": "html_to_markdown",
830
+ "description": "Sanitize HTML then convert it to Markdown (via ReverseMarkdown).",
831
+ "examples": [
832
+ {
833
+ "name": "html_to_markdown"
834
+ }
835
+ ],
836
+ "properties": {
837
+ "name": {
838
+ "type": "string",
839
+ "const": "html_to_markdown"
840
+ }
841
+ },
842
+ "required": [
843
+ "name"
844
+ ],
845
+ "additionalProperties": true
846
+ },
847
+ "markdown_to_html": {
848
+ "type": "object",
849
+ "title": "markdown_to_html",
850
+ "description": "Convert Markdown to HTML (Kramdown) and sanitize the result. Often chained after `template`.",
851
+ "examples": [
852
+ {
853
+ "name": "markdown_to_html"
854
+ }
855
+ ],
856
+ "properties": {
857
+ "name": {
858
+ "type": "string",
859
+ "const": "markdown_to_html"
860
+ }
861
+ },
862
+ "required": [
863
+ "name"
864
+ ],
865
+ "additionalProperties": true
866
+ },
867
+ "parse_time": {
868
+ "type": "object",
869
+ "title": "parse_time",
870
+ "description": "Parse a time string with Time.parse and return RFC822, using the channel `time_zone`.",
871
+ "examples": [
872
+ {
873
+ "name": "parse_time"
874
+ }
875
+ ],
876
+ "properties": {
877
+ "name": {
878
+ "type": "string",
879
+ "const": "parse_time"
880
+ }
881
+ },
882
+ "required": [
883
+ "name"
884
+ ],
885
+ "additionalProperties": true
886
+ },
887
+ "parse_uri": {
888
+ "type": "object",
889
+ "title": "parse_uri",
890
+ "description": "Normalize a URL string; resolve relative URLs against the channel URL.",
891
+ "examples": [
892
+ {
893
+ "name": "parse_uri"
894
+ }
895
+ ],
896
+ "properties": {
897
+ "name": {
898
+ "type": "string",
899
+ "const": "parse_uri"
900
+ }
901
+ },
902
+ "required": [
903
+ "name"
904
+ ],
905
+ "additionalProperties": true
906
+ },
907
+ "sanitize_html": {
908
+ "type": "object",
909
+ "title": "sanitize_html",
910
+ "description": "Sanitize HTML (sanitize gem RELAXED plus html2rss defaults: absolute URLs, safe link/img attributes, wrap lone images in anchors).",
911
+ "examples": [
912
+ {
913
+ "name": "sanitize_html"
914
+ }
915
+ ],
916
+ "properties": {
917
+ "name": {
918
+ "type": "string",
919
+ "const": "sanitize_html"
920
+ }
921
+ },
922
+ "required": [
923
+ "name"
924
+ ],
925
+ "additionalProperties": true
926
+ },
927
+ "substring": {
928
+ "type": "object",
929
+ "title": "substring",
930
+ "description": "Return a slice of the extracted string using Integer `start` and optional `end` (Ruby String#[] range semantics; end may be omitted).",
931
+ "examples": [
932
+ {
933
+ "name": "substring",
934
+ "start": 4,
935
+ "end": 6
936
+ }
937
+ ],
938
+ "properties": {
939
+ "name": {
940
+ "type": "string",
941
+ "const": "substring"
942
+ },
943
+ "start": {
944
+ "type": "integer"
945
+ },
946
+ "end": {
947
+ "type": "integer"
948
+ }
949
+ },
950
+ "required": [
951
+ "name",
952
+ "start"
953
+ ],
954
+ "additionalProperties": true
955
+ },
956
+ "template": {
957
+ "type": "object",
958
+ "title": "template",
959
+ "description": "Format a string with Kernel#format-style placeholders (`%{key}` / `%<key>s`). `%{self}` is the current selector value; other keys resolve sibling selectors.",
960
+ "examples": [
961
+ {
962
+ "name": "template",
963
+ "string": "`%{self}` (`%{price}`)"
964
+ }
965
+ ],
966
+ "properties": {
967
+ "name": {
968
+ "type": "string",
969
+ "const": "template"
970
+ },
971
+ "string": {
972
+ "type": "string"
973
+ }
974
+ },
975
+ "required": [
976
+ "name",
977
+ "string"
978
+ ],
979
+ "additionalProperties": true
980
+ }
981
+ },
982
+ "extractors": {
983
+ "attribute": {
984
+ "type": "string",
985
+ "const": "attribute",
986
+ "title": "attribute",
987
+ "description": "Return the value of an HTML attribute on the selected element. Requires sibling selector option `attribute` (attribute name).",
988
+ "examples": [
989
+ "attribute"
990
+ ]
991
+ },
992
+ "href": {
993
+ "type": "string",
994
+ "const": "href",
995
+ "title": "href",
996
+ "description": "Return the absolute URL from the selected element's `href` attribute (relative hrefs are resolved against the channel URL).",
997
+ "examples": [
998
+ "href"
999
+ ]
1000
+ },
1001
+ "html": {
1002
+ "type": "string",
1003
+ "const": "html",
1004
+ "title": "html",
1005
+ "description": "Return the outer HTML of the selected element. Sanitize during post-processing (e.g. `sanitize_html`).",
1006
+ "examples": [
1007
+ "html"
1008
+ ]
1009
+ },
1010
+ "static": {
1011
+ "type": "string",
1012
+ "const": "static",
1013
+ "title": "static",
1014
+ "description": "Return a fixed value from sibling selector option `static` (no DOM read).",
1015
+ "examples": [
1016
+ "static"
1017
+ ]
1018
+ },
1019
+ "text": {
1020
+ "type": "string",
1021
+ "const": "text",
1022
+ "title": "text",
1023
+ "description": "Return collapsed visible text of the selected element (default extractor).",
1024
+ "examples": [
1025
+ "text"
1026
+ ]
1027
+ }
1028
+ }
1029
+ }
696
1030
  }