From 92e328314a3bf690d8d4b85449348a9d869d6496 Mon Sep 17 00:00:00 2001 From: Christine Dodrill Date: Wed, 10 May 2017 14:30:31 -0700 Subject: [PATCH] initial commit --- .gitignore | 2 ++ README.md | 15 +++++++++ main.go | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a645e14 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.env +*.so diff --git a/README.md b/README.md new file mode 100644 index 0000000..3293866 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# ircsuite + +Like tootsuite but for IRC. + +This basically connects to Mastodon and then relays public, home, and optionally hashtag searches to your IRC client, bot or script can react to toots in real time. + +## TODO + +- [ ] Hashtag streaming by joining `#hashtag` +- [ ] Multiple mastodon account support + `#//` ? +- [ ] Multiple user support +- [ ] Full text searches by joining `?terms+in+query+syntax` + Common paginated contextual UI concepts that people have been talking about for chatbots might be amazing here, as that is what this would basically be. +- [ ] diff --git a/main.go b/main.go new file mode 100644 index 0000000..daad2ee --- /dev/null +++ b/main.go @@ -0,0 +1,96 @@ +package main + +import ( + "context" + "net" + + "github.com/Xe/ln" + "github.com/caarlos0/env" + "gopkg.in/irc.v1" +) + +type Config struct { + ServerName string `env:"SERVER_NAME" envDefault:"to.ot"` + ServerAddr string `env:"SERVER_ADDR" envDefault:"127.0.0.1:42069"` + // XXX RIP HACK TODO fix once user accounts exist + UserPassword string `env:"USER_PASSWORD" envDefault:"hunter2"` + + MastodonInstance string `env:"MASTODON_INSTANCE,required"` + MastodonToken string `env:"MASTODON_TOKEN,required"` + MastodonClientID string `env:"MASTODON_CLIENT_ID,required"` + MastodonClientSecret string `env:"MASTODON_CLIENT_SECRET,required"` +} + +func main() { + cfg := Config{} + err := env.Parse(&cfg) + if err != nil { + ln.Fatal(ln.F{"err": err}) + } + + l, err := net.Listen(cfg.ServerAddr) + if err != nil { + ln.Fatal(ln.F{"err": err, "addr": cfg.ServerAddr}) + } + + for { + ctx := context.Context() + conn, err := l.Accept() + if err != nil { + ln.Error(err, ln.F{"addr": cfg.ServerAddr}) + } + + ir := irc.NewReader(conn) + iw := irc.NewWriter(conn) + + msg, err := ir.ReadMessage() + if err != nil { + ln.Error(err, ln.F{"client_addr": conn.RemoteAddr().String()}) + conn.Close() + continue + } + + if msg.Command != "PASS" { + ln.Log(ln.F{"action": "auth_failed", "client_addr": conn.RemoteAddr().String()}) + iw.Writef(":%s ERROR :authentication failed", cfg.ServerName) + conn.Close() + continue + } + + if msg.Params[0] != cfg.UserPassword { + ln.Log(ln.F{"action": "wrong_password", "client_addr": conn.RemoteAddr().String()}) + iw.Writef(":%s ERROR :authentication failed", cfg.ServerName) + conn.Close() + continue + } + + mc, err := madon.RestoreApp("ircsuite", cfg.MastodonInstance, cfg.MastodonClientID, cfg.MastodonClientSecret, &madon.UserToken{AccessToken: cfg.MastodonToken}) + if err != nil { + ln.Error(ln.F{"action": "madon.RestoreApp"}) + iw.Writef(":%s ERROR :authentication failed", cfg.ServerName) + conn.Close() + continue + } + _ = mc + + s := &Server{ + channels: map[string]struct{}{}, + mc: mc, + + iw: iw, + ir: ir, + } + + go s.HandleConn(ctx, conn) + } +} + +type Server struct { + sync.Mutex // locked at per line execution + + channels map[string]struct{} // channels the client has joined + mc *madon.Client + + iw *irc.Writer + ir *irc.Reader +}