aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Garrelou <simon@sixfoisneuf.fr>2023-11-09 20:48:06 +0100
committerSimon Garrelou <simon@sixfoisneuf.fr>2023-11-09 20:48:06 +0100
commitea6ef13df36ee220a336a83f4db92b3326b69bc4 (patch)
treec4624324e7d2572baa5e4083d26e77bd718feb6d
downloadnoticeme-ea6ef13df36ee220a336a83f4db92b3326b69bc4.tar.gz
noticeme-ea6ef13df36ee220a336a83f4db92b3326b69bc4.zip
First commit
-rw-r--r--.envrc1
-rw-r--r--.gitignore3
-rw-r--r--cmd/noticeme/main.go54
-rw-r--r--go.mod9
-rw-r--r--go.sum49
-rw-r--r--irc.go107
-rw-r--r--message.go64
-rw-r--r--shell.nix10
8 files changed, 297 insertions, 0 deletions
diff --git a/.envrc b/.envrc
new file mode 100644
index 0000000..1d953f4
--- /dev/null
+++ b/.envrc
@@ -0,0 +1 @@
use nix
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d23f327
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
1.direnv
2*.toml
3/noticeme
diff --git a/cmd/noticeme/main.go b/cmd/noticeme/main.go
new file mode 100644
index 0000000..f85ad5b
--- /dev/null
+++ b/cmd/noticeme/main.go
@@ -0,0 +1,54 @@
1package main
2
3import (
4 "flag"
5 "io/ioutil"
6 "log"
7 "os/exec"
8
9 "git.sixfoisneuf.fr/noticeme"
10 "github.com/pelletier/go-toml"
11)
12
13type Config struct {
14 Server string
15 Username string
16 Password string
17
18 Highlights []string
19 Command string
20}
21
22func monitorMessage(hl []string, cmd string, c chan noticeme.Message) {
23 for msg := range c {
24 if msg.IsHighlight(hl) {
25 c := exec.Command(cmd, msg.String())
26 if err := c.Run(); err != nil {
27 log.Printf("Error running command: %v", err)
28 }
29 }
30 }
31}
32
33func main() {
34 configPath := flag.String("config", "config.toml", "Path to the configuration file")
35 flag.Parse()
36
37 var cfg Config
38 data, err := ioutil.ReadFile(*configPath)
39 if err != nil {
40 log.Fatalf("error reading '%s': %v", *configPath, err)
41 }
42
43 if err := toml.Unmarshal(data, &cfg); err != nil {
44 log.Fatalf("error parsing '%s': %v", *configPath, err)
45 }
46
47 bouncer := noticeme.NewBouncer(cfg.Server, cfg.Username, cfg.Password)
48
49 go monitorMessage(cfg.Highlights, cfg.Command, bouncer.Messages)
50
51 if err := bouncer.Connect(); err != nil {
52 panic(err)
53 }
54}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..117ee4d
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,9 @@
1module git.sixfoisneuf.fr/noticeme
2
3go 1.20
4
5require (
6 git.sr.ht/~taiite/senpai v0.2.0 // indirect
7 github.com/pelletier/go-toml v1.9.5 // indirect
8 golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 // indirect
9)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..e148884
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,49 @@
1git.sr.ht/~emersion/go-scfg v0.0.0-20201019143924-142a8aa629fc/go.mod h1:t+Ww6SR24yYnXzEWiNlOY0AFo5E9B73X++10lrSpp4U=
2git.sr.ht/~taiite/senpai v0.2.0 h1:a9ZzJj6gKFJ5gz+s6sVcq94DSCPrzFFR/hIh6yONS+U=
3git.sr.ht/~taiite/senpai v0.2.0/go.mod h1:oSJWECrxhhe0IFO8mJLn5YtP5Ze/r70W6fuCHmhwqCE=
4github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5github.com/delthas/go-libnp v0.0.0-20221222161248-0e45ece1f878/go.mod h1:aGVXnhWpDlt5U4SphG97o1gszctZKvBTXy320E8Buw4=
6github.com/delthas/go-localeinfo v0.0.0-20221116001557-686a1e185118/go.mod h1:sG54BxlyQgIskYURLrg7mvhoGBe0Qq12DNtYRALwNa4=
7github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
8github.com/gdamore/tcell/v2 v2.5.4-0.20221017224006-ede1dd5ee680/go.mod h1:XmCynGHvvGG7UFI6az9zzoEHBvZB1PGf5APwOJMFUyE=
9github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
10github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
11github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
12github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
13github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
14github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
15github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
16github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
17github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
18github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
19github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
20github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
21github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
22golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
23golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
24golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
25golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
26golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
27golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
28golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
29golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
30golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
31golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
32golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
33golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
34golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
35golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
36golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
37golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
38golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
39golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
40golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
41golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 h1:Vv0JUPWTyeqUq42B2WJ1FeIDjjvGKoA2Ss+Ts0lAVbs=
42golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
43golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
44golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
45golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
46golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
47gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
48gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
49mvdan.cc/xurls/v2 v2.3.0/go.mod h1:AjuTy7gEiUArFMjgBBDU4SMxlfUYsRokpJQgNWOt3e4=
diff --git a/irc.go b/irc.go
new file mode 100644
index 0000000..ecf3d72
--- /dev/null
+++ b/irc.go
@@ -0,0 +1,107 @@
1package noticeme
2
3import (
4 "fmt"
5 "log"
6 "net"
7
8 "git.sr.ht/~taiite/senpai/irc"
9)
10
11type network struct {
12 NetID string
13
14 in <-chan irc.Message
15 out chan<- irc.Message
16 session *irc.Session
17}
18
19type Bouncer struct {
20 networks []*network
21
22 Server string
23 Username, Password string
24
25 Messages chan Message
26}
27
28func NewBouncer(server, username, password string) *Bouncer {
29 b := Bouncer{
30 Server: server,
31 Username: username,
32 Password: password,
33 }
34 b.Messages = make(chan Message, 100)
35
36 return &b
37}
38
39func (b *Bouncer) Connect() error {
40 return b.newNetwork("", false)
41}
42
43func (b *Bouncer) newNetwork(netID string, background bool) error {
44 auth := irc.SASLPlain{
45 Username: b.Username,
46 Password: b.Password,
47 }
48
49 params := irc.SessionParams{
50 Nickname: "NoticeMe",
51 Username: b.Username,
52 RealName: b.Username,
53 NetID: netID,
54 Auth: &auth,
55 }
56
57 conn, err := net.Dial("tcp", b.Server)
58 if err != nil {
59 return fmt.Errorf("net.Dial: %w", err)
60 }
61
62 n := &network{
63 NetID: netID,
64 }
65
66 n.in, n.out = irc.ChanInOut(conn)
67 n.session = irc.NewSession(n.out, params)
68
69 b.networks = append(b.networks, n)
70
71 if background {
72 go b.monitorNetwork(n)
73 } else {
74 b.monitorNetwork(n)
75 }
76
77 return nil
78}
79
80func (b *Bouncer) hasNetID(netID string) bool {
81 for _, net := range b.networks {
82 if net.NetID == netID {
83 return true
84 }
85 }
86 return false
87}
88
89func (b *Bouncer) monitorNetwork(n *network) {
90 log.Printf("Monitoring NetID<%s>", n.NetID)
91
92 for msg := range n.in {
93 switch msg.Command {
94 case "PRIVMSG":
95 b.Messages <- fromIrcMessage(msg, n.session)
96 case "BOUNCER":
97 if msg.Params[0] != "NETWORK" {
98 log.Printf("[%s] Invalid BOUNCER message, stopping. msg: %+v", n.NetID, msg)
99 break
100 }
101
102 if !b.hasNetID(msg.Params[1]) {
103 b.newNetwork(msg.Params[1], true)
104 }
105 }
106 }
107}
diff --git a/message.go b/message.go
new file mode 100644
index 0000000..19c4e30
--- /dev/null
+++ b/message.go
@@ -0,0 +1,64 @@
1package noticeme
2
3import (
4 "fmt"
5 "strings"
6
7 "git.sr.ht/~taiite/senpai/irc"
8)
9
10type Message struct {
11 Channel string // a #channel or a user
12 Author string
13
14 Text string
15
16 isMe bool
17}
18
19func fromIrcMessage(msg irc.Message, sess *irc.Session) Message {
20 user := msg.Prefix.Name
21 channel := msg.Params[0]
22 txt := msg.Params[1]
23
24 return Message{
25 Channel: channel,
26 Author: user,
27 Text: txt,
28 isMe: sess.IsMe(user),
29 }
30}
31
32func (m Message) IsPrivate() bool {
33 return m.Channel[0] != '#'
34}
35
36func (m Message) IsHighlight(matches []string) bool {
37 // special case: if the message is a PM and is not
38 // sent by us, highlight
39 if m.IsPrivate() && !m.isMe {
40 return true
41 }
42
43 lower := make([]string, len(matches))
44
45 for i, m := range matches {
46 lower[i] = strings.ToLower(m)
47 }
48
49 for _, l := range lower {
50 if strings.Contains(strings.ToLower(m.Text), l) {
51 return true
52 }
53 }
54
55 return false
56}
57func (m Message) String() string {
58 c := m.Channel
59
60 if m.IsPrivate() {
61 c = "private"
62 }
63 return fmt.Sprintf("[%s] <%s> %s", c, m.Author, m.Text)
64}
diff --git a/shell.nix b/shell.nix
new file mode 100644
index 0000000..867d0d3
--- /dev/null
+++ b/shell.nix
@@ -0,0 +1,10 @@
1let
2 pkgs = import <nixpkgs> {};
3in
4pkgs.mkShell {
5 packages = [
6 pkgs.go
7 pkgs.gopls
8 pkgs.gotools
9 ];
10}