UrlCategorise 0.1.7 → 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.
@@ -0,0 +1,533 @@
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
+ require 'optparse'
11
+
12
+ module CategorisedListGenerator
13
+ class SiteParser
14
+ MR_HOLMES_SITE_LIST_URL = 'https://raw.githubusercontent.com/Lucksi/Mr.Holmes/master/Site_lists/Username/site_list.json'
15
+
16
+ # Map Mr.Holmes tags to our category list names
17
+ TAG_TO_CATEGORY = {
18
+ 'gaming' => :gaming,
19
+ 'fortnite' => :gaming,
20
+ 'playstation' => :gaming,
21
+ 'minecraft' => :gaming,
22
+ 'roblox' => :gaming,
23
+ 'steam' => :gaming,
24
+ 'chess' => :gaming,
25
+ 'chess.com' => :gaming,
26
+ 'osu' => :gaming,
27
+ 'pokemon' => :gaming,
28
+ 'lol' => :gaming,
29
+ 'speedrun' => :gaming,
30
+ 'programming' => :developer,
31
+ 'opensource' => :developer,
32
+ 'python' => :developer,
33
+ 'ruby' => :developer,
34
+ 'npm' => :developer,
35
+ 'docker' => :developer,
36
+ 'music' => :music,
37
+ 'sing' => :music,
38
+ 'streaming' => :streaming,
39
+ 'forum' => :forum,
40
+ 'questions' => :forum,
41
+ 'chatting' => :messaging,
42
+ 'crypto' => :crypto,
43
+ 'bitcoin' => :crypto,
44
+ 'bitcoin-forum' => :crypto,
45
+ 'blog' => :blogging,
46
+ 'article' => :blogging,
47
+ 'writing' => :blogging,
48
+ 'stories' => :blogging,
49
+ 'social' => :social_media,
50
+ 'photo' => :social_media,
51
+ 'image' => :social_media,
52
+ 'portfolio' => :social_media,
53
+ 'art' => :social_media,
54
+ 'meme' => :social_media,
55
+ 'fashion' => :social_media,
56
+ 'modelling' => :social_media,
57
+ 'shopping' => :social_media,
58
+ 'hacking' => :security,
59
+ 'bugbounty' => :security,
60
+ 'donations' => :social_media,
61
+ 'link' => :social_media,
62
+ 'gallery' => :social_media
63
+ }.freeze
64
+
65
+ # Categories to generate hosts files for
66
+ CATEGORIES = {
67
+ gaming: {
68
+ filename: 'gaming_domains.hosts',
69
+ title: 'Gaming Platform Domains',
70
+ description: 'gaming platforms and communities'
71
+ },
72
+ developer: {
73
+ filename: 'developer_domains.hosts',
74
+ title: 'Developer Platform Domains',
75
+ description: 'developer tools, package registries, and programming platforms'
76
+ },
77
+ music: {
78
+ filename: 'music_domains.hosts',
79
+ title: 'Music Platform Domains',
80
+ description: 'music streaming and sharing platforms'
81
+ },
82
+ streaming: {
83
+ filename: 'streaming_domains.hosts',
84
+ title: 'Streaming Platform Domains',
85
+ description: 'live streaming platforms'
86
+ },
87
+ forum: {
88
+ filename: 'forum_domains.hosts',
89
+ title: 'Forum and Q&A Domains',
90
+ description: 'forums, Q&A platforms, and community discussion sites'
91
+ },
92
+ messaging: {
93
+ filename: 'messaging_domains.hosts',
94
+ title: 'Messaging Platform Domains',
95
+ description: 'messaging and chat platforms'
96
+ },
97
+ crypto: {
98
+ filename: 'crypto_domains.hosts',
99
+ title: 'Cryptocurrency Platform Domains',
100
+ description: 'cryptocurrency exchanges, forums, and NFT platforms'
101
+ },
102
+ blogging: {
103
+ filename: 'blogging_domains.hosts',
104
+ title: 'Blogging Platform Domains',
105
+ description: 'blogging, writing, and content publishing platforms'
106
+ },
107
+ security: {
108
+ filename: 'security_domains.hosts',
109
+ title: 'Security Community Domains',
110
+ description: 'security research, bug bounty, and hacking community platforms'
111
+ }
112
+ }.freeze
113
+
114
+ def initialize(options = {})
115
+ @category_domains = {}
116
+ CATEGORIES.each_key { |cat| @category_domains[cat] = Set.new }
117
+ @verbose = options[:verbose] || false
118
+ end
119
+
120
+ def generate_lists
121
+ puts 'Generating categorised domain lists...'
122
+
123
+ puts 'Adding manually curated domains...'
124
+ add_manual_domains
125
+
126
+ puts 'Fetching Mr.Holmes site list data...'
127
+ fetch_mr_holmes_data
128
+
129
+ create_lists_directory
130
+
131
+ CATEGORIES.each do |category, info|
132
+ generate_hosts_file(category, info)
133
+ end
134
+
135
+ puts ''
136
+ puts 'Categorised lists generated successfully!'
137
+ puts 'Files created in lists/ directory:'
138
+ CATEGORIES.each do |category, info|
139
+ puts " - #{info[:filename]} (#{@category_domains[category].size} domains)"
140
+ end
141
+ end
142
+
143
+ private
144
+
145
+ def add_manual_domains
146
+ # Gaming platforms
147
+ add_domains(:gaming, [
148
+ 'steamcommunity.com', 'store.steampowered.com', 'steampowered.com',
149
+ 'roblox.com', 'www.roblox.com',
150
+ 'epicgames.com', 'www.epicgames.com',
151
+ 'ea.com', 'www.ea.com',
152
+ 'ubisoft.com', 'www.ubisoft.com',
153
+ 'blizzard.com', 'www.blizzard.com', 'battle.net',
154
+ 'nintendo.com', 'www.nintendo.com',
155
+ 'rockstargames.com', 'www.rockstargames.com',
156
+ 'twitch.tv', 'www.twitch.tv', 'clips.twitch.tv', 'm.twitch.tv',
157
+ 'twitchtracker.com',
158
+ 'fortnitetracker.com',
159
+ 'psnprofiles.com',
160
+ 'osu.ppy.sh',
161
+ 'gamespot.com', 'www.gamespot.com',
162
+ 'newgrounds.com',
163
+ 'speedrun.com', 'www.speedrun.com',
164
+ 'chess.com', 'www.chess.com',
165
+ 'lichess.org',
166
+ 'fandom.com', 'www.fandom.com',
167
+ 'pokemonshowdown.com',
168
+ 'splits.io',
169
+ 'lolprofile.net',
170
+ 'pubg.op.gg', 'euw.op.gg', 'op.gg',
171
+ 'ign.com', 'www.ign.com',
172
+ 'gog.com', 'www.gog.com',
173
+ 'humblebundle.com', 'www.humblebundle.com',
174
+ 'itch.io',
175
+ 'myanimelist.net',
176
+ 'crunchyroll.com', 'www.crunchyroll.com'
177
+ ])
178
+
179
+ # Developer platforms
180
+ add_domains(:developer, [
181
+ 'github.com', 'www.github.com', 'gist.github.com',
182
+ 'gitlab.com',
183
+ 'bitbucket.org',
184
+ 'sourceforge.net',
185
+ 'pypi.org',
186
+ 'npmjs.com', 'www.npmjs.com',
187
+ 'rubygems.org',
188
+ 'hub.docker.com',
189
+ 'gitee.com',
190
+ 'notabug.org',
191
+ 'scratch.mit.edu',
192
+ 'codewars.com', 'www.codewars.com',
193
+ 'codecademy.com', 'www.codecademy.com',
194
+ 'hackaday.io',
195
+ 'pastebin.com',
196
+ 'asciinema.org',
197
+ 'drupal.org', 'www.drupal.org',
198
+ 'opensource.com',
199
+ 'dev.to',
200
+ 'stackoverflow.com', 'www.stackoverflow.com',
201
+ 'stackexchange.com',
202
+ 'crates.io',
203
+ 'packagist.org',
204
+ 'maven.org',
205
+ 'nuget.org',
206
+ 'hex.pm',
207
+ 'pkg.go.dev',
208
+ 'cocoapods.org',
209
+ 'pub.dev',
210
+ 'cpan.org',
211
+ 'cran.r-project.org',
212
+ 'conda.io',
213
+ 'anaconda.org',
214
+ 'codepen.io',
215
+ 'jsfiddle.net',
216
+ 'replit.com', 'www.replit.com',
217
+ 'glitch.com',
218
+ 'vercel.com',
219
+ 'netlify.com',
220
+ 'heroku.com',
221
+ 'render.com',
222
+ 'fly.io',
223
+ 'railway.app'
224
+ ])
225
+
226
+ # Music platforms
227
+ add_domains(:music, [
228
+ 'soundcloud.com', 'www.soundcloud.com',
229
+ 'bandcamp.com', 'www.bandcamp.com',
230
+ 'mixcloud.com',
231
+ 'audiojungle.net',
232
+ 'rateyourmusic.com',
233
+ 'smule.com', 'www.smule.com',
234
+ 'ourdjtalk.com',
235
+ 'house-mixes.com', 'www.house-mixes.com',
236
+ 'freesound.org',
237
+ 'spotify.com', 'open.spotify.com',
238
+ 'music.apple.com',
239
+ 'music.amazon.com',
240
+ 'tidal.com', 'www.tidal.com',
241
+ 'deezer.com', 'www.deezer.com',
242
+ 'pandora.com', 'www.pandora.com',
243
+ 'last.fm', 'www.last.fm',
244
+ 'genius.com',
245
+ 'musixmatch.com',
246
+ 'songkick.com',
247
+ 'discogs.com', 'www.discogs.com',
248
+ 'musicbrainz.org'
249
+ ])
250
+
251
+ # Streaming platforms
252
+ add_domains(:streaming, [
253
+ 'twitch.tv', 'www.twitch.tv', 'clips.twitch.tv',
254
+ 'twitchtracker.com',
255
+ 'younow.com',
256
+ 'pscp.tv', 'www.pscp.tv',
257
+ 'livestream.com',
258
+ 'kick.com', 'www.kick.com',
259
+ 'caffeine.tv',
260
+ 'dlive.tv',
261
+ 'trovo.live',
262
+ 'bigo.tv', 'www.bigo.tv',
263
+ 'nimo.tv',
264
+ 'streamlabs.com'
265
+ ])
266
+
267
+ # Forum and Q&A platforms
268
+ add_domains(:forum, [
269
+ 'ask.fm',
270
+ 'tellonym.me',
271
+ 'disqus.com',
272
+ 'quora.com', 'www.quora.com',
273
+ 'reddit.com', 'www.reddit.com', 'old.reddit.com',
274
+ 'stackoverflow.com', 'www.stackoverflow.com',
275
+ 'stackexchange.com',
276
+ 'quizsilo.com',
277
+ 'community.oracle.com',
278
+ 'forum.sublimetext.com',
279
+ 'forum.pixelfederation.com',
280
+ 'community.signalusers.org',
281
+ 'discourse.org',
282
+ 'lemmy.world',
283
+ 'kbin.social',
284
+ 'lobste.rs',
285
+ 'news.ycombinator.com',
286
+ 'slashdot.org',
287
+ 'ngl.link',
288
+ 'fandom.com', 'www.fandom.com',
289
+ 'colourlovers.com', 'www.colourlovers.com',
290
+ 'native-instruments.com', 'www.native-instruments.com'
291
+ ])
292
+
293
+ # Messaging platforms
294
+ add_domains(:messaging, [
295
+ 'whatsapp.com', 'www.whatsapp.com', 'web.whatsapp.com',
296
+ 'telegram.org', 'www.telegram.org', 't.me', 'web.telegram.org',
297
+ 'discord.com', 'www.discord.com', 'discord.gg',
298
+ 'signal.org', 'www.signal.org',
299
+ 'messenger.com', 'www.messenger.com',
300
+ 'slack.com', 'www.slack.com',
301
+ 'kik.me',
302
+ 'viber.com', 'www.viber.com',
303
+ 'line.me', 'www.line.me',
304
+ 'kakaotalk.com',
305
+ 'element.io',
306
+ 'matrix.org',
307
+ 'rocket.chat',
308
+ 'zulip.com',
309
+ 'mattermost.com',
310
+ 'teams.microsoft.com',
311
+ 'skype.com', 'www.skype.com',
312
+ 'zoom.us',
313
+ 'meet.google.com',
314
+ 'webex.com'
315
+ ])
316
+
317
+ # Crypto platforms
318
+ add_domains(:crypto, [
319
+ 'bitcoinforum.com',
320
+ 'opensea.io',
321
+ 'coinbase.com', 'www.coinbase.com',
322
+ 'binance.com', 'www.binance.com',
323
+ 'kraken.com', 'www.kraken.com',
324
+ 'coinmarketcap.com',
325
+ 'coingecko.com',
326
+ 'etherscan.io',
327
+ 'blockchain.com',
328
+ 'bitcoin.org',
329
+ 'ethereum.org',
330
+ 'rarible.com',
331
+ 'foundation.app',
332
+ 'looksrare.org',
333
+ 'uniswap.org',
334
+ 'pancakeswap.finance',
335
+ 'defi.org'
336
+ ])
337
+
338
+ # Blogging platforms
339
+ add_domains(:blogging, [
340
+ 'medium.com',
341
+ 'wordpress.com',
342
+ 'blogspot.com',
343
+ 'substack.com',
344
+ 'hubpages.com',
345
+ 'wattpad.com', 'www.wattpad.com',
346
+ 'quotev.com', 'www.quotev.com',
347
+ 'flipboard.com',
348
+ 'buzzfeed.com',
349
+ 'about.me',
350
+ 'tumblr.com', 'www.tumblr.com',
351
+ 'ghost.org',
352
+ 'hashnode.dev',
353
+ 'beehiiv.com',
354
+ 'buttondown.email',
355
+ 'revue.email',
356
+ 'tinyletter.com',
357
+ 'writeas.com', 'write.as',
358
+ 'telegraph.ph',
359
+ 'noted.lol',
360
+ 'bookcrossing.com', 'www.bookcrossing.com',
361
+ 'slideshare.net',
362
+ 'livejournal.com', 'www.livejournal.com'
363
+ ])
364
+
365
+ # Security communities
366
+ add_domains(:security, [
367
+ 'hackthebox.eu', 'www.hackthebox.eu', 'app.hackthebox.com',
368
+ 'tryhackme.com',
369
+ 'hackerone.com',
370
+ 'bugcrowd.com',
371
+ 'vulnhub.com',
372
+ 'exploit-db.com',
373
+ 'cvedetails.com',
374
+ 'shodan.io',
375
+ 'censys.io',
376
+ 'virustotal.com',
377
+ 'urlscan.io',
378
+ 'otx.alienvault.com',
379
+ 'intigriti.com',
380
+ 'synack.com',
381
+ 'cobalt.io',
382
+ 'yeswehack.com',
383
+ 'portswigger.net'
384
+ ])
385
+ end
386
+
387
+ def add_domains(category, domains)
388
+ domains.each { |domain| @category_domains[category].add(domain) }
389
+ end
390
+
391
+ def fetch_mr_holmes_data
392
+ uri = URI(MR_HOLMES_SITE_LIST_URL)
393
+ response = Net::HTTP.get_response(uri)
394
+
395
+ unless response.code == '200'
396
+ puts ' Could not fetch from Mr.Holmes repository, using manual domains only'
397
+ return
398
+ end
399
+
400
+ begin
401
+ data = JSON.parse(response.body)
402
+ process_mr_holmes_site_list(data)
403
+ rescue JSON::ParserError => e
404
+ puts " Error parsing Mr.Holmes site list: #{e.message}"
405
+ end
406
+
407
+ @category_domains.each do |category, domains|
408
+ puts " #{category}: #{domains.size} domains" if @verbose
409
+ end
410
+ end
411
+
412
+ def process_mr_holmes_site_list(data)
413
+ sites = data.is_a?(Array) ? data.first : data
414
+ return unless sites.is_a?(Hash)
415
+
416
+ sites.each do |_key, site_info|
417
+ next unless site_info.is_a?(Hash)
418
+
419
+ tags = site_info['Tag']
420
+ next unless tags.is_a?(Array)
421
+
422
+ main_domain = site_info['main']
423
+ next unless main_domain
424
+
425
+ domain = clean_mr_holmes_domain(main_domain)
426
+ next unless domain && valid_domain?(domain)
427
+
428
+ # Determine categories from tags (only include categories we have sets for)
429
+ categories = tags.flat_map { |tag| TAG_TO_CATEGORY[tag.downcase.strip] }.compact.uniq
430
+ categories = categories.select { |cat| @category_domains.key?(cat) }
431
+
432
+ categories.each do |category|
433
+ @category_domains[category].add(domain.downcase)
434
+
435
+ # Also extract domains from user URLs
436
+ extract_domain_to_category(site_info['user'], category)
437
+ extract_domain_to_category(site_info['user2'], category)
438
+ extract_domain_to_category(site_info['Scrapable_url'], category) if site_info['Scrapable_url']
439
+ end
440
+ end
441
+ end
442
+
443
+ def extract_domain_to_category(url, category)
444
+ return unless url
445
+
446
+ match = url.match(%r{https?://(?:www\.)?([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})})
447
+ return unless match
448
+
449
+ domain = match[1].downcase
450
+ @category_domains[category].add(domain) if valid_domain?(domain)
451
+ end
452
+
453
+ def clean_mr_holmes_domain(domain)
454
+ return nil unless domain
455
+
456
+ cleaned = domain.strip
457
+ cleaned = cleaned.sub(%r{^https?://}, '')
458
+ cleaned = cleaned.sub(%r{/+$}, '')
459
+ cleaned = cleaned.sub(/^www\./, '')
460
+ cleaned
461
+ end
462
+
463
+ def valid_domain?(domain)
464
+ return false unless domain
465
+ return false if domain.length < 4
466
+ return false unless domain.match?(/^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/)
467
+ return false if domain.include?('example.') || domain.include?('test.')
468
+ return false if domain.include?('localhost')
469
+ return false if domain.end_with?('.html') || domain.end_with?('.htm')
470
+ return false if domain.start_with?('api.') && !domain.include?('mojang')
471
+
472
+ true
473
+ end
474
+
475
+ def create_lists_directory
476
+ FileUtils.mkdir_p('lists')
477
+ end
478
+
479
+ def generate_hosts_file(category, info)
480
+ hosts_file_path = "lists/#{info[:filename]}"
481
+
482
+ File.open(hosts_file_path, 'w') do |file|
483
+ write_hosts_header(file, info)
484
+
485
+ @category_domains[category].sort.each do |domain|
486
+ file.puts "0.0.0.0 #{domain}"
487
+ file.puts "0.0.0.0 www.#{domain}" unless domain.start_with?('www.')
488
+ end
489
+ end
490
+ end
491
+
492
+ def write_hosts_header(file, info)
493
+ file.puts "# #{info[:title]} - PiHole Compatible Hosts File"
494
+ file.puts "# Generated on: #{Time.now.strftime('%Y-%m-%d %H:%M:%S UTC')}"
495
+ file.puts '# Sources:'
496
+ file.puts '# - Mr.Holmes OSINT tool (https://github.com/Lucksi/Mr.Holmes)'
497
+ file.puts '# - Manually curated domain lists'
498
+ file.puts "# Purpose: Identify and categorize #{info[:description]}"
499
+ file.puts '# Format: 0.0.0.0 domain.com'
500
+ file.puts '#'
501
+ file.puts ''
502
+ end
503
+ end
504
+ end
505
+
506
+ # Run the generator if this file is executed directly
507
+ if __FILE__ == $0
508
+ options = { verbose: false }
509
+
510
+ OptionParser.new do |opts|
511
+ opts.banner = "Usage: #{$0} [options]"
512
+ opts.separator ''
513
+ opts.separator 'Generate categorised domain lists from Mr.Holmes OSINT tool and curated sources'
514
+ opts.separator ''
515
+ opts.separator 'Categories generated:'
516
+ CategorisedListGenerator::SiteParser::CATEGORIES.each do |category, info|
517
+ opts.separator " - #{category}: #{info[:description]}"
518
+ end
519
+ opts.separator ''
520
+
521
+ opts.on('-v', '--verbose', 'Verbose output') do
522
+ options[:verbose] = true
523
+ end
524
+
525
+ opts.on('-h', '--help', 'Show this help message') do
526
+ puts opts
527
+ exit
528
+ end
529
+ end.parse!
530
+
531
+ generator = CategorisedListGenerator::SiteParser.new(options)
532
+ generator.generate_lists
533
+ end