2021-10-18 17:42:25 -05:00
|
|
|
package main
|
|
|
|
|
|
|
|
// https://pkg.go.dev/github.com/tebeka/selenium#example-package
|
|
|
|
// https://github.com/domainr/whois
|
|
|
|
// https://golangexample.com/dns-lookup-using-go/
|
|
|
|
|
|
|
|
/*
|
|
|
|
chandler@xenon ~/projects/hats-domains % for i in {1..50}; do [21:17:08]
|
|
|
|
dig ${i}hats.com | grep -i nxdomain >/dev/null && echo ${i}hats.com is available
|
|
|
|
done
|
|
|
|
5hats.com is available
|
|
|
|
16hats.com is available
|
|
|
|
26hats.com is available
|
|
|
|
28hats.com is available
|
|
|
|
30hats.com is available
|
|
|
|
35hats.com is available
|
|
|
|
36hats.com is available
|
|
|
|
37hats.com is available
|
|
|
|
41hats.com is available
|
|
|
|
43hats.com is available
|
|
|
|
44hats.com is available
|
|
|
|
46hats.com is available
|
|
|
|
48hats.com is available
|
|
|
|
49hats.com is available
|
|
|
|
[1] chandler@xenon ~/projects/hats-domains %
|
|
|
|
*/
|
|
|
|
|
|
|
|
import (
|
2021-10-18 17:56:43 -05:00
|
|
|
"encoding/base64"
|
2021-10-18 17:42:25 -05:00
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"html/template"
|
|
|
|
"io"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"time"
|
|
|
|
|
2021-10-18 19:18:32 -05:00
|
|
|
"github.com/likexian/whois"
|
|
|
|
whoisparser "github.com/likexian/whois-parser"
|
2021-10-18 17:42:25 -05:00
|
|
|
"github.com/tebeka/selenium"
|
|
|
|
)
|
|
|
|
|
|
|
|
type HatsSite struct {
|
|
|
|
DomainName string
|
2021-10-18 19:18:32 -05:00
|
|
|
Available bool
|
|
|
|
FetchTime time.Time
|
|
|
|
DomainInfo *whoisparser.Domain
|
|
|
|
Registrar *whoisparser.Contact
|
|
|
|
Registrant *whoisparser.Contact
|
2021-10-18 17:56:43 -05:00
|
|
|
ScreenshotURL template.URL
|
2021-10-18 17:42:25 -05:00
|
|
|
Title string
|
|
|
|
HTTPOpen bool
|
|
|
|
HTTPSOpen bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func getSites(largest int, wd selenium.WebDriver) (sites []HatsSite, err error) {
|
2021-10-18 19:18:32 -05:00
|
|
|
// TODO: 1hat.com 0hats.com, hats.com; possibly onehat.com, twohats.com, etc
|
2021-10-18 17:42:25 -05:00
|
|
|
for i := 2; i <= largest; i++ {
|
2021-10-18 19:18:32 -05:00
|
|
|
hatsSite := HatsSite{
|
|
|
|
DomainName: fmt.Sprintf("%dhats.com", i),
|
|
|
|
FetchTime: time.Now(),
|
|
|
|
}
|
|
|
|
log.Printf("Retrieving info for %v\n", hatsSite.DomainName)
|
2021-10-18 17:42:25 -05:00
|
|
|
|
2021-10-18 19:18:32 -05:00
|
|
|
// Check if domain is registered
|
|
|
|
query_result, err := whois.Whois(hatsSite.DomainName)
|
2021-10-18 17:42:25 -05:00
|
|
|
if err != nil {
|
|
|
|
return sites, err
|
|
|
|
}
|
2021-10-18 19:18:32 -05:00
|
|
|
result, err := whoisparser.Parse(query_result)
|
|
|
|
if err == whoisparser.ErrNotFoundDomain {
|
|
|
|
hatsSite.Available = true
|
|
|
|
sites = append(sites, hatsSite)
|
|
|
|
continue
|
|
|
|
} else if err != nil {
|
|
|
|
return sites, err
|
|
|
|
}
|
|
|
|
hatsSite.Available = false
|
|
|
|
hatsSite.DomainInfo = result.Domain
|
|
|
|
hatsSite.Registrar = result.Registrar
|
|
|
|
hatsSite.Registrant = result.Registrant
|
2021-10-18 17:42:25 -05:00
|
|
|
|
2021-10-18 19:18:32 -05:00
|
|
|
// Get web page, take screenshot
|
|
|
|
err = wd.Get(fmt.Sprintf("http://%v/", hatsSite.DomainName))
|
|
|
|
if err != nil {
|
|
|
|
return sites, err
|
|
|
|
}
|
|
|
|
|
|
|
|
hatsSite.Title, err = wd.Title()
|
2021-10-18 17:42:25 -05:00
|
|
|
if err != nil {
|
|
|
|
return sites, err
|
|
|
|
}
|
|
|
|
|
|
|
|
screenshot, err := wd.Screenshot()
|
|
|
|
if err != nil {
|
|
|
|
return sites, err
|
|
|
|
}
|
2021-10-18 19:18:32 -05:00
|
|
|
hatsSite.ScreenshotURL = template.URL(fmt.Sprintf("data:image/png;base64,%v", base64.StdEncoding.EncodeToString(screenshot)))
|
2021-10-18 17:42:25 -05:00
|
|
|
|
2021-10-18 19:18:32 -05:00
|
|
|
sites = append(sites, hatsSite)
|
2021-10-18 17:42:25 -05:00
|
|
|
}
|
|
|
|
return sites, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func generateHTML(sites []HatsSite, w io.Writer) error {
|
2021-10-18 19:18:32 -05:00
|
|
|
funcs := template.FuncMap{
|
|
|
|
"parseTime": func(s string) time.Time { time, _ := time.Parse(time.RFC3339, s); return time },
|
|
|
|
}
|
|
|
|
tmpl, err := template.New("main").Funcs(funcs).Parse(`<!DOCTYPE html>
|
2021-10-18 17:42:25 -05:00
|
|
|
<html lang="en">
|
|
|
|
<head>
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
<title>{n}hats.com domains</title>
|
|
|
|
<style>
|
2021-10-18 18:02:43 -05:00
|
|
|
body { max-width: 800px; font-family: sans-serif; margin: auto; }
|
2021-10-18 17:42:25 -05:00
|
|
|
dt { font-weight: bold; }
|
2021-10-18 18:02:43 -05:00
|
|
|
img { max-width: 100%; }
|
2021-10-18 19:26:30 -05:00
|
|
|
h3 { margin-top: 100px; }
|
|
|
|
* { line-height: 1.5em; }
|
2021-10-18 17:42:25 -05:00
|
|
|
</style>
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<h1>{n}hats.com domains</h1>
|
2021-10-18 18:54:08 -05:00
|
|
|
<h3>Summary</h3>
|
2021-10-18 19:26:30 -05:00
|
|
|
<ul>
|
2021-10-18 19:18:32 -05:00
|
|
|
{{range .}}
|
|
|
|
{{if .Available}}
|
|
|
|
<li><a href="#{{.DomainName}}">{{.DomainName}}</a> – Available!</li>
|
|
|
|
{{else}}
|
|
|
|
<li><a href="#{{.DomainName}}">{{.DomainName}}</a> – Registered since {{(parseTime .DomainInfo.CreatedDate).Format "Mon Jan 2 2006" }}</li>
|
2021-10-18 18:54:08 -05:00
|
|
|
{{end}}
|
2021-10-18 19:18:32 -05:00
|
|
|
{{end}}
|
2021-10-18 19:26:30 -05:00
|
|
|
</ul>
|
2021-10-18 17:42:25 -05:00
|
|
|
{{range .}}
|
2021-10-18 18:54:08 -05:00
|
|
|
<h3 id="{{.DomainName}}">{{.DomainName}}</h3>
|
2021-10-18 17:42:25 -05:00
|
|
|
<dl>
|
|
|
|
<dt>Fetched</dt>
|
2021-10-18 18:07:44 -05:00
|
|
|
<dd>{{.FetchTime.Format "Mon Jan 2 15:04:05 -0700 MST 2006" }}</dd>
|
2021-10-18 17:42:25 -05:00
|
|
|
<dt>Owner</dt>
|
2021-10-18 19:18:32 -05:00
|
|
|
<dd><a href="mailto:{{.Registrant.Email}}">{{.Registrant.Name}} <{{.Registrant.Email}}></a></dd>
|
2021-10-18 19:28:23 -05:00
|
|
|
{{if not .Available}}
|
2021-10-18 17:42:25 -05:00
|
|
|
<dt>Since</dt>
|
2021-10-18 19:28:23 -05:00
|
|
|
<dd>{{(parseTime .DomainInfo.CreatedDate).Format "Mon Jan 2 2006" }}</dd>
|
|
|
|
{{end}}
|
|
|
|
{{with .Title}}
|
|
|
|
<dt>Title</dt>
|
|
|
|
<dd>{{.}}</dd>
|
2021-10-18 19:18:32 -05:00
|
|
|
{{end}}
|
2021-10-18 17:42:25 -05:00
|
|
|
</dl>
|
2021-10-18 19:28:23 -05:00
|
|
|
{{if .ScreenshotURL}}
|
2021-10-18 18:07:44 -05:00
|
|
|
<img src="{{.ScreenshotURL}}" alt="screenshot of {{.DomainName}} as of {{.FetchTime.Format "Mon Jan 2 15:04:05 -0700 MST 2006" }}">
|
2021-10-18 17:42:25 -05:00
|
|
|
{{end}}
|
2021-10-18 19:28:23 -05:00
|
|
|
{{end}}
|
2021-10-18 17:42:25 -05:00
|
|
|
</body>
|
|
|
|
</html>`)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return tmpl.Execute(w, sites)
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
serve := flag.Bool("serve", false, "Serve HTTP rather than writing a file")
|
|
|
|
filename := flag.String("path", "index.html", "Output filename (if -serve=false, default)")
|
|
|
|
port := flag.Int("port", 8080, "Port to serve on")
|
2021-10-18 18:55:36 -05:00
|
|
|
debug := flag.Bool("debug", false, "Enable debug logging for the selenium package")
|
2021-10-18 17:42:25 -05:00
|
|
|
largest := flag.Int("largest", 50, "largest n for {n}hats.com")
|
|
|
|
flag.Parse()
|
|
|
|
|
|
|
|
const (
|
|
|
|
geckoDriverPath = "deps/geckodriver"
|
2021-10-18 17:43:46 -05:00
|
|
|
geckoDriverPort = 8080
|
2021-10-18 17:42:25 -05:00
|
|
|
)
|
2021-10-18 17:43:46 -05:00
|
|
|
|
2021-10-18 17:42:25 -05:00
|
|
|
opts := []selenium.ServiceOption{
|
2021-10-18 17:43:46 -05:00
|
|
|
selenium.StartFrameBuffer(),
|
2021-10-18 17:42:25 -05:00
|
|
|
selenium.Output(nil),
|
|
|
|
}
|
2021-10-18 18:55:36 -05:00
|
|
|
selenium.SetDebug(*debug)
|
2021-10-18 17:43:46 -05:00
|
|
|
|
|
|
|
service, err := selenium.NewGeckoDriverService(geckoDriverPath, geckoDriverPort, opts...)
|
2021-10-18 17:42:25 -05:00
|
|
|
if err != nil {
|
2021-10-18 17:43:46 -05:00
|
|
|
panic(err)
|
2021-10-18 17:42:25 -05:00
|
|
|
}
|
|
|
|
defer service.Stop()
|
2021-10-18 17:43:46 -05:00
|
|
|
|
|
|
|
wd, err := selenium.NewRemote(nil, fmt.Sprintf("http://localhost:%d", geckoDriverPort))
|
2021-10-18 17:42:25 -05:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
defer wd.Quit()
|
|
|
|
|
|
|
|
if *serve {
|
|
|
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
// err := generateHTML(w, *largest, wd)
|
|
|
|
// if err != nil {
|
|
|
|
// w.Write([]byte(err.Error()))
|
|
|
|
// }
|
|
|
|
})
|
|
|
|
fmt.Printf("Serving on %v\n", port)
|
|
|
|
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%v", *port), nil))
|
|
|
|
} else {
|
|
|
|
file, err := os.Create(*filename)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
sites, err := getSites(*largest, wd)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
}
|
|
|
|
err = generateHTML(sites, file)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|