Compare commits

..

59 Commits

Author SHA1 Message Date
Wim
cca1ea2404 Release v1.17.0 (#1050) 2020-03-21 21:33:16 +01:00
Wim
281016a501 Fix duplicate separator on empty description/url (discord). Fixes #1008 (#1035)
Make this work for all possible cases.
Add tests
2020-03-21 21:27:17 +01:00
Qais Patankar
d4acdf2f89 Use blocks not attachments (slack) (#1048)
This removes the extra space below messages, as shown in
https://user-images.githubusercontent.com/923242/77235190-a3359980-6bab-11ea-8b7b-697d730ae5c1.png
2020-03-21 21:03:12 +01:00
Qais Patankar
0951e75c85 Fix #1039: messages sent to Slack being synced back (#1046)
This is a regression from https://github.com/42wim/matterbridge/pull/581#issuecomment-562937576

Behaves the same as 95190f11bf
2020-03-21 20:12:30 +01:00
Jakub
6b017b226a Support JSON and YAML config formats (#1045)
Signed-off-by: Jakub Sokołowski <jakub@status.im>
2020-03-18 23:20:29 +01:00
Qais Patankar
9e3bd7398c Fix #1027: warning when handling inbound webhooks (discord) (#1044) 2020-03-18 23:12:48 +01:00
Qais Patankar
79f764c7a8 Refactor webhook permission checks 2020-03-18 23:10:47 +01:00
Qais Patankar
b5dc4353fb Fix #1040: spotty webhook permission verification 2020-03-18 23:10:47 +01:00
Qais Patankar
2fbac73c29 Ignore ConnectingEvent (slack) (#1041) 2020-03-18 23:03:20 +01:00
burner1024
6616d105d1 Add markdownv2 mode for telegram documentation, see #1032 (#1037) 2020-03-18 22:43:11 +01:00
Wim
6b4b19194e Update vendor shazow/ssh-chat (#1029) 2020-03-08 23:55:09 +01:00
Wim
9785edd263 Remove replace directives and use own fork to make go get work again (#1028)
See https://github.com/golang/go/issues/30354
go get doesn't honor the go.mod replace options.
2020-03-08 17:08:18 +01:00
Qais Patankar
2a0bc11b68 Make some discord matterbridge.toml.sample lines less verbose 2020-03-08 14:30:54 +01:00
Qais Patankar
dd0325a88d Remove trailing newlines from matterbridge.toml.sample 2020-03-08 14:30:54 +01:00
Qais Patankar
20783c0978 Refactor matterbridge.toml.sample discord section 2020-03-08 14:30:54 +01:00
Wim
3f06a40bd5 Support code snippets from msteams 2020-03-01 22:19:33 +01:00
Wim
68f43985ad Add scopes again 2020-03-01 22:19:33 +01:00
Wim
915ca8f817 Make linter happy and cleanup (msteams) 2020-03-01 22:19:33 +01:00
Wim
a65a81610b Support threading from other bridges to msteams 2020-03-01 22:19:33 +01:00
Wim
8eb6ed5639 Support receiving attachments from msteams 2020-03-01 22:19:33 +01:00
Wim
795a8705c3 Add initial Microsoft Teams support
Documentation on https://github.com/42wim/matterbridge/wiki/MS-Teams-setup
2020-03-01 22:19:33 +01:00
Wim
3af0dc3b3a Vendor libraries needed for msteams support 2020-03-01 22:19:33 +01:00
Wim
9cf9b958a3 Do not lint gomnd (#1021) 2020-03-01 22:05:50 +01:00
Wim
3ac2ba8d5a Update to go1.14 and golangci-lint 1.23.7 (#1020) 2020-03-01 21:50:21 +01:00
Wim
d893421c7b Update vendor keybase/go-keybase-chat-bot (#1019) 2020-03-01 21:09:23 +01:00
Wim
250b3bb579 Use upstream slack-go/slack again (#1018) 2020-03-01 20:59:19 +01:00
Alexander Pushkov
e9edbfc051 Make Keybase link point to the team directly (#1013) 2020-02-19 21:02:44 +01:00
Wim
e343db6f72 Make avatars download work with mediaserverdownload (telegram). Fixes #920 (#1012) 2020-02-15 18:31:40 +01:00
Qais Patankar
4d57d66f85 Fix typo in feature_request.md (#1009) 2020-02-10 22:35:11 +01:00
Wim
54ed6320c2 Add support for avatars from matrix. #984 (#1007) 2020-02-10 00:06:54 +01:00
Wim
23083f3ae0 Rebase gomatrix vendor with upstream (#1006) 2020-02-09 23:49:17 +01:00
Wim
1985873494 Implement basic reconnect (whatsapp). Fixes #987 (#1003) 2020-02-09 22:11:46 +01:00
Qais Patankar
8ae5917659 Be less lossy when throttling IRC messages (#1004)
Note that msg.Text and chucking it through a chan is OK: https://play.golang.org/p/MTfT3YSsgPX
2020-02-09 22:10:18 +01:00
Qais Patankar
c91bfd08d8 Add ability to procure avatars from the destination bridge (#1000)
* remote_avatar: add UseLocalAvatar

* remote_avatar: make sure msg.Protocol is always set correctly

* remote_avatars: support msg.Account

* remote_avatar: add to matterbridge.toml.sample

* remote_avatar: clarify something
2020-02-09 22:07:26 +01:00
Qais Patankar
49110a5872 Assign automatically labels when creating issues (#1005)
* Update Bug_report.md

* Add 'label: enhancement' to feature_request.md
2020-02-09 22:03:53 +01:00
Wim
c01c8edeb8 Fix go-keybase-chat-bot api changes 2020-02-08 18:33:05 +01:00
Wim
ff8cf067b8 Update kekeybase/go-keybase-chat-bot vendor 2020-02-08 18:33:05 +01:00
Qais Patankar
1420f68050 Check only bridged channels for PermManageWebhooks (discord) (#1001)
* Check only bridged channels for PermManageWebhooks

* add note
2020-02-08 15:13:23 +01:00
Martijn Braam
c0be3e585a Enable intra-word emphasis supression in markdown (#999)
This fixes plain links sent to Matrix being broken if they contain
underscores. Fixes issue #997
2020-02-04 13:22:05 +01:00
Wim
3049ef9151 Bump version 2020-02-02 22:40:44 +01:00
Wim
4be00bbe6b Release v1.16.5 2020-02-02 22:36:07 +01:00
Wim
9382dde098 Release v1.16.4 2020-02-02 22:22:39 +01:00
Wim
1bf46b7711 Fix duplicated messages (sshchat). Fixes #950 (#996) 2020-02-02 22:08:37 +01:00
Wim
b85bae31d9 Show file comment in webhook if normal message is empty (discord). Fixes #962 (#995) 2020-02-02 21:14:54 +01:00
Patrizio Bekerle
0898829313 Add Docker Compose configuration (#990)
* Add Docker Compose configuration

* Add docker wiki link
2020-02-02 21:14:19 +01:00
Wim
f8ad877601 Add DisableWebPagePreview option (telegram). Closes #980 (#994) 2020-02-02 18:53:04 +01:00
Wim
585d1556c1 Disable smartypants in markdown parser. Fixes #989, #983 (#993) 2020-02-02 18:35:43 +01:00
Wim
7486555875 Fail with message instead of panic. #988 (#991) 2020-02-01 15:23:50 +01:00
Humorhenker
fc30b1bacc Add QuoteLengthLimit option (telegram) fixes #963 (#985)
* QuoteLengthLimit option added to limit max. quoted message length if QuoteLengthLimit = 0 the whole message will be quoted
2020-01-30 00:02:33 +01:00
c0ncord2
0dd19af6e8 Create outmessage-discordemoji.tengo (#979) 2020-01-30 00:00:57 +01:00
Wim
4c44515f9d Fix channel ID problem with multiple gateways (discord). Fixes #953 (#977) 2020-01-09 23:54:04 +01:00
Wim
9d84d6dd64 Update to tengo v2 (#976) 2020-01-09 21:52:19 +01:00
Wim
0f708daf2d Update dependencies (#975) 2020-01-09 21:02:56 +01:00
Wim
b9354de8fd Clean up go.mod and vendor 2020-01-09 18:21:10 +01:00
Guillaume Lazzara
c9d5f4c898 Add support for WhatsApp media (jpeg/png/gif) bridging (#974)
* Whatsapp image bridging

* Prevent double message in telegram when media with caption received

Co-authored-by: imShara <shara@protonmail.com>
2020-01-09 18:14:01 +01:00
c0ncord2
810c150781 move stripCustomoji logic to default Tengo script (#973)
*  move stripCustomoji logic to default Tengo script 

Removing the image ID from the message (without any possibility of recovering it later) is a loss of valuable data that prevents users from giving support to custom emoji via Tengo scripts.

* bugfix - do send colors to other irc bridges

"if we're not sending to an irc bridge we strip the IRC colors"

Co-authored-by: c0ncord <59654954+c0ncord@users.noreply.github.com>
2020-01-09 18:02:53 +01:00
Wim
31dd538c0b Add extra mimetypes to docker image. Fixes #969 2020-01-07 23:34:11 +01:00
Justin W. Flory
62e38e7c45 Add link to Ansible role for Matterbridge (#968)
This commit replaces the FOSSRIT/infrastructure link to the Matterbridge
role to a properly-defined Ansible role published in Ansible Galaxy. I
am the maintainer of the FOSSRIT/infrastructure repo and I decided to
split the Ansible role there into its own dedicated role. I figure this
might make it more accessible to others and also gives other folks a
chance to contribute. 😄

Signed-off-by: Justin W. Flory <git@jwf.io>
2020-01-01 21:38:26 +01:00
Wim
b9da28a29b Bump version 2019-12-16 00:01:00 +01:00
4192 changed files with 343410 additions and 53649 deletions

View File

@@ -1,6 +1,7 @@
---
name: Bug report
about: Create a report to help us improve. (Check the FAQ on the wiki first)
labels: bug
---

View File

@@ -1,6 +1,7 @@
---
name: Feature request
about: Suggest an idea for this project
labels: enhancement
---

View File

@@ -175,6 +175,7 @@ linters:
- maligned
- prealloc
- wsl
- gomnd
# rules to deal with reported isues

View File

@@ -20,22 +20,22 @@ jobs:
- stage: lint
# Run linting in one Go environment only.
script: ./ci/lint.sh
go: 1.13.x
go: 1.14.x
env:
- GO111MODULE=on
- GOLANGCI_VERSION="v1.21.0"
- GOLANGCI_VERSION="v1.23.7"
- stage: test
# Run tests in a combination of Go environments.
script: ./ci/test.sh
go: 1.12.x
go: 1.13.x
env:
- GO111MODULE=off
- GOFLAGS=-mod=vendor
- script: ./ci/test.sh
go: 1.12.x
go: 1.13.x
env:
- GO111MODULE=on
- script: ./ci/test.sh
go: 1.13.x
go: 1.14.x
env:
- GO111MODULE=on
- REPORT_COVERAGE=1

View File

@@ -2,7 +2,7 @@ FROM alpine:edge
ENTRYPOINT ["/bin/matterbridge"]
COPY . /go/src/github.com/42wim/matterbridge
RUN apk update && apk add go git gcc musl-dev ca-certificates \
RUN apk update && apk add go git gcc musl-dev ca-certificates mailcap \
&& cd /go/src/github.com/42wim/matterbridge \
&& export GOPATH=/go \
&& go get \

View File

@@ -44,28 +44,34 @@ And more...
</a>
</p>
### Table of Contents
# Table of Contents
- [Features](https://github.com/42wim/matterbridge/wiki/Features)
- [Natively supported](#natively-supported)
- [3rd party via matterbridge api](#3rd-party-via-matterbridge-api)
- [API](#API)
- [Chat with us](#chat-with-us)
- [Screenshots](https://github.com/42wim/matterbridge/wiki/)
- [Installing/upgrading](#installing--upgrading)
- [Binaries](#binaries)
- [Building](#building)
- [Configuration](#configuration)
- [Howto](https://github.com/42wim/matterbridge/wiki/How-to-create-your-config)
- [Settings](#settings)
- [Examples](#examples)
- [Running](#running)
- [Docker](#docker)
- [Changelog](#changelog)
- [FAQ](#faq)
- [Related projects](#related-projects)
- [Articles](#articles)
- [Thanks](#thanks)
- [matterbridge](#matterbridge)
- [Table of Contents](#table-of-contents)
- [Features](#features)
- [Natively supported](#natively-supported)
- [3rd party via matterbridge api](#3rd-party-via-matterbridge-api)
- [API](#api)
- [Chat with us](#chat-with-us)
- [Screenshots](#screenshots)
- [Installing / upgrading](#installing--upgrading)
- [Binaries](#binaries)
- [Packages](#packages)
- [Building](#building)
- [Configuration](#configuration)
- [Basic configuration](#basic-configuration)
- [Settings](#settings)
- [Advanced configuration](#advanced-configuration)
- [Examples](#examples)
- [Bridge mattermost (off-topic) - irc (#testing)](#bridge-mattermost-off-topic---irc-testing)
- [Bridge slack (#general) - discord (general)](#bridge-slack-general---discord-general)
- [Running](#running)
- [Docker](#docker)
- [Changelog](#changelog)
- [FAQ](#faq)
- [Related projects](#related-projects)
- [Articles](#articles)
- [Thanks](#thanks)
## Features
@@ -133,6 +139,7 @@ Questions or want to test on your favorite platform? Join below:
- [Twitch][mb-twitch]
- [Zulip][mb-zulip]
- [Telegram][mb-telegram]
- [Keybase][mb-keybase]
## Screenshots
@@ -142,7 +149,7 @@ See https://github.com/42wim/matterbridge/wiki
### Binaries
- Latest stable release [v1.16.3](https://github.com/42wim/matterbridge/releases/latest)
- Latest stable release [v1.17.0](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.
@@ -262,11 +269,7 @@ Usage of ./matterbridge:
### Docker
Create your matterbridge.toml file locally eg in `/tmp/matterbridge.toml`
```
docker run -ti -v /tmp/matterbridge.toml:/matterbridge.toml 42wim/matterbridge
```
Please take a look at the [Docker Wiki page](https://github.com/42wim/matterbridge/wiki/Deploy:-Docker) for more information.
## Changelog
@@ -278,7 +281,7 @@ See [FAQ](https://github.com/42wim/matterbridge/wiki/FAQ)
## Related projects
- [FOSSRIT/infrastructure - roles/matterbridge](https://github.com/FOSSRIT/infrastructure/tree/master/roles/matterbridge) (Ansible role used to automate deployments of Matterbridge)
- [jwflory/ansible-role-matterbridge](https://galaxy.ansible.com/jwflory/matterbridge) (Ansible role to simplify deploying Matterbridge)
- [matterbridge autoconfig](https://github.com/patcon/matterbridge-autoconfig)
- [matterbridge config viewer](https://github.com/patcon/matterbridge-heroku-viewer)
- [matterbridge-heroku](https://github.com/cadecairos/matterbridge-heroku)
@@ -343,6 +346,6 @@ Matterbridge wouldn't exist without these libraries:
[mb-xmpp]: https://inverse.chat/
[mb-twitch]: https://www.twitch.tv/matterbridge
[mb-whatsapp]: https://www.whatsapp.com/
[mb-keybase]: https://keybase.io
[mb-keybase]: https://keybase.io/team/matterbridge
[mb-zulip]: https://matterbridge.zulipchat.com/register/
[mb-telegram]: https://t.me/Matterbridge

View File

@@ -1,6 +1,7 @@
package bridge
import (
"log"
"strings"
"sync"
@@ -41,6 +42,10 @@ type Factory func(*Config) Bridger
func New(bridge *config.Bridge) *Bridge {
accInfo := strings.Split(bridge.Account, ".")
if len(accInfo) != 2 {
log.Fatalf("config failure, account incorrect: %s", bridge.Account)
}
protocol := accInfo[0]
name := accInfo[1]

View File

@@ -3,6 +3,7 @@ package config
import (
"bytes"
"io/ioutil"
"path/filepath"
"strings"
"sync"
"time"
@@ -76,9 +77,11 @@ type Protocol struct {
BindAddress string // mattermost, slack // DEPRECATED
Buffer int // api
Charset string // irc
ClientID string // msteams
ColorNicks bool // only irc for now
Debug bool // general
DebugLevel int // only for irc now
DisableWebPagePreview bool // telegram
EditSuffix string // mattermost, slack, discord, telegram, gitter
EditDisable bool // mattermost, slack, discord, telegram, gitter
IconURL string // mattermost, slack
@@ -116,12 +119,14 @@ type Protocol struct {
Protocol string // all protocols
QuoteDisable bool // telegram
QuoteFormat string // telegram
QuoteLengthLimit int // telegram
RejoinDelay int // IRC
ReplaceMessages [][]string // all protocols
ReplaceNicks [][]string // all protocols
RemoteNickFormat string // all protocols
RunCommands []string // IRC
Server string // IRC,mattermost,XMPP,discord
SessionFile string // msteams,whatsapp
ShowJoinPart bool // all protocols
ShowTopicChange bool // slack
ShowUserTyping bool // slack
@@ -132,10 +137,13 @@ type Protocol struct {
SyncTopic bool // slack
TengoModifyMessage string // general
Team string // mattermost, keybase
TeamID string // msteams
TenantID string // msteams
Token string // gitter, slack, discord, api
Topic string // zulip
URL string // mattermost, slack // DEPRECATED
UseAPI bool // mattermost, slack
UseLocalAvatar []string // discord
UseSASL bool // IRC
UseTLS bool // IRC
UseDiscriminator bool // discord
@@ -233,7 +241,8 @@ func NewConfig(rootLogger *logrus.Logger, cfgfile string) Config {
logger.Fatalf("Failed to read configuration file: %#v", err)
}
mycfg := newConfigFromString(logger, input)
cfgtype := detectConfigType(cfgfile)
mycfg := newConfigFromString(logger, input, cfgtype)
if mycfg.cv.General.MediaDownloadSize == 0 {
mycfg.cv.General.MediaDownloadSize = 1000000
}
@@ -244,14 +253,26 @@ func NewConfig(rootLogger *logrus.Logger, cfgfile string) Config {
return mycfg
}
// detectConfigType detects JSON and YAML formats, defaults to TOML.
func detectConfigType(cfgfile string) string {
fileExt := filepath.Ext(cfgfile)
switch fileExt {
case ".json":
return "json"
case ".yaml", ".yml":
return "yaml"
}
return "toml"
}
// NewConfigFromString instantiates a new configuration based on the specified string.
func NewConfigFromString(rootLogger *logrus.Logger, input []byte) Config {
logger := rootLogger.WithFields(logrus.Fields{"prefix": "config"})
return newConfigFromString(logger, input)
return newConfigFromString(logger, input, "toml")
}
func newConfigFromString(logger *logrus.Entry, input []byte) *config {
viper.SetConfigType("toml")
func newConfigFromString(logger *logrus.Entry, input []byte, cfgtype string) *config {
viper.SetConfigType(cfgtype)
viper.SetEnvPrefix("matterbridge")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
viper.AutomaticEnv()

View File

@@ -10,7 +10,7 @@ import (
"github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
"github.com/bwmarrin/discordgo"
"github.com/matterbridge/discordgo"
)
const MessageLength = 1950
@@ -21,7 +21,6 @@ type Bdiscord struct {
c *discordgo.Session
nick string
useChannelID bool
guildID string
webhookID string
webhookToken string
@@ -115,30 +114,37 @@ func (b *Bdiscord) Connect() error {
b.Log.Infof("Server=\"%s\" # Server ID", guild.ID)
}
}
if err != nil {
return err
}
b.channelsMutex.RLock()
if b.GetString("WebhookURL") == "" {
for _, channel := range b.channels {
b.Log.Debugf("found channel %#v", channel)
}
} else {
b.canEditWebhooks = true
for _, channel := range b.channels {
b.Log.Debugf("found channel %#v; verifying PermissionManageWebhooks", channel)
perms, permsErr := b.c.State.UserChannelPermissions(userinfo.ID, channel.ID)
manageWebhooks := discordgo.PermissionManageWebhooks
if permsErr != nil || perms&manageWebhooks != manageWebhooks {
b.Log.Warnf("Can't manage webhooks in channel \"%s\"", channel.Name)
b.canEditWebhooks = false
manageWebhooks := discordgo.PermissionManageWebhooks
var channelsDenied []string
for _, info := range b.Channels {
id := b.getChannelID(info.Name) // note(qaisjp): this readlocks channelsMutex
b.Log.Debugf("Verifying PermissionManageWebhooks for %s with ID %s", info.ID, id)
perms, permsErr := b.c.UserChannelPermissions(userinfo.ID, id)
if permsErr != nil {
b.Log.Warnf("Failed to check PermissionManageWebhooks in channel \"%s\": %s", info.Name, permsErr.Error())
} else if perms&manageWebhooks == manageWebhooks {
continue
}
channelsDenied = append(channelsDenied, fmt.Sprintf("%#v", info.Name))
}
b.canEditWebhooks = len(channelsDenied) == 0
if b.canEditWebhooks {
b.Log.Info("Can manage webhooks; will edit channel for global webhook on send")
} else {
b.Log.Warn("Can't manage webhooks; won't edit channel for global webhook on send")
b.Log.Warn("Can't manage webhooks in channels: ", strings.Join(channelsDenied, ", "))
}
}
b.channelsMutex.RUnlock()
@@ -174,10 +180,6 @@ func (b *Bdiscord) JoinChannel(channel config.ChannelInfo) error {
defer b.channelsMutex.Unlock()
b.channelInfoMap[channel.ID] = &channel
idcheck := strings.Split(channel.Name, "ID:")
if len(idcheck) > 1 {
b.useChannelID = true
}
return nil
}
@@ -385,6 +387,19 @@ func (b *Bdiscord) webhookSend(msg *config.Message, webhookID, token string) (*d
err error
)
// If avatar is unset, check if UseLocalAvatar contains the message's
// account or protocol, and if so, try to find a local avatar
if msg.Avatar == "" {
for _, val := range b.GetStringSlice("UseLocalAvatar") {
if msg.Protocol == val || msg.Account == val {
if avatar := b.findAvatar(msg); avatar != "" {
msg.Avatar = avatar
}
break
}
}
}
// WebhookParams can have either `Content` or `File`.
// We can't send empty messages.
@@ -412,6 +427,10 @@ func (b *Bdiscord) webhookSend(msg *config.Message, webhookID, token string) (*d
ContentType: "",
Reader: bytes.NewReader(*fi.Data),
}
content := ""
if msg.Text == "" {
content = fi.Comment
}
_, e2 := b.c.WebhookExecute(
webhookID,
token,
@@ -420,6 +439,7 @@ func (b *Bdiscord) webhookSend(msg *config.Message, webhookID, token string) (*d
Username: msg.Username,
AvatarURL: msg.Avatar,
File: &file,
Content: content,
},
)
if e2 != nil {
@@ -429,3 +449,11 @@ func (b *Bdiscord) webhookSend(msg *config.Message, webhookID, token string) (*d
}
return res, err
}
func (b *Bdiscord) findAvatar(m *config.Message) string {
member, err := b.getGuildMemberByNick(m.Username)
if err != nil {
return ""
}
return member.User.AvatarURL("")
}

View File

@@ -2,15 +2,13 @@ package bdiscord
import (
"github.com/42wim/matterbridge/bridge/config"
"github.com/bwmarrin/discordgo"
"github.com/matterbridge/discordgo"
)
func (b *Bdiscord) messageDelete(s *discordgo.Session, m *discordgo.MessageDelete) { //nolint:unparam
rmsg := config.Message{Account: b.Account, ID: m.ID, Event: config.EventMsgDelete, Text: config.EventMsgDelete}
rmsg.Channel = b.getChannelName(m.ChannelID)
if b.useChannelID {
rmsg.Channel = "ID:" + m.ChannelID
}
b.Log.Debugf("<= Sending message from %s to gateway", b.Account)
b.Log.Debugf("<= Message is %#v", rmsg)
b.Remote <- rmsg
@@ -24,11 +22,7 @@ func (b *Bdiscord) messageDeleteBulk(s *discordgo.Session, m *discordgo.MessageD
ID: msgID,
Event: config.EventMsgDelete,
Text: config.EventMsgDelete,
Channel: "ID:" + m.ChannelID,
}
if !b.useChannelID {
rmsg.Channel = b.getChannelName(m.ChannelID)
Channel: b.getChannelName(m.ChannelID),
}
b.Log.Debugf("<= Sending message from %s to gateway", b.Account)
@@ -44,9 +38,6 @@ func (b *Bdiscord) messageTyping(s *discordgo.Session, m *discordgo.TypingStart)
rmsg := config.Message{Account: b.Account, Event: config.EventUserTyping}
rmsg.Channel = b.getChannelName(m.ChannelID)
if b.useChannelID {
rmsg.Channel = "ID:" + m.ChannelID
}
b.Remote <- rmsg
}
@@ -88,7 +79,6 @@ func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
if m.Content != "" {
b.Log.Debugf("== Receiving event %#v", m.Message)
m.Message.Content = b.stripCustomoji(m.Message.Content)
m.Message.Content = b.replaceChannelMentions(m.Message.Content)
rmsg.Text, err = m.ContentWithMoreMentionsReplaced(b.c)
if err != nil {
@@ -99,16 +89,13 @@ func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
// set channel name
rmsg.Channel = b.getChannelName(m.ChannelID)
if b.useChannelID {
rmsg.Channel = "ID:" + m.ChannelID
}
// set username
if !b.GetBool("UseUserName") {
fromWebhook := m.WebhookID != ""
if !fromWebhook && !b.GetBool("UseUserName") {
rmsg.Username = b.getNick(m.Author, m.GuildID)
} else {
rmsg.Username = m.Author.Username
if b.GetBool("UseDiscriminator") {
if !fromWebhook && b.GetBool("UseDiscriminator") {
rmsg.Username += "#" + m.Author.Discriminator
}
}
@@ -116,7 +103,7 @@ func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
// if we have embedded content add it to text
if b.GetBool("ShowEmbeds") && m.Message.Embeds != nil {
for _, embed := range m.Message.Embeds {
rmsg.Text = rmsg.Text + "embed: " + embed.Title + " - " + embed.Description + " - " + embed.URL + "\n"
rmsg.Text += handleEmbed(embed)
}
}
@@ -205,3 +192,33 @@ func (b *Bdiscord) memberRemove(s *discordgo.Session, m *discordgo.GuildMemberRe
b.Log.Debugf("<= Message is %#v", rmsg)
b.Remote <- rmsg
}
func handleEmbed(embed *discordgo.MessageEmbed) string {
var t []string
var result string
t = append(t, embed.Title)
t = append(t, embed.Description)
t = append(t, embed.URL)
i := 0
for _, e := range t {
if e == "" {
continue
}
i++
if i == 1 {
result += "embed: " + e
continue
}
result += " - " + e
}
if result != "" {
result += "\n"
}
return result
}

View File

@@ -0,0 +1,58 @@
package bdiscord
import (
"testing"
"github.com/matterbridge/discordgo"
"github.com/stretchr/testify/assert"
)
func TestHandleEmbed(t *testing.T) {
testcases := map[string]struct {
embed *discordgo.MessageEmbed
result string
}{
"allempty": {
embed: &discordgo.MessageEmbed{},
result: "",
},
"one": {
embed: &discordgo.MessageEmbed{
Title: "blah",
},
result: "embed: blah\n",
},
"two": {
embed: &discordgo.MessageEmbed{
Title: "blah",
Description: "blah2",
},
result: "embed: blah - blah2\n",
},
"three": {
embed: &discordgo.MessageEmbed{
Title: "blah",
Description: "blah2",
URL: "blah3",
},
result: "embed: blah - blah2 - blah3\n",
},
"twob": {
embed: &discordgo.MessageEmbed{
Description: "blah2",
URL: "blah3",
},
result: "embed: blah2 - blah3\n",
},
"oneb": {
embed: &discordgo.MessageEmbed{
URL: "blah3",
},
result: "embed: blah3\n",
},
}
for name, tc := range testcases {
assert.Equalf(t, tc.result, handleEmbed(tc.embed), "Testcases %s", name)
}
}

View File

@@ -6,7 +6,7 @@ import (
"strings"
"unicode"
"github.com/bwmarrin/discordgo"
"github.com/matterbridge/discordgo"
)
func (b *Bdiscord) getNick(user *discordgo.User, guildID string) string {
@@ -96,6 +96,13 @@ func (b *Bdiscord) getChannelName(id string) string {
b.channelsMutex.RLock()
defer b.channelsMutex.RUnlock()
for _, c := range b.channelInfoMap {
if c.Name == "ID:"+id {
// if we have ID: specified in our gateway configuration return this
return c.Name
}
}
for _, channel := range b.channels {
if channel.ID == id {
return b.getCategoryChannelName(channel.Name, channel.ParentID)
@@ -129,7 +136,6 @@ func (b *Bdiscord) getCategoryChannelName(name, parentID string) string {
var (
// See https://discordapp.com/developers/docs/reference#message-formatting.
channelMentionRE = regexp.MustCompile("<#[0-9]+>")
emojiRE = regexp.MustCompile("<(:.*?:)[0-9]+>")
userMentionRE = regexp.MustCompile("@[^@\n]{1,32}")
)
@@ -176,10 +182,6 @@ func (b *Bdiscord) replaceUserMentions(text string) string {
return userMentionRE.ReplaceAllStringFunc(text, replaceUserMentionFunc)
}
func (b *Bdiscord) stripCustomoji(text string) string {
return emojiRE.ReplaceAllString(text, `$1`)
}
func (b *Bdiscord) replaceAction(text string) (string, bool) {
if strings.HasPrefix(text, "_") && strings.HasSuffix(text, "_") {
return text[1 : len(text)-1], true

View File

@@ -15,6 +15,7 @@ import (
"github.com/42wim/matterbridge/bridge/config"
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/html"
"github.com/gomarkdown/markdown/parser"
"github.com/sirupsen/logrus"
)
@@ -179,9 +180,12 @@ func ClipMessage(text string, length int) string {
// ParseMarkdown takes in an input string as markdown and parses it to html
func ParseMarkdown(input string) string {
extensions := parser.HardLineBreak
extensions := parser.HardLineBreak | parser.NoIntraEmphasis
markdownParser := parser.NewWithExtensions(extensions)
parsedMarkdown := markdown.ToHTML([]byte(input), markdownParser, nil)
renderer := html.NewRenderer(html.RendererOptions{
Flags: 0,
})
parsedMarkdown := markdown.ToHTML([]byte(input), markdownParser, renderer)
res := string(parsedMarkdown)
res = strings.TrimPrefix(res, "<p>")
res = strings.TrimSuffix(res, "</p>\n")

View File

@@ -167,12 +167,8 @@ func (b *Birc) Send(msg config.Message) (string, error) {
return "", nil
}
b.Local <- config.Message{
Text: msgLines[i],
Username: msg.Username,
Channel: msg.Channel,
Event: msg.Event,
}
msg.Text = msgLines[i]
b.Local <- msg
}
return "", nil
}

View File

@@ -4,7 +4,7 @@ import (
"strconv"
"github.com/42wim/matterbridge/bridge/config"
"github.com/keybase/go-keybase-chat-bot/kbchat"
"github.com/keybase/go-keybase-chat-bot/kbchat/types/chat1"
)
func (b *Bkeybase) handleKeybase() {
@@ -20,7 +20,7 @@ func (b *Bkeybase) handleKeybase() {
b.Log.Errorf("failed to read message: %s", err.Error())
}
if msg.Message.Content.Type != "text" {
if msg.Message.Content.TypeName != "text" {
continue
}
@@ -34,7 +34,7 @@ func (b *Bkeybase) handleKeybase() {
}()
}
func (b *Bkeybase) handleMessage(msg kbchat.Message) {
func (b *Bkeybase) handleMessage(msg chat1.MsgSummary) {
b.Log.Debugf("== Receiving event: %#v", msg)
if msg.Channel.TopicName != b.channel || msg.Channel.Name != b.team {
return
@@ -45,10 +45,10 @@ func (b *Bkeybase) handleMessage(msg kbchat.Message) {
// TODO download avatar
// Create our message
rmsg := config.Message{Username: msg.Sender.Username, Text: msg.Content.Text.Body, UserID: msg.Sender.Uid, Channel: msg.Channel.TopicName, ID: strconv.Itoa(msg.MsgID), Account: b.Account}
rmsg := config.Message{Username: msg.Sender.Username, Text: msg.Content.Text.Body, UserID: string(msg.Sender.Uid), Channel: msg.Channel.TopicName, ID: strconv.Itoa(int(msg.Id)), Account: b.Account}
// Text must be a string
if msg.Content.Type != "text" {
if msg.Content.TypeName != "text" {
b.Log.Errorf("message is not text")
return
}

View File

@@ -90,16 +90,17 @@ func (b *Bkeybase) Send(msg config.Message) (string, error) {
return "", err
}
_, _ = b.kbc.SendAttachmentByTeam(b.team, fpath, fcaption, &b.channel)
_, _ = b.kbc.SendAttachmentByTeam(b.team, &b.channel, fpath, fcaption)
}
return "", nil
}
// Send regular message
resp, err := b.kbc.SendMessageByTeamName(b.team, msg.Username+msg.Text, &b.channel)
text := msg.Username + msg.Text
resp, err := b.kbc.SendMessageByTeamName(b.team, &b.channel, text)
if err != nil {
return "", err
}
return strconv.Itoa(resp.Result.MsgID), err
return strconv.Itoa(int(*resp.Result.MessageID)), err
}

View File

@@ -172,10 +172,15 @@ func (b *Bmatrix) handleEvent(ev *matrix.Event) {
return
}
// TODO download avatar
// Create our message
rmsg := config.Message{Username: ev.Sender[1:], Channel: channel, Account: b.Account, UserID: ev.Sender, ID: ev.ID}
rmsg := config.Message{
Username: ev.Sender[1:],
Channel: channel,
Account: b.Account,
UserID: ev.Sender,
ID: ev.ID,
Avatar: b.getAvatarURL(ev.Sender),
}
// Text must be a string
if rmsg.Text, ok = ev.Content["body"].(string); !ok {
@@ -358,3 +363,15 @@ func (b *Bmatrix) containsAttachment(content map[string]interface{}) bool {
}
return true
}
// getAvatarURL returns the avatar URL of the specified sender
func (b *Bmatrix) getAvatarURL(sender string) string {
mxcURL, err := b.mc.GetSenderAvatarURL(sender)
if err != nil {
b.Log.Errorf("getAvatarURL failed: %s", err)
return ""
}
url := strings.ReplaceAll(mxcURL, "mxc://", b.GetString("Server")+"/_matrix/media/r0/thumbnail/")
url += "?width=37&height=37&method=crop"
return url
}

101
bridge/msteams/handler.go Normal file
View File

@@ -0,0 +1,101 @@
package bmsteams
import (
"encoding/json"
"fmt"
"io/ioutil"
"strings"
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
msgraph "github.com/matterbridge/msgraph.go/beta"
)
func (b *Bmsteams) findFile(weburl string) (string, error) {
itemRB, err := b.gc.GetDriveItemByURL(b.ctx, weburl)
if err != nil {
return "", err
}
itemRB.Workbook().Worksheets()
b.gc.Workbooks()
item, err := itemRB.Request().Get(b.ctx)
if err != nil {
return "", err
}
if url, ok := item.GetAdditionalData("@microsoft.graph.downloadUrl"); ok {
return url.(string), nil
}
return "", nil
}
// handleDownloadFile handles file download
func (b *Bmsteams) handleDownloadFile(rmsg *config.Message, filename, weburl string) error {
realURL, err := b.findFile(weburl)
if err != nil {
return err
}
// Actually download the file.
data, err := helper.DownloadFile(realURL)
if err != nil {
return fmt.Errorf("download %s failed %#v", weburl, err)
}
// If a comment is attached to the file(s) it is in the 'Text' field of the teams messge event
// and should be added as comment to only one of the files. We reset the 'Text' field to ensure
// that the comment is not duplicated.
comment := rmsg.Text
rmsg.Text = ""
helper.HandleDownloadData(b.Log, rmsg, filename, comment, weburl, data, b.General)
return nil
}
func (b *Bmsteams) handleAttachments(rmsg *config.Message, msg msgraph.ChatMessage) {
for _, a := range msg.Attachments {
//remove the attachment tags from the text
rmsg.Text = attachRE.ReplaceAllString(rmsg.Text, "")
//handle a code snippet (code block)
if *a.ContentType == "application/vnd.microsoft.card.codesnippet" {
b.handleCodeSnippet(rmsg, a)
continue
}
//handle the download
err := b.handleDownloadFile(rmsg, *a.Name, *a.ContentURL)
if err != nil {
b.Log.Errorf("download of %s failed: %s", *a.Name, err)
}
}
}
type AttachContent struct {
Language string `json:"language"`
CodeSnippetURL string `json:"codeSnippetUrl"`
}
func (b *Bmsteams) handleCodeSnippet(rmsg *config.Message, attach msgraph.ChatMessageAttachment) {
var content AttachContent
err := json.Unmarshal([]byte(*attach.Content), &content)
if err != nil {
b.Log.Errorf("unmarshal codesnippet failed: %s", err)
return
}
s := strings.Split(content.CodeSnippetURL, "/")
if len(s) != 13 {
b.Log.Errorf("codesnippetUrl has unexpected size: %s", content.CodeSnippetURL)
return
}
resp, err := b.gc.Teams().Request().Client().Get(content.CodeSnippetURL)
if err != nil {
b.Log.Errorf("retrieving snippet content failed:%s", err)
return
}
defer resp.Body.Close()
res, err := ioutil.ReadAll(resp.Body)
if err != nil {
b.Log.Errorf("reading snippet data failed: %s", err)
return
}
rmsg.Text = rmsg.Text + "\n```" + content.Language + "\n" + string(res) + "\n```\n"
}

205
bridge/msteams/msteams.go Normal file
View File

@@ -0,0 +1,205 @@
package bmsteams
import (
"context"
"fmt"
"os"
"regexp"
"strings"
"time"
"github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config"
// "github.com/davecgh/go-spew/spew"
msgraph "github.com/matterbridge/msgraph.go/beta"
"github.com/matterbridge/msgraph.go/msauth"
"github.com/mattn/godown"
"golang.org/x/oauth2"
)
var defaultScopes = []string{"openid", "profile", "offline_access", "Group.Read.All", "Group.ReadWrite.All"}
var attachRE = regexp.MustCompile(`<attachment id=.*?attachment>`)
type Bmsteams struct {
gc *msgraph.GraphServiceRequestBuilder
ctx context.Context
botID string
*bridge.Config
}
func New(cfg *bridge.Config) bridge.Bridger {
return &Bmsteams{Config: cfg}
}
func (b *Bmsteams) Connect() error {
tokenCachePath := b.GetString("sessionFile")
if tokenCachePath == "" {
tokenCachePath = "msteams_session.json"
}
ctx := context.Background()
m := msauth.NewManager()
m.LoadFile(tokenCachePath) //nolint:errcheck
ts, err := m.DeviceAuthorizationGrant(ctx, b.GetString("TenantID"), b.GetString("ClientID"), defaultScopes, nil)
if err != nil {
return err
}
err = m.SaveFile(tokenCachePath)
if err != nil {
b.Log.Errorf("Couldn't save sessionfile in %s: %s", tokenCachePath, err)
}
// make file readable only for matterbridge user
err = os.Chmod(tokenCachePath, 0600)
if err != nil {
b.Log.Errorf("Couldn't change permissions for %s: %s", tokenCachePath, err)
}
httpClient := oauth2.NewClient(ctx, ts)
graphClient := msgraph.NewClient(httpClient)
b.gc = graphClient
b.ctx = ctx
err = b.setBotID()
if err != nil {
return err
}
b.Log.Info("Connection succeeded")
return nil
}
func (b *Bmsteams) Disconnect() error {
return nil
}
func (b *Bmsteams) JoinChannel(channel config.ChannelInfo) error {
go b.poll(channel.Name)
return nil
}
func (b *Bmsteams) Send(msg config.Message) (string, error) {
b.Log.Debugf("=> Receiving %#v", msg)
if msg.ParentID != "" && msg.ParentID != "msg-parent-not-found" {
return b.sendReply(msg)
}
if msg.ParentID == "msg-parent-not-found" {
msg.ParentID = ""
msg.Text = fmt.Sprintf("[thread]: %s", msg.Text)
}
ct := b.gc.Teams().ID(b.GetString("TeamID")).Channels().ID(msg.Channel).Messages().Request()
text := msg.Username + msg.Text
content := &msgraph.ItemBody{Content: &text}
rmsg := &msgraph.ChatMessage{Body: content}
res, err := ct.Add(b.ctx, rmsg)
if err != nil {
return "", err
}
return *res.ID, nil
}
func (b *Bmsteams) sendReply(msg config.Message) (string, error) {
ct := b.gc.Teams().ID(b.GetString("TeamID")).Channels().ID(msg.Channel).Messages().ID(msg.ParentID).Replies().Request()
// Handle prefix hint for unthreaded messages.
text := msg.Username + msg.Text
content := &msgraph.ItemBody{Content: &text}
rmsg := &msgraph.ChatMessage{Body: content}
res, err := ct.Add(b.ctx, rmsg)
if err != nil {
return "", err
}
return *res.ID, nil
}
func (b *Bmsteams) getMessages(channel string) ([]msgraph.ChatMessage, error) {
ct := b.gc.Teams().ID(b.GetString("TeamID")).Channels().ID(channel).Messages().Request()
rct, err := ct.Get(b.ctx)
if err != nil {
return nil, err
}
b.Log.Debugf("got %#v messages", len(rct))
return rct, nil
}
//nolint:gocognit
func (b *Bmsteams) poll(channelName string) {
msgmap := make(map[string]time.Time)
b.Log.Debug("getting initial messages")
res, err := b.getMessages(channelName)
if err != nil {
panic(err)
}
for _, msg := range res {
msgmap[*msg.ID] = *msg.CreatedDateTime
if msg.LastModifiedDateTime != nil {
msgmap[*msg.ID] = *msg.LastModifiedDateTime
}
}
time.Sleep(time.Second * 5)
b.Log.Debug("polling for messages")
for {
res, err := b.getMessages(channelName)
if err != nil {
panic(err)
}
for i := len(res) - 1; i >= 0; i-- {
msg := res[i]
if mtime, ok := msgmap[*msg.ID]; ok {
if mtime == *msg.CreatedDateTime && msg.LastModifiedDateTime == nil {
continue
}
if msg.LastModifiedDateTime != nil && mtime == *msg.LastModifiedDateTime {
continue
}
}
if *msg.From.User.ID == b.botID {
b.Log.Debug("skipping own message")
msgmap[*msg.ID] = *msg.CreatedDateTime
continue
}
msgmap[*msg.ID] = *msg.CreatedDateTime
if msg.LastModifiedDateTime != nil {
msgmap[*msg.ID] = *msg.LastModifiedDateTime
}
b.Log.Debugf("<= Sending message from %s on %s to gateway", *msg.From.User.DisplayName, b.Account)
text := b.convertToMD(*msg.Body.Content)
rmsg := config.Message{
Username: *msg.From.User.DisplayName,
Text: text,
Channel: channelName,
Account: b.Account,
Avatar: "",
UserID: *msg.From.User.ID,
ID: *msg.ID,
Extra: make(map[string][]interface{}),
}
b.handleAttachments(&rmsg, msg)
b.Log.Debugf("<= Message is %#v", rmsg)
b.Remote <- rmsg
}
time.Sleep(time.Second * 5)
}
}
func (b *Bmsteams) setBotID() error {
req := b.gc.Me().Request()
r, err := req.Get(b.ctx)
if err != nil {
return err
}
b.botID = *r.ID
return nil
}
func (b *Bmsteams) convertToMD(text string) string {
if !strings.Contains(text, "<div>") {
return text
}
var sb strings.Builder
err := godown.Convert(&sb, strings.NewReader(text), nil)
if err != nil {
b.Log.Errorf("Couldn't convert message to markdown %s", text)
return text
}
return sb.String()
}

View File

@@ -7,7 +7,7 @@ import (
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
"github.com/nlopes/slack"
"github.com/slack-go/slack"
)
func (b *Bslack) handleSlack() {
@@ -87,7 +87,7 @@ func (b *Bslack) handleSlackClient(messages chan *config.Message) {
b.Log.Errorf("Connection failed %#v %#v", ev.Error(), ev.ErrorObj)
case *slack.MemberJoinedChannelEvent:
b.users.populateUser(ev.User)
case *slack.HelloEvent, *slack.LatencyReport:
case *slack.HelloEvent, *slack.LatencyReport, *slack.ConnectingEvent:
continue
default:
b.Log.Debugf("Unhandled incoming event: %T", ev)
@@ -124,10 +124,16 @@ func (b *Bslack) skipMessageEvent(ev *slack.MessageEvent) bool {
}
}
// Check for our callback ID
hasOurCallbackID := false
if len(ev.Blocks.BlockSet) == 1 {
block, ok := ev.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) ||
(len(ev.Attachments) > 0 && ev.Attachments[0].CallbackID == "matterbridge_"+b.uuid) {
(b.rtm != nil && ev.Username == b.si.User.Name) || hasOurCallbackID {
return true
}

View File

@@ -7,8 +7,8 @@ import (
"time"
"github.com/42wim/matterbridge/bridge/config"
"github.com/nlopes/slack"
"github.com/sirupsen/logrus"
"github.com/slack-go/slack"
)
// populateReceivedMessage shapes the initial Matterbridge message that we will forward to the

View File

@@ -5,7 +5,7 @@ import (
"github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/matterhook"
"github.com/nlopes/slack"
"github.com/slack-go/slack"
)
type BLegacy struct {

View File

@@ -13,8 +13,8 @@ import (
"github.com/42wim/matterbridge/bridge/helper"
"github.com/42wim/matterbridge/matterhook"
lru "github.com/hashicorp/golang-lru"
"github.com/nlopes/slack"
"github.com/rs/xid"
"github.com/slack-go/slack"
)
type Bslack struct {
@@ -408,7 +408,6 @@ func (b *Bslack) editMessage(msg *config.Message, channelInfo *slack.Channel) (b
}
messageOptions := b.prepareMessageOptions(msg)
for {
messageOptions = append(messageOptions, slack.MsgOptionText(msg.Text, false))
_, _, _, err := b.rtm.UpdateMessage(channelInfo.ID, msg.ID, messageOptions...)
if err == nil {
return true, nil
@@ -427,11 +426,6 @@ func (b *Bslack) postMessage(msg *config.Message, channelInfo *slack.Channel) (s
return "", nil
}
messageOptions := b.prepareMessageOptions(msg)
messageOptions = append(
messageOptions,
slack.MsgOptionText(msg.Text, false),
slack.MsgOptionEnableLinkUnfurl(),
)
for {
_, id, err := b.rtm.PostMessage(channelInfo.ID, messageOptions...)
if err == nil {
@@ -497,8 +491,6 @@ func (b *Bslack) prepareMessageOptions(msg *config.Message) []slack.MsgOption {
}
var attachments []slack.Attachment
// add a callback ID so we can see we created it
attachments = append(attachments, slack.Attachment{CallbackID: "matterbridge_" + b.uuid})
// add file attachments
attachments = append(attachments, b.createAttach(msg.Extra)...)
// add slack attachments (from another slack bridge)
@@ -509,6 +501,19 @@ func (b *Bslack) prepareMessageOptions(msg *config.Message) []slack.MsgOption {
}
var opts []slack.MsgOption
opts = append(opts,
// provide regular text field (fallback used in Slack notifications, etc.)
slack.MsgOptionText(msg.Text, false),
// add a callback ID so we can see we created it
slack.MsgOptionBlocks(slack.NewSectionBlock(
slack.NewTextBlockObject(slack.MarkdownType, msg.Text, false, false),
nil, nil,
slack.SectionBlockOptionBlockID("matterbridge_"+b.uuid),
)),
slack.MsgOptionEnableLinkUnfurl(),
)
opts = append(opts, slack.MsgOptionAttachments(attachments...))
opts = append(opts, slack.MsgOptionPostMessageParameters(params))
return opts

View File

@@ -8,8 +8,8 @@ import (
"time"
"github.com/42wim/matterbridge/bridge/config"
"github.com/nlopes/slack"
"github.com/sirupsen/logrus"
"github.com/slack-go/slack"
)
const minimumRefreshInterval = 10 * time.Second

View File

@@ -130,6 +130,10 @@ func (b *Bsshchat) handleSSHChat() error {
if strings.Contains(b.r.Text(), "Rate limiting is in effect") {
continue
}
// skip our own messages
if !strings.HasPrefix(b.r.Text(), "["+b.GetString("Nick")+"] \x1b") {
continue
}
res := strings.Split(stripPrompt(b.r.Text()), ":")
if res[0] == "-> Set theme" {
wait = false

View File

@@ -95,7 +95,7 @@ func (b *Btelegram) handleUsername(rmsg *config.Message, message *tgbotapi.Messa
}
}
// only download avatars if we have a place to upload them (configured mediaserver)
if b.General.MediaServerUpload != "" {
if b.General.MediaServerUpload != "" || (b.General.MediaServerDownload != "" && b.General.MediaDownloadPath != "") {
b.handleDownloadAvatar(message.From.ID, rmsg.Channel)
}
}
@@ -357,6 +357,14 @@ func (b *Btelegram) handleQuote(message, quoteNick, quoteMessage string) string
if format == "" {
format = "{MESSAGE} (re @{QUOTENICK}: {QUOTEMESSAGE})"
}
quoteMessagelength := len(quoteMessage)
if b.GetInt("QuoteLengthLimit") != 0 && quoteMessagelength >= b.GetInt("QuoteLengthLimit") {
runes := []rune(quoteMessage)
quoteMessage = string(runes[0:b.GetInt("QuoteLengthLimit")])
if quoteMessagelength > b.GetInt("QuoteLengthLimit") {
quoteMessage += "..."
}
}
format = strings.Replace(format, "{MESSAGE}", message, -1)
format = strings.Replace(format, "{QUOTENICK}", quoteNick, -1)
format = strings.Replace(format, "{QUOTEMESSAGE}", quoteMessage, -1)

View File

@@ -8,7 +8,7 @@ import (
"github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
"github.com/go-telegram-bot-api/telegram-bot-api"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
)
const (
@@ -81,8 +81,8 @@ func (b *Btelegram) Send(msg config.Message) (string, error) {
// Upload a file if it exists
if msg.Extra != nil {
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
if _, err := b.sendMessage(chatid, rmsg.Username, rmsg.Text); err != nil {
b.Log.Errorf("sendMessage failed: %s", err)
if _, msgErr := b.sendMessage(chatid, rmsg.Username, rmsg.Text); msgErr != nil {
b.Log.Errorf("sendMessage failed: %s", msgErr)
}
}
// check if we have files to upload (from slack, telegram or mattermost)
@@ -97,7 +97,14 @@ func (b *Btelegram) Send(msg config.Message) (string, error) {
}
// Post normal message
return b.sendMessage(chatid, msg.Username, msg.Text)
// TODO: recheck it.
// Ignore empty text field needs for prevent double messages from whatsapp to telegram
// when sending media with text caption
if msg.Text != "" {
return b.sendMessage(chatid, msg.Username, msg.Text)
}
return "", nil
}
func (b *Btelegram) getFileDirectURL(id string) string {
@@ -124,6 +131,9 @@ func (b *Btelegram) sendMessage(chatid int64, username, text string) (string, er
m.Text = username + html.EscapeString(text)
m.ParseMode = tgbotapi.ModeHTML
}
m.DisableWebPagePreview = b.GetBool("DisableWebPagePreview")
res, err := b.c.Send(m)
if err != nil {
return "", err

View File

@@ -1,11 +1,15 @@
package bwhatsapp
import (
"fmt"
"mime"
"strings"
"time"
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
"github.com/Rhymen/go-whatsapp"
"github.com/jpillora/backoff"
)
/*
@@ -22,7 +26,38 @@ func (b *Bwhatsapp) HandleError(err error) {
if strings.Contains(err.Error(), "error processing data: received invalid data") {
return
}
b.Log.Errorf("%v", err) // TODO implement proper handling? at least respond to different error types
switch err.(type) {
case *whatsapp.ErrConnectionClosed, *whatsapp.ErrConnectionFailed:
b.reconnect(err)
default:
switch err {
case whatsapp.ErrConnectionTimeout:
b.reconnect(err)
default:
b.Log.Errorf("%v", err)
}
}
}
func (b *Bwhatsapp) reconnect(err error) {
bf := &backoff.Backoff{
Min: time.Second,
Max: 5 * time.Minute,
Jitter: true,
}
for {
d := bf.Duration()
b.Log.Errorf("Connection failed, underlying error: %v", err)
b.Log.Infof("Waiting %s...", d)
time.Sleep(d)
b.Log.Info("Reconnecting...")
err := b.conn.Restore()
if err == nil {
bf.Reset()
return
}
}
}
// HandleTextMessage sent from WhatsApp, relay it to the brige
@@ -36,16 +71,16 @@ func (b *Bwhatsapp) HandleTextMessage(message whatsapp.TextMessage) {
}
messageTime := time.Unix(int64(message.Info.Timestamp), 0) // TODO check how behaves between timezones
groupJid := message.Info.RemoteJid
groupJID := message.Info.RemoteJid
senderJid := message.Info.SenderJid
if len(senderJid) == 0 {
senderJID := message.Info.SenderJid
if len(senderJID) == 0 {
// TODO workaround till https://github.com/Rhymen/go-whatsapp/issues/86 resolved
senderJid = *message.Info.Source.Participant
senderJID = *message.Info.Source.Participant
}
// translate sender's Jid to the nicest username we can get
senderName := b.getSenderName(senderJid)
// translate sender's JID to the nicest username we can get
senderName := b.getSenderName(senderJID)
if senderName == "" {
senderName = "Someone" // don't expose telephone number
}
@@ -53,8 +88,8 @@ func (b *Bwhatsapp) HandleTextMessage(message whatsapp.TextMessage) {
extText := message.Info.Source.Message.ExtendedTextMessage
if extText != nil && extText.ContextInfo != nil && extText.ContextInfo.MentionedJid != nil {
// handle user mentions
for _, mentionedJid := range extText.ContextInfo.MentionedJid {
numberAndSuffix := strings.SplitN(mentionedJid, "@", 2)
for _, mentionedJID := range extText.ContextInfo.MentionedJid {
numberAndSuffix := strings.SplitN(mentionedJID, "@", 2)
// mentions comes as telephone numbers and we don't want to expose it to other bridges
// replace it with something more meaninful to others
@@ -66,22 +101,22 @@ func (b *Bwhatsapp) HandleTextMessage(message whatsapp.TextMessage) {
}
}
b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJid, b.Account)
b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account)
rmsg := config.Message{
UserID: senderJid,
UserID: senderJID,
Username: senderName,
Text: message.Text,
Timestamp: messageTime,
Channel: groupJid,
Channel: groupJID,
Account: b.Account,
Protocol: b.Protocol,
Extra: make(map[string][]interface{}),
// ParentID: TODO, // TODO handle thread replies // map from Info.QuotedMessageID string
// ParentID: TODO, // TODO handle thread replies // map from Info.QuotedMessageID string
// Event string `json:"event"`
// Gateway string // will be added during message processing
ID: message.Info.Id}
if avatarURL, exists := b.userAvatars[senderJid]; exists {
if avatarURL, exists := b.userAvatars[senderJID]; exists {
rmsg.Avatar = avatarURL
}
@@ -89,11 +124,75 @@ func (b *Bwhatsapp) HandleTextMessage(message whatsapp.TextMessage) {
b.Remote <- rmsg
}
//
//func (b *Bwhatsapp) HandleImageMessage(message whatsapp.ImageMessage) {
// fmt.Println(message) // TODO implement
//}
//
// HandleImageMessage sent from WhatsApp, relay it to the brige
func (b *Bwhatsapp) HandleImageMessage(message whatsapp.ImageMessage) {
if message.Info.FromMe { // || !strings.Contains(strings.ToLower(message.Text), "@echo") {
return
}
// whatsapp sends last messages to show context , cut them
if message.Info.Timestamp < b.startedAt {
return
}
messageTime := time.Unix(int64(message.Info.Timestamp), 0) // TODO check how behaves between timezones
groupJID := message.Info.RemoteJid
senderJID := message.Info.SenderJid
// if len(senderJid) == 0 {
// // TODO workaround till https://github.com/Rhymen/go-whatsapp/issues/86 resolved
// senderJid = *message.Info.Source.Participant
// }
// translate sender's Jid to the nicest username we can get
senderName := b.getSenderName(senderJID)
if senderName == "" {
senderName = "Someone" // don't expose telephone number
}
b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account)
rmsg := config.Message{
UserID: senderJID,
Username: senderName,
Timestamp: messageTime,
Channel: groupJID,
Account: b.Account,
Protocol: b.Protocol,
Extra: make(map[string][]interface{}),
// ParentID: TODO, // TODO handle thread replies // map from Info.QuotedMessageID string
// Event string `json:"event"`
// Gateway string // will be added during message processing
ID: message.Info.Id}
if avatarURL, exists := b.userAvatars[senderJID]; exists {
rmsg.Avatar = avatarURL
}
// Download and unencrypt content
data, err := message.Download()
if err != nil {
b.Log.Errorf("%v", err)
return
}
// Get file extension by mimetype
fileExt, err := mime.ExtensionsByType(message.Type)
if err != nil {
b.Log.Errorf("%v", err)
return
}
filename := fmt.Sprintf("%v%v", message.Info.Id, fileExt[0])
b.Log.Debugf("<= Image downloaded and unencrypted")
// Move file to bridge storage
helper.HandleDownloadData(b.Log, &rmsg, filename, message.Caption, "", &data, b.General)
b.Log.Debugf("<= Image Message is %#v", rmsg)
b.Remote <- rmsg
}
//func (b *Bwhatsapp) HandleVideoMessage(message whatsapp.VideoMessage) {
// fmt.Println(message) // TODO implement
//}

View File

@@ -1,11 +1,14 @@
package bwhatsapp
import (
"bytes"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"mime"
"os"
"path/filepath"
"strings"
"time"
@@ -230,6 +233,66 @@ func (b *Bwhatsapp) JoinChannel(channel config.ChannelInfo) error {
return nil
}
// Post a document message from the bridge to WhatsApp
func (b *Bwhatsapp) PostDocumentMessage(msg config.Message, filetype string) (string, error) {
fi := msg.Extra["file"][0].(config.FileInfo)
// Post document message
message := whatsapp.DocumentMessage{
Info: whatsapp.MessageInfo{
RemoteJid: msg.Channel,
},
Title: fi.Name,
FileName: fi.Name,
Type: filetype,
Content: bytes.NewReader(*fi.Data),
}
b.Log.Debugf("=> Sending %#v", msg)
// create message ID
// TODO follow and act if https://github.com/Rhymen/go-whatsapp/issues/101 implemented
idBytes := make([]byte, 10)
if _, err := rand.Read(idBytes); err != nil {
b.Log.Warn(err.Error())
}
message.Info.Id = strings.ToUpper(hex.EncodeToString(idBytes))
_, err := b.conn.Send(message)
return message.Info.Id, err
}
// Post an image message from the bridge to WhatsApp
// Handle, for sure image/jpeg, image/png and image/gif MIME types
func (b *Bwhatsapp) PostImageMessage(msg config.Message, filetype string) (string, error) {
fi := msg.Extra["file"][0].(config.FileInfo)
// Post image message
message := whatsapp.ImageMessage{
Info: whatsapp.MessageInfo{
RemoteJid: msg.Channel,
},
Type: filetype,
Caption: msg.Username + fi.Comment,
Content: bytes.NewReader(*fi.Data),
}
b.Log.Debugf("=> Sending %#v", msg)
// create message ID
// TODO follow and act if https://github.com/Rhymen/go-whatsapp/issues/101 implemented
idBytes := make([]byte, 10)
if _, err := rand.Read(idBytes); err != nil {
b.Log.Warn(err.Error())
}
message.Info.Id = strings.ToUpper(hex.EncodeToString(idBytes))
_, err := b.conn.Send(message)
return message.Info.Id, err
}
// Send a message from the bridge to WhatsApp
// Required implementation of the Bridger interface
// https://github.com/42wim/matterbridge/blob/2cfd880cdb0df29771bf8f31df8d990ab897889d/bridge/bridge.go#L11-L16
@@ -259,18 +322,25 @@ func (b *Bwhatsapp) Send(msg config.Message) (string, error) {
// TODO handle edit as a message reply with updated text
}
//// TODO Handle Upload a file
//if msg.Extra != nil {
// for _, rmsg := range helper.HandleExtra(&msg, b.General) {
// b.c.SendMessage(roomID, rmsg.Username+rmsg.Text)
// }
// if len(msg.Extra["file"]) > 0 {
// return b.handleUploadFile(&msg, roomID)
// }
//}
// Handle Upload a file
if msg.Extra["file"] != nil {
fi := msg.Extra["file"][0].(config.FileInfo)
filetype := mime.TypeByExtension(filepath.Ext(fi.Name))
b.Log.Debugf("Extra file is %#v", filetype)
// TODO: add different types
// TODO: add webp conversion
switch filetype {
case "image/jpeg", "image/png", "image/gif":
return b.PostImageMessage(msg, filetype)
default:
return b.PostDocumentMessage(msg, filetype)
}
}
// Post text message
text := whatsapp.TextMessage{
message := whatsapp.TextMessage{
Info: whatsapp.MessageInfo{
RemoteJid: msg.Channel, // which equals to group id
},
@@ -281,15 +351,14 @@ func (b *Bwhatsapp) Send(msg config.Message) (string, error) {
// create message ID
// TODO follow and act if https://github.com/Rhymen/go-whatsapp/issues/101 implemented
bytes := make([]byte, 10)
if _, err := rand.Read(bytes); err != nil {
idBytes := make([]byte, 10)
if _, err := rand.Read(idBytes); err != nil {
b.Log.Warn(err.Error())
}
text.Info.Id = strings.ToUpper(hex.EncodeToString(bytes))
message.Info.Id = strings.ToUpper(hex.EncodeToString(idBytes))
_, err := b.conn.Send(message)
_, err := b.conn.Send(text)
return text.Info.Id, err
return message.Info.Id, err
}
// TODO do we want that? to allow login with QR code from a bridged channel? https://github.com/tulir/mautrix-whatsapp/blob/513eb18e2d59bada0dd515ee1abaaf38a3bfe3d5/commands.go#L76

View File

@@ -1,3 +1,68 @@
# v1.17.0
## New features
- msteams: new protocol added. Add initial Microsoft Teams support #967
See https://github.com/42wim/matterbridge/wiki/MS-Teams-setup for a complete walkthrough
- discord: Add ability to procure avatars from the destination bridge #1000
- matrix: Add support for avatars from matrix. #1007
- general: support JSON and YAML config formats #1045
## Enhancements
- discord: Check only bridged channels for PermManageWebhooks #1001
- irc: Be less lossy when throttling IRC messages #1004
- keybase: updated library #1002, #1019
- matrix: Rebase gomatrix vendor with upstream #1006
- slack: Use upstream slack-go/slack again #1018
- slack: Ignore ConnectingEvent #1041
- slack: use blocks not attachments #1048
- sshchat: Update vendor shazow/ssh-chat #1029
- telegram: added markdownv2 mode for telegram #1037
- whatsapp: Implement basic reconnect (whatsapp). Fixes #987 #1003
## Bugfix
- discord: Fix webhook permission checks sometimes failing #1043
- discord: Fix #1027: warning when handling inbound webhooks #1044
- discord: Fix duplicate separator on empty description/url (discord) #1035
- matrix: Fix issue with underscores in links #999
- slack: Fix #1039: messages sent to Slack being synced back #1046
- telegram: Make avatars download work with mediaserverdownload (telegram). Fixes #920
This release couldn't exist without the following contributors:
@qaisjp, @jakubgs, @burner1024, @notpushkin, @MartijnBraam, @42wim
# v1.16.5
- Fix version bump
# v1.16.4
## New features
- whatsapp: Add support for WhatsApp media (jpeg/png/gif) bridging (#974)
- telegram: Add QuoteLengthLimit option (telegram) fixes #963 (#985)
- telegram: Add DisableWebPagePreview option (telegram). Closes #980 (#994)
## Enhancements
- general: update dependencies
- tengo: update to tengo v2
- general: Add Docker Compose configuration (#990)
## Bugfix
- general: Fail with message instead of panic. #988 (#991)
- telegram: Add extra mimetypes to docker image. Fixes #969
- discord: Fix channel ID problem with multiple gateways (discord). Fixes #953 (#977)
- discord: Show file comment in webhook if normal message is empty (discord). Fixes #962 (#995)
- matrix: Fix parsing issues - Disable smartypants in markdown parser. Fixes #989, #983 (#993)
- sshchat: Fix duplicated messages (sshchat). Fixes #950 (#996)
This release couldn't exist without the following contributors:
@jwflory, @42wim, @pbek, @Humorhenker, @c0ncord2, @glazzara
# v1.16.3
## Bugfix

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -u -e -x -o pipefail
go version | grep go1.13 || exit
go version | grep go1.14 || exit
VERSION=$(git describe --tags)
mkdir ci/binaries

View File

@@ -0,0 +1,10 @@
text := import("text")
// if we're not sending to a discord bridge,
// then convert custom emoji tags into url's
if (inProtocol == "discord" && outProtocol != "discord") {
rePNG := text.re_compile(`<:.*?:([0-9]+)>`)
msgText=rePNG.replace(msgText,"https://cdn.discordapp.com/emojis/$1.png")
reGIF := text.re_compile(`<a:.*?:([0-9]+)>`)
msgText=reGIF.replace(msgText,"https://cdn.discordapp.com/emojis/$1.gif")
}

View File

@@ -9,6 +9,7 @@ import (
bkeybase "github.com/42wim/matterbridge/bridge/keybase"
bmatrix "github.com/42wim/matterbridge/bridge/matrix"
bmattermost "github.com/42wim/matterbridge/bridge/mattermost"
bmsteams "github.com/42wim/matterbridge/bridge/msteams"
brocketchat "github.com/42wim/matterbridge/bridge/rocketchat"
bslack "github.com/42wim/matterbridge/bridge/slack"
bsshchat "github.com/42wim/matterbridge/bridge/sshchat"
@@ -37,6 +38,7 @@ var (
"xmpp": bxmpp.New,
"zulip": bzulip.New,
"keybase": bkeybase.New,
"msteams": bmsteams.New,
}
UserTypingSupport = map[string]struct{}{

View File

@@ -10,8 +10,8 @@ import (
"github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/internal"
"github.com/d5/tengo/script"
"github.com/d5/tengo/stdlib"
"github.com/d5/tengo/v2"
"github.com/d5/tengo/v2/stdlib"
lru "github.com/hashicorp/golang-lru"
"github.com/matterbridge/emoji"
"github.com/sirupsen/logrus"
@@ -306,8 +306,6 @@ func (gw *Gateway) ignoreMessage(msg *config.Message) bool {
}
func (gw *Gateway) modifyUsername(msg *config.Message, dest *bridge.Bridge) string {
br := gw.Bridges[msg.Account]
msg.Protocol = br.Protocol
if dest.GetBool("StripNick") {
re := regexp.MustCompile("[^a-zA-Z0-9]+")
msg.Username = re.ReplaceAllString(msg.Username, "")
@@ -315,6 +313,7 @@ func (gw *Gateway) modifyUsername(msg *config.Message, dest *bridge.Bridge) stri
nick := dest.GetString("RemoteNickFormat")
// loop to replace nicks
br := gw.Bridges[msg.Account]
for _, outer := range br.GetStringSlice2D("ReplaceNicks") {
search := outer[0]
replace := outer[1]
@@ -514,7 +513,7 @@ func modifyMessageTengo(filename string, msg *config.Message) error {
if err != nil {
return err
}
s := script.New(res)
s := tengo.NewScript(res)
s.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...))
_ = s.Add("msgText", msg.Text)
_ = s.Add("msgUsername", msg.Username)
@@ -541,7 +540,7 @@ func (gw *Gateway) modifyUsernameTengo(msg *config.Message, br *bridge.Bridge) (
if err != nil {
return "", err
}
s := script.New(res)
s := tengo.NewScript(res)
s.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...))
_ = s.Add("result", "")
_ = s.Add("msgText", msg.Text)
@@ -580,7 +579,7 @@ func (gw *Gateway) modifySendMessageTengo(origmsg *config.Message, msg *config.M
return err
}
}
s := script.New(res)
s := tengo.NewScript(res)
s.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...))
_ = s.Add("inAccount", origmsg.Account)
_ = s.Add("inProtocol", origmsg.Protocol)

View File

@@ -132,6 +132,9 @@ func (r *Router) handleReceive() {
r.handleEventFailure(&msg)
r.handleEventRejoinChannels(&msg)
// Set message protocol based on the account it came from
msg.Protocol = r.getBridge(msg.Account).Protocol
filesHandled := false
for _, gw := range r.Gateways {
// record all the message ID's of the different bridges

43
go.mod
View File

@@ -5,40 +5,38 @@ 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.0.3-0.20191003184814-fc3f792c814c
github.com/bwmarrin/discordgo v0.19.0
// github.com/bwmarrin/discordgo v0.19.0
github.com/d5/tengo v1.24.8
github.com/Rhymen/go-whatsapp v0.1.0
github.com/d5/tengo/v2 v2.0.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
github.com/gomarkdown/markdown v0.0.0-20190912180731-281270bc6d83
github.com/gomarkdown/markdown v0.0.0-20200127000047-1813ea067497
github.com/google/gops v0.3.6
github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4 // indirect
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f // indirect
github.com/gorilla/schema v1.1.0
github.com/gorilla/websocket v1.4.1
github.com/hashicorp/golang-lru v0.5.3
github.com/hpcloud/tail v1.0.0 // indirect
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7
github.com/jtolds/gls v4.2.1+incompatible // indirect
github.com/keybase/go-keybase-chat-bot v0.0.0-20190816161829-561f10822eb2
github.com/labstack/echo/v4 v4.1.10
github.com/jpillora/backoff v1.0.0
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/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/gomatrix v0.0.0-20191026211822-6fc7accd00ca
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
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474 // indirect
github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff // indirect
github.com/nelsonken/gomf v0.0.0-20180504123937-a9dd2f9deae9
github.com/nicksnyder/go-i18n v1.4.0 // indirect
github.com/nlopes/slack v0.6.0
//github.com/nlopes/slack v0.6.0
github.com/onsi/ginkgo v1.6.0 // indirect
github.com/onsi/gomega v1.4.1 // indirect
github.com/paulrosania/go-charset v0.0.0-20190326053356-55c9d7a5834c
@@ -46,26 +44,23 @@ require (
github.com/rs/xid v1.2.1
github.com/russross/blackfriday v1.5.2
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca
github.com/shazow/ssh-chat v1.8.2
github.com/shazow/ssh-chat v1.8.3-0.20200308224626-80ddf1f43a98
github.com/sirupsen/logrus v1.4.2
github.com/smartystreets/assertions v0.0.0-20180803164922-886ec427f6b9 // indirect
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a // indirect
github.com/spf13/viper v1.4.0
github.com/slack-go/slack v0.6.3-0.20200228121756-f56d616d5901
github.com/spf13/viper v1.6.1
github.com/stretchr/testify v1.4.0
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect
github.com/zfjagann/golang-ring v0.0.0-20190304061218-d34796e0a6c2
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 // indirect
golang.org/x/image v0.0.0-20190902063713-cb417be4ba39
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 // indirect
golang.org/x/text v0.3.2 // indirect
github.com/zfjagann/golang-ring v0.0.0-20190106091943-a88bb6aef447
golang.org/x/image v0.0.0-20191214001246-9130b4cfad52
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
gopkg.in/fsnotify.v1 v1.4.7 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
)
replace github.com/nlopes/slack v0.6.0 => github.com/matterbridge/slack v0.1.1-0.20191208194820-95190f11bfb6
//replace github.com/bwmarrin/discordgo v0.20.2 => github.com/matterbridge/discordgo v0.18.1-0.20200109173909-ed873362fa43
replace github.com/bwmarrin/discordgo v0.19.0 => github.com/matterbridge/discordgo v0.0.0-20191026232317-01823f4ebba4
//replace github.com/yaegashi/msgraph.go => github.com/matterbridge/msgraph.go v0.0.0-20191226214848-9e5d9c08a4e1
go 1.13

136
go.sum
View File

@@ -1,4 +1,5 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/42wim/go-gitter v0.0.0-20170828205020-017310c2d557 h1:IZtuWGfzQnKnCSu+vl8WGLhpVQ5Uvy3rlSwqXSg+sQg=
github.com/42wim/go-gitter v0.0.0-20170828205020-017310c2d557/go.mod h1:jL0YSXMs/txjtGJ4PWrmETOk6KUHMDPMshgQZlTeB3Y=
github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f h1:2dk3eOnYllh+wUOuDhOoC2vUVoJF/5z478ryJ+wzEII=
@@ -11,13 +12,12 @@ 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.0.3-0.20191003184814-fc3f792c814c h1:+yAllLxP+WjpuVVE1WNm0/Oigbeob9+liYEyk/v4nj8=
github.com/Rhymen/go-whatsapp v0.0.3-0.20191003184814-fc3f792c814c/go.mod h1:qf/2PQi82Okxw/igghu/oMGzTeUYuKBq1JNo3tdQyNg=
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/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=
github.com/Rhymen/go-whatsapp/examples/sendTextMessages v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:suwzklatySS3Q0+NCxCDh5hYfgXdQUWU1DNcxwAxStM=
github.com/StackExchange/wmi v0.0.0-20170410192909-ea383cf3ba6e h1:IHXQQIpxASe3m0Jtcd3XongL+lxHNd5nUmvHxJARUmg=
github.com/StackExchange/wmi v0.0.0-20170410192909-ea383cf3ba6e/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@@ -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 v1.24.8 h1:PRJ+NWt7ae/9sSbIfThOBTkPSvNV+dwYoBAvwfNgNJY=
github.com/d5/tengo v1.24.8/go.mod h1:VhLq8Q2QFhCIJO3NhvM934qOThykMqJi9y9Siqd1ocQ=
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/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=
@@ -49,35 +49,35 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-telegram-bot-api/telegram-bot-api v4.6.5-0.20181225215658-ec221ba9ea45+incompatible h1:i64CCJcSqkRIkm5OSdZQjZq84/gJsk2zNwHWIRYWlKE=
github.com/go-telegram-bot-api/telegram-bot-api v4.6.5-0.20181225215658-ec221ba9ea45+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
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/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
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/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gomarkdown/markdown v0.0.0-20190912180731-281270bc6d83 h1:w5VNUHB0SP2tr1+boQJWKvnyn3P61UFErZ2e2ih6x0A=
github.com/gomarkdown/markdown v0.0.0-20190912180731-281270bc6d83/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU=
github.com/gomarkdown/markdown v0.0.0-20200127000047-1813ea067497 h1:wJkj+x9gPYlDyM34C6r3SXPs270coWeh85wu1CsusDo=
github.com/gomarkdown/markdown v0.0.0-20200127000047-1813ea067497/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/gops v0.3.6 h1:6akvbMlpZrEYOuoebn2kR+ZJekbZqJ28fJXTs84+8to=
github.com/google/gops v0.3.6/go.mod h1:RZ1rH95wsAGX4vMWKmqBOIWynmWisBf4QFdgT/k/xOI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4 h1:4EZlYQIiyecYJlUbVkFXCXHz1QPhVXcHnQKAzBTPfQo=
github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4/go.mod h1:lEO7XoHJ/xNRBCxrn4h/CEB67h0kW1B0t4ooP2yrjUA=
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f h1:FDM3EtwZLyhW48YRiyqjivNlNZjAObv4xt4NnJaU+NQ=
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY=
github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
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=
@@ -93,15 +93,15 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7 h1:K//n/AqR5HjG3qxbrBCL4vJPW0MVFSs9CPK1OOJdRME=
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro=
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/keybase/go-keybase-chat-bot v0.0.0-20190816161829-561f10822eb2 h1:zacJswvfPqUSGdcBXJzKvLN/dB1UjDGDvDesMBBzoA4=
github.com/keybase/go-keybase-chat-bot v0.0.0-20190816161829-561f10822eb2/go.mod h1:vNc28YFzigVJod0j5EbuTtRIe7swx8vodh2yA4jZ2s8=
github.com/keybase/go-keybase-chat-bot v0.0.0-20200226211841-4e48f3eaef3e h1:KbPTfR/PYuau1IzKoE4lnd8yby5I2pBj+VR6fSVbYU8=
github.com/keybase/go-keybase-chat-bot v0.0.0-20200226211841-4e48f3eaef3e/go.mod h1:vNc28YFzigVJod0j5EbuTtRIe7swx8vodh2yA4jZ2s8=
github.com/keybase/go-ps v0.0.0-20161005175911-668c8856d999 h1:2d+FLQbz4xRTi36DO1qYNUwfORax9XcQ0jhbO81Vago=
github.com/keybase/go-ps v0.0.0-20161005175911-668c8856d999/go.mod h1:hY+WOq6m2FpbvyrI93sMaypsttvaIL5nhVR92dTMUcQ=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
@@ -114,44 +114,47 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/labstack/echo/v4 v4.1.10 h1:/yhIpO50CBInUbE/nHJtGIyhBv0dJe2cDAYxc3V3uMo=
github.com/labstack/echo/v4 v4.1.10/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
github.com/labstack/echo/v4 v4.1.13 h1:JYgKq6NQQSaKbQcsOadAKX1kUVLCUzLGwu8sxN5tC34=
github.com/labstack/echo/v4 v4.1.13/go.mod h1:3WZNypykZ3tnqpF2Qb4fPg27XDunFqgP3HGDmCMgv7U=
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/lrstanley/girc v0.0.0-20190801035559-4fc93959e1a7 h1:BS9tqL0OCiOGuy/CYYk2gc33fxqaqh5/rhqMKu4tcYA=
github.com/lrstanley/girc v0.0.0-20190801035559-4fc93959e1a7/go.mod h1:liX5MxHPrwgHaKowoLkYGwbXfYABh1jbZ6FpElbGF1I=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
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/discordgo v0.0.0-20191026232317-01823f4ebba4 h1:RvH3lC4bEp+bieca+Yh5xPU8tLJgnk7ridiSwMFHrrw=
github.com/matterbridge/discordgo v0.0.0-20191026232317-01823f4ebba4/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVOwN61zSZ0r4Q=
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/gomatrix v0.0.0-20191026211822-6fc7accd00ca h1:3ypqEpFpt6vg5Sv2xxA8/v4WiSOnWMXW7DqxTxpM4XI=
github.com/matterbridge/gomatrix v0.0.0-20191026211822-6fc7accd00ca/go.mod h1:+jWeaaUtXQbBRdKYWfjW6JDDYiI2XXE+3NnTjW5kg8g=
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/slack v0.1.1-0.20191207211853-5b5f1326cdab h1:4eNOoF+qvyXTwmlD5PwEwPHEpYAUTh2KpEjmEZOSVyE=
github.com/matterbridge/slack v0.1.1-0.20191207211853-5b5f1326cdab/go.mod h1:2uCJim0Ct2z1Uj+XQq47KCLLC1b/9UTYaZOvDtbZfK4=
github.com/matterbridge/slack v0.1.1-0.20191208194820-95190f11bfb6 h1:UvXXR9tHYqJUXZVEtiK2qkEWBXfFneicate5kOshVFk=
github.com/matterbridge/slack v0.1.1-0.20191208194820-95190f11bfb6/go.mod h1:2uCJim0Ct2z1Uj+XQq47KCLLC1b/9UTYaZOvDtbZfK4=
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 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/godown v0.0.0-20180312012330-2e9e17e0ea51 h1:MpI7hy3MiCnrggmZI/s8LaPbLVOOWpzDbjA4F+XaXaM=
github.com/mattn/godown v0.0.0-20180312012330-2e9e17e0ea51/go.mod h1:s3KUdOIXJ+jaGM++XHiXA6gikdleaWVATCcQGD4h734=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
@@ -200,21 +203,21 @@ github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxT
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
github.com/shazow/rateio v0.0.0-20150116013248-e8e00881e5c1 h1:Lx3BlDGFElJt4u/zKc9A3BuGYbQAGlEFyPuUA3jeMD0=
github.com/shazow/rateio v0.0.0-20150116013248-e8e00881e5c1/go.mod h1:vt2jWY/3Qw1bIzle5thrJWucsLuuX9iUNnp20CqCciI=
github.com/shazow/ssh-chat v1.8.2 h1:MMso9eWfCnPBelRsusYxKcRBUwHIPEQkR9WrO89II38=
github.com/shazow/ssh-chat v1.8.2/go.mod h1:cXTZK/D1zujEwB0y8DIT1GX8rIKjyLDYeWd+jitPX84=
github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7 h1:80VN+vGkqM773Br/uNNTSheo3KatTgV8IpjIKjvVLng=
github.com/shazow/ssh-chat v1.8.3-0.20200308224626-80ddf1f43a98 h1:sN07ff+PSRsUNhpSod4uGKAQ+Nc0FXsBPG9FmYMNg4w=
github.com/shazow/ssh-chat v1.8.3-0.20200308224626-80ddf1f43a98/go.mod h1:xkTgfD+WP+KR4HuG76oal25BBEeu5kJyi2EOsgiu/4Q=
github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
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/smartystreets/assertions v0.0.0-20180803164922-886ec427f6b9 h1:lXQ+j+KwZcbwrbgU0Rp4Eglg3EJLHbuZU3BbOqAGBmg=
github.com/smartystreets/assertions v0.0.0-20180803164922-886ec427f6b9/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a h1:JSvGDIbmil4Ui/dDdFBExb7/cmkNjyX5F97oglmvCDo=
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
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/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=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
@@ -225,29 +228,31 @@ github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.6.1 h1:VPZzIkznI1YhVMRi6vNFLHSwhnhReBfgTxIPccpfdZk=
github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4=
github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg=
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6 h1:YdYsPAZ2pC6Tow/nPZOPQ96O3hm/ToAkGsPLzedXERk=
github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/zfjagann/golang-ring v0.0.0-20190304061218-d34796e0a6c2 h1:UQwvu7FjUEdVYofx0U6bsc5odNE7wa5TSA0fl559GcA=
github.com/zfjagann/golang-ring v0.0.0-20190304061218-d34796e0a6c2/go.mod h1:0MsIttMJIF/8Y7x0XjonJP7K99t3sR6bjj4m5S4JmqU=
github.com/zfjagann/golang-ring v0.0.0-20190106091943-a88bb6aef447 h1:CHgPZh8bFkZmislPrr/0gd7MciDAX+JJB70A2/5Lvmo=
github.com/zfjagann/golang-ring v0.0.0-20190106091943-a88bb6aef447/go.mod h1:0MsIttMJIF/8Y7x0XjonJP7K99t3sR6bjj4m5S4JmqU=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@@ -259,29 +264,30 @@ golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90te
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/image v0.0.0-20190902063713-cb417be4ba39 h1:4dQcAORh9oYBwVSBVIkP489LUPC+f1HBkTYXgmqfR+o=
golang.org/x/image v0.0.0-20190902063713-cb417be4ba39/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 h1:sKJQZMuxjOAR/Uo2LBfU90onWEf1dF4C+0hPJCc9Mpc=
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/image v0.0.0-20191214001246-9130b4cfad52 h1:2fktqPPvDiVEEVT/vSTeoUPXfmRxRaGy6GU8jypvEn0=
golang.org/x/image v0.0.0-20191214001246-9130b4cfad52/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20171017063910-8dbc5d05d6ed/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -293,9 +299,10 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8 h1:JA8d3MPx/IToSyXZG/RhwYEtfrKO1Fxrqe8KrkiLXKM=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -304,7 +311,10 @@ golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@@ -315,6 +325,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
@@ -322,8 +334,8 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
rsc.io/goversion v1.0.0 h1:/IhXBiai89TyuerPquiZZ39IQkTfAUbZB2awsyYZ/2c=
rsc.io/goversion v1.0.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo=

View File

@@ -12,8 +12,14 @@ text := import("text")
// start - strip irc colors
// if we're not sending to an irc bridge we strip the IRC colors
if inProtocol == "irc" {
if inProtocol == "irc" && outProtocol != "irc" {
re := text.re_compile(`\x03(?:\d{1,2}(?:,\d{1,2})?)?|[[:cntrl:]]`)
msgText=re.replace(msgText,"")
}
// end - strip irc colors
// strip custom emoji
if inProtocol == "discord" {
re := text.re_compile(`<a?(:.*?:)[0-9]+>`)
msgText=re.replace(msgText,"$1")
}

View File

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

View File

@@ -12,7 +12,7 @@
#In this example we use [irc.freenode]
#REQUIRED
[irc.freenode]
#irc server to connect to.
#irc server to connect to.
#REQUIRED
Server="irc.freenode.net:6667"
@@ -20,7 +20,7 @@ Server="irc.freenode.net:6667"
#OPTIONAL (default "")
Password=""
#Enable to use TLS connection to your irc server.
#Enable to use TLS connection to your irc server.
#OPTIONAL (default false)
UseTLS=false
@@ -34,28 +34,28 @@ UseSASL=false
#OPTIONAL (default false)
SkipTLSVerify=true
#If you know your charset, you can specify it manually.
#If you know your charset, you can specify it manually.
#Otherwise it tries to detect this automatically. Select one below
# "iso-8859-2:1987", "iso-8859-9:1989", "866", "latin9", "iso-8859-10:1992", "iso-ir-109", "hebrew",
# "cp932", "iso-8859-15", "cp437", "utf-16be", "iso-8859-3:1988", "windows-1251", "utf16", "latin6",
# "latin3", "iso-8859-1:1987", "iso-8859-9", "utf-16le", "big5", "cp819", "asmo-708", "utf-8",
# "ibm437", "iso-ir-157", "iso-ir-144", "latin4", "850", "iso-8859-5", "iso-8859-5:1988", "l3",
# "windows-31j", "utf8", "iso-8859-3", "437", "greek", "iso-8859-8", "l6", "l9-iso-8859-15",
# "iso-8859-2", "latin2", "iso-ir-100", "iso-8859-6", "arabic", "iso-ir-148", "us-ascii", "x-sjis",
# "utf16be", "iso-8859-8:1988", "utf16le", "l4", "utf-16", "iso-ir-138", "iso-8859-7", "iso-8859-7:1987",
# "windows-1252", "l2", "koi8-r", "iso8859-1", "latin1", "ecma-114", "iso-ir-110", "elot-928",
# "iso-ir-126", "iso-8859-1", "iso-ir-127", "cp850", "cyrillic", "greek8", "windows-1250", "iso-latin-1",
# "l5", "ibm866", "cp866", "ms-kanji", "ibm850", "ecma-118", "iso-ir-101", "ibm819", "l1", "iso-8859-6:1987",
# "iso-8859-2:1987", "iso-8859-9:1989", "866", "latin9", "iso-8859-10:1992", "iso-ir-109", "hebrew",
# "cp932", "iso-8859-15", "cp437", "utf-16be", "iso-8859-3:1988", "windows-1251", "utf16", "latin6",
# "latin3", "iso-8859-1:1987", "iso-8859-9", "utf-16le", "big5", "cp819", "asmo-708", "utf-8",
# "ibm437", "iso-ir-157", "iso-ir-144", "latin4", "850", "iso-8859-5", "iso-8859-5:1988", "l3",
# "windows-31j", "utf8", "iso-8859-3", "437", "greek", "iso-8859-8", "l6", "l9-iso-8859-15",
# "iso-8859-2", "latin2", "iso-ir-100", "iso-8859-6", "arabic", "iso-ir-148", "us-ascii", "x-sjis",
# "utf16be", "iso-8859-8:1988", "utf16le", "l4", "utf-16", "iso-ir-138", "iso-8859-7", "iso-8859-7:1987",
# "windows-1252", "l2", "koi8-r", "iso8859-1", "latin1", "ecma-114", "iso-ir-110", "elot-928",
# "iso-ir-126", "iso-8859-1", "iso-ir-127", "cp850", "cyrillic", "greek8", "windows-1250", "iso-latin-1",
# "l5", "ibm866", "cp866", "ms-kanji", "ibm850", "ecma-118", "iso-ir-101", "ibm819", "l1", "iso-8859-6:1987",
# "latin5", "ascii", "sjis", "iso-8859-10", "iso-8859-4", "iso-8859-4:1988", "shift-jis
# The select charset will be converted to utf-8 when sent to other bridges.
#OPTIONAL (default "")
Charset=""
#Your nick on irc.
#Your nick on irc.
#REQUIRED
Nick="matterbot"
#If you registered your bot with a service like Nickserv on freenode.
#If you registered your bot with a service like Nickserv on freenode.
#Also being used when UseSASL=true
#
#Note: if you want do to quakenet auth, set NickServNick="Q@CServe.quakenet.org"
@@ -74,8 +74,8 @@ NickServUsername="username"
#OPTIONAL (default 1300)
MessageDelay=1300
#Maximum amount of messages to hold in queue. If queue is full
#messages will be dropped.
#Maximum amount of messages to hold in queue. If queue is full
#messages will be dropped.
#<message clipped> will be add to the message that fills the queue.
#OPTIONAL (default 30)
MessageQueue=30
@@ -103,13 +103,13 @@ ColorNicks=false
#OPTIONAL (default empty)
RunCommands=["PRIVMSG user hello","PRIVMSG chanserv something"]
#Nicks you want to ignore.
#Nicks you want to ignore.
#Regular expressions supported
#Messages from those users will not be sent to other bridges.
#OPTIONAL
IgnoreNicks="ircspammer1 ircspammer2"
#Messages you want to ignore.
#Messages you want to ignore.
#Messages matching these regexp will be ignored and not sent to other bridges
#See https://regex-golang.appspot.com/assets/html/index.html for more regex info
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
@@ -147,12 +147,12 @@ ExtractNicks=[ ["otherbot","<(.*?)>\\s+" ] ]
#optional (default empty)
Label=""
#RemoteNickFormat defines how remote users appear on this bridge
#RemoteNickFormat defines how remote users appear on this bridge
#See [general] config section for default options
#The string "{NOPINGNICK}" (case sensitive) will be replaced by the actual nick / username, but with a ZWSP inside the nick, so the irc user with the same nick won't get pinged. See https://github.com/42wim/matterbridge/issues/175 for more information
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges
#Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
#OPTIONAL (default false)
ShowJoinPart=false
@@ -172,7 +172,7 @@ NoSendJoinPart=false
#OPTIONAL (default false)
StripNick=false
#Enable to show topic changes from other bridges
#Enable to show topic changes from other bridges
#Only works hiding/show topic changes from slack bridge for now
#OPTIONAL (default false)
ShowTopicChange=false
@@ -186,7 +186,7 @@ ShowTopicChange=false
#In this example we use [xmpp.jabber]
#REQUIRED
[xmpp.jabber]
#xmpp server to connect to.
#xmpp server to connect to.
#REQUIRED
Server="jabber.example.com:5222"
@@ -214,13 +214,13 @@ SkipTLSVerify=true
## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file
#Nicks you want to ignore.
#Nicks you want to ignore.
#Regular expressions supported
#Messages from those users will not be sent to other bridges.
#OPTIONAL
IgnoreNicks="ircspammer1 ircspammer2"
#Messages you want to ignore.
#Messages you want to ignore.
#Messages matching these regexp will be ignored and not sent to other bridges
#See https://regex-golang.appspot.com/assets/html/index.html for more regex info
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
@@ -258,11 +258,11 @@ ExtractNicks=[ ["otherbot","<(.*?)>\\s+" ] ]
#optional (default empty)
Label=""
#RemoteNickFormat defines how remote users appear on this bridge
#RemoteNickFormat defines how remote users appear on this bridge
#See [general] config section for default options
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges
#Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
#OPTIONAL (default false)
ShowJoinPart=false
@@ -272,7 +272,7 @@ ShowJoinPart=false
#OPTIONAL (default false)
StripNick=false
#Enable to show topic changes from other bridges
#Enable to show topic changes from other bridges
#Only works hiding/show topic changes from slack bridge for now
#OPTIONAL (default false)
ShowTopicChange=false
@@ -288,14 +288,14 @@ ShowTopicChange=false
[mattermost.work]
#The mattermost hostname. (do not prefix it with http or https)
#REQUIRED (when not using webhooks)
Server="yourmattermostserver.domain"
Server="yourmattermostserver.domain"
#Your team on mattermost.
#Your team on mattermost.
#REQUIRED (when not using webhooks)
Team="yourteam"
#login/pass of your bot.
#Use a dedicated user for this and not your own!
#login/pass of your bot.
#Use a dedicated user for this and not your own!
#REQUIRED (when not using webhooks)
Login="yourlogin"
Password="yourpass"
@@ -305,16 +305,16 @@ Password="yourpass"
#OPTIONAL (you can use token instead of login/password)
#Token="abcdefghijklm"
#Enable this to make a http connection (instead of https) to your mattermost.
#Enable this to make a http connection (instead of https) to your mattermost.
#OPTIONAL (default false)
NoTLS=false
#### Settings for webhook matterbridge.
#NOT RECOMMENDED TO USE INCOMING/OUTGOING WEBHOOK. USE DEDICATED BOT USER WHEN POSSIBLE!
#You don't need to configure this, if you have configured the settings
#You don't need to configure this, if you have configured the settings
#above.
#Url is your incoming webhook url as specified in mattermost.
#Url is your incoming webhook url as specified in mattermost.
#See account settings - integrations - incoming webhooks on mattermost.
#If specified, messages will be sent to mattermost using this URL
#OPTIONAL
@@ -324,17 +324,17 @@ WebhookURL="https://yourdomain/hooks/yourhookkey"
#See account settings - integrations - outgoing webhooks on mattermost.
#If specified, messages will be received from mattermost on this ip:port
#(this will only work if WebhookURL above is also configured)
#OPTIONAL
#OPTIONAL
WebhookBindAddress="0.0.0.0:9999"
#Icon that will be showed in mattermost.
#Icon that will be showed in mattermost.
#This only works when WebhookURL is configured
#OPTIONAL
IconURL="http://youricon.png"
#### End settings for webhook matterbridge.
#Enable to not verify the certificate on your mattermost server.
#Enable to not verify the certificate on your mattermost server.
#e.g. when using selfsigned certificates
#OPTIONAL (default false)
SkipTLSVerify=true
@@ -342,11 +342,11 @@ SkipTLSVerify=true
## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file
#how to format the list of IRC nicks when displayed in mattermost.
#how to format the list of IRC nicks when displayed in mattermost.
#Possible options are "table" and "plain"
#OPTIONAL (default plain)
NickFormatter="plain"
#How many nicks to list per row for formatters that support this.
#How many nicks to list per row for formatters that support this.
#OPTIONAL (default 4)
NicksPerRow=4
@@ -356,11 +356,11 @@ NicksPerRow=4
#OPTIONAL (default false)
SkipVersionCheck=false
#Whether to prefix messages from other bridges to mattermost with the sender's nick.
#Useful if username overrides for incoming webhooks isn't enabled on the
#mattermost server. If you set PrefixMessagesWithNick to true, each message
#from bridge to Mattermost will by default be prefixed by "bridge-" + nick. You can,
#however, modify how the messages appear, by setting (and modifying) RemoteNickFormat
#Whether to prefix messages from other bridges to mattermost with the sender's nick.
#Useful if username overrides for incoming webhooks isn't enabled on the
#mattermost server. If you set PrefixMessagesWithNick to true, each message
#from bridge to Mattermost will by default be prefixed by "bridge-" + nick. You can,
#however, modify how the messages appear, by setting (and modifying) RemoteNickFormat
#OPTIONAL (default false)
PrefixMessagesWithNick=false
@@ -372,13 +372,13 @@ EditDisable=false
#OPTIONAL (default empty)
EditSuffix=" (edited)"
#Nicks you want to ignore.
#Nicks you want to ignore.
#Regular expressions supported
#Messages from those users will not be sent to other bridges.
#OPTIONAL
IgnoreNicks="ircspammer1 ircspammer2"
#Messages you want to ignore.
#Messages you want to ignore.
#Messages matching these regexp will be ignored and not sent to other bridges
#See https://regex-golang.appspot.com/assets/html/index.html for more regex info
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
@@ -416,11 +416,11 @@ ExtractNicks=[ ["otherbot","<(.*?)>\\s+" ] ]
#optional (default empty)
Label=""
#RemoteNickFormat defines how remote users appear on this bridge
#RemoteNickFormat defines how remote users appear on this bridge
#See [general] config section for default options
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges
#Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
#OPTIONAL (default false)
ShowJoinPart=false
@@ -435,7 +435,7 @@ NoSendJoinPart=false
#OPTIONAL (default false)
StripNick=false
#Enable to show topic changes from other bridges
#Enable to show topic changes from other bridges
#Only works hiding/show topic changes from slack bridge for now
#OPTIONAL (default false)
ShowTopicChange=false
@@ -459,13 +459,13 @@ Token="Yourtokenhere"
## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file
#Nicks you want to ignore.
#Nicks you want to ignore.
#Regular expressions supported
#Messages from those users will not be sent to other bridges.
#OPTIONAL
IgnoreNicks="ircspammer1 ircspammer2"
#Messages you want to ignore.
#Messages you want to ignore.
#Messages matching these regexp will be ignored and not sent to other bridges
#See https://regex-golang.appspot.com/assets/html/index.html for more regex info
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
@@ -503,11 +503,11 @@ ExtractNicks=[ ["otherbot","<(.*?)>\\s+" ] ]
#optional (default empty)
Label=""
#RemoteNickFormat defines how remote users appear on this bridge
#RemoteNickFormat defines how remote users appear on this bridge
#See [general] config section for default options
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges
#Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
#OPTIONAL (default false)
ShowJoinPart=false
@@ -517,7 +517,7 @@ ShowJoinPart=false
#OPTIONAL (default false)
StripNick=false
#Enable to show topic changes from other bridges
#Enable to show topic changes from other bridges
#Only works hiding/show topic changes from slack bridge for now
#OPTIONAL (default false)
ShowTopicChange=false
@@ -532,7 +532,7 @@ ShowTopicChange=false
[keybase.myteam]
# RemoteNickFormat defines how remote users appear on this bridge
# RemoteNickFormat defines how remote users appear on this bridge
# See [general] config section for default options
RemoteNickFormat="{NICK} ({PROTOCOL}): "
@@ -596,7 +596,7 @@ IconURL="https://robohash.org/{NICK}.png?size=48x48"
#Possible options are "table" and "plain"
#OPTIONAL (default plain)
NickFormatter="plain"
#How many nicks to list per row for formatters that support this.
#How many nicks to list per row for formatters that support this.
#OPTIONAL (default 4)
NicksPerRow=4
@@ -609,20 +609,20 @@ EditDisable=true
EditSuffix=" (edited)"
#Whether to prefix messages from other bridges to mattermost with RemoteNickFormat
#Useful if username overrides for incoming webhooks isn't enabled on the
#slack server. If you set PrefixMessagesWithNick to true, each message
#from bridge to Slack will by default be prefixed by "bridge-" + nick. You can,
#however, modify how the messages appear, by setting (and modifying) RemoteNickFormat
#Useful if username overrides for incoming webhooks isn't enabled on the
#slack server. If you set PrefixMessagesWithNick to true, each message
#from bridge to Slack will by default be prefixed by "bridge-" + nick. You can,
#however, modify how the messages appear, by setting (and modifying) RemoteNickFormat
#OPTIONAL (default false)
PrefixMessagesWithNick=false
#Nicks you want to ignore.
#Nicks you want to ignore.
#Regular expressions supported
#Messages from those users will not be sent to other bridges.
#OPTIONAL
IgnoreNicks="ircspammer1 ircspammer2"
#Messages you want to ignore.
#Messages you want to ignore.
#Messages matching these regexp will be ignored and not sent to other bridges
#See https://regex-golang.appspot.com/assets/html/index.html for more regex info
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
@@ -660,11 +660,11 @@ ExtractNicks=[ ["otherbot","<(.*?)>\\s+" ] ]
#optional (default empty)
Label=""
#RemoteNickFormat defines how remote users appear on this bridge
#RemoteNickFormat defines how remote users appear on this bridge
#See [general] config section for default options
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges
#Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
#OPTIONAL (default false)
ShowJoinPart=false
@@ -679,7 +679,7 @@ NoSendJoinPart=false
#OPTIONAL (default false)
StripNick=false
#Enable to show topic changes from other bridges
#Enable to show topic changes from other bridges
#Only works hiding/show topic changes from slack bridge for now
#OPTIONAL (default false)
ShowTopicChange=false
@@ -703,114 +703,125 @@ ShowUserTyping=false
###################################################################
[discord]
#You can configure multiple servers "[discord.name]" or "[discord.name2]"
#In this example we use [discord.game]
# You can configure multiple servers "[discord.name]" or "[discord.name2]"
# In this example we use [discord.game]
#REQUIRED
[discord.game]
#Token to connect with Discord API
#You can get your token by following the instructions on
#https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token
#If you want roles/groups mentions to be shown with names instead of ID, you'll need to give your bot the "Manage Roles" permission.
#REQUIRED
# Token (REQUIRED) is the token to connect with Discord API
# You can get your token by following the instructions on
# https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token
# If you want roles/groups mentions to be shown with names instead of ID, you'll need to give your bot the "Manage Roles" permission.
Token="Yourtokenhere"
#REQUIRED
# Server (REQUIRED) is the ID or name of the guild to connect to, selected from the guilds the bot has been invited to
Server="yourservername"
## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file
## All settings below can be reloaded by editing the file.
## They are also all optional.
#Shows title, description and URL of embedded messages (sent by other bots)
#OPTIONAL (default false)
# ShowEmbeds shows the title, description and URL of embedded messages (sent by other bots)
ShowEmbeds=false
#Shows the username instead of the server nickname
#OPTIONAL (default false)
# UseLocalAvatar specifies source bridges for which an avatar should be 'guessed' when an incoming message has no avatar.
# This works by comparing the username of the message to an existing Discord user, and using the avatar of the Discord user.
#
# This only works if WebhookURL is set (AND the message has no avatar).
# Example: ["irc"]
UseLocalAvatar=[]
# UseUserName shows the username instead of the server nickname
UseUserName=false
#Show #xxxx discriminator with UseUserName
#OPTIONAL (default false)
# UseDiscriminator appends the `#xxxx` discriminator when used with UseUserName
UseDiscriminator=false
#Specify WebhookURL. If given, will relay messages using the Webhook, which gives a better look to messages.
#This only works if you have one discord channel, if you have multiple discord channels you'll have to specify it in the gateway config
#OPTIONAL (default empty)
WebhookURL="Yourwebhooktokenhere"
# WebhookURL sends messages in the style of puppets.
# This only works if you have one discord channel, if you have multiple discord channels you'll have to specify it in the gateway config
# Example: "https://discordapp.com/api/webhooks/1234/abcd_xyzw"
WebhookURL=""
#Disable sending of edits to other bridges
#OPTIONAL (default false)
# EditDisable disables sending of edits to other bridges
EditDisable=false
#Message to be appended to every edited message
#OPTIONAL (default empty)
EditSuffix=" (edited)"
# EditSuffix specifies the message to be appended to every edited message
# Example: " (edited)"
EditSuffix=""
#Nicks you want to ignore.
#Regular expressions supported
#Messages from those users will not be sent to other bridges.
#OPTIONAL
IgnoreNicks="ircspammer1 ircspammer2"
# IgnoreNicks mutes outgoing messages from certain users.
# Messages from these users will not be transmitted to other bridges.
# Regular expressions are also supported.
# Example: "ircspammer1 ircspammer2"
IgnoreNicks=""
#Messages you want to ignore.
#Messages matching these regexp will be ignored and not sent to other bridges
#See https://regex-golang.appspot.com/assets/html/index.html for more regex info
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
IgnoreMessages="^~~ badword"
# IgnoreMessages mutes outgoing messages of a certain format.
# Messages matching this regular expression will not be transmitted sent to other bridges
# See https://regex-golang.appspot.com/assets/html/index.html for more regex info
#
# Example that ignores messages starting with ~~ or messages containing badword:
# IgnoreMessages="^~~ badword"
IgnoreMessages=""
#messages you want to replace.
#it replaces outgoing messages from the bridge.
#so you need to place it by the sending bridge definition.
#regular expressions supported
#some examples:
#this replaces cat => dog and sleep => awake
#replacemessages=[ ["cat","dog"], ["sleep","awake"] ]
#this replaces every number with number. 123 => numbernumbernumber
#replacemessages=[ ["[0-9]","number"] ]
#optional (default empty)
ReplaceMessages=[ ["cat","dog"] ]
# ReplaceMessages replaces substrings of messages in outgoing messages.
# Regular expressions are supported.
#
# Example that replaces 'cat' => 'dog' and 'sleep' => 'awake':
# ReplaceMessages=[ ["cat","dog"], ["sleep","awake"] ]
# Example that replaces all digits with the letter 'X', so 'hello123' becomes 'helloXXX':
# ReplaceMessages=[ ["[0-9]","X"] ]
ReplaceMessages=[]
#nicks you want to replace.
#see replacemessages for syntaxa
#optional (default empty)
ReplaceNicks=[ ["user--","user"] ]
# ReplaceNicks replaces substrings of usernames in outgoing messages.
# See the ReplaceMessages setting for examples.
# Example: [ ["user--","user"] ]
ReplaceNicks=[]
#Extractnicks is used to for example rewrite messages from other relaybots
#See https://github.com/42wim/matterbridge/issues/713 and https://github.com/42wim/matterbridge/issues/466
#some examples:
#this replaces a message like "Relaybot: <relayeduser> something interesting" to "relayeduser: something interesting"
#ExtractNicks=[ [ "Relaybot", "<(.*?)>\\s+" ] ]
#you can use multiple entries for multiplebots
#this also replaces a message like "otherbot: (relayeduser) something else" to "relayeduser: something else"
#ExtractNicks=[ [ "Relaybot", "<(.*?)>\\s+" ],[ "otherbot","\\((.*?)\\)\\s+" ]
#OPTIONAL (default empty)
ExtractNicks=[ ["otherbot","<(.*?)>\\s+" ] ]
# ExtractNicks allows for interoperability with other bridge software by rewriting messages and extracting usernames.
#
# Recommended reading:
# - https://github.com/42wim/matterbridge/issues/466
# - https://github.com/42wim/matterbridge/issues/713
#
# This example translates the following message
# "Relaybot: <relayeduser> something interesting"
# into this message
# "relayeduser: something interesting"
# like so:
# ExtractNicks=[ [ "Relaybot", "<(.*?)>\\s+" ] ]
#
# This example translates the following message
# "otherbot: (relayeduser) something else"
# into this message
# "relayeduser: something else"
# like so:
# ExtractNicks=[ [ "otherbot","\\((.*?)\\)\\s+" ] ]
#
# This example combines both of the above examples into one:
# ExtractNicks=[ [ "Relaybot", "<(.*?)>\\s+" ],[ "otherbot","\\((.*?)\\)\\s+" ]
#
ExtractNicks=[]
#extra label that can be used in the RemoteNickFormat
#optional (default empty)
# Label is as an extra identifier for use in the RemoteNickFormat setting.
Label=""
#RemoteNickFormat defines how remote users appear on this bridge
#See [general] config section for default options
# RemoteNickFormat formats how remote users appear on this bridge.
# See the [general] config section for default options
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
#OPTIONAL (default false)
# ShowJoinPart emits messages that show joins/parts from other bridges
# Supported from the following bridges: irc, mattermost, slack, discord
ShowJoinPart=false
#StripNick only allows alphanumerical nicks. See https://github.com/42wim/matterbridge/issues/285
#It will strip other characters from the nick
#OPTIONAL (default false)
# StripNick strips non-alphanumeric characters from nicknames.
# Recommended reading: https://github.com/42wim/matterbridge/issues/285
StripNick=false
#Enable to show topic/purpose changes from other bridges
#Only works hiding/show topic changes from slack bridge for now
#OPTIONAL (default false)
# ShowTopicChange emits messages that show topic/purpose updates from other bridges
# Supported from the following bridges: slack
ShowTopicChange=false
#Enable to sync topic/purpose changes from other bridges
#Only works syncing topic changes from slack bridge for now
#OPTIONAL (default false)
# SyncTopic synchronises topic/purpose updates from other bridges
# Supported from the following bridges: slack
SyncTopic=false
###################################################################
@@ -831,14 +842,19 @@ Token="Yourtokenhere"
## Settings below can be reloaded by editing the file
#OPTIONAL (default empty)
#Supported formats are "HTML", "Markdown" and "HTMLNick"
#See https://core.telegram.org/bots/api#html-style
#See https://core.telegram.org/bots/api#markdown-style
#HTMLNick only allows HTML for the nick, the message itself will be html-escaped
#Supported formats are:
#"HTML" https://core.telegram.org/bots/api#html-style
#"Markdown" https://core.telegram.org/bots/api#markdown-style - deprecated, doesn't display links with underscores correctly
#"MarkdownV2" https://core.telegram.org/bots/api#markdownv2-style
#"HTMLNick" - only allows HTML for the nick, the message itself will be html-escaped
MessageFormat=""
#OPTIONAL (default false)
#Disables link previews for links in messages
DisableWebPagePreview=false
#If enabled use the "First Name" as username. If this is empty use the Username
#If disabled use the "Username" as username. If this is empty use the First Name
#If disabled use the "Username" as username. If this is empty use the First Name
#If all names are empty, username will be "unknown"
#OPTIONAL (default false)
UseFirstName=false
@@ -853,6 +869,10 @@ UseInsecureURL=false
#OPTIONAL (default false)
QuoteDisable=false
#Set the max. quoted length if 0 the whole message will be quoted
#OPTIONAL (default 0)
QuoteLengthLimit=0
#Format quoted/reply messages
#OPTIONAL (default "{MESSAGE} (re @{QUOTENICK}: {QUOTEMESSAGE})")
QuoteFormat="{MESSAGE} (re @{QUOTENICK}: {QUOTEMESSAGE})"
@@ -870,13 +890,13 @@ EditDisable=false
#OPTIONAL (default empty)
EditSuffix=" (edited)"
#Nicks you want to ignore.
#Nicks you want to ignore.
#Regular expressions supported
#Messages from those users will not be sent to other bridges.
#OPTIONAL
IgnoreNicks="spammer1 spammer2"
#Messages you want to ignore.
#Messages you want to ignore.
#Messages matching these regexp will be ignored and not sent to other bridges
#See https://regex-golang.appspot.com/assets/html/index.html for more regex info
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
@@ -914,7 +934,7 @@ ExtractNicks=[ ["otherbot","<(.*?)>\\s+" ] ]
#optional (default empty)
Label=""
#RemoteNickFormat defines how remote users appear on this bridge
#RemoteNickFormat defines how remote users appear on this bridge
#See [general] config section for default options
#
#WARNING: if you have set MessageFormat="HTML" be sure that this format matches the guidelines
@@ -922,7 +942,7 @@ Label=""
#telegram! eg <{NICK}> should be &lt;{NICK}&gt;
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges
#Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
#OPTIONAL (default false)
ShowJoinPart=false
@@ -932,7 +952,7 @@ ShowJoinPart=false
#OPTIONAL (default false)
StripNick=false
#Enable to show topic changes from other bridges
#Enable to show topic changes from other bridges
#Only works hiding/show topic changes from slack bridge for now
#OPTIONAL (default false)
ShowTopicChange=false
@@ -950,9 +970,9 @@ ShowTopicChange=false
#REQUIRED (when not using webhooks)
Server="https://yourrocketchatserver.domain.com:443"
#login/pass of your bot.
#login/pass of your bot.
#login needs to be the login with email address! user@domain.com
#Use a dedicated user for this and not your own!
#Use a dedicated user for this and not your own!
#REQUIRED (when not using webhooks)
Login="yourlogin@domain.com"
Password="yourpass"
@@ -964,7 +984,7 @@ Password="yourpass"
#### Settings for webhook matterbridge.
#USE DEDICATED BOT USER WHEN POSSIBLE! This allows you to use advanced features like message editing/deleting and uploads
#You don't need to configure this, if you have configured the settings
#You don't need to configure this, if you have configured the settings
#above.
#Url is your incoming webhook url as specified in rocketchat
@@ -975,7 +995,7 @@ WebhookURL="https://yourdomain/hooks/yourhookkey"
#Address to listen on for outgoing webhook requests from rocketchat.
#See administration - integrations - new integration - outgoing webhook
#REQUIRED
#REQUIRED
WebhookBindAddress="0.0.0.0:9999"
#Your nick/username as specified in your incoming webhook "Post as" setting
@@ -986,7 +1006,7 @@ Nick="matterbot"
#OPTIONAL (default false)
NoTLS=false
#Enable to not verify the certificate on your rocketchat server.
#Enable to not verify the certificate on your rocketchat server.
#e.g. when using selfsigned certificates
#OPTIONAL (default false)
SkipTLSVerify=true
@@ -996,22 +1016,22 @@ SkipTLSVerify=true
## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file
#Whether to prefix messages from other bridges to rocketchat with the sender's nick.
#Useful if username overrides for incoming webhooks isn't enabled on the
#rocketchat server. If you set PrefixMessagesWithNick to true, each message
#Whether to prefix messages from other bridges to rocketchat with the sender's nick.
#Useful if username overrides for incoming webhooks isn't enabled on the
#rocketchat server. If you set PrefixMessagesWithNick to true, each message
#from bridge to rocketchat will by default be prefixed by the RemoteNickFormat setting. i
#if you're using login/pass you can better enable because of this bug:
#https://github.com/RocketChat/Rocket.Chat/issues/7549
#OPTIONAL (default false)
PrefixMessagesWithNick=false
#Nicks you want to ignore.
#Nicks you want to ignore.
#Regular expressions supported
#Messages from those users will not be sent to other bridges.
#OPTIONAL
IgnoreNicks="ircspammer1 ircspammer2"
#Messages you want to ignore.
#Messages you want to ignore.
#Messages matching these regexp will be ignored and not sent to other bridges
#See https://regex-golang.appspot.com/assets/html/index.html for more regex info
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
@@ -1049,11 +1069,11 @@ ExtractNicks=[ ["otherbot","<(.*?)>\\s+" ] ]
#optional (default empty)
Label=""
#RemoteNickFormat defines how remote users appear on this bridge
#RemoteNickFormat defines how remote users appear on this bridge
#See [general] config section for default options
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges
#Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
#OPTIONAL (default false)
ShowJoinPart=false
@@ -1063,7 +1083,7 @@ ShowJoinPart=false
#OPTIONAL (default false)
StripNick=false
#Enable to show topic changes from other bridges
#Enable to show topic changes from other bridges
#Only works hiding/show topic changes from slack bridge for now
#OPTIONAL (default false)
ShowTopicChange=false
@@ -1081,10 +1101,10 @@ ShowTopicChange=false
#REQUIRED
Server="https://matrix.org"
#login/pass of your bot.
#Use a dedicated user for this and not your own!
#login/pass of your bot.
#Use a dedicated user for this and not your own!
#Messages sent from this user will not be relayed to avoid loops.
#REQUIRED
#REQUIRED
Login="yourlogin"
Password="yourpass"
@@ -1096,20 +1116,20 @@ NoHomeServerSuffix=false
## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file
#Whether to prefix messages from other bridges to matrix with the sender's nick.
#Useful if username overrides for incoming webhooks isn't enabled on the
#matrix server. If you set PrefixMessagesWithNick to true, each message
#Whether to prefix messages from other bridges to matrix with the sender's nick.
#Useful if username overrides for incoming webhooks isn't enabled on the
#matrix server. If you set PrefixMessagesWithNick to true, each message
#from bridge to matrix will by default be prefixed by the RemoteNickFormat setting. i
#OPTIONAL (default false)
PrefixMessagesWithNick=false
#Nicks you want to ignore.
#Nicks you want to ignore.
#Regular expressions supported
#Messages from those users will not be sent to other bridges.
#OPTIONAL
IgnoreNicks="spammer1 spammer2"
#Messages you want to ignore.
#Messages you want to ignore.
#Messages matching these regexp will be ignored and not sent to other bridges
#See https://regex-golang.appspot.com/assets/html/index.html for more regex info
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
@@ -1147,11 +1167,11 @@ ExtractNicks=[ ["otherbot","<(.*?)>\\s+" ] ]
#optional (default empty)
Label=""
#RemoteNickFormat defines how remote users appear on this bridge
#RemoteNickFormat defines how remote users appear on this bridge
#See [general] config section for default options
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges
#Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
#OPTIONAL (default false)
ShowJoinPart=false
@@ -1161,7 +1181,7 @@ ShowJoinPart=false
#OPTIONAL (default false)
StripNick=false
#Enable to show topic changes from other bridges
#Enable to show topic changes from other bridges
#Only works hiding/show topic changes from slack bridge for now
#OPTIONAL (default false)
ShowTopicChange=false
@@ -1175,33 +1195,33 @@ ShowTopicChange=false
#REQUIRED
[steam.gamechat]
#login/pass of your bot.
#Use a dedicated user for this and not your own account!
#REQUIRED
#login/pass of your bot.
#Use a dedicated user for this and not your own account!
#REQUIRED
Login="yourlogin"
Password="yourpass"
#steamguard mail authcode (not the 2FA code)
#OPTIONAL
#OPTIONAL
Authcode="ABCE12"
## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file
#Whether to prefix messages from other bridges to matrix with the sender's nick.
#Useful if username overrides for incoming webhooks isn't enabled on the
#matrix server. If you set PrefixMessagesWithNick to true, each message
#Whether to prefix messages from other bridges to matrix with the sender's nick.
#Useful if username overrides for incoming webhooks isn't enabled on the
#matrix server. If you set PrefixMessagesWithNick to true, each message
#from bridge to matrix will by default be prefixed by the RemoteNickFormat setting. i
#OPTIONAL (default false)
PrefixMessagesWithNick=false
#Nicks you want to ignore.
#Nicks you want to ignore.
#Regular expressions supported
#Messages from those users will not be sent to other bridges.
#OPTIONAL
IgnoreNicks="spammer1 spammer2"
#Messages you want to ignore.
#Messages you want to ignore.
#Messages matching these regexp will be ignored and not sent to other bridges
#See https://regex-golang.appspot.com/assets/html/index.html for more regex info
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
@@ -1239,11 +1259,11 @@ ExtractNicks=[ ["otherbot","<(.*?)>\\s+" ] ]
#optional (default empty)
Label=""
#RemoteNickFormat defines how remote users appear on this bridge
#RemoteNickFormat defines how remote users appear on this bridge
#See [general] config section for default options
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges
#Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
#OPTIONAL (default false)
ShowJoinPart=false
@@ -1253,7 +1273,7 @@ ShowJoinPart=false
#OPTIONAL (default false)
StripNick=false
#Enable to show topic changes from other bridges
#Enable to show topic changes from other bridges
#Only works hiding/show topic changes from slack bridge for now
#OPTIONAL (default false)
ShowTopicChange=false
@@ -1306,25 +1326,25 @@ Label="Organization"
#REQUIRED
Token="Yourtokenhere"
#Username of the bot, normally called yourbot-bot@yourserver.zulipchat.com
#See username in Settings - Your bots
#Username of the bot, normally called yourbot-bot@yourserver.zulipchat.com
#See username in Settings - Your bots
#REQUIRED
Login="yourbot-bot@yourserver.zulipchat.com"
#Servername of your zulip instance
#REQUIRED
#REQUIRED
Server="https://yourserver.zulipchat.com"
## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file
#Nicks you want to ignore.
#Nicks you want to ignore.
#Regular expressions supported
#Messages from those users will not be sent to other bridges.
#OPTIONAL
IgnoreNicks="spammer1 spammer2"
#Messages you want to ignore.
#Messages you want to ignore.
#Messages matching these regexp will be ignored and not sent to other bridges
#See https://regex-golang.appspot.com/assets/html/index.html for more regex info
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
@@ -1362,11 +1382,11 @@ ExtractNicks=[ ["otherbot","<(.*?)>\\s+" ] ]
#optional (default empty)
Label=""
#RemoteNickFormat defines how remote users appear on this bridge
#RemoteNickFormat defines how remote users appear on this bridge
#See [general] config section for default options
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges
#Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
#OPTIONAL (default false)
ShowJoinPart=false
@@ -1376,7 +1396,7 @@ ShowJoinPart=false
#OPTIONAL (default false)
StripNick=false
#Enable to show topic changes from other bridges
#Enable to show topic changes from other bridges
#Only works hiding/show topic changes from slack bridge for now
#OPTIONAL (default false)
ShowTopicChange=false
@@ -1391,7 +1411,7 @@ ShowTopicChange=false
[api.local]
#Address to listen on for API
#REQUIRED
#REQUIRED
BindAddress="127.0.0.1:4242"
#Amount of messages to keep in memory
@@ -1407,7 +1427,7 @@ Token="mytoken"
#optional (default empty)
Label=""
#RemoteNickFormat defines how remote users appear on this bridge
#RemoteNickFormat defines how remote users appear on this bridge
#See [general] config section for default options
RemoteNickFormat="{NICK}"
@@ -1422,7 +1442,7 @@ RemoteNickFormat="{NICK}"
## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file
#RemoteNickFormat defines how remote users appear on this bridge
#RemoteNickFormat defines how remote users appear on this bridge
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
#The string "{LABEL}" (case sensitive) will be replaced by label= field of the sending bridge
@@ -1550,7 +1570,7 @@ RemoteNickFormat="remotenickformat.tengo"
#[[gateway.out]] specifies the account and channels we will send the messages
#from [[gateway.in]] to.
#
#Most of the time [[gateway.in]] and [[gateway.out]] are the same if you
#Most of the time [[gateway.in]] and [[gateway.out]] are the same if you
#want bidirectional bridging. You can then use [[gateway.inout]]
#
@@ -1595,7 +1615,7 @@ enable=true
# - "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 #)
#
#
# REQUIRED
channel="#testing"
@@ -1615,7 +1635,7 @@ enable=true
#OPTIONAL - your irc / xmpp channel key
key="yourkey"
#[[gateway.inout]] can be used when then channel will be used to receive from
#[[gateway.inout]] can be used when then channel will be used to receive from
#and send messages to
[[gateway.inout]]
account="mattermost.work"

View File

@@ -14,7 +14,7 @@ import (
"time"
"github.com/gorilla/schema"
"github.com/nlopes/slack"
"github.com/slack-go/slack"
)
// OMessage for mattermost incoming webhook. (send to mattermost)

View File

@@ -23,7 +23,7 @@ qrChan := make(chan string)
go func() {
fmt.Printf("qr code: %v\n", <-qrChan)
//show qr code or save it somewhere to scan
}
}()
sess, err := wac.Login(qrChan)
```
The authentication process requires you to scan the qr code, that is send through the channel, with the device you are using whatsapp on. The session struct that is returned can be saved and used to restore the login without scanning the qr code again. The qr code has a ttl of 20 seconds and the login function throws a timeout err if the time has passed or any other request fails.
@@ -66,6 +66,10 @@ func (myHandler) HandleJsonMessage(message string) {
fmt.Println(message)
}
func (myHandler) HandleContactMessage(message whatsapp.ContactMessage) {
fmt.Println(message)
}
wac.AddHandler(myHandler{})
```
The message handlers are all optional, you don't need to implement anything but the error handler to implement the interface. The ImageMessage, VideoMessage, AudioMessage and DocumentMessage provide a Download function to get the media data.
@@ -81,6 +85,21 @@ text := whatsapp.TextMessage{
err := wac.Send(text)
```
### Sending Contact Messages
```go
contactMessage := whatsapp.ContactMessage{
Info: whatsapp.MessageInfo{
RemoteJid: "0123456789@s.whatsapp.net",
},
DisplayName: "Luke Skylwallker",
Vcard: "BEGIN:VCARD\nVERSION:3.0\nN:Skyllwalker;Luke;;\nFN:Luke Skywallker\nitem1.TEL;waid=0123456789:+1 23 456789789\nitem1.X-ABLabel:Mobile\nEND:VCARD",
}
id, error := client.WaConn.Send(contactMessage)
```
The message will be send over the websocket. The attributes seen above are the required ones. All other relevant attributes (id, timestamp, fromMe, status) are set if they are missing in the struct. For the time being we only support text messages, but other types are planned for the near future.
## Legal

View File

@@ -66,9 +66,12 @@ func Unmarshal(data []byte) (*Node, error) {
}
if n != nil && n.Attributes != nil && n.Content != nil {
n.Content, err = unmarshalMessageArray(n.Content.([]Node))
if err != nil {
return nil, err
nContent, ok := n.Content.([]Node)
if ok {
n.Content, err = unmarshalMessageArray(nContent)
if err != nil {
return nil, err
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@ package proto;
message HydratedQuickReplyButton {
optional string displayText = 1;
optional string buttonId = 2;
optional string id = 2;
}
message HydratedURLButton {
@@ -17,6 +17,7 @@ message HydratedCallButton {
}
message HydratedTemplateButton {
optional uint32 index = 4;
oneof hydratedButton {
HydratedQuickReplyButton quickReplyButton = 1;
HydratedURLButton urlButton = 2;
@@ -26,7 +27,7 @@ message HydratedTemplateButton {
message QuickReplyButton {
optional HighlyStructuredMessage displayText = 1;
optional string buttonId = 2;
optional string id = 2;
}
message URLButton {
@@ -40,6 +41,7 @@ message CallButton {
}
message TemplateButton {
optional uint32 index = 4;
oneof button {
QuickReplyButton quickReplyButton = 1;
URLButton urlButton = 2;
@@ -89,6 +91,8 @@ message ContextInfo {
optional uint32 forwardingScore = 21;
optional bool isForwarded = 22;
optional AdReplyInfo quotedAd = 23;
optional MessageKey placeholderKey = 24;
optional uint32 expiration = 25;
}
message SenderKeyDistributionMessage {
@@ -114,6 +118,10 @@ message ImageMessage {
optional bytes firstScanSidecar = 18;
optional uint32 firstScanLength = 19;
optional uint32 experimentGroupId = 20;
optional bytes scansSidecar = 21;
repeated uint32 scanLengths = 22;
optional bytes midQualityFileSha256 = 23;
optional bytes midQualityFileEncSha256 = 24;
}
message ContactMessage {
@@ -156,6 +164,7 @@ message ExtendedTextMessage {
optional EXTENDED_TEXT_MESSAGE_PREVIEWTYPE previewType = 10;
optional bytes jpegThumbnail = 16;
optional ContextInfo contextInfo = 17;
optional bool doNotPlayInline = 18;
}
message DocumentMessage {
@@ -228,8 +237,10 @@ message ProtocolMessage {
optional MessageKey key = 1;
enum PROTOCOL_MESSAGE_TYPE {
REVOKE = 0;
EPHEMERAL_SETTING = 3;
}
optional PROTOCOL_MESSAGE_TYPE type = 2;
optional uint32 ephemeralExpiration = 4;
}
message ContactsArrayMessage {
@@ -294,6 +305,7 @@ message HighlyStructuredMessage {
repeated HSMLocalizableParameter localizableParams = 6;
optional string deterministicLg = 7;
optional string deterministicLc = 8;
optional TemplateMessage hydratedHsm = 9;
}
message SendPaymentMessage {
@@ -341,7 +353,8 @@ message StickerMessage {
optional string directPath = 8;
optional uint64 fileLength = 9;
optional int64 mediaKeyTimestamp = 10;
optional bytes pngThumbnail = 16;
optional uint32 firstFrameLength = 11;
optional bytes firstFrameSidecar = 12;
optional ContextInfo contextInfo = 17;
}
@@ -361,7 +374,8 @@ message FourRowTemplate {
message HydratedFourRowTemplate {
optional string hydratedContentText = 6;
optional string hydratedFooterText = 7;
repeated HydratedTemplateButton hydratedButtons = 9;
repeated HydratedTemplateButton hydratedButtons = 8;
optional string templateId = 9;
oneof title {
DocumentMessage documentMessage = 1;
string hydratedTitleText = 2;
@@ -372,6 +386,8 @@ message HydratedFourRowTemplate {
}
message TemplateMessage {
optional ContextInfo contextInfo = 3;
optional HydratedFourRowTemplate hydratedTemplate = 4;
oneof format {
FourRowTemplate fourRowTemplate = 1;
HydratedFourRowTemplate hydratedFourRowTemplate = 2;
@@ -379,9 +395,10 @@ message TemplateMessage {
}
message TemplateButtonReplyMessage {
optional string selectedButtonId = 1;
repeated string selectedButtonDisplayText = 2;
optional string selectedId = 1;
optional string selectedDisplayText = 2;
optional ContextInfo contextInfo = 3;
optional uint32 selectedIndex = 4;
}
message ProductSnapshot {
@@ -394,6 +411,7 @@ message ProductSnapshot {
optional string retailerId = 7;
optional string url = 8;
optional uint32 productImageCount = 9;
optional string firstImageId = 11;
}
message ProductMessage {
@@ -409,6 +427,16 @@ message GroupInviteMessage {
optional string groupName = 4;
optional bytes jpegThumbnail = 5;
optional string caption = 6;
optional ContextInfo contextInfo = 7;
}
message DeviceSentMessage {
optional string destinationJid = 1;
optional Message message = 2;
}
message DeviceSyncMessage {
optional bytes serializedXmlBytes = 1;
}
message Message {
@@ -434,8 +462,11 @@ message Message {
optional CancelPaymentRequestMessage cancelPaymentRequestMessage = 24;
optional TemplateMessage templateMessage = 25;
optional StickerMessage stickerMessage = 26;
optional ProductMessage productMessage = 27;
optional GroupInviteMessage groupInviteMessage = 28;
optional TemplateButtonReplyMessage templateButtonReplyMessage = 29;
optional ProductMessage productMessage = 30;
optional DeviceSentMessage deviceSentMessage = 31;
optional DeviceSyncMessage deviceSyncMessage = 32;
}
message MessageKey {
@@ -447,9 +478,10 @@ message MessageKey {
message WebFeatures {
enum WEB_FEATURES_FLAG {
NOT_IMPLEMENTED = 0;
IMPLEMENTED = 1;
OPTIONAL = 2;
NOT_STARTED = 0;
FORCE_UPGRADE = 1;
DEVELOPMENT = 2;
PRODUCTION = 3;
}
optional WEB_FEATURES_FLAG labelsDisplay = 1;
optional WEB_FEATURES_FLAG voipIndividualOutgoing = 2;
@@ -473,6 +505,14 @@ message WebFeatures {
optional WEB_FEATURES_FLAG voipIndividualVideo = 22;
optional WEB_FEATURES_FLAG thirdPartyStickers = 23;
optional WEB_FEATURES_FLAG frequentlyForwardedSetting = 24;
optional WEB_FEATURES_FLAG groupsV4JoinPermission = 25;
optional WEB_FEATURES_FLAG recentStickers = 26;
optional WEB_FEATURES_FLAG catalog = 27;
optional WEB_FEATURES_FLAG starredStickers = 28;
optional WEB_FEATURES_FLAG voipGroupCall = 29;
optional WEB_FEATURES_FLAG templateMessage = 30;
optional WEB_FEATURES_FLAG templateMessageInteractivity = 31;
optional WEB_FEATURES_FLAG ephemeralMessages = 32;
}
message TabletNotificationsInfo {
@@ -614,6 +654,9 @@ message WebMessageInfo {
BIZ_TWO_TIER_MIGRATION_BOTTOM = 67;
OVERSIZED = 68;
GROUP_CHANGE_NO_FREQUENTLY_FORWARDED = 69;
GROUP_V4_ADD_INVITE_SENT = 70;
GROUP_PARTICIPANT_ADD_REQUEST_JOIN = 71;
CHANGE_EPHEMERAL_SETTING = 72;
}
optional WEB_MESSAGE_INFO_STUBTYPE messageStubType = 24;
optional bool clearMedia = 25;
@@ -623,4 +666,6 @@ message WebMessageInfo {
optional PaymentInfo paymentInfo = 29;
optional LiveLocationMessage finalLiveLocation = 30;
optional PaymentInfo quotedPaymentInfo = 31;
}
optional uint64 ephemeralStartTimestamp = 32;
optional uint32 ephemeralDuration = 33;
}

View File

@@ -93,6 +93,8 @@ type Conn struct {
loginSessionLock sync.RWMutex
Proxy func(*http.Request) (*url.URL, error)
writerLock sync.RWMutex
}
type websocketWrapper struct {

View File

@@ -10,11 +10,11 @@ import (
type Presence string
const (
PresenceAvailable = "available"
PresenceUnavailable = "unavailable"
PresenceComposing = "composing"
PresenceRecording = "recording"
PresencePaused = "paused"
PresenceAvailable Presence = "available"
PresenceUnavailable Presence = "unavailable"
PresenceComposing Presence = "composing"
PresenceRecording Presence = "recording"
PresencePaused Presence = "paused"
)
//TODO: filename? WhatsApp uses Store.Contacts for these functions

View File

@@ -10,3 +10,5 @@ require (
github.com/pkg/errors v0.8.1
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
)
go 1.13

View File

@@ -82,13 +82,21 @@ type LocationMessageHandler interface {
}
/*
The StickerMessageHandler interface needs to be implemented to receive location messages dispatched by the dispatcher.
The StickerMessageHandler interface needs to be implemented to receive sticker messages dispatched by the dispatcher.
*/
type StickerMessageHandler interface {
Handler
HandleStickerMessage(message StickerMessage)
}
/*
The ContactMessageHandler interface needs to be implemented to receive contact messages dispatched by the dispatcher.
*/
type ContactMessageHandler interface {
Handler
HandleContactMessage(message ContactMessage)
}
/*
The JsonMessageHandler interface needs to be implemented to receive json messages dispatched by the dispatcher.
These json messages contain status updates of every kind sent by WhatsAppWeb servers. WhatsAppWeb uses these messages
@@ -267,6 +275,17 @@ func (wac *Conn) handleWithCustomHandlers(message interface{}, handlers []Handle
}
}
case ContactMessage:
for _, h := range handlers {
if x, ok := h.(ContactMessageHandler); ok {
if wac.shouldCallSynchronously(h) {
x.HandleContactMessage(m)
} else {
go x.HandleContactMessage(m)
}
}
}
case *proto.WebMessageInfo:
for _, h := range handlers {
if x, ok := h.(RawMessageHandler); ok {

View File

@@ -23,64 +23,53 @@ const (
MediaDocument MediaType = "WhatsApp Document Keys"
)
var msgInfo MessageInfo
func (wac *Conn) Send(msg interface{}) (string, error) {
var err error
var ch <-chan string
var msgProto *proto.WebMessageInfo
switch m := msg.(type) {
case *proto.WebMessageInfo:
ch, err = wac.sendProto(m)
msgProto = m
case TextMessage:
msgProto = getTextProto(m)
msgInfo = getMessageInfo(msgProto)
ch, err = wac.sendProto(msgProto)
case ImageMessage:
var err error
m.url, m.mediaKey, m.fileEncSha256, m.fileSha256, m.fileLength, err = wac.Upload(m.Content, MediaImage)
if err != nil {
return "ERROR", fmt.Errorf("image upload failed: %v", err)
}
msgProto = getImageProto(m)
msgInfo = getMessageInfo(msgProto)
ch, err = wac.sendProto(msgProto)
case VideoMessage:
var err error
m.url, m.mediaKey, m.fileEncSha256, m.fileSha256, m.fileLength, err = wac.Upload(m.Content, MediaVideo)
if err != nil {
return "ERROR", fmt.Errorf("video upload failed: %v", err)
}
msgProto = getVideoProto(m)
msgInfo = getMessageInfo(msgProto)
ch, err = wac.sendProto(msgProto)
case DocumentMessage:
var err error
m.url, m.mediaKey, m.fileEncSha256, m.fileSha256, m.fileLength, err = wac.Upload(m.Content, MediaDocument)
if err != nil {
return "ERROR", fmt.Errorf("document upload failed: %v", err)
}
msgProto = getDocumentProto(m)
msgInfo = getMessageInfo(msgProto)
ch, err = wac.sendProto(msgProto)
case AudioMessage:
var err error
m.url, m.mediaKey, m.fileEncSha256, m.fileSha256, m.fileLength, err = wac.Upload(m.Content, MediaAudio)
if err != nil {
return "ERROR", fmt.Errorf("audio upload failed: %v", err)
}
msgProto = getAudioProto(m)
msgInfo = getMessageInfo(msgProto)
ch, err = wac.sendProto(msgProto)
case LocationMessage:
msgProto = GetLocationProto(m)
msgInfo = getMessageInfo(msgProto)
ch, err = wac.sendProto(msgProto)
case LiveLocationMessage:
msgProto = GetLiveLocationProto(m)
msgInfo = getMessageInfo(msgProto)
ch, err = wac.sendProto(msgProto)
case ContactMessage:
msgProto = getContactMessageProto(m)
default:
return "ERROR", fmt.Errorf("cannot match type %T, use message types declared in the package", msg)
}
ch, err := wac.sendProto(msgProto)
if err != nil {
return "ERROR", fmt.Errorf("could not send proto: %v", err)
}
@@ -95,7 +84,7 @@ func (wac *Conn) Send(msg interface{}) (string, error) {
return "ERROR", fmt.Errorf("message sending responded with %d", resp["status"])
}
if int(resp["status"].(float64)) == 200 {
return msgInfo.Id, nil
return getMessageInfo(msgProto).Id, nil
}
case <-time.After(wac.msgTimeout):
return "ERROR", fmt.Errorf("sending message timed out")
@@ -124,15 +113,13 @@ func init() {
MessageInfo contains general message information. It is part of every of every message type.
*/
type MessageInfo struct {
Id string
RemoteJid string
SenderJid string
FromMe bool
Timestamp uint64
PushName string
Status MessageStatus
QuotedMessageID string
QuotedMessage proto.Message
Id string
RemoteJid string
SenderJid string
FromMe bool
Timestamp uint64
PushName string
Status MessageStatus
Source *proto.WebMessageInfo
}
@@ -185,14 +172,35 @@ func getInfoProto(info *MessageInfo) *proto.WebMessageInfo {
}
}
func getContextInfoProto(info *MessageInfo) *proto.ContextInfo {
if len(info.QuotedMessageID) > 0 {
/*
ContextInfo represents contextinfo of every message
*/
type ContextInfo struct {
QuotedMessageID string //StanzaId
QuotedMessage *proto.Message
Participant string
IsForwarded bool
}
func getMessageContext(msg *proto.ContextInfo) ContextInfo {
return ContextInfo{
QuotedMessageID: msg.GetStanzaId(), //StanzaId
QuotedMessage: msg.GetQuotedMessage(),
Participant: msg.GetParticipant(),
IsForwarded: msg.GetIsForwarded(),
}
}
func getContextInfoProto(context *ContextInfo) *proto.ContextInfo {
if len(context.QuotedMessageID) > 0 {
contextInfo := &proto.ContextInfo{
StanzaId: &info.QuotedMessageID,
StanzaId: &context.QuotedMessageID,
}
if &info.QuotedMessage != nil {
contextInfo.QuotedMessage = &info.QuotedMessage
if &context.QuotedMessage != nil {
contextInfo.QuotedMessage = context.QuotedMessage
contextInfo.Participant = &context.Participant
}
return contextInfo
@@ -205,24 +213,28 @@ func getContextInfoProto(info *MessageInfo) *proto.ContextInfo {
TextMessage represents a text message.
*/
type TextMessage struct {
Info MessageInfo
Text string
Info MessageInfo
Text string
ContextInfo ContextInfo
}
func getTextMessage(msg *proto.WebMessageInfo) TextMessage {
text := TextMessage{Info: getMessageInfo(msg)}
if m := msg.GetMessage().GetExtendedTextMessage(); m != nil {
text.Text = m.GetText()
text.Info.QuotedMessageID = m.GetContextInfo().GetStanzaId()
text.ContextInfo = getMessageContext(m.GetContextInfo())
} else {
text.Text = msg.GetMessage().GetConversation()
}
return text
}
func getTextProto(msg TextMessage) *proto.WebMessageInfo {
p := getInfoProto(&msg.Info)
contextInfo := getContextInfoProto(&msg.Info)
contextInfo := getContextInfoProto(&msg.ContextInfo)
if contextInfo == nil {
p.Message = &proto.Message{
@@ -255,6 +267,7 @@ type ImageMessage struct {
fileEncSha256 []byte
fileSha256 []byte
fileLength uint64
ContextInfo ContextInfo
}
func getImageMessage(msg *proto.WebMessageInfo) ImageMessage {
@@ -270,10 +283,7 @@ func getImageMessage(msg *proto.WebMessageInfo) ImageMessage {
fileEncSha256: image.GetFileEncSha256(),
fileSha256: image.GetFileSha256(),
fileLength: image.GetFileLength(),
}
if contextInfo := image.GetContextInfo(); contextInfo != nil {
imageMessage.Info.QuotedMessageID = contextInfo.GetStanzaId()
ContextInfo: getMessageContext(image.GetContextInfo()),
}
return imageMessage
@@ -281,7 +291,7 @@ func getImageMessage(msg *proto.WebMessageInfo) ImageMessage {
func getImageProto(msg ImageMessage) *proto.WebMessageInfo {
p := getInfoProto(&msg.Info)
contextInfo := getContextInfoProto(&msg.Info)
contextInfo := getContextInfoProto(&msg.ContextInfo)
p.Message = &proto.Message{
ImageMessage: &proto.ImageMessage{
@@ -323,6 +333,7 @@ type VideoMessage struct {
fileEncSha256 []byte
fileSha256 []byte
fileLength uint64
ContextInfo ContextInfo
}
func getVideoMessage(msg *proto.WebMessageInfo) VideoMessage {
@@ -340,10 +351,7 @@ func getVideoMessage(msg *proto.WebMessageInfo) VideoMessage {
fileEncSha256: vid.GetFileEncSha256(),
fileSha256: vid.GetFileSha256(),
fileLength: vid.GetFileLength(),
}
if contextInfo := vid.GetContextInfo(); contextInfo != nil {
videoMessage.Info.QuotedMessageID = contextInfo.GetStanzaId()
ContextInfo: getMessageContext(vid.GetContextInfo()),
}
return videoMessage
@@ -351,7 +359,7 @@ func getVideoMessage(msg *proto.WebMessageInfo) VideoMessage {
func getVideoProto(msg VideoMessage) *proto.WebMessageInfo {
p := getInfoProto(&msg.Info)
contextInfo := getContextInfoProto(&msg.Info)
contextInfo := getContextInfoProto(&msg.ContextInfo)
p.Message = &proto.Message{
VideoMessage: &proto.VideoMessage{
@@ -393,6 +401,7 @@ type AudioMessage struct {
fileEncSha256 []byte
fileSha256 []byte
fileLength uint64
ContextInfo ContextInfo
}
func getAudioMessage(msg *proto.WebMessageInfo) AudioMessage {
@@ -407,10 +416,7 @@ func getAudioMessage(msg *proto.WebMessageInfo) AudioMessage {
fileEncSha256: aud.GetFileEncSha256(),
fileSha256: aud.GetFileSha256(),
fileLength: aud.GetFileLength(),
}
if contextInfo := aud.GetContextInfo(); contextInfo != nil {
audioMessage.Info.QuotedMessageID = contextInfo.GetStanzaId()
ContextInfo: getMessageContext(aud.GetContextInfo()),
}
return audioMessage
@@ -418,7 +424,7 @@ func getAudioMessage(msg *proto.WebMessageInfo) AudioMessage {
func getAudioProto(msg AudioMessage) *proto.WebMessageInfo {
p := getInfoProto(&msg.Info)
contextInfo := getContextInfoProto(&msg.Info)
contextInfo := getContextInfoProto(&msg.ContextInfo)
p.Message = &proto.Message{
AudioMessage: &proto.AudioMessage{
Url: &msg.url,
@@ -459,6 +465,7 @@ type DocumentMessage struct {
fileEncSha256 []byte
fileSha256 []byte
fileLength uint64
ContextInfo ContextInfo
}
func getDocumentMessage(msg *proto.WebMessageInfo) DocumentMessage {
@@ -476,10 +483,7 @@ func getDocumentMessage(msg *proto.WebMessageInfo) DocumentMessage {
fileEncSha256: doc.GetFileEncSha256(),
fileSha256: doc.GetFileSha256(),
fileLength: doc.GetFileLength(),
}
if contextInfo := doc.GetContextInfo(); contextInfo != nil {
documentMessage.Info.QuotedMessageID = contextInfo.GetStanzaId()
ContextInfo: getMessageContext(doc.GetContextInfo()),
}
return documentMessage
@@ -487,7 +491,7 @@ func getDocumentMessage(msg *proto.WebMessageInfo) DocumentMessage {
func getDocumentProto(msg DocumentMessage) *proto.WebMessageInfo {
p := getInfoProto(&msg.Info)
contextInfo := getContextInfoProto(&msg.Info)
contextInfo := getContextInfoProto(&msg.ContextInfo)
p.Message = &proto.Message{
DocumentMessage: &proto.DocumentMessage{
JpegThumbnail: msg.Thumbnail,
@@ -523,6 +527,7 @@ type LocationMessage struct {
Address string
Url string
JpegThumbnail []byte
ContextInfo ContextInfo
}
func GetLocationMessage(msg *proto.WebMessageInfo) LocationMessage {
@@ -536,10 +541,7 @@ func GetLocationMessage(msg *proto.WebMessageInfo) LocationMessage {
Address: loc.GetAddress(),
Url: loc.GetUrl(),
JpegThumbnail: loc.GetJpegThumbnail(),
}
if contextInfo := loc.GetContextInfo(); contextInfo != nil {
locationMessage.Info.QuotedMessageID = contextInfo.GetStanzaId()
ContextInfo: getMessageContext(loc.GetContextInfo()),
}
return locationMessage
@@ -547,7 +549,7 @@ func GetLocationMessage(msg *proto.WebMessageInfo) LocationMessage {
func GetLocationProto(msg LocationMessage) *proto.WebMessageInfo {
p := getInfoProto(&msg.Info)
contextInfo := getContextInfoProto(&msg.Info)
contextInfo := getContextInfoProto(&msg.ContextInfo)
p.Message = &proto.Message{
LocationMessage: &proto.LocationMessage{
@@ -576,6 +578,7 @@ type LiveLocationMessage struct {
Caption string
SequenceNumber int64
JpegThumbnail []byte
ContextInfo ContextInfo
}
func GetLiveLocationMessage(msg *proto.WebMessageInfo) LiveLocationMessage {
@@ -591,10 +594,7 @@ func GetLiveLocationMessage(msg *proto.WebMessageInfo) LiveLocationMessage {
Caption: loc.GetCaption(),
SequenceNumber: loc.GetSequenceNumber(),
JpegThumbnail: loc.GetJpegThumbnail(),
}
if contextInfo := loc.GetContextInfo(); contextInfo != nil {
liveLocationMessage.Info.QuotedMessageID = contextInfo.GetStanzaId()
ContextInfo: getMessageContext(loc.GetContextInfo()),
}
return liveLocationMessage
@@ -602,7 +602,7 @@ func GetLiveLocationMessage(msg *proto.WebMessageInfo) LiveLocationMessage {
func GetLiveLocationProto(msg LiveLocationMessage) *proto.WebMessageInfo {
p := getInfoProto(&msg.Info)
contextInfo := getContextInfoProto(&msg.Info)
contextInfo := getContextInfoProto(&msg.ContextInfo)
p.Message = &proto.Message{
LiveLocationMessage: &proto.LiveLocationMessage{
DegreesLatitude: &msg.DegreesLatitude,
@@ -625,7 +625,6 @@ StickerMessage represents a sticker message.
type StickerMessage struct {
Info MessageInfo
Thumbnail []byte
Type string
Content io.Reader
url string
@@ -633,30 +632,79 @@ type StickerMessage struct {
fileEncSha256 []byte
fileSha256 []byte
fileLength uint64
ContextInfo ContextInfo
}
func getStickerMessage(msg *proto.WebMessageInfo) StickerMessage {
sticker := msg.GetMessage().GetStickerMessage()
StickerMessage := StickerMessage{
stickerMessage := StickerMessage{
Info: getMessageInfo(msg),
Thumbnail: sticker.GetPngThumbnail(),
url: sticker.GetUrl(),
mediaKey: sticker.GetMediaKey(),
Type: sticker.GetMimetype(),
fileEncSha256: sticker.GetFileEncSha256(),
fileSha256: sticker.GetFileSha256(),
fileLength: sticker.GetFileLength(),
ContextInfo: getMessageContext(sticker.GetContextInfo()),
}
if contextInfo := sticker.GetContextInfo(); contextInfo != nil {
StickerMessage.Info.QuotedMessageID = contextInfo.GetStanzaId()
return stickerMessage
}
/*
Download is the function to retrieve Sticker media data. The media gets downloaded, validated and returned.
*/
func (m *StickerMessage) Download() ([]byte, error) {
return Download(m.url, m.mediaKey, MediaImage, int(m.fileLength))
}
/*
ContactMessage represents a contact message.
*/
type ContactMessage struct {
Info MessageInfo
DisplayName string
Vcard string
ContextInfo ContextInfo
}
func getContactMessage(msg *proto.WebMessageInfo) ContactMessage {
contact := msg.GetMessage().GetContactMessage()
contactMessage := ContactMessage{
Info: getMessageInfo(msg),
DisplayName: contact.GetDisplayName(),
Vcard: contact.GetVcard(),
ContextInfo: getMessageContext(contact.GetContextInfo()),
}
return StickerMessage
return contactMessage
}
func getContactMessageProto(msg ContactMessage) *proto.WebMessageInfo {
p := getInfoProto(&msg.Info)
contextInfo := getContextInfoProto(&msg.ContextInfo)
p.Message = &proto.Message{
ContactMessage: &proto.ContactMessage{
DisplayName: &msg.DisplayName,
Vcard: &msg.Vcard,
ContextInfo: contextInfo,
},
}
return p
}
func ParseProtoMessage(msg *proto.WebMessageInfo) interface{} {
switch {
case msg.GetMessage().GetAudioMessage() != nil:
@@ -686,8 +734,12 @@ func ParseProtoMessage(msg *proto.WebMessageInfo) interface{} {
case msg.GetMessage().GetStickerMessage() != nil:
return getStickerMessage(msg)
case msg.GetMessage().GetContactMessage() != nil:
return getContactMessage(msg)
default:
//cannot match message
}
return nil

43
vendor/github.com/Rhymen/go-whatsapp/profile.go generated vendored Normal file
View File

@@ -0,0 +1,43 @@
package whatsapp
import (
"fmt"
"github.com/Rhymen/go-whatsapp/binary"
"strconv"
"time"
)
// Pictures must be JPG 640x640 and 96x96, respectively
func (wac *Conn) UploadProfilePic(image, preview []byte) (<-chan string, error) {
tag := fmt.Sprintf("%d.--%d", time.Now().Unix(), wac.msgCount*19)
n := binary.Node{
Description: "action",
Attributes: map[string]string{
"type": "set",
"epoch": strconv.Itoa(wac.msgCount),
},
Content: []interface{}{
binary.Node{
Description: "picture",
Attributes: map[string]string{
"id": tag,
"jid": wac.Info.Wid,
"type": "set",
},
Content: []binary.Node{
{
Description: "image",
Attributes: nil,
Content: image,
},
{
Description: "preview",
Attributes: nil,
Content: preview,
},
},
},
},
}
return wac.writeBinary(n, profile, 136, tag)
}

View File

@@ -5,16 +5,22 @@ import (
"crypto/sha256"
"encoding/json"
"fmt"
"strconv"
"time"
"github.com/Rhymen/go-whatsapp/binary"
"github.com/Rhymen/go-whatsapp/crypto/cbc"
"github.com/gorilla/websocket"
"github.com/pkg/errors"
"strconv"
"time"
)
//writeJson enqueues a json message into the writeChan
func (wac *Conn) writeJson(data []interface{}) (<-chan string, error) {
wac.writerLock.Lock()
defer wac.writerLock.Unlock()
d, err := json.Marshal(data)
if err != nil {
return nil, err
@@ -38,6 +44,9 @@ func (wac *Conn) writeBinary(node binary.Node, metric metric, flag flag, message
return nil, ErrMissingMessageTag
}
wac.writerLock.Lock()
defer wac.writerLock.Unlock()
data, err := wac.encryptBinaryMessage(node)
if err != nil {
return nil, errors.Wrap(err, "encryptBinaryMessage(node) failed")

View File

@@ -1,17 +0,0 @@
language: go
go:
- "1.12"
install:
- env GO111MODULE=on go get -u golang.org/x/lint/golint
script:
- env GO111MODULE=on make test
deploy:
- provider: script
skip_cleanup: true
script: curl -sL https://git.io/goreleaser | bash
on:
tags: true

View File

@@ -1,35 +0,0 @@
package ast
import (
"strings"
"github.com/d5/tengo/compiler/source"
)
// ArrayLit represents an array literal.
type ArrayLit struct {
Elements []Expr
LBrack source.Pos
RBrack source.Pos
}
func (e *ArrayLit) exprNode() {}
// Pos returns the position of first character belonging to the node.
func (e *ArrayLit) Pos() source.Pos {
return e.LBrack
}
// End returns the position of first character immediately after the node.
func (e *ArrayLit) End() source.Pos {
return e.RBrack + 1
}
func (e *ArrayLit) String() string {
var elements []string
for _, m := range e.Elements {
elements = append(elements, m.String())
}
return "[" + strings.Join(elements, ", ") + "]"
}

View File

@@ -1,40 +0,0 @@
package ast
import (
"strings"
"github.com/d5/tengo/compiler/source"
"github.com/d5/tengo/compiler/token"
)
// AssignStmt represents an assignment statement.
type AssignStmt struct {
LHS []Expr
RHS []Expr
Token token.Token
TokenPos source.Pos
}
func (s *AssignStmt) stmtNode() {}
// Pos returns the position of first character belonging to the node.
func (s *AssignStmt) Pos() source.Pos {
return s.LHS[0].Pos()
}
// End returns the position of first character immediately after the node.
func (s *AssignStmt) End() source.Pos {
return s.RHS[len(s.RHS)-1].End()
}
func (s *AssignStmt) String() string {
var lhs, rhs []string
for _, e := range s.LHS {
lhs = append(lhs, e.String())
}
for _, e := range s.RHS {
rhs = append(rhs, e.String())
}
return strings.Join(lhs, ", ") + " " + s.Token.String() + " " + strings.Join(rhs, ", ")
}

View File

@@ -1,5 +0,0 @@
package ast
const (
nullRep = "<null>"
)

View File

@@ -1,25 +0,0 @@
package ast
import "github.com/d5/tengo/compiler/source"
// BadExpr represents a bad expression.
type BadExpr struct {
From source.Pos
To source.Pos
}
func (e *BadExpr) exprNode() {}
// Pos returns the position of first character belonging to the node.
func (e *BadExpr) Pos() source.Pos {
return e.From
}
// End returns the position of first character immediately after the node.
func (e *BadExpr) End() source.Pos {
return e.To
}
func (e *BadExpr) String() string {
return "<bad expression>"
}

View File

@@ -1,25 +0,0 @@
package ast
import "github.com/d5/tengo/compiler/source"
// BadStmt represents a bad statement.
type BadStmt struct {
From source.Pos
To source.Pos
}
func (s *BadStmt) stmtNode() {}
// Pos returns the position of first character belonging to the node.
func (s *BadStmt) Pos() source.Pos {
return s.From
}
// End returns the position of first character immediately after the node.
func (s *BadStmt) End() source.Pos {
return s.To
}
func (s *BadStmt) String() string {
return "<bad statement>"
}

View File

@@ -1,30 +0,0 @@
package ast
import (
"github.com/d5/tengo/compiler/source"
"github.com/d5/tengo/compiler/token"
)
// BinaryExpr represents a binary operator expression.
type BinaryExpr struct {
LHS Expr
RHS Expr
Token token.Token
TokenPos source.Pos
}
func (e *BinaryExpr) exprNode() {}
// Pos returns the position of first character belonging to the node.
func (e *BinaryExpr) Pos() source.Pos {
return e.LHS.Pos()
}
// End returns the position of first character immediately after the node.
func (e *BinaryExpr) End() source.Pos {
return e.RHS.End()
}
func (e *BinaryExpr) String() string {
return "(" + e.LHS.String() + " " + e.Token.String() + " " + e.RHS.String() + ")"
}

View File

@@ -1,35 +0,0 @@
package ast
import (
"strings"
"github.com/d5/tengo/compiler/source"
)
// BlockStmt represents a block statement.
type BlockStmt struct {
Stmts []Stmt
LBrace source.Pos
RBrace source.Pos
}
func (s *BlockStmt) stmtNode() {}
// Pos returns the position of first character belonging to the node.
func (s *BlockStmt) Pos() source.Pos {
return s.LBrace
}
// End returns the position of first character immediately after the node.
func (s *BlockStmt) End() source.Pos {
return s.RBrace + 1
}
func (s *BlockStmt) String() string {
var list []string
for _, e := range s.Stmts {
list = append(list, e.String())
}
return "{" + strings.Join(list, "; ") + "}"
}

View File

@@ -1,26 +0,0 @@
package ast
import "github.com/d5/tengo/compiler/source"
// BoolLit represents a boolean literal.
type BoolLit struct {
Value bool
ValuePos source.Pos
Literal string
}
func (e *BoolLit) exprNode() {}
// Pos returns the position of first character belonging to the node.
func (e *BoolLit) Pos() source.Pos {
return e.ValuePos
}
// End returns the position of first character immediately after the node.
func (e *BoolLit) End() source.Pos {
return source.Pos(int(e.ValuePos) + len(e.Literal))
}
func (e *BoolLit) String() string {
return e.Literal
}

View File

@@ -1,38 +0,0 @@
package ast
import (
"github.com/d5/tengo/compiler/source"
"github.com/d5/tengo/compiler/token"
)
// BranchStmt represents a branch statement.
type BranchStmt struct {
Token token.Token
TokenPos source.Pos
Label *Ident
}
func (s *BranchStmt) stmtNode() {}
// Pos returns the position of first character belonging to the node.
func (s *BranchStmt) Pos() source.Pos {
return s.TokenPos
}
// End returns the position of first character immediately after the node.
func (s *BranchStmt) End() source.Pos {
if s.Label != nil {
return s.Label.End()
}
return source.Pos(int(s.TokenPos) + len(s.Token.String()))
}
func (s *BranchStmt) String() string {
var label string
if s.Label != nil {
label = " " + s.Label.Name
}
return s.Token.String() + label
}

View File

@@ -1,36 +0,0 @@
package ast
import (
"strings"
"github.com/d5/tengo/compiler/source"
)
// CallExpr represents a function call expression.
type CallExpr struct {
Func Expr
LParen source.Pos
Args []Expr
RParen source.Pos
}
func (e *CallExpr) exprNode() {}
// Pos returns the position of first character belonging to the node.
func (e *CallExpr) Pos() source.Pos {
return e.Func.Pos()
}
// End returns the position of first character immediately after the node.
func (e *CallExpr) End() source.Pos {
return e.RParen + 1
}
func (e *CallExpr) String() string {
var args []string
for _, e := range e.Args {
args = append(args, e.String())
}
return e.Func.String() + "(" + strings.Join(args, ", ") + ")"
}

View File

@@ -1,26 +0,0 @@
package ast
import "github.com/d5/tengo/compiler/source"
// CharLit represents a character literal.
type CharLit struct {
Value rune
ValuePos source.Pos
Literal string
}
func (e *CharLit) exprNode() {}
// Pos returns the position of first character belonging to the node.
func (e *CharLit) Pos() source.Pos {
return e.ValuePos
}
// End returns the position of first character immediately after the node.
func (e *CharLit) End() source.Pos {
return source.Pos(int(e.ValuePos) + len(e.Literal))
}
func (e *CharLit) String() string {
return e.Literal
}

View File

@@ -1,30 +0,0 @@
package ast
import (
"github.com/d5/tengo/compiler/source"
)
// CondExpr represents a ternary conditional expression.
type CondExpr struct {
Cond Expr
True Expr
False Expr
QuestionPos source.Pos
ColonPos source.Pos
}
func (e *CondExpr) exprNode() {}
// Pos returns the position of first character belonging to the node.
func (e *CondExpr) Pos() source.Pos {
return e.Cond.Pos()
}
// End returns the position of first character immediately after the node.
func (e *CondExpr) End() source.Pos {
return e.False.End()
}
func (e *CondExpr) String() string {
return "(" + e.Cond.String() + " ? " + e.True.String() + " : " + e.False.String() + ")"
}

View File

@@ -1,29 +0,0 @@
package ast
import "github.com/d5/tengo/compiler/source"
// EmptyStmt represents an empty statement.
type EmptyStmt struct {
Semicolon source.Pos
Implicit bool
}
func (s *EmptyStmt) stmtNode() {}
// Pos returns the position of first character belonging to the node.
func (s *EmptyStmt) Pos() source.Pos {
return s.Semicolon
}
// End returns the position of first character immediately after the node.
func (s *EmptyStmt) End() source.Pos {
if s.Implicit {
return s.Semicolon
}
return s.Semicolon + 1
}
func (s *EmptyStmt) String() string {
return ";"
}

View File

@@ -1,29 +0,0 @@
package ast
import (
"github.com/d5/tengo/compiler/source"
)
// ErrorExpr represents an error expression
type ErrorExpr struct {
Expr Expr
ErrorPos source.Pos
LParen source.Pos
RParen source.Pos
}
func (e *ErrorExpr) exprNode() {}
// Pos returns the position of first character belonging to the node.
func (e *ErrorExpr) Pos() source.Pos {
return e.ErrorPos
}
// End returns the position of first character immediately after the node.
func (e *ErrorExpr) End() source.Pos {
return e.RParen
}
func (e *ErrorExpr) String() string {
return "error(" + e.Expr.String() + ")"
}

View File

@@ -1,27 +0,0 @@
package ast
import (
"github.com/d5/tengo/compiler/source"
)
// ExportStmt represents an export statement.
type ExportStmt struct {
ExportPos source.Pos
Result Expr
}
func (s *ExportStmt) stmtNode() {}
// Pos returns the position of first character belonging to the node.
func (s *ExportStmt) Pos() source.Pos {
return s.ExportPos
}
// End returns the position of first character immediately after the node.
func (s *ExportStmt) End() source.Pos {
return s.Result.End()
}
func (s *ExportStmt) String() string {
return "export " + s.Result.String()
}

View File

@@ -1,7 +0,0 @@
package ast
// Expr represents an expression node in the AST.
type Expr interface {
Node
exprNode()
}

View File

@@ -1,24 +0,0 @@
package ast
import "github.com/d5/tengo/compiler/source"
// ExprStmt represents an expression statement.
type ExprStmt struct {
Expr Expr
}
func (s *ExprStmt) stmtNode() {}
// Pos returns the position of first character belonging to the node.
func (s *ExprStmt) Pos() source.Pos {
return s.Expr.Pos()
}
// End returns the position of first character immediately after the node.
func (s *ExprStmt) End() source.Pos {
return s.Expr.End()
}
func (s *ExprStmt) String() string {
return s.Expr.String()
}

View File

@@ -1,26 +0,0 @@
package ast
import "github.com/d5/tengo/compiler/source"
// FloatLit represents a floating point literal.
type FloatLit struct {
Value float64
ValuePos source.Pos
Literal string
}
func (e *FloatLit) exprNode() {}
// Pos returns the position of first character belonging to the node.
func (e *FloatLit) Pos() source.Pos {
return e.ValuePos
}
// End returns the position of first character immediately after the node.
func (e *FloatLit) End() source.Pos {
return source.Pos(int(e.ValuePos) + len(e.Literal))
}
func (e *FloatLit) String() string {
return e.Literal
}

View File

@@ -1,32 +0,0 @@
package ast
import "github.com/d5/tengo/compiler/source"
// ForInStmt represents a for-in statement.
type ForInStmt struct {
ForPos source.Pos
Key *Ident
Value *Ident
Iterable Expr
Body *BlockStmt
}
func (s *ForInStmt) stmtNode() {}
// Pos returns the position of first character belonging to the node.
func (s *ForInStmt) Pos() source.Pos {
return s.ForPos
}
// End returns the position of first character immediately after the node.
func (s *ForInStmt) End() source.Pos {
return s.Body.End()
}
func (s *ForInStmt) String() string {
if s.Value != nil {
return "for " + s.Key.String() + ", " + s.Value.String() + " in " + s.Iterable.String() + " " + s.Body.String()
}
return "for " + s.Key.String() + " in " + s.Iterable.String() + " " + s.Body.String()
}

View File

@@ -1,43 +0,0 @@
package ast
import "github.com/d5/tengo/compiler/source"
// ForStmt represents a for statement.
type ForStmt struct {
ForPos source.Pos
Init Stmt
Cond Expr
Post Stmt
Body *BlockStmt
}
func (s *ForStmt) stmtNode() {}
// Pos returns the position of first character belonging to the node.
func (s *ForStmt) Pos() source.Pos {
return s.ForPos
}
// End returns the position of first character immediately after the node.
func (s *ForStmt) End() source.Pos {
return s.Body.End()
}
func (s *ForStmt) String() string {
var init, cond, post string
if s.Init != nil {
init = s.Init.String()
}
if s.Cond != nil {
cond = s.Cond.String() + " "
}
if s.Post != nil {
post = s.Post.String()
}
if init != "" || post != "" {
return "for " + init + " ; " + cond + " ; " + post + s.Body.String()
}
return "for " + cond + s.Body.String()
}

View File

@@ -1,25 +0,0 @@
package ast
import "github.com/d5/tengo/compiler/source"
// FuncLit represents a function literal.
type FuncLit struct {
Type *FuncType
Body *BlockStmt
}
func (e *FuncLit) exprNode() {}
// Pos returns the position of first character belonging to the node.
func (e *FuncLit) Pos() source.Pos {
return e.Type.Pos()
}
// End returns the position of first character immediately after the node.
func (e *FuncLit) End() source.Pos {
return e.Body.End()
}
func (e *FuncLit) String() string {
return "func" + e.Type.Params.String() + " " + e.Body.String()
}

View File

@@ -1,25 +0,0 @@
package ast
import "github.com/d5/tengo/compiler/source"
// FuncType represents a function type definition.
type FuncType struct {
FuncPos source.Pos
Params *IdentList
}
func (e *FuncType) exprNode() {}
// Pos returns the position of first character belonging to the node.
func (e *FuncType) Pos() source.Pos {
return e.FuncPos
}
// End returns the position of first character immediately after the node.
func (e *FuncType) End() source.Pos {
return e.Params.End()
}
func (e *FuncType) String() string {
return "func" + e.Params.String()
}

View File

@@ -1,29 +0,0 @@
package ast
import "github.com/d5/tengo/compiler/source"
// Ident represents an identifier.
type Ident struct {
Name string
NamePos source.Pos
}
func (e *Ident) exprNode() {}
// Pos returns the position of first character belonging to the node.
func (e *Ident) Pos() source.Pos {
return e.NamePos
}
// End returns the position of first character immediately after the node.
func (e *Ident) End() source.Pos {
return source.Pos(int(e.NamePos) + len(e.Name))
}
func (e *Ident) String() string {
if e != nil {
return e.Name
}
return nullRep
}

View File

@@ -1,40 +0,0 @@
package ast
import "github.com/d5/tengo/compiler/source"
// IfStmt represents an if statement.
type IfStmt struct {
IfPos source.Pos
Init Stmt
Cond Expr
Body *BlockStmt
Else Stmt // else branch; or nil
}
func (s *IfStmt) stmtNode() {}
// Pos returns the position of first character belonging to the node.
func (s *IfStmt) Pos() source.Pos {
return s.IfPos
}
// End returns the position of first character immediately after the node.
func (s *IfStmt) End() source.Pos {
if s.Else != nil {
return s.Else.End()
}
return s.Body.End()
}
func (s *IfStmt) String() string {
var initStmt, elseStmt string
if s.Init != nil {
initStmt = s.Init.String() + "; "
}
if s.Else != nil {
elseStmt = " else " + s.Else.String()
}
return "if " + initStmt + s.Cond.String() + " " + s.Body.String() + elseStmt
}

View File

@@ -1,29 +0,0 @@
package ast
import (
"github.com/d5/tengo/compiler/source"
)
// ImmutableExpr represents an immutable expression
type ImmutableExpr struct {
Expr Expr
ErrorPos source.Pos
LParen source.Pos
RParen source.Pos
}
func (e *ImmutableExpr) exprNode() {}
// Pos returns the position of first character belonging to the node.
func (e *ImmutableExpr) Pos() source.Pos {
return e.ErrorPos
}
// End returns the position of first character immediately after the node.
func (e *ImmutableExpr) End() source.Pos {
return e.RParen
}
func (e *ImmutableExpr) String() string {
return "immutable(" + e.Expr.String() + ")"
}

View File

@@ -1,29 +0,0 @@
package ast
import (
"github.com/d5/tengo/compiler/source"
"github.com/d5/tengo/compiler/token"
)
// ImportExpr represents an import expression
type ImportExpr struct {
ModuleName string
Token token.Token
TokenPos source.Pos
}
func (e *ImportExpr) exprNode() {}
// Pos returns the position of first character belonging to the node.
func (e *ImportExpr) Pos() source.Pos {
return e.TokenPos
}
// End returns the position of first character immediately after the node.
func (e *ImportExpr) End() source.Pos {
return source.Pos(int(e.TokenPos) + 10 + len(e.ModuleName)) // import("moduleName")
}
func (e *ImportExpr) String() string {
return `import("` + e.ModuleName + `")"`
}

View File

@@ -1,29 +0,0 @@
package ast
import (
"github.com/d5/tengo/compiler/source"
"github.com/d5/tengo/compiler/token"
)
// IncDecStmt represents increment or decrement statement.
type IncDecStmt struct {
Expr Expr
Token token.Token
TokenPos source.Pos
}
func (s *IncDecStmt) stmtNode() {}
// Pos returns the position of first character belonging to the node.
func (s *IncDecStmt) Pos() source.Pos {
return s.Expr.Pos()
}
// End returns the position of first character immediately after the node.
func (s *IncDecStmt) End() source.Pos {
return source.Pos(int(s.TokenPos) + 2)
}
func (s *IncDecStmt) String() string {
return s.Expr.String() + s.Token.String()
}

View File

@@ -1,32 +0,0 @@
package ast
import "github.com/d5/tengo/compiler/source"
// IndexExpr represents an index expression.
type IndexExpr struct {
Expr Expr
LBrack source.Pos
Index Expr
RBrack source.Pos
}
func (e *IndexExpr) exprNode() {}
// Pos returns the position of first character belonging to the node.
func (e *IndexExpr) Pos() source.Pos {
return e.Expr.Pos()
}
// End returns the position of first character immediately after the node.
func (e *IndexExpr) End() source.Pos {
return e.RBrack + 1
}
func (e *IndexExpr) String() string {
var index string
if e.Index != nil {
index = e.Index.String()
}
return e.Expr.String() + "[" + index + "]"
}

View File

@@ -1,26 +0,0 @@
package ast
import "github.com/d5/tengo/compiler/source"
// IntLit represents an integer literal.
type IntLit struct {
Value int64
ValuePos source.Pos
Literal string
}
func (e *IntLit) exprNode() {}
// Pos returns the position of first character belonging to the node.
func (e *IntLit) Pos() source.Pos {
return e.ValuePos
}
// End returns the position of first character immediately after the node.
func (e *IntLit) End() source.Pos {
return source.Pos(int(e.ValuePos) + len(e.Literal))
}
func (e *IntLit) String() string {
return e.Literal
}

View File

@@ -1,27 +0,0 @@
package ast
import "github.com/d5/tengo/compiler/source"
// MapElementLit represents a map element.
type MapElementLit struct {
Key string
KeyPos source.Pos
ColonPos source.Pos
Value Expr
}
func (e *MapElementLit) exprNode() {}
// Pos returns the position of first character belonging to the node.
func (e *MapElementLit) Pos() source.Pos {
return e.KeyPos
}
// End returns the position of first character immediately after the node.
func (e *MapElementLit) End() source.Pos {
return e.Value.End()
}
func (e *MapElementLit) String() string {
return e.Key + ": " + e.Value.String()
}

View File

@@ -1,35 +0,0 @@
package ast
import (
"strings"
"github.com/d5/tengo/compiler/source"
)
// MapLit represents a map literal.
type MapLit struct {
LBrace source.Pos
Elements []*MapElementLit
RBrace source.Pos
}
func (e *MapLit) exprNode() {}
// Pos returns the position of first character belonging to the node.
func (e *MapLit) Pos() source.Pos {
return e.LBrace
}
// End returns the position of first character immediately after the node.
func (e *MapLit) End() source.Pos {
return e.RBrace + 1
}
func (e *MapLit) String() string {
var elements []string
for _, m := range e.Elements {
elements = append(elements, m.String())
}
return "{" + strings.Join(elements, ", ") + "}"
}

View File

@@ -1,13 +0,0 @@
package ast
import "github.com/d5/tengo/compiler/source"
// Node represents a node in the AST.
type Node interface {
// Pos returns the position of first character belonging to the node.
Pos() source.Pos
// End returns the position of first character immediately after the node.
End() source.Pos
// String returns a string representation of the node.
String() string
}

View File

@@ -1,26 +0,0 @@
package ast
import "github.com/d5/tengo/compiler/source"
// ParenExpr represents a parenthesis wrapped expression.
type ParenExpr struct {
Expr Expr
LParen source.Pos
RParen source.Pos
}
func (e *ParenExpr) exprNode() {}
// Pos returns the position of first character belonging to the node.
func (e *ParenExpr) Pos() source.Pos {
return e.LParen
}
// End returns the position of first character immediately after the node.
func (e *ParenExpr) End() source.Pos {
return e.RParen + 1
}
func (e *ParenExpr) String() string {
return "(" + e.Expr.String() + ")"
}

View File

@@ -1,35 +0,0 @@
package ast
import (
"github.com/d5/tengo/compiler/source"
)
// ReturnStmt represents a return statement.
type ReturnStmt struct {
ReturnPos source.Pos
Result Expr
}
func (s *ReturnStmt) stmtNode() {}
// Pos returns the position of first character belonging to the node.
func (s *ReturnStmt) Pos() source.Pos {
return s.ReturnPos
}
// End returns the position of first character immediately after the node.
func (s *ReturnStmt) End() source.Pos {
if s.Result != nil {
return s.Result.End()
}
return s.ReturnPos + 6
}
func (s *ReturnStmt) String() string {
if s.Result != nil {
return "return " + s.Result.String()
}
return "return"
}

View File

@@ -1,25 +0,0 @@
package ast
import "github.com/d5/tengo/compiler/source"
// SelectorExpr represents a selector expression.
type SelectorExpr struct {
Expr Expr
Sel Expr
}
func (e *SelectorExpr) exprNode() {}
// Pos returns the position of first character belonging to the node.
func (e *SelectorExpr) Pos() source.Pos {
return e.Expr.Pos()
}
// End returns the position of first character immediately after the node.
func (e *SelectorExpr) End() source.Pos {
return e.Sel.End()
}
func (e *SelectorExpr) String() string {
return e.Expr.String() + "." + e.Sel.String()
}

View File

@@ -1,36 +0,0 @@
package ast
import "github.com/d5/tengo/compiler/source"
// SliceExpr represents a slice expression.
type SliceExpr struct {
Expr Expr
LBrack source.Pos
Low Expr
High Expr
RBrack source.Pos
}
func (e *SliceExpr) exprNode() {}
// Pos returns the position of first character belonging to the node.
func (e *SliceExpr) Pos() source.Pos {
return e.Expr.Pos()
}
// End returns the position of first character immediately after the node.
func (e *SliceExpr) End() source.Pos {
return e.RBrack + 1
}
func (e *SliceExpr) String() string {
var low, high string
if e.Low != nil {
low = e.Low.String()
}
if e.High != nil {
high = e.High.String()
}
return e.Expr.String() + "[" + low + ":" + high + "]"
}

View File

@@ -1,7 +0,0 @@
package ast
// Stmt represents a statement in the AST.
type Stmt interface {
Node
stmtNode()
}

View File

@@ -1,26 +0,0 @@
package ast
import "github.com/d5/tengo/compiler/source"
// StringLit represents a string literal.
type StringLit struct {
Value string
ValuePos source.Pos
Literal string
}
func (e *StringLit) exprNode() {}
// Pos returns the position of first character belonging to the node.
func (e *StringLit) Pos() source.Pos {
return e.ValuePos
}
// End returns the position of first character immediately after the node.
func (e *StringLit) End() source.Pos {
return source.Pos(int(e.ValuePos) + len(e.Literal))
}
func (e *StringLit) String() string {
return e.Literal
}

View File

@@ -1,29 +0,0 @@
package ast
import (
"github.com/d5/tengo/compiler/source"
"github.com/d5/tengo/compiler/token"
)
// UnaryExpr represents an unary operator expression.
type UnaryExpr struct {
Expr Expr
Token token.Token
TokenPos source.Pos
}
func (e *UnaryExpr) exprNode() {}
// Pos returns the position of first character belonging to the node.
func (e *UnaryExpr) Pos() source.Pos {
return e.Expr.Pos()
}
// End returns the position of first character immediately after the node.
func (e *UnaryExpr) End() source.Pos {
return e.Expr.End()
}
func (e *UnaryExpr) String() string {
return "(" + e.Token.String() + e.Expr.String() + ")"
}

View File

@@ -1,24 +0,0 @@
package ast
import "github.com/d5/tengo/compiler/source"
// UndefinedLit represents an undefined literal.
type UndefinedLit struct {
TokenPos source.Pos
}
func (e *UndefinedLit) exprNode() {}
// Pos returns the position of first character belonging to the node.
func (e *UndefinedLit) Pos() source.Pos {
return e.TokenPos
}
// End returns the position of first character immediately after the node.
func (e *UndefinedLit) End() source.Pos {
return e.TokenPos + 9 // len(undefined) == 9
}
func (e *UndefinedLit) String() string {
return "undefined"
}

View File

@@ -1,90 +0,0 @@
package compiler
import (
"encoding/gob"
"fmt"
"io"
"reflect"
"github.com/d5/tengo/compiler/source"
"github.com/d5/tengo/objects"
)
// Bytecode is a compiled instructions and constants.
type Bytecode struct {
FileSet *source.FileSet
MainFunction *objects.CompiledFunction
Constants []objects.Object
}
// Encode writes Bytecode data to the writer.
func (b *Bytecode) Encode(w io.Writer) error {
enc := gob.NewEncoder(w)
if err := enc.Encode(b.FileSet); err != nil {
return err
}
if err := enc.Encode(b.MainFunction); err != nil {
return err
}
// constants
return enc.Encode(b.Constants)
}
// CountObjects returns the number of objects found in Constants.
func (b *Bytecode) CountObjects() int {
n := 0
for _, c := range b.Constants {
n += objects.CountObjects(c)
}
return n
}
// FormatInstructions returns human readable string representations of
// compiled instructions.
func (b *Bytecode) FormatInstructions() []string {
return FormatInstructions(b.MainFunction.Instructions, 0)
}
// FormatConstants returns human readable string representations of
// compiled constants.
func (b *Bytecode) FormatConstants() (output []string) {
for cidx, cn := range b.Constants {
switch cn := cn.(type) {
case *objects.CompiledFunction:
output = append(output, fmt.Sprintf("[% 3d] (Compiled Function|%p)", cidx, &cn))
for _, l := range FormatInstructions(cn.Instructions, 0) {
output = append(output, fmt.Sprintf(" %s", l))
}
default:
output = append(output, fmt.Sprintf("[% 3d] %s (%s|%p)", cidx, cn, reflect.TypeOf(cn).Elem().Name(), &cn))
}
}
return
}
func init() {
gob.Register(&source.FileSet{})
gob.Register(&source.File{})
gob.Register(&objects.Array{})
gob.Register(&objects.Bool{})
gob.Register(&objects.Bytes{})
gob.Register(&objects.Char{})
gob.Register(&objects.Closure{})
gob.Register(&objects.CompiledFunction{})
gob.Register(&objects.Error{})
gob.Register(&objects.Float{})
gob.Register(&objects.ImmutableArray{})
gob.Register(&objects.ImmutableMap{})
gob.Register(&objects.Int{})
gob.Register(&objects.Map{})
gob.Register(&objects.String{})
gob.Register(&objects.Time{})
gob.Register(&objects.Undefined{})
gob.Register(&objects.UserFunction{})
}

View File

@@ -1,97 +0,0 @@
package compiler
import (
"encoding/gob"
"fmt"
"io"
"github.com/d5/tengo/objects"
)
// Decode reads Bytecode data from the reader.
func (b *Bytecode) Decode(r io.Reader, modules *objects.ModuleMap) error {
if modules == nil {
modules = objects.NewModuleMap()
}
dec := gob.NewDecoder(r)
if err := dec.Decode(&b.FileSet); err != nil {
return err
}
// TODO: files in b.FileSet.File does not have their 'set' field properly set to b.FileSet
// as it's private field and not serialized by gob encoder/decoder.
if err := dec.Decode(&b.MainFunction); err != nil {
return err
}
if err := dec.Decode(&b.Constants); err != nil {
return err
}
for i, v := range b.Constants {
fv, err := fixDecoded(v, modules)
if err != nil {
return err
}
b.Constants[i] = fv
}
return nil
}
func fixDecoded(o objects.Object, modules *objects.ModuleMap) (objects.Object, error) {
switch o := o.(type) {
case *objects.Bool:
if o.IsFalsy() {
return objects.FalseValue, nil
}
return objects.TrueValue, nil
case *objects.Undefined:
return objects.UndefinedValue, nil
case *objects.Array:
for i, v := range o.Value {
fv, err := fixDecoded(v, modules)
if err != nil {
return nil, err
}
o.Value[i] = fv
}
case *objects.ImmutableArray:
for i, v := range o.Value {
fv, err := fixDecoded(v, modules)
if err != nil {
return nil, err
}
o.Value[i] = fv
}
case *objects.Map:
for k, v := range o.Value {
fv, err := fixDecoded(v, modules)
if err != nil {
return nil, err
}
o.Value[k] = fv
}
case *objects.ImmutableMap:
modName := moduleName(o)
if mod := modules.GetBuiltinModule(modName); mod != nil {
return mod.AsImmutableMap(modName), nil
}
for k, v := range o.Value {
// encoding of user function not supported
if _, isUserFunction := v.(*objects.UserFunction); isUserFunction {
return nil, fmt.Errorf("user function not decodable")
}
fv, err := fixDecoded(v, modules)
if err != nil {
return nil, err
}
o.Value[k] = fv
}
}
return o, nil
}

View File

@@ -1,129 +0,0 @@
package compiler
import (
"fmt"
"github.com/d5/tengo/objects"
)
// RemoveDuplicates finds and remove the duplicate values in Constants.
// Note this function mutates Bytecode.
func (b *Bytecode) RemoveDuplicates() {
var deduped []objects.Object
indexMap := make(map[int]int) // mapping from old constant index to new index
ints := make(map[int64]int)
strings := make(map[string]int)
floats := make(map[float64]int)
chars := make(map[rune]int)
immutableMaps := make(map[string]int) // for modules
for curIdx, c := range b.Constants {
switch c := c.(type) {
case *objects.CompiledFunction:
// add to deduped list
indexMap[curIdx] = len(deduped)
deduped = append(deduped, c)
case *objects.ImmutableMap:
modName := moduleName(c)
newIdx, ok := immutableMaps[modName]
if modName != "" && ok {
indexMap[curIdx] = newIdx
} else {
newIdx = len(deduped)
immutableMaps[modName] = newIdx
indexMap[curIdx] = newIdx
deduped = append(deduped, c)
}
case *objects.Int:
if newIdx, ok := ints[c.Value]; ok {
indexMap[curIdx] = newIdx
} else {
newIdx = len(deduped)
ints[c.Value] = newIdx
indexMap[curIdx] = newIdx
deduped = append(deduped, c)
}
case *objects.String:
if newIdx, ok := strings[c.Value]; ok {
indexMap[curIdx] = newIdx
} else {
newIdx = len(deduped)
strings[c.Value] = newIdx
indexMap[curIdx] = newIdx
deduped = append(deduped, c)
}
case *objects.Float:
if newIdx, ok := floats[c.Value]; ok {
indexMap[curIdx] = newIdx
} else {
newIdx = len(deduped)
floats[c.Value] = newIdx
indexMap[curIdx] = newIdx
deduped = append(deduped, c)
}
case *objects.Char:
if newIdx, ok := chars[c.Value]; ok {
indexMap[curIdx] = newIdx
} else {
newIdx = len(deduped)
chars[c.Value] = newIdx
indexMap[curIdx] = newIdx
deduped = append(deduped, c)
}
default:
panic(fmt.Errorf("unsupported top-level constant type: %s", c.TypeName()))
}
}
// replace with de-duplicated constants
b.Constants = deduped
// update CONST instructions with new indexes
// main function
updateConstIndexes(b.MainFunction.Instructions, indexMap)
// other compiled functions in constants
for _, c := range b.Constants {
switch c := c.(type) {
case *objects.CompiledFunction:
updateConstIndexes(c.Instructions, indexMap)
}
}
}
func updateConstIndexes(insts []byte, indexMap map[int]int) {
i := 0
for i < len(insts) {
op := insts[i]
numOperands := OpcodeOperands[op]
_, read := ReadOperands(numOperands, insts[i+1:])
switch op {
case OpConstant:
curIdx := int(insts[i+2]) | int(insts[i+1])<<8
newIdx, ok := indexMap[curIdx]
if !ok {
panic(fmt.Errorf("constant index not found: %d", curIdx))
}
copy(insts[i:], MakeInstruction(op, newIdx))
case OpClosure:
curIdx := int(insts[i+2]) | int(insts[i+1])<<8
numFree := int(insts[i+3])
newIdx, ok := indexMap[curIdx]
if !ok {
panic(fmt.Errorf("constant index not found: %d", curIdx))
}
copy(insts[i:], MakeInstruction(op, newIdx, numFree))
}
i += 1 + read
}
}
func moduleName(mod *objects.ImmutableMap) string {
if modName, ok := mod.Value["__module_name__"].(*objects.String); ok {
return modName.Value
}
return ""
}

View File

@@ -1,11 +0,0 @@
package compiler
import "github.com/d5/tengo/compiler/source"
// CompilationScope represents a compiled instructions
// and the last two instructions that were emitted.
type CompilationScope struct {
instructions []byte
symbolInit map[string]bool
sourceMap map[int]source.Pos
}

View File

@@ -1,846 +0,0 @@
package compiler
import (
"fmt"
"io"
"io/ioutil"
"path/filepath"
"reflect"
"strings"
"github.com/d5/tengo"
"github.com/d5/tengo/compiler/ast"
"github.com/d5/tengo/compiler/source"
"github.com/d5/tengo/compiler/token"
"github.com/d5/tengo/objects"
)
// Compiler compiles the AST into a bytecode.
type Compiler struct {
file *source.File
parent *Compiler
modulePath string
constants []objects.Object
symbolTable *SymbolTable
scopes []CompilationScope
scopeIndex int
modules *objects.ModuleMap
compiledModules map[string]*objects.CompiledFunction
allowFileImport bool
loops []*Loop
loopIndex int
trace io.Writer
indent int
}
// NewCompiler creates a Compiler.
func NewCompiler(file *source.File, symbolTable *SymbolTable, constants []objects.Object, modules *objects.ModuleMap, trace io.Writer) *Compiler {
mainScope := CompilationScope{
symbolInit: make(map[string]bool),
sourceMap: make(map[int]source.Pos),
}
// symbol table
if symbolTable == nil {
symbolTable = NewSymbolTable()
}
// add builtin functions to the symbol table
for idx, fn := range objects.Builtins {
symbolTable.DefineBuiltin(idx, fn.Name)
}
// builtin modules
if modules == nil {
modules = objects.NewModuleMap()
}
return &Compiler{
file: file,
symbolTable: symbolTable,
constants: constants,
scopes: []CompilationScope{mainScope},
scopeIndex: 0,
loopIndex: -1,
trace: trace,
modules: modules,
compiledModules: make(map[string]*objects.CompiledFunction),
}
}
// Compile compiles the AST node.
func (c *Compiler) Compile(node ast.Node) error {
if c.trace != nil {
if node != nil {
defer un(trace(c, fmt.Sprintf("%s (%s)", node.String(), reflect.TypeOf(node).Elem().Name())))
} else {
defer un(trace(c, "<nil>"))
}
}
switch node := node.(type) {
case *ast.File:
for _, stmt := range node.Stmts {
if err := c.Compile(stmt); err != nil {
return err
}
}
case *ast.ExprStmt:
if err := c.Compile(node.Expr); err != nil {
return err
}
c.emit(node, OpPop)
case *ast.IncDecStmt:
op := token.AddAssign
if node.Token == token.Dec {
op = token.SubAssign
}
return c.compileAssign(node, []ast.Expr{node.Expr}, []ast.Expr{&ast.IntLit{Value: 1}}, op)
case *ast.ParenExpr:
if err := c.Compile(node.Expr); err != nil {
return err
}
case *ast.BinaryExpr:
if node.Token == token.LAnd || node.Token == token.LOr {
return c.compileLogical(node)
}
if node.Token == token.Less {
if err := c.Compile(node.RHS); err != nil {
return err
}
if err := c.Compile(node.LHS); err != nil {
return err
}
c.emit(node, OpBinaryOp, int(token.Greater))
return nil
} else if node.Token == token.LessEq {
if err := c.Compile(node.RHS); err != nil {
return err
}
if err := c.Compile(node.LHS); err != nil {
return err
}
c.emit(node, OpBinaryOp, int(token.GreaterEq))
return nil
}
if err := c.Compile(node.LHS); err != nil {
return err
}
if err := c.Compile(node.RHS); err != nil {
return err
}
switch node.Token {
case token.Add:
c.emit(node, OpBinaryOp, int(token.Add))
case token.Sub:
c.emit(node, OpBinaryOp, int(token.Sub))
case token.Mul:
c.emit(node, OpBinaryOp, int(token.Mul))
case token.Quo:
c.emit(node, OpBinaryOp, int(token.Quo))
case token.Rem:
c.emit(node, OpBinaryOp, int(token.Rem))
case token.Greater:
c.emit(node, OpBinaryOp, int(token.Greater))
case token.GreaterEq:
c.emit(node, OpBinaryOp, int(token.GreaterEq))
case token.Equal:
c.emit(node, OpEqual)
case token.NotEqual:
c.emit(node, OpNotEqual)
case token.And:
c.emit(node, OpBinaryOp, int(token.And))
case token.Or:
c.emit(node, OpBinaryOp, int(token.Or))
case token.Xor:
c.emit(node, OpBinaryOp, int(token.Xor))
case token.AndNot:
c.emit(node, OpBinaryOp, int(token.AndNot))
case token.Shl:
c.emit(node, OpBinaryOp, int(token.Shl))
case token.Shr:
c.emit(node, OpBinaryOp, int(token.Shr))
default:
return c.errorf(node, "invalid binary operator: %s", node.Token.String())
}
case *ast.IntLit:
c.emit(node, OpConstant, c.addConstant(&objects.Int{Value: node.Value}))
case *ast.FloatLit:
c.emit(node, OpConstant, c.addConstant(&objects.Float{Value: node.Value}))
case *ast.BoolLit:
if node.Value {
c.emit(node, OpTrue)
} else {
c.emit(node, OpFalse)
}
case *ast.StringLit:
if len(node.Value) > tengo.MaxStringLen {
return c.error(node, objects.ErrStringLimit)
}
c.emit(node, OpConstant, c.addConstant(&objects.String{Value: node.Value}))
case *ast.CharLit:
c.emit(node, OpConstant, c.addConstant(&objects.Char{Value: node.Value}))
case *ast.UndefinedLit:
c.emit(node, OpNull)
case *ast.UnaryExpr:
if err := c.Compile(node.Expr); err != nil {
return err
}
switch node.Token {
case token.Not:
c.emit(node, OpLNot)
case token.Sub:
c.emit(node, OpMinus)
case token.Xor:
c.emit(node, OpBComplement)
case token.Add:
// do nothing?
default:
return c.errorf(node, "invalid unary operator: %s", node.Token.String())
}
case *ast.IfStmt:
// open new symbol table for the statement
c.symbolTable = c.symbolTable.Fork(true)
defer func() {
c.symbolTable = c.symbolTable.Parent(false)
}()
if node.Init != nil {
if err := c.Compile(node.Init); err != nil {
return err
}
}
if err := c.Compile(node.Cond); err != nil {
return err
}
// first jump placeholder
jumpPos1 := c.emit(node, OpJumpFalsy, 0)
if err := c.Compile(node.Body); err != nil {
return err
}
if node.Else != nil {
// second jump placeholder
jumpPos2 := c.emit(node, OpJump, 0)
// update first jump offset
curPos := len(c.currentInstructions())
c.changeOperand(jumpPos1, curPos)
if err := c.Compile(node.Else); err != nil {
return err
}
// update second jump offset
curPos = len(c.currentInstructions())
c.changeOperand(jumpPos2, curPos)
} else {
// update first jump offset
curPos := len(c.currentInstructions())
c.changeOperand(jumpPos1, curPos)
}
case *ast.ForStmt:
return c.compileForStmt(node)
case *ast.ForInStmt:
return c.compileForInStmt(node)
case *ast.BranchStmt:
if node.Token == token.Break {
curLoop := c.currentLoop()
if curLoop == nil {
return c.errorf(node, "break not allowed outside loop")
}
pos := c.emit(node, OpJump, 0)
curLoop.Breaks = append(curLoop.Breaks, pos)
} else if node.Token == token.Continue {
curLoop := c.currentLoop()
if curLoop == nil {
return c.errorf(node, "continue not allowed outside loop")
}
pos := c.emit(node, OpJump, 0)
curLoop.Continues = append(curLoop.Continues, pos)
} else {
panic(fmt.Errorf("invalid branch statement: %s", node.Token.String()))
}
case *ast.BlockStmt:
if len(node.Stmts) == 0 {
return nil
}
c.symbolTable = c.symbolTable.Fork(true)
defer func() {
c.symbolTable = c.symbolTable.Parent(false)
}()
for _, stmt := range node.Stmts {
if err := c.Compile(stmt); err != nil {
return err
}
}
case *ast.AssignStmt:
if err := c.compileAssign(node, node.LHS, node.RHS, node.Token); err != nil {
return err
}
case *ast.Ident:
symbol, _, ok := c.symbolTable.Resolve(node.Name)
if !ok {
return c.errorf(node, "unresolved reference '%s'", node.Name)
}
switch symbol.Scope {
case ScopeGlobal:
c.emit(node, OpGetGlobal, symbol.Index)
case ScopeLocal:
c.emit(node, OpGetLocal, symbol.Index)
case ScopeBuiltin:
c.emit(node, OpGetBuiltin, symbol.Index)
case ScopeFree:
c.emit(node, OpGetFree, symbol.Index)
}
case *ast.ArrayLit:
for _, elem := range node.Elements {
if err := c.Compile(elem); err != nil {
return err
}
}
c.emit(node, OpArray, len(node.Elements))
case *ast.MapLit:
for _, elt := range node.Elements {
// key
if len(elt.Key) > tengo.MaxStringLen {
return c.error(node, objects.ErrStringLimit)
}
c.emit(node, OpConstant, c.addConstant(&objects.String{Value: elt.Key}))
// value
if err := c.Compile(elt.Value); err != nil {
return err
}
}
c.emit(node, OpMap, len(node.Elements)*2)
case *ast.SelectorExpr: // selector on RHS side
if err := c.Compile(node.Expr); err != nil {
return err
}
if err := c.Compile(node.Sel); err != nil {
return err
}
c.emit(node, OpIndex)
case *ast.IndexExpr:
if err := c.Compile(node.Expr); err != nil {
return err
}
if err := c.Compile(node.Index); err != nil {
return err
}
c.emit(node, OpIndex)
case *ast.SliceExpr:
if err := c.Compile(node.Expr); err != nil {
return err
}
if node.Low != nil {
if err := c.Compile(node.Low); err != nil {
return err
}
} else {
c.emit(node, OpNull)
}
if node.High != nil {
if err := c.Compile(node.High); err != nil {
return err
}
} else {
c.emit(node, OpNull)
}
c.emit(node, OpSliceIndex)
case *ast.FuncLit:
c.enterScope()
for _, p := range node.Type.Params.List {
s := c.symbolTable.Define(p.Name)
// function arguments is not assigned directly.
s.LocalAssigned = true
}
if err := c.Compile(node.Body); err != nil {
return err
}
// code optimization
c.optimizeFunc(node)
freeSymbols := c.symbolTable.FreeSymbols()
numLocals := c.symbolTable.MaxSymbols()
instructions, sourceMap := c.leaveScope()
for _, s := range freeSymbols {
switch s.Scope {
case ScopeLocal:
if !s.LocalAssigned {
// Here, the closure is capturing a local variable that's not yet assigned its value.
// One example is a local recursive function:
//
// func() {
// foo := func(x) {
// // ..
// return foo(x-1)
// }
// }
//
// which translate into
//
// 0000 GETL 0
// 0002 CLOSURE ? 1
// 0006 DEFL 0
//
// . So the local variable (0) is being captured before it's assigned the value.
//
// Solution is to transform the code into something like this:
//
// func() {
// foo := undefined
// foo = func(x) {
// // ..
// return foo(x-1)
// }
// }
//
// that is equivalent to
//
// 0000 NULL
// 0001 DEFL 0
// 0003 GETL 0
// 0005 CLOSURE ? 1
// 0009 SETL 0
//
c.emit(node, OpNull)
c.emit(node, OpDefineLocal, s.Index)
s.LocalAssigned = true
}
c.emit(node, OpGetLocalPtr, s.Index)
case ScopeFree:
c.emit(node, OpGetFreePtr, s.Index)
}
}
compiledFunction := &objects.CompiledFunction{
Instructions: instructions,
NumLocals: numLocals,
NumParameters: len(node.Type.Params.List),
VarArgs: node.Type.Params.VarArgs,
SourceMap: sourceMap,
}
if len(freeSymbols) > 0 {
c.emit(node, OpClosure, c.addConstant(compiledFunction), len(freeSymbols))
} else {
c.emit(node, OpConstant, c.addConstant(compiledFunction))
}
case *ast.ReturnStmt:
if c.symbolTable.Parent(true) == nil {
// outside the function
return c.errorf(node, "return not allowed outside function")
}
if node.Result == nil {
c.emit(node, OpReturn, 0)
} else {
if err := c.Compile(node.Result); err != nil {
return err
}
c.emit(node, OpReturn, 1)
}
case *ast.CallExpr:
if err := c.Compile(node.Func); err != nil {
return err
}
for _, arg := range node.Args {
if err := c.Compile(arg); err != nil {
return err
}
}
c.emit(node, OpCall, len(node.Args))
case *ast.ImportExpr:
if node.ModuleName == "" {
return c.errorf(node, "empty module name")
}
if mod := c.modules.Get(node.ModuleName); mod != nil {
v, err := mod.Import(node.ModuleName)
if err != nil {
return err
}
switch v := v.(type) {
case []byte: // module written in Tengo
compiled, err := c.compileModule(node, node.ModuleName, node.ModuleName, v)
if err != nil {
return err
}
c.emit(node, OpConstant, c.addConstant(compiled))
c.emit(node, OpCall, 0)
case objects.Object: // builtin module
c.emit(node, OpConstant, c.addConstant(v))
default:
panic(fmt.Errorf("invalid import value type: %T", v))
}
} else if c.allowFileImport {
moduleName := node.ModuleName
if !strings.HasSuffix(moduleName, ".tengo") {
moduleName += ".tengo"
}
modulePath, err := filepath.Abs(moduleName)
if err != nil {
return c.errorf(node, "module file path error: %s", err.Error())
}
if err := c.checkCyclicImports(node, modulePath); err != nil {
return err
}
moduleSrc, err := ioutil.ReadFile(moduleName)
if err != nil {
return c.errorf(node, "module file read error: %s", err.Error())
}
compiled, err := c.compileModule(node, moduleName, modulePath, moduleSrc)
if err != nil {
return err
}
c.emit(node, OpConstant, c.addConstant(compiled))
c.emit(node, OpCall, 0)
} else {
return c.errorf(node, "module '%s' not found", node.ModuleName)
}
case *ast.ExportStmt:
// export statement must be in top-level scope
if c.scopeIndex != 0 {
return c.errorf(node, "export not allowed inside function")
}
// export statement is simply ignore when compiling non-module code
if c.parent == nil {
break
}
if err := c.Compile(node.Result); err != nil {
return err
}
c.emit(node, OpImmutable)
c.emit(node, OpReturn, 1)
case *ast.ErrorExpr:
if err := c.Compile(node.Expr); err != nil {
return err
}
c.emit(node, OpError)
case *ast.ImmutableExpr:
if err := c.Compile(node.Expr); err != nil {
return err
}
c.emit(node, OpImmutable)
case *ast.CondExpr:
if err := c.Compile(node.Cond); err != nil {
return err
}
// first jump placeholder
jumpPos1 := c.emit(node, OpJumpFalsy, 0)
if err := c.Compile(node.True); err != nil {
return err
}
// second jump placeholder
jumpPos2 := c.emit(node, OpJump, 0)
// update first jump offset
curPos := len(c.currentInstructions())
c.changeOperand(jumpPos1, curPos)
if err := c.Compile(node.False); err != nil {
return err
}
// update second jump offset
curPos = len(c.currentInstructions())
c.changeOperand(jumpPos2, curPos)
}
return nil
}
// Bytecode returns a compiled bytecode.
func (c *Compiler) Bytecode() *Bytecode {
return &Bytecode{
FileSet: c.file.Set(),
MainFunction: &objects.CompiledFunction{
Instructions: c.currentInstructions(),
SourceMap: c.currentSourceMap(),
},
Constants: c.constants,
}
}
// EnableFileImport enables or disables module loading from local files.
// Local file modules are disabled by default.
func (c *Compiler) EnableFileImport(enable bool) {
c.allowFileImport = enable
}
func (c *Compiler) fork(file *source.File, modulePath string, symbolTable *SymbolTable) *Compiler {
child := NewCompiler(file, symbolTable, nil, c.modules, c.trace)
child.modulePath = modulePath // module file path
child.parent = c // parent to set to current compiler
return child
}
func (c *Compiler) error(node ast.Node, err error) error {
return &Error{
fileSet: c.file.Set(),
node: node,
error: err,
}
}
func (c *Compiler) errorf(node ast.Node, format string, args ...interface{}) error {
return &Error{
fileSet: c.file.Set(),
node: node,
error: fmt.Errorf(format, args...),
}
}
func (c *Compiler) addConstant(o objects.Object) int {
if c.parent != nil {
// module compilers will use their parent's constants array
return c.parent.addConstant(o)
}
c.constants = append(c.constants, o)
if c.trace != nil {
c.printTrace(fmt.Sprintf("CONST %04d %s", len(c.constants)-1, o))
}
return len(c.constants) - 1
}
func (c *Compiler) addInstruction(b []byte) int {
posNewIns := len(c.currentInstructions())
c.scopes[c.scopeIndex].instructions = append(c.currentInstructions(), b...)
return posNewIns
}
func (c *Compiler) replaceInstruction(pos int, inst []byte) {
copy(c.currentInstructions()[pos:], inst)
if c.trace != nil {
c.printTrace(fmt.Sprintf("REPLC %s",
FormatInstructions(c.scopes[c.scopeIndex].instructions[pos:], pos)[0]))
}
}
func (c *Compiler) changeOperand(opPos int, operand ...int) {
op := Opcode(c.currentInstructions()[opPos])
inst := MakeInstruction(op, operand...)
c.replaceInstruction(opPos, inst)
}
// optimizeFunc performs some code-level optimization for the current function instructions
// it removes unreachable (dead code) instructions and adds "returns" instruction if needed.
func (c *Compiler) optimizeFunc(node ast.Node) {
// any instructions between RETURN and the function end
// or instructions between RETURN and jump target position
// are considered as unreachable.
// pass 1. identify all jump destinations
dsts := make(map[int]bool)
iterateInstructions(c.scopes[c.scopeIndex].instructions, func(pos int, opcode Opcode, operands []int) bool {
switch opcode {
case OpJump, OpJumpFalsy, OpAndJump, OpOrJump:
dsts[operands[0]] = true
}
return true
})
var newInsts []byte
// pass 2. eliminate dead code
posMap := make(map[int]int) // old position to new position
var dstIdx int
var deadCode bool
iterateInstructions(c.scopes[c.scopeIndex].instructions, func(pos int, opcode Opcode, operands []int) bool {
switch {
case opcode == OpReturn:
if deadCode {
return true
}
deadCode = true
case dsts[pos]:
dstIdx++
deadCode = false
case deadCode:
return true
}
posMap[pos] = len(newInsts)
newInsts = append(newInsts, MakeInstruction(opcode, operands...)...)
return true
})
// pass 3. update jump positions
var lastOp Opcode
var appendReturn bool
endPos := len(c.scopes[c.scopeIndex].instructions)
iterateInstructions(newInsts, func(pos int, opcode Opcode, operands []int) bool {
switch opcode {
case OpJump, OpJumpFalsy, OpAndJump, OpOrJump:
newDst, ok := posMap[operands[0]]
if ok {
copy(newInsts[pos:], MakeInstruction(opcode, newDst))
} else if endPos == operands[0] {
// there's a jump instruction that jumps to the end of function
// compiler should append "return".
appendReturn = true
} else {
panic(fmt.Errorf("invalid jump position: %d", newDst))
}
}
lastOp = opcode
return true
})
if lastOp != OpReturn {
appendReturn = true
}
// pass 4. update source map
newSourceMap := make(map[int]source.Pos)
for pos, srcPos := range c.scopes[c.scopeIndex].sourceMap {
newPos, ok := posMap[pos]
if ok {
newSourceMap[newPos] = srcPos
}
}
c.scopes[c.scopeIndex].instructions = newInsts
c.scopes[c.scopeIndex].sourceMap = newSourceMap
// append "return"
if appendReturn {
c.emit(node, OpReturn, 0)
}
}
func (c *Compiler) emit(node ast.Node, opcode Opcode, operands ...int) int {
filePos := source.NoPos
if node != nil {
filePos = node.Pos()
}
inst := MakeInstruction(opcode, operands...)
pos := c.addInstruction(inst)
c.scopes[c.scopeIndex].sourceMap[pos] = filePos
if c.trace != nil {
c.printTrace(fmt.Sprintf("EMIT %s",
FormatInstructions(c.scopes[c.scopeIndex].instructions[pos:], pos)[0]))
}
return pos
}
func (c *Compiler) printTrace(a ...interface{}) {
const (
dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "
n = len(dots)
)
i := 2 * c.indent
for i > n {
_, _ = fmt.Fprint(c.trace, dots)
i -= n
}
_, _ = fmt.Fprint(c.trace, dots[0:i])
_, _ = fmt.Fprintln(c.trace, a...)
}
func trace(c *Compiler, msg string) *Compiler {
c.printTrace(msg, "{")
c.indent++
return c
}
func un(c *Compiler) {
c.indent--
c.printTrace("}")
}

View File

@@ -1,133 +0,0 @@
package compiler
import (
"fmt"
"github.com/d5/tengo/compiler/ast"
"github.com/d5/tengo/compiler/token"
)
func (c *Compiler) compileAssign(node ast.Node, lhs, rhs []ast.Expr, op token.Token) error {
numLHS, numRHS := len(lhs), len(rhs)
if numLHS > 1 || numRHS > 1 {
return c.errorf(node, "tuple assignment not allowed")
}
// resolve and compile left-hand side
ident, selectors := resolveAssignLHS(lhs[0])
numSel := len(selectors)
if op == token.Define && numSel > 0 {
// using selector on new variable does not make sense
return c.errorf(node, "operator ':=' not allowed with selector")
}
symbol, depth, exists := c.symbolTable.Resolve(ident)
if op == token.Define {
if depth == 0 && exists {
return c.errorf(node, "'%s' redeclared in this block", ident)
}
symbol = c.symbolTable.Define(ident)
} else {
if !exists {
return c.errorf(node, "unresolved reference '%s'", ident)
}
}
// +=, -=, *=, /=
if op != token.Assign && op != token.Define {
if err := c.Compile(lhs[0]); err != nil {
return err
}
}
// compile RHSs
for _, expr := range rhs {
if err := c.Compile(expr); err != nil {
return err
}
}
switch op {
case token.AddAssign:
c.emit(node, OpBinaryOp, int(token.Add))
case token.SubAssign:
c.emit(node, OpBinaryOp, int(token.Sub))
case token.MulAssign:
c.emit(node, OpBinaryOp, int(token.Mul))
case token.QuoAssign:
c.emit(node, OpBinaryOp, int(token.Quo))
case token.RemAssign:
c.emit(node, OpBinaryOp, int(token.Rem))
case token.AndAssign:
c.emit(node, OpBinaryOp, int(token.And))
case token.OrAssign:
c.emit(node, OpBinaryOp, int(token.Or))
case token.AndNotAssign:
c.emit(node, OpBinaryOp, int(token.AndNot))
case token.XorAssign:
c.emit(node, OpBinaryOp, int(token.Xor))
case token.ShlAssign:
c.emit(node, OpBinaryOp, int(token.Shl))
case token.ShrAssign:
c.emit(node, OpBinaryOp, int(token.Shr))
}
// compile selector expressions (right to left)
for i := numSel - 1; i >= 0; i-- {
if err := c.Compile(selectors[i]); err != nil {
return err
}
}
switch symbol.Scope {
case ScopeGlobal:
if numSel > 0 {
c.emit(node, OpSetSelGlobal, symbol.Index, numSel)
} else {
c.emit(node, OpSetGlobal, symbol.Index)
}
case ScopeLocal:
if numSel > 0 {
c.emit(node, OpSetSelLocal, symbol.Index, numSel)
} else {
if op == token.Define && !symbol.LocalAssigned {
c.emit(node, OpDefineLocal, symbol.Index)
} else {
c.emit(node, OpSetLocal, symbol.Index)
}
}
// mark the symbol as local-assigned
symbol.LocalAssigned = true
case ScopeFree:
if numSel > 0 {
c.emit(node, OpSetSelFree, symbol.Index, numSel)
} else {
c.emit(node, OpSetFree, symbol.Index)
}
default:
panic(fmt.Errorf("invalid assignment variable scope: %s", symbol.Scope))
}
return nil
}
func resolveAssignLHS(expr ast.Expr) (name string, selectors []ast.Expr) {
switch term := expr.(type) {
case *ast.SelectorExpr:
name, selectors = resolveAssignLHS(term.Expr)
selectors = append(selectors, term.Sel)
return
case *ast.IndexExpr:
name, selectors = resolveAssignLHS(term.Expr)
selectors = append(selectors, term.Index)
case *ast.Ident:
name = term.Name
}
return
}

View File

@@ -1,181 +0,0 @@
package compiler
import (
"github.com/d5/tengo/compiler/ast"
)
func (c *Compiler) compileForStmt(stmt *ast.ForStmt) error {
c.symbolTable = c.symbolTable.Fork(true)
defer func() {
c.symbolTable = c.symbolTable.Parent(false)
}()
// init statement
if stmt.Init != nil {
if err := c.Compile(stmt.Init); err != nil {
return err
}
}
// pre-condition position
preCondPos := len(c.currentInstructions())
// condition expression
postCondPos := -1
if stmt.Cond != nil {
if err := c.Compile(stmt.Cond); err != nil {
return err
}
// condition jump position
postCondPos = c.emit(stmt, OpJumpFalsy, 0)
}
// enter loop
loop := c.enterLoop()
// body statement
if err := c.Compile(stmt.Body); err != nil {
c.leaveLoop()
return err
}
c.leaveLoop()
// post-body position
postBodyPos := len(c.currentInstructions())
// post statement
if stmt.Post != nil {
if err := c.Compile(stmt.Post); err != nil {
return err
}
}
// back to condition
c.emit(stmt, OpJump, preCondPos)
// post-statement position
postStmtPos := len(c.currentInstructions())
if postCondPos >= 0 {
c.changeOperand(postCondPos, postStmtPos)
}
// update all break/continue jump positions
for _, pos := range loop.Breaks {
c.changeOperand(pos, postStmtPos)
}
for _, pos := range loop.Continues {
c.changeOperand(pos, postBodyPos)
}
return nil
}
func (c *Compiler) compileForInStmt(stmt *ast.ForInStmt) error {
c.symbolTable = c.symbolTable.Fork(true)
defer func() {
c.symbolTable = c.symbolTable.Parent(false)
}()
// for-in statement is compiled like following:
//
// for :it := iterator(iterable); :it.next(); {
// k, v := :it.get() // DEFINE operator
//
// ... body ...
// }
//
// ":it" is a local variable but will be conflict with other user variables
// because character ":" is not allowed.
// init
// :it = iterator(iterable)
itSymbol := c.symbolTable.Define(":it")
if err := c.Compile(stmt.Iterable); err != nil {
return err
}
c.emit(stmt, OpIteratorInit)
if itSymbol.Scope == ScopeGlobal {
c.emit(stmt, OpSetGlobal, itSymbol.Index)
} else {
c.emit(stmt, OpDefineLocal, itSymbol.Index)
}
// pre-condition position
preCondPos := len(c.currentInstructions())
// condition
// :it.HasMore()
if itSymbol.Scope == ScopeGlobal {
c.emit(stmt, OpGetGlobal, itSymbol.Index)
} else {
c.emit(stmt, OpGetLocal, itSymbol.Index)
}
c.emit(stmt, OpIteratorNext)
// condition jump position
postCondPos := c.emit(stmt, OpJumpFalsy, 0)
// enter loop
loop := c.enterLoop()
// assign key variable
if stmt.Key.Name != "_" {
keySymbol := c.symbolTable.Define(stmt.Key.Name)
if itSymbol.Scope == ScopeGlobal {
c.emit(stmt, OpGetGlobal, itSymbol.Index)
} else {
c.emit(stmt, OpGetLocal, itSymbol.Index)
}
c.emit(stmt, OpIteratorKey)
if keySymbol.Scope == ScopeGlobal {
c.emit(stmt, OpSetGlobal, keySymbol.Index)
} else {
c.emit(stmt, OpDefineLocal, keySymbol.Index)
}
}
// assign value variable
if stmt.Value.Name != "_" {
valueSymbol := c.symbolTable.Define(stmt.Value.Name)
if itSymbol.Scope == ScopeGlobal {
c.emit(stmt, OpGetGlobal, itSymbol.Index)
} else {
c.emit(stmt, OpGetLocal, itSymbol.Index)
}
c.emit(stmt, OpIteratorValue)
if valueSymbol.Scope == ScopeGlobal {
c.emit(stmt, OpSetGlobal, valueSymbol.Index)
} else {
c.emit(stmt, OpDefineLocal, valueSymbol.Index)
}
}
// body statement
if err := c.Compile(stmt.Body); err != nil {
c.leaveLoop()
return err
}
c.leaveLoop()
// post-body position
postBodyPos := len(c.currentInstructions())
// back to condition
c.emit(stmt, OpJump, preCondPos)
// post-statement position
postStmtPos := len(c.currentInstructions())
c.changeOperand(postCondPos, postStmtPos)
// update all break/continue jump positions
for _, pos := range loop.Breaks {
c.changeOperand(pos, postStmtPos)
}
for _, pos := range loop.Continues {
c.changeOperand(pos, postBodyPos)
}
return nil
}

Some files were not shown because too many files have changed in this diff Show More