Blame


1 7cc05214 2021-04-27 op package main
2 7cc05214 2021-04-27 op
3 7cc05214 2021-04-27 op import (
4 7cc05214 2021-04-27 op "crypto/tls"
5 7cc05214 2021-04-27 op "flag"
6 7cc05214 2021-04-27 op "fmt"
7 7cc05214 2021-04-27 op "io"
8 7cc05214 2021-04-27 op "log"
9 7cc05214 2021-04-27 op "net/http"
10 657af0a4 2022-07-03 op "os"
11 ae97f748 2021-07-30 op "path"
12 5ad39add 2021-04-27 op "path/filepath"
13 7cc05214 2021-04-27 op "regexp"
14 88370dfe 2021-11-07 op "strings"
15 7cc05214 2021-04-27 op
16 deee939b 2022-01-08 op "github.com/andybalholm/cascadia"
17 7cc05214 2021-04-27 op irc "github.com/fluffle/goirc/client"
18 5659492e 2021-12-02 op "golang.org/x/net/html"
19 7cc05214 2021-04-27 op )
20 7cc05214 2021-04-27 op
21 7cc05214 2021-04-27 op var (
22 88370dfe 2021-11-07 op baseurl = flag.String("baseurl", "gemini://m2i.omarpolo.com", "base url")
23 7cc05214 2021-04-27 op matrixOutDir = flag.String("matrix-out", "", "matrix out directory")
24 7cc05214 2021-04-27 op
25 88370dfe 2021-11-07 op msgRe = regexp.MustCompile(`https://.*/[^\s]+\.(txt|png|jpg|jpeg|gif)`)
26 c6fd4def 2021-07-30 op channel = "#gemini-it"
27 88370dfe 2021-11-07 op
28 88370dfe 2021-11-07 op tooLongRe = regexp.MustCompile(`full message at (https://libera.ems.host/.*)[)]`)
29 5659492e 2021-12-02 op
30 4852f411 2021-12-02 op httplink = regexp.MustCompile(`https?://[^\s)]+`)
31 cfb62e6e 2022-01-08 op gemlink = regexp.MustCompile(`gemini://[^\s)]+`)
32 7cc05214 2021-04-27 op )
33 7cc05214 2021-04-27 op
34 7cc05214 2021-04-27 op func matrix2gemini(conn *irc.Conn, line *irc.Line) {
35 ae97f748 2021-07-30 op matches := msgRe.FindAllString(line.Text(), -1)
36 7cc05214 2021-04-27 op
37 7cc05214 2021-04-27 op // it's not a good idea to defer inside a loop, but we know
38 7cc05214 2021-04-27 op // len(matches) is small (usually just 1). Morover, I like
39 7cc05214 2021-04-27 op // living in danger!
40 7cc05214 2021-04-27 op
41 7cc05214 2021-04-27 op for _, link := range matches {
42 7cc05214 2021-04-27 op resp, err := http.Get(link)
43 7cc05214 2021-04-27 op if err != nil {
44 7cc05214 2021-04-27 op conn.Privmsg(
45 7cc05214 2021-04-27 op channel,
46 7cc05214 2021-04-27 op fmt.Sprintf("failed to download %q: %s", link, err),
47 7cc05214 2021-04-27 op )
48 7cc05214 2021-04-27 op continue
49 7cc05214 2021-04-27 op }
50 7cc05214 2021-04-27 op defer resp.Body.Close()
51 7cc05214 2021-04-27 op
52 ae97f748 2021-07-30 op ext := path.Ext(link)
53 657af0a4 2022-07-03 op tmpfile, err := os.CreateTemp(*matrixOutDir, "message-*"+ext)
54 7cc05214 2021-04-27 op if err != nil {
55 7cc05214 2021-04-27 op conn.Privmsg(channel, fmt.Sprintf("failed to tmpfile: %s", err))
56 7cc05214 2021-04-27 op return
57 7cc05214 2021-04-27 op }
58 7cc05214 2021-04-27 op defer tmpfile.Close()
59 7cc05214 2021-04-27 op
60 657af0a4 2022-07-03 op os.Chmod(tmpfile.Name(), 0644)
61 657af0a4 2022-07-03 op
62 7cc05214 2021-04-27 op io.Copy(tmpfile, resp.Body)
63 7cc05214 2021-04-27 op
64 7cc05214 2021-04-27 op conn.Privmsg(
65 7cc05214 2021-04-27 op channel,
66 5ad39add 2021-04-27 op fmt.Sprintf(
67 5ad39add 2021-04-27 op "better: %s/%s",
68 5ad39add 2021-04-27 op *baseurl,
69 5ad39add 2021-04-27 op filepath.Base(tmpfile.Name()),
70 5ad39add 2021-04-27 op ),
71 7cc05214 2021-04-27 op )
72 7cc05214 2021-04-27 op }
73 7cc05214 2021-04-27 op }
74 7cc05214 2021-04-27 op
75 88370dfe 2021-11-07 op func messageTooLong(conn *irc.Conn, line *irc.Line) {
76 88370dfe 2021-11-07 op matches := tooLongRe.FindStringSubmatch(line.Text())
77 88370dfe 2021-11-07 op if len(matches) != 2 {
78 88370dfe 2021-11-07 op return
79 88370dfe 2021-11-07 op }
80 88370dfe 2021-11-07 op
81 88370dfe 2021-11-07 op url := matches[1]
82 88370dfe 2021-11-07 op
83 88370dfe 2021-11-07 op resp, err := http.Get(url)
84 88370dfe 2021-11-07 op if err != nil {
85 88370dfe 2021-11-07 op conn.Privmsg(
86 88370dfe 2021-11-07 op channel,
87 88370dfe 2021-11-07 op fmt.Sprintf("failed to download %q: %s", url, err),
88 88370dfe 2021-11-07 op )
89 88370dfe 2021-11-07 op return
90 88370dfe 2021-11-07 op }
91 88370dfe 2021-11-07 op defer resp.Body.Close()
92 88370dfe 2021-11-07 op
93 88370dfe 2021-11-07 op sb := &strings.Builder{}
94 88370dfe 2021-11-07 op if _, err := io.Copy(sb, resp.Body); err != nil {
95 88370dfe 2021-11-07 op conn.Privmsg(
96 88370dfe 2021-11-07 op channel,
97 88370dfe 2021-11-07 op fmt.Sprintf("failed to read body of %q: %s", url, err),
98 88370dfe 2021-11-07 op )
99 88370dfe 2021-11-07 op return
100 88370dfe 2021-11-07 op }
101 88370dfe 2021-11-07 op
102 88370dfe 2021-11-07 op conn.Privmsg(channel, fmt.Sprintf("%s ha detto:", line.Nick))
103 88370dfe 2021-11-07 op for _, line := range strings.Split(sb.String(), "\n") {
104 88370dfe 2021-11-07 op conn.Privmsg(
105 88370dfe 2021-11-07 op channel,
106 88370dfe 2021-11-07 op line,
107 88370dfe 2021-11-07 op )
108 88370dfe 2021-11-07 op }
109 88370dfe 2021-11-07 op }
110 88370dfe 2021-11-07 op
111 3adf60f8 2021-12-02 op func stringifyNode(node *html.Node) string {
112 3adf60f8 2021-12-02 op s := ""
113 3adf60f8 2021-12-02 op
114 3adf60f8 2021-12-02 op if node.Type == html.TextNode {
115 3adf60f8 2021-12-02 op return node.Data
116 3adf60f8 2021-12-02 op }
117 3adf60f8 2021-12-02 op
118 3adf60f8 2021-12-02 op for child := node.FirstChild; child != nil; child = child.NextSibling {
119 3adf60f8 2021-12-02 op s += stringifyNode(child)
120 3adf60f8 2021-12-02 op }
121 3adf60f8 2021-12-02 op
122 3adf60f8 2021-12-02 op return s
123 3adf60f8 2021-12-02 op }
124 3adf60f8 2021-12-02 op
125 cfb62e6e 2022-01-08 op func wwwpagetitle(conn *irc.Conn, line *irc.Line) {
126 5659492e 2021-12-02 op matches := httplink.FindAllString(line.Text(), -1)
127 5659492e 2021-12-02 op
128 5659492e 2021-12-02 op for _, link := range matches {
129 d26637f1 2021-12-02 op log.Println("fetching", link, "...")
130 d26637f1 2021-12-02 op
131 5659492e 2021-12-02 op resp, err := http.Get(link)
132 5659492e 2021-12-02 op if err != nil {
133 5659492e 2021-12-02 op continue
134 5659492e 2021-12-02 op }
135 5659492e 2021-12-02 op defer resp.Body.Close()
136 5659492e 2021-12-02 op
137 5659492e 2021-12-02 op doc, err := html.Parse(resp.Body)
138 5659492e 2021-12-02 op if err != nil {
139 5659492e 2021-12-02 op continue
140 5659492e 2021-12-02 op }
141 5659492e 2021-12-02 op
142 deee939b 2022-01-08 op sel := cascadia.MustCompile("head > title")
143 deee939b 2022-01-08 op n := cascadia.Query(doc, sel)
144 deee939b 2022-01-08 op
145 deee939b 2022-01-08 op if n == nil {
146 5659492e 2021-12-02 op continue
147 5659492e 2021-12-02 op }
148 deee939b 2022-01-08 op
149 0c3e9562 2022-01-08 op title := stringifyNode(n)
150 0c3e9562 2022-01-08 op if len(title) > 50 {
151 0c3e9562 2022-01-08 op title = title[:50]
152 0c3e9562 2022-01-08 op }
153 deee939b 2022-01-08 op conn.Privmsg(channel, stringifyNode(n))
154 5659492e 2021-12-02 op }
155 5659492e 2021-12-02 op }
156 5659492e 2021-12-02 op
157 cfb62e6e 2022-01-08 op func gempagetitle(conn *irc.Conn, line *irc.Line) {
158 cfb62e6e 2022-01-08 op matches := gemlink.FindAllString(line.Text(), -1)
159 cfb62e6e 2022-01-08 op
160 cfb62e6e 2022-01-08 op for _, link := range matches {
161 cfb62e6e 2022-01-08 op log.Println("fetching", link, "...")
162 cfb62e6e 2022-01-08 op title, err := geminiTitle(link)
163 cfb62e6e 2022-01-08 op if err != nil {
164 cfb62e6e 2022-01-08 op continue
165 cfb62e6e 2022-01-08 op }
166 cfb62e6e 2022-01-08 op
167 cfb62e6e 2022-01-08 op conn.Privmsg(channel, title)
168 cfb62e6e 2022-01-08 op }
169 cfb62e6e 2022-01-08 op }
170 cfb62e6e 2022-01-08 op
171 56842f40 2022-06-29 op func pong(conn *irc.Conn, line *irc.Line) {
172 56842f40 2022-06-29 op if line.Text() != ",ping" {
173 56842f40 2022-06-29 op return
174 56842f40 2022-06-29 op }
175 56842f40 2022-06-29 op conn.Privmsg(
176 56842f40 2022-06-29 op channel,
177 56842f40 2022-06-29 op "pong :)",
178 56842f40 2022-06-29 op )
179 56842f40 2022-06-29 op }
180 56842f40 2022-06-29 op
181 7cc05214 2021-04-27 op func dostuff(conn *irc.Conn, line *irc.Line) {
182 7cc05214 2021-04-27 op matrix2gemini(conn, line)
183 88370dfe 2021-11-07 op messageTooLong(conn, line)
184 cfb62e6e 2022-01-08 op wwwpagetitle(conn, line)
185 cfb62e6e 2022-01-08 op gempagetitle(conn, line)
186 56842f40 2022-06-29 op pong(conn, line)
187 7cc05214 2021-04-27 op // ...
188 7cc05214 2021-04-27 op }
189 7cc05214 2021-04-27 op
190 7cc05214 2021-04-27 op func main() {
191 7cc05214 2021-04-27 op flag.Parse()
192 7cc05214 2021-04-27 op
193 7cc05214 2021-04-27 op cfg := irc.NewConfig("gemitbot")
194 7cc05214 2021-04-27 op cfg.SSL = true
195 d0286003 2021-07-30 op cfg.SSLConfig = &tls.Config{ServerName: "irc.libera.chat"}
196 d0286003 2021-07-30 op cfg.Server = "irc.libera.chat:7000"
197 7cc05214 2021-04-27 op cfg.NewNick = func(n string) string { return n + "^" }
198 7cc05214 2021-04-27 op
199 7cc05214 2021-04-27 op c := irc.Client(cfg)
200 7cc05214 2021-04-27 op
201 7cc05214 2021-04-27 op c.HandleFunc(irc.CONNECTED, func(conn *irc.Conn, line *irc.Line) {
202 7cc05214 2021-04-27 op log.Println("connected, joining", channel)
203 7cc05214 2021-04-27 op conn.Join(channel)
204 7cc05214 2021-04-27 op })
205 7cc05214 2021-04-27 op
206 7cc05214 2021-04-27 op c.HandleFunc(irc.PRIVMSG, dostuff)
207 7cc05214 2021-04-27 op c.HandleFunc(irc.ACTION, dostuff)
208 7cc05214 2021-04-27 op
209 7cc05214 2021-04-27 op quit := make(chan bool)
210 7cc05214 2021-04-27 op
211 7cc05214 2021-04-27 op c.HandleFunc(irc.DISCONNECTED, func(conn *irc.Conn, line *irc.Line) {
212 7cc05214 2021-04-27 op quit <- true
213 7cc05214 2021-04-27 op })
214 7cc05214 2021-04-27 op
215 7cc05214 2021-04-27 op if err := c.Connect(); err != nil {
216 7cc05214 2021-04-27 op log.Fatalln("connection error:", err)
217 7cc05214 2021-04-27 op }
218 7cc05214 2021-04-27 op
219 7cc05214 2021-04-27 op <-quit
220 7cc05214 2021-04-27 op }