all repos — paprika @ 4c12596cb5e4bd954d9e16a486ade62c0cb21409

go rewrite of taigabot

plugins/link-handler.go (view raw)

  1// Plugin that looks for links and provides additional info whenever
  2// someone posts an URL.
  3// Author: nojusr
  4package plugins
  5
  6import (
  7	"errors"
  8	"fmt"
  9	"io"
 10	"log"
 11	"net/http"
 12	"net/url"
 13	"strings"
 14
 15	"golang.org/x/net/html"
 16	"gopkg.in/irc.v3"
 17)
 18
 19// This plugin is an example of how to make something that will
 20// respond (or just have read access to) every message that comes in.
 21// The plugins.go file has a special case for handling an 'empty' Triggers string.
 22// on such a case, it will simply run Execute on every message that it sees.
 23
 24func init() {
 25	Register(LinkHandler{})
 26}
 27
 28type LinkHandler struct{}
 29
 30func (LinkHandler) Triggers() []string {
 31	return []string{""} // More than a single empty string here will probs get you undefined behaviour
 32}
 33
 34func (LinkHandler) Execute(m *irc.Message) (string, error) {
 35
 36	var output string
 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() == "youtube.com" || u.Hostname() == "youtu.be" {
 52			// TODO finish this
 53			output += "[Youtube] yeah you definitely posted a youtube link\n"
 54		} else if len(u.Hostname()) > 0 {
 55			desc, err := getDescriptionFromURL(value)
 56			if err != nil {
 57				log.Printf("Failed to get title from http URL")
 58				fmt.Println(err)
 59				continue
 60			}
 61			output += fmt.Sprintf("[URL] %s (%s)\n", desc, u.Hostname())
 62		}
 63	}
 64
 65	return output, nil
 66}
 67
 68// the three funcs below are taken from:
 69// https://siongui.github.io/2016/05/10/go-get-html-title-via-net-html/
 70func isTitleElement(n *html.Node) bool {
 71	return n.Type == html.ElementNode && n.Data == "title"
 72}
 73
 74func traverse(n *html.Node) (string, bool) {
 75	if isTitleElement(n) {
 76		return n.FirstChild.Data, true
 77	}
 78
 79	for c := n.FirstChild; c != nil; c = c.NextSibling {
 80		result, ok := traverse(c)
 81		if ok {
 82			return result, ok
 83		}
 84	}
 85
 86	return "", false
 87}
 88
 89func getHtmlTitle(r io.Reader) (string, bool) {
 90	doc, err := html.Parse(r)
 91	if err != nil {
 92		return "", false
 93	}
 94
 95	return traverse(doc)
 96}
 97
 98// yoinkies from
 99// https://yourbasic.org/golang/formatting-byte-size-to-human-readable-format/
100func byteCountSI(b int64) string {
101	const unit = 1000
102	if b < unit {
103		return fmt.Sprintf("%d B", b)
104	}
105	div, exp := int64(unit), 0
106	for n := b / unit; n >= unit; n /= unit {
107		div *= unit
108		exp++
109	}
110	return fmt.Sprintf("%.1f %cB",
111		float64(b)/float64(div), "kMGTPE"[exp])
112}
113
114// provides a basic, short description of whatever is inside
115// a posted URL.
116func getDescriptionFromURL(url string) (string, error) {
117	resp, err := http.Get(url)
118
119	if err != nil {
120		return "", err
121	}
122
123	defer resp.Body.Close()
124
125	mime := resp.Header.Get("content-type")
126
127	switch mime {
128	case "image/jpeg":
129		return fmt.Sprintf("JPEG image, size: %s", byteCountSI(resp.ContentLength)), nil
130	case "image/png":
131		return fmt.Sprintf("PNG image, size: %s", byteCountSI(resp.ContentLength)), nil
132	default:
133		output, ok := getHtmlTitle(resp.Body)
134
135		if !ok {
136			return "", errors.New("Failed to find <title> in html")
137		}
138
139		return output, nil
140	}
141}