16 "github.com/andybalholm/cascadia"
17 irc "github.com/fluffle/goirc/client"
18 "golang.org/x/net/html"
22 baseurl = flag.String("baseurl", "gemini://m2i.omarpolo.com", "base url")
23 matrixOutDir = flag.String("matrix-out", "", "matrix out directory")
25 msgRe = regexp.MustCompile(`https://.*/[^\s]+\.(txt|png|jpg|jpeg|gif)`)
26 channel = "#gemini-it"
28 tooLongRe = regexp.MustCompile(`full message at (https://libera.ems.host/.*)[)]`)
30 httplink = regexp.MustCompile(`https?://[^\s)]+`)
31 gemlink = regexp.MustCompile(`gemini://[^\s)]+`)
34 func matrix2gemini(conn *irc.Conn, line *irc.Line) {
35 matches := msgRe.FindAllString(line.Text(), -1)
37 // it's not a good idea to defer inside a loop, but we know
38 // len(matches) is small (usually just 1). Morover, I like
41 for _, link := range matches {
42 resp, err := http.Get(link)
46 fmt.Sprintf("failed to download %q: %s", link, err),
50 defer resp.Body.Close()
53 tmpfile, err := ioutil.TempFile(*matrixOutDir, "message-*"+ext)
55 conn.Privmsg(channel, fmt.Sprintf("failed to tmpfile: %s", err))
60 io.Copy(tmpfile, resp.Body)
67 filepath.Base(tmpfile.Name()),
73 func messageTooLong(conn *irc.Conn, line *irc.Line) {
74 matches := tooLongRe.FindStringSubmatch(line.Text())
75 if len(matches) != 2 {
81 resp, err := http.Get(url)
85 fmt.Sprintf("failed to download %q: %s", url, err),
89 defer resp.Body.Close()
91 sb := &strings.Builder{}
92 if _, err := io.Copy(sb, resp.Body); err != nil {
95 fmt.Sprintf("failed to read body of %q: %s", url, err),
100 conn.Privmsg(channel, fmt.Sprintf("%s ha detto:", line.Nick))
101 for _, line := range strings.Split(sb.String(), "\n") {
109 func stringifyNode(node *html.Node) string {
112 if node.Type == html.TextNode {
116 for child := node.FirstChild; child != nil; child = child.NextSibling {
117 s += stringifyNode(child)
123 func wwwpagetitle(conn *irc.Conn, line *irc.Line) {
124 log.Println("text is", line.Text())
125 matches := httplink.FindAllString(line.Text(), -1)
127 for _, link := range matches {
128 log.Println("fetching", link, "...")
130 resp, err := http.Get(link)
134 defer resp.Body.Close()
136 doc, err := html.Parse(resp.Body)
141 sel := cascadia.MustCompile("head > title")
142 n := cascadia.Query(doc, sel)
148 title := stringifyNode(n)
152 conn.Privmsg(channel, stringifyNode(n))
156 func gempagetitle(conn *irc.Conn, line *irc.Line) {
157 matches := gemlink.FindAllString(line.Text(), -1)
159 for _, link := range matches {
160 log.Println("fetching", link, "...")
161 title, err := geminiTitle(link)
166 conn.Privmsg(channel, title)
170 func dostuff(conn *irc.Conn, line *irc.Line) {
171 matrix2gemini(conn, line)
172 messageTooLong(conn, line)
173 wwwpagetitle(conn, line)
174 gempagetitle(conn, line)
181 cfg := irc.NewConfig("gemitbot")
183 cfg.SSLConfig = &tls.Config{ServerName: "irc.libera.chat"}
184 cfg.Server = "irc.libera.chat:7000"
185 cfg.NewNick = func(n string) string { return n + "^" }
189 c.HandleFunc(irc.CONNECTED, func(conn *irc.Conn, line *irc.Line) {
190 log.Println("connected, joining", channel)
194 c.HandleFunc(irc.PRIVMSG, dostuff)
195 c.HandleFunc(irc.ACTION, dostuff)
197 quit := make(chan bool)
199 c.HandleFunc(irc.DISCONNECTED, func(conn *irc.Conn, line *irc.Line) {
203 if err := c.Connect(); err != nil {
204 log.Fatalln("connection error:", err)