remi-domain-finder 0.1.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.
data/NOTES ADDED
@@ -0,0 +1,41 @@
1
+ TODO
2
+
3
+ put all classes within DomainFinder namespace to not class with other libraries
4
+
5
+ turn into gem (github-compatible)
6
+
7
+ make a domain-finder console with some nice and easy built in commands for checking domains/etc
8
+
9
+ make it check and eval a .domainfinderrc or _domainfinderrc for easily overriding anything
10
+ like the default domain-finder command and whatever else
11
+
12
+ make domain-auction (shortcut to domain-finder auction)
13
+
14
+ make a shoes-based GUI specific to the auction functionality ... ./bin/domain-auction (maybe?)
15
+
16
+ NOTES
17
+
18
+ Auction Date google query examples:
19
+
20
+ "Auction Date" 01-15-2008 intitle:club site:whois.domaintools.com
21
+ intitle optional
22
+
23
+ "date should be atleast 2 weeks ago" (so it'll be in the cache, probably?)
24
+
25
+ expected usages ...
26
+
27
+ domain-finder auction
28
+ domain-finder auction --date 01-01-2008
29
+ domain-finder auction --date 01-01-2008..01-05-2008
30
+ domain-finder auction --date 01-01-2008 --keyword blog
31
+ domain-finder auction --date 01-01-2008 --keyword blog,seo
32
+ domain-finder auction --date 01-01-2008,01-04-2008..01-05-2008 --keyword blog,seo
33
+
34
+ Google usage ...
35
+
36
+ Google.search %{"Auction Date" blah ...}, :page => 2 should return an array of GoogleResult objects
37
+
38
+ Google.search_url(....).should == "http://q=....."
39
+ Google.fetch_url(...).should == string
40
+ Google.parse_results(str).should == []
41
+ Google.parse_result(hpricot thing).should == GoogleResult
data/README ADDED
@@ -0,0 +1,19 @@
1
+ domain-finder
2
+ =============
3
+
4
+ domain-finder is a little utility for checking the availability
5
+ of domains and, specifically, for showing some recently used but
6
+ no longer registered domain names, using a technique developed
7
+ by Mark Fulton of dotsauce.com
8
+
9
+
10
+ See NOTES for current TODO and future ideal example usage
11
+
12
+
13
+ License
14
+ -------
15
+
16
+ domain-finder is released under the terms of the GNU Affero General Public License
17
+
18
+ We reserve the right to re-release under a more permissive license in the future,
19
+ which will likely be the LGPL, allowing for library use by proprietery applications
@@ -0,0 +1,9 @@
1
+ desc "Generate and view specdoc"
2
+ task :specdoc do
3
+ system "spec -f specdoc spec/*_spec.rb" # kills BASH colors :(
4
+ end
5
+
6
+ desc "Generate and view HTML specdoc"
7
+ task :spechtml do
8
+ system "spec -f html spec/*_spec.rb > tmp/specdoc.html; firefox tmp/specdoc.html"
9
+ end
@@ -0,0 +1,4 @@
1
+ #! /usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../lib/domain-finder'
3
+ require 'domain-finder/bin'
4
+ DomainFinder::Bin.new( ARGV ).run
@@ -0,0 +1,22 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "domain-finder"
3
+ s.version = "0.1.0"
4
+ s.date = "2008-06-14"
5
+ s.summary = "Find available domains names"
6
+ s.email = "remi@remitaylor.com"
7
+ s.homepage = "http://github.com/remi/domain-finder"
8
+ s.description = "Find some previously used domain names and check availability"
9
+ s.has_rdoc = true
10
+ s.rdoc_options = ["--quiet", "--title", "domain-finder", "--opname", "index.html", "--line-numbers", "--main", "README", "--inline-source"]
11
+ s.extra_rdoc_files = ['README']
12
+ s.authors = ["remi Taylor"]
13
+
14
+ # generate using: $ ruby -e "puts Dir['**/**'].select{|x| File.file?x}.inspect"
15
+ s.files = ["bin/domain-finder", "COPYING", "lib/domain-finder.rb", "lib/domain-finder/bin.rb", "lib/domain-finder/domaintools.rb", "lib/domain-finder/google.rb", "lib/domain-finder/daterange.rb", "lib/domain-finder/moniker.rb", "lib/domain-finder/domain.rb", "lib/domain-finder/secure_post.rb", "domain-finder.gemspec", "Rakefile", "NOTES", "README", "tmp/specdoc.html", "spec/spec_helper.rb", "spec/google_spec.rb", "spec/moniker_spec.rb", "spec/daterange_spec.rb", "spec/html/google-1.html", "spec/html/moniker-1.html", "spec/html/domaintools-1.html", "spec/domaintools_spec.rb"]
16
+
17
+ s.add_dependency("remi-simplecli", ["> 0.0.0"])
18
+ s.add_dependency("hpricot", ["> 0.0.0"])
19
+
20
+ s.executables = ["domain-finder"]
21
+ s.default_executable = "domain-finder"
22
+ end
@@ -0,0 +1,6 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ class DomainFinder; end
4
+
5
+ %w( rubygems open-uri net/https hpricot date ).each { |lib| require lib }
6
+ %w( secure_post google domaintools moniker domain daterange ).each { |lib| require "domain-finder/#{lib}" }
@@ -0,0 +1,113 @@
1
+ require 'optparse'
2
+ require 'simplecli'
3
+
4
+ class DomainFinder::Bin
5
+ include SimpleCLI
6
+
7
+ def usage *args
8
+ puts <<doco
9
+
10
+ domain-finder == %{ domain availability utility }
11
+
12
+ Usage:
13
+ domain-finder -h/--help [Not Implemented Yet]
14
+ domain-finder -v/--version [Not Implemented Yet]
15
+ domain-finder command [options]
16
+
17
+ Examples:
18
+ domain-finder auction
19
+
20
+ Further help:
21
+ domain-finder commands # list all available commands
22
+ domain-finder help <COMMAND> # show help for COMMAND
23
+ domain-finder help # show this help message
24
+
25
+ doco
26
+ end
27
+
28
+ def available_help
29
+ <<doco
30
+ Usage: #{ script_name } available domain.com[ domain2.com]
31
+
32
+ Summary:
33
+ Check the availability of domains (max 500)
34
+ doco
35
+ end
36
+ def available *domains
37
+ available_domains = Domain.available? *domains
38
+
39
+ unless available_domains.empty?
40
+ puts "\nAvailable Domains:\n------------------"
41
+ available_domains.sort.each do |domain|
42
+ puts "- #{domain}"
43
+ end
44
+ else
45
+ puts "No Domains Available"
46
+ end
47
+ end
48
+
49
+ def auction_help
50
+ <<doco
51
+ Usage: #{ script_name } auction
52
+
53
+ Options:
54
+ -d, --dates Set of dates to use in search
55
+ -k, --keywords Comma-delimited keywords for domain titles
56
+ -p, --pages Number of Google pages to gather domains from
57
+ -f, --file Name of a text file to save available domain names to
58
+
59
+ Examples:
60
+ #{ script_name } auction --dates 05-01-2008,06-10-2008..06.15.2008
61
+ #{ script_name } auction --keywords seo,blog --dates 06-01-2008..07-01-2008
62
+ #{ script_name } auction -f domains.txt --keywords cat,dog -p 2
63
+
64
+ Summary:
65
+ Display available auction domains
66
+ doco
67
+ end
68
+ def auction *args
69
+ begin
70
+ options = { :dates => [Date.today - 14], :pages => 1 }
71
+ opts = OptionParser.new do |opts|
72
+ opts.on('-d','--dates DATE'){ |d| options[:dates] = DateRange.parse(d) }
73
+ opts.on('-k','--keywords WORDS'){ |w| options[:keywords] = w.split(',') }
74
+ opts.on('-p','--pages PAGES'){ |p| options[:pages] = p.to_i }
75
+ opts.on('-f','--file FILE'){ |f| options[:file] = f }
76
+ end
77
+ opts.parse! args
78
+
79
+ query = %{"Auction Date" #{ options[:dates].join(' OR ') } site:whois.domaintools.com }
80
+ query += options[:keywords].map { |k| "intitle:#{k}" }.join(' OR ') if options[:keywords]
81
+
82
+ puts "Searching google ... [#{query}]"
83
+ results = Google.search(query, :pages => options[:pages] )
84
+
85
+ print "Getting domains ."
86
+ auction_domains = results.inject([]) do |all,result|
87
+ if result.cached
88
+ print '.'
89
+ all + DomainTools.auction_domains( open(result.cached) )
90
+ else
91
+ all # no google cache for this result, continue on to the next result
92
+ end
93
+ end
94
+ print "\n"
95
+
96
+ puts "Checking availability of #{ auction_domains.length } domains ..."
97
+ available_domains = Domain.available? *auction_domains
98
+
99
+ unless available_domains.empty?
100
+ puts "\nAvailable Domains:\n------------------"
101
+ available_domains.sort.each do |domain|
102
+ puts "- #{domain}"
103
+ end
104
+ File.open(options[:file],'w'){|f| f << available_domains.sort.join("\n") } if options[:file]
105
+ else
106
+ puts "No Domains Available"
107
+ end
108
+ rescue => ex
109
+ puts "Exception: #{ex.inspect}"
110
+ end
111
+ end
112
+
113
+ end
@@ -0,0 +1,25 @@
1
+ require 'date'
2
+
3
+ # Change default Date#to_s format
4
+ class Date
5
+ def to_s
6
+ self.strftime '%m-%d-%Y'
7
+ end
8
+ end
9
+
10
+ # Simple class for helping to parse string sets of dates and date ranges
11
+ class DateRange
12
+
13
+ def self.parse text
14
+ dates = text.gsub('-','/').split(',')
15
+ dates.inject([]) do |all,this|
16
+ if this.include?'..'
17
+ range_dates = this.split('..')
18
+ all + (Date.parse(range_dates.first)..Date.parse(range_dates.last)).to_a
19
+ else
20
+ all << Date.parse(this)
21
+ end
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,9 @@
1
+ # Simple class for checking the availability of domains
2
+ # (abstracts specific website in use so API doesn't change if we switch websites)
3
+ class Domain
4
+
5
+ def self.available? *domains
6
+ Moniker.find_available *domains
7
+ end
8
+
9
+ end
@@ -0,0 +1,8 @@
1
+ # Simple class for parsing a DomainTools page
2
+ class DomainTools
3
+
4
+ def self.auction_domains html
5
+ ( Hpricot(html) / 'div.ajax.12 tr td a' ).map { |a| a.inner_text.strip }.select { |txt| not txt.empty? }
6
+ end
7
+
8
+ end
@@ -0,0 +1,53 @@
1
+ # Simple class for searching google and getting results
2
+ class Google
3
+
4
+ BASE_SEARCH_URL = "http://www.google.com/search?q="
5
+
6
+ def self.search_url query, options = {}
7
+ url = BASE_SEARCH_URL + URI.encode(query)
8
+ url += "&start=#{ (options[:page] - 1) * 10 }" if options[:page] and options[:page] != 1
9
+ url
10
+ end
11
+
12
+ def self.fetch_url url
13
+ open(url).read
14
+ end
15
+
16
+ def self.parse_results html
17
+ ( Hpricot(html) / 'div.g' ).map { |result| Result.new(result) }
18
+ end
19
+
20
+ def self.search query, options = {}
21
+ if options[:pages] and options[:pages] > 1
22
+ results = []
23
+ options[:pages].times do |i|
24
+ results += parse_results( fetch_url(search_url(query, :page => i+1)) )
25
+ end
26
+ results
27
+ else
28
+ parse_results( fetch_url(search_url(query)) )
29
+ end
30
+ end
31
+
32
+ # Simple class representing a single Google result
33
+ class Result
34
+ attr_accessor :title, :url, :snippet, :cached
35
+
36
+ def initialize result
37
+ @title = ( result / 'h2.r' ).inner_text
38
+ @url = ( result / 'h2.r a' )[0]['href']
39
+
40
+ cached_element = ( result / 'div.std nobr a' )[0]
41
+ @cached = cached_element['href'] if cached_element and cached_element.inner_text == 'Cached'
42
+
43
+ snippet_element = ( result / 'div.std' )[0]
44
+ if snippet_element
45
+ @snippet = ""
46
+ snippet_element.children.each do |this|
47
+ break if this.to_s == '<br />'
48
+ @snippet << this.inner_text
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,17 @@
1
+ # Simple class for batch searching domain availability
2
+ class Moniker
3
+
4
+ INTERNATIONAL_TEXT = "Select language Chinese Afrikaans Albanian Arabic Aragonese Armenian Assamese Asturian Avestan Awadhi Azerbaijani Balinese Baluchi Basa Bashkir Basque Belarusian Bengali Bhojpuri Bosnian Bulgarian Burmese Carib Catalan Chechen Chinese Chuvash Coptic Corsican Croatian Czech Danish Divehi Dogri Dutch English Estonian Fijian Finnish French Frisian Gaelic Georgian German Gondi Greek Gujarati Hebrew Hindi Hungarian Icelandic Indic Indonesian Ingush Irish Italian Japanese Javanese Kashmiri Kazakh Khmer Kirghiz Korean Kurdish Lao Latvian Lithuanian Luxembourgish Macedonian Malay Malayalam Maltese Maori Moldavian Nepali Norwegian Oriya Ossetian Panjabi Persian Polish Portuguese Pushto Rajasthani Romanian Russian Samoan Sanskrit Sardinian Serbian Sindhi Sinhalese Slovak Slovenian Somali Spanish Swahili Swedish Syriac Tajik Tamil Telugu Thai Tibetan Turkish Ukrainian Urdu Uzbek Vietnamese Welsh Yiddish"
5
+
6
+ def self.find_available *domains
7
+ data = { :cmd => 'check', :scope => 'batch', :domnamelist => domains.join("\n") }
8
+ parse_results SecurePost.post('https://www.moniker.com/pub/DomainCheckBatch', data)
9
+ end
10
+
11
+ def self.parse_results html
12
+ ( Hpricot(html) / "td[@style='font-family: verdana; font-size: 8pt;']").map do
13
+ |td| td.inner_text.gsub('?','').sub(INTERNATIONAL_TEXT,'').strip
14
+ end
15
+ end
16
+
17
+ end
@@ -0,0 +1,12 @@
1
+ # Simple class for helping to easily make a secure POST without messing with Net::HTTP
2
+ class SecurePost
3
+
4
+ def self.post url, data = {}
5
+ uri = URI.parse url
6
+ http = Net::HTTP.new uri.host, 443
7
+ http.use_ssl = true
8
+ resp, data = http.post(uri.path, data.map{ |k,v| "#{k}=#{v}" }.join('&'), {})
9
+ data
10
+ end
11
+
12
+ end
@@ -0,0 +1,35 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ describe DateRange do
4
+
5
+ it '.parse should parse a single date' do
6
+ DateRange.parse("01-31-2008").first.should == Date.parse("01/31/2008")
7
+ DateRange.parse("01/31/2008").first.should == Date.parse("01/31/2008")
8
+ end
9
+
10
+ it '.parse should parse comma delimited dates' do
11
+ dates = DateRange.parse("01-15-2008,01-31-2008")
12
+ dates.length.should == 2
13
+ dates.first.should == Date.parse("01/15/2008")
14
+ dates.last.should == Date.parse("01/31/2008")
15
+ end
16
+
17
+ it '.parse should parse a date range' do
18
+ dates = DateRange.parse("01-10-2008..01-14-2008")
19
+ dates.length.should == 5
20
+ dates.first.should == Date.parse("01/10/2008")
21
+ dates[1].should == Date.parse("01/11/2008")
22
+ dates.last.should == Date.parse("01/14/2008")
23
+ end
24
+
25
+ it '.parse should parse comma delimited dates and date ranges' do
26
+ dates = DateRange.parse("01-10-2008..01-14-2008,02-01-2008,06-15-2005,12-20-1000..12-24-1000")
27
+ dates.length.should == 12
28
+ dates.first.should == Date.parse("01/10/2008")
29
+ dates.last.should == Date.parse("12/24/1000")
30
+ %w( 01/13/2008 02/01/2008 06/15/2005 12/21/1000 ).each do |date|
31
+ dates.should include(Date.parse(date))
32
+ end
33
+ end
34
+
35
+ end
@@ -0,0 +1,15 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ describe DomainTools do
4
+
5
+ before(:all) do
6
+ @example_html = File.read(File.dirname(__FILE__) + "/html/domaintools-1.html")
7
+ end
8
+
9
+ it '.auction_domains should return Domains at Auction' do
10
+ domains = DomainTools.auction_domains(@example_html)
11
+ domains.length.should == 13
12
+ domains.should include("EcoRus.net")
13
+ end
14
+
15
+ end
@@ -0,0 +1,54 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ describe Google do
4
+
5
+ before(:all)do
6
+ @example_query = %{site:whois.domaintools.com "Auction Date" 06-01-2008}
7
+ @example_url = %{http://www.google.com/search?q=site:whois.domaintools.com%20%22Auction%20Date%22%2006-01-2008}
8
+ @example_html = File.read(File.dirname(__FILE__) + "/html/google-1.html")
9
+ end
10
+
11
+ it '.search_url should generate an escaped search url' do
12
+ Google.search_url(@example_query).should == @example_url
13
+ end
14
+
15
+ it '.search_url should support pagination' do
16
+ Google.search_url(@example_query, :page => 1 ).should == @example_url
17
+ Google.search_url(@example_query, :page => 2 ).should == @example_url + '&start=10'
18
+ Google.search_url(@example_query, :page => 10).should == @example_url + '&start=90'
19
+ end
20
+
21
+ it '.fetch_url should fetch the results of a full url' do
22
+ Google.should_receive(:open).with(@example_url).and_return(StringIO.new(@example_html))
23
+ Google.fetch_url(@example_url).should == @example_html
24
+ end
25
+
26
+ it '.parse_results should return array of GoogleResult objects' do
27
+ results = Google.parse_results @example_html
28
+ results.length.should == 10
29
+ first = results.first
30
+
31
+ first.title.should == "Auctionrus.com - Auction Rus"
32
+ first.url.should == "http://whois.domaintools.com/auctionrus.com"
33
+ first.cached.should == %{http://209.85.173.104/search?q=cache:f7qbaIH-UTEJ:whois.domaintools.com/auctionrus.com+site:whois.domaintools.com+%22Auction+Date%22+06-01-2008&hl=en&ct=clnk&cd=1&gl=us&ie=UTF-8}
34
+ first.snippet.should == "Domain, Auction Date. EcoRus.net, 06-01-2008. PlusRus.com, 06-01-2008. Exclusiv-Rus.com, 06-01-2008. SermonsRus.com, 06-01-2008. HaUroRus.com, 06-01-2008 ..."
35
+ end
36
+
37
+ it '.search should return GoogleResult objects for a query' do
38
+ Google.should_receive(:open).with(@example_url).and_return(StringIO.new(@example_html))
39
+ results = Google.search(@example_query)
40
+ results.length.should == 10
41
+ results.first.should be_a_kind_of(Google::Result)
42
+ end
43
+
44
+ it '.search should support pagination' do
45
+ Google.should_receive(:open).with(@example_url).and_return(StringIO.new(@example_html))
46
+ Google.should_receive(:open).with(@example_url + '&start=10').and_return(StringIO.new(@example_html))
47
+ Google.should_receive(:open).with(@example_url + '&start=20').and_return(StringIO.new(@example_html))
48
+
49
+ results = Google.search(@example_query, :pages => 3)
50
+ results.length.should == 30
51
+ results.first.should be_a_kind_of(Google::Result)
52
+ end
53
+
54
+ end