| 1 | package bzulip |
| 2 | |
| 3 | import ( |
| 4 | "encoding/json" |
| 5 | "fmt" |
| 6 | "io" |
| 7 | "strconv" |
| 8 | "strings" |
| 9 | "sync" |
| 10 | "time" |
| 11 | |
| 12 | "github.com/matterbridge-org/matterbridge/bridge" |
| 13 | "github.com/matterbridge-org/matterbridge/bridge/config" |
| 14 | "github.com/matterbridge-org/matterbridge/bridge/helper" |
| 15 | "github.com/matterbridge-org/matterbridge/version" |
| 16 | |
| 17 | // Seems not significantly different from upstream https://github.com/ifo/gozulipbot replace? |
| 18 | gzb "github.com/matterbridge/gozulipbot" |
| 19 | ) |
| 20 | |
| 21 | type Bzulip struct { |
| 22 | q *gzb.Queue |
| 23 | bot *gzb.Bot |
| 24 | streams map[int]string |
| 25 | *bridge.Config |
| 26 | sync.RWMutex |
| 27 | } |
| 28 | |
| 29 | func New(cfg *bridge.Config) bridge.Bridger { |
| 30 | return &Bzulip{Config: cfg, streams: make(map[int]string)} |
| 31 | } |
| 32 | |
| 33 | func (b *Bzulip) Connect() error { |
| 34 | bot := gzb.Bot{APIKey: b.GetString("token"), APIURL: b.GetString("server") + "/api/v1/", Email: b.GetString("login"), UserAgent: fmt.Sprintf("matterbridge/%s", version.Release)} |
| 35 | bot.Init() |
| 36 | q, err := bot.RegisterAll() |
| 37 | b.q = q |
| 38 | b.bot = &bot |
| 39 | if err != nil { |
| 40 | b.Log.Errorf("Connect() %#v", err) |
| 41 | return err |
| 42 | } |
| 43 | // init stream |
| 44 | b.getChannel(0) |
| 45 | b.Log.Info("Connection succeeded") |
| 46 | go b.handleQueue() |
| 47 | return nil |
| 48 | } |
| 49 | |
| 50 | func (b *Bzulip) Disconnect() error { |
| 51 | return nil |
| 52 | } |
| 53 | |
| 54 | func (b *Bzulip) JoinChannel(channel config.ChannelInfo) error { |
| 55 | return nil |
| 56 | } |
| 57 | |
| 58 | func (b *Bzulip) Send(msg config.Message) (string, error) { |
| 59 | b.Log.Debugf("=> Receiving %#v", msg) |
| 60 | |
| 61 | // Delete message |
| 62 | if msg.Event == config.EventMsgDelete { |
| 63 | if msg.ID == "" { |
| 64 | return "", nil |
| 65 | } |
| 66 | _, err := b.bot.UpdateMessage(msg.ID, "") |
| 67 | return "", err |
| 68 | } |
| 69 | |
| 70 | // Upload a file if it exists |
| 71 | if msg.Extra != nil { |
| 72 | for _, rmsg := range helper.HandleExtra(&msg, b.General) { |
| 73 | b.sendMessage(rmsg) |
| 74 | } |
| 75 | if len(msg.Extra["file"]) > 0 { |
| 76 | return b.handleUploadFile(&msg) |
| 77 | } |
| 78 | } |
| 79 | |
| 80 | // edit the message if we have a msg ID |
| 81 | if msg.ID != "" { |
| 82 | _, err := b.bot.UpdateMessage(msg.ID, msg.Username+msg.Text) |
| 83 | return "", err |
| 84 | } |
| 85 | |
| 86 | // Post normal message |
| 87 | return b.sendMessage(msg) |
| 88 | } |
| 89 | |
| 90 | func (b *Bzulip) getChannel(id int) string { |
| 91 | if name, ok := b.streams[id]; ok { |
| 92 | return name |
| 93 | } |
| 94 | streams, err := b.bot.GetRawStreams() |
| 95 | if err != nil { |
| 96 | b.Log.Errorf("getChannel: %#v", err) |
| 97 | return "" |
| 98 | } |
| 99 | for _, stream := range streams.Streams { |
| 100 | b.streams[stream.StreamID] = stream.Name |
| 101 | } |
| 102 | if name, ok := b.streams[id]; ok { |
| 103 | return name |
| 104 | } |
| 105 | return "" |
| 106 | } |
| 107 | |
| 108 | func (b *Bzulip) handleQueue() error { |
| 109 | for { |
| 110 | messages, err := b.q.GetEvents() |
| 111 | if err != nil { |
| 112 | switch err { |
| 113 | case gzb.BackoffError: |
| 114 | time.Sleep(time.Second * 5) |
| 115 | case gzb.NoJSONError: |
| 116 | b.Log.Error("Response wasn't JSON, server down or restarting? sleeping 10 seconds") |
| 117 | time.Sleep(time.Second * 10) |
| 118 | case gzb.BadEventQueueError: |
| 119 | b.Log.Info("got a bad event queue id error, reconnecting") |
| 120 | b.bot.Queues = nil |
| 121 | for { |
| 122 | b.q, err = b.bot.RegisterAll() |
| 123 | if err != nil { |
| 124 | b.Log.Errorf("reconnecting failed: %s. Sleeping 10 seconds", err) |
| 125 | time.Sleep(time.Second * 10) |
| 126 | } |
| 127 | break |
| 128 | } |
| 129 | case gzb.HeartbeatError: |
| 130 | b.Log.Debug("heartbeat received.") |
| 131 | default: |
| 132 | b.Log.Debugf("receiving error: %#v", err) |
| 133 | time.Sleep(time.Second * 10) |
| 134 | } |
| 135 | |
| 136 | continue |
| 137 | } |
| 138 | for _, m := range messages { |
| 139 | b.Log.Debugf("== Receiving %#v", m) |
| 140 | // ignore our own messages |
| 141 | if m.SenderEmail == b.GetString("login") { |
| 142 | continue |
| 143 | } |
| 144 | |
| 145 | avatarURL := m.AvatarURL |
| 146 | if !strings.HasPrefix(avatarURL, "http") { |
| 147 | avatarURL = b.GetString("server") + avatarURL |
| 148 | } |
| 149 | |
| 150 | rmsg := config.Message{ |
| 151 | Username: m.SenderFullName, |
| 152 | Text: m.Content, |
| 153 | Channel: b.getChannel(m.StreamID) + "/topic:" + m.Subject, |
| 154 | Account: b.Account, |
| 155 | UserID: strconv.Itoa(m.SenderID), |
| 156 | Avatar: avatarURL, |
| 157 | } |
| 158 | b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account) |
| 159 | b.Log.Debugf("<= Message is %#v", rmsg) |
| 160 | b.Remote <- rmsg |
| 161 | } |
| 162 | |
| 163 | time.Sleep(time.Second * 3) |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | func (b *Bzulip) sendMessage(msg config.Message) (string, error) { |
| 168 | topic := "" |
| 169 | if strings.Contains(msg.Channel, "/topic:") { |
| 170 | res := strings.Split(msg.Channel, "/topic:") |
| 171 | topic = res[1] |
| 172 | msg.Channel = res[0] |
| 173 | } |
| 174 | m := gzb.Message{ |
| 175 | Stream: msg.Channel, |
| 176 | Topic: topic, |
| 177 | Content: msg.Username + msg.Text, |
| 178 | } |
| 179 | resp, err := b.bot.Message(m) |
| 180 | if err != nil { |
| 181 | return "", err |
| 182 | } |
| 183 | if resp != nil { |
| 184 | defer resp.Body.Close() |
| 185 | |
| 186 | res, err := io.ReadAll(resp.Body) |
| 187 | if err != nil { |
| 188 | return "", err |
| 189 | } |
| 190 | var jr struct { |
| 191 | ID int `json:"id"` |
| 192 | } |
| 193 | err = json.Unmarshal(res, &jr) |
| 194 | if err != nil { |
| 195 | return "", err |
| 196 | } |
| 197 | return strconv.Itoa(jr.ID), nil |
| 198 | } |
| 199 | return "", nil |
| 200 | } |
| 201 | |
| 202 | func (b *Bzulip) handleUploadFile(msg *config.Message) (string, error) { |
| 203 | for _, f := range msg.Extra["file"] { |
| 204 | fi := f.(config.FileInfo) |
| 205 | if fi.Comment != "" { |
| 206 | msg.Text += fi.Comment + ": " |
| 207 | } |
| 208 | if fi.URL != "" { |
| 209 | msg.Text = fi.URL |
| 210 | if fi.Comment != "" { |
| 211 | msg.Text = fi.Comment + ": " + fi.URL |
| 212 | } |
| 213 | } |
| 214 | _, err := b.sendMessage(*msg) |
| 215 | if err != nil { |
| 216 | return "", err |
| 217 | } |
| 218 | } |
| 219 | return "", nil |
| 220 | } |
| 221 | |