forked from jshiffer/matterbridge
264 lines
6.8 KiB
Go
264 lines
6.8 KiB
Go
package gozulipbot
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
)
|
|
|
|
// A Message is all of the necessary metadata to post on Zulip.
|
|
// It can be either a public message, where Topic is set, or a private message,
|
|
// where there is at least one element in Emails.
|
|
//
|
|
// If the length of Emails is not 0, functions will always assume it is a private message.
|
|
type Message struct {
|
|
Stream string
|
|
Topic string
|
|
Emails []string
|
|
Content string
|
|
}
|
|
|
|
type EventMessage struct {
|
|
AvatarURL string `json:"avatar_url"`
|
|
Client string `json:"client"`
|
|
Content string `json:"content"`
|
|
ContentType string `json:"content_type"`
|
|
DisplayRecipient DisplayRecipient `json:"display_recipient"`
|
|
GravatarHash string `json:"gravatar_hash"`
|
|
ID int `json:"id"`
|
|
RecipientID int `json:"recipient_id"`
|
|
SenderDomain string `json:"sender_domain"`
|
|
SenderEmail string `json:"sender_email"`
|
|
SenderFullName string `json:"sender_full_name"`
|
|
SenderID int `json:"sender_id"`
|
|
SenderShortName string `json:"sender_short_name"`
|
|
Subject string `json:"subject"`
|
|
SubjectLinks []interface{} `json:"subject_links"`
|
|
StreamID int `json:"stream_id"`
|
|
Timestamp int `json:"timestamp"`
|
|
Type string `json:"type"`
|
|
Queue *Queue `json:"-"`
|
|
}
|
|
|
|
type DisplayRecipient struct {
|
|
Users []User `json:"users,omitempty"`
|
|
Topic string `json:"topic,omitempty"`
|
|
}
|
|
|
|
type User struct {
|
|
Domain string `json:"domain"`
|
|
Email string `json:"email"`
|
|
FullName string `json:"full_name"`
|
|
ID int `json:"id"`
|
|
IsMirrorDummy bool `json:"is_mirror_dummy"`
|
|
ShortName string `json:"short_name"`
|
|
}
|
|
|
|
func (d *DisplayRecipient) UnmarshalJSON(b []byte) (err error) {
|
|
topic, users := "", make([]User, 1)
|
|
if err = json.Unmarshal(b, &topic); err == nil {
|
|
d.Topic = topic
|
|
return
|
|
}
|
|
if err = json.Unmarshal(b, &users); err == nil {
|
|
d.Users = users
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
// Message posts a message to Zulip. If any emails have been set on the message,
|
|
// the message will be re-routed to the PrivateMessage function.
|
|
func (b *Bot) Message(m Message) (*http.Response, error) {
|
|
if m.Content == "" {
|
|
return nil, fmt.Errorf("content cannot be empty")
|
|
}
|
|
|
|
// if any emails are set, this is a private message
|
|
if len(m.Emails) != 0 {
|
|
return b.PrivateMessage(m)
|
|
}
|
|
|
|
// otherwise it's a stream message
|
|
if m.Stream == "" {
|
|
return nil, fmt.Errorf("stream cannot be empty")
|
|
}
|
|
if m.Topic == "" {
|
|
return nil, fmt.Errorf("topic cannot be empty")
|
|
}
|
|
req, err := b.constructMessageRequest(m)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return b.Client.Do(req)
|
|
}
|
|
|
|
// PrivateMessage sends a message to the users in the message email slice.
|
|
func (b *Bot) PrivateMessage(m Message) (*http.Response, error) {
|
|
if len(m.Emails) == 0 {
|
|
return nil, fmt.Errorf("there must be at least one recipient")
|
|
}
|
|
req, err := b.constructMessageRequest(m)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return b.Client.Do(req)
|
|
}
|
|
|
|
// Respond sends a given message as a response to whatever context from which
|
|
// an EventMessage was received.
|
|
func (b *Bot) Respond(e EventMessage, response string) (*http.Response, error) {
|
|
if response == "" {
|
|
return nil, fmt.Errorf("Message response cannot be blank")
|
|
}
|
|
m := Message{
|
|
Stream: e.DisplayRecipient.Topic,
|
|
Topic: e.Subject,
|
|
Content: response,
|
|
}
|
|
if m.Topic != "" {
|
|
return b.Message(m)
|
|
}
|
|
// private message
|
|
if m.Stream == "" {
|
|
emails, err := b.privateResponseList(e)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
m.Emails = emails
|
|
return b.Message(m)
|
|
}
|
|
return nil, fmt.Errorf("EventMessage is not understood: %v\n", e)
|
|
}
|
|
|
|
// privateResponseList gets the list of other users in a private multiple
|
|
// message conversation.
|
|
func (b *Bot) privateResponseList(e EventMessage) ([]string, error) {
|
|
var out []string
|
|
for _, u := range e.DisplayRecipient.Users {
|
|
if u.Email != b.Email {
|
|
out = append(out, u.Email)
|
|
}
|
|
}
|
|
if len(out) == 0 {
|
|
return nil, fmt.Errorf("EventMessage had no Users within the DisplayRecipient")
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// constructMessageRequest is a helper for simplifying sending a message.
|
|
func (b *Bot) constructMessageRequest(m Message) (*http.Request, error) {
|
|
to := m.Stream
|
|
mtype := "stream"
|
|
|
|
le := len(m.Emails)
|
|
if le != 0 {
|
|
mtype = "private"
|
|
}
|
|
if le == 1 {
|
|
to = m.Emails[0]
|
|
}
|
|
if le > 1 {
|
|
to = ""
|
|
for i, e := range m.Emails {
|
|
to += e
|
|
if i != le-1 {
|
|
to += ","
|
|
}
|
|
}
|
|
}
|
|
|
|
values := url.Values{}
|
|
values.Set("type", mtype)
|
|
values.Set("to", to)
|
|
values.Set("content", m.Content)
|
|
if mtype == "stream" {
|
|
values.Set("subject", m.Topic)
|
|
}
|
|
|
|
return b.constructRequest("POST", "messages", values.Encode())
|
|
}
|
|
|
|
func (b *Bot) UpdateMessage(id string, content string) (*http.Response, error) {
|
|
//mid, _ := strconv.Atoi(id)
|
|
values := url.Values{}
|
|
values.Set("content", content)
|
|
req, err := b.constructRequest("PATCH", "messages/"+id, values.Encode())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return b.Client.Do(req)
|
|
}
|
|
|
|
// React adds an emoji reaction to an EventMessage.
|
|
func (b *Bot) React(e EventMessage, emoji string) (*http.Response, error) {
|
|
url := fmt.Sprintf("messages/%d/emoji_reactions/%s", e.ID, emoji)
|
|
req, err := b.constructRequest("PUT", url, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return b.Client.Do(req)
|
|
}
|
|
|
|
// Unreact removes an emoji reaction from an EventMessage.
|
|
func (b *Bot) Unreact(e EventMessage, emoji string) (*http.Response, error) {
|
|
url := fmt.Sprintf("messages/%d/emoji_reactions/%s", e.ID, emoji)
|
|
req, err := b.constructRequest("DELETE", url, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return b.Client.Do(req)
|
|
}
|
|
|
|
type Emoji struct {
|
|
Author string `json:"author"`
|
|
DisplayURL string `json:"display_url"`
|
|
SourceURL string `json:"source_url"`
|
|
}
|
|
|
|
type EmojiResponse struct {
|
|
Emoji map[string]*Emoji `json:"emoji"`
|
|
Msg string `json:"msg"`
|
|
Result string `json:"result"`
|
|
}
|
|
|
|
// RealmEmoji gets the custom emoji information for the Zulip instance.
|
|
func (b *Bot) RealmEmoji() (map[string]*Emoji, error) {
|
|
req, err := b.constructRequest("GET", "realm/emoji", "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp, err := b.Client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var emjResp EmojiResponse
|
|
err = json.Unmarshal(body, &emjResp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return emjResp.Emoji, nil
|
|
}
|
|
|
|
// RealmEmojiSet makes a set of the names of the custom emoji in the Zulip instance.
|
|
func (b *Bot) RealmEmojiSet() (map[string]struct{}, error) {
|
|
emj, err := b.RealmEmoji()
|
|
if err != nil {
|
|
return nil, nil
|
|
}
|
|
out := map[string]struct{}{}
|
|
for k, _ := range emj {
|
|
out[k] = struct{}{}
|
|
}
|
|
return out, nil
|
|
}
|