Adds search plugin. (#1) * add search * Formatting and consistency Co-authored-by: Anirudh Oppiliappan <x@icyphox.sh>
Anthony DeDominic adedomin@gmail.com
Tue, 16 Nov 2021 00:22:58 -0500
4 files changed,
167 insertions(+),
4 deletions(-)
M
go.mod
→
go.mod
@@ -3,6 +3,7 @@
go 1.17 require ( + golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 github.com/dgraph-io/badger/v3 v3.2103.2 github.com/dustin/go-humanize v1.0.0 gopkg.in/irc.v3 v3.1.4@@ -23,4 +24,4 @@ github.com/pkg/errors v0.9.1 // indirect
go.opencensus.io v0.22.5 // indirect golang.org/x/net v0.0.0-20201021035429-f5854403a974 // indirect golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect -) +)
M
go.sum
→
go.sum
@@ -71,6 +71,13 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
M
main.go
→
main.go
@@ -18,7 +18,7 @@ c.Write("JOIN #taigobot-test")
case "PRIVMSG": response, err := plugins.ProcessTrigger(m) if err != nil { - c.Writef("error: %v", err) + log.Printf("error: %v", err) } c.WriteMessage(&irc.Message{ Command: "PRIVMSG",@@ -38,9 +38,9 @@ log.Fatal(err)
} config := irc.ClientConfig{ - Nick: "taigobot", + Nick: "taigobot112", Pass: "", - User: "taigobot", + User: "taigobot112", Name: "taigobot", Handler: irc.HandlerFunc(ircHandler), }
A
plugins/search.go
@@ -0,0 +1,155 @@
+package plugins + +import ( + "bytes" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + "golang.org/x/net/html" + "gopkg.in/irc.v3" +) + +func init() { + Register(Search{}) +} + +type Search struct{} + +func (Search) Triggers() []string { + return []string{".ddg", ".g", ".mdn", ".wiki"} +} + +var ddgClient = &http.Client{ + Timeout: 10 * time.Second, +} + +func ddgParse(body io.Reader) (string, error) { + inResult := false + href := "" + var snippet strings.Builder + tok := html.NewTokenizer(body) + + for tt := tok.Next(); tt != html.ErrorToken; tt = tok.Next() { + switch tt { + case html.StartTagToken: + tag, hasAttr := tok.TagName() + + if tag[0] == 'a' { + if hasAttr { + var key []byte + var val []byte + var lhref []byte + isRes := false + + for hasAttr { + key, val, hasAttr = tok.TagAttr() + if bytes.Equal(key, []byte("href")) { + lhref = val + } else if bytes.Equal(val, []byte("result__snippet")) { + isRes = true + } + } + + if isRes && len(lhref) != 0 { + realUrl, err := url.Parse(string(lhref)) + if err == nil { + if v, ok := realUrl.Query()["uddg"]; len(v) > 0 && ok { + inResult = true + href = v[0] + } + } + } + } + } else if tag[0] == 'b' { // support bold text + //snippet.WriteRune('\x02') + } + case html.TextToken: + if inResult && snippet.Len() < 300 { + snippet.Write(tok.Text()) + } + case html.EndTagToken: + tag, _ := tok.TagName() + + if inResult && tag[0] == 'a' { + var len int + if snippet.Len() > 300 { + len = 300 + } else { + len = snippet.Len() + } + return snippet.String()[:len] + " - " + href, nil + } else if inResult && tag[0] == 'b' { + //snippet.WriteRune('\x02') + } + } + } + + if err := tok.Err(); err != io.EOF && err != nil { + return "HTML parse error", err + } else { + return "No results.", nil + } +} + +// Use DuckDuckGo's +func ddg(query string) (string, error) { + req, err := http.NewRequest("GET", "https://html.duckduckgo.com/html", nil) + + if err != nil { + return "Client request error", err + } + + req.Header.Add("User-Agent", "github.com/icyphox/Taigobot") + + q := req.URL.Query() + q.Add("q", query) + req.URL.RawQuery = q.Encode() + + res, err := ddgClient.Do(req) + if err != nil { + return "Server response error", err + } + + defer res.Body.Close() + result, err := ddgParse(res.Body) + if err != nil { + return "HTML parse error", err + } + return result, nil +} + +// This is just an alias for now. +func google(query string) (string, error) { + return ddg(query) +} + +func mdn(query string) (string, error) { + return ddg("site:https://developer.mozilla.org/en-US " + query) +} + +func wiki(query string) (string, error) { + return ddg("site:https://en.wikipedia.org " + query) +} + +func (Search) Execute(m *irc.Message) (string, error) { + parsed := strings.SplitN(m.Trailing(), " ", 2) + if len(parsed) != 2 { + return fmt.Sprintf("Usage: %s <query>", parsed[0]), nil + } + trigger, query := parsed[0], parsed[1] + + switch trigger { + case ".mdn": + return mdn(query) + case ".wiki": + return wiki(query) + case ".g": + return google(query) + default: + return ddg(query) + } +}