all repos — paprika @ 2e70eabfec188c44b6aba9721df4331a513d06f8

go rewrite of taigabot

plugins/link_handler.go (view raw)

  1package plugins
  2
  3import (
  4	"errors"
  5	"fmt"
  6	"io"
  7	"log"
  8	"net/http"
  9	"net/url"
 10	"strings"
 11
 12	"golang.org/x/net/html"
 13	"gopkg.in/irc.v3"
 14)
 15
 16// This plugin is an example of how to make something that will
 17// respond (or just have read access to) every message that comes in.
 18// The plugins.go file has a special case for handling an 'empty' Triggers string.
 19// on such a case, it will simply run Execute on every message that it sees.
 20func init() {
 21	Register(LinkHandler{})
 22}
 23
 24type LinkHandler struct{}
 25
 26func (LinkHandler) Triggers() []string {
 27	return []string{""}
 28}
 29
 30func (LinkHandler) Execute(m *irc.Message) (string, error) {
 31	// The message starts with a '.', so we ignore it.
 32	if strings.HasPrefix(m.Params[1], ".") {
 33		return "", NoReply
 34	}
 35
 36	var output strings.Builder
 37
 38	// in PRIVMSG's case, the second (first, if counting from 0) parameter
 39	// is the string that contains the complete message.
 40	for _, value := range strings.Split(m.Params[1], " ") {
 41		u, err := url.Parse(value)
 42
 43		if err != nil {
 44			continue
 45		}
 46
 47		// just a test check for the time being.
 48		// this if statement block will be used for content that is
 49		// non-generic. I.e it belongs to a specific website, like
 50		// stackoverflow or youtube.
 51		if u.Hostname() == "www.youtube.com" || u.Hostname() == "youtube.com" || u.Hostname() == "youtu.be" {
 52			// TODO finish this
 53			yt, err := YoutubeDescriptionFromUrl(u)
 54			if err != nil {
 55				return "", err
 56			}
 57			output.WriteString(yt)
 58			output.WriteByte('\n')
 59		} else if len(u.Hostname()) > 0 {
 60			desc, err := getDescriptionFromURL(value)
 61			if err != nil {
 62				log.Printf("Failed to get title from http URL")
 63				fmt.Println(err)
 64				continue
 65			}
 66			output.WriteString(fmt.Sprintf("[URL] %s (%s)\n", desc, u.Hostname()))
 67		}
 68	}
 69
 70	if output.Len() > 0 {
 71		return output.String(), nil
 72	} else {
 73		return "", NoReply // We need to NoReply so we don't consume all messages.
 74	}
 75}
 76
 77// the three funcs below are taken from:
 78// https://siongui.github.io/2016/05/10/go-get-html-title-via-net-html/
 79func isTitleElement(n *html.Node) bool {
 80	return n.Type == html.ElementNode && n.Data == "title"
 81}
 82
 83func traverse(n *html.Node) (string, bool) {
 84	if isTitleElement(n) {
 85		return n.FirstChild.Data, true
 86	}
 87
 88	for c := n.FirstChild; c != nil; c = c.NextSibling {
 89		result, ok := traverse(c)
 90		if ok {
 91			return result, ok
 92		}
 93	}
 94
 95	return "", false
 96}
 97
 98func getHtmlTitle(r io.Reader) (string, bool) {
 99	doc, err := html.Parse(r)
100	if err != nil {
101		return "", false
102	}
103
104	return traverse(doc)
105}
106
107// yoinkies from
108// https://yourbasic.org/golang/formatting-byte-size-to-human-readable-format/
109func byteCountSI(b int64) string {
110	const unit = 1000
111	if b < unit {
112		return fmt.Sprintf("%d B", b)
113	}
114	div, exp := int64(unit), 0
115	for n := b / unit; n >= unit; n /= unit {
116		div *= unit
117		exp++
118	}
119	return fmt.Sprintf("%.1f %cB",
120		float64(b)/float64(div), "kMGTPE"[exp])
121}
122
123// provides a basic, short description of whatever is inside
124// a posted URL.
125func getDescriptionFromURL(url string) (string, error) {
126	resp, err := http.Get(url)
127
128	if err != nil {
129		return "", err
130	}
131
132	defer resp.Body.Close()
133
134	mime := resp.Header.Get("content-type")
135
136	switch mime {
137	case "image/jpeg":
138		return fmt.Sprintf("JPEG image, size: %s", byteCountSI(resp.ContentLength)), nil
139	case "image/png":
140		return fmt.Sprintf("PNG image, size: %s", byteCountSI(resp.ContentLength)), nil
141	default:
142		output, ok := getHtmlTitle(resp.Body)
143
144		if !ok {
145			return "", errors.New("Failed to find <title> in html")
146		}
147
148		return output, nil
149	}
150}