diff --git a/cmd/jc/main.go b/cmd/jc/main.go index bce4c488ba6f7867d2b390f34efc5c0f0a081a5a..08951fcad42b227e84915f917f4d5e939077e4e2 100644 --- a/cmd/jc/main.go +++ b/cmd/jc/main.go @@ -4,12 +4,11 @@ import ( "flag" "io/ioutil" "log" - "reflect" - "strings" "gopkg.in/yaml.v2" "git.iiens.net/morignot2011/jc" + "git.iiens.net/morignot2011/jc/dispatcher" "git.iiens.net/morignot2011/jc/irc" "git.iiens.net/morignot2011/jc/slack" ) @@ -28,7 +27,6 @@ type LinkDest struct { filter []string } -var transports = make(map[string]jc.Transport) var links = make(Links) func main() { @@ -46,6 +44,8 @@ func main() { log.Fatal(err) } + transports := make(map[string]jc.Transport) + for name, transport := range cfg.Transports { type_, ok := transport["type"] if !ok { @@ -59,9 +59,9 @@ func main() { switch type_.(string) { case "irc": - t, err = irc.New(transport) + t, err = irc.New(name, transport) case "slack": - t, err = slack.New(transport) + t, err = slack.New(name, transport) } if err != nil { @@ -75,131 +75,10 @@ func main() { } } - for _, link := range cfg.Links { - filter, ok := link["filter"] - if !ok { - filter = []string{} - } - - delete(link, "filter") - - for a, b := range link { - cfgA := strings.Split(a, "@") - cfgB := strings.Split(b.(string), "@") - - if len(cfgA) != 2 || len(cfgB) != 2 { - log.Fatalf("Invalid link '%s => %s', it must be at the format 'channelA@transportAÂ => channelB@transportB'", a, b.(string)) - } - - if _, ok := transports[cfgA[1]]; !ok { - log.Fatalf("Unknown transport %s in link '%s => %s'", cfgA[1], a, b.(string)) - } - - if _, ok := transports[cfgB[1]]; !ok { - log.Fatalf("Unknown transport %s in link '%s => %s'", cfgB[1], a, b.(string)) - } - - var filters []string - for _, f := range filter.([]interface{}) { - filters = append(filters, f.(string)) - } - - if links[cfgA[1]] == nil { - links[cfgA[1]] = make(Link) - } - links[cfgA[1]][cfgA[0]] = LinkDest{ - transport: cfgB[1], - channel: cfgB[0], - filter: filters, - } - - if links[cfgB[1]] == nil { - links[cfgB[1]] = make(Link) - } - links[cfgB[1]][cfgB[0]] = LinkDest{ - transport: cfgA[1], - channel: cfgA[0], - filter: filters, - } - } - } + dispatcher := dispatcher.NewDispatcher(cfg.Links, transports) // remove sensitive data cfg = Cfg{} - cases := make([]reflect.SelectCase, len(transports)*2) - i := 0 - for _, t := range transports { - cases[i] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(t.GetEvents())} - i++ - cases[i] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(t.GetEnd())} - i++ - } - - for { - idx, value, ok := reflect.Select(cases) - - i := 0 - var name string - for name, _ = range transports { - if i == idx || i+1 == idx { - break - } - - i += 2 - } - - if !ok { - log.Fatalf("Transport %s close one of its channel!", name) - } - - switch ev := value.Interface().(type) { - case *jc.JoinEvent: - dest, ok := links[name][ev.Channel] - if !ok { - continue - } - - if isFiltered(dest.filter, ev.Nick) { - continue - } - - t := transports[dest.transport] - - go t.Join(&jc.JoinEvent{ - Nick: ev.Nick + "_jc", - Channel: dest.channel, - }) - case *jc.MessageEvent: - dest, ok := links[name][ev.Channel] - if !ok { - continue - } - - if isFiltered(dest.filter, ev.Nick) { - continue - } - - t := transports[dest.transport] - - go t.Message(&jc.MessageEvent{ - Nick: ev.Nick + "_jc", - Channel: dest.channel, - Text: ev.Text, - }) - case bool: - log.Fatalf("Disconnected from %s", name) - return - } - } -} - -func isFiltered(filter []string, nick string) bool { - for _, v := range filter { - if v == nick { - return true - } - } - - return false + dispatcher.Run() } diff --git a/dispatcher.go b/dispatcher.go new file mode 100644 index 0000000000000000000000000000000000000000..fbf977e1fd8eceb97688ec30a9770d3351cb6971 --- /dev/null +++ b/dispatcher.go @@ -0,0 +1,17 @@ +package jc + +type Dispatcher interface { + Run() +} + +type Links []Link + +type Link struct { + endpoints []Endpoint + filters []string +} + +type Endpoint struct { + Channel string + Transport string +} diff --git a/dispatcher/dispatcher.go b/dispatcher/dispatcher.go new file mode 100644 index 0000000000000000000000000000000000000000..56ecdb646293b6de3bc5acb1c5693704da51e4fb --- /dev/null +++ b/dispatcher/dispatcher.go @@ -0,0 +1,126 @@ +package dispatcher + +import ( + "log" + "reflect" + + "git.iiens.net/morignot2011/jc" +) + +type Dispatcher struct { + links []Link + transports map[string]jc.Transport + transportNames []string +} + +type Link struct { + endpoints []Endpoint + filters []string +} + +type Endpoint struct { + channel string + transport string +} + +func NewDispatcher(cfg []map[string]interface{}, transports map[string]jc.Transport) jc.Dispatcher { + d := &Dispatcher{ + transports: transports, + } + + for _, linkCfg := range cfg { + link := Link{} + + filters, ok := linkCfg["filters"] + if ok { + for _, filter := range filters.([]interface{}) { + link.filters = append(link.filters, filter.(string)) + } + } + + delete(linkCfg, "filters") + + for name, channels := range linkCfg { + for _, channel := range channels.([]interface{}) { + endpoint := Endpoint{ + channel: channel.(string), + transport: name, + } + + link.endpoints = append(link.endpoints, endpoint) + } + } + + d.links = append(d.links, link) + } + + return d +} + +func (d *Dispatcher) Run() { + var cases []reflect.SelectCase + var transportNames []string + + for name, t := range d.transports { + cases = append(cases, reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(t.GetEvents())}) + transportNames = append(transportNames, name) + } + + for { + i, value, ok := reflect.Select(cases) + name := transportNames[i] + + if !ok { + log.Fatalf("Transport %s closed its event channel!", name) + } + + switch ev := value.Interface().(type) { + case *jc.JoinEvent: + d.join(name, ev) + case *jc.MessageEvent: + d.message(name, ev) + } + } +} + +func (d *Dispatcher) findLink(transport string, channels ...string) []Link { + byChan := false + if len(channels) > 0 { + byChan = true + } + + var links []Link + + for _, link := range d.links { + match := false + + for _, endpoint := range link.endpoints { + if endpoint.transport != transport { + continue + } + + if byChan && endpoint.channel != channels[0] { + continue + } + + match = true + break + } + + if match { + links = append(links, link) + } + } + + return links +} + +func isFiltered(filters []string, nick string) bool { + for _, v := range filters { + if v == nick { + return true + } + } + + return false +} diff --git a/dispatcher/events.go b/dispatcher/events.go new file mode 100644 index 0000000000000000000000000000000000000000..7c2908bcd4a456a42b80aaae8bf5cc6dd5c77558 --- /dev/null +++ b/dispatcher/events.go @@ -0,0 +1,52 @@ +package dispatcher + +import ( + "log" + + "git.iiens.net/morignot2011/jc" +) + +func (d *Dispatcher) join(transport string, ev *jc.JoinEvent) { + log.Printf("Receive JoinEvent from %s: %s on %s", transport, ev.Nick, ev.Channel) + + links := d.findLink(transport, ev.Channel) + for _, link := range links { + if isFiltered(link.filters, ev.Nick) { + continue + } + + for _, endpoint := range link.endpoints { + if endpoint.transport == transport && endpoint.channel == ev.Channel { + continue + } + + d.transports[endpoint.transport].Join(&jc.JoinEvent{ + Nick: ev.Nick + "_jc", + Channel: endpoint.channel, + }) + } + } +} + +func (d *Dispatcher) message(transport string, ev *jc.MessageEvent) { + log.Printf("Receive MessageEvent from %s: %s on %s", transport, ev.Nick, ev.Channel) + + links := d.findLink(transport, ev.Channel) + for _, link := range links { + if isFiltered(link.filters, ev.Nick) { + continue + } + + for _, endpoint := range link.endpoints { + if endpoint.transport == transport && endpoint.channel == ev.Channel { + continue + } + + d.transports[endpoint.transport].Message(&jc.MessageEvent{ + Nick: ev.Nick + "_jc", + Channel: endpoint.channel, + Text: ev.Text, + }) + } + } +} diff --git a/irc/commands.go b/irc/commands.go index 2ec2001fb53b484dec7fd996242f74a3c5c7aff4..ab71f621f6922046dca36088e6250fcd5a9f75ff 100644 --- a/irc/commands.go +++ b/irc/commands.go @@ -1,22 +1,23 @@ package irc import ( - "log" - "git.iiens.net/morignot2011/jc" ) func (t *Transport) Join(ev *jc.JoinEvent) { + t.Logger.Printf("Join: %s on %s", ev.Nick, ev.Channel) if i := FindChannel(t.channels, ev.Channel); i == -1 { // cannot join a channel not configured + t.Logger.Printf("Channel %s is not configured", ev.Channel) return } client, ok := t.userClients[ev.Nick] if !ok { + t.Logger.Printf("Client %s does not exist, creating it", ev.Nick) ircCfg, err := t.newIrcConfig(ev.Nick, t.cfg) if err != nil { - log.Print(err) + t.Logger.Print(err) return } @@ -26,7 +27,7 @@ func (t *Transport) Join(ev *jc.JoinEvent) { t.userChannels[ev.Nick] = []string{ev.Channel} if err := client.Connect(); err != nil { - log.Print(err) + t.Logger.Print(err) return } } else { @@ -39,14 +40,17 @@ func (t *Transport) Join(ev *jc.JoinEvent) { } func (t *Transport) Message(ev *jc.MessageEvent) { + t.Logger.Printf("Message: %s on %s", ev.Nick, ev.Channel) client, ok := t.userClients[ev.Nick] if !ok { // unknown client + t.Logger.Printf("Unknown client %s", ev.Nick) return } if i := FindChannel(t.userChannels[ev.Nick], ev.Channel); i == -1 { // this user is not on this channel + t.Logger.Printf("%s is not on %s", ev.Nick, ev.Channel) return } diff --git a/irc/events.go b/irc/events.go index 22e6ed34bed74bfc2c89e673d3fd4a6557803a64..b1f916b0575ba0da02d364c1cbd3936766b8d752 100644 --- a/irc/events.go +++ b/irc/events.go @@ -12,10 +12,11 @@ import ( ) func (t *Transport) connected(client *irc.Conn, line *irc.Line) { + t.Logger.Printf("%s is connected", client.Me().Nick) if t.client != client { // user's client for _, channel := range t.userChannels[client.Me().Nick] { - log.Printf("join %s", channel) + log.Printf("Join %s", channel) client.Join(channel) } } else { diff --git a/irc/transport.go b/irc/transport.go index 192fcd30a0df2d3fd5f2f2e244bad2ad95b7c0b9..e44a8344fbc3a50e64c428f8d44b0140e130e97f 100644 --- a/irc/transport.go +++ b/irc/transport.go @@ -4,6 +4,7 @@ import ( "crypto/tls" "fmt" "log" + "os" irc "github.com/fluffle/goirc/client" @@ -28,7 +29,7 @@ type Transport struct { connectionError chan error } -func New(cfg map[string]interface{}) (jc.Transport, error) { +func New(name string, cfg map[string]interface{}) (jc.Transport, error) { t := &Transport{ cfg: cfg, connectionError: make(chan error), @@ -38,6 +39,8 @@ func New(cfg map[string]interface{}) (jc.Transport, error) { BaseTransport: jc.BaseTransport{ Events: make(chan interface{}), End: make(chan bool), + + Logger: log.New(os.Stdout, name+": ", log.LstdFlags), }, } diff --git a/slack/events.go b/slack/events.go index 473295dbbf40cb5bd835b3f54a703cec45080f58..f71afef3465ef3394e481c8a40bb883d57de894c 100644 --- a/slack/events.go +++ b/slack/events.go @@ -9,6 +9,7 @@ import ( ) func (t *Transport) connected(ev *slack.ConnectedEvent) { + t.Logger.Printf("Connected event") events := []*jc.JoinEvent{} for name, id := range t.channelIDs { @@ -18,7 +19,6 @@ func (t *Transport) connected(ev *slack.ConnectedEvent) { return } - fmt.Printf("%s: %d\n", name, len(channel.Members)) for _, id := range channel.Members { user, err := t.api.GetUserInfo(id) if err != nil { @@ -47,6 +47,7 @@ func (t *Transport) invalidAuth(ev *slack.InvalidAuthEvent) { } func (t *Transport) message(ev *slack.MessageEvent) { + t.Logger.Printf("Message event") channel, ok := t.channelNames[ev.Channel] if !ok { // probably we got invited on a channel after starting the bot diff --git a/slack/transport.go b/slack/transport.go index 59a04bf9f6a99ef85c00f61a5176af93ef018dcf..a36eefb67d1e8a903977aa1522719c3135ec53f0 100644 --- a/slack/transport.go +++ b/slack/transport.go @@ -1,6 +1,9 @@ package slack import ( + "log" + "os" + "github.com/nlopes/slack" "git.iiens.net/morignot2011/jc" @@ -17,7 +20,7 @@ type Transport struct { userNames map[string]string } -func New(cfg map[string]interface{}) (jc.Transport, error) { +func New(name string, cfg map[string]interface{}) (jc.Transport, error) { token, ok := cfg["token"] if !ok { return nil, jc.ConfigError{"token"} @@ -36,6 +39,8 @@ func New(cfg map[string]interface{}) (jc.Transport, error) { BaseTransport: jc.BaseTransport{ Events: make(chan interface{}), End: make(chan bool), + + Logger: log.New(os.Stdout, name+": ", log.LstdFlags), }, } diff --git a/transport.go b/transport.go index 8190ad31a6884697105d1b37a019929379078b02..250b7bca9a7186bc94f053a3f86ae75fecac41a6 100644 --- a/transport.go +++ b/transport.go @@ -1,5 +1,9 @@ package jc +import ( + "log" +) + type Transport interface { Run() (err error) @@ -17,6 +21,8 @@ type Transport interface { type BaseTransport struct { Events chan interface{} End chan bool + + Logger *log.Logger } func (t *BaseTransport) GetEvents() chan interface{} {