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,398 @@
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 SocialMediaListGenerator
13
+ class SiteParser
14
+ MR_HOLMES_API_URL = 'https://api.github.com/repos/Lucksi/Mr.Holmes/contents/Site_lists/Username'
15
+ MR_HOLMES_SITE_LIST_URL = 'https://raw.githubusercontent.com/Lucksi/Mr.Holmes/master/Site_lists/Username/site_list.json'
16
+
17
+ attr_accessor :max_ractor_workers, :batch_size, :parallel_threshold, :parallel_enabled
18
+
19
+ def initialize(options = {})
20
+ @social_media_domains = Set.new
21
+ @max_ractor_workers = options[:max_ractor_workers] || 4
22
+ @batch_size = options[:batch_size] || 25
23
+ @parallel_threshold = options[:parallel_threshold] || 10
24
+ @parallel_enabled = options.fetch(:parallel_enabled, true)
25
+ end
26
+
27
+ def generate_lists
28
+ puts 'Generating social media domain lists...'
29
+
30
+ puts 'Adding manually curated social media domains...'
31
+ add_manual_domains
32
+
33
+ puts 'Fetching Mr.Holmes site list data...'
34
+ fetch_mr_holmes_data
35
+
36
+ create_lists_directory
37
+ generate_hosts_file
38
+
39
+ puts 'Social media lists generated successfully!'
40
+ puts 'Files created in lists/ directory:'
41
+ puts " - social_media_domains.hosts (#{@social_media_domains.size} domains)"
42
+ end
43
+
44
+ private
45
+
46
+ def add_manual_domains
47
+ # Core social media platforms - major networks
48
+ core_social = [
49
+ 'facebook.com', 'www.facebook.com', 'm.facebook.com', 'web.facebook.com',
50
+ 'instagram.com', 'www.instagram.com',
51
+ 'twitter.com', 'www.twitter.com', 'mobile.twitter.com', 'x.com',
52
+ 'threads.net', 'www.threads.net',
53
+ 'tiktok.com', 'www.tiktok.com', 'vm.tiktok.com',
54
+ 'linkedin.com', 'www.linkedin.com',
55
+ 'pinterest.com', 'www.pinterest.com',
56
+ 'reddit.com', 'www.reddit.com', 'old.reddit.com',
57
+ 'tumblr.com', 'www.tumblr.com',
58
+ 'snapchat.com', 'www.snapchat.com',
59
+ 'whatsapp.com', 'www.whatsapp.com', 'web.whatsapp.com',
60
+ 'telegram.org', 'www.telegram.org', 't.me', 'web.telegram.org',
61
+ 'discord.com', 'www.discord.com', 'discord.gg',
62
+ 'signal.org', 'www.signal.org',
63
+ 'mastodon.social',
64
+ 'bsky.app', 'bsky.social',
65
+ 'vk.com', 'www.vk.com',
66
+ 'weibo.com', 'www.weibo.com',
67
+ 'wechat.com', 'www.wechat.com'
68
+ ]
69
+
70
+ # Messaging and communication platforms
71
+ messaging = [
72
+ 'messenger.com', 'www.messenger.com',
73
+ 'slack.com', 'www.slack.com',
74
+ 'kik.me',
75
+ 'viber.com', 'www.viber.com',
76
+ 'line.me', 'www.line.me',
77
+ 'kakaotalk.com',
78
+ 'community.signalusers.org'
79
+ ]
80
+
81
+ # Content sharing and blogging
82
+ content_sharing = [
83
+ 'medium.com',
84
+ 'wordpress.com',
85
+ 'blogspot.com',
86
+ 'dev.to',
87
+ 'substack.com',
88
+ 'quora.com', 'www.quora.com',
89
+ 'hubpages.com',
90
+ 'flipboard.com',
91
+ 'buzzfeed.com'
92
+ ]
93
+
94
+ # Image and photo sharing
95
+ image_sharing = [
96
+ 'imgur.com', 'www.imgur.com', 'i.imgur.com',
97
+ 'flickr.com', 'www.flickr.com',
98
+ 'deviantart.com', 'www.deviantart.com',
99
+ 'unsplash.com',
100
+ 'behance.net', 'www.behance.net',
101
+ 'dribbble.com',
102
+ 'vsco.co',
103
+ '500px.com',
104
+ 'youpic.com',
105
+ '9gag.com'
106
+ ]
107
+
108
+ # Professional and portfolio
109
+ professional = [
110
+ 'about.me',
111
+ 'linktr.ee',
112
+ 'heylink.me',
113
+ 'allmylinks.com',
114
+ 'linkgenie.co',
115
+ 'milkshake.app',
116
+ 'ngl.link',
117
+ 'ko-fi.com',
118
+ 'patreon.com',
119
+ 'buymeacoffee.com'
120
+ ]
121
+
122
+ # Music and audio social
123
+ music_social = [
124
+ 'soundcloud.com', 'www.soundcloud.com',
125
+ 'bandcamp.com', 'www.bandcamp.com',
126
+ 'mixcloud.com',
127
+ 'audiojungle.net',
128
+ 'rateyourmusic.com',
129
+ 'smule.com', 'www.smule.com',
130
+ 'ourdjtalk.com',
131
+ 'house-mixes.com', 'www.house-mixes.com',
132
+ 'freesound.org'
133
+ ]
134
+
135
+ # Gaming social
136
+ gaming_social = [
137
+ 'steamcommunity.com',
138
+ 'twitch.tv', 'www.twitch.tv', 'clips.twitch.tv', 'm.twitch.tv',
139
+ 'twitchtracker.com',
140
+ 'roblox.com', 'www.roblox.com',
141
+ 'fortnitetracker.com',
142
+ 'psnprofiles.com',
143
+ 'osu.ppy.sh',
144
+ 'gamespot.com', 'www.gamespot.com',
145
+ 'newgrounds.com',
146
+ 'speedrun.com', 'www.speedrun.com',
147
+ 'chess.com', 'www.chess.com',
148
+ 'fandom.com', 'www.fandom.com',
149
+ 'pokemonshowdown.com',
150
+ 'splits.io',
151
+ 'lolprofile.net',
152
+ 'pubg.op.gg', 'euw.op.gg'
153
+ ]
154
+
155
+ # Developer and tech social
156
+ developer_social = [
157
+ 'github.com', 'www.github.com',
158
+ 'gitlab.com',
159
+ 'bitbucket.org',
160
+ 'sourceforge.net',
161
+ 'pypi.org',
162
+ 'npmjs.com', 'www.npmjs.com',
163
+ 'rubygems.org',
164
+ 'hub.docker.com',
165
+ 'gitee.com',
166
+ 'notabug.org',
167
+ 'scratch.mit.edu',
168
+ 'codewars.com', 'www.codewars.com',
169
+ 'codecademy.com', 'www.codecademy.com',
170
+ 'hackaday.io',
171
+ 'pastebin.com',
172
+ 'asciinema.org',
173
+ 'drupal.org', 'www.drupal.org',
174
+ 'opensource.com',
175
+ 'skillshare.com', 'www.skillshare.com'
176
+ ]
177
+
178
+ # Security and hacking communities
179
+ security_social = [
180
+ 'hackthebox.eu',
181
+ 'tryhackme.com',
182
+ 'hackerone.com',
183
+ 'bugcrowd.com'
184
+ ]
185
+
186
+ # Question and answer platforms
187
+ qa_platforms = [
188
+ 'ask.fm',
189
+ 'tellonym.me',
190
+ 'disqus.com'
191
+ ]
192
+
193
+ # Miscellaneous social platforms
194
+ misc_social = [
195
+ 'myspace.com',
196
+ 'ello.co',
197
+ 'vbox7.com', 'www.vbox7.com',
198
+ 'interpals.net', 'www.interpals.net',
199
+ 'joinclubhouse.com',
200
+ 'younow.com',
201
+ 'camfrog.com',
202
+ 'facciabuco.com', 'www.facciabuco.com',
203
+ 'couchsurfing.com', 'www.couchsurfing.com',
204
+ 'wattpad.com', 'www.wattpad.com',
205
+ 'quotev.com', 'www.quotev.com',
206
+ 'bookcrossing.com', 'www.bookcrossing.com',
207
+ 'myanimelist.net',
208
+ 'opensea.io',
209
+ 'slideshare.net',
210
+ 'openstreetmap.org', 'www.openstreetmap.org',
211
+ 'gravatar.com',
212
+ 'keybase.io',
213
+ 'pscp.tv', 'www.pscp.tv',
214
+ 'bitcoinforum.com',
215
+ 'aminoapps.com',
216
+ 'joinroll.com',
217
+ 'pr0gramm.com',
218
+ 'shitpostbot.com', 'www.shitpostbot.com',
219
+ 'lookbook.nu',
220
+ 'listal.com',
221
+ 'career.habr.com',
222
+ 'thehandbook.com', 'www.thehandbook.com',
223
+ 'venmo.com',
224
+ 'themeforest.net',
225
+ 'cbtnuggets.com', 'www.cbtnuggets.com',
226
+ '7cups.com', 'www.7cups.com',
227
+ 'quizsilo.com',
228
+ 'colourlovers.com', 'www.colourlovers.com',
229
+ '21buttons.com', 'www.21buttons.com',
230
+ 'passes.com',
231
+ 'holopin.io'
232
+ ]
233
+
234
+ # Website builders with social features
235
+ website_builders = [
236
+ 'wix.com',
237
+ 'jimdosite.com',
238
+ 'mymonat.com'
239
+ ]
240
+
241
+ # Nitter and alternative frontends
242
+ alternative_frontends = [
243
+ 'nitter.net',
244
+ 'urlebird.com',
245
+ 'picuki.com', 'www.picuki.com',
246
+ 'mstdn.io'
247
+ ]
248
+
249
+ all_domains = core_social + messaging + content_sharing + image_sharing +
250
+ professional + music_social + gaming_social + developer_social +
251
+ security_social + qa_platforms + misc_social + website_builders +
252
+ alternative_frontends
253
+
254
+ all_domains.each { |domain| @social_media_domains.add(domain) }
255
+ end
256
+
257
+ def fetch_mr_holmes_data
258
+ uri = URI(MR_HOLMES_SITE_LIST_URL)
259
+ response = Net::HTTP.get_response(uri)
260
+
261
+ unless response.code == '200'
262
+ puts ' Could not fetch from Mr.Holmes repository, using manual domains only'
263
+ return
264
+ end
265
+
266
+ begin
267
+ data = JSON.parse(response.body)
268
+ process_mr_holmes_site_list(data)
269
+ rescue JSON::ParserError => e
270
+ puts " Error parsing Mr.Holmes site list: #{e.message}"
271
+ end
272
+
273
+ puts "\n Found #{@social_media_domains.size} unique social media domains"
274
+ end
275
+
276
+ def process_mr_holmes_site_list(data)
277
+ # The JSON structure is an array with a single hash containing all sites
278
+ sites = data.is_a?(Array) ? data.first : data
279
+
280
+ return unless sites.is_a?(Hash)
281
+
282
+ sites.each do |_key, site_info|
283
+ next unless site_info.is_a?(Hash)
284
+
285
+ main_domain = site_info['main']
286
+ next unless main_domain
287
+
288
+ # Clean domain (remove protocol and trailing slashes)
289
+ domain = clean_mr_holmes_domain(main_domain)
290
+ next unless domain && valid_domain?(domain)
291
+
292
+ @social_media_domains.add(domain.downcase)
293
+
294
+ # Extract additional domains from user URLs
295
+ extract_domains_from_url(site_info['user'])
296
+ extract_domains_from_url(site_info['user2'])
297
+ extract_domains_from_url(site_info['Scrapable_url']) if site_info['Scrapable_url']
298
+ end
299
+ end
300
+
301
+ def clean_mr_holmes_domain(domain)
302
+ return nil unless domain
303
+
304
+ cleaned = domain.strip
305
+ cleaned = cleaned.sub(%r{^https?://}, '')
306
+ cleaned = cleaned.sub(%r{/+$}, '')
307
+ cleaned = cleaned.sub(/^www\./, '')
308
+ cleaned
309
+ end
310
+
311
+ def extract_domains_from_url(url)
312
+ return unless url
313
+
314
+ match = url.match(%r{https?://(?:www\.)?([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})})
315
+ return unless match
316
+
317
+ domain = match[1].downcase
318
+ @social_media_domains.add(domain) if valid_domain?(domain)
319
+ end
320
+
321
+ def valid_domain?(domain)
322
+ return false unless domain
323
+ return false if domain.length < 4
324
+ return false unless domain.match?(/^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/)
325
+ return false if domain.include?('example.') || domain.include?('test.')
326
+ return false if domain.include?('localhost')
327
+ return false if domain.end_with?('.html') || domain.end_with?('.htm')
328
+ # Filter out API-only domains that aren't social media sites
329
+ return false if domain.start_with?('api.') && !domain.include?('mojang')
330
+
331
+ true
332
+ end
333
+
334
+ def create_lists_directory
335
+ FileUtils.mkdir_p('lists')
336
+ end
337
+
338
+ def generate_hosts_file
339
+ hosts_file_path = 'lists/social_media_domains.hosts'
340
+
341
+ File.open(hosts_file_path, 'w') do |file|
342
+ write_hosts_header(file)
343
+
344
+ @social_media_domains.sort.each do |domain|
345
+ file.puts "0.0.0.0 #{domain}"
346
+ file.puts "0.0.0.0 www.#{domain}" unless domain.start_with?('www.')
347
+ end
348
+ end
349
+ end
350
+
351
+ def write_hosts_header(file)
352
+ file.puts '# Social Media Domains - PiHole Compatible Hosts File'
353
+ file.puts "# Generated on: #{Time.now.strftime('%Y-%m-%d %H:%M:%S UTC')}"
354
+ file.puts '# Sources:'
355
+ file.puts '# - Mr.Holmes OSINT tool (https://github.com/Lucksi/Mr.Holmes)'
356
+ file.puts '# - Manually curated social media platform domains'
357
+ file.puts '# Purpose: Identify and categorize social media websites'
358
+ file.puts '# Format: 0.0.0.0 domain.com'
359
+ file.puts '#'
360
+ file.puts '# This file contains domains extracted from the Mr.Holmes OSINT tool'
361
+ file.puts '# and manually curated lists to help identify and categorize'
362
+ file.puts '# social media platforms, messaging apps, and community websites.'
363
+ file.puts '#'
364
+ file.puts ''
365
+ end
366
+ end
367
+ end
368
+
369
+ # Run the generator if this file is executed directly
370
+ if __FILE__ == $0
371
+ # Default configuration
372
+ options = {
373
+ max_ractor_workers: 4,
374
+ batch_size: 25,
375
+ parallel_threshold: 10,
376
+ verbose: false
377
+ }
378
+
379
+ # Parse command-line options
380
+ OptionParser.new do |opts|
381
+ opts.banner = "Usage: #{$0} [options]"
382
+ opts.separator ''
383
+ opts.separator 'Generate social media domain lists from Mr.Holmes OSINT tool and curated sources'
384
+ opts.separator ''
385
+
386
+ opts.on('-v', '--verbose', 'Verbose output') do
387
+ options[:verbose] = true
388
+ end
389
+
390
+ opts.on('-h', '--help', 'Show this help message') do
391
+ puts opts
392
+ exit
393
+ end
394
+ end.parse!
395
+
396
+ generator = SocialMediaListGenerator::SiteParser.new(options)
397
+ generator.generate_lists
398
+ end