UrlCategorise 0.1.9 → 0.1.11

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c22ada722efc33979930ece5d8737625943f1d815d2c7acdbfbc0f5b0fede15f
4
- data.tar.gz: 15747c8a4b23c4c805cd74118627dfa8d102d4d813ae9f08c8c49011ff2ebad8
3
+ metadata.gz: 6ed2176041a871f6fae72dc1b7c1f266910f6233879e1359d02eead164f8f6ff
4
+ data.tar.gz: 5ff46c4dd9faaa22218a86d7b40f54bedaa6f91e9914b8e9671b14ba55ec6495
5
5
  SHA512:
6
- metadata.gz: b1c21b59b8a7631c4c39f206fb37bb9c51c1925a544ddd5f8a627b595dafa92d366d880a33c6303eafc477ae9be4130055af060953e27617e5ce198618e5f9bd
7
- data.tar.gz: 1621c52578daf8957546c778125df120f9480dbe2f2d365feb6024890ddfe5affac3aae77100047ea46b15c96939db1fc228b876806197c63dda954199536bc9
6
+ metadata.gz: 71aa116bb3de0c269ccaf18a3f159f78c722ab010589a2118c411f826f1ff4de48b9e5ed8c9936a67a6eac3cb17b3f52d71e41bac552e73a91c67edee804f9c4
7
+ data.tar.gz: 4c2d8f50a9cad1b1f32c56ddc51b44e8ca28f49af5718970bb0bea02a12e934cb2c8075589fa9ca8db56437e3a377a5e0cc1afe7a30e44aab5b393cb94a70f3a
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- UrlCategorise (0.1.9)
4
+ UrlCategorise (0.1.10)
5
5
  active_attr (>= 0.17.1, < 1.0)
6
6
  api_pattern (>= 0.0.6, < 1.0)
7
7
  csv (>= 3.3.0, < 4.0)
@@ -18,9 +18,9 @@ PATH
18
18
  GEM
19
19
  remote: https://rubygems.org/
20
20
  specs:
21
- actionpack (8.0.2.1)
22
- actionview (= 8.0.2.1)
23
- activesupport (= 8.0.2.1)
21
+ actionpack (8.1.3)
22
+ actionview (= 8.1.3)
23
+ activesupport (= 8.1.3)
24
24
  nokogiri (>= 1.8.5)
25
25
  rack (>= 2.2.4)
26
26
  rack-session (>= 1.0.1)
@@ -28,37 +28,37 @@ GEM
28
28
  rails-dom-testing (~> 2.2)
29
29
  rails-html-sanitizer (~> 1.6)
30
30
  useragent (~> 0.16)
31
- actionview (8.0.2.1)
32
- activesupport (= 8.0.2.1)
31
+ actionview (8.1.3)
32
+ activesupport (= 8.1.3)
33
33
  builder (~> 3.1)
34
34
  erubi (~> 1.11)
35
35
  rails-dom-testing (~> 2.2)
36
36
  rails-html-sanitizer (~> 1.6)
37
- active_attr (0.17.1)
38
- actionpack (>= 3.0.2, < 8.1)
39
- activemodel (>= 3.0.2, < 8.1)
40
- activesupport (>= 3.0.2, < 8.1)
41
- activemodel (8.0.2.1)
42
- activesupport (= 8.0.2.1)
43
- activerecord (8.0.2.1)
44
- activemodel (= 8.0.2.1)
45
- activesupport (= 8.0.2.1)
37
+ active_attr (0.17.2)
38
+ actionpack (>= 3.0.2, < 8.2)
39
+ activemodel (>= 3.0.2, < 8.2)
40
+ activesupport (>= 3.0.2, < 8.2)
41
+ activemodel (8.1.3)
42
+ activesupport (= 8.1.3)
43
+ activerecord (8.1.3)
44
+ activemodel (= 8.1.3)
45
+ activesupport (= 8.1.3)
46
46
  timeout (>= 0.4.0)
47
- activesupport (8.0.2.1)
47
+ activesupport (8.1.3)
48
48
  base64
49
- benchmark (>= 0.3)
50
49
  bigdecimal
51
50
  concurrent-ruby (~> 1.0, >= 1.3.1)
52
51
  connection_pool (>= 2.2.5)
53
52
  drb
54
53
  i18n (>= 1.6, < 2)
54
+ json
55
55
  logger (>= 1.4.2)
56
56
  minitest (>= 5.1)
57
57
  securerandom (>= 0.3)
58
58
  tzinfo (~> 2.0, >= 2.0.5)
59
59
  uri (>= 0.13.1)
60
- addressable (2.8.7)
61
- public_suffix (>= 2.0.2, < 7.0)
60
+ addressable (2.9.0)
61
+ public_suffix (>= 2.0.2, < 8.0)
62
62
  ansi (1.5.0)
63
63
  api_pattern (0.0.6)
64
64
  active_attr (>= 0.15.4)
@@ -67,15 +67,14 @@ GEM
67
67
  nokogiri (>= 1.16.0)
68
68
  ast (2.4.3)
69
69
  base64 (0.3.0)
70
- benchmark (0.4.1)
71
- bigdecimal (4.1.0)
70
+ bigdecimal (4.1.2)
72
71
  builder (3.3.0)
73
72
  bundler-audit (0.9.3)
74
73
  bundler (>= 1.2.0)
75
74
  thor (~> 1.0)
76
75
  coderay (1.1.3)
77
- concurrent-ruby (1.3.5)
78
- connection_pool (2.5.3)
76
+ concurrent-ruby (1.3.6)
77
+ connection_pool (3.0.2)
79
78
  crack (1.0.0)
80
79
  bigdecimal
81
80
  rexml
@@ -91,10 +90,10 @@ GEM
91
90
  csv
92
91
  mini_mime (>= 1.0.0)
93
92
  multi_xml (>= 0.5.2)
94
- i18n (1.14.7)
93
+ i18n (1.14.8)
95
94
  concurrent-ruby (~> 1.0)
96
95
  io-console (0.8.1)
97
- json (2.13.2)
96
+ json (2.19.5)
98
97
  kaggle (0.0.3)
99
98
  csv (>= 3.3)
100
99
  fileutils (>= 1.7)
@@ -104,7 +103,7 @@ GEM
104
103
  language_server-protocol (3.17.0.5)
105
104
  lint_roller (1.1.0)
106
105
  logger (1.7.0)
107
- loofah (2.24.1)
106
+ loofah (2.25.1)
108
107
  crass (~> 1.0.2)
109
108
  nokogiri (>= 1.12.0)
110
109
  method_source (1.1.0)
@@ -121,7 +120,7 @@ GEM
121
120
  ruby2_keywords (>= 0.0.5)
122
121
  multi_xml (0.8.1)
123
122
  bigdecimal (>= 3.1, < 5)
124
- nokogiri (1.19.2-arm64-darwin)
123
+ nokogiri (1.19.3-arm64-darwin)
125
124
  racc (~> 1.4)
126
125
  oj (3.16.11)
127
126
  bigdecimal (>= 3.0)
@@ -135,10 +134,10 @@ GEM
135
134
  pry (0.15.2)
136
135
  coderay (~> 1.1)
137
136
  method_source (~> 1.0)
138
- public_suffix (6.0.2)
137
+ public_suffix (7.0.5)
139
138
  racc (1.8.1)
140
- rack (3.2.5)
141
- rack-session (2.1.1)
139
+ rack (3.2.6)
140
+ rack-session (2.1.2)
142
141
  base64 (>= 0.1.0)
143
142
  rack (>= 3.0.0)
144
143
  rack-test (2.2.0)
@@ -147,8 +146,8 @@ GEM
147
146
  activesupport (>= 5.0.0)
148
147
  minitest
149
148
  nokogiri (>= 1.6)
150
- rails-html-sanitizer (1.6.2)
151
- loofah (~> 2.21)
149
+ rails-html-sanitizer (1.7.0)
150
+ loofah (~> 2.25)
152
151
  nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
153
152
  rainbow (3.1.1)
154
153
  rake (13.3.0)
@@ -0,0 +1,381 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'net/http'
5
+ require 'uri'
6
+ require 'json'
7
+ require 'fileutils'
8
+ require 'time'
9
+ require 'set'
10
+
11
+ module VideoListGenerator
12
+ class FocusedExtractorParser
13
+ # Focus on the most popular video hosting platforms first
14
+ PRIORITY_EXTRACTORS = [
15
+ 'youtube.py',
16
+ 'vimeo.py',
17
+ 'dailymotion.py',
18
+ 'twitch.py',
19
+ 'tiktok.py',
20
+ 'instagram.py',
21
+ 'facebook.py',
22
+ 'twitter.py',
23
+ 'reddit.py',
24
+ 'streamable.py',
25
+ 'wistia.py',
26
+ 'brightcove.py',
27
+ 'jwplatform.py',
28
+ 'kaltura.py',
29
+ 'vidyard.py',
30
+ 'vzaar.py',
31
+ 'soundcloud.py',
32
+ 'mixcloud.py',
33
+ 'bandcamp.py',
34
+ 'peertube.py',
35
+ 'rumble.py',
36
+ 'odysee.py',
37
+ 'bitchute.py'
38
+ ].freeze
39
+
40
+ YT_DLP_API_URL = 'https://api.github.com/repos/yt-dlp/yt-dlp/contents/yt_dlp/extractor'
41
+
42
+ def initialize
43
+ @video_domains = Set.new
44
+ @video_regexes = []
45
+ @manual_domains = get_manual_video_domains
46
+ end
47
+
48
+ def generate_lists
49
+ puts "🎬 Generating focused video hosting lists..."
50
+
51
+ puts "📝 Adding manually curated video hosting domains..."
52
+ @manual_domains.each { |domain| @video_domains.add(domain) }
53
+
54
+ puts "📡 Fetching priority yt-dlp extractors..."
55
+ fetch_priority_extractors
56
+
57
+ create_lists_directory
58
+ generate_hosts_file
59
+ generate_regex_file
60
+
61
+ puts "✅ Video hosting lists generated successfully!"
62
+ puts "📁 Files created in lists/ directory:"
63
+ puts " - video_hosting_domains.hosts (PiHole compatible)"
64
+ puts " - video_url_patterns.txt (Regex patterns for video detection)"
65
+ end
66
+
67
+ private
68
+
69
+ def get_manual_video_domains
70
+ # Manually curated list of major video hosting domains
71
+ [
72
+ 'youtube.com', 'youtu.be', 'youtube-nocookie.com',
73
+ 'vimeo.com', 'player.vimeo.com',
74
+ 'dailymotion.com', 'dai.ly',
75
+ 'twitch.tv', 'clips.twitch.tv',
76
+ 'tiktok.com', 'vm.tiktok.com',
77
+ 'instagram.com', 'instagr.am',
78
+ 'facebook.com', 'fb.com', 'fb.watch',
79
+ 'twitter.com', 'x.com', 't.co',
80
+ 'reddit.com', 'redd.it', 'v.redd.it',
81
+ 'streamable.com',
82
+ 'wistia.com', 'fast.wistia.com', 'fast.wistia.net',
83
+ 'brightcove.com', 'bcove.video',
84
+ 'jwplatform.com', 'jwpcdn.com',
85
+ 'kaltura.com',
86
+ 'vidyard.com',
87
+ 'vzaar.com',
88
+ 'soundcloud.com', 'snd.sc',
89
+ 'mixcloud.com',
90
+ 'bandcamp.com',
91
+ 'rumble.com',
92
+ 'odysee.com', 'lbry.tv',
93
+ 'bitchute.com',
94
+ 'peertube.tv',
95
+ 'archive.org',
96
+ 'vevo.com',
97
+ 'liveleak.com',
98
+ 'metacafe.com',
99
+ 'veoh.com',
100
+ 'break.com',
101
+ 'crunchyroll.com',
102
+ 'funimation.com',
103
+ 'vrv.co',
104
+ 'roosterteeth.com',
105
+ 'newgrounds.com',
106
+ 'coub.com',
107
+ 'vidme.com',
108
+ 'vid.me',
109
+ 'gfycat.com',
110
+ 'imgur.com',
111
+ 'giphy.com',
112
+ 'tenor.com'
113
+ ]
114
+ end
115
+
116
+ def fetch_priority_extractors
117
+ uri = URI(YT_DLP_API_URL)
118
+ response = Net::HTTP.get_response(uri)
119
+
120
+ unless response.code == '200'
121
+ puts "⚠️ Failed to fetch extractor list from GitHub API, using manual domains only"
122
+ return
123
+ end
124
+
125
+ files = JSON.parse(response.body)
126
+
127
+ # Filter to priority extractor files
128
+ priority_files = files.select { |file| PRIORITY_EXTRACTORS.include?(file['name']) }
129
+
130
+ puts "📝 Processing #{priority_files.length} priority extractor files..."
131
+
132
+ priority_files.each_with_index do |file, index|
133
+ puts "Processing #{index + 1}/#{priority_files.length}: #{file['name']}"
134
+ process_extractor_file(file)
135
+ end
136
+
137
+ puts "🎯 Found #{@video_domains.size} unique video hosting domains"
138
+ puts "🔍 Extracted #{@video_regexes.size} URL pattern regexes"
139
+ end
140
+
141
+ def process_extractor_file(file_info)
142
+ begin
143
+ response = Net::HTTP.get_response(URI(file_info['download_url']))
144
+ return unless response.code == '200'
145
+
146
+ content = response.body
147
+
148
+ extract_domains_from_content(content, file_info['name'])
149
+ extract_regexes_from_content(content, file_info['name'])
150
+ rescue StandardError => e
151
+ puts "⚠️ Error processing #{file_info['name']}: #{e.message}"
152
+ end
153
+ end
154
+
155
+ def extract_domains_from_content(content, filename)
156
+ # Look for _VALID_URL patterns
157
+ valid_url_patterns = content.scan(/_VALID_URL\s*=\s*r?['"]([^'"]+)['"]/m)
158
+
159
+ valid_url_patterns.each do |pattern|
160
+ pattern = pattern[0] if pattern.is_a?(Array)
161
+
162
+ domains = extract_domains_from_regex(pattern)
163
+ domains.each { |domain| @video_domains.add(domain) }
164
+ end
165
+
166
+ # Also look for explicit domain lists or IE_NAME patterns
167
+ ie_name_patterns = content.scan(/IE_NAME\s*=\s*['"]([^'"]+)['"]/m)
168
+ ie_name_patterns.each do |name|
169
+ name = name[0] if name.is_a?(Array)
170
+ next unless name
171
+
172
+ # Many IE_NAME values correspond to domain names
173
+ if name.include?('.') && valid_domain?(name)
174
+ @video_domains.add(name.downcase)
175
+ end
176
+ end
177
+ end
178
+
179
+ def extract_regexes_from_content(content, filename)
180
+ # Extract _VALID_URL patterns for video detection
181
+ valid_url_patterns = content.scan(/_VALID_URL\s*=\s*r?['"]([^'"]+)['"]/m)
182
+
183
+ valid_url_patterns.each do |pattern|
184
+ pattern = pattern[0] if pattern.is_a?(Array)
185
+
186
+ cleaned_pattern = clean_regex_pattern(pattern)
187
+ if cleaned_pattern && !cleaned_pattern.empty?
188
+ @video_regexes << {
189
+ source: filename.gsub('.py', ''),
190
+ pattern: cleaned_pattern,
191
+ original: pattern,
192
+ description: generate_pattern_description(pattern, filename)
193
+ }
194
+ end
195
+ end
196
+ end
197
+
198
+ def extract_domains_from_regex(pattern)
199
+ domains = Set.new
200
+
201
+ # Various patterns to extract domains from regex
202
+ # Pattern: https?://(?:www\.)?domain\.com
203
+ domain_matches = pattern.scan(/https?:\/\/[^\/]*?([a-zA-Z0-9-]+\.[a-zA-Z]{2,})/i)
204
+ domain_matches.each do |match|
205
+ domain = match[0] if match.is_a?(Array)
206
+ domain = clean_domain(domain) if domain
207
+ domains.add(domain.downcase) if domain && valid_domain?(domain)
208
+ end
209
+
210
+ # Pattern: (?:www\.)?domain\.com
211
+ simple_matches = pattern.scan(/(?:\(\?\:www\\\.\)\?)?([a-zA-Z0-9-]+\.[a-zA-Z]{2,})/i)
212
+ simple_matches.each do |match|
213
+ domain = match[0] if match.is_a?(Array)
214
+ domain = clean_domain(domain) if domain
215
+ domains.add(domain.downcase) if domain && valid_domain?(domain)
216
+ end
217
+
218
+ domains.to_a
219
+ end
220
+
221
+ def clean_domain(domain)
222
+ return nil unless domain
223
+
224
+ # Remove common regex escapes
225
+ cleaned = domain.gsub(/\\\./, '.').gsub(/[\(\)\?\:\|\\]/, '')
226
+ cleaned = cleaned.strip
227
+
228
+ # Remove any trailing regex syntax
229
+ cleaned = cleaned.split(/[\(\)\[\]]/)[0] if cleaned.include?('(') || cleaned.include?('[')
230
+
231
+ cleaned
232
+ end
233
+
234
+ def clean_regex_pattern(pattern)
235
+ return nil unless pattern
236
+
237
+ # Simplify common patterns for practical use
238
+ cleaned = pattern.dup
239
+
240
+ # Remove complex lookaheads/lookbehinds
241
+ cleaned = cleaned.gsub(/\(\?\![^)]*\)/, '')
242
+ cleaned = cleaned.gsub(/\(\?\<\![^)]*\)/, '')
243
+
244
+ # Simplify common patterns
245
+ cleaned = cleaned.gsub(/\(\?\:www\\\.\)\?/, '(?:www\.)?')
246
+ cleaned = cleaned.gsub(/\\\./, '\.')
247
+
248
+ # Validate that it's a reasonable regex
249
+ begin
250
+ Regexp.new(cleaned)
251
+ cleaned
252
+ rescue RegexpError
253
+ # Return a simplified version if the complex regex fails
254
+ extract_simple_pattern(pattern)
255
+ end
256
+ end
257
+
258
+ def extract_simple_pattern(pattern)
259
+ # Extract a simple domain pattern from complex regex
260
+ if pattern.include?('https?://')
261
+ # Extract the basic domain part
262
+ match = pattern.match(/https?:\/\/[^\/]*?([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/i)
263
+ if match
264
+ return "https?://[^/]*#{Regexp.escape(match[1])}"
265
+ end
266
+ end
267
+
268
+ nil
269
+ end
270
+
271
+ def generate_pattern_description(pattern, filename)
272
+ service = filename.gsub('.py', '').capitalize
273
+
274
+ case filename
275
+ when 'youtube.py'
276
+ 'YouTube video, playlist, and channel URLs'
277
+ when 'vimeo.py'
278
+ 'Vimeo video and album URLs'
279
+ when 'twitch.py'
280
+ 'Twitch streams, clips, and VODs'
281
+ when 'tiktok.py'
282
+ 'TikTok video URLs'
283
+ else
284
+ "#{service} video content URLs"
285
+ end
286
+ end
287
+
288
+ def valid_domain?(domain)
289
+ return false unless domain
290
+ return false if domain.length < 4
291
+ return false unless domain.match?(/^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/)
292
+ return false if domain.include?('example.') || domain.include?('test.')
293
+ return false if domain.include?('localhost')
294
+ true
295
+ end
296
+
297
+ def create_lists_directory
298
+ FileUtils.mkdir_p('lists')
299
+ end
300
+
301
+ def generate_hosts_file
302
+ hosts_file_path = 'lists/video_hosting_domains.hosts'
303
+
304
+ File.open(hosts_file_path, 'w') do |file|
305
+ write_hosts_header(file)
306
+
307
+ @video_domains.sort.each do |domain|
308
+ file.puts "0.0.0.0 #{domain}"
309
+ file.puts "0.0.0.0 www.#{domain}" unless domain.start_with?('www.')
310
+ end
311
+ end
312
+
313
+ puts "📝 Generated hosts file: #{hosts_file_path} (#{@video_domains.size} domains)"
314
+ end
315
+
316
+ def generate_regex_file
317
+ regex_file_path = 'lists/video_url_patterns.txt'
318
+
319
+ File.open(regex_file_path, 'w') do |file|
320
+ write_regex_header(file)
321
+
322
+ @video_regexes.each do |regex_info|
323
+ file.puts "# Source: #{regex_info[:source]}"
324
+ file.puts "# Description: #{regex_info[:description]}"
325
+ file.puts "# Pattern: #{regex_info[:pattern]}"
326
+ file.puts "# Original: #{regex_info[:original]}"
327
+ file.puts regex_info[:pattern]
328
+ file.puts ""
329
+ end
330
+ end
331
+
332
+ puts "📝 Generated regex file: #{regex_file_path} (#{@video_regexes.size} patterns)"
333
+ end
334
+
335
+ def write_hosts_header(file)
336
+ file.puts "# Video Hosting Domains - PiHole Compatible Hosts File"
337
+ file.puts "# Generated on: #{Time.now.strftime('%Y-%m-%d %H:%M:%S UTC')}"
338
+ file.puts "# Source: yt-dlp extractors + manual curation"
339
+ file.puts "# Purpose: Block or categorize video hosting websites"
340
+ file.puts "# Format: 0.0.0.0 domain.com"
341
+ file.puts "#"
342
+ file.puts "# This file contains domains from popular video hosting platforms"
343
+ file.puts "# extracted from yt-dlp video extractors and manually curated lists."
344
+ file.puts "# Use this for URL categorization or content filtering."
345
+ file.puts "#"
346
+ file.puts "# Generated by UrlCategorise gem: https://github.com/trex22/url_categorise"
347
+ file.puts "#"
348
+ file.puts ""
349
+ end
350
+
351
+ def write_regex_header(file)
352
+ file.puts "# Video URL Detection Patterns"
353
+ file.puts "# Generated on: #{Time.now.strftime('%Y-%m-%d %H:%M:%S UTC')}"
354
+ file.puts "# Source: yt-dlp extractors + manual curation"
355
+ file.puts "# Purpose: Regex patterns to detect video URLs vs other content"
356
+ file.puts "#"
357
+ file.puts "# These patterns help distinguish between:"
358
+ file.puts "# - Direct video content URLs (matches these patterns)"
359
+ file.puts "# - Homepage, playlist, user profile, community content URLs"
360
+ file.puts "#"
361
+ file.puts "# Usage: Use these patterns to categorize URLs from video hosting domains"
362
+ file.puts "# to determine if they contain actual video content or other resources"
363
+ file.puts "#"
364
+ file.puts "# Each pattern includes:"
365
+ file.puts "# - Source extractor name"
366
+ file.puts "# - Human-readable description"
367
+ file.puts "# - Simplified regex pattern"
368
+ file.puts "# - Original complex pattern (for reference)"
369
+ file.puts "#"
370
+ file.puts "# Generated by UrlCategorise gem: https://github.com/trex22/url_categorise"
371
+ file.puts "#"
372
+ file.puts ""
373
+ end
374
+ end
375
+ end
376
+
377
+ # Run the generator if this file is executed directly
378
+ if __FILE__ == $0
379
+ generator = VideoListGenerator::FocusedExtractorParser.new
380
+ generator.generate_lists
381
+ end
@@ -101,10 +101,16 @@ module UrlCategorise
101
101
  end
102
102
 
103
103
  def categorise(url)
104
+ return [] if url.nil? || (url.respond_to?(:strip) && url.strip.empty?)
105
+
104
106
  debug_time("Categorizing '#{url}'") do
105
107
  debug_log("🔍 Starting categorization for URL: #{url}")
106
108
 
107
- host = (URI.parse(url).host || url).downcase
109
+ host = begin
110
+ (URI.parse(url).host || url).downcase
111
+ rescue URI::InvalidURIError
112
+ url.to_s.downcase
113
+ end
108
114
  host = host.gsub("www.", "")
109
115
  debug_log("📌 Extracted host: #{host}")
110
116
 
@@ -1318,7 +1324,7 @@ module UrlCategorise
1318
1324
  # Phase 2: Process content in parallel using Ractors (if available) or threads
1319
1325
  debug_log("⚡ Phase 2: Processing content in parallel")
1320
1326
 
1321
- if self.class.ractor_available?
1327
+ if self.class.ractor_available? && !test_environment?
1322
1328
  process_content_with_ractors(downloaded_content)
1323
1329
  else
1324
1330
  process_content_with_threads(downloaded_content)
@@ -1332,9 +1338,10 @@ module UrlCategorise
1332
1338
  original_verbose = $VERBOSE
1333
1339
  $VERBOSE = nil
1334
1340
  work_queue = Ractor.new do
1341
+ # :nocov:
1335
1342
  tasks = []
1336
1343
  no_more_tasks = false
1337
-
1344
+
1338
1345
  while (task = Ractor.receive) != :done
1339
1346
  if task == :get_task
1340
1347
  if tasks.empty? && no_more_tasks
@@ -1348,6 +1355,7 @@ module UrlCategorise
1348
1355
  tasks << task
1349
1356
  end
1350
1357
  end
1358
+ # :nocov:
1351
1359
  end
1352
1360
 
1353
1361
  # Add all tasks to queue
@@ -1364,6 +1372,7 @@ module UrlCategorise
1364
1372
 
1365
1373
  workers = num_workers.times.map do
1366
1374
  Ractor.new(work_queue, cache_dir) do |queue, cache_path|
1375
+ # :nocov:
1367
1376
  require "digest"
1368
1377
  require "fileutils"
1369
1378
 
@@ -1433,6 +1442,7 @@ module UrlCategorise
1433
1442
  end
1434
1443
 
1435
1444
  results
1445
+ # :nocov:
1436
1446
  end
1437
1447
  end
1438
1448
 
@@ -1585,9 +1595,11 @@ module UrlCategorise
1585
1595
  end
1586
1596
 
1587
1597
  def extract_host(url)
1598
+ return nil if url.nil? || (url.respond_to?(:strip) && url.strip.empty?)
1599
+
1588
1600
  (URI.parse(url).host || url).downcase.gsub("www.", "")
1589
1601
  rescue URI::InvalidURIError
1590
- url.downcase.gsub("www.", "")
1602
+ url.to_s.downcase.gsub("www.", "")
1591
1603
  end
1592
1604
 
1593
1605
  def build_host_data(urls)
@@ -1803,7 +1815,7 @@ module UrlCategorise
1803
1815
  return true if url.start_with?("file://")
1804
1816
 
1805
1817
  uri = URI.parse(url)
1806
- uri.is_a?(URI::HTTP) && !uri.host.nil?
1818
+ uri.is_a?(URI::HTTP) && !uri.host.nil? && !uri.host.empty?
1807
1819
  rescue URI::InvalidURIError
1808
1820
  false
1809
1821
  end
@@ -262,10 +262,16 @@ module UrlCategorise
262
262
 
263
263
  def extract_zip_file(zip_file_path, extract_to_dir)
264
264
  FileUtils.mkdir_p(extract_to_dir)
265
+ base_dir = File.expand_path(extract_to_dir)
265
266
 
266
267
  Zip::File.open(zip_file_path) do |zip_file|
267
268
  zip_file.each do |entry|
268
- extract_path = File.join(extract_to_dir, entry.name)
269
+ extract_path = File.expand_path(File.join(base_dir, entry.name))
270
+
271
+ # Reject entries that would be written outside the extraction directory (zip-slip)
272
+ unless extract_path.start_with?("#{base_dir}#{File::SEPARATOR}")
273
+ raise Error, "Unsafe path in zip file: #{entry.name}"
274
+ end
269
275
 
270
276
  if entry.directory?
271
277
  FileUtils.mkdir_p(extract_path)
@@ -1,3 +1,3 @@
1
1
  module UrlCategorise
2
- VERSION = "0.1.9"
2
+ VERSION = "0.1.11"
3
3
  end
@@ -1,5 +1,5 @@
1
1
  # Blogging Platform Domains - PiHole Compatible Hosts File
2
- # Generated on: 2026-02-15 21:10:15 UTC
2
+ # Generated on: 2026-05-22 11:06:15 UTC
3
3
  # Sources:
4
4
  # - Mr.Holmes OSINT tool (https://github.com/Lucksi/Mr.Holmes)
5
5
  # - Manually curated domain lists
@@ -1,5 +1,5 @@
1
1
  # Cryptocurrency Platform Domains - PiHole Compatible Hosts File
2
- # Generated on: 2026-02-15 21:10:15 UTC
2
+ # Generated on: 2026-05-22 11:06:15 UTC
3
3
  # Sources:
4
4
  # - Mr.Holmes OSINT tool (https://github.com/Lucksi/Mr.Holmes)
5
5
  # - Manually curated domain lists
@@ -1,5 +1,5 @@
1
1
  # Developer Platform Domains - PiHole Compatible Hosts File
2
- # Generated on: 2026-02-15 21:10:15 UTC
2
+ # Generated on: 2026-05-22 11:06:15 UTC
3
3
  # Sources:
4
4
  # - Mr.Holmes OSINT tool (https://github.com/Lucksi/Mr.Holmes)
5
5
  # - Manually curated domain lists
@@ -1,5 +1,5 @@
1
1
  # Forum and Q&A Domains - PiHole Compatible Hosts File
2
- # Generated on: 2026-02-15 21:10:15 UTC
2
+ # Generated on: 2026-05-22 11:06:15 UTC
3
3
  # Sources:
4
4
  # - Mr.Holmes OSINT tool (https://github.com/Lucksi/Mr.Holmes)
5
5
  # - Manually curated domain lists
@@ -1,5 +1,5 @@
1
1
  # Gaming Platform Domains - PiHole Compatible Hosts File
2
- # Generated on: 2026-02-15 21:10:15 UTC
2
+ # Generated on: 2026-05-22 11:06:15 UTC
3
3
  # Sources:
4
4
  # - Mr.Holmes OSINT tool (https://github.com/Lucksi/Mr.Holmes)
5
5
  # - Manually curated domain lists
@@ -1,5 +1,5 @@
1
1
  # Messaging Platform Domains - PiHole Compatible Hosts File
2
- # Generated on: 2026-02-15 21:10:15 UTC
2
+ # Generated on: 2026-05-22 11:06:15 UTC
3
3
  # Sources:
4
4
  # - Mr.Holmes OSINT tool (https://github.com/Lucksi/Mr.Holmes)
5
5
  # - Manually curated domain lists
@@ -1,5 +1,5 @@
1
1
  # Music Platform Domains - PiHole Compatible Hosts File
2
- # Generated on: 2026-02-15 21:10:15 UTC
2
+ # Generated on: 2026-05-22 11:06:15 UTC
3
3
  # Sources:
4
4
  # - Mr.Holmes OSINT tool (https://github.com/Lucksi/Mr.Holmes)
5
5
  # - Manually curated domain lists
@@ -1,5 +1,5 @@
1
1
  # Security Community Domains - PiHole Compatible Hosts File
2
- # Generated on: 2026-02-15 21:10:15 UTC
2
+ # Generated on: 2026-05-22 11:06:15 UTC
3
3
  # Sources:
4
4
  # - Mr.Holmes OSINT tool (https://github.com/Lucksi/Mr.Holmes)
5
5
  # - Manually curated domain lists
@@ -1,5 +1,5 @@
1
1
  # Social Media Domains - PiHole Compatible Hosts File
2
- # Generated on: 2026-02-15 21:10:27 UTC
2
+ # Generated on: 2026-05-22 11:06:06 UTC
3
3
  # Sources:
4
4
  # - Mr.Holmes OSINT tool (https://github.com/Lucksi/Mr.Holmes)
5
5
  # - Manually curated social media platform domains
@@ -1,5 +1,5 @@
1
1
  # Streaming Platform Domains - PiHole Compatible Hosts File
2
- # Generated on: 2026-02-15 21:10:15 UTC
2
+ # Generated on: 2026-05-22 11:06:15 UTC
3
3
  # Sources:
4
4
  # - Mr.Holmes OSINT tool (https://github.com/Lucksi/Mr.Holmes)
5
5
  # - Manually curated domain lists
@@ -1,5 +1,5 @@
1
1
  # Video Hosting Domains - PiHole Compatible Hosts File
2
- # Generated on: 2026-02-15 21:17:19 UTC
2
+ # Generated on: 2026-05-22 11:05:43 UTC
3
3
  # Source: yt-dlp extractors (https://github.com/yt-dlp/yt-dlp)
4
4
  # Purpose: Block access to video hosting websites
5
5
  # Format: 0.0.0.0 domain.com
@@ -618,6 +618,8 @@
618
618
  0.0.0.0 www.api.vkplay
619
619
  0.0.0.0 api.vmh
620
620
  0.0.0.0 www.api.vmh
621
+ 0.0.0.0 api.watch
622
+ 0.0.0.0 www.api.watch
621
623
  0.0.0.0 api.whowatch
622
624
  0.0.0.0 www.api.whowatch
623
625
  0.0.0.0 api.zype
@@ -704,6 +706,8 @@
704
706
  0.0.0.0 www.ardaudiothek.de
705
707
  0.0.0.0 ardmediathek.de
706
708
  0.0.0.0 www.ardmediathek.de
709
+ 0.0.0.0 ardsounds.de
710
+ 0.0.0.0 www.ardsounds.de
707
711
  0.0.0.0 arhiiv.err
708
712
  0.0.0.0 www.arhiiv.err
709
713
  0.0.0.0 armed-f.akamaihd
@@ -1168,6 +1172,8 @@
1168
1172
  0.0.0.0 www.capitaltv.cy
1169
1173
  0.0.0.0 caracoltv.com
1170
1174
  0.0.0.0 www.caracoltv.com
1175
+ 0.0.0.0 cas.global
1176
+ 0.0.0.0 www.cas.global
1171
1177
  0.0.0.0 catalogue.pr
1172
1178
  0.0.0.0 www.catalogue.pr
1173
1179
  0.0.0.0 cbc.ca
@@ -1258,6 +1264,8 @@
1258
1264
  0.0.0.0 www.cdn.syndication
1259
1265
  0.0.0.0 cdn.teamcococdn
1260
1266
  0.0.0.0 www.cdn.teamcococdn
1267
+ 0.0.0.0 cdn.thechosen
1268
+ 0.0.0.0 www.cdn.thechosen
1261
1269
  0.0.0.0 cdn.vidyard
1262
1270
  0.0.0.0 www.cdn.vidyard
1263
1271
  0.0.0.0 cdn.viqeo
@@ -1304,8 +1312,6 @@
1304
1312
  0.0.0.0 www.cgtn.com
1305
1313
  0.0.0.0 ch.nicovideo
1306
1314
  0.0.0.0 www.ch.nicovideo
1307
- 0.0.0.0 ch.sooplive
1308
- 0.0.0.0 www.ch.sooplive
1309
1315
  0.0.0.0 channel.microcms
1310
1316
  0.0.0.0 www.channel.microcms
1311
1317
  0.0.0.0 channel4.com
@@ -2204,8 +2210,6 @@
2204
2210
  0.0.0.0 www.feeds.cbsn
2205
2211
  0.0.0.0 feeds.rasset
2206
2212
  0.0.0.0 www.feeds.rasset
2207
- 0.0.0.0 feeds.video
2208
- 0.0.0.0 www.feeds.video
2209
2213
  0.0.0.0 felixcosm.tumblr
2210
2214
  0.0.0.0 www.felixcosm.tumblr
2211
2215
  0.0.0.0 fem.com
@@ -3754,8 +3758,6 @@
3754
3758
  0.0.0.0 www.music.amazon
3755
3759
  0.0.0.0 music.apple
3756
3760
  0.0.0.0 www.music.apple
3757
- 0.0.0.0 music.yandex
3758
- 0.0.0.0 www.music.yandex
3759
3761
  0.0.0.0 music.youtube
3760
3762
  0.0.0.0 www.music.youtube
3761
3763
  0.0.0.0 musicdex.com
@@ -4730,6 +4732,8 @@
4730
4732
  0.0.0.0 www.public-api.wordpress
4731
4733
  0.0.0.0 public.api
4732
4734
  0.0.0.0 www.public.api
4735
+ 0.0.0.0 public.aurora
4736
+ 0.0.0.0 www.public.aurora
4733
4737
  0.0.0.0 puhutv.com
4734
4738
  0.0.0.0 www.puhutv.com
4735
4739
  0.0.0.0 puls4.com
@@ -5318,6 +5322,8 @@
5318
5322
  0.0.0.0 www.solidarum.org
5319
5323
  0.0.0.0 sonyliv.com
5320
5324
  0.0.0.0 www.sonyliv.com
5325
+ 0.0.0.0 sooplive.com
5326
+ 0.0.0.0 www.sooplive.com
5321
5327
  0.0.0.0 sound.orf
5322
5328
  0.0.0.0 www.sound.orf
5323
5329
  0.0.0.0 soundcloud.com
@@ -5776,6 +5782,8 @@
5776
5782
  0.0.0.0 www.the-voice-of-germany.de
5777
5783
  0.0.0.0 theater-complex.town
5778
5784
  0.0.0.0 www.theater-complex.town
5785
+ 0.0.0.0 thechosen.com
5786
+ 0.0.0.0 www.thechosen.com
5779
5787
  0.0.0.0 theepochtimes.com
5780
5788
  0.0.0.0 www.theepochtimes.com
5781
5789
  0.0.0.0 theguardian.com
@@ -7041,8 +7049,6 @@
7041
7049
  0.0.0.0 www.yahoo.com
7042
7050
  0.0.0.0 yandexdisk.com
7043
7051
  0.0.0.0 www.yandexdisk.com
7044
- 0.0.0.0 yandexmusic.com
7045
- 0.0.0.0 www.yandexmusic.com
7046
7052
  0.0.0.0 yinyue.kuwo
7047
7053
  0.0.0.0 www.yinyue.kuwo
7048
7054
  0.0.0.0 yoga.appsvcs
@@ -1,5 +1,5 @@
1
1
  # Video URL Detection Patterns
2
- # Generated on: 2026-02-15 21:17:19 UTC
2
+ # Generated on: 2026-05-22 11:05:44 UTC
3
3
  # Source: yt-dlp extractors (https://github.com/yt-dlp/yt-dlp) + manual patterns
4
4
  # Purpose: Regex patterns to detect video URLs vs other content
5
5
  #
@@ -190,26 +190,26 @@ https?://(?:www\.)?gronkh\.tv(?:/feed)?/?(?:#|$)
190
190
  # Original: https?://(?:www\.)?gronkh\.tv/vods/streams/?(?:#|$)
191
191
  https?://(?:www\.)?gronkh\.tv/vods/streams/?(?:#|$)
192
192
 
193
- # Source: internetvideoarchive
194
- # Pattern: https?://video\.internetvideoarchive\.net/(?:player|flash/players)/.*?\?.*?publishedid.*?
195
- # Original: https?://video\.internetvideoarchive\.net/(?:player|flash/players)/.*?\?.*?publishedid.*?
196
- https?://video\.internetvideoarchive\.net/(?:player|flash/players)/.*?\?.*?publishedid.*?
197
-
198
193
  # Source: iqiyi
199
194
  # Pattern: https?://(?:(?:[^.]+\.)?iqiyi\.com|www\.pps\.tv)/.+\.html
200
195
  # Original: https?://(?:(?:[^.]+\.)?iqiyi\.com|www\.pps\.tv)/.+\.html
201
196
  https?://(?:(?:[^.]+\.)?iqiyi\.com|www\.pps\.tv)/.+\.html
202
197
 
203
- # Source: jeuxvideo
204
- # Pattern: https?://.*?\.jeuxvideo\.com/.*/(.*?)\.htm
205
- # Original: https?://.*?\.jeuxvideo\.com/.*/(.*?)\.htm
206
- https?://.*?\.jeuxvideo\.com/.*/(.*?)\.htm
198
+ # Source: internetvideoarchive
199
+ # Pattern: https?://video\.internetvideoarchive\.net/(?:player|flash/players)/.*?\?.*?publishedid.*?
200
+ # Original: https?://video\.internetvideoarchive\.net/(?:player|flash/players)/.*?\?.*?publishedid.*?
201
+ https?://video\.internetvideoarchive\.net/(?:player|flash/players)/.*?\?.*?publishedid.*?
207
202
 
208
203
  # Source: japandiet
209
204
  # Pattern: https?://www\.webtv\.sangiin\.go\.jp/webtv/index\.php
210
205
  # Original: https?://www\.webtv\.sangiin\.go\.jp/webtv/index\.php
211
206
  https?://www\.webtv\.sangiin\.go\.jp/webtv/index\.php
212
207
 
208
+ # Source: jeuxvideo
209
+ # Pattern: https?://.*?\.jeuxvideo\.com/.*/(.*?)\.htm
210
+ # Original: https?://.*?\.jeuxvideo\.com/.*/(.*?)\.htm
211
+ https?://.*?\.jeuxvideo\.com/.*/(.*?)\.htm
212
+
213
213
  # Source: leeco
214
214
  # Pattern: https?://yuntv\.letv\.com/bcloud.html\?.+
215
215
  # Original: https?://yuntv\.letv\.com/bcloud.html\?.+
@@ -30,11 +30,11 @@ Gem::Specification.new do |spec|
30
30
  spec.add_dependency 'fileutils', '>= 1.7.0', '< 2.0'
31
31
  spec.add_dependency 'httparty', '>= 0.24.0', '< 1.0'
32
32
  spec.add_dependency 'json', '>= 2.7.0', '< 3.0'
33
- spec.add_dependency 'kaggle', '>= 0.0.3', '< 1.0'
33
+ spec.add_dependency 'kaggle', '>= 0.0.6', '< 1.0'
34
34
  spec.add_dependency 'nokogiri', '>= 1.19.1', '< 2.0'
35
35
  spec.add_dependency 'reline', '>= 0.6.2', '< 2.0'
36
36
  spec.add_dependency 'resolv', '>= 0.4.0', '< 1.0'
37
- spec.add_dependency 'rubyzip', '>= 2.3.0', '< 3.0'
37
+ spec.add_dependency 'rubyzip', '>= 3.4.0', '< 4.0'
38
38
 
39
39
  # Development dependancies
40
40
  spec.add_development_dependency 'minitest', '~> 5.25.5'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: UrlCategorise
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.9
4
+ version: 0.1.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - trex22
@@ -155,7 +155,7 @@ dependencies:
155
155
  requirements:
156
156
  - - ">="
157
157
  - !ruby/object:Gem::Version
158
- version: 0.0.3
158
+ version: 0.0.6
159
159
  - - "<"
160
160
  - !ruby/object:Gem::Version
161
161
  version: '1.0'
@@ -165,7 +165,7 @@ dependencies:
165
165
  requirements:
166
166
  - - ">="
167
167
  - !ruby/object:Gem::Version
168
- version: 0.0.3
168
+ version: 0.0.6
169
169
  - - "<"
170
170
  - !ruby/object:Gem::Version
171
171
  version: '1.0'
@@ -235,20 +235,20 @@ dependencies:
235
235
  requirements:
236
236
  - - ">="
237
237
  - !ruby/object:Gem::Version
238
- version: 2.3.0
238
+ version: 3.4.0
239
239
  - - "<"
240
240
  - !ruby/object:Gem::Version
241
- version: '3.0'
241
+ version: '4.0'
242
242
  type: :runtime
243
243
  prerelease: false
244
244
  version_requirements: !ruby/object:Gem::Requirement
245
245
  requirements:
246
246
  - - ">="
247
247
  - !ruby/object:Gem::Version
248
- version: 2.3.0
248
+ version: 3.4.0
249
249
  - - "<"
250
250
  - !ruby/object:Gem::Version
251
- version: '3.0'
251
+ version: '4.0'
252
252
  - !ruby/object:Gem::Dependency
253
253
  name: minitest
254
254
  requirement: !ruby/object:Gem::Requirement
@@ -476,6 +476,7 @@ files:
476
476
  - bin/generate_categorised_lists
477
477
  - bin/generate_social_media_lists
478
478
  - bin/generate_video_lists
479
+ - bin/generate_video_lists_focused
479
480
  - bin/rake
480
481
  - bin/setup
481
482
  - correct_usage_example.rb