From c1edf2c4dd7e32f88babbc8be63a6e095c1d931e Mon Sep 17 00:00:00 2001 From: gigirassy Date: Sun, 23 Aug 2026 20:46:24 +0200 Subject: [PATCH] init --- blogfetch.cr | 164 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 blogfetch.cr diff --git a/blogfetch.cr b/blogfetch.cr new file mode 100644 index 0000000..824dfe7 --- /dev/null +++ b/blogfetch.cr @@ -0,0 +1,164 @@ +#!/usr/bin/env crystal + +require "http/client" +require "uri" +require "set" + +USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64; rv:154.0) Gecko/20100101 Firefox/154.0" +MAX_BLOGS = 5000 + +def fetch(url : String) : String? + begin + response = HTTP::Client.get( + url, + headers: HTTP::Headers{ + "User-Agent" => USER_AGENT, + } + ) + + return nil unless response.success? + + response.body + rescue + nil + end +end + +def blogspot_host(url : String) : String? + begin + host = URI.parse(url).host + return nil unless host + + host = host.downcase + + blogspot_domains = [ + "blogspot.com", + "blogspot.co.uk", + "blogspot.de", + "blogspot.fr", + "blogspot.ca", + "blogspot.com.au", + "blogspot.in", + "blogspot.it", + "blogspot.nl", + "blogspot.es", + ] + + blogspot_domains.each do |domain| + if host == domain || host.ends_with?(".#{domain}") + return host + end + end + + nil + rescue + nil + end +end + +def blog_root(url : String) : String? + host = blogspot_host(url) + return nil unless host + + "https://#{host}/" +end + +def make_absolute(href : String, base : URI) : String? + href = href.strip + + return nil if href.empty? + return nil if href.starts_with?("#") + return nil if href.starts_with?("javascript:") + return nil if href.starts_with?("mailto:") + return nil if href.starts_with?("tel:") + return nil if href.starts_with?("data:") + + begin + uri = URI.parse(href) + + if uri.scheme && uri.host + return href + end + + host = base.host + return nil unless host + + scheme = base.scheme || "https" + + if href.starts_with?("/") + return "#{scheme}://#{host}#{href}" + end + + base_path = base.path + base_path = "/" if base_path.nil? || base_path.empty? + + directory = + if base_path.ends_with?("/") + base_path + else + base_path.sub(/\/[^\/]*$/, "/") + end + + "#{scheme}://#{host}#{directory}#{href}" + rescue + nil + end +end + +def extract_blog_roots(html : String, page_url : String) : Array(String) + results = Set(String).new + base = URI.parse(page_url) + + html.scan(/]*\bhref\s*=\s*["']([^"']+)["']/im) do |match| + href = match[1] + + if absolute = make_absolute(href, base) + if root = blog_root(absolute) + results << root + end + end + end + + results.to_a +end + +def crawl(start_urls : Array(String)) + queue = Array(String).new + discovered = Set(String).new + crawled = Set(String).new + + start_urls.each do |url| + if root = blog_root(url) + unless discovered.includes?(root) + discovered << root + queue << root + end + end + end + + while !queue.empty? && crawled.size < MAX_BLOGS + root = queue.shift + + next if crawled.includes?(root) + + crawled << root + + puts root + + html = fetch(root) + next unless html + + extract_blog_roots(html, root).each do |new_root| + next if discovered.includes?(new_root) + + discovered << new_root + queue << new_root + end + end +end + +if ARGV.empty? + exit 1 +end + +crawl(ARGV.to_a) \ No newline at end of file