Compare commits

..

31 Commits

Author SHA1 Message Date
Wim
6c442e239d Release v1.17.4 (#1112) 2020-04-21 23:53:51 +02:00
Wim
eaf92fca4d Add an ID cache (discord). Fixes #1106 (#1111)
When a webhook "edits" a message, it does this by deleting the message
and creating a new one with the new content.

On creation of this new message, we'll get another ID then already is
know by the gateway in its id cache. So we add it in our own cache and
replace it whenever we want to edit/delete it again.
2020-04-21 23:35:46 +02:00
Wim
06b7bad714 Lowercase account names. Fixes #1108 (#1110) 2020-04-21 20:42:11 +02:00
Wim
19eec2ed03 Update Rhymen/go-whatsapp. Fixes #1107 (#1109) 2020-04-21 19:55:47 +02:00
Wim
d99c54343a Remove panics and retry polling on failure (msteams). Fixes #1104 (#1105) 2020-04-21 19:29:24 +02:00
Wim
308a110000 Bump version 2020-04-19 17:23:17 +02:00
Wim
4f406b2ce6 Release v1.17.3 (#1103) 2020-04-19 17:13:58 +02:00
Wim
e564c555d7 Clip too long messages on 3000 length (slack). Fixes #1081 (#1102) 2020-04-19 17:00:11 +02:00
Wim
f7ec9af9e8 Add extra space before colon in attachments (irc). Fixes #1089 (#1101) 2020-04-19 16:45:53 +02:00
Wim
4d93a774ce Ignore non-critical errors (whatsapp). Fixes #1094 (#1100) 2020-04-19 13:45:35 +02:00
Wim
2595dd30bf Update matterbridge/go-xmpp. Fixes #1097 (#1099) 2020-04-19 01:06:44 +02:00
Wim
9190365289 Add JoinDelay option (irc). Fixes #1084 (#1098) 2020-04-19 00:46:35 +02:00
Wim
57794b3b9f Prevent image/message looping (slack). Fixes #1088 (#1096)
Also check for our matterbridge ID in Blocks set in SubMessages.
2020-04-18 22:30:49 +02:00
ldruschk
3c36f651be Fix the behavior of ShowTopicChange and SyncTopic (#1086)
Currently, the "topic_change" events are ignored if both, 
ShowTopicChange and SyncTopic are set, and forwarded otherwise. 

This pull requests changes the behavior such that the events are 
only forwarded if one of those two config options is set to true 
and ignored otherwise.
2020-04-18 22:05:27 +02:00
ldruschk
8e6ddadba2 Relay Joins/Topic changes in RocketChat bridge (#1085)
This pull request properly sets the events EventJoinLeave and EventTopicChange for messages from the RocketChat bridge and drops messages which are neither one of those events nor plain messages.
2020-04-18 22:00:35 +02:00
Wim
8a87a71927 Update matterbridge/go-xmpp to add PEP-0030 support (#1095) 2020-04-18 20:58:55 +02:00
Qais Patankar
0047e6f523 Sort README bridge and library links (#1093) 2020-04-18 18:12:16 +02:00
Alexander
7183095a28 Implement User Avatar spoofing of XMPP users (#1090)
* Implement User Avatar spoofing of XMPP users
2020-04-16 22:16:25 +02:00
Wim
13c90893c7 Update matterbridge/Rocket.Chat.Go.SDK (#1087) 2020-04-16 21:48:53 +02:00
Wim
976fbcd07f Bump version 2020-04-09 23:03:45 +02:00
Wim
d97b077e85 Release v1.17.2 (#1080) 2020-04-09 22:54:51 +02:00
Wim
8950575bfb Update Rhymen/go-whatsapp vendor and whatsapp version (#1078) 2020-04-09 22:30:08 +02:00
Jerry Heiselman
11fc4c286f Clarify terminology used in mapping group chat IDs to channels in config (#1079)
* Clarify embedded docs for channel specification

Should help with #1072
2020-04-08 23:52:38 +02:00
Wim
8d08e348a9 Reset start timestamp on reconnect (whatsapp). Fixes #1059 (#1064) 2020-03-31 23:26:53 +02:00
Wim
a18807f19e Update matterbridge/go-xmpp to add xmpp avatar support (#1070) 2020-03-29 17:35:40 +02:00
Wim
29f658fd3c Use DebugWriter after upstream changes (xmpp) 2020-03-29 15:03:24 +02:00
Wim
a30bb8fed0 Sync matterbridge/go-xmpp with upstream 2020-03-29 15:03:24 +02:00
Wim
092ca1cd67 Update vendor slack-go/slack (#1068) 2020-03-28 23:50:47 +01:00
Wim
0df2539641 Use upstream yaegashi/msgraph.go/msauth (msteams) (#1067) 2020-03-28 23:44:49 +01:00
Wim
0f2d8a599c Update vendor d5/tengo (#1066) 2020-03-28 23:41:35 +01:00
Wim
54b3143a1d Bump version 2020-03-28 00:29:41 +01:00
69 changed files with 3301 additions and 1138 deletions

View File

@@ -9,20 +9,20 @@ Letting people be where they want to be.<br />
<sup>
[Discord][mb-discord] |
[Gitter][mb-gitter] |
[IRC][mb-irc] |
[Discord][mb-discord] |
[Keybase][mb-keybase] |
[Matrix][mb-matrix] |
[Slack][mb-slack] |
[Mattermost][mb-mattermost] |
[MSTeams][mb-msteams] |
[Rocket.Chat][mb-rocketchat] |
[XMPP][mb-xmpp] |
[Slack][mb-slack] |
[Telegram][mb-telegram] |
[Twitch][mb-twitch] |
[WhatsApp][mb-whatsapp] |
[XMPP][mb-xmpp] |
[Zulip][mb-zulip] |
[Telegram][mb-telegram] |
[Keybase][mb-keybase] |
[MSTeams][mb-msteams] |
And more...
</sup>
@@ -87,29 +87,29 @@ And more...
### Natively supported
- [Mattermost](https://github.com/mattermost/mattermost-server/) 4.x, 5.x
- [IRC](http://www.mirc.com/servers.html)
- [XMPP](https://xmpp.org)
- [Gitter](https://gitter.im)
- [Slack](https://slack.com)
- [Discord](https://discordapp.com)
- [Telegram](https://telegram.org)
- [Rocket.chat](https://rocket.chat)
- [Matrix](https://matrix.org)
- [Microsoft Teams](https://teams.microsoft.com)
- [Steam](https://store.steampowered.com/)
- [Twitch](https://twitch.tv)
- [Ssh-chat](https://github.com/shazow/ssh-chat)
- [WhatsApp](https://www.whatsapp.com/)
- [Zulip](https://zulipchat.com)
- [Gitter](https://gitter.im)
- [IRC](http://www.mirc.com/servers.html)
- [Keybase](https://keybase.io)
- [Matrix](https://matrix.org)
- [Mattermost](https://github.com/mattermost/mattermost-server/) 4.x, 5.x
- [Microsoft Teams](https://teams.microsoft.com)
- [Rocket.chat](https://rocket.chat)
- [Slack](https://slack.com)
- [Ssh-chat](https://github.com/shazow/ssh-chat)
- [Steam](https://store.steampowered.com/)
- [Telegram](https://telegram.org)
- [Twitch](https://twitch.tv)
- [WhatsApp](https://www.whatsapp.com/)
- [XMPP](https://xmpp.org)
- [Zulip](https://zulipchat.com)
### 3rd party via matterbridge api
- [Discourse](https://github.com/DeclanHoare/matterbabble)
- [Facebook messenger](https://github.com/VictorNine/fbridge)
- [Minecraft](https://github.com/elytra/MatterLink)
- [Reddit](https://github.com/bonehurtingjuice/mattereddit)
- [Facebook messenger](https://github.com/VictorNine/fbridge)
- [Discourse](https://github.com/DeclanHoare/matterbabble)
- [Counter-Strike, half-life and more](https://forums.alliedmods.net/showthread.php?t=319430)
### API
@@ -130,18 +130,18 @@ Used by the projects below. Feel free to make a PR to add your project to this l
Questions or want to test on your favorite platform? Join below:
- [Discord][mb-discord]
- [Gitter][mb-gitter]
- [IRC][mb-irc]
- [Discord][mb-discord]
- [Keybase][mb-keybase]
- [Matrix][mb-matrix]
- [Slack][mb-slack]
- [Mattermost][mb-mattermost]
- [Rocket.Chat][mb-rocketchat]
- [XMPP][mb-xmpp] (matterbridge@conference.jabber.de)
- [Twitch][mb-twitch]
- [Zulip][mb-zulip]
- [Slack][mb-slack]
- [Telegram][mb-telegram]
- [Keybase][mb-keybase]
- [Twitch][mb-twitch]
- [XMPP][mb-xmpp] (matterbridge@conference.jabber.de)
- [Zulip][mb-zulip]
## Screenshots
@@ -151,7 +151,7 @@ See https://github.com/42wim/matterbridge/wiki
### Binaries
- Latest stable release [v1.17.1](https://github.com/42wim/matterbridge/releases/latest)
- Latest stable release [v1.17.4](https://github.com/42wim/matterbridge/releases/latest)
- Development releases (follows master) can be downloaded [here](https://dl.bintray.com/42wim/nightly/)
To install or upgrade just download the latest [binary](https://github.com/42wim/matterbridge/releases/latest) and follow the instructions on the [howto](https://github.com/42wim/matterbridge/wiki/How-to-create-your-config) for a step by step walkthrough for creating your configuration.
@@ -324,32 +324,32 @@ Matterbridge wouldn't exist without these libraries:
- gops - https://github.com/google/gops
- gozulipbot - https://github.com/ifo/gozulipbot
- irc - https://github.com/lrstanley/girc
- mattermost - https://github.com/mattermost/mattermost-server
- keybase - https://github.com/keybase/go-keybase-chat-bot
- matrix - https://github.com/matrix-org/gomatrix
- sshchat - https://github.com/shazow/ssh-chat
- mattermost - https://github.com/mattermost/mattermost-server
- msgraph.go - https://github.com/yaegashi/msgraph.go
- slack - https://github.com/nlopes/slack
- sshchat - https://github.com/shazow/ssh-chat
- steam - https://github.com/Philipp15b/go-steam
- telegram - https://github.com/go-telegram-bot-api/telegram-bot-api
- xmpp - https://github.com/mattn/go-xmpp
- whatsapp - https://github.com/Rhymen/go-whatsapp/
- zulip - https://github.com/ifo/gozulipbot
- tengo - https://github.com/d5/tengo
- keybase - https://github.com/keybase/go-keybase-chat-bot
- msgraph.go - https://github.com/yaegashi/msgraph.go
- whatsapp - https://github.com/Rhymen/go-whatsapp/
- xmpp - https://github.com/mattn/go-xmpp
- zulip - https://github.com/ifo/gozulipbot
<!-- Links -->
[mb-discord]: https://discord.gg/AkKPtrQ
[mb-gitter]: https://gitter.im/42wim/matterbridge
[mb-irc]: https://webchat.freenode.net/?channels=matterbridgechat
[mb-discord]: https://discord.gg/AkKPtrQ
[mb-keybase]: https://keybase.io/team/matterbridge
[mb-matrix]: https://riot.im/app/#/room/#matterbridge:matrix.org
[mb-slack]: https://join.slack.com/matterbridgechat/shared_invite/MjEwODMxNjU1NDMwLTE0OTk2MTU3NTMtMzZkZmRiNDZhOA
[mb-mattermost]: https://framateam.org/signup_user_complete/?id=tfqm33ggop8x3qgu4boeieta6e
[mb-msteams]: https://teams.microsoft.com/join/hj92x75gd3y7
[mb-rocketchat]: https://open.rocket.chat/channel/matterbridge
[mb-xmpp]: https://inverse.chat/
[mb-slack]: https://join.slack.com/matterbridgechat/shared_invite/MjEwODMxNjU1NDMwLTE0OTk2MTU3NTMtMzZkZmRiNDZhOA
[mb-telegram]: https://t.me/Matterbridge
[mb-twitch]: https://www.twitch.tv/matterbridge
[mb-whatsapp]: https://www.whatsapp.com/
[mb-keybase]: https://keybase.io/team/matterbridge
[mb-xmpp]: https://inverse.chat/
[mb-zulip]: https://matterbridge.zulipchat.com/register/
[mb-telegram]: https://t.me/Matterbridge
[mb-msteams]: https://teams.microsoft.com/join/hj92x75gd3y7

View File

@@ -4,6 +4,7 @@ import (
"log"
"strings"
"sync"
"time"
"github.com/42wim/matterbridge/bridge/config"
"github.com/sirupsen/logrus"
@@ -74,6 +75,7 @@ func (b *Bridge) joinChannels(channels map[string]config.ChannelInfo, exists map
for ID, channel := range channels {
if !exists[ID] {
b.Log.Infof("%s: joining %s (ID: %s)", b.Account, channel.Name, ID)
time.Sleep(time.Duration(b.GetInt("JoinDelay")) * time.Millisecond)
err := b.JoinChannel(channel)
if err != nil {
return err

View File

@@ -89,6 +89,7 @@ type Protocol struct {
IgnoreNicks string // all protocols
IgnoreMessages string // all protocols
Jid string // xmpp
JoinDelay string // all protocols
Label string // all protocols
Login string // mattermost, matrix
MediaDownloadBlackList []string

View File

@@ -34,6 +34,8 @@ type Bdiscord struct {
membersMutex sync.RWMutex
userMemberMap map[string]*discordgo.Member
nickMemberMap map[string]*discordgo.Member
webhookCache map[string]string
webhookMutex sync.RWMutex
}
func New(cfg *bridge.Config) bridge.Bridger {
@@ -41,6 +43,7 @@ func New(cfg *bridge.Config) bridge.Bridger {
b.userMemberMap = make(map[string]*discordgo.Member)
b.nickMemberMap = make(map[string]*discordgo.Member)
b.channelInfoMap = make(map[string]*config.ChannelInfo)
b.webhookCache = make(map[string]string)
if b.GetString("WebhookURL") != "" {
b.Log.Debug("Configuring Discord Incoming Webhook")
b.webhookID, b.webhookToken = b.splitURL(b.GetString("WebhookURL"))
@@ -188,6 +191,8 @@ func (b *Bdiscord) JoinChannel(channel config.ChannelInfo) error {
func (b *Bdiscord) Send(msg config.Message) (string, error) {
b.Log.Debugf("=> Receiving %#v", msg)
origMsgID := msg.ID
channelID := b.getChannelID(msg.Channel)
if channelID == "" {
return "", fmt.Errorf("Could not find channelID for %v", msg.Channel)
@@ -230,6 +235,7 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
// If we are editing a message, delete the old message
if msg.ID != "" {
msg.ID = b.getCacheID(msg.ID)
b.Log.Debugf("Deleting edited webhook message")
err := b.c.ChannelMessageDelete(channelID, msg.ID)
if err != nil {
@@ -273,6 +279,8 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
if msg == nil {
return "", nil
}
b.updateCacheID(origMsgID, msg.ID)
return msg.ID, nil
}
@@ -283,6 +291,7 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
if msg.ID == "" {
return "", nil
}
msg.ID = b.getCacheID(msg.ID)
err := b.c.ChannelMessageDelete(channelID, msg.ID)
return "", err
}

View File

@@ -208,6 +208,40 @@ func (b *Bdiscord) splitURL(url string) (string, string) {
return webhookURLSplit[webhookIdxID], webhookURLSplit[webhookIdxToken]
}
// getcacheID tries to find a corresponding msgID in the webhook cache.
// if not found returns the original request.
func (b *Bdiscord) getCacheID(msgID string) string {
b.webhookMutex.RLock()
defer b.webhookMutex.RUnlock()
for k, v := range b.webhookCache {
if msgID == k {
return v
}
}
return msgID
}
// updateCacheID updates the cache so that the newID takes the place of
// the original ID. This is used for edit/deletes in combination with webhooks
// as editing a message via webhook means deleting the message and creating a
// new message (with a new ID). This ID needs to be set instead of the original ID
func (b *Bdiscord) updateCacheID(origID, newID string) {
b.webhookMutex.Lock()
match := false
for k, v := range b.webhookCache {
if v == origID {
delete(b.webhookCache, k)
b.webhookCache[origID] = newID
match = true
continue
}
}
if !match && origID != "" {
b.webhookCache[origID] = newID
}
b.webhookMutex.Unlock()
}
func enumerateUsernames(s string) []string {
onlySpace := true
for _, r := range s {

View File

@@ -54,12 +54,12 @@ func (b *Birc) handleFiles(msg *config.Message) bool {
for _, f := range msg.Extra["file"] {
fi := f.(config.FileInfo)
if fi.Comment != "" {
msg.Text += fi.Comment + ": "
msg.Text += fi.Comment + " : "
}
if fi.URL != "" {
msg.Text = fi.URL
if fi.Comment != "" {
msg.Text = fi.Comment + ": " + fi.URL
msg.Text = fi.Comment + " : " + fi.URL
}
}
b.Local <- config.Message{Text: msg.Text, Username: msg.Username, Channel: msg.Channel, Event: msg.Event}

View File

@@ -11,10 +11,9 @@ import (
"github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config"
// "github.com/davecgh/go-spew/spew"
"github.com/matterbridge/msgraph.go/msauth"
"github.com/mattn/godown"
msgraph "github.com/yaegashi/msgraph.go/beta"
"github.com/yaegashi/msgraph.go/msauth"
"golang.org/x/oauth2"
)
@@ -72,7 +71,15 @@ func (b *Bmsteams) Disconnect() error {
}
func (b *Bmsteams) JoinChannel(channel config.ChannelInfo) error {
go b.poll(channel.Name)
go func(name string) {
for {
err := b.poll(name)
if err != nil {
b.Log.Errorf("polling failed for %s: %s. retrying in 5 seconds", name, err)
}
time.Sleep(time.Second * 5)
}
}(channel.Name)
return nil
}
@@ -121,12 +128,12 @@ func (b *Bmsteams) getMessages(channel string) ([]msgraph.ChatMessage, error) {
}
//nolint:gocognit
func (b *Bmsteams) poll(channelName string) {
func (b *Bmsteams) poll(channelName string) error {
msgmap := make(map[string]time.Time)
b.Log.Debug("getting initial messages")
res, err := b.getMessages(channelName)
if err != nil {
panic(err)
return err
}
for _, msg := range res {
msgmap[*msg.ID] = *msg.CreatedDateTime
@@ -139,7 +146,7 @@ func (b *Bmsteams) poll(channelName string) {
for {
res, err := b.getMessages(channelName)
if err != nil {
panic(err)
return err
}
for i := len(res) - 1; i >= 0; i-- {
msg := res[i]

View File

@@ -2,6 +2,7 @@ package brocketchat
import (
"github.com/42wim/matterbridge/bridge/config"
"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
)
func (b *Brocketchat) handleRocket() {
@@ -38,6 +39,23 @@ func (b *Brocketchat) handleRocketHook(messages chan *config.Message) {
}
}
func (b *Brocketchat) handleStatusEvent(ev models.Message, rmsg *config.Message) bool {
switch ev.Type {
case "":
// this is a normal message, no processing needed
// return true so the message is not dropped
return true
case sUserJoined, sUserLeft:
rmsg.Event = config.EventJoinLeave
return true
case sRoomChangedTopic:
rmsg.Event = config.EventTopicChange
return true
}
b.Log.Debugf("Dropping message with unknown type: %s", ev.Type)
return false
}
func (b *Brocketchat) handleRocketClient(messages chan *config.Message) {
for message := range b.messageChan {
// skip messages with same ID, apparently messages get duplicated for an unknown reason
@@ -59,7 +77,12 @@ func (b *Brocketchat) handleRocketClient(messages chan *config.Message) {
UserID: message.User.ID,
ID: message.ID,
}
messages <- rmsg
// handleStatusEvent returns false if the message should be dropped
// in that case it is probably some modification to the channel we do not want to relay
if b.handleStatusEvent(m, rmsg) {
messages <- rmsg
}
}
}

View File

@@ -29,6 +29,12 @@ type Brocketchat struct {
sync.RWMutex
}
const (
sUserJoined = "uj"
sUserLeft = "ul"
sRoomChangedTopic = "room_changed_topic"
)
func New(cfg *bridge.Config) bridge.Bridger {
newCache, err := lru.New(100)
if err != nil {

View File

@@ -137,12 +137,6 @@ func (b *Bslack) skipMessageEvent(ev *slack.MessageEvent) bool {
hasOurCallbackID = ok && block.BlockID == "matterbridge_"+b.uuid
}
// Skip any messages that we made ourselves or from 'slackbot' (see #527).
if ev.Username == sSlackBotUser ||
(b.rtm != nil && ev.Username == b.si.User.Name) || hasOurCallbackID {
return true
}
if ev.SubMessage != nil {
// It seems ev.SubMessage.Edited == nil when slack unfurls.
// Do not forward these messages. See Github issue #266.
@@ -155,6 +149,16 @@ func (b *Bslack) skipMessageEvent(ev *slack.MessageEvent) bool {
if ev.SubType == "message_replied" && ev.Hidden {
return true
}
if len(ev.SubMessage.Blocks.BlockSet) == 1 {
block, ok := ev.SubMessage.Blocks.BlockSet[0].(*slack.SectionBlock)
hasOurCallbackID = ok && block.BlockID == "matterbridge_"+b.uuid
}
}
// Skip any messages that we made ourselves or from 'slackbot' (see #527).
if ev.Username == sSlackBotUser ||
(b.rtm != nil && ev.Username == b.si.User.Name) || hasOurCallbackID {
return true
}
if len(ev.Files) > 0 {

View File

@@ -64,6 +64,7 @@ const (
editSuffixConfig = "EditSuffix"
iconURLConfig = "iconurl"
noSendJoinConfig = "nosendjoinpart"
messageLength = 3000
)
func New(cfg *bridge.Config) bridge.Bridger {
@@ -194,6 +195,7 @@ func (b *Bslack) Send(msg config.Message) (string, error) {
b.Log.Debugf("=> Receiving %#v", msg)
}
msg.Text = helper.ClipMessage(msg.Text, messageLength)
msg.Text = b.replaceCodeFence(msg.Text)
// Make a action /me of the message

View File

@@ -23,7 +23,8 @@ Check:
// HandleError received from WhatsApp
func (b *Bwhatsapp) HandleError(err error) {
// ignore received invalid data errors. https://github.com/42wim/matterbridge/issues/843
if strings.Contains(err.Error(), "error processing data: received invalid data") {
// ignore tag 174 errors. https://github.com/42wim/matterbridge/issues/1094
if strings.Contains(err.Error(), "error processing data: received invalid data") || strings.Contains(err.Error(), "invalid string with tag 174") {
return
}
@@ -55,6 +56,7 @@ func (b *Bwhatsapp) reconnect(err error) {
err := b.conn.Restore()
if err == nil {
bf.Reset()
b.startedAt = uint64(time.Now().Unix())
return
}
}

View File

@@ -67,7 +67,7 @@ func (b *Bwhatsapp) Connect() error {
// https://github.com/Rhymen/go-whatsapp#creating-a-connection
b.Log.Debugln("Connecting to WhatsApp..")
conn, err := whatsapp.NewConn(20 * time.Second)
conn.SetClientVersion(0, 4, 1307)
conn.SetClientVersion(0, 4, 2080)
if err != nil {
return errors.New("failed to connect to WhatsApp: " + err.Error())
}

34
bridge/xmpp/handler.go Normal file
View File

@@ -0,0 +1,34 @@
package bxmpp
import (
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
"github.com/matterbridge/go-xmpp"
)
// handleDownloadAvatar downloads the avatar of userid from channel
// sends a EVENT_AVATAR_DOWNLOAD message to the gateway if successful.
// logs an error message if it fails
func (b *Bxmpp) handleDownloadAvatar(avatar xmpp.AvatarData) {
rmsg := config.Message{
Username: "system",
Text: "avatar",
Channel: b.parseChannel(avatar.From),
Account: b.Account,
UserID: avatar.From,
Event: config.EventAvatarDownload,
Extra: make(map[string][]interface{}),
}
if _, ok := b.avatarMap[avatar.From]; !ok {
b.Log.Debugf("Avatar.From: %s", avatar.From)
err := helper.HandleDownloadSize(b.Log, &rmsg, avatar.From+".png", int64(len(avatar.Data)), b.General)
if err != nil {
b.Log.Error(err)
return
}
helper.HandleDownloadData(b.Log, &rmsg, avatar.From+".png", rmsg.Text, "", &avatar.Data, b.General)
b.Log.Debugf("Avatar download complete")
b.Remote <- rmsg
}
}

30
bridge/xmpp/helpers.go Normal file
View File

@@ -0,0 +1,30 @@
package bxmpp
import (
"regexp"
"github.com/42wim/matterbridge/bridge/config"
)
var pathRegex = regexp.MustCompile("[^a-zA-Z0-9]+")
// GetAvatar constructs a URL for a given user-avatar if it is available in the cache.
func getAvatar(av map[string]string, userid string, general *config.Protocol) string {
if hash, ok := av[userid]; ok {
// NOTE: This does not happen in bridge/helper/helper.go but messes up XMPP
id := pathRegex.ReplaceAllString(userid, "_")
return general.MediaServerDownload + "/" + hash + "/" + id + ".png"
}
return ""
}
func (b *Bxmpp) cacheAvatar(msg *config.Message) string {
fi := msg.Extra["file"][0].(config.FileInfo)
/* if we have a sha we have successfully uploaded the file to the media server,
so we can now cache the sha */
if fi.SHA != "" {
b.Log.Debugf("Added %s to %s in avatarMap", fi.SHA, msg.UserID)
b.avatarMap[msg.UserID] = fi.SHA
}
return ""
}

View File

@@ -23,12 +23,15 @@ type Bxmpp struct {
xmppMap map[string]string
connected bool
sync.RWMutex
avatarMap map[string]string
}
func New(cfg *bridge.Config) bridge.Bridger {
return &Bxmpp{
Config: cfg,
xmppMap: make(map[string]string),
Config: cfg,
xmppMap: make(map[string]string),
avatarMap: make(map[string]string),
}
}
@@ -69,6 +72,10 @@ func (b *Bxmpp) Send(msg config.Message) (string, error) {
}
b.Log.Debugf("=> Receiving %#v", msg)
if msg.Event == config.EventAvatarDownload {
return b.cacheAvatar(&msg), nil
}
// Upload a file (in XMPP case send the upload URL because XMPP has no native upload support).
if msg.Extra != nil {
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
@@ -114,6 +121,9 @@ func (b *Bxmpp) createXMPP() error {
ServerName: strings.Split(b.GetString("Jid"), "@")[1],
InsecureSkipVerify: b.GetBool("SkipTLSVerify"), // nolint: gosec
}
xmpp.DebugWriter = b.Log.Writer()
options := xmpp.Options{
Host: b.GetString("Server"),
User: b.GetString("Jid"),
@@ -122,7 +132,6 @@ func (b *Bxmpp) createXMPP() error {
StartTLS: true,
TLSConfig: tc,
Debug: b.GetBool("debug"),
Logger: b.Log.Writer(),
Session: true,
Status: "",
StatusMessage: "",
@@ -228,6 +237,12 @@ func (b *Bxmpp) handleXMPP() error {
event = config.EventTopicChange
}
avatar := getAvatar(b.avatarMap, v.Remote, b.General)
if avatar == "" {
b.Log.Debugf("Requesting avatar data")
b.xc.AvatarRequestData(v.Remote)
}
msgID := v.ID
if v.ReplaceID != "" {
msgID = v.ReplaceID
@@ -237,6 +252,7 @@ func (b *Bxmpp) handleXMPP() error {
Text: v.Text,
Channel: b.parseChannel(v.Remote),
Account: b.Account,
Avatar: avatar,
UserID: v.Remote,
ID: msgID,
Event: event,
@@ -253,6 +269,8 @@ func (b *Bxmpp) handleXMPP() error {
b.Log.Debugf("<= Message is %#v", rmsg)
b.Remote <- rmsg
}
case xmpp.AvatarData:
b.handleDownloadAvatar(v)
case xmpp.Presence:
// Do nothing.
}

View File

@@ -1,3 +1,47 @@
# v1.17.4
## Bugfix
- general: Lowercase account names. Fixes #1108 (#1110)
- msteams: Remove panics and retry polling on failure (msteams). Fixes #1104 (#1105
- whatsapp: Update Rhymen/go-whatsapp. Fixes #1107 (#1109) (make whatsapp working again)
- discord: Add an ID cache (discord). Fixes #1106 (#1111) (fix delete/edits with webhooks)
# v1.17.3
## Enhancements
- xmpp: Implement User Avatar spoofing of XMPP users #1090
- rocketchat: Relay Joins/Topic changes in RocketChat bridge (#1085)
- irc: Add JoinDelay option (irc). Fixes #1084 (#1098)
- slack: Clip too long messages on 3000 length (slack). Fixes #1081 (#1102)
## Bugfix
- general: Fix the behavior of ShowTopicChange and SyncTopic (#1086)
- slack: Prevent image/message looping (slack). Fixes #1088 (#1096)
- whatsapp: Ignore non-critical errors (whatsapp). Fixes #1094 (#1100)
- irc: Add extra space before colon in attachments (irc). Fixes #1089 (#1101)
This release couldn't exist without the following contributors:
@42wim, @ldruschk, @qaisjp, @Polynomdivision
# v1.17.2
## Enhancements
- slack: Update vendor slack-go/slack (#1068)
- general: Update vendor d5/tengo (#1066)
- general: Clarify terminology used in mapping group chat IDs to channels in config (#1079)
## Bugfix
- whatsapp: Update Rhymen/go-whatsapp vendor and whatsapp version (#1078). Fixes Media upload #1074
- whatsapp: Reset start timestamp on reconnect (whatsapp). Fixes #1059 (#1064)
This release couldn't exist without the following contributors:
@42wim, @jheiselman
# v1.17.1
## Enhancements

View File

@@ -108,7 +108,7 @@ func (gw *Gateway) AddBridge(cfg *config.Bridge) error {
func (gw *Gateway) checkConfig(cfg *config.Bridge) {
match := false
for _, key := range gw.Router.Config.Viper().AllKeys() {
if strings.HasPrefix(key, cfg.Account) {
if strings.HasPrefix(key, strings.ToLower(cfg.Account)) {
match = true
break
}

View File

@@ -169,7 +169,7 @@ func (gw *Gateway) ignoreEvent(event string, dest *bridge.Bridge) bool {
switch event {
case config.EventAvatarDownload:
// Avatar downloads are only relevant for telegram and mattermost for now
if dest.Protocol != "mattermost" && dest.Protocol != "telegram" {
if dest.Protocol != "mattermost" && dest.Protocol != "telegram" && dest.Protocol != "xmpp" {
return true
}
case config.EventJoinLeave:
@@ -179,7 +179,7 @@ func (gw *Gateway) ignoreEvent(event string, dest *bridge.Bridge) bool {
}
case config.EventTopicChange:
// only relay topic change when used in some way on other side
if dest.GetBool("ShowTopicChange") && dest.GetBool("SyncTopic") {
if !dest.GetBool("ShowTopicChange") && !dest.GetBool("SyncTopic") {
return true
}
}

11
go.mod
View File

@@ -5,8 +5,8 @@ require (
github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f
github.com/Jeffail/gabs v1.1.1 // indirect
github.com/Philipp15b/go-steam v1.0.1-0.20190816133340-b04c5a83c1c0
github.com/Rhymen/go-whatsapp v0.1.0
github.com/d5/tengo/v2 v2.0.2
github.com/Rhymen/go-whatsapp v0.1.1-0.20200421062035-31e8111ac334
github.com/d5/tengo/v2 v2.1.2
github.com/dfordsoft/golib v0.0.0-20180902042739-76ee6ab99bec
github.com/fsnotify/fsnotify v1.4.7
github.com/go-telegram-bot-api/telegram-bot-api v4.6.5-0.20181225215658-ec221ba9ea45+incompatible
@@ -21,14 +21,13 @@ require (
github.com/keybase/go-keybase-chat-bot v0.0.0-20200226211841-4e48f3eaef3e
github.com/labstack/echo/v4 v4.1.13
github.com/lrstanley/girc v0.0.0-20190801035559-4fc93959e1a7
github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20190210153444-cc9d05784d5d
github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20200411204219-d5c18ce75048
github.com/matterbridge/discordgo v0.18.1-0.20200308151012-aa40f01cbcc3
github.com/matterbridge/emoji v2.1.1-0.20191117213217-af507f6b02db+incompatible
github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91
github.com/matterbridge/go-xmpp v0.0.0-20200418225040-c8a3a57b4050
github.com/matterbridge/gomatrix v0.0.0-20200209224845-c2104d7936a6
github.com/matterbridge/gozulipbot v0.0.0-20190212232658-7aa251978a18
github.com/matterbridge/logrus-prefixed-formatter v0.0.0-20180806162718-01618749af61
github.com/matterbridge/msgraph.go v0.0.0-20200308150230-9e043fe9dbaa
github.com/mattermost/mattermost-server v5.5.0+incompatible
github.com/mattn/go-runewidth v0.0.7 // indirect
github.com/mattn/godown v0.0.0-20180312012330-2e9e17e0ea51
@@ -46,7 +45,7 @@ require (
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca
github.com/shazow/ssh-chat v1.8.3-0.20200308224626-80ddf1f43a98
github.com/sirupsen/logrus v1.4.2
github.com/slack-go/slack v0.6.3-0.20200228121756-f56d616d5901
github.com/slack-go/slack v0.6.3
github.com/spf13/viper v1.6.1
github.com/stretchr/testify v1.4.0
github.com/technoweenie/multipartstreamer v1.0.1 // indirect

26
go.sum
View File

@@ -12,8 +12,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
github.com/Philipp15b/go-steam v1.0.1-0.20190816133340-b04c5a83c1c0 h1:TO7d4rocnNFng6ZQrPe7U6WqHtK5eHEMrgrnnM/72IQ=
github.com/Philipp15b/go-steam v1.0.1-0.20190816133340-b04c5a83c1c0/go.mod h1:HuVM+sZFzumUdKPWiz+IlCMb4RdsKdT3T+nQBKL+sYg=
github.com/Rhymen/go-whatsapp v0.0.0/go.mod h1:rdQr95g2C1xcOfM7QGOhza58HeI3I+tZ/bbluv7VazA=
github.com/Rhymen/go-whatsapp v0.1.0 h1:XTXhFIQ/fx9jKObUnUX2Q+nh58EyeHNhX7DniE8xeuA=
github.com/Rhymen/go-whatsapp v0.1.0/go.mod h1:xJSy+okeRjKkQEH/lEYrnekXB3PG33fqL0I6ncAkV50=
github.com/Rhymen/go-whatsapp v0.1.1-0.20200421062035-31e8111ac334 h1:kb1zvD+xd+XbPUdQ0lMxnRaQ76N5C9vMAClLi8Dyw1Y=
github.com/Rhymen/go-whatsapp v0.1.1-0.20200421062035-31e8111ac334/go.mod h1:o7jjkvKnigfu432dMbQ/w4PH0Yp5u4Y6ysCNjUlcYCk=
github.com/Rhymen/go-whatsapp/examples/echo v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:zgCiQtBtZ4P4gFWvwl9aashsdwOcbb/EHOGRmSzM8ME=
github.com/Rhymen/go-whatsapp/examples/restoreSession v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:5sCUSpG616ZoSJhlt9iBNI/KXBqrVLcNUJqg7J9+8pU=
github.com/Rhymen/go-whatsapp/examples/sendImage v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:RdiyhanVEGXTam+mZ3k6Y3VDCCvXYCwReOoxGozqhHw=
@@ -33,8 +33,8 @@ github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/d5/tengo/v2 v2.0.2 h1:3APkPZPc1FExaJoWrN5YzvDqc6GNkQH6ehmCRDmN83I=
github.com/d5/tengo/v2 v2.0.2/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0DZC8=
github.com/d5/tengo/v2 v2.1.2 h1:JR5O6qJW2GW9lpv/MfEqK16a/Wpp2y8I0JZZ5fqNOL0=
github.com/d5/tengo/v2 v2.1.2/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0DZC8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -124,22 +124,20 @@ github.com/lrstanley/girc v0.0.0-20190801035559-4fc93959e1a7 h1:BS9tqL0OCiOGuy/C
github.com/lrstanley/girc v0.0.0-20190801035559-4fc93959e1a7/go.mod h1:liX5MxHPrwgHaKowoLkYGwbXfYABh1jbZ6FpElbGF1I=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20190210153444-cc9d05784d5d h1:F+Sr+C0ojSlYQ37BLylQtSFmyQULe3jbAygcyXQ9mVs=
github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20190210153444-cc9d05784d5d/go.mod h1:c6MxwqHD+0HvtAJjsHMIdPCiAwGiQwPRPTp69ACMg8A=
github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20200411204219-d5c18ce75048 h1:B9HaistmV+MD8/33BXmZe1zPIn+RImAFVXNNSOrwU2E=
github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20200411204219-d5c18ce75048/go.mod h1:c6MxwqHD+0HvtAJjsHMIdPCiAwGiQwPRPTp69ACMg8A=
github.com/matterbridge/discordgo v0.18.1-0.20200308151012-aa40f01cbcc3 h1:VP/DNRn2HtrVRN6+X3h4FDcQI2OOKT+88WUi21ZD1Kw=
github.com/matterbridge/discordgo v0.18.1-0.20200308151012-aa40f01cbcc3/go.mod h1:5a1bHtG/38ofcx9cgwM5eTW/Pl4SpbQksNDnTRcGA2Y=
github.com/matterbridge/emoji v2.1.1-0.20191117213217-af507f6b02db+incompatible h1:oaOqwbg5HxHRxvAbd84ks0Okwoc1ISyUZ87EiVJFhGI=
github.com/matterbridge/emoji v2.1.1-0.20191117213217-af507f6b02db+incompatible/go.mod h1:igE6rUAn3jai2wCdsjFHfhUoekjrFthoEjFObKKwSb4=
github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91 h1:KzDEcy8eDbTx881giW8a6llsAck3e2bJvMyKvh1IK+k=
github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91/go.mod h1:ECDRehsR9TYTKCAsRS8/wLeOk6UUqDydw47ln7wG41Q=
github.com/matterbridge/go-xmpp v0.0.0-20200418225040-c8a3a57b4050 h1:kWkP1lXpkvtoNL08jkP3XQH/zvDOEXJpdCJd/DlIvMw=
github.com/matterbridge/go-xmpp v0.0.0-20200418225040-c8a3a57b4050/go.mod h1:ECDRehsR9TYTKCAsRS8/wLeOk6UUqDydw47ln7wG41Q=
github.com/matterbridge/gomatrix v0.0.0-20200209224845-c2104d7936a6 h1:Kl65VJv38HjYFnnwH+MP6Z8hcJT5UHuSpHVU5vW1HH0=
github.com/matterbridge/gomatrix v0.0.0-20200209224845-c2104d7936a6/go.mod h1:+jWeaaUtXQbBRdKYWfjW6JDDYiI2XXE+3NnTjW5kg8g=
github.com/matterbridge/gozulipbot v0.0.0-20190212232658-7aa251978a18 h1:fLhwXtWGtfTgZVxHG1lcKjv+re7dRwyyuYFNu69xdho=
github.com/matterbridge/gozulipbot v0.0.0-20190212232658-7aa251978a18/go.mod h1:yAjnZ34DuDyPHMPHHjOsTk/FefW4JJjoMMCGt/8uuQA=
github.com/matterbridge/logrus-prefixed-formatter v0.0.0-20180806162718-01618749af61 h1:R/MgM/eUyRBQx2FiH6JVmXck8PaAuKfe2M1tWIzW7nE=
github.com/matterbridge/logrus-prefixed-formatter v0.0.0-20180806162718-01618749af61/go.mod h1:iXGEotOvwI1R1SjLxRc+BF5rUORTMtE0iMZBT2lxqAU=
github.com/matterbridge/msgraph.go v0.0.0-20200308150230-9e043fe9dbaa h1:ZP87nK5Mhgvt6Rpgbztdmq9+6cb3aZ6MgW/JWKbnMrI=
github.com/matterbridge/msgraph.go v0.0.0-20200308150230-9e043fe9dbaa/go.mod h1:l0kx9L8Z+NbBCGrQ/y+ldKZ/fiwBZjPoXwDS55LTumI=
github.com/mattermost/mattermost-server v5.5.0+incompatible h1:0wcLGgYtd+YImtLDPf2AOfpBHxbU4suATx+6XKw1XbU=
github.com/mattermost/mattermost-server v5.5.0+incompatible/go.mod h1:5L6MjAec+XXQwMIt791Ganu45GKsSiM+I0tLR9wUj8Y=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
@@ -169,6 +167,8 @@ github.com/nelsonken/gomf v0.0.0-20180504123937-a9dd2f9deae9 h1:mp6tU1r0xLostUGL
github.com/nelsonken/gomf v0.0.0-20180504123937-a9dd2f9deae9/go.mod h1:A5SRAcpTemjGgIuBq6Kic2yHcoeUFWUinOAlMP/i9xo=
github.com/nicksnyder/go-i18n v1.4.0 h1:AgLl+Yq7kg5OYlzCgu9cKTZOyI4tD/NgukKqLqC8E+I=
github.com/nicksnyder/go-i18n v1.4.0/go.mod h1:HrK7VCrbOvQoUAQ7Vpy7i87N7JZZZ7R2xBGjv0j365Q=
github.com/nlopes/slack v0.6.0 h1:jt0jxVQGhssx1Ib7naAOZEZcGdtIhTzkP0nopK0AsRA=
github.com/nlopes/slack v0.6.0/go.mod h1:JzQ9m3PMAqcpeCam7UaHSuBuupz7CmpjehYMayT6YOk=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -216,8 +216,8 @@ github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9 h1:lpEzuenPuO1XNTeikEmvqYFcU37GVLl8SRNblzyvGBE=
github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9/go.mod h1:PLPIyL7ikehBD1OAjmKKiOEhbvWyHGaNDjquXMcYABo=
github.com/slack-go/slack v0.6.3-0.20200228121756-f56d616d5901 h1:sXIMY2YPYEm5NoGMCrJC50N+8t9W6vbY9qr61zcLEAE=
github.com/slack-go/slack v0.6.3-0.20200228121756-f56d616d5901/go.mod h1:ZUNi+O1Pwr2ch2UOp2AfF+s7QYQgwht2Cd1UTeIYw9A=
github.com/slack-go/slack v0.6.3 h1:qU037g8gQ71EuH6S9zYKnvYrEUj0fLFH4HFekFqBoRU=
github.com/slack-go/slack v0.6.3/go.mod h1:HE4RwNe7YpOg/F0vqo5PwXH3Hki31TplTvKRW9dGGaw=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
@@ -290,8 +290,6 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

View File

@@ -15,7 +15,7 @@ import (
)
var (
version = "1.17.1"
version = "1.17.4"
githash string
flagConfig = flag.String("conf", "matterbridge.toml", "config file")

View File

@@ -177,6 +177,12 @@ StripNick=false
#OPTIONAL (default false)
ShowTopicChange=false
#Delay in milliseconds between channel joins
#Only useful when you have a LOT of channels to join
#See https://github.com/42wim/matterbridge/issues/1084
#OPTIONAL (default 0)
JoinDelay=0
###################################################################
#XMPP section
###################################################################
@@ -1679,34 +1685,44 @@ enable=true
# REQUIRED
account="irc.freenode"
# channel to connect on that account
# How to specify them for the different bridges:
# The channel key in each gateway is mapped to a similar group chat ID on the chat platform
# To find the group chat ID for different platforms, refer to the table below
#
# irc - #channel (# is required) (this needs to be lowercase!)
# mattermost - channel (the channel name as seen in the URL, not the displayname)
# gitter - username/room
# xmpp - channel
# slack - channel (without the #)
# - ID:C123456 (where C123456 is the channel ID) does not work with webhook
# discord - channel (without the #)
# - ID:123456789 (where 123456789 is the channel ID)
# (https://github.com/42wim/matterbridge/issues/57)
# - category/channel (without the #) if you're using discord categories to group your channels
# telegram - chatid (a large negative number, eg -123456789)
# see (https://www.linkedin.com/pulse/telegram-bots-beginners-marco-frau)
# hipchat - id_channel (see https://www.hipchat.com/account/xmpp for the correct channel)
# rocketchat - #channel (# is required (also needed for private channels!)
# matrix - #channel:server (eg #yourchannel:matrix.org)
# - encrypted rooms are not supported in matrix
# msteams - 19:xxxxxxxxxxxxxxxxxxxxxxxxxx@thread.skype
# - You'll find the channel ID in the URL in the threadId=19:82abcxxxxxxxxx@thread.skype
# steam - chatid (a large number).
# The number in the URL when you click "enter chat room" in the browser
# whatsapp - 48111222333-123455678999@g.us A unique group JID;
# if you specify an empty string bridge will list all the possibilities
# - "Group Name" if you specify a group name the bridge will hint its JID to specify
# as group names might change in time and contain weird emoticons
# zulip - stream/topic:topicname (without the #)
# Platform | Identifier name | Example | Description
# -------------------------------------------------------------------------------------------------------------------------------------
# | channel | general | Do not include the # symbol
# discord | channel id | ID:123456789 | See https://github.com/42wim/matterbridge/issues/57
# | category/channel | Media/gaming | Without # symbol. If you're using discord categories to group your channels
# -------------------------------------------------------------------------------------------------------------------------------------
# gitter | username/room | general | As seen in the gitter.im URL
# -------------------------------------------------------------------------------------------------------------------------------------
# hipchat | id_channel | example needed | See https://www.hipchat.com/account/xmpp for the correct channel
# -------------------------------------------------------------------------------------------------------------------------------------
# irc | channel | #general | The # symbol is required and should be lowercase!
# -------------------------------------------------------------------------------------------------------------------------------------
# mattermost | channel | general | This is the channel name as seen in the URL, not the display name
# -------------------------------------------------------------------------------------------------------------------------------------
# matrix | #channel:server | #yourchannel:matrix.org | Encrypted rooms are not supported in matrix
# -------------------------------------------------------------------------------------------------------------------------------------
# msteams | threadId | 19:82abcxx@thread.skype | You'll find the threadId in the URL
# -------------------------------------------------------------------------------------------------------------------------------------
# rocketchat | channel | #channel | # is required for private channels too
# -------------------------------------------------------------------------------------------------------------------------------------
# slack | channel name | general | Do not include the # symbol
# | channel id | ID:C123456 | The underlying ID of a channel. This doesn't work with
# -------------------------------------------------------------------------------------------------------------------------------------
# steam | chatid | example needed | The number in the URL when you click "enter chat room" in the browser
# -------------------------------------------------------------------------------------------------------------------------------------
# telegram | chatid | -123456789 | A large negative number. see https://www.linkedin.com/pulse/telegram-bots-beginners-marco-frau
# -------------------------------------------------------------------------------------------------------------------------------------
# whatsapp | group JID | 48111222333-123455678999@g.us | A unique group JID. If you specify an empty string, bridge will list all the possibilities
# | "Group Name" | "Family Chat" | if you specify a group name, the bridge will find hint the JID to specify. Names can change over time and are not stable.
# -------------------------------------------------------------------------------------------------------------------------------------
# xmpp | channel | general | The room name
# -------------------------------------------------------------------------------------------------------------------------------------
# zulip | stream/topic:topic | general/off-topic:food | Do not use the # when specifying a topic
# -------------------------------------------------------------------------------------------------------------------------------------
#
# REQUIRED
channel="#testing"

File diff suppressed because it is too large Load Diff

View File

@@ -56,6 +56,8 @@ message Location {
}
message Point {
optional int32 xDeprecated = 1;
optional int32 yDeprecated = 2;
optional double x = 3;
optional double y = 4;
}
@@ -93,6 +95,7 @@ message ContextInfo {
optional AdReplyInfo quotedAd = 23;
optional MessageKey placeholderKey = 24;
optional uint32 expiration = 25;
optional int64 ephemeralSettingTimestamp = 26;
}
message SenderKeyDistributionMessage {
@@ -136,6 +139,11 @@ message LocationMessage {
optional string name = 3;
optional string address = 4;
optional string url = 5;
optional bool isLive = 6;
optional uint32 accuracyInMeters = 7;
optional float speedInMps = 8;
optional uint32 degreesClockwiseFromMagneticNorth = 9;
optional string comment = 11;
optional bytes jpegThumbnail = 16;
optional ContextInfo contextInfo = 17;
}
@@ -238,9 +246,29 @@ message ProtocolMessage {
enum PROTOCOL_MESSAGE_TYPE {
REVOKE = 0;
EPHEMERAL_SETTING = 3;
EPHEMERAL_SYNC_RESPONSE = 4;
HISTORY_SYNC_NOTIFICATION = 5;
}
optional PROTOCOL_MESSAGE_TYPE type = 2;
optional uint32 ephemeralExpiration = 4;
optional int64 ephemeralSettingTimestamp = 5;
optional HistorySyncNotification historySyncNotification = 6;
}
message HistorySyncNotification {
optional bytes fileSha256 = 1;
optional uint64 fileLength = 2;
optional bytes mediaKey = 3;
optional bytes fileEncSha256 = 4;
optional string directPath = 5;
enum HISTORY_SYNC_NOTIFICATION_HISTORYSYNCTYPE {
INITIAL_BOOTSTRAP = 0;
INITIAL_STATUS_V3 = 1;
FULL = 2;
RECENT = 3;
}
optional HISTORY_SYNC_NOTIFICATION_HISTORYSYNCTYPE syncType = 6;
optional uint32 chunkOrder = 7;
}
message ContactsArrayMessage {
@@ -355,6 +383,8 @@ message StickerMessage {
optional int64 mediaKeyTimestamp = 10;
optional uint32 firstFrameLength = 11;
optional bytes firstFrameSidecar = 12;
optional bool isAnimated = 13;
optional bytes pngThumbnail = 16;
optional ContextInfo contextInfo = 17;
}
@@ -401,6 +431,12 @@ message TemplateButtonReplyMessage {
optional uint32 selectedIndex = 4;
}
message CatalogSnapshot {
optional ImageMessage catalogImage = 1;
optional string title = 2;
optional string description = 3;
}
message ProductSnapshot {
optional ImageMessage productImage = 1;
optional string productId = 2;
@@ -417,6 +453,7 @@ message ProductSnapshot {
message ProductMessage {
optional ProductSnapshot product = 1;
optional string businessOwnerJid = 2;
optional CatalogSnapshot catalog = 4;
optional ContextInfo contextInfo = 17;
}
@@ -513,6 +550,8 @@ message WebFeatures {
optional WEB_FEATURES_FLAG templateMessage = 30;
optional WEB_FEATURES_FLAG templateMessageInteractivity = 31;
optional WEB_FEATURES_FLAG ephemeralMessages = 32;
optional WEB_FEATURES_FLAG e2ENotificationSync = 33;
optional WEB_FEATURES_FLAG recentStickersV2 = 34;
}
message TabletNotificationsInfo {
@@ -537,6 +576,11 @@ message WebNotificationsInfo {
}
message PaymentInfo {
enum PAYMENT_INFO_CURRENCY {
UNKNOWN_CURRENCY = 0;
INR = 1;
}
optional PAYMENT_INFO_CURRENCY currencyDeprecated = 1;
optional uint64 amount1000 = 2;
optional string receiverJid = 3;
enum PAYMENT_INFO_STATUS {
@@ -559,6 +603,37 @@ message PaymentInfo {
optional uint64 expiryTimestamp = 7;
optional bool futureproofed = 8;
optional string currency = 9;
enum PAYMENT_INFO_TXNSTATUS {
UNKNOWN = 0;
PENDING_SETUP = 1;
PENDING_RECEIVER_SETUP = 2;
INIT = 3;
SUCCESS = 4;
COMPLETED = 5;
FAILED = 6;
FAILED_RISK = 7;
FAILED_PROCESSING = 8;
FAILED_RECEIVER_PROCESSING = 9;
FAILED_DA = 10;
FAILED_DA_FINAL = 11;
REFUNDED_TXN = 12;
REFUND_FAILED = 13;
REFUND_FAILED_PROCESSING = 14;
REFUND_FAILED_DA = 15;
EXPIRED_TXN = 16;
AUTH_CANCELED = 17;
AUTH_CANCEL_FAILED_PROCESSING = 18;
AUTH_CANCEL_FAILED = 19;
COLLECT_INIT = 20;
COLLECT_SUCCESS = 21;
COLLECT_FAILED = 22;
COLLECT_FAILED_RISK = 23;
COLLECT_REJECTED = 24;
COLLECT_EXPIRED = 25;
COLLECT_CANCELED = 26;
COLLECT_CANCELLING = 27;
}
optional PAYMENT_INFO_TXNSTATUS txnStatus = 10;
}
message WebMessageInfo {
@@ -668,4 +743,5 @@ message WebMessageInfo {
optional PaymentInfo quotedPaymentInfo = 31;
optional uint64 ephemeralStartTimestamp = 32;
optional uint32 ephemeralDuration = 33;
}
}

View File

@@ -90,6 +90,7 @@ type Conn struct {
longClientName string
shortClientName string
clientVersion string
loginSessionLock sync.RWMutex
Proxy func(*http.Request) (*url.URL, error)
@@ -121,6 +122,7 @@ func NewConn(timeout time.Duration) (*Conn, error) {
longClientName: "github.com/rhymen/go-whatsapp",
shortClientName: "go-whatsapp",
clientVersion: "0.1.0",
}
return wac, wac.connect()
}
@@ -135,6 +137,7 @@ func NewConnWithProxy(timeout time.Duration, proxy func(*http.Request) (*url.URL
longClientName: "github.com/rhymen/go-whatsapp",
shortClientName: "go-whatsapp",
clientVersion: "0.1.0",
Proxy: proxy,
}
return wac, wac.connect()

View File

@@ -6,7 +6,7 @@ require (
github.com/Rhymen/go-whatsapp/examples/sendImage v0.0.0-20190325075644-cc2581bbf24d // indirect
github.com/Rhymen/go-whatsapp/examples/sendTextMessages v0.0.0-20190325075644-cc2581bbf24d // indirect
github.com/golang/protobuf v1.3.0
github.com/gorilla/websocket v1.4.0
github.com/gorilla/websocket v1.4.1
github.com/pkg/errors v0.8.1
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
)

View File

@@ -12,8 +12,9 @@ github.com/Rhymen/go-whatsapp/examples/sendTextMessages v0.0.0-20190325075644-cc
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk=
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw=

View File

@@ -10,10 +10,8 @@ import (
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"os"
"strings"
"net/url"
"time"
"github.com/Rhymen/go-whatsapp/crypto/cbc"
@@ -95,7 +93,50 @@ func downloadMedia(url string) (file []byte, mac []byte, err error) {
return data[:n-10], data[n-10 : n], nil
}
func (wac *Conn) Upload(reader io.Reader, appInfo MediaType) (url string, mediaKey []byte, fileEncSha256 []byte, fileSha256 []byte, fileLength uint64, err error) {
type MediaConn struct {
Status int `json:"status"`
MediaConn struct {
Auth string `json:"auth"`
TTL int `json:"ttl"`
Hosts []struct {
Hostname string `json:"hostname"`
IPs []string `json:"ips"`
} `json:"hosts"`
} `json:"media_conn"`
}
func (wac *Conn) queryMediaConn() (hostname, auth string, ttl int, err error) {
queryReq := []interface{}{"query", "mediaConn"}
ch, err := wac.writeJson(queryReq)
if err != nil {
return "", "", 0, err
}
var resp MediaConn
select {
case r := <-ch:
if err = json.Unmarshal([]byte(r), &resp); err != nil {
return "", "", 0, fmt.Errorf("error decoding query media conn response: %v", err)
}
case <-time.After(wac.msgTimeout):
return "", "", 0, fmt.Errorf("query media conn timed out")
}
if resp.Status != 200 {
return "", "", 0, fmt.Errorf("query media conn responded with %d", resp.Status)
}
return resp.MediaConn.Hosts[0].Hostname, resp.MediaConn.Auth, resp.MediaConn.TTL, nil
}
var mediaTypeMap = map[MediaType]string{
MediaImage: "/mms/image",
MediaVideo: "/mms/video",
MediaDocument: "/mms/document",
MediaAudio: "/mms/audio",
}
func (wac *Conn) Upload(reader io.Reader, appInfo MediaType) (downloadURL string, mediaKey []byte, fileEncSha256 []byte, fileSha256 []byte, fileLength uint64, err error) {
data, err := ioutil.ReadAll(reader)
if err != nil {
return "", nil, nil, nil, 0, err
@@ -128,67 +169,30 @@ func (wac *Conn) Upload(reader io.Reader, appInfo MediaType) (url string, mediaK
sha.Write(append(enc, mac...))
fileEncSha256 = sha.Sum(nil)
var filetype string
switch appInfo {
case MediaImage:
filetype = "image"
case MediaAudio:
filetype = "audio"
case MediaDocument:
filetype = "document"
case MediaVideo:
filetype = "video"
hostname, auth, _, err := wac.queryMediaConn()
token := base64.URLEncoding.EncodeToString(fileEncSha256)
q := url.Values{
"auth": []string{auth},
"token": []string{token},
}
path := mediaTypeMap[appInfo]
uploadURL := url.URL{
Scheme: "https",
Host: hostname,
Path: fmt.Sprintf("%s/%s", path, token),
RawQuery: q.Encode(),
}
uploadReq := []interface{}{"action", "encr_upload", filetype, base64.StdEncoding.EncodeToString(fileEncSha256)}
ch, err := wac.writeJson(uploadReq)
body := bytes.NewReader(append(enc, mac...))
req, err := http.NewRequest("POST", uploadURL.String(), body)
if err != nil {
return "", nil, nil, nil, 0, err
}
var resp map[string]interface{}
select {
case r := <-ch:
if err = json.Unmarshal([]byte(r), &resp); err != nil {
return "", nil, nil, nil, 0, fmt.Errorf("error decoding upload response: %v", err)
}
case <-time.After(wac.msgTimeout):
return "", nil, nil, nil, 0, fmt.Errorf("restore session init timed out")
}
if int(resp["status"].(float64)) != 200 {
return "", nil, nil, nil, 0, fmt.Errorf("upload responsed with %d", resp["status"])
}
var b bytes.Buffer
w := multipart.NewWriter(&b)
hashWriter, err := w.CreateFormField("hash")
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
}
io.Copy(hashWriter, strings.NewReader(base64.StdEncoding.EncodeToString(fileEncSha256)))
fileWriter, err := w.CreateFormFile("file", "blob")
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
}
io.Copy(fileWriter, bytes.NewReader(append(enc, mac...)))
err = w.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
}
req, err := http.NewRequest("POST", resp["url"].(string), &b)
if err != nil {
return "", nil, nil, nil, 0, err
}
req.Header.Set("Content-Type", w.FormDataContentType())
req.Header.Set("Origin", "https://web.whatsapp.com")
req.Header.Set("Referer", "https://web.whatsapp.com/")
req.URL.Query().Set("f", "j")
client := &http.Client{}
// Submit the request
res, err := client.Do(req)

View File

@@ -15,7 +15,10 @@ import (
)
func (wac *Conn) readPump() {
defer wac.wg.Done()
defer func() {
wac.wg.Done()
_, _ = wac.Disconnect()
}()
var readErr error
var msgType int
@@ -31,7 +34,6 @@ func (wac *Conn) readPump() {
case <-readerFound:
if readErr != nil {
wac.handle(&ErrConnectionFailed{Err: readErr})
_, _ = wac.Disconnect()
return
}
msg, err := ioutil.ReadAll(reader)

View File

@@ -18,7 +18,7 @@ import (
)
//represents the WhatsAppWeb client version
var waVersion = []int{0, 3, 3324}
var waVersion = []int{0, 4, 2080}
/*
Session contains session individual information. To be able to resume the connection without scanning the qr code
@@ -107,10 +107,10 @@ func CheckCurrentServerVersion() ([]int, error) {
}
b64ClientId := base64.StdEncoding.EncodeToString(clientId)
login := []interface{}{"admin", "init", waVersion, []string{wac.longClientName, wac.shortClientName}, b64ClientId, true}
login := []interface{}{"admin", "init", waVersion, []string{wac.longClientName, wac.shortClientName, wac.clientVersion}, b64ClientId, true}
loginChan, err := wac.writeJson(login)
if err != nil {
return nil, fmt.Errorf("error writing login", err)
return nil, fmt.Errorf("error writing login: %s", err.Error())
}
// Retrieve an answer from the websocket
@@ -123,7 +123,7 @@ func CheckCurrentServerVersion() ([]int, error) {
var resp map[string]interface{}
if err = json.Unmarshal([]byte(r), &resp); err != nil {
return nil, fmt.Errorf("error decoding login", err)
return nil, fmt.Errorf("error decoding login: %s", err.Error())
}
// Take the curr property as X.Y.Z and split it into as int slice
@@ -141,17 +141,17 @@ func CheckCurrentServerVersion() ([]int, error) {
SetClientName sets the long and short client names that are sent to WhatsApp when logging in and displayed in the
WhatsApp Web device list. As the values are only sent when logging in, changing them after logging in is not possible.
*/
func (wac *Conn) SetClientName(long, short string) error {
func (wac *Conn) SetClientName(long, short, version string) error {
if wac.session != nil && (wac.session.EncKey != nil || wac.session.MacKey != nil) {
return fmt.Errorf("cannot change client name after logging in")
}
wac.longClientName, wac.shortClientName = long, short
wac.longClientName, wac.shortClientName, wac.clientVersion = long, short, version
return nil
}
/*
SetClientVersion sets WhatsApp client version
Default value is 0.3.3324
Default value is 0.4.2080
*/
func (wac *Conn) SetClientVersion(major int, minor int, patch int) {
waVersion = []int{major, minor, patch}
@@ -213,7 +213,7 @@ func (wac *Conn) Login(qrChan chan<- string) (Session, error) {
}
session.ClientId = base64.StdEncoding.EncodeToString(clientId)
login := []interface{}{"admin", "init", waVersion, []string{wac.longClientName, wac.shortClientName}, session.ClientId, true}
login := []interface{}{"admin", "init", waVersion, []string{wac.longClientName, wac.shortClientName, wac.clientVersion}, session.ClientId, true}
loginChan, err := wac.writeJson(login)
if err != nil {
return session, fmt.Errorf("error writing login: %v\n", err)
@@ -369,7 +369,7 @@ func (wac *Conn) Restore() error {
wac.listener.Unlock()
//admin init
init := []interface{}{"admin", "init", waVersion, []string{wac.longClientName, wac.shortClientName}, wac.session.ClientId, true}
init := []interface{}{"admin", "init", waVersion, []string{wac.longClientName, wac.shortClientName, wac.clientVersion}, wac.session.ClientId, true}
initChan, err := wac.writeJson(init)
if err != nil {
return fmt.Errorf("error writing admin init: %v\n", err)

View File

@@ -11,9 +11,10 @@ builds:
- darwin
- linux
- windows
archive:
files:
- none*
archives:
-
files:
- none*
checksum:
name_template: 'checksums.txt'
changelog:

View File

@@ -7,7 +7,6 @@
[![GoDoc](https://godoc.org/github.com/d5/tengo?status.svg)](https://godoc.org/github.com/d5/tengo)
[![Go Report Card](https://goreportcard.com/badge/github.com/d5/tengo)](https://goreportcard.com/report/github.com/d5/tengo)
[![CircleCI](https://circleci.com/gh/d5/tengo.svg?style=svg)](https://circleci.com/gh/d5/tengo)
[![Sourcegraph](https://sourcegraph.com/github.com/d5/tengo/-/badge.svg)](https://sourcegraph.com/github.com/d5/tengo?badge)
**Tengo is a small, dynamic, fast, secure script language for Go.**
@@ -75,6 +74,10 @@ _* See [here](https://github.com/d5/tengobench) for commands/codes used_
## Quick Start
```
go get github.com/d5/tengo/v2
```
A simple Go example code that compiles/runs Tengo script code with some input/output values:
```golang

View File

@@ -13,6 +13,14 @@ var builtinFuncs = []*BuiltinFunction{
Name: "append",
Value: builtinAppend,
},
{
Name: "delete",
Value: builtinDelete,
},
{
Name: "splice",
Value: builtinSplice,
},
{
Name: "string",
Value: builtinString,
@@ -500,3 +508,104 @@ func builtinAppend(args ...Object) (Object, error) {
}
}
}
// builtinDelete deletes Map keys
// usage: delete(map, "key")
// key must be a string
func builtinDelete(args ...Object) (Object, error) {
argsLen := len(args)
if argsLen != 2 {
return nil, ErrWrongNumArguments
}
switch arg := args[0].(type) {
case *Map:
if key, ok := args[1].(*String); ok {
delete(arg.Value, key.Value)
return UndefinedValue, nil
}
return nil, ErrInvalidArgumentType{
Name: "second",
Expected: "string",
Found: args[1].TypeName(),
}
default:
return nil, ErrInvalidArgumentType{
Name: "first",
Expected: "map",
Found: arg.TypeName(),
}
}
}
// builtinSplice deletes and changes given Array, returns deleted items.
// usage:
// deleted_items := splice(array[,start[,delete_count[,item1[,item2[,...]]]])
func builtinSplice(args ...Object) (Object, error) {
argsLen := len(args)
if argsLen == 0 {
return nil, ErrWrongNumArguments
}
array, ok := args[0].(*Array)
if !ok {
return nil, ErrInvalidArgumentType{
Name: "first",
Expected: "array",
Found: args[0].TypeName(),
}
}
arrayLen := len(array.Value)
var startIdx int
if argsLen > 1 {
arg1, ok := args[1].(*Int)
if !ok {
return nil, ErrInvalidArgumentType{
Name: "second",
Expected: "int",
Found: args[1].TypeName(),
}
}
startIdx = int(arg1.Value)
if startIdx < 0 || startIdx > arrayLen {
return nil, ErrIndexOutOfBounds
}
}
delCount := len(array.Value)
if argsLen > 2 {
arg2, ok := args[2].(*Int)
if !ok {
return nil, ErrInvalidArgumentType{
Name: "third",
Expected: "int",
Found: args[2].TypeName(),
}
}
delCount = int(arg2.Value)
if delCount < 0 {
return nil, ErrIndexOutOfBounds
}
}
// if count of to be deleted items is bigger than expected, truncate it
if startIdx+delCount > arrayLen {
delCount = arrayLen - startIdx
}
// delete items
endIdx := startIdx + delCount
deleted := append([]Object{}, array.Value[startIdx:endIdx]...)
head := array.Value[:startIdx]
var items []Object
if argsLen > 3 {
items = make([]Object, 0, argsLen-3)
for i := 3; i < argsLen; i++ {
items = append(items, args[i])
}
}
items = append(items, array.Value[endIdx:]...)
array.Value = append(head, items...)
// return deleted items
return &Array{Value: deleted}, nil
}

View File

@@ -80,14 +80,14 @@ func (v *VM) Run() (err error) {
if err != nil {
filePos := v.fileSet.Position(
v.curFrame.fn.SourcePos(v.ip - 1))
err = fmt.Errorf("Runtime Error: %s\n\tat %s",
err.Error(), filePos)
err = fmt.Errorf("Runtime Error: %w\n\tat %s",
err, filePos)
for v.framesIndex > 1 {
v.framesIndex--
v.curFrame = &v.frames[v.framesIndex-1]
filePos = v.fileSet.Position(
v.curFrame.fn.SourcePos(v.curFrame.ip - 1))
err = fmt.Errorf("%s\n\tat %s", err.Error(), filePos)
err = fmt.Errorf("%w\n\tat %s", err, filePos)
}
return err
}

View File

@@ -7,6 +7,7 @@ type Message struct {
RoomID string `json:"rid"`
Msg string `json:"msg"`
EditedBy string `json:"editedBy,omitempty"`
Type string `json:"t,omitempty"`
Groupable bool `json:"groupable,omitempty"`
@@ -16,6 +17,9 @@ type Message struct {
Mentions []User `json:"mentions,omitempty"`
User *User `json:"u,omitempty"`
Attachments []Attachment `json:"attachments,omitempty"`
PostMessage
// Bot interface{} `json:"bot"`

View File

@@ -191,6 +191,26 @@ func getMessageFromData(data interface{}) *models.Message {
func getMessageFromDocument(arg *gabs.Container) *models.Message {
var ts *time.Time
var attachments []models.Attachment
attachmentSrc, err := arg.Path("attachments").Children()
if err != nil {
attachments = make([]models.Attachment, 0)
} else {
attachments = make([]models.Attachment, len(attachmentSrc))
for i, attachment := range attachmentSrc {
attachments[i] = models.Attachment{
Timestamp: stringOrZero(attachment.Path("ts").Data()),
Title: stringOrZero(attachment.Path("title").Data()),
TitleLink: stringOrZero(attachment.Path("title_link").Data()),
TitleLinkDownload: stringOrZero(attachment.Path("title_link_download").Data()),
ImageURL: stringOrZero(attachment.Path("image_url").Data()),
AuthorName: stringOrZero(arg.Path("u.name").Data()),
}
}
}
date := stringOrZero(arg.Path("ts.$date").Data())
if len(date) > 0 {
if ti, err := strconv.ParseFloat(date, 64); err == nil {
@@ -202,11 +222,13 @@ func getMessageFromDocument(arg *gabs.Container) *models.Message {
ID: stringOrZero(arg.Path("_id").Data()),
RoomID: stringOrZero(arg.Path("rid").Data()),
Msg: stringOrZero(arg.Path("msg").Data()),
Type: stringOrZero(arg.Path("t").Data()),
Timestamp: ts,
User: &models.User{
ID: stringOrZero(arg.Path("u._id").Data()),
UserName: stringOrZero(arg.Path("u.username").Data()),
},
Attachments: attachments,
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
@@ -45,6 +45,9 @@ const (
// Default TLS configuration options
var DefaultConfig tls.Config
// DebugWriter is the writer used to write debugging output to.
var DebugWriter io.Writer = os.Stderr
// Cookie is a unique XMPP session identifier
type Cookie uint64
@@ -191,21 +194,40 @@ type Options struct {
// Status message
StatusMessage string
// Logger
Logger io.Writer
}
// NewClient establishes a new Client connection based on a set of Options.
func (o Options) NewClient() (*Client, error) {
host := o.Host
if strings.TrimSpace(host) == "" {
a := strings.SplitN(o.User, "@", 2)
if len(a) == 2 {
if _, addrs, err := net.LookupSRV("xmpp-client", "tcp", a[1]); err == nil {
if len(addrs) > 0 {
// default to first record
host = fmt.Sprintf("%s:%d", addrs[0].Target, addrs[0].Port)
defP := addrs[0].Priority
for _, adr := range addrs {
if adr.Priority < defP {
host = fmt.Sprintf("%s:%d", adr.Target, adr.Port)
defP = adr.Priority
}
}
} else {
host = a[1]
}
} else {
host = a[1]
}
}
}
c, err := connect(host, o.User, o.Password)
if err != nil {
return nil, err
}
if strings.LastIndex(o.Host, ":") > 0 {
host = host[:strings.LastIndex(o.Host, ":")]
if strings.LastIndex(host, ":") > 0 {
host = host[:strings.LastIndex(host, ":")]
}
client := new(Client)
@@ -487,6 +509,8 @@ func (c *Client) startTLSIfRequired(f *streamFeatures, o *Options, domain string
case f.StartTLS == nil:
// the server does not support STARTTLS
return f, nil
case !o.StartTLS && f.StartTLS.Required == nil:
return f, nil
case f.StartTLS.Required != nil:
// the server requires STARTTLS.
case !o.StartTLS:
@@ -527,7 +551,7 @@ func (c *Client) startTLSIfRequired(f *streamFeatures, o *Options, domain string
// will be returned.
func (c *Client) startStream(o *Options, domain string) (*streamFeatures, error) {
if o.Debug {
c.p = xml.NewDecoder(tee{c.conn, o.Logger})
c.p = xml.NewDecoder(tee{c.conn, DebugWriter})
} else {
c.p = xml.NewDecoder(c.conn)
}
@@ -618,6 +642,24 @@ func (c *Client) Recv() (stanza interface{}, err error) {
}
switch v := val.(type) {
case *clientMessage:
if v.Event.XMLNS == XMPPNS_PUBSUB_EVENT {
// Handle Pubsub notifications
switch v.Event.Items.Node {
case XMPPNS_AVATAR_PEP_METADATA:
return handleAvatarMetadata(v.Event.Items.Items[0].Body,
v.From)
// I am not sure whether this can even happen.
// XEP-0084 only specifies a subscription to
// the metadata node.
/*case XMPPNS_AVATAR_PEP_DATA:
return handleAvatarData(v.Event.Items.Items[0].Body,
v.From,
v.Event.Items.Items[0].ID)*/
default:
return pubsubClientToReturn(v.Event), nil
}
}
stamp, _ := time.Parse(
"2006-01-02T15:04:05Z",
v.Delay.Stamp,
@@ -644,25 +686,166 @@ func (c *Client) Recv() (stanza interface{}, err error) {
case *clientPresence:
return Presence{v.From, v.To, v.Type, v.Show, v.Status}, nil
case *clientIQ:
// TODO check more strictly
if bytes.Equal(bytes.TrimSpace(v.Query), []byte(`<ping xmlns='urn:xmpp:ping'/>`)) || bytes.Equal(bytes.TrimSpace(v.Query), []byte(`<ping xmlns="urn:xmpp:ping"/>`)) {
switch {
case v.Query.XMLName.Space == "urn:xmpp:ping":
// TODO check more strictly
err := c.SendResultPing(v.ID, v.From)
if err != nil {
return Chat{}, err
}
fallthrough
case v.Type == "error":
switch v.ID {
case "sub1":
// Pubsub subscription failed
var errs []clientPubsubError
err := xml.Unmarshal([]byte(v.Error.InnerXML), &errs)
if err != nil {
return PubsubSubscription{}, err
}
var errsStr []string
for _, e := range errs {
errsStr = append(errsStr, e.XMLName.Local)
}
return PubsubSubscription{
Errors: errsStr,
}, nil
}
case v.Type == "result":
switch v.ID {
case "sub1":
if v.Query.XMLName.Local == "pubsub" {
// Subscription or unsubscription was successful
var sub clientPubsubSubscription
err := xml.Unmarshal([]byte(v.Query.InnerXML), &sub)
if err != nil {
return PubsubSubscription{}, err
}
return PubsubSubscription{
SubID: sub.SubID,
JID: sub.JID,
Node: sub.Node,
Errors: nil,
}, nil
}
case "unsub1":
if v.Query.XMLName.Local == "pubsub" {
var sub clientPubsubSubscription
err := xml.Unmarshal([]byte(v.Query.InnerXML), &sub)
if err != nil {
return PubsubUnsubscription{}, err
}
return PubsubUnsubscription{
SubID: sub.SubID,
JID: v.From,
Node: sub.Node,
Errors: nil,
}, nil
} else {
// Unsubscribing MAY contain a pubsub element. But it does
// not have to
return PubsubUnsubscription{
SubID: "",
JID: v.From,
Node: "",
Errors: nil,
}, nil
}
case "info1":
if v.Query.XMLName.Space == XMPPNS_DISCO_ITEMS {
var itemsQuery clientDiscoItemsQuery
err := xml.Unmarshal(v.InnerXML, &itemsQuery)
if err != nil {
return []DiscoItem{}, err
}
return DiscoItems{
Jid: v.From,
Items: clientDiscoItemsToReturn(itemsQuery.Items),
}, nil
}
case "info3":
if v.Query.XMLName.Space == XMPPNS_DISCO_INFO {
var disco clientDiscoQuery
err := xml.Unmarshal(v.InnerXML, &disco)
if err != nil {
return DiscoResult{}, err
}
return DiscoResult{
Features: clientFeaturesToReturn(disco.Features),
Identities: clientIdentitiesToReturn(disco.Identities),
}, nil
}
case "items1", "items3":
if v.Query.XMLName.Local == "pubsub" {
var p clientPubsubItems
err := xml.Unmarshal([]byte(v.Query.InnerXML), &p)
if err != nil {
return PubsubItems{}, err
}
switch p.Node {
case XMPPNS_AVATAR_PEP_DATA:
return handleAvatarData(p.Items[0].Body,
v.From,
p.Items[0].ID)
case XMPPNS_AVATAR_PEP_METADATA:
return handleAvatarMetadata(p.Items[0].Body,
v.From)
default:
return PubsubItems{
p.Node,
pubsubItemsToReturn(p.Items),
}, nil
}
}
// Note: XEP-0084 states that metadata and data
// should be fetched with an id of retrieve1.
// Since we already have PubSub implemented, we
// can just use items1 and items3 to do the same
// as an Avatar node is just a PEP (PubSub) node.
/*case "retrieve1":
if v.Query.XMLName.Local == "pubsub" {
var p clientPubsubItems
err := xml.Unmarshal([]byte(v.Query.InnerXML), &p)
if err != nil {
return PubsubItems{}, err
}
switch p.Node {
case XMPPNS_AVATAR_PEP_DATA:
return handleAvatarData(p.Items[0].Body,
v.From,
p.Items[0].ID)
case XMPPNS_AVATAR_PEP_METADATA:
return handleAvatarMetadata(p.Items[0].Body,
v.From)
}
}*/
}
case v.Query.XMLName.Local == "":
return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type}, nil
default:
res, err := xml.Marshal(v.Query)
if err != nil {
return Chat{}, err
}
return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type,
Query: res}, nil
}
return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type, Query: v.Query}, nil
}
}
}
// Send sends the message wrapped inside an XMPP message stanza body.
func (c *Client) Send(chat Chat) (n int, err error) {
var subtext = ``
var thdtext = ``
var oobtext = ``
var msgidtext = ``
var msgcorrecttext = ``
var subtext, thdtext, oobtext, msgidtext, msgcorrecttext string
if chat.Subject != `` {
subtext = `<subject>` + xmlEscape(chat.Subject) + `</subject>`
}
@@ -676,20 +859,25 @@ func (c *Client) Send(chat Chat) (n int, err error) {
}
oobtext += `</x>`
}
if chat.ID != `` {
msgidtext = `id='` + xmlEscape(chat.ID) + `'`
} else {
msgidtext = `id='` + cnonce() + `'`
}
if chat.ReplaceID != `` {
msgcorrecttext = `<replace id='` + xmlEscape(chat.ReplaceID) + `' xmlns='urn:xmpp:message-correct:0'/>`
}
return fmt.Fprintf(c.conn, "<message to='%s' type='%s' " + msgidtext + " xml:lang='en'>" + subtext + "<body>%s</body>" + msgcorrecttext + oobtext + thdtext + "</message>",
xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text))
stanza := "<message to='%s' type='%s' " + msgidtext + " xml:lang='en'>" + subtext + "<body>%s</body>" + msgcorrecttext + oobtext + thdtext + "</message>"
return fmt.Fprintf(c.conn, stanza, xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text))
}
// SendOOB sends OOB data wrapped inside an XMPP message stanza, without actual body.
func (c *Client) SendOOB(chat Chat) (n int, err error) {
var thdtext = ``
var oobtext = ``
var thdtext, oobtext string
if chat.Thread != `` {
thdtext = `<thread>` + xmlEscape(chat.Thread) + `</thread>`
}
@@ -700,8 +888,8 @@ func (c *Client) SendOOB(chat Chat) (n int, err error) {
}
oobtext += `</x>`
}
return fmt.Fprintf(c.conn, "<message to='%s' type='%s' xml:lang='en'>" + oobtext + thdtext + "</message>",
xmlEscape(chat.Remote), xmlEscape(chat.Type))
return fmt.Fprintf(c.conn, "<message to='%s' type='%s' id='%s' xml:lang='en'>"+oobtext+thdtext+"</message>",
xmlEscape(chat.Remote), xmlEscape(chat.Type), cnonce())
}
// SendOrg sends the original text without being wrapped in an XMPP message stanza.
@@ -800,8 +988,8 @@ type bindBind struct {
}
type clientMessageCorrect struct {
XMLName xml.Name `xml:"urn:xmpp:message-correct:0 replace"`
ID string `xml:"id,attr"`
XMLName xml.Name `xml:"urn:xmpp:message-correct:0 replace"`
ID string `xml:"id,attr"`
}
// RFC 3921 B.1 jabber:client
@@ -813,11 +1001,14 @@ type clientMessage struct {
Type string `xml:"type,attr"` // chat, error, groupchat, headline, or normal
// These should technically be []clientText, but string is much more convenient.
Subject string `xml:"subject"`
Body string `xml:"body"`
Thread string `xml:"thread"`
Subject string `xml:"subject"`
Body string `xml:"body"`
Thread string `xml:"thread"`
ReplaceID clientMessageCorrect
// Pubsub
Event clientPubsubEvent `xml:"event"`
// Any hasn't matched element
Other []XMLElement `xml:",any"`
@@ -882,23 +1073,27 @@ type clientPresence struct {
Error *clientError
}
type clientIQ struct { // info/query
XMLName xml.Name `xml:"jabber:client iq"`
From string `xml:"from,attr"`
ID string `xml:"id,attr"`
To string `xml:"to,attr"`
Type string `xml:"type,attr"` // error, get, result, set
Query []byte `xml:",innerxml"`
type clientIQ struct {
// info/query
XMLName xml.Name `xml:"jabber:client iq"`
From string `xml:"from,attr"`
ID string `xml:"id,attr"`
To string `xml:"to,attr"`
Type string `xml:"type,attr"` // error, get, result, set
Query XMLElement `xml:",any"`
Error clientError
Bind bindBind
InnerXML []byte `xml:",innerxml"`
}
type clientError struct {
XMLName xml.Name `xml:"jabber:client error"`
Code string `xml:",attr"`
Type string `xml:",attr"`
Any xml.Name
Text string
XMLName xml.Name `xml:"jabber:client error"`
Code string `xml:",attr"`
Type string `xml:"type,attr"`
Any xml.Name
InnerXML []byte `xml:",innerxml"`
Text string
}
type clientQuery struct {

125
vendor/github.com/matterbridge/go-xmpp/xmpp_avatar.go generated vendored Normal file
View File

@@ -0,0 +1,125 @@
package xmpp
import (
"crypto/sha1"
"encoding/base64"
"encoding/hex"
"encoding/xml"
"errors"
"strconv"
)
const (
XMPPNS_AVATAR_PEP_DATA = "urn:xmpp:avatar:data"
XMPPNS_AVATAR_PEP_METADATA = "urn:xmpp:avatar:metadata"
)
type clientAvatarData struct {
XMLName xml.Name `xml:"data"`
Data []byte `xml:",innerxml"`
}
type clientAvatarInfo struct {
XMLName xml.Name `xml:"info"`
Bytes string `xml:"bytes,attr"`
Width string `xml:"width,attr"`
Height string `xml:"height,attr"`
ID string `xml:"id,attr"`
Type string `xml:"type,attr"`
URL string `xml:"url,attr"`
}
type clientAvatarMetadata struct {
XMLName xml.Name `xml:"metadata"`
XMLNS string `xml:"xmlns,attr"`
Info clientAvatarInfo `xml:"info"`
}
type AvatarData struct {
Data []byte
From string
}
type AvatarMetadata struct {
From string
Bytes int
Width int
Height int
ID string
Type string
URL string
}
func handleAvatarData(itemsBody []byte, from, id string) (AvatarData, error) {
var data clientAvatarData
err := xml.Unmarshal(itemsBody, &data)
if err != nil {
return AvatarData{}, err
}
// Base64-decode the avatar data to check its SHA1 hash
dataRaw, err := base64.StdEncoding.DecodeString(
string(data.Data))
if err != nil {
return AvatarData{}, err
}
hash := sha1.Sum(dataRaw)
hashStr := hex.EncodeToString(hash[:])
if hashStr != id {
return AvatarData{}, errors.New("SHA1 hashes do not match")
}
return AvatarData{
Data: dataRaw,
From: from,
}, nil
}
func handleAvatarMetadata(body []byte, from string) (AvatarMetadata, error) {
var meta clientAvatarMetadata
err := xml.Unmarshal(body, &meta)
if err != nil {
return AvatarMetadata{}, err
}
return AvatarMetadata{
From: from,
Bytes: atoiw(meta.Info.Bytes),
Width: atoiw(meta.Info.Width),
Height: atoiw(meta.Info.Height),
ID: meta.Info.ID,
Type: meta.Info.Type,
URL: meta.Info.URL,
}, nil
}
// A wrapper for atoi which just returns -1 if an error occurs
func atoiw(str string) int {
i, err := strconv.Atoi(str)
if err != nil {
return -1
}
return i
}
func (c *Client) AvatarSubscribeMetadata(jid string) {
c.PubsubSubscribeNode(XMPPNS_AVATAR_PEP_METADATA, jid)
}
func (c *Client) AvatarUnsubscribeMetadata(jid string) {
c.PubsubUnsubscribeNode(XMPPNS_AVATAR_PEP_METADATA, jid)
}
func (c *Client) AvatarRequestData(jid string) {
c.PubsubRequestLastItems(XMPPNS_AVATAR_PEP_DATA, jid)
}
func (c *Client) AvatarRequestDataByID(jid, id string) {
c.PubsubRequestItem(XMPPNS_AVATAR_PEP_DATA, jid, id)
}
func (c *Client) AvatarRequestMetadata(jid string) {
c.PubsubRequestLastItems(XMPPNS_AVATAR_PEP_METADATA, jid)
}

99
vendor/github.com/matterbridge/go-xmpp/xmpp_disco.go generated vendored Normal file
View File

@@ -0,0 +1,99 @@
package xmpp
import (
"encoding/xml"
)
const (
XMPPNS_DISCO_ITEMS = "http://jabber.org/protocol/disco#items"
XMPPNS_DISCO_INFO = "http://jabber.org/protocol/disco#info"
)
type clientDiscoFeature struct {
XMLName xml.Name `xml:"feature"`
Var string `xml:"var,attr"`
}
type clientDiscoIdentity struct {
XMLName xml.Name `xml:"identity"`
Category string `xml:"category,attr"`
Type string `xml:"type,attr"`
Name string `xml:"name,attr"`
}
type clientDiscoQuery struct {
XMLName xml.Name `xml:"query"`
Features []clientDiscoFeature `xml:"feature"`
Identities []clientDiscoIdentity `xml:"identity"`
}
type clientDiscoItem struct {
XMLName xml.Name `xml:"item"`
Jid string `xml:"jid,attr"`
Node string `xml:"node,attr"`
Name string `xml:"name,attr"`
}
type clientDiscoItemsQuery struct {
XMLName xml.Name `xml:"query"`
Items []clientDiscoItem `xml:"item"`
}
type DiscoIdentity struct {
Category string
Type string
Name string
}
type DiscoItem struct {
Jid string
Name string
Node string
}
type DiscoResult struct {
Features []string
Identities []DiscoIdentity
}
type DiscoItems struct {
Jid string
Items []DiscoItem
}
func clientFeaturesToReturn(features []clientDiscoFeature) []string {
var ret []string
for _, feature := range features {
ret = append(ret, feature.Var)
}
return ret
}
func clientIdentitiesToReturn(identities []clientDiscoIdentity) []DiscoIdentity {
var ret []DiscoIdentity
for _, id := range identities {
ret = append(ret, DiscoIdentity{
Category: id.Category,
Type: id.Type,
Name: id.Name,
})
}
return ret
}
func clientDiscoItemsToReturn(items []clientDiscoItem) []DiscoItem {
var ret []DiscoItem
for _, item := range items {
ret = append(ret, DiscoItem{
Jid: item.Jid,
Name: item.Name,
Node: item.Node,
})
}
return ret
}

View File

@@ -10,10 +10,26 @@ const IQTypeSet = "set"
const IQTypeResult = "result"
func (c *Client) Discovery() (string, error) {
const namespace = "http://jabber.org/protocol/disco#items"
// use getCookie for a pseudo random id.
reqID := strconv.FormatUint(uint64(getCookie()), 10)
return c.RawInformationQuery(c.jid, c.domain, reqID, IQTypeGet, namespace, "")
return c.RawInformationQuery(c.jid, c.domain, reqID, IQTypeGet, XMPPNS_DISCO_ITEMS, "")
}
// Discover information about a node
func (c *Client) DiscoverNodeInfo(node string) (string, error) {
query := fmt.Sprintf("<query xmlns='%s' node='%s'/>", XMPPNS_DISCO_INFO, node)
return c.RawInformation(c.jid, c.domain, "info3", IQTypeGet, query)
}
// Discover items that the server exposes
func (c *Client) DiscoverServerItems() (string, error) {
return c.DiscoverEntityItems(c.domain)
}
// Discover items that an entity exposes
func (c *Client) DiscoverEntityItems(jid string) (string, error) {
query := fmt.Sprintf("<query xmlns='%s'/>", XMPPNS_DISCO_ITEMS)
return c.RawInformation(c.jid, jid, "info1", IQTypeGet, query)
}
// RawInformationQuery sends an information query request to the server.

View File

@@ -8,19 +8,19 @@
package xmpp
import (
"errors"
"fmt"
"time"
"errors"
)
const (
nsMUC = "http://jabber.org/protocol/muc"
nsMUCUser = "http://jabber.org/protocol/muc#user"
NoHistory = 0
CharHistory = 1
StanzaHistory = 2
nsMUC = "http://jabber.org/protocol/muc"
nsMUCUser = "http://jabber.org/protocol/muc#user"
NoHistory = 0
CharHistory = 1
StanzaHistory = 2
SecondsHistory = 3
SinceHistory = 4
SinceHistory = 4
)
// Send sends room topic wrapped inside an XMPP message stanza body.
@@ -47,35 +47,35 @@ func (c *Client) JoinMUC(jid, nick string, history_type, history int, history_da
}
switch history_type {
case NoHistory:
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
"<x xmlns='%s' />\n" +
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+
"<x xmlns='%s' />\n"+
"</presence>",
xmlEscape(jid), xmlEscape(nick), nsMUC)
xmlEscape(jid), xmlEscape(nick), nsMUC)
case CharHistory:
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
"<x xmlns='%s'>\n" +
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+
"<x xmlns='%s'>\n"+
"<history maxchars='%d'/></x>\n"+
"</presence>",
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
case StanzaHistory:
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
"<x xmlns='%s'>\n" +
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+
"<x xmlns='%s'>\n"+
"<history maxstanzas='%d'/></x>\n"+
"</presence>",
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
case SecondsHistory:
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
"<x xmlns='%s'>\n" +
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+
"<x xmlns='%s'>\n"+
"<history seconds='%d'/></x>\n"+
"</presence>",
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
case SinceHistory:
if history_date != nil {
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
"<x xmlns='%s'>\n" +
"<history since='%s'/></x>\n" +
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+
"<x xmlns='%s'>\n"+
"<history since='%s'/></x>\n"+
"</presence>",
xmlEscape(jid), xmlEscape(nick), nsMUC, history_date.Format(time.RFC3339))
xmlEscape(jid), xmlEscape(nick), nsMUC, history_date.Format(time.RFC3339))
}
}
return 0, errors.New("Unknown history option")
@@ -88,41 +88,41 @@ func (c *Client) JoinProtectedMUC(jid, nick string, password string, history_typ
}
switch history_type {
case NoHistory:
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
"<x xmlns='%s'>\n" +
"<password>%s</password>" +
"</x>\n" +
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+
"<x xmlns='%s'>\n"+
"<password>%s</password>"+
"</x>\n"+
"</presence>",
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password))
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password))
case CharHistory:
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
"<x xmlns='%s'>\n" +
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+
"<x xmlns='%s'>\n"+
"<password>%s</password>\n"+
"<history maxchars='%d'/></x>\n"+
"</presence>",
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
case StanzaHistory:
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
"<x xmlns='%s'>\n" +
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+
"<x xmlns='%s'>\n"+
"<password>%s</password>\n"+
"<history maxstanzas='%d'/></x>\n"+
"</presence>",
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
case SecondsHistory:
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
"<x xmlns='%s'>\n" +
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+
"<x xmlns='%s'>\n"+
"<password>%s</password>\n"+
"<history seconds='%d'/></x>\n"+
"</presence>",
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
case SinceHistory:
if history_date != nil {
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
"<x xmlns='%s'>\n" +
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+
"<x xmlns='%s'>\n"+
"<password>%s</password>\n"+
"<history since='%s'/></x>\n" +
"<history since='%s'/></x>\n"+
"</presence>",
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history_date.Format(time.RFC3339))
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history_date.Format(time.RFC3339))
}
}
return 0, errors.New("Unknown history option")

133
vendor/github.com/matterbridge/go-xmpp/xmpp_pubsub.go generated vendored Normal file
View File

@@ -0,0 +1,133 @@
package xmpp
import (
"encoding/xml"
"fmt"
)
const (
XMPPNS_PUBSUB = "http://jabber.org/protocol/pubsub"
XMPPNS_PUBSUB_EVENT = "http://jabber.org/protocol/pubsub#event"
)
type clientPubsubItem struct {
XMLName xml.Name `xml:"item"`
ID string `xml:"id,attr"`
Body []byte `xml:",innerxml"`
}
type clientPubsubItems struct {
XMLName xml.Name `xml:"items"`
Node string `xml:"node,attr"`
Items []clientPubsubItem `xml:"item"`
}
type clientPubsub struct {
XMLName xml.Name `xml:"pubsub"`
Items clientPubsubItems `xml:"items"`
}
type clientPubsubEvent struct {
XMLName xml.Name `xml:"event"`
XMLNS string `xml:"xmlns,attr"`
Items clientPubsubItems `xml:"items"`
}
type clientPubsubError struct {
XMLName xml.Name
}
type clientPubsubSubscription struct {
XMLName xml.Name `xml:"subscription"`
Node string `xml:"node,attr"`
JID string `xml:"jid,attr"`
SubID string `xml:"subid,attr"`
}
type PubsubEvent struct {
Node string
Items []PubsubItem
}
type PubsubSubscription struct {
SubID string
JID string
Node string
Errors []string
}
type PubsubUnsubscription PubsubSubscription
type PubsubItem struct {
ID string
InnerXML []byte
}
type PubsubItems struct {
Node string
Items []PubsubItem
}
// Converts []clientPubsubItem to []PubsubItem
func pubsubItemsToReturn(items []clientPubsubItem) []PubsubItem {
var tmp []PubsubItem
for _, i := range items {
tmp = append(tmp, PubsubItem{
ID: i.ID,
InnerXML: i.Body,
})
}
return tmp
}
func pubsubClientToReturn(event clientPubsubEvent) PubsubEvent {
return PubsubEvent{
Node: event.Items.Node,
Items: pubsubItemsToReturn(event.Items.Items),
}
}
func pubsubStanza(body string) string {
return fmt.Sprintf("<pubsub xmlns='%s'>%s</pubsub>",
XMPPNS_PUBSUB, body)
}
func pubsubSubscriptionStanza(node, jid string) string {
body := fmt.Sprintf("<subscribe node='%s' jid='%s'/>",
xmlEscape(node),
xmlEscape(jid))
return pubsubStanza(body)
}
func pubsubUnsubscriptionStanza(node, jid string) string {
body := fmt.Sprintf("<unsubscribe node='%s' jid='%s'/>",
xmlEscape(node),
xmlEscape(jid))
return pubsubStanza(body)
}
func (c *Client) PubsubSubscribeNode(node, jid string) {
c.RawInformation(c.jid,
jid,
"sub1",
"set",
pubsubSubscriptionStanza(node, c.jid))
}
func (c *Client) PubsubUnsubscribeNode(node, jid string) {
c.RawInformation(c.jid,
jid,
"unsub1",
"set",
pubsubUnsubscriptionStanza(node, c.jid))
}
func (c *Client) PubsubRequestLastItems(node, jid string) {
body := fmt.Sprintf("<items node='%s'/>", node)
c.RawInformation(c.jid, jid, "items1", "get", pubsubStanza(body))
}
func (c *Client) PubsubRequestItem(node, jid, id string) {
body := fmt.Sprintf("<items node='%s'><item id='%s'/></items>", node, id)
c.RawInformation(c.jid, jid, "items3", "get", pubsubStanza(body))
}

View File

@@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -25,12 +25,12 @@ lint:
@go vet .
test:
@go test -count=1 -timeout 300s -short .
@go test -v -count=1 -timeout 300s -short ./...
test-race:
@go test -count=1 -timeout 300s -short -race .
@go test -v -count=1 -timeout 300s -short -race ./...
test-integration:
@go test -count=1 -timeout 600s .
@go test -v -count=1 -timeout 600s ./...
pr-prep: fmt lint test-race test-integration

View File

@@ -27,7 +27,7 @@ func (api *Client) SendAuthRevoke(token string) (*AuthRevokeResponse, error) {
return api.SendAuthRevokeContext(context.Background(), token)
}
// SendAuthRevokeContext will retrieve the satus from api.test
// SendAuthRevokeContext will send a revocation request for our token to api.revoke with context
func (api *Client) SendAuthRevokeContext(ctx context.Context, token string) (*AuthRevokeResponse, error) {
if token == "" {
token = api.token

View File

@@ -14,6 +14,7 @@ const (
MBTImage MessageBlockType = "image"
MBTAction MessageBlockType = "actions"
MBTContext MessageBlockType = "context"
MBTFile MessageBlockType = "file"
MBTInput MessageBlockType = "input"
)

View File

@@ -56,20 +56,16 @@ func (b *Blocks) UnmarshalJSON(data []byte) error {
block = &ContextBlock{}
case "divider":
block = &DividerBlock{}
case "file":
block = &FileBlock{}
case "image":
block = &ImageBlock{}
case "input":
block = &InputBlock{}
case "section":
block = &SectionBlock{}
case "rich_text":
// for now ignore the (complex) content of rich_text blocks until we can fully support it
continue
case "file":
// for now ignore the file blocks until we can fully support it
continue
default:
return errors.New("unsupported block type")
block = &UnknownBlock{}
}
err = json.Unmarshal(r, block)
@@ -253,12 +249,36 @@ func (a *Accessory) UnmarshalJSON(data []byte) error {
return err
}
a.DatePickerElement = element.(*DatePickerBlockElement)
case "static_select":
case "plain_text_input":
element, err := unmarshalBlockElement(r, &PlainTextInputBlockElement{})
if err != nil {
return err
}
a.PlainTextInputElement = element.(*PlainTextInputBlockElement)
case "radio_buttons":
element, err := unmarshalBlockElement(r, &RadioButtonsBlockElement{})
if err != nil {
return err
}
a.RadioButtonsElement = element.(*RadioButtonsBlockElement)
case "static_select", "external_select", "users_select", "conversations_select", "channels_select":
element, err := unmarshalBlockElement(r, &SelectBlockElement{})
if err != nil {
return err
}
a.SelectElement = element.(*SelectBlockElement)
case "multi_static_select", "multi_external_select", "multi_users_select", "multi_conversations_select", "multi_channels_select":
element, err := unmarshalBlockElement(r, &MultiSelectBlockElement{})
if err != nil {
return err
}
a.MultiSelectElement = element.(*MultiSelectBlockElement)
default:
element, err := unmarshalBlockElement(r, &UnknownBlockElement{})
if err != nil {
return err
}
a.UnknownElement = element.(*UnknownBlockElement)
}
return nil
@@ -285,9 +305,18 @@ func toBlockElement(element *Accessory) BlockElement {
if element.DatePickerElement != nil {
return element.DatePickerElement
}
if element.PlainTextInputElement != nil {
return element.PlainTextInputElement
}
if element.RadioButtonsElement != nil {
return element.RadioButtonsElement
}
if element.SelectElement != nil {
return element.SelectElement
}
if element.MultiSelectElement != nil {
return element.MultiSelectElement
}
return nil
}

View File

@@ -8,6 +8,7 @@ const (
METOverflow MessageElementType = "overflow"
METDatepicker MessageElementType = "datepicker"
METPlainTextInput MessageElementType = "plain_text_input"
METRadioButtons MessageElementType = "radio_buttons"
MixedElementImage MixedElementType = "mixed_image"
MixedElementText MixedElementType = "mixed_text"
@@ -17,6 +18,12 @@ const (
OptTypeUser string = "users_select"
OptTypeConversations string = "conversations_select"
OptTypeChannels string = "channels_select"
MultiOptTypeStatic string = "multi_static_select"
MultiOptTypeExternal string = "multi_external_select"
MultiOptTypeUser string = "multi_users_select"
MultiOptTypeConversations string = "multi_conversations_select"
MultiOptTypeChannels string = "multi_channels_select"
)
type MessageElementType string
@@ -32,11 +39,15 @@ type MixedElement interface {
}
type Accessory struct {
ImageElement *ImageBlockElement
ButtonElement *ButtonBlockElement
OverflowElement *OverflowBlockElement
DatePickerElement *DatePickerBlockElement
SelectElement *SelectBlockElement
ImageElement *ImageBlockElement
ButtonElement *ButtonBlockElement
OverflowElement *OverflowBlockElement
DatePickerElement *DatePickerBlockElement
PlainTextInputElement *PlainTextInputBlockElement
RadioButtonsElement *RadioButtonsBlockElement
SelectElement *SelectBlockElement
MultiSelectElement *MultiSelectBlockElement
UnknownElement *UnknownBlockElement
}
// NewAccessory returns a new Accessory for a given block element
@@ -50,11 +61,17 @@ func NewAccessory(element BlockElement) *Accessory {
return &Accessory{OverflowElement: element.(*OverflowBlockElement)}
case *DatePickerBlockElement:
return &Accessory{DatePickerElement: element.(*DatePickerBlockElement)}
case *PlainTextInputBlockElement:
return &Accessory{PlainTextInputElement: element.(*PlainTextInputBlockElement)}
case *RadioButtonsBlockElement:
return &Accessory{RadioButtonsElement: element.(*RadioButtonsBlockElement)}
case *SelectBlockElement:
return &Accessory{SelectElement: element.(*SelectBlockElement)}
case *MultiSelectBlockElement:
return &Accessory{MultiSelectElement: element.(*MultiSelectBlockElement)}
default:
return &Accessory{UnknownElement: element.(*UnknownBlockElement)}
}
return nil
}
// BlockElements is a convenience struct defined to allow dynamic unmarshalling of
@@ -63,6 +80,20 @@ type BlockElements struct {
ElementSet []BlockElement `json:"elements,omitempty"`
}
// UnknownBlockElement any block element that this library does not directly support.
// See the "Rich Elements" section at the following URL:
// https://api.slack.com/changelog/2019-09-what-they-see-is-what-you-get-and-more-and-less
// New block element types may be introduced by Slack at any time; this is a catch-all for any such block elements.
type UnknownBlockElement struct {
Type MessageElementType `json:"type"`
Elements BlockElements
}
// ElementType returns the type of the Element
func (s UnknownBlockElement) ElementType() MessageElementType {
return s.Type
}
// ImageBlockElement An element to insert an image - this element can be used
// in section and context blocks only. If you want a block with only an image
// in it, you're looking for the image block.
@@ -135,6 +166,20 @@ func NewButtonBlockElement(actionID, value string, text *TextBlockObject) *Butto
}
}
// OptionsResponse defines the response used for select block typahead.
//
// More Information: https://api.slack.com/reference/block-kit/block-elements#external_multi_select
type OptionsResponse struct {
Options []*OptionBlockObject `json:"options,omitempty"`
}
// OptionGroupsResponse defines the response used for select block typahead.
//
// More Information: https://api.slack.com/reference/block-kit/block-elements#external_multi_select
type OptionGroupsResponse struct {
OptionGroups []*OptionGroupBlockObject `json:"option_groups,omitempty"`
}
// SelectBlockElement defines the simplest form of select menu, with a static list
// of options passed in when defining the element.
//
@@ -149,7 +194,7 @@ type SelectBlockElement struct {
InitialUser string `json:"initial_user,omitempty"`
InitialConversation string `json:"initial_conversation,omitempty"`
InitialChannel string `json:"initial_channel,omitempty"`
MinQueryLength int `json:"min_query_length,omitempty"`
MinQueryLength *int `json:"min_query_length,omitempty"`
Confirm *ConfirmationBlockObject `json:"confirm,omitempty"`
}
@@ -185,6 +230,56 @@ func NewOptionsGroupSelectBlockElement(
}
}
// MultiSelectBlockElement defines a multiselect menu, with a static list
// of options passed in when defining the element.
//
// More Information: https://api.slack.com/reference/messaging/block-elements#multi_select
type MultiSelectBlockElement struct {
Type string `json:"type,omitempty"`
Placeholder *TextBlockObject `json:"placeholder,omitempty"`
ActionID string `json:"action_id,omitempty"`
Options []*OptionBlockObject `json:"options,omitempty"`
OptionGroups []*OptionGroupBlockObject `json:"option_groups,omitempty"`
InitialOptions []*OptionBlockObject `json:"initial_options,omitempty"`
InitialUsers []string `json:"initial_users,omitempty"`
InitialConversations []string `json:"initial_conversations,omitempty"`
InitialChannels []string `json:"initial_channels,omitempty"`
MinQueryLength *int `json:"min_query_length,omitempty"`
Confirm *ConfirmationBlockObject `json:"confirm,omitempty"`
}
// ElementType returns the type of the Element
func (s MultiSelectBlockElement) ElementType() MessageElementType {
return MessageElementType(s.Type)
}
// NewOptionsMultiSelectBlockElement returns a new instance of SelectBlockElement for use with
// the Options object only.
func NewOptionsMultiSelectBlockElement(optType string, placeholder *TextBlockObject, actionID string, options ...*OptionBlockObject) *MultiSelectBlockElement {
return &MultiSelectBlockElement{
Type: optType,
Placeholder: placeholder,
ActionID: actionID,
Options: options,
}
}
// NewOptionsGroupMultiSelectBlockElement returns a new instance of MultiSelectBlockElement for use with
// the Options object only.
func NewOptionsGroupMultiSelectBlockElement(
optType string,
placeholder *TextBlockObject,
actionID string,
optGroups ...*OptionGroupBlockObject,
) *MultiSelectBlockElement {
return &MultiSelectBlockElement{
Type: optType,
Placeholder: placeholder,
ActionID: actionID,
OptionGroups: optGroups,
}
}
// OverflowBlockElement defines the fields needed to use an overflow element.
// And Overflow Element is like a cross between a button and a select menu -
// when a user clicks on this overflow button, they will be presented with a
@@ -238,10 +333,11 @@ func NewDatePickerBlockElement(actionID string) *DatePickerBlockElement {
}
}
// PlainTextInputBlockElement creates a field where a user can enter freeform data.
// PlainTextInputBlockElement creates a field where a user can enter freeform
// data.
// Plain-text input elements are currently only available in modals.
//
// More Information: https://api.slack.com/reference/messaging/block-elements#input
// More Information: https://api.slack.com/reference/block-kit/block-elements#input
type PlainTextInputBlockElement struct {
Type MessageElementType `json:"type"`
ActionID string `json:"action_id"`
@@ -257,7 +353,8 @@ func (s PlainTextInputBlockElement) ElementType() MessageElementType {
return s.Type
}
// NewPlainTextInputBlockElement returns an instance of a plain-text input element
// NewPlainTextInputBlockElement returns an instance of a plain-text input
// element
func NewPlainTextInputBlockElement(placeholder *TextBlockObject, actionID string) *PlainTextInputBlockElement {
return &PlainTextInputBlockElement{
Type: METPlainTextInput,
@@ -265,3 +362,29 @@ func NewPlainTextInputBlockElement(placeholder *TextBlockObject, actionID string
Placeholder: placeholder,
}
}
// RadioButtonsBlockElement defines an element which lets users choose one item
// from a list of possible options.
//
// More Information: https://api.slack.com/reference/block-kit/block-elements#radio
type RadioButtonsBlockElement struct {
Type MessageElementType `json:"type"`
ActionID string `json:"action_id"`
Options []*OptionBlockObject `json:"options"`
InitialOption *OptionBlockObject `json:"initial_option,omitempty"`
Confirm *ConfirmationBlockObject `json:"confirm,omitempty"`
}
// ElementType returns the type of the Element
func (s RadioButtonsBlockElement) ElementType() MessageElementType {
return s.Type
}
// NewRadioButtonsBlockElement returns an instance of a radio buttons element.
func NewRadioButtonsBlockElement(actionID string, options ...*OptionBlockObject) *RadioButtonsBlockElement {
return &RadioButtonsBlockElement{
Type: METRadioButtons,
ActionID: actionID,
Options: options,
}
}

26
vendor/github.com/slack-go/slack/block_file.go generated vendored Normal file
View File

@@ -0,0 +1,26 @@
package slack
// FileBlock defines data that is used to display a remote file.
//
// More Information: https://api.slack.com/reference/block-kit/blocks#file
type FileBlock struct {
Type MessageBlockType `json:"type"`
BlockID string `json:"block_id,omitempty"`
ExternalID string `json:"external_id"`
Source string `json:"source"`
}
// BlockType returns the type of the block
func (s FileBlock) BlockType() MessageBlockType {
return s.Type
}
// NewFileBlock returns a new instance of a file block
func NewFileBlock(blockID string, externalID string, source string) *FileBlock {
return &FileBlock{
Type: MBTFile,
BlockID: blockID,
ExternalID: externalID,
Source: source,
}
}

View File

@@ -1,10 +1,8 @@
package slack
// InputBlock defines data that is used to collect information from users -
// it can hold a plain-text input element, a select menu element,
// a multi-select menu element, or a datepicker.
// InputBlock defines data that is used to display user input fields.
//
// More Information: https://api.slack.com/reference/messaging/blocks#input
// More Information: https://api.slack.com/reference/block-kit/blocks#input
type InputBlock struct {
Type MessageBlockType `json:"type"`
BlockID string `json:"block_id,omitempty"`
@@ -19,7 +17,7 @@ func (s InputBlock) BlockType() MessageBlockType {
return s.Type
}
// NewInputBlock returns a new instance of an Input Block
// NewInputBlock returns a new instance of an input block
func NewInputBlock(blockID string, label *TextBlockObject, element BlockElement) *InputBlock {
return &InputBlock{
Type: MBTInput,

View File

@@ -145,6 +145,14 @@ func NewTextBlockObject(elementType, text string, emoji, verbatim bool) *TextBlo
}
}
// BlockType returns the type of the block
func (t TextBlockObject) BlockType() MessageBlockType {
if t.Type == "mrkdown" {
return MarkdownType
}
return PlainTextType
}
// ConfirmationBlockObject defines a dialog that provides a confirmation step to
// any interactive element. This dialog will ask the user to confirm their action by
// offering a confirm and deny buttons.

13
vendor/github.com/slack-go/slack/block_unknown.go generated vendored Normal file
View File

@@ -0,0 +1,13 @@
package slack
// UnknownBlock represents a block type that is not yet known. This block type exists to prevent Slack from introducing
// new and unknown block types that break this library.
type UnknownBlock struct {
Type MessageBlockType `json:"type"`
BlockID string `json:"block_id,omitempty"`
}
// BlockType returns the type of the block
func (b UnknownBlock) BlockType() MessageBlockType {
return b.Type
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"net/url"
"strconv"
"time"
)
type channelResponseFull struct {
@@ -14,6 +15,7 @@ type channelResponseFull struct {
NotInChannel bool `json:"not_in_channel"`
History
SlackResponse
Metadata ResponseMetadata `json:"response_metadata"`
}
// Channel contains information about the channel
@@ -35,25 +37,21 @@ func (api *Client) channelRequest(ctx context.Context, path string, values url.V
return response, response.Err()
}
type channelsConfig struct {
values url.Values
}
// GetChannelsOption option provided when getting channels.
type GetChannelsOption func(*channelsConfig) error
type GetChannelsOption func(*ChannelPagination) error
// GetChannelsOptionExcludeMembers excludes the members collection from each channel.
func GetChannelsOptionExcludeMembers() GetChannelsOption {
return func(config *channelsConfig) error {
config.values.Add("exclude_members", "true")
return func(p *ChannelPagination) error {
p.excludeMembers = true
return nil
}
}
// GetChannelsOptionExcludeArchived excludes archived channels from results.
func GetChannelsOptionExcludeArchived() GetChannelsOption {
return func(config *channelsConfig) error {
config.values.Add("exclude_archived", "true")
return func(p *ChannelPagination) error {
p.excludeArchived = true
return nil
}
}
@@ -266,6 +264,78 @@ func (api *Client) KickUserFromChannelContext(ctx context.Context, channelID, us
return err
}
func newChannelPagination(c *Client, options ...GetChannelsOption) (cp ChannelPagination) {
cp = ChannelPagination{
c: c,
limit: 200, // per slack api documentation.
}
for _, opt := range options {
opt(&cp)
}
return cp
}
// ChannelPagination allows for paginating over the channels
type ChannelPagination struct {
Channels []Channel
limit int
excludeArchived bool
excludeMembers bool
previousResp *ResponseMetadata
c *Client
}
// Done checks if the pagination has completed
func (ChannelPagination) Done(err error) bool {
return err == errPaginationComplete
}
// Failure checks if pagination failed.
func (t ChannelPagination) Failure(err error) error {
if t.Done(err) {
return nil
}
return err
}
func (t ChannelPagination) Next(ctx context.Context) (_ ChannelPagination, err error) {
var (
resp *channelResponseFull
)
if t.c == nil || (t.previousResp != nil && t.previousResp.Cursor == "") {
return t, errPaginationComplete
}
t.previousResp = t.previousResp.initialize()
values := url.Values{
"limit": {strconv.Itoa(t.limit)},
"exclude_archived": {strconv.FormatBool(t.excludeArchived)},
"exclude_members": {strconv.FormatBool(t.excludeMembers)},
"token": {t.c.token},
"cursor": {t.previousResp.Cursor},
}
if resp, err = t.c.channelRequest(ctx, "channels.list", values); err != nil {
return t, err
}
t.c.Debugf("GetChannelsContext: got %d channels; metadata %v", len(resp.Channels), resp.Metadata)
t.Channels = resp.Channels
t.previousResp = &resp.Metadata
return t, nil
}
// GetChannelsPaginated fetches channels in a paginated fashion, see GetChannelsContext for usage.
func (api *Client) GetChannelsPaginated(options ...GetChannelsOption) ChannelPagination {
return newChannelPagination(api, options...)
}
// GetChannels retrieves all the channels
// see https://api.slack.com/methods/channels.list
func (api *Client) GetChannels(excludeArchived bool, options ...GetChannelsOption) ([]Channel, error) {
@@ -274,28 +344,27 @@ func (api *Client) GetChannels(excludeArchived bool, options ...GetChannelsOptio
// GetChannelsContext retrieves all the channels with a custom context
// see https://api.slack.com/methods/channels.list
func (api *Client) GetChannelsContext(ctx context.Context, excludeArchived bool, options ...GetChannelsOption) ([]Channel, error) {
config := channelsConfig{
values: url.Values{
"token": {api.token},
},
}
func (api *Client) GetChannelsContext(ctx context.Context, excludeArchived bool, options ...GetChannelsOption) (results []Channel, err error) {
if excludeArchived {
options = append(options, GetChannelsOptionExcludeArchived())
}
for _, opt := range options {
if err := opt(&config); err != nil {
return nil, err
p := api.GetChannelsPaginated(options...)
for err == nil {
p, err = p.Next(ctx)
if err == nil {
results = append(results, p.Channels...)
} else if rateLimitedError, ok := err.(*RateLimitedError); ok {
select {
case <-ctx.Done():
err = ctx.Err()
case <-time.After(rateLimitedError.RetryAfter):
err = nil
}
}
}
response, err := api.channelRequest(ctx, "channels.list", config.values)
if err != nil {
return nil, err
}
return response.Channels, nil
return results, p.Failure(err)
}
// SetChannelReadMark sets the read mark of a given channel to a specific point

View File

@@ -5,6 +5,7 @@ import (
"encoding/json"
"net/http"
"net/url"
"strconv"
"github.com/slack-go/slack/slackutilsx"
)
@@ -25,10 +26,11 @@ const (
)
type chatResponseFull struct {
Channel string `json:"channel"`
Timestamp string `json:"ts"` //Regular message timestamp
MessageTimeStamp string `json:"message_ts"` //Ephemeral message timestamp
Text string `json:"text"`
Channel string `json:"channel"`
Timestamp string `json:"ts"` //Regular message timestamp
MessageTimeStamp string `json:"message_ts"` //Ephemeral message timestamp
ScheduledMessageID string `json:"scheduled_message_id,omitempty"` //Scheduled message id
Text string `json:"text"`
SlackResponse
}
@@ -82,13 +84,34 @@ func NewPostMessageParameters() PostMessageParameters {
// DeleteMessage deletes a message in a channel
func (api *Client) DeleteMessage(channel, messageTimestamp string) (string, string, error) {
respChannel, respTimestamp, _, err := api.SendMessageContext(context.Background(), channel, MsgOptionDelete(messageTimestamp))
respChannel, respTimestamp, _, err := api.SendMessageContext(
context.Background(),
channel,
MsgOptionDelete(messageTimestamp),
)
return respChannel, respTimestamp, err
}
// DeleteMessageContext deletes a message in a channel with a custom context
func (api *Client) DeleteMessageContext(ctx context.Context, channel, messageTimestamp string) (string, string, error) {
respChannel, respTimestamp, _, err := api.SendMessageContext(ctx, channel, MsgOptionDelete(messageTimestamp))
respChannel, respTimestamp, _, err := api.SendMessageContext(
ctx,
channel,
MsgOptionDelete(messageTimestamp),
)
return respChannel, respTimestamp, err
}
// ScheduleMessage sends a message to a channel.
// Message is escaped by default according to https://api.slack.com/docs/formatting
// Use http://davestevens.github.io/slack-message-builder/ to help crafting your message.
func (api *Client) ScheduleMessage(channelID, postAt string, options ...MsgOption) (string, string, error) {
respChannel, respTimestamp, _, err := api.SendMessageContext(
context.Background(),
channelID,
MsgOptionSchedule(postAt),
MsgOptionCompose(options...),
)
return respChannel, respTimestamp, err
}
@@ -132,18 +155,33 @@ func (api *Client) PostEphemeral(channelID, userID string, options ...MsgOption)
// PostEphemeralContext sends an ephemeal message to a user in a channel with a custom context
// For more details, see PostEphemeral documentation
func (api *Client) PostEphemeralContext(ctx context.Context, channelID, userID string, options ...MsgOption) (timestamp string, err error) {
_, timestamp, _, err = api.SendMessageContext(ctx, channelID, MsgOptionPostEphemeral(userID), MsgOptionCompose(options...))
_, timestamp, _, err = api.SendMessageContext(
ctx,
channelID,
MsgOptionPostEphemeral(userID),
MsgOptionCompose(options...),
)
return timestamp, err
}
// UpdateMessage updates a message in a channel
func (api *Client) UpdateMessage(channelID, timestamp string, options ...MsgOption) (string, string, string, error) {
return api.SendMessageContext(context.Background(), channelID, MsgOptionUpdate(timestamp), MsgOptionCompose(options...))
return api.SendMessageContext(
context.Background(),
channelID,
MsgOptionUpdate(timestamp),
MsgOptionCompose(options...),
)
}
// UpdateMessageContext updates a message in a channel
func (api *Client) UpdateMessageContext(ctx context.Context, channelID, timestamp string, options ...MsgOption) (string, string, string, error) {
return api.SendMessageContext(ctx, channelID, MsgOptionUpdate(timestamp), MsgOptionCompose(options...))
return api.SendMessageContext(
ctx,
channelID,
MsgOptionUpdate(timestamp),
MsgOptionCompose(options...),
)
}
// UnfurlMessage unfurls a message in a channel
@@ -212,13 +250,14 @@ func buildSender(apiurl string, options ...MsgOption) sendConfig {
type sendMode string
const (
chatUpdate sendMode = "chat.update"
chatPostMessage sendMode = "chat.postMessage"
chatDelete sendMode = "chat.delete"
chatPostEphemeral sendMode = "chat.postEphemeral"
chatResponse sendMode = "chat.responseURL"
chatMeMessage sendMode = "chat.meMessage"
chatUnfurl sendMode = "chat.unfurl"
chatUpdate sendMode = "chat.update"
chatPostMessage sendMode = "chat.postMessage"
chatScheduleMessage sendMode = "chat.scheduleMessage"
chatDelete sendMode = "chat.delete"
chatPostEphemeral sendMode = "chat.postEphemeral"
chatResponse sendMode = "chat.responseURL"
chatMeMessage sendMode = "chat.meMessage"
chatUnfurl sendMode = "chat.unfurl"
)
type sendConfig struct {
@@ -287,6 +326,15 @@ func (t responseURLSender) BuildRequest() (*http.Request, func(*chatResponseFull
// MsgOption option provided when sending a message.
type MsgOption func(*sendConfig) error
// MsgOptionSchedule schedules a messages.
func MsgOptionSchedule(postAt string) MsgOption {
return func(config *sendConfig) error {
config.endpoint = config.apiurl + string(chatScheduleMessage)
config.values.Add("post_at", postAt)
return nil
}
}
// MsgOptionPost posts a messages, this is the default.
func MsgOptionPost() MsgOption {
return func(config *sendConfig) error {
@@ -625,3 +673,81 @@ func (api *Client) GetPermalinkContext(ctx context.Context, params *PermalinkPar
}
return response.Permalink, response.Err()
}
type GetScheduledMessagesParameters struct {
Channel string
Cursor string
Latest string
Limit int
Oldest string
}
// GetScheduledMessages returns the list of scheduled messages based on params
func (api *Client) GetScheduledMessages(params *GetScheduledMessagesParameters) (channels []Message, nextCursor string, err error) {
return api.GetScheduledMessagesContext(context.Background(), params)
}
// GetScheduledMessagesContext returns the list of scheduled messages in a Slack team with a custom context
func (api *Client) GetScheduledMessagesContext(ctx context.Context, params *GetScheduledMessagesParameters) (channels []Message, nextCursor string, err error) {
values := url.Values{
"token": {api.token},
}
if params.Channel != "" {
values.Add("channel", params.Channel)
}
if params.Cursor != "" {
values.Add("cursor", params.Cursor)
}
if params.Limit != 0 {
values.Add("limit", strconv.Itoa(params.Limit))
}
if params.Latest != "" {
values.Add("latest", params.Latest)
}
if params.Oldest != "" {
values.Add("oldest", params.Oldest)
}
response := struct {
Messages []Message `json:"scheduled_messages"`
ResponseMetaData responseMetaData `json:"response_metadata"`
SlackResponse
}{}
err = api.postMethod(ctx, "chat.scheduledMessages.list", values, &response)
if err != nil {
return nil, "", err
}
return response.Messages, response.ResponseMetaData.NextCursor, response.Err()
}
type DeleteScheduledMessageParameters struct {
Channel string
ScheduledMessageID string
AsUser bool
}
// DeleteScheduledMessage returns the list of scheduled messages based on params
func (api *Client) DeleteScheduledMessage(params *DeleteScheduledMessageParameters) (bool, error) {
return api.DeleteScheduledMessageContext(context.Background(), params)
}
// DeleteScheduledMessageContext returns the list of scheduled messages in a Slack team with a custom context
func (api *Client) DeleteScheduledMessageContext(ctx context.Context, params *DeleteScheduledMessageParameters) (bool, error) {
values := url.Values{
"token": {api.token},
"channel": {params.Channel},
"scheduled_message_id": {params.ScheduledMessageID},
"as_user": {strconv.FormatBool(params.AsUser)},
}
response := struct {
SlackResponse
}{}
err := api.postMethod(ctx, "chat.deleteScheduledMessage", values, &response)
if err != nil {
return false, err
}
return response.Ok, response.Err()
}

View File

@@ -1,12 +1,11 @@
module github.com/slack-go/slack
go 1.13
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-test/deep v1.0.4
github.com/gorilla/websocket v1.2.0
github.com/nlopes/slack v0.6.0
github.com/pkg/errors v0.8.0
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.2.2
)
go 1.13

View File

@@ -4,6 +4,8 @@ github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ=
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/nlopes/slack v0.6.0 h1:jt0jxVQGhssx1Ib7naAOZEZcGdtIhTzkP0nopK0AsRA=
github.com/nlopes/slack v0.6.0/go.mod h1:JzQ9m3PMAqcpeCam7UaHSuBuupz7CmpjehYMayT6YOk=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

View File

@@ -2,108 +2,381 @@ package slack
import (
"bytes"
"context"
"fmt"
"net/url"
"strconv"
"strings"
"time"
)
// UserPrefs needs to be implemented
type UserPrefsCarrier struct {
SlackResponse
UserPrefs *UserPrefs `json:"prefs"`
}
// UserPrefs carries a bunch of user settings including some unknown types
type UserPrefs struct {
// "highlight_words":"",
// "user_colors":"",
// "color_names_in_list":true,
// "growls_enabled":true,
// "tz":"Europe\/London",
// "push_dm_alert":true,
// "push_mention_alert":true,
// "push_everything":true,
// "push_idle_wait":2,
// "push_sound":"b2.mp3",
// "push_loud_channels":"",
// "push_mention_channels":"",
// "push_loud_channels_set":"",
// "email_alerts":"instant",
// "email_alerts_sleep_until":0,
// "email_misc":false,
// "email_weekly":true,
// "welcome_message_hidden":false,
// "all_channels_loud":true,
// "loud_channels":"",
// "never_channels":"",
// "loud_channels_set":"",
// "show_member_presence":true,
// "search_sort":"timestamp",
// "expand_inline_imgs":true,
// "expand_internal_inline_imgs":true,
// "expand_snippets":false,
// "posts_formatting_guide":true,
// "seen_welcome_2":true,
// "seen_ssb_prompt":false,
// "search_only_my_channels":false,
// "emoji_mode":"default",
// "has_invited":true,
// "has_uploaded":false,
// "has_created_channel":true,
// "search_exclude_channels":"",
// "messages_theme":"default",
// "webapp_spellcheck":true,
// "no_joined_overlays":false,
// "no_created_overlays":true,
// "dropbox_enabled":false,
// "seen_user_menu_tip_card":true,
// "seen_team_menu_tip_card":true,
// "seen_channel_menu_tip_card":true,
// "seen_message_input_tip_card":true,
// "seen_channels_tip_card":true,
// "seen_domain_invite_reminder":false,
// "seen_member_invite_reminder":false,
// "seen_flexpane_tip_card":true,
// "seen_search_input_tip_card":true,
// "mute_sounds":false,
// "arrow_history":false,
// "tab_ui_return_selects":true,
// "obey_inline_img_limit":true,
// "new_msg_snd":"knock_brush.mp3",
// "collapsible":false,
// "collapsible_by_click":true,
// "require_at":false,
// "mac_ssb_bounce":"",
// "mac_ssb_bullet":true,
// "win_ssb_bullet":true,
// "expand_non_media_attachments":true,
// "show_typing":true,
// "pagekeys_handled":true,
// "last_snippet_type":"",
// "display_real_names_override":0,
// "time24":false,
// "enter_is_special_in_tbt":false,
// "graphic_emoticons":false,
// "convert_emoticons":true,
// "autoplay_chat_sounds":true,
// "ss_emojis":true,
// "sidebar_behavior":"",
// "mark_msgs_read_immediately":true,
// "start_scroll_at_oldest":true,
// "snippet_editor_wrap_long_lines":false,
// "ls_disabled":false,
// "sidebar_theme":"default",
// "sidebar_theme_custom_values":"",
// "f_key_search":false,
// "k_key_omnibox":true,
// "speak_growls":false,
// "mac_speak_voice":"com.apple.speech.synthesis.voice.Alex",
// "mac_speak_speed":250,
// "comma_key_prefs":false,
// "at_channel_suppressed_channels":"",
// "push_at_channel_suppressed_channels":"",
// "prompted_for_email_disabling":false,
// "full_text_extracts":false,
// "no_text_in_notifications":false,
// "muted_channels":"",
// "no_macssb1_banner":false,
// "privacy_policy_seen":true,
// "search_exclude_bots":false,
// "fuzzy_matching":false
UserColors string `json:"user_colors,omitempty"`
ColorNamesInList bool `json:"color_names_in_list,omitempty"`
// Keyboard UnknownType `json:"keyboard"`
EmailAlerts string `json:"email_alerts,omitempty"`
EmailAlertsSleepUntil int `json:"email_alerts_sleep_until,omitempty"`
EmailTips bool `json:"email_tips,omitempty"`
EmailWeekly bool `json:"email_weekly,omitempty"`
EmailOffers bool `json:"email_offers,omitempty"`
EmailResearch bool `json:"email_research,omitempty"`
EmailDeveloper bool `json:"email_developer,omitempty"`
WelcomeMessageHidden bool `json:"welcome_message_hidden,omitempty"`
SearchSort string `json:"search_sort,omitempty"`
SearchFileSort string `json:"search_file_sort,omitempty"`
SearchChannelSort string `json:"search_channel_sort,omitempty"`
SearchPeopleSort string `json:"search_people_sort,omitempty"`
ExpandInlineImages bool `json:"expand_inline_images,omitempty"`
ExpandInternalInlineImages bool `json:"expand_internal_inline_images,omitempty"`
ExpandSnippets bool `json:"expand_snippets,omitempty"`
PostsFormattingGuide bool `json:"posts_formatting_guide,omitempty"`
SeenWelcome2 bool `json:"seen_welcome_2,omitempty"`
SeenSSBPrompt bool `json:"seen_ssb_prompt,omitempty"`
SpacesNewXpBannerDismissed bool `json:"spaces_new_xp_banner_dismissed,omitempty"`
SearchOnlyMyChannels bool `json:"search_only_my_channels,omitempty"`
SearchOnlyCurrentTeam bool `json:"search_only_current_team,omitempty"`
SearchHideMyChannels bool `json:"search_hide_my_channels,omitempty"`
SearchOnlyShowOnline bool `json:"search_only_show_online,omitempty"`
SearchHideDeactivatedUsers bool `json:"search_hide_deactivated_users,omitempty"`
EmojiMode string `json:"emoji_mode,omitempty"`
EmojiUse string `json:"emoji_use,omitempty"`
HasInvited bool `json:"has_invited,omitempty"`
HasUploaded bool `json:"has_uploaded,omitempty"`
HasCreatedChannel bool `json:"has_created_channel,omitempty"`
HasSearched bool `json:"has_searched,omitempty"`
SearchExcludeChannels string `json:"search_exclude_channels,omitempty"`
MessagesTheme string `json:"messages_theme,omitempty"`
WebappSpellcheck bool `json:"webapp_spellcheck,omitempty"`
NoJoinedOverlays bool `json:"no_joined_overlays,omitempty"`
NoCreatedOverlays bool `json:"no_created_overlays,omitempty"`
DropboxEnabled bool `json:"dropbox_enabled,omitempty"`
SeenDomainInviteReminder bool `json:"seen_domain_invite_reminder,omitempty"`
SeenMemberInviteReminder bool `json:"seen_member_invite_reminder,omitempty"`
MuteSounds bool `json:"mute_sounds,omitempty"`
ArrowHistory bool `json:"arrow_history,omitempty"`
TabUIReturnSelects bool `json:"tab_ui_return_selects,omitempty"`
ObeyInlineImgLimit bool `json:"obey_inline_img_limit,omitempty"`
RequireAt bool `json:"require_at,omitempty"`
SsbSpaceWindow string `json:"ssb_space_window,omitempty"`
MacSsbBounce string `json:"mac_ssb_bounce,omitempty"`
MacSsbBullet bool `json:"mac_ssb_bullet,omitempty"`
ExpandNonMediaAttachments bool `json:"expand_non_media_attachments,omitempty"`
ShowTyping bool `json:"show_typing,omitempty"`
PagekeysHandled bool `json:"pagekeys_handled,omitempty"`
LastSnippetType string `json:"last_snippet_type,omitempty"`
DisplayRealNamesOverride int `json:"display_real_names_override,omitempty"`
DisplayDisplayNames bool `json:"display_display_names,omitempty"`
Time24 bool `json:"time24,omitempty"`
EnterIsSpecialInTbt bool `json:"enter_is_special_in_tbt,omitempty"`
MsgInputSendBtn bool `json:"msg_input_send_btn,omitempty"`
MsgInputSendBtnAutoSet bool `json:"msg_input_send_btn_auto_set,omitempty"`
MsgInputStickyComposer bool `json:"msg_input_sticky_composer,omitempty"`
GraphicEmoticons bool `json:"graphic_emoticons,omitempty"`
ConvertEmoticons bool `json:"convert_emoticons,omitempty"`
SsEmojis bool `json:"ss_emojis,omitempty"`
SeenOnboardingStart bool `json:"seen_onboarding_start,omitempty"`
OnboardingCancelled bool `json:"onboarding_cancelled,omitempty"`
SeenOnboardingSlackbotConversation bool `json:"seen_onboarding_slackbot_conversation,omitempty"`
SeenOnboardingChannels bool `json:"seen_onboarding_channels,omitempty"`
SeenOnboardingDirectMessages bool `json:"seen_onboarding_direct_messages,omitempty"`
SeenOnboardingInvites bool `json:"seen_onboarding_invites,omitempty"`
SeenOnboardingSearch bool `json:"seen_onboarding_search,omitempty"`
SeenOnboardingRecentMentions bool `json:"seen_onboarding_recent_mentions,omitempty"`
SeenOnboardingStarredItems bool `json:"seen_onboarding_starred_items,omitempty"`
SeenOnboardingPrivateGroups bool `json:"seen_onboarding_private_groups,omitempty"`
SeenOnboardingBanner bool `json:"seen_onboarding_banner,omitempty"`
OnboardingSlackbotConversationStep int `json:"onboarding_slackbot_conversation_step,omitempty"`
SetTzAutomatically bool `json:"set_tz_automatically,omitempty"`
SuppressLinkWarning bool `json:"suppress_link_warning,omitempty"`
DndEnabled bool `json:"dnd_enabled,omitempty"`
DndStartHour string `json:"dnd_start_hour,omitempty"`
DndEndHour string `json:"dnd_end_hour,omitempty"`
DndBeforeMonday string `json:"dnd_before_monday,omitempty"`
DndAfterMonday string `json:"dnd_after_monday,omitempty"`
DndEnabledMonday string `json:"dnd_enabled_monday,omitempty"`
DndBeforeTuesday string `json:"dnd_before_tuesday,omitempty"`
DndAfterTuesday string `json:"dnd_after_tuesday,omitempty"`
DndEnabledTuesday string `json:"dnd_enabled_tuesday,omitempty"`
DndBeforeWednesday string `json:"dnd_before_wednesday,omitempty"`
DndAfterWednesday string `json:"dnd_after_wednesday,omitempty"`
DndEnabledWednesday string `json:"dnd_enabled_wednesday,omitempty"`
DndBeforeThursday string `json:"dnd_before_thursday,omitempty"`
DndAfterThursday string `json:"dnd_after_thursday,omitempty"`
DndEnabledThursday string `json:"dnd_enabled_thursday,omitempty"`
DndBeforeFriday string `json:"dnd_before_friday,omitempty"`
DndAfterFriday string `json:"dnd_after_friday,omitempty"`
DndEnabledFriday string `json:"dnd_enabled_friday,omitempty"`
DndBeforeSaturday string `json:"dnd_before_saturday,omitempty"`
DndAfterSaturday string `json:"dnd_after_saturday,omitempty"`
DndEnabledSaturday string `json:"dnd_enabled_saturday,omitempty"`
DndBeforeSunday string `json:"dnd_before_sunday,omitempty"`
DndAfterSunday string `json:"dnd_after_sunday,omitempty"`
DndEnabledSunday string `json:"dnd_enabled_sunday,omitempty"`
DndDays string `json:"dnd_days,omitempty"`
DndCustomNewBadgeSeen bool `json:"dnd_custom_new_badge_seen,omitempty"`
DndNotificationScheduleNewBadgeSeen bool `json:"dnd_notification_schedule_new_badge_seen,omitempty"`
// UnreadCollapsedChannels unknownType `json:"unread_collapsed_channels,omitempty"`
SidebarBehavior string `json:"sidebar_behavior,omitempty"`
ChannelSort string `json:"channel_sort,omitempty"`
SeparatePrivateChannels bool `json:"separate_private_channels,omitempty"`
SeparateSharedChannels bool `json:"separate_shared_channels,omitempty"`
SidebarTheme string `json:"sidebar_theme,omitempty"`
SidebarThemeCustomValues string `json:"sidebar_theme_custom_values,omitempty"`
NoInvitesWidgetInSidebar bool `json:"no_invites_widget_in_sidebar,omitempty"`
NoOmniboxInChannels bool `json:"no_omnibox_in_channels,omitempty"`
KKeyOmniboxAutoHideCount int `json:"k_key_omnibox_auto_hide_count,omitempty"`
ShowSidebarQuickswitcherButton bool `json:"show_sidebar_quickswitcher_button,omitempty"`
EntOrgWideChannelsSidebar bool `json:"ent_org_wide_channels_sidebar,omitempty"`
MarkMsgsReadImmediately bool `json:"mark_msgs_read_immediately,omitempty"`
StartScrollAtOldest bool `json:"start_scroll_at_oldest,omitempty"`
SnippetEditorWrapLongLines bool `json:"snippet_editor_wrap_long_lines,omitempty"`
LsDisabled bool `json:"ls_disabled,omitempty"`
FKeySearch bool `json:"f_key_search,omitempty"`
KKeyOmnibox bool `json:"k_key_omnibox,omitempty"`
PromptedForEmailDisabling bool `json:"prompted_for_email_disabling,omitempty"`
NoMacelectronBanner bool `json:"no_macelectron_banner,omitempty"`
NoMacssb1Banner bool `json:"no_macssb1_banner,omitempty"`
NoMacssb2Banner bool `json:"no_macssb2_banner,omitempty"`
NoWinssb1Banner bool `json:"no_winssb1_banner,omitempty"`
HideUserGroupInfoPane bool `json:"hide_user_group_info_pane,omitempty"`
MentionsExcludeAtUserGroups bool `json:"mentions_exclude_at_user_groups,omitempty"`
MentionsExcludeReactions bool `json:"mentions_exclude_reactions,omitempty"`
PrivacyPolicySeen bool `json:"privacy_policy_seen,omitempty"`
EnterpriseMigrationSeen bool `json:"enterprise_migration_seen,omitempty"`
LastTosAcknowledged string `json:"last_tos_acknowledged,omitempty"`
SearchExcludeBots bool `json:"search_exclude_bots,omitempty"`
LoadLato2 bool `json:"load_lato_2,omitempty"`
FullerTimestamps bool `json:"fuller_timestamps,omitempty"`
LastSeenAtChannelWarning int `json:"last_seen_at_channel_warning,omitempty"`
EmojiAutocompleteBig bool `json:"emoji_autocomplete_big,omitempty"`
TwoFactorAuthEnabled bool `json:"two_factor_auth_enabled,omitempty"`
// TwoFactorType unknownType `json:"two_factor_type,omitempty"`
// TwoFactorBackupType unknownType `json:"two_factor_backup_type,omitempty"`
HideHexSwatch bool `json:"hide_hex_swatch,omitempty"`
ShowJumperScores bool `json:"show_jumper_scores,omitempty"`
EnterpriseMdmCustomMsg string `json:"enterprise_mdm_custom_msg,omitempty"`
// EnterpriseExcludedAppTeams unknownType `json:"enterprise_excluded_app_teams,omitempty"`
ClientLogsPri string `json:"client_logs_pri,omitempty"`
FlannelServerPool string `json:"flannel_server_pool,omitempty"`
MentionsExcludeAtChannels bool `json:"mentions_exclude_at_channels,omitempty"`
ConfirmClearAllUnreads bool `json:"confirm_clear_all_unreads,omitempty"`
ConfirmUserMarkedAway bool `json:"confirm_user_marked_away,omitempty"`
BoxEnabled bool `json:"box_enabled,omitempty"`
SeenSingleEmojiMsg bool `json:"seen_single_emoji_msg,omitempty"`
ConfirmShCallStart bool `json:"confirm_sh_call_start,omitempty"`
PreferredSkinTone string `json:"preferred_skin_tone,omitempty"`
ShowAllSkinTones bool `json:"show_all_skin_tones,omitempty"`
WhatsNewRead int `json:"whats_new_read,omitempty"`
// FrecencyJumper unknownType `json:"frecency_jumper,omitempty"`
FrecencyEntJumper string `json:"frecency_ent_jumper,omitempty"`
FrecencyEntJumperBackup string `json:"frecency_ent_jumper_backup,omitempty"`
Jumbomoji bool `json:"jumbomoji,omitempty"`
NewxpSeenLastMessage int `json:"newxp_seen_last_message,omitempty"`
ShowMemoryInstrument bool `json:"show_memory_instrument,omitempty"`
EnableUnreadView bool `json:"enable_unread_view,omitempty"`
SeenUnreadViewCoachmark bool `json:"seen_unread_view_coachmark,omitempty"`
EnableReactEmojiPicker bool `json:"enable_react_emoji_picker,omitempty"`
SeenCustomStatusBadge bool `json:"seen_custom_status_badge,omitempty"`
SeenCustomStatusCallout bool `json:"seen_custom_status_callout,omitempty"`
SeenCustomStatusExpirationBadge bool `json:"seen_custom_status_expiration_badge,omitempty"`
UsedCustomStatusKbShortcut bool `json:"used_custom_status_kb_shortcut,omitempty"`
SeenGuestAdminSlackbotAnnouncement bool `json:"seen_guest_admin_slackbot_announcement,omitempty"`
SeenThreadsNotificationBanner bool `json:"seen_threads_notification_banner,omitempty"`
SeenNameTaggingCoachmark bool `json:"seen_name_tagging_coachmark,omitempty"`
AllUnreadsSortOrder string `json:"all_unreads_sort_order,omitempty"`
Locale string `json:"locale,omitempty"`
SeenIntlChannelNamesCoachmark bool `json:"seen_intl_channel_names_coachmark,omitempty"`
SeenP2LocaleChangeMessage int `json:"seen_p2_locale_change_message,omitempty"`
SeenLocaleChangeMessage int `json:"seen_locale_change_message,omitempty"`
SeenJapaneseLocaleChangeMessage bool `json:"seen_japanese_locale_change_message,omitempty"`
SeenSharedChannelsCoachmark bool `json:"seen_shared_channels_coachmark,omitempty"`
SeenSharedChannelsOptInChangeMessage bool `json:"seen_shared_channels_opt_in_change_message,omitempty"`
HasRecentlySharedaChannel bool `json:"has_recently_shared_a_channel,omitempty"`
SeenChannelBrowserAdminCoachmark bool `json:"seen_channel_browser_admin_coachmark,omitempty"`
SeenAdministrationMenu bool `json:"seen_administration_menu,omitempty"`
SeenDraftsSectionCoachmark bool `json:"seen_drafts_section_coachmark,omitempty"`
SeenEmojiUpdateOverlayCoachmark bool `json:"seen_emoji_update_overlay_coachmark,omitempty"`
SeenSonicDeluxeToast int `json:"seen_sonic_deluxe_toast,omitempty"`
SeenWysiwygDeluxeToast bool `json:"seen_wysiwyg_deluxe_toast,omitempty"`
SeenMarkdownPasteToast int `json:"seen_markdown_paste_toast,omitempty"`
SeenMarkdownPasteShortcut int `json:"seen_markdown_paste_shortcut,omitempty"`
SeenIaEducation bool `json:"seen_ia_education,omitempty"`
PlainTextMode bool `json:"plain_text_mode,omitempty"`
ShowSharedChannelsEducationBanner bool `json:"show_shared_channels_education_banner,omitempty"`
AllowCallsToSetCurrentStatus bool `json:"allow_calls_to_set_current_status,omitempty"`
InInteractiveMasMigrationFlow bool `json:"in_interactive_mas_migration_flow,omitempty"`
SunsetInteractiveMessageViews int `json:"sunset_interactive_message_views,omitempty"`
ShdepPromoCodeSubmitted bool `json:"shdep_promo_code_submitted,omitempty"`
SeenShdepSlackbotMessage bool `json:"seen_shdep_slackbot_message,omitempty"`
SeenCallsInteractiveCoachmark bool `json:"seen_calls_interactive_coachmark,omitempty"`
AllowCmdTabIss bool `json:"allow_cmd_tab_iss,omitempty"`
SeenWorkflowBuilderDeluxeToast bool `json:"seen_workflow_builder_deluxe_toast,omitempty"`
WorkflowBuilderIntroModalClickedThrough bool `json:"workflow_builder_intro_modal_clicked_through,omitempty"`
// WorkflowBuilderCoachmarks unknownType `json:"workflow_builder_coachmarks,omitempty"`
SeenGdriveCoachmark bool `json:"seen_gdrive_coachmark,omitempty"`
OverloadedMessageEnabled bool `json:"overloaded_message_enabled,omitempty"`
SeenHighlightsCoachmark bool `json:"seen_highlights_coachmark,omitempty"`
SeenHighlightsArrowsCoachmark bool `json:"seen_highlights_arrows_coachmark,omitempty"`
SeenHighlightsWarmWelcome bool `json:"seen_highlights_warm_welcome,omitempty"`
SeenNewSearchUi bool `json:"seen_new_search_ui,omitempty"`
SeenChannelSearch bool `json:"seen_channel_search,omitempty"`
SeenPeopleSearch bool `json:"seen_people_search,omitempty"`
SeenPeopleSearchCount int `json:"seen_people_search_count,omitempty"`
DismissedScrollSearchTooltipCount int `json:"dismissed_scroll_search_tooltip_count,omitempty"`
LastDismissedScrollSearchTooltipTimestamp int `json:"last_dismissed_scroll_search_tooltip_timestamp,omitempty"`
HasUsedQuickswitcherShortcut bool `json:"has_used_quickswitcher_shortcut,omitempty"`
SeenQuickswitcherShortcutTipCount int `json:"seen_quickswitcher_shortcut_tip_count,omitempty"`
BrowsersDismissedChannelsLowResultsEducation bool `json:"browsers_dismissed_channels_low_results_education,omitempty"`
BrowsersSeenInitialChannelsEducation bool `json:"browsers_seen_initial_channels_education,omitempty"`
BrowsersDismissedPeopleLowResultsEducation bool `json:"browsers_dismissed_people_low_results_education,omitempty"`
BrowsersSeenInitialPeopleEducation bool `json:"browsers_seen_initial_people_education,omitempty"`
BrowsersDismissedUserGroupsLowResultsEducation bool `json:"browsers_dismissed_user_groups_low_results_education,omitempty"`
BrowsersSeenInitialUserGroupsEducation bool `json:"browsers_seen_initial_user_groups_education,omitempty"`
BrowsersDismissedFilesLowResultsEducation bool `json:"browsers_dismissed_files_low_results_education,omitempty"`
BrowsersSeenInitialFilesEducation bool `json:"browsers_seen_initial_files_education,omitempty"`
A11yAnimations bool `json:"a11y_animations,omitempty"`
SeenKeyboardShortcutsCoachmark bool `json:"seen_keyboard_shortcuts_coachmark,omitempty"`
NeedsInitialPasswordSet bool `json:"needs_initial_password_set,omitempty"`
LessonsEnabled bool `json:"lessons_enabled,omitempty"`
TractorEnabled bool `json:"tractor_enabled,omitempty"`
TractorExperimentGroup string `json:"tractor_experiment_group,omitempty"`
OpenedSlackbotDm bool `json:"opened_slackbot_dm,omitempty"`
NewxpSuggestedChannels string `json:"newxp_suggested_channels,omitempty"`
OnboardingComplete bool `json:"onboarding_complete,omitempty"`
WelcomePlaceState string `json:"welcome_place_state,omitempty"`
// OnboardingRoleApps unknownType `json:"onboarding_role_apps,omitempty"`
HasReceivedThreadedMessage bool `json:"has_received_threaded_message,omitempty"`
SendYourFirstMessageBannerEnabled bool `json:"send_your_first_message_banner_enabled,omitempty"`
WhocanseethisDmMpdmBadge bool `json:"whocanseethis_dm_mpdm_badge,omitempty"`
HighlightWords string `json:"highlight_words,omitempty"`
ThreadsEverything bool `json:"threads_everything,omitempty"`
NoTextInNotifications bool `json:"no_text_in_notifications,omitempty"`
PushShowPreview bool `json:"push_show_preview,omitempty"`
GrowlsEnabled bool `json:"growls_enabled,omitempty"`
AllChannelsLoud bool `json:"all_channels_loud,omitempty"`
PushDmAlert bool `json:"push_dm_alert,omitempty"`
PushMentionAlert bool `json:"push_mention_alert,omitempty"`
PushEverything bool `json:"push_everything,omitempty"`
PushIdleWait int `json:"push_idle_wait,omitempty"`
PushSound string `json:"push_sound,omitempty"`
NewMsgSnd string `json:"new_msg_snd,omitempty"`
PushLoudChannels string `json:"push_loud_channels,omitempty"`
PushMentionChannels string `json:"push_mention_channels,omitempty"`
PushLoudChannelsSet string `json:"push_loud_channels_set,omitempty"`
LoudChannels string `json:"loud_channels,omitempty"`
NeverChannels string `json:"never_channels,omitempty"`
LoudChannelsSet string `json:"loud_channels_set,omitempty"`
AtChannelSuppressedChannels string `json:"at_channel_suppressed_channels,omitempty"`
PushAtChannelSuppressedChannels string `json:"push_at_channel_suppressed_channels,omitempty"`
MutedChannels string `json:"muted_channels,omitempty"`
// AllNotificationsPrefs unknownType `json:"all_notifications_prefs,omitempty"`
GrowthMsgLimitApproachingCtaCount int `json:"growth_msg_limit_approaching_cta_count,omitempty"`
GrowthMsgLimitApproachingCtaTs int `json:"growth_msg_limit_approaching_cta_ts,omitempty"`
GrowthMsgLimitReachedCtaCount int `json:"growth_msg_limit_reached_cta_count,omitempty"`
GrowthMsgLimitReachedCtaLastTs int `json:"growth_msg_limit_reached_cta_last_ts,omitempty"`
GrowthMsgLimitLongReachedCtaCount int `json:"growth_msg_limit_long_reached_cta_count,omitempty"`
GrowthMsgLimitLongReachedCtaLastTs int `json:"growth_msg_limit_long_reached_cta_last_ts,omitempty"`
GrowthMsgLimitSixtyDayBannerCtaCount int `json:"growth_msg_limit_sixty_day_banner_cta_count,omitempty"`
GrowthMsgLimitSixtyDayBannerCtaLastTs int `json:"growth_msg_limit_sixty_day_banner_cta_last_ts,omitempty"`
// GrowthAllBannersPrefs unknownType `json:"growth_all_banners_prefs,omitempty"`
AnalyticsUpsellCoachmarkSeen bool `json:"analytics_upsell_coachmark_seen,omitempty"`
SeenAppSpaceCoachmark bool `json:"seen_app_space_coachmark,omitempty"`
SeenAppSpaceTutorial bool `json:"seen_app_space_tutorial,omitempty"`
DismissedAppLauncherWelcome bool `json:"dismissed_app_launcher_welcome,omitempty"`
DismissedAppLauncherLimit bool `json:"dismissed_app_launcher_limit,omitempty"`
Purchaser bool `json:"purchaser,omitempty"`
ShowEntOnboarding bool `json:"show_ent_onboarding,omitempty"`
FoldersEnabled bool `json:"folders_enabled,omitempty"`
// FolderData unknownType `json:"folder_data,omitempty"`
SeenCorporateExportAlert bool `json:"seen_corporate_export_alert,omitempty"`
ShowAutocompleteHelp int `json:"show_autocomplete_help,omitempty"`
DeprecationToastLastSeen int `json:"deprecation_toast_last_seen,omitempty"`
DeprecationModalLastSeen int `json:"deprecation_modal_last_seen,omitempty"`
Iap1Lab int `json:"iap1_lab,omitempty"`
IaTopNavTheme string `json:"ia_top_nav_theme,omitempty"`
IaPlatformActionsLab int `json:"ia_platform_actions_lab,omitempty"`
ActivityView string `json:"activity_view,omitempty"`
FailoverProxyCheckCompleted int `json:"failover_proxy_check_completed,omitempty"`
EdgeUploadProxyCheckCompleted int `json:"edge_upload_proxy_check_completed,omitempty"`
AppSubdomainCheckCompleted int `json:"app_subdomain_check_completed,omitempty"`
AddAppsPromptDismissed bool `json:"add_apps_prompt_dismissed,omitempty"`
AddChannelPromptDismissed bool `json:"add_channel_prompt_dismissed,omitempty"`
ChannelSidebarHideInvite bool `json:"channel_sidebar_hide_invite,omitempty"`
InProdSurveysEnabled bool `json:"in_prod_surveys_enabled,omitempty"`
DismissedInstalledAppDmSuggestions string `json:"dismissed_installed_app_dm_suggestions,omitempty"`
SeenContextualMessageShortcutsModal bool `json:"seen_contextual_message_shortcuts_modal,omitempty"`
SeenMessageNavigationEducationalToast bool `json:"seen_message_navigation_educational_toast,omitempty"`
ContextualMessageShortcutsModalWasSeen bool `json:"contextual_message_shortcuts_modal_was_seen,omitempty"`
MessageNavigationToastWasSeen bool `json:"message_navigation_toast_was_seen,omitempty"`
UpToBrowseKbShortcut bool `json:"up_to_browse_kb_shortcut,omitempty"`
ChannelSections string `json:"channel_sections,omitempty"`
TZ string `json:"tz,omitempty"`
}
func (api *Client) GetUserPrefs() (*UserPrefsCarrier, error) {
values := url.Values{"token": {api.token}}
response := UserPrefsCarrier{}
err := api.getMethod(context.Background(), "users.prefs.get", values, &response)
if err != nil {
return nil, err
}
return &response, response.Err()
}
func (api *Client) MuteChat(channelID string) (*UserPrefsCarrier, error) {
prefs, err := api.GetUserPrefs()
if err != nil {
return nil, err
}
chnls := strings.Split(prefs.UserPrefs.MutedChannels, ",")
for _, chn := range chnls {
if chn == channelID {
return nil, nil // noop
}
}
newChnls := prefs.UserPrefs.MutedChannels + "," + channelID
values := url.Values{"token": {api.token}, "muted_channels": {newChnls}, "reason": {"update-muted-channels"}}
response := UserPrefsCarrier{}
err = api.postMethod(context.Background(), "users.prefs.set", values, &response)
if err != nil {
return nil, err
}
return &response, response.Err()
}
func (api *Client) UnMuteChat(channelID string) (*UserPrefsCarrier, error) {
prefs, err := api.GetUserPrefs()
if err != nil {
return nil, err
}
chnls := strings.Split(prefs.UserPrefs.MutedChannels, ",")
newChnls := make([]string, len(chnls)-1)
for i, chn := range chnls {
if chn == channelID {
return nil, nil // noop
}
newChnls[i] = chn
}
values := url.Values{"token": {api.token}, "muted_channels": {strings.Join(newChnls, ",")}, "reason": {"update-muted-channels"}}
response := UserPrefsCarrier{}
err = api.postMethod(context.Background(), "users.prefs.set", values, &response)
if err != nil {
return nil, err
}
return &response, response.Err()
}
// UserDetails contains user details coming in the initial response from StartRTM

View File

@@ -24,6 +24,9 @@ const (
InteractionTypeInteractionMessage = InteractionType("interactive_message")
InteractionTypeMessageAction = InteractionType("message_action")
InteractionTypeBlockActions = InteractionType("block_actions")
InteractionTypeBlockSuggestion = InteractionType("block_suggestion")
InteractionTypeViewSubmission = InteractionType("view_submission")
InteractionTypeViewClosed = InteractionType("view_closed")
)
// InteractionCallback is sent from slack when a user interactions with a button or dialog.
@@ -44,8 +47,19 @@ type InteractionCallback struct {
MessageTs string `json:"message_ts"`
AttachmentID string `json:"attachment_id"`
ActionCallback ActionCallbacks `json:"actions"`
View View `json:"view"`
ActionID string `json:"action_id"`
APIAppID string `json:"api_app_id"`
BlockID string `json:"block_id"`
Container Container `json:"container"`
DialogSubmissionCallback
ViewSubmissionCallback
ViewClosedCallback
}
type Container struct {
Type string `json:"type"`
ViewID string `json:"view_id"`
}
// ActionCallback is a convenience struct defined to allow dynamic unmarshalling of

View File

@@ -31,6 +31,40 @@ type OAuthResponse struct {
SlackResponse
}
// OAuthV2Response ...
type OAuthV2Response struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
Scope string `json:"scope"`
BotUserID string `json:"bot_user_id"`
AppID string `json:"app_id"`
TeamID string `json:"team_id"`
Team OAuthV2ResponseTeam `json:"team"`
Enterprise OAuthV2ResponseEnterprise `json:"enterprise"`
AuthedUser OAuthV2ResponseAuthedUser `json:"authed_user"`
SlackResponse
}
// OAuthV2ResponseTeam ...
type OAuthV2ResponseTeam struct {
ID string `json:"id"`
Name string `json:"name"`
}
// OAuthV2ResponseEnterprise ...
type OAuthV2ResponseEnterprise struct {
ID string `json:"id"`
Name string `json:"name"`
}
// OAuthV2ResponseAuthedUser ...
type OAuthV2ResponseAuthedUser struct {
ID string `json:"id"`
Scope string `json:"scope"`
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
}
// GetOAuthToken retrieves an AccessToken
func GetOAuthToken(client httpClient, clientID, clientSecret, code, redirectURI string) (accessToken string, scope string, err error) {
return GetOAuthTokenContext(context.Background(), client, clientID, clientSecret, code, redirectURI)
@@ -62,3 +96,23 @@ func GetOAuthResponseContext(ctx context.Context, client httpClient, clientID, c
}
return response, response.Err()
}
// GetOAuthV2Response gets a V2 OAuth access token response - https://api.slack.com/methods/oauth.v2.access
func GetOAuthV2Response(client httpClient, clientID, clientSecret, code, redirectURI string) (resp *OAuthV2Response, err error) {
return GetOAuthV2ResponseContext(context.Background(), client, clientID, clientSecret, code, redirectURI)
}
// GetOAuthV2ResponseContext with a context, gets a V2 OAuth access token response
func GetOAuthV2ResponseContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string) (resp *OAuthV2Response, err error) {
values := url.Values{
"client_id": {clientID},
"client_secret": {clientSecret},
"code": {code},
"redirect_uri": {redirectURI},
}
response := &OAuthV2Response{}
if err = postForm(ctx, client, APIURL+"oauth.v2.access", values, response, discard{}); err != nil {
return nil, err
}
return response, response.Err()
}

View File

@@ -12,6 +12,10 @@ const (
type ViewType string
type ViewState struct {
Values map[string]map[string]BlockAction `json:"values"`
}
type View struct {
SlackResponse
ID string `json:"id"`
@@ -23,7 +27,7 @@ type View struct {
Blocks Blocks `json:"blocks"`
PrivateMetadata string `json:"private_metadata"`
CallbackID string `json:"callback_id"`
State interface{} `json:"state"`
State *ViewState `json:"state"`
Hash string `json:"hash"`
ClearOnClose bool `json:"clear_on_close"`
NotifyOnClose bool `json:"notify_on_close"`
@@ -34,17 +38,67 @@ type View struct {
BotID string `json:"bot_id"`
}
type ViewSubmissionCallback struct {
Hash string `json:"hash"`
}
type ViewClosedCallback struct {
IsCleared bool `json:"is_cleared"`
}
const (
RAClear ViewResponseAction = "clear"
RAUpdate ViewResponseAction = "update"
RAPush ViewResponseAction = "push"
RAErrors ViewResponseAction = "errors"
)
type ViewResponseAction string
type ViewSubmissionResponse struct {
ResponseAction ViewResponseAction `json:"response_action"`
View *ModalViewRequest `json:"view,omitempty"`
Errors map[string]string `json:"errors,omitempty"`
}
func NewClearViewSubmissionResponse() *ViewSubmissionResponse {
return &ViewSubmissionResponse{
ResponseAction: RAClear,
}
}
func NewUpdateViewSubmissionResponse(view *ModalViewRequest) *ViewSubmissionResponse {
return &ViewSubmissionResponse{
ResponseAction: RAUpdate,
View: view,
}
}
func NewPushViewSubmissionResponse(view *ModalViewRequest) *ViewSubmissionResponse {
return &ViewSubmissionResponse{
ResponseAction: RAPush,
View: view,
}
}
func NewErrorsViewSubmissionResponse(errors map[string]string) *ViewSubmissionResponse {
return &ViewSubmissionResponse{
ResponseAction: RAErrors,
Errors: errors,
}
}
type ModalViewRequest struct {
Type ViewType `json:"type"`
Title *TextBlockObject `json:"title"`
Blocks Blocks `json:"blocks"`
Close *TextBlockObject `json:"close"`
Submit *TextBlockObject `json:"submit"`
PrivateMetadata string `json:"private_metadata"`
CallbackID string `json:"callback_id"`
ClearOnClose bool `json:"clear_on_close"`
NotifyOnClose bool `json:"notify_on_close"`
ExternalID string `json:"external_id"`
Close *TextBlockObject `json:"close,omitempty"`
Submit *TextBlockObject `json:"submit,omitempty"`
PrivateMetadata string `json:"private_metadata,omitempty"`
CallbackID string `json:"callback_id,omitempty"`
ClearOnClose bool `json:"clear_on_close,omitempty"`
NotifyOnClose bool `json:"notify_on_close,omitempty"`
ExternalID string `json:"external_id,omitempty"`
}
func (v *ModalViewRequest) ViewType() ViewType {
@@ -54,9 +108,9 @@ func (v *ModalViewRequest) ViewType() ViewType {
type HomeTabViewRequest struct {
Type ViewType `json:"type"`
Blocks Blocks `json:"blocks"`
PrivateMetadata string `json:"private_metadata"`
CallbackID string `json:"callback_id"`
ExternalID string `json:"external_id"`
PrivateMetadata string `json:"private_metadata,omitempty"`
CallbackID string `json:"callback_id,omitempty"`
ExternalID string `json:"external_id,omitempty"`
}
func (v *HomeTabViewRequest) ViewType() ViewType {
@@ -71,7 +125,7 @@ type openViewRequest struct {
type publishViewRequest struct {
UserID string `json:"user_id"`
View HomeTabViewRequest `json:"view"`
Hash string `json:"hash"`
Hash string `json:"hash,omitempty"`
}
type pushViewRequest struct {
@@ -81,9 +135,9 @@ type pushViewRequest struct {
type updateViewRequest struct {
View ModalViewRequest `json:"view"`
ExternalID string `json:"external_id"`
Hash string `json:"hash"`
ViewID string `json:"view_id"`
ExternalID string `json:"external_id,omitempty"`
Hash string `json:"hash,omitempty"`
ViewID string `json:"view_id,omitempty"`
}
type ViewResponse struct {

View File

@@ -45,7 +45,7 @@ type ChannelRenameEvent struct {
type ChannelRenameInfo struct {
ID string `json:"id"`
Name string `json:"name"`
Created string `json:"created"`
Created int `json:"created"`
}
// ChannelHistoryChangedEvent represents the Channel history changed event

View File

@@ -577,5 +577,6 @@ var EventMapping = map[string]interface{}{
"subteam_self_removed": SubteamSelfRemovedEvent{},
"subteam_updated": SubteamUpdatedEvent{},
"desktop_notification": DesktopNotificationEvent{},
"desktop_notification": DesktopNotificationEvent{},
"mobile_in_app_notification": MobileInAppNotificationEvent{},
}

View File

@@ -0,0 +1,20 @@
package slack
// MobileInAppNotificationEvent represents the update event for Mobile App Notification.
type MobileInAppNotificationEvent struct {
Type string `json:"type"`
Title string `json:"title"`
Subtitle string `json:"subtitle"`
Timestamp string `json:"ts"`
Channel string `json:"channel"`
AvatarImage string `json:"avatarImage"`
IsShared bool `json:"is_shared"`
ChannelName string `json:"channel_name"`
AuthorID string `json:"author_id"`
AuthorDisplayName string `json:"author_display_name"`
MessageText string `json:"msg_text"`
PushID string `json:"push_id"`
NotifcationID string `json:"notif_id"`
MobileLaunchURI string `json:"mobileLaunchUri"`
EventTimestamp string `json:"event_ts"`
}

13
vendor/modules.txt vendored
View File

@@ -15,7 +15,7 @@ github.com/Philipp15b/go-steam/protocol/steamlang
github.com/Philipp15b/go-steam/rwu
github.com/Philipp15b/go-steam/socialcache
github.com/Philipp15b/go-steam/steamid
# github.com/Rhymen/go-whatsapp v0.1.0
# github.com/Rhymen/go-whatsapp v0.1.1-0.20200421062035-31e8111ac334
github.com/Rhymen/go-whatsapp
github.com/Rhymen/go-whatsapp/binary
github.com/Rhymen/go-whatsapp/binary/proto
@@ -23,7 +23,7 @@ github.com/Rhymen/go-whatsapp/binary/token
github.com/Rhymen/go-whatsapp/crypto/cbc
github.com/Rhymen/go-whatsapp/crypto/curve25519
github.com/Rhymen/go-whatsapp/crypto/hkdf
# github.com/d5/tengo/v2 v2.0.2
# github.com/d5/tengo/v2 v2.1.2
github.com/d5/tengo/v2
github.com/d5/tengo/v2/parser
github.com/d5/tengo/v2/stdlib
@@ -95,7 +95,7 @@ github.com/labstack/gommon/random
github.com/lrstanley/girc
# github.com/magiconair/properties v1.8.1
github.com/magiconair/properties
# github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20190210153444-cc9d05784d5d
# github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20200411204219-d5c18ce75048
github.com/matterbridge/Rocket.Chat.Go.SDK/models
github.com/matterbridge/Rocket.Chat.Go.SDK/realtime
github.com/matterbridge/Rocket.Chat.Go.SDK/rest
@@ -103,7 +103,7 @@ github.com/matterbridge/Rocket.Chat.Go.SDK/rest
github.com/matterbridge/discordgo
# github.com/matterbridge/emoji v2.1.1-0.20191117213217-af507f6b02db+incompatible
github.com/matterbridge/emoji
# github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91
# github.com/matterbridge/go-xmpp v0.0.0-20200418225040-c8a3a57b4050
github.com/matterbridge/go-xmpp
# github.com/matterbridge/gomatrix v0.0.0-20200209224845-c2104d7936a6
github.com/matterbridge/gomatrix
@@ -111,8 +111,6 @@ github.com/matterbridge/gomatrix
github.com/matterbridge/gozulipbot
# github.com/matterbridge/logrus-prefixed-formatter v0.0.0-20180806162718-01618749af61
github.com/matterbridge/logrus-prefixed-formatter
# github.com/matterbridge/msgraph.go v0.0.0-20200308150230-9e043fe9dbaa
github.com/matterbridge/msgraph.go/msauth
# github.com/mattermost/mattermost-server v5.5.0+incompatible
github.com/mattermost/mattermost-server/mlog
github.com/mattermost/mattermost-server/model
@@ -174,7 +172,7 @@ github.com/sirupsen/logrus
github.com/skip2/go-qrcode
github.com/skip2/go-qrcode/bitset
github.com/skip2/go-qrcode/reedsolomon
# github.com/slack-go/slack v0.6.3-0.20200228121756-f56d616d5901
# github.com/slack-go/slack v0.6.3
github.com/slack-go/slack
github.com/slack-go/slack/internal/errorsx
github.com/slack-go/slack/internal/timex
@@ -205,6 +203,7 @@ github.com/valyala/fasttemplate
# github.com/yaegashi/msgraph.go v0.1.2
github.com/yaegashi/msgraph.go/beta
github.com/yaegashi/msgraph.go/jsonx
github.com/yaegashi/msgraph.go/msauth
# github.com/zfjagann/golang-ring v0.0.0-20190106091943-a88bb6aef447
github.com/zfjagann/golang-ring
# go.uber.org/atomic v1.4.0