Compare commits

..

2 Commits

Author SHA1 Message Date
MOZGIII
831b5b332f Switched to custom discordgo branch 2019-08-13 03:56:49 +03:00
MOZGIII
d93879bca5 Proper support for uploading files via webhooks for discord 2019-08-12 15:41:24 +03:00
3674 changed files with 247311 additions and 575885 deletions

View File

@@ -1,2 +0,0 @@
Dockerfile
tgs.Dockerfile

View File

@@ -1,3 +0,0 @@
go:
comments:
disabled: true

View File

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

View File

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

View File

@@ -1,71 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
name: "CodeQL"
on:
push:
branches: [master]
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
schedule:
- cron: '0 16 * * 1'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
language: ['go']
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@@ -1,57 +0,0 @@
name: Development
on: [push, pull_request]
jobs:
lint:
name: golangci-lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 20
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v2
with:
version: v1.29
args: "-v --new-from-rev HEAD~5"
test-build-upload:
strategy:
matrix:
go-version: [1.14.x, 1.15.x]
platform: [ubuntu-latest]
runs-on: ${{ matrix.platform }}
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Test
run: go test ./... -mod=vendor
- name: Build
run: |
mkdir -p output/{win,lin,arm,mac}
VERSION=$(git describe --tags)
GOOS=linux GOARCH=amd64 go build -mod=vendor -ldflags "-s -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o output/lin/matterbridge-$VERSION-linux-amd64
GOOS=windows GOARCH=amd64 go build -mod=vendor -ldflags "-s -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o output/win/matterbridge-$VERSION-windows-amd64.exe
GOOS=darwin GOARCH=amd64 go build -mod=vendor -ldflags "-s -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o output/mac/matterbridge-$VERSION-darwin-amd64
- name: Upload linux 64-bit
if: startsWith(matrix.go-version,'1.15')
uses: actions/upload-artifact@v2
with:
name: matterbridge-linux-64bit
path: output/lin
- name: Upload windows 64-bit
if: startsWith(matrix.go-version,'1.15')
uses: actions/upload-artifact@v2
with:
name: matterbridge-windows-64bit
path: output/win
- name: Upload darwin 64-bit
if: startsWith(matrix.go-version,'1.15')
uses: actions/upload-artifact@v2
with:
name: matterbridge-darwin-64bit
path: output/mac

3
.gitignore vendored
View File

@@ -1,6 +1,5 @@
# Exclude matterbridge binary
/matterbridge
/matterbridge.exe
matterbridge
# Exclude configuration file
matterbridge.toml

View File

@@ -23,7 +23,7 @@ run:
# default value is empty list, but next dirs are always skipped independently
# from this option's value:
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
skip-dirs: gateway/bridgemap$
skip-dirs:
# which files to skip: they will be analyzed, but issues from them
# won't be reported. Default value is empty list, but there is
@@ -174,15 +174,7 @@ linters:
- lll
- maligned
- prealloc
- wsl
- gomnd
- godox
- goerr113
- testpackage
- godot
- interfacer
- goheader
- noctx
# rules to deal with reported isues
issues:

View File

@@ -21,11 +21,7 @@ builds:
ldflags:
- -s -w -X main.githash={{.ShortCommit}}
archives:
-
id: matterbridge
builds:
- matterbridge
archive:
name_template: "{{ .Binary }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
format: binary
files:

56
.travis.yml Normal file
View File

@@ -0,0 +1,56 @@
language: go
go_import_path: github.com/42wim/matterbridge
# We have everything vendored so this helps TravisCI not run `go get ...`.
install: true
git:
depth: 200
notifications:
email: false
branches:
only:
- master
- /.*/
jobs:
include:
- stage: lint
# Run linting in one Go environment only.
script: ./ci/lint.sh
go: 1.12.x
env:
- GO111MODULE=on
- GOLANGCI_VERSION="v1.17.1"
- stage: test
# Run tests in a combination of Go environments.
script: ./ci/test.sh
go: 1.11.x
env:
- GO111MODULE=off
- script: ./ci/test.sh
go: 1.11.x
env:
- GO111MODULE=on
- script: ./ci/test.sh
go: 1.12.x
env:
- GO111MODULE=on
- REPORT_COVERAGE=1
- BINDEPLOY=1
before_deploy: /bin/bash ci/bintray.sh
deploy:
on:
all_branches: true
condition: $BINDEPLOY = 1
provider: bintray
edge:
branch: v1.8.47
file: ci/deploy.json
user: 42wim
key:
secure: "CeXXe6JOmt7HYR81MdWLua0ltQHhDdkIeRGBFbgd7hkb1wi8eF9DgpAcQrTso8NIlHNZmSAP46uhFgsRvkuezzX0ygalZ7DCJyAyn3sAMEh+UQSHV1WGThRehTtidqRGjetzsIGSwdrJOWil+XTfbO1Z8DGzfakhSuAZka8CM4BAoe3YeP9rYK8h+84x0GHfczvsLtXZ3mWLvQuwe4pK6+ItBCUg0ae7O7ZUpWHy0xQQkkWztY/6RAzXfaG7DuGjIw+20fhx3WOXRNpHCtZ6Bc3qERCpk0s1HhlQWlrN9wDaFTBWYwlvSnNgvxxMbNXJ6RrRJ0l0bA7FUswYwyroxhzrGLdzWDg8dHaQkypocngdalfhpsnoO9j3ApJhomUFJ3UoEq5nOGRUrKn8MPi+dP0zE4kNQ3e4VNa1ufNrvfpWolMg3xh8OXuhQdD5wIM5zFAbRJLqWSCVAjPq4DDPecmvXBOlIial7oa312lN5qnBnUjvAcxszZ+FUyDHT1Grxzna4tMwxY9obPzZUzm7359AOCCwIQFVB8GLqD2nwIstcXS0zGRz+fhviPipHuBa02q5bGUZwmkvrSNab0s8Jo7pCrel2Rz3nWPKaiCfq2WjbW1CLheSMkOQrjsdUd1hhbqNWFPUjJPInTc77NAKCfm5runv5uyowRLh4NNd0sI="

View File

@@ -1,16 +1,11 @@
FROM alpine AS builder
FROM alpine:edge
ENTRYPOINT ["/bin/matterbridge"]
COPY . /go/src/github.com/42wim/matterbridge
RUN apk update && apk add go git gcc musl-dev \
RUN apk update && apk add go git gcc musl-dev ca-certificates \
&& cd /go/src/github.com/42wim/matterbridge \
&& export GOPATH=/go \
&& go get \
&& go build -x -ldflags "-X main.githash=$(git log --pretty=format:'%h' -n 1)" -o /bin/matterbridge
FROM alpine
RUN apk --no-cache add ca-certificates mailcap
COPY --from=builder /bin/matterbridge /bin/matterbridge
RUN mkdir /etc/matterbridge \
&& touch /etc/matterbridge/matterbridge.toml \
&& ln -sf /matterbridge.toml /etc/matterbridge/matterbridge.toml
ENTRYPOINT ["/bin/matterbridge", "-conf", "/etc/matterbridge/matterbridge.toml"]
&& go build -x -ldflags "-X main.githash=$(git log --pretty=format:'%h' -n 1)" -o /bin/matterbridge \
&& rm -rf /go \
&& apk del --purge git go gcc musl-dev

296
README.md
View File

@@ -9,29 +9,26 @@ Letting people be where they want to be.<br />
<sup>
[Discord][mb-discord] |
[Gitter][mb-gitter] |
[IRC][mb-irc] |
[Keybase][mb-keybase] |
[Discord][mb-discord] |
[Matrix][mb-matrix] |
[Mattermost][mb-mattermost] |
[MSTeams][mb-msteams] |
[Rocket.Chat][mb-rocketchat] |
[Slack][mb-slack] |
[Telegram][mb-telegram] |
[Mattermost][mb-mattermost] |
[Rocket.Chat][mb-rocketchat] |
[XMPP][mb-xmpp] |
[Twitch][mb-twitch] |
[WhatsApp][mb-whatsapp] |
[XMPP][mb-xmpp] |
[Zulip][mb-zulip] |
[Telegram][mb-telegram] |
And more...
</sup>
---
----
[![Download stable](https://img.shields.io/github/release/42wim/matterbridge.svg?label=download%20stable)](https://github.com/42wim/matterbridge/releases/latest)
[![Download dev](https://img.shields.io/bintray/v/42wim/nightly/Matterbridge.svg?label=download%20dev&colorB=007ec6)](https://bintray.com/42wim/nightly/Matterbridge/_latestVersion)
[![Maintainability](https://api.codeclimate.com/v1/badges/82dff70ef2ba85a6173a/maintainability)](https://codeclimate.com/github/42wim/matterbridge/maintainability)
[![Test Coverage](https://api.codeclimate.com/v1/badges/82dff70ef2ba85a6173a/test_coverage)](https://codeclimate.com/github/42wim/matterbridge/test_coverage)<br />
<hr />
</div>
<div align="right"><sup>
@@ -44,161 +41,134 @@ And more...
</a>
</p>
# Table of Contents
- [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)
### 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)
## Features
- [Support bridging between any protocols](https://github.com/42wim/matterbridge/wiki/Features#support-bridging-between-any-protocols)
- [Support multiple gateways(bridges) for your protocols](https://github.com/42wim/matterbridge/wiki/Features#support-multiple-gatewaysbridges-for-your-protocols)
- [Message edits and deletes](https://github.com/42wim/matterbridge/wiki/Features#message-edits-and-deletes)
- Preserves threading when possible
- [Attachment / files handling](https://github.com/42wim/matterbridge/wiki/Features#attachment--files-handling)
- [Username and avatar spoofing](https://github.com/42wim/matterbridge/wiki/Features#username-and-avatar-spoofing)
- [Private groups](https://github.com/42wim/matterbridge/wiki/Features#private-groups)
- [API](https://github.com/42wim/matterbridge/wiki/Features#api)
* [Support bridging between any protocols](https://github.com/42wim/matterbridge/wiki/Features#support-bridging-between-any-protocols)
* [Support multiple gateways(bridges) for your protocols](https://github.com/42wim/matterbridge/wiki/Features#support-multiple-gatewaysbridges-for-your-protocols)
* [Message edits and deletes](https://github.com/42wim/matterbridge/wiki/Features#message-edits-and-deletes)
* Preserves threading when possible
* [Attachment / files handling](https://github.com/42wim/matterbridge/wiki/Features#attachment--files-handling)
* [Username and avatar spoofing](https://github.com/42wim/matterbridge/wiki/Features#username-and-avatar-spoofing)
* [Private groups](https://github.com/42wim/matterbridge/wiki/Features#private-groups)
* [API](https://github.com/42wim/matterbridge/wiki/Features#api)
### Natively supported
- [Discord](https://discordapp.com)
- [Gitter](https://gitter.im)
- [IRC](http://www.mirc.com/servers.html)
- [Keybase](https://keybase.io)
- [Matrix](https://matrix.org)
- [Mattermost](https://github.com/mattermost/mattermost-server/) 4.x, 5.x
- [Microsoft Teams](https://teams.microsoft.com)
- [Mumble](https://www.mumble.info/)
- [Nextcloud Talk](https://nextcloud.com/talk/)
- [Rocket.chat](https://rocket.chat)
- [Slack](https://slack.com)
- [Ssh-chat](https://github.com/shazow/ssh-chat)
- [Steam](https://store.steampowered.com/)
- [Telegram](https://telegram.org)
- [Twitch](https://twitch.tv)
- [WhatsApp](https://www.whatsapp.com/)
- [XMPP](https://xmpp.org)
- [Zulip](https://zulipchat.com)
* [Mattermost](https://github.com/mattermost/mattermost-server/) 4.x, 5.x
* [IRC](http://www.mirc.com/servers.html)
* [XMPP](https://xmpp.org)
* [Gitter](https://gitter.im)
* [Slack](https://slack.com)
* [Discord](https://discordapp.com)
* [Telegram](https://telegram.org)
* [Rocket.chat](https://rocket.chat)
* [Matrix](https://matrix.org)
* [Steam](https://store.steampowered.com/)
* [Twitch](https://twitch.tv)
* [Ssh-chat](https://github.com/shazow/ssh-chat)
* [WhatsApp](https://www.whatsapp.com/)
* [Zulip](https://zulipchat.com)
### 3rd party via matterbridge api
- [Discourse](https://github.com/DeclanHoare/matterbabble)
- [Facebook messenger](https://github.com/VictorNine/fbridge)
- [Minecraft](https://github.com/elytra/MatterLink)
- [Reddit](https://github.com/bonehurtingjuice/mattereddit)
- [Counter-Strike, half-life and more](https://forums.alliedmods.net/showthread.php?t=319430)
- [MatterAMXX](https://github.com/GabeIggy/MatterAMXX)
* [Minecraft](https://github.com/elytra/MatterLink)
* [Reddit](https://github.com/bonehurtingjuice/mattereddit)
* [Facebook messenger](https://github.com/VictorNine/fbridge)
* [Discourse](https://github.com/DeclanHoare/matterbabble)
### API
The API is basic at the moment.
More info and examples on the [wiki](https://github.com/42wim/matterbridge/wiki/Api).
Used by the projects below. Feel free to make a PR to add your project to this list.
- [MatterLink](https://github.com/elytra/MatterLink) (Matterbridge link for Minecraft Server chat)
- [pyCord](https://github.com/NikkyAI/pyCord) (crossplatform chatbot)
- [Mattereddit](https://github.com/bonehurtingjuice/mattereddit) (Reddit chat support)
- [fbridge](https://github.com/VictorNine/fbridge) (Facebook messenger support)
- [matterbabble](https://github.com/DeclanHoare/matterbabble) (Discourse support)
- [MatterAMXX](https://forums.alliedmods.net/showthread.php?t=319430) (Counter-Strike, half-life and more via AMXX mod)
* [MatterLink](https://github.com/elytra/MatterLink) (Matterbridge link for Minecraft Server chat)
* [pyCord](https://github.com/NikkyAI/pyCord) (crossplatform chatbot)
* [Mattereddit](https://github.com/bonehurtingjuice/mattereddit) (Reddit chat support)
* [fbridge](https://github.com/VictorNine/fbridge) (Facebook messenger support)
* [matterbabble](https://github.com/DeclanHoare/matterbabble) (Discourse support)
## Chat with us
Questions or want to test on your favorite platform? Join below:
- [Discord][mb-discord]
- [Gitter][mb-gitter]
- [IRC][mb-irc]
- [Keybase][mb-keybase]
- [Matrix][mb-matrix]
- [Mattermost][mb-mattermost]
- [Rocket.Chat][mb-rocketchat]
- [Slack][mb-slack]
- [Telegram][mb-telegram]
- [Twitch][mb-twitch]
- [XMPP][mb-xmpp] (matterbridge@conference.jabber.de)
- [Zulip][mb-zulip]
* [Gitter][mb-gitter]
* [IRC][mb-irc]
* [Discord][mb-discord]
* [Matrix][mb-matrix]
* [Slack][mb-slack]
* [Mattermost][mb-mattermost]
* [Rocket.Chat][mb-rocketchat]
* [XMPP][mb-xmpp] (matterbridge@conference.jabber.de)
* [Twitch][mb-twitch]
* [Zulip][mb-zulip]
* [Telegram][mb-telegram]
## Screenshots
See <https://github.com/42wim/matterbridge/wiki>
See https://github.com/42wim/matterbridge/wiki
## Installing / upgrading
### Binaries
- Latest stable release [v1.20.0](https://github.com/42wim/matterbridge/releases/latest)
- Development releases (follows master) can be downloaded [here](https://github.com/42wim/matterbridge/actions) selecting the latest green build and then artifacts.
* Latest stable release [v1.15.1](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.
### Packages
- [Overview](https://repology.org/metapackage/matterbridge/versions)
- [snap](https://snapcraft.io/matterbridge)
* [Overview](https://repology.org/metapackage/matterbridge/versions)
## Building
Most people just want to use binaries, you can find those [here](https://github.com/42wim/matterbridge/releases/latest)
If you really want to build from source, follow these instructions:
Go 1.12+ is required. Make sure you have [Go](https://golang.org/doc/install) properly installed.
Go 1.9+ is required. Make sure you have [Go](https://golang.org/doc/install) properly installed, including setting up your [GOPATH](https://golang.org/doc/code.html#GOPATH).
```bash
After Go is setup, download matterbridge to your $GOPATH directory.
```
cd $GOPATH
go get github.com/42wim/matterbridge
```
You should now have matterbridge binary in the ~/go/bin directory:
You should now have matterbridge binary in the bin directory:
```bash
$ ls ~/go/bin/
```
$ ls bin/
matterbridge
```
## Configuration
### Basic configuration
See [howto](https://github.com/42wim/matterbridge/wiki/How-to-create-your-config) for a step by step walkthrough for creating your configuration.
### Settings
All possible [settings](https://github.com/42wim/matterbridge/wiki/Settings) for each bridge.
### Advanced configuration
- [matterbridge.toml.sample](https://github.com/42wim/matterbridge/blob/master/matterbridge.toml.sample) for documentation and an example.
* [matterbridge.toml.sample](https://github.com/42wim/matterbridge/blob/master/matterbridge.toml.sample) for documentation and an example.
### Examples
#### Bridge mattermost (off-topic) - irc (#testing)
```toml
[irc]
[irc.freenode]
@@ -227,7 +197,6 @@ enable=true
```
#### Bridge slack (#general) - discord (general)
```toml
[slack]
[slack.test]
@@ -259,7 +228,7 @@ RemoteNickFormat="[{PROTOCOL}/{BRIDGE}] <{NICK}> "
See [howto](https://github.com/42wim/matterbridge/wiki/How-to-create-your-config) for a step by step walkthrough for creating your configuration.
```bash
```
Usage of ./matterbridge:
-conf string
config file (default "matterbridge.toml")
@@ -272,11 +241,12 @@ Usage of ./matterbridge:
```
### Docker
Please take a look at the [Docker Wiki page](https://github.com/42wim/matterbridge/wiki/Deploy:-Docker) for more information.
Create your matterbridge.toml file locally eg in `/tmp/matterbridge.toml`
```
docker run -ti -v /tmp/matterbridge.toml:/matterbridge.toml 42wim/matterbridge
```
## Changelog
See [changelog.md](https://github.com/42wim/matterbridge/blob/master/changelog.md)
## FAQ
@@ -284,30 +254,28 @@ See [changelog.md](https://github.com/42wim/matterbridge/blob/master/changelog.m
See [FAQ](https://github.com/42wim/matterbridge/wiki/FAQ)
## Related projects
- [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)
- [mattereddit](https://github.com/bonehurtingjuice/mattereddit)
- [matterlink](https://github.com/elytra/MatterLink)
- [mattermost-plugin](https://github.com/matterbridge/mattermost-plugin) - Run matterbridge as a plugin in mattermost
- [pyCord](https://github.com/NikkyAI/pyCord) (crossplatform chatbot)
- [fbridge](https://github.com/VictorNine/fbridge) (Facebook messenger support)
- [isla](https://github.com/alphachung/isla) (Bot for Discord-Telegram groups used alongside matterbridge)
- [matterbabble](https://github.com/DeclanHoare/matterbabble) (Connect Discourse threads to Matterbridge)
* [FOSSRIT/infrastructure - roles/matterbridge](https://github.com/FOSSRIT/infrastructure/tree/master/roles/matterbridge) (Ansible role used to automate deployments of 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)
* [mattereddit](https://github.com/bonehurtingjuice/mattereddit)
* [matterlink](https://github.com/elytra/MatterLink)
* [mattermost-plugin](https://github.com/matterbridge/mattermost-plugin) - Run matterbridge as a plugin in mattermost
* [pyCord](https://github.com/NikkyAI/pyCord) (crossplatform chatbot)
* [fbridge](https://github.com/VictorNine/fbridge) (Facebook messenger support)
* [isla](https://github.com/alphachung/isla) (Bot for Discord-Telegram groups used alongside matterbridge)
* [matterbabble](https://github.com/DeclanHoare/matterbabble) (Connect Discourse threads to Matterbridge)
## Articles
- [matterbridge on kubernetes](https://medium.freecodecamp.org/using-kubernetes-to-deploy-a-chat-gateway-or-when-technology-works-like-its-supposed-to-a169a8cd69a3)
- <https://mattermost.com/blog/connect-irc-to-mattermost/>
- <https://blog.valvin.fr/2016/09/17/mattermost-et-un-channel-irc-cest-possible/>
- <https://blog.brightscout.com/top-10-mattermost-integrations/>
- <https://www.algoo.fr/blog/2018/01/19/recouvrez-votre-liberte-en-quittant-slack-pour-un-mattermost-auto-heberge/>
- <https://kopano.com/blog/matterbridge-bridging-mattermost-chat/>
- <https://www.stitcher.com/s/?eid=52382713>
- <https://daniele.tech/2019/02/how-to-use-matterbridge-to-connect-2-different-slack-workspaces/>
- <https://userlinux.net/mattermost-and-matterbridge.html>
* [matterbridge on kubernetes](https://medium.freecodecamp.org/using-kubernetes-to-deploy-a-chat-gateway-or-when-technology-works-like-its-supposed-to-a169a8cd69a3)
* https://mattermost.com/blog/connect-irc-to-mattermost/
* https://blog.valvin.fr/2016/09/17/mattermost-et-un-channel-irc-cest-possible/
* https://blog.brightscout.com/top-10-mattermost-integrations/
* http://bencey.co.nz/2018/09/17/bridge/
* https://www.algoo.fr/blog/2018/01/19/recouvrez-votre-liberte-en-quittant-slack-pour-un-mattermost-auto-heberge/
* https://kopano.com/blog/matterbridge-bridging-mattermost-chat/
* https://www.stitcher.com/s/?eid=52382713
* https://daniele.tech/2019/02/how-to-use-matterbridge-to-connect-2-different-slack-workspaces/
## Thanks
@@ -319,42 +287,34 @@ See [FAQ](https://github.com/42wim/matterbridge/wiki/FAQ)
</p>
Matterbridge wouldn't exist without these libraries:
- discord - <https://github.com/bwmarrin/discordgo>
- echo - <https://github.com/labstack/echo>
- gitter - <https://github.com/sromku/go-gitter>
- gops - <https://github.com/google/gops>
- gozulipbot - <https://github.com/ifo/gozulipbot>
- gumble - <https://github.com/layeh/gumble>
- irc - <https://github.com/lrstanley/girc>
- keybase - <https://github.com/keybase/go-keybase-chat-bot>
- matrix - <https://github.com/matrix-org/gomatrix>
- mattermost - <https://github.com/mattermost/mattermost-server>
- msgraph.go - <https://github.com/yaegashi/msgraph.go>
- mumble - <https://github.com/layeh/gumble>
- nctalk - <https://github.com/gary-kim/go-nc-talk>
- slack - <https://github.com/nlopes/slack>
- sshchat - <https://github.com/shazow/ssh-chat>
- steam - <https://github.com/Philipp15b/go-steam>
- telegram - <https://github.com/go-telegram-bot-api/telegram-bot-api>
- tengo - <https://github.com/d5/tengo>
- whatsapp - <https://github.com/Rhymen/go-whatsapp>
- xmpp - <https://github.com/mattn/go-xmpp>
- zulip - <https://github.com/ifo/gozulipbot>
* discord - https://github.com/bwmarrin/discordgo
* echo - https://github.com/labstack/echo
* gitter - https://github.com/sromku/go-gitter
* gops - https://github.com/google/gops
* gozulipbot - https://github.com/ifo/gozulipbot
* irc - https://github.com/lrstanley/girc
* mattermost - https://github.com/mattermost/mattermost-server
* matrix - https://github.com/matrix-org/gomatrix
* sshchat - https://github.com/shazow/ssh-chat
* slack - https://github.com/nlopes/slack
* steam - https://github.com/Philipp15b/go-steam
* telegram - https://github.com/go-telegram-bot-api/telegram-bot-api
* xmpp - https://github.com/mattn/go-xmpp
* whatsapp - https://github.com/Rhymen/go-whatsapp/
* zulip - https://github.com/ifo/gozulipbot
* tengo - https://github.com/d5/tengo
<!-- Links -->
[mb-discord]: https://discord.gg/AkKPtrQ
[mb-gitter]: https://gitter.im/42wim/matterbridge
[mb-irc]: https://webchat.freenode.net/?channels=matterbridgechat
[mb-keybase]: https://keybase.io/team/matterbridge
[mb-discord]: https://discord.gg/AkKPtrQ
[mb-matrix]: https://riot.im/app/#/room/#matterbridge:matrix.org
[mb-slack]: https://join.slack.com/matterbridgechat/shared_invite/MjEwODMxNjU1NDMwLTE0OTk2MTU3NTMtMzZkZmRiNDZhOA
[mb-mattermost]: https://framateam.org/signup_user_complete/?id=tfqm33ggop8x3qgu4boeieta6e
[mb-msteams]: https://teams.microsoft.com/join/hj92x75gd3y7
[mb-rocketchat]: https://open.rocket.chat/channel/matterbridge
[mb-slack]: https://join.slack.com/t/matterbridgechat/shared_invite/zt-2ourq2h2-7YvyYBq2WFGC~~zEzA68_Q
[mb-telegram]: https://t.me/Matterbridge
[mb-xmpp]: https://inverse.chat/
[mb-twitch]: https://www.twitch.tv/matterbridge
[mb-whatsapp]: https://www.whatsapp.com/
[mb-xmpp]: https://inverse.chat/
[mb-zulip]: https://matterbridge.zulipchat.com/register/
[mb-telegram]: https://t.me/Matterbridge

View File

@@ -6,20 +6,17 @@ import (
"sync"
"time"
"gopkg.in/olahol/melody.v1"
"github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
ring "github.com/zfjagann/golang-ring"
"github.com/zfjagann/golang-ring"
)
type API struct {
Messages ring.Ring
sync.RWMutex
*bridge.Config
mrouter *melody.Melody
}
type Message struct {
@@ -35,32 +32,6 @@ func New(cfg *bridge.Config) bridge.Bridger {
e := echo.New()
e.HideBanner = true
e.HidePort = true
b.mrouter = melody.New()
b.mrouter.HandleMessage(func(s *melody.Session, msg []byte) {
message := config.Message{}
err := json.Unmarshal(msg, &message)
if err != nil {
b.Log.Errorf("failed to decode message from byte[] '%s'", string(msg))
return
}
b.handleWebsocketMessage(message, s)
})
b.mrouter.HandleConnect(func(session *melody.Session) {
greet := b.getGreeting()
data, err := json.Marshal(greet)
if err != nil {
b.Log.Errorf("failed to encode message '%v'", greet)
return
}
err = session.Write(data)
if err != nil {
b.Log.Errorf("failed to write message '%s'", string(data))
return
}
// TODO: send message history buffer from `b.Messages` here
})
b.Messages = ring.Ring{}
if b.GetInt("Buffer") != 0 {
b.Messages.SetCapacity(b.GetInt("Buffer"))
@@ -70,17 +41,9 @@ func New(cfg *bridge.Config) bridge.Bridger {
return key == b.GetString("Token"), nil
}))
}
// Set RemoteNickFormat to a sane default
if !b.IsKeySet("RemoteNickFormat") {
b.Log.Debugln("RemoteNickFormat is unset, defaulting to \"{NICK}\"")
b.Config.Config.Viper().Set(b.GetConfigKey("RemoteNickFormat"), "{NICK}")
}
e.GET("/api/health", b.handleHealthcheck)
e.GET("/api/messages", b.handleMessages)
e.GET("/api/stream", b.handleStream)
e.GET("/api/websocket", b.handleWebsocket)
e.POST("/api/message", b.handlePostMessage)
go func() {
if b.GetString("BindAddress") == "" {
@@ -95,13 +58,13 @@ func New(cfg *bridge.Config) bridge.Bridger {
func (b *API) Connect() error {
return nil
}
func (b *API) Disconnect() error {
return nil
}
}
func (b *API) JoinChannel(channel config.ChannelInfo) error {
return nil
}
func (b *API) Send(msg config.Message) (string, error) {
@@ -111,14 +74,7 @@ func (b *API) Send(msg config.Message) (string, error) {
if msg.Event == config.EventMsgDelete {
return "", nil
}
b.Log.Debugf("enqueueing message from %s on ring buffer", msg.Username)
b.Messages.Enqueue(msg)
data, err := json.Marshal(msg)
if err != nil {
b.Log.Errorf("failed to encode message '%s'", msg)
}
_ = b.mrouter.Broadcast(data)
b.Messages.Enqueue(&msg)
return "", nil
}
@@ -150,23 +106,18 @@ func (b *API) handleMessages(c echo.Context) error {
return nil
}
func (b *API) getGreeting() config.Message {
return config.Message{
Event: config.EventAPIConnected,
Timestamp: time.Now(),
}
}
func (b *API) handleStream(c echo.Context) error {
c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
c.Response().WriteHeader(http.StatusOK)
greet := b.getGreeting()
greet := config.Message{
Event: config.EventAPIConnected,
Timestamp: time.Now(),
}
if err := json.NewEncoder(c.Response()).Encode(greet); err != nil {
return err
}
c.Response().Flush()
for {
// TODO: this causes issues, messages should be broadcasted to all connected clients
msg := b.Messages.Dequeue()
if msg != nil {
if err := json.NewEncoder(c.Response()).Encode(msg); err != nil {
@@ -177,31 +128,3 @@ func (b *API) handleStream(c echo.Context) error {
time.Sleep(200 * time.Millisecond)
}
}
func (b *API) handleWebsocketMessage(message config.Message, s *melody.Session) {
message.Channel = "api"
message.Protocol = "api"
message.Account = b.Account
message.ID = ""
message.Timestamp = time.Now()
data, err := json.Marshal(message)
if err != nil {
b.Log.Errorf("failed to encode message for loopback '%v'", message)
return
}
_ = b.mrouter.BroadcastOthers(data, s)
b.Log.Debugf("Sending websocket message from %s on %s to gateway", message.Username, "api")
b.Remote <- message
}
func (b *API) handleWebsocket(c echo.Context) error {
err := b.mrouter.HandleRequest(c.Response(), c.Request())
if err != nil {
b.Log.Errorf("error in websocket handling '%v'", err)
return err
}
return nil
}

View File

@@ -1,10 +1,8 @@
package bridge
import (
"log"
"strings"
"sync"
"time"
"github.com/42wim/matterbridge/bridge/config"
"github.com/sirupsen/logrus"
@@ -43,10 +41,6 @@ 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]
@@ -75,7 +69,6 @@ func (b *Bridge) joinChannels(channels map[string]config.ChannelInfo, exists map
for ID, channel := range channels {
if !exists[ID] {
b.Log.Infof("%s: joining %s (ID: %s)", b.Account, channel.Name, ID)
time.Sleep(time.Duration(b.GetInt("JoinDelay")) * time.Millisecond)
err := b.JoinChannel(channel)
if err != nil {
return err
@@ -86,16 +79,8 @@ func (b *Bridge) joinChannels(channels map[string]config.ChannelInfo, exists map
return nil
}
func (b *Bridge) GetConfigKey(key string) string {
return b.Account + "." + key
}
func (b *Bridge) IsKeySet(key string) bool {
return b.Config.IsKeySet(b.GetConfigKey(key)) || b.Config.IsKeySet("general."+key)
}
func (b *Bridge) GetBool(key string) bool {
val, ok := b.Config.GetBool(b.GetConfigKey(key))
val, ok := b.Config.GetBool(b.Account + "." + key)
if !ok {
val, _ = b.Config.GetBool("general." + key)
}
@@ -103,7 +88,7 @@ func (b *Bridge) GetBool(key string) bool {
}
func (b *Bridge) GetInt(key string) int {
val, ok := b.Config.GetInt(b.GetConfigKey(key))
val, ok := b.Config.GetInt(b.Account + "." + key)
if !ok {
val, _ = b.Config.GetInt("general." + key)
}
@@ -111,7 +96,7 @@ func (b *Bridge) GetInt(key string) int {
}
func (b *Bridge) GetString(key string) string {
val, ok := b.Config.GetString(b.GetConfigKey(key))
val, ok := b.Config.GetString(b.Account + "." + key)
if !ok {
val, _ = b.Config.GetString("general." + key)
}
@@ -119,7 +104,7 @@ func (b *Bridge) GetString(key string) string {
}
func (b *Bridge) GetStringSlice(key string) []string {
val, ok := b.Config.GetStringSlice(b.GetConfigKey(key))
val, ok := b.Config.GetStringSlice(b.Account + "." + key)
if !ok {
val, _ = b.Config.GetStringSlice("general." + key)
}
@@ -127,7 +112,7 @@ func (b *Bridge) GetStringSlice(key string) []string {
}
func (b *Bridge) GetStringSlice2D(key string) [][]string {
val, ok := b.Config.GetStringSlice2D(b.GetConfigKey(key))
val, ok := b.Config.GetStringSlice2D(b.Account + "." + key)
if !ok {
val, _ = b.Config.GetStringSlice2D("general." + key)
}

View File

@@ -3,8 +3,6 @@ package config
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"time"
@@ -78,29 +76,23 @@ 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
HTMLDisable bool // matrix
IconURL string // mattermost, slack
IgnoreFailureOnStart bool // general
IgnoreNicks string // all protocols
IgnoreMessages string // all protocols
Jid string // xmpp
JoinDelay string // all protocols
Label string // all protocols
Login string // mattermost, matrix
LogFile string // general
MediaDownloadBlackList []string
MediaDownloadPath string // Basically MediaServerUpload, but instead of uploading it, just write it to a file on the same server.
MediaDownloadSize int // all protocols
MediaServerDownload string
MediaServerUpload string
MediaConvertTgs string // telegram
MediaConvertWebPToPNG bool // telegram
MessageDelay int // IRC, time in millisecond to wait between messages
MessageFormat string // telegram
@@ -117,21 +109,19 @@ type Protocol struct {
NicksPerRow int // mattermost, slack
NoHomeServerSuffix bool // matrix
NoSendJoinPart bool // all protocols
NoTLS bool // mattermost, xmpp
NoTLS bool // mattermost
Password string // IRC,mattermost,XMPP,matrix
PrefixMessagesWithNick bool // mattemost, slack
PreserveThreading bool // slack
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
@@ -139,22 +129,18 @@ type Protocol struct {
SkipTLSVerify bool // IRC, mattermost
SkipVersionCheck bool // mattermost
StripNick bool // all protocols
StripMarkdown bool // irc
SyncTopic bool // slack
TengoModifyMessage string // general
Team string // mattermost, keybase
TeamID string // msteams
TenantID string // msteams
Team string // mattermost
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
UseFirstName bool // telegram
UseUserName bool // discord, matrix
UseUserName bool // discord
UseInsecureURL bool // telegram
VerboseJoinPart bool // IRC
WebhookBindAddress string // mattermost, slack
@@ -212,8 +198,6 @@ type BridgeValues struct {
SSHChat map[string]Protocol
WhatsApp map[string]Protocol // TODO is this struct used? Search for "SlackLegacy" for example didn't return any results
Zulip map[string]Protocol
Keybase map[string]Protocol
Mumble map[string]Protocol
General Protocol
Tengo Tengo
Gateway []Gateway
@@ -221,9 +205,7 @@ type BridgeValues struct {
}
type Config interface {
Viper() *viper.Viper
BridgeValues() *BridgeValues
IsKeySet(key string) bool
GetBool(key string) (bool, bool)
GetInt(key string) (int, bool)
GetString(key string) (string, bool)
@@ -249,17 +231,7 @@ func NewConfig(rootLogger *logrus.Logger, cfgfile string) Config {
logger.Fatalf("Failed to read configuration file: %#v", err)
}
cfgtype := detectConfigType(cfgfile)
mycfg := newConfigFromString(logger, input, cfgtype)
if mycfg.cv.General.LogFile != "" {
logfile, err := os.OpenFile(mycfg.cv.General.LogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
if err == nil {
logger.Info("Opening log file ", mycfg.cv.General.LogFile)
rootLogger.Out = logfile
} else {
logger.Warn("Failed to open ", mycfg.cv.General.LogFile)
}
}
mycfg := newConfigFromString(logger, input)
if mycfg.cv.General.MediaDownloadSize == 0 {
mycfg.cv.General.MediaDownloadSize = 1000000
}
@@ -270,26 +242,14 @@ 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, "toml")
return newConfigFromString(logger, input)
}
func newConfigFromString(logger *logrus.Entry, input []byte, cfgtype string) *config {
viper.SetConfigType(cfgtype)
func newConfigFromString(logger *logrus.Entry, input []byte) *config {
viper.SetConfigType("toml")
viper.SetEnvPrefix("matterbridge")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
viper.AutomaticEnv()
@@ -313,16 +273,6 @@ func (c *config) BridgeValues() *BridgeValues {
return c.cv
}
func (c *config) Viper() *viper.Viper {
return c.v
}
func (c *config) IsKeySet(key string) bool {
c.RLock()
defer c.RUnlock()
return c.v.IsSet(key)
}
func (c *config) GetBool(key string) (bool, bool) {
c.RLock()
defer c.RUnlock()
@@ -382,11 +332,6 @@ type TestConfig struct {
Overrides map[string]interface{}
}
func (c *TestConfig) IsKeySet(key string) bool {
_, ok := c.Overrides[key]
return ok || c.Config.IsKeySet(key)
}
func (c *TestConfig) GetBool(key string) (bool, bool) {
val, ok := c.Overrides[key]
if ok {

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/matterbridge/discordgo"
"github.com/bwmarrin/discordgo"
)
const MessageLength = 1950
@@ -21,7 +21,7 @@ type Bdiscord struct {
c *discordgo.Session
nick string
userID string
useChannelID bool
guildID string
webhookID string
webhookToken string
@@ -72,7 +72,6 @@ func (b *Bdiscord) Connect() error {
}
b.Log.Info("Connection succeeded")
b.c.AddHandler(b.messageCreate)
b.c.AddHandler(b.messageTyping)
b.c.AddHandler(b.memberUpdate)
b.c.AddHandler(b.messageUpdate)
b.c.AddHandler(b.messageDelete)
@@ -93,7 +92,6 @@ func (b *Bdiscord) Connect() error {
}
serverName := strings.Replace(b.GetString("Server"), "ID:", "", -1)
b.nick = userinfo.Username
b.userID = userinfo.ID
b.channelsMutex.Lock()
for _, guild := range guilds {
if guild.Name == serverName || guild.ID == serverName {
@@ -116,42 +114,31 @@ 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
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 permsErr != nil || perms&manageWebhooks != manageWebhooks {
b.Log.Warnf("Can't manage webhooks in channel \"%s\"", channel.Name)
b.canEditWebhooks = false
b.Log.Info("Webhook editing is disabled because of ratelimit issues")
/*
}
}
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()
@@ -186,6 +173,10 @@ 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
}
@@ -197,14 +188,6 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
return "", fmt.Errorf("Could not find channelID for %v", msg.Channel)
}
if msg.Event == config.EventUserTyping {
if b.GetBool("ShowUserTyping") {
err := b.c.ChannelTyping(channelID)
return "", err
}
return "", nil
}
// Make a action /me of the message
if msg.Event == config.EventUserAction {
msg.Text = "_" + msg.Text + "_"
@@ -228,13 +211,34 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
// Use webhook to send the message
if wID != "" && msg.Event != config.EventMsgDelete {
// skip events
if msg.Event != "" && msg.Event != config.EventUserAction && msg.Event != config.EventJoinLeave && msg.Event != config.EventTopicChange {
if msg.Event != "" && msg.Event != config.EventJoinLeave && msg.Event != config.EventTopicChange {
return "", nil
}
// If we are editing a message, delete the old message
if msg.ID != "" {
b.Log.Debugf("Deleting edited webhook message")
err := b.c.ChannelMessageDelete(channelID, msg.ID)
if err != nil {
b.Log.Errorf("Could not delete edited webhook message: %s", err)
}
}
b.Log.Debugf("Broadcasting using Webhook")
for _, f := range msg.Extra["file"] {
fi := f.(config.FileInfo)
if fi.Comment != "" {
msg.Text += fi.Comment + ": "
}
if fi.URL != "" {
msg.Text = fi.URL
if fi.Comment != "" {
msg.Text = fi.Comment + ": " + fi.URL
}
}
}
// skip empty messages
if msg.Text == "" && (msg.Extra == nil || len(msg.Extra["file"]) == 0) {
b.Log.Debugf("Skipping empty message %#v", msg)
if msg.Text == "" {
return "", nil
}
@@ -244,25 +248,11 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
if len(msg.Username) > 32 {
msg.Username = msg.Username[0:32]
}
if msg.ID != "" {
b.Log.Debugf("Editing webhook message")
uri := discordgo.EndpointWebhookToken(wID, wToken) + "/messages/" + msg.ID
_, err := b.c.RequestWithBucketID("PATCH", uri, discordgo.WebhookParams{
Content: msg.Text,
Username: msg.Username,
}, discordgo.EndpointWebhookToken("", ""))
if err == nil {
return msg.ID, nil
}
b.Log.Errorf("Could not edit webhook message: %s", err)
}
b.Log.Debugf("Broadcasting using Webhook")
// if we have a global webhook for this Discord account, and permission
// to modify webhooks (previously verified), then set its channel to
// the message channel before using it.
// the message channel before using it
// TODO: this isn't necessary if the last message from this webhook was
// sent to the current channel
if isGlobalWebhook && b.canEditWebhooks {
b.Log.Debugf("Setting webhook channel to \"%s\"", msg.Channel)
_, err := b.c.WebhookEdit(wID, "", "", channelID)
@@ -271,16 +261,10 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
return "", err
}
}
b.Log.Debugf("Processing webhook sending for message %#v", msg)
msg, err := b.webhookSend(&msg, wID, wToken)
if err != nil {
b.Log.Errorf("Could not broadcast via webook for message %#v: %s", msg, err)
return "", err
}
if msg == nil {
return "", nil
}
return msg.ID, nil
}
@@ -323,7 +307,7 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
if err != nil {
return "", err
}
return res.ID, nil
return res.ID, err
}
// useWebhook returns true if we have a webhook defined somewhere
@@ -392,29 +376,10 @@ func (b *Bdiscord) handleUploadFile(msg *config.Message, channelID string) (stri
// uploads (from slack, telegram or mattermost).
// Returns messageID and error.
func (b *Bdiscord) webhookSend(msg *config.Message, webhookID, token string) (*discordgo.Message, error) {
var (
res *discordgo.Message
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
}
}
}
var err error
// WebhookParams can have either `Content` or `File`.
// We can't send empty messages.
if msg.Text != "" {
res, err = b.c.WebhookExecute(
res, err := b.c.WebhookExecute(
webhookID,
token,
true,
@@ -425,8 +390,7 @@ func (b *Bdiscord) webhookSend(msg *config.Message, webhookID, token string) (*d
},
)
if err != nil {
b.Log.Errorf("Could not send text (%s) for message %#v: %s", msg.Text, msg, err)
}
return nil, err
}
if msg.Extra != nil {
@@ -437,11 +401,7 @@ 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(
_, err := b.c.WebhookExecute(
webhookID,
token,
false,
@@ -449,21 +409,13 @@ func (b *Bdiscord) webhookSend(msg *config.Message, webhookID, token string) (*d
Username: msg.Username,
AvatarURL: msg.Avatar,
File: &file,
Content: content,
},
)
if e2 != nil {
b.Log.Errorf("Could not send file %#v for message %#v: %s", file, msg, e2)
if err != nil {
return nil, fmt.Errorf("file upload failed: %s", err)
}
}
}
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("")
return res, nil
}

View File

@@ -2,13 +2,15 @@ package bdiscord
import (
"github.com/42wim/matterbridge/bridge/config"
"github.com/matterbridge/discordgo"
"github.com/bwmarrin/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
@@ -22,7 +24,11 @@ func (b *Bdiscord) messageDeleteBulk(s *discordgo.Session, m *discordgo.MessageD
ID: msgID,
Event: config.EventMsgDelete,
Text: config.EventMsgDelete,
Channel: b.getChannelName(m.ChannelID),
Channel: "ID:" + m.ChannelID,
}
if !b.useChannelID {
rmsg.Channel = b.getChannelName(m.ChannelID)
}
b.Log.Debugf("<= Sending message from %s to gateway", b.Account)
@@ -31,21 +37,6 @@ func (b *Bdiscord) messageDeleteBulk(s *discordgo.Session, m *discordgo.MessageD
}
}
func (b *Bdiscord) messageTyping(s *discordgo.Session, m *discordgo.TypingStart) {
if !b.GetBool("ShowUserTyping") {
return
}
// Ignore our own typing messages
if m.UserID == b.userID {
return
}
rmsg := config.Message{Account: b.Account, Event: config.EventUserTyping}
rmsg.Channel = b.getChannelName(m.ChannelID)
b.Remote <- rmsg
}
func (b *Bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) { //nolint:unparam
if b.GetBool("EditDisable") {
return
@@ -54,10 +45,7 @@ func (b *Bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdat
if m.Message.EditedTimestamp != "" {
b.Log.Debugf("Sending edit message")
m.Content += b.GetString("EditSuffix")
msg := &discordgo.MessageCreate{
Message: m.Message,
}
b.messageCreate(s, msg)
b.messageCreate(s, (*discordgo.MessageCreate)(m))
}
}
@@ -84,6 +72,7 @@ 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 {
@@ -94,13 +83,16 @@ 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
}
fromWebhook := m.WebhookID != ""
if !fromWebhook && !b.GetBool("UseUserName") {
rmsg.Username = b.getNick(m.Author, m.GuildID)
// set username
if !b.GetBool("UseUserName") {
rmsg.Username = b.getNick(m.Author)
} else {
rmsg.Username = m.Author.Username
if !fromWebhook && b.GetBool("UseDiscriminator") {
if b.GetBool("UseDiscriminator") {
rmsg.Username += "#" + m.Author.Discriminator
}
}
@@ -108,7 +100,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 += handleEmbed(embed)
rmsg.Text = rmsg.Text + "embed: " + embed.Title + " - " + embed.Description + " - " + embed.URL + "\n"
}
}
@@ -124,9 +116,6 @@ func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
rmsg.Event = config.EventUserAction
}
// Replace emotes
rmsg.Text = replaceEmotes(rmsg.Text)
b.Log.Debugf("<= Sending message from %s on %s to gateway", m.Author.Username, b.Account)
b.Log.Debugf("<= Message is %#v", rmsg)
b.Remote <- rmsg
@@ -200,33 +189,3 @@ 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

@@ -1,58 +0,0 @@
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,10 +6,10 @@ import (
"strings"
"unicode"
"github.com/matterbridge/discordgo"
"github.com/bwmarrin/discordgo"
)
func (b *Bdiscord) getNick(user *discordgo.User, guildID string) string {
func (b *Bdiscord) getNick(user *discordgo.User) string {
b.membersMutex.RLock()
defer b.membersMutex.RUnlock()
@@ -23,9 +23,9 @@ func (b *Bdiscord) getNick(user *discordgo.User, guildID string) string {
}
// If we didn't find nick, search for it.
member, err := b.c.GuildMember(guildID, user.ID)
member, err := b.c.GuildMember(b.guildID, user.ID)
if err != nil {
b.Log.Warnf("Failed to fetch information for member %#v on guild %#v: %s", user, guildID, err)
b.Log.Warnf("Failed to fetch information for member %#v: %s", user, err)
return user.Username
} else if member == nil {
b.Log.Warnf("Got no information for member %#v", user)
@@ -96,13 +96,6 @@ 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)
@@ -136,8 +129,8 @@ 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}")
emoteRE = regexp.MustCompile(`<a?(:\w+:)\d+>`)
)
func (b *Bdiscord) replaceChannelMentions(text string) string {
@@ -183,14 +176,13 @@ func (b *Bdiscord) replaceUserMentions(text string) string {
return userMentionRE.ReplaceAllStringFunc(text, replaceUserMentionFunc)
}
func replaceEmotes(text string) string {
return emoteRE.ReplaceAllString(text, "$1")
func (b *Bdiscord) stripCustomoji(text string) string {
return emojiRE.ReplaceAllString(text, `$1`)
}
func (b *Bdiscord) replaceAction(text string) (string, bool) {
length := len(text)
if length > 1 && text[0] == '_' && text[length-1] == '_' {
return text[1 : length-1], true
if strings.HasPrefix(text, "_") && strings.HasSuffix(text, "_") {
return text[1 : len(text)-1], true
}
return text, false
}

View File

@@ -5,10 +5,7 @@ import (
"fmt"
"image/png"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"regexp"
"strings"
"time"
@@ -17,10 +14,8 @@ import (
"golang.org/x/image/webp"
"github.com/42wim/matterbridge/bridge/config"
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/html"
"github.com/gomarkdown/markdown/parser"
"github.com/sirupsen/logrus"
"gitlab.com/golang-commonmark/markdown"
)
// DownloadFile downloads the given non-authenticated URL.
@@ -181,21 +176,15 @@ func ClipMessage(text string, length int) string {
return text
}
// ParseMarkdown takes in an input string as markdown and parses it to html
func ParseMarkdown(input string) string {
extensions := parser.HardLineBreak | parser.NoIntraEmphasis
markdownParser := parser.NewWithExtensions(extensions)
renderer := html.NewRenderer(html.RendererOptions{
Flags: 0,
})
parsedMarkdown := markdown.ToHTML([]byte(input), markdownParser, renderer)
res := string(parsedMarkdown)
md := markdown.New(markdown.XHTMLOutput(true), markdown.Breaks(true))
res := md.RenderToString([]byte(input))
res = strings.TrimPrefix(res, "<p>")
res = strings.TrimSuffix(res, "</p>\n")
return res
}
// ConvertWebPToPNG converts input data (which should be WebP format) to PNG format
// ConvertWebPToPNG convert input data (which should be WebP format to PNG format)
func ConvertWebPToPNG(data *[]byte) error {
r := bytes.NewReader(*data)
m, err := webp.Decode(r)
@@ -210,49 +199,3 @@ func ConvertWebPToPNG(data *[]byte) error {
*data = w.Bytes()
return nil
}
// CanConvertTgsToX Checks whether the external command necessary for ConvertTgsToX works.
func CanConvertTgsToX() error {
// We depend on the fact that `lottie_convert.py --help` has exit status 0.
// Hyrum's Law predicted this, and Murphy's Law predicts that this will break eventually.
// However, there is no alternative like `lottie_convert.py --is-properly-installed`
cmd := exec.Command("lottie_convert.py", "--help")
return cmd.Run()
}
// ConvertTgsToWebP convert input data (which should be tgs format) to WebP format
// This relies on an external command, which is ugly, but works.
func ConvertTgsToX(data *[]byte, outputFormat string, logger *logrus.Entry) error {
// lottie can't handle input from a pipe, so write to a temporary file:
tmpFile, err := ioutil.TempFile(os.TempDir(), "matterbridge-lottie-*.tgs")
if err != nil {
return err
}
tmpFileName := tmpFile.Name()
defer func() {
if removeErr := os.Remove(tmpFileName); removeErr != nil {
logger.Errorf("Could not delete temporary file %s: %v", tmpFileName, removeErr)
}
}()
if _, writeErr := tmpFile.Write(*data); writeErr != nil {
return writeErr
}
// Must close before calling lottie to avoid data races:
if closeErr := tmpFile.Close(); closeErr != nil {
return closeErr
}
// Call lottie to transform:
cmd := exec.Command("lottie_convert.py", "--input-format", "lottie", "--output-format", outputFormat, tmpFileName, "/dev/stdout")
cmd.Stderr = nil
// NB: lottie writes progress into to stderr in all cases.
stdout, stderr := cmd.Output()
if stderr != nil {
// 'stderr' already contains some parts of Stderr, because it was set to 'nil'.
return stderr
}
*data = stdout
return nil
}

View File

@@ -10,8 +10,8 @@ import (
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
"github.com/dfordsoft/golib/ic"
"github.com/lrstanley/girc"
"github.com/missdeer/golib/ic"
"github.com/paulrosania/go-charset/charset"
"github.com/saintfish/chardet"

View File

@@ -4,7 +4,6 @@ import (
"crypto/tls"
"fmt"
"hash/crc32"
"io/ioutil"
"net"
"sort"
"strconv"
@@ -15,7 +14,6 @@ import (
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
"github.com/lrstanley/girc"
stripmd "github.com/writeas/go-strip-markdown"
// We need to import the 'data' package as an implicit dependency.
// See: https://godoc.org/github.com/paulrosania/go-charset/charset
@@ -158,10 +156,6 @@ func (b *Birc) Send(msg config.Message) (string, error) {
}
var msgLines []string
if b.GetBool("StripMarkdown") {
msg.Text = stripmd.Strip(msg.Text)
}
if b.GetBool("MessageSplit") {
msgLines = helper.GetSubLines(msg.Text, b.MessageLength)
} else {
@@ -173,8 +167,12 @@ func (b *Birc) Send(msg config.Message) (string, error) {
return "", nil
}
msg.Text = msgLines[i]
b.Local <- msg
b.Local <- config.Message{
Text: msgLines[i],
Username: msg.Username,
Channel: msg.Channel,
Event: msg.Event,
}
}
return "", nil
}
@@ -207,7 +205,7 @@ func (b *Birc) doSend() {
for msg := range b.Local {
<-throttle.C
username := msg.Username
if b.GetBool("Colornicks") && len(username) > 1 {
if b.GetBool("Colornicks") {
checksum := crc32.ChecksumIEEE([]byte(msg.Username))
colorCode := checksum%14 + 2 // quick fix - prevent white or black color codes
username = fmt.Sprintf("\x03%02d%s\x0F", colorCode, msg.Username)
@@ -241,18 +239,6 @@ func (b *Birc) getClient() (*girc.Client, error) {
user = user[1:]
}
debug := ioutil.Discard
if b.GetInt("DebugLevel") == 2 {
debug = b.Log.Writer()
}
pingDelay, err := time.ParseDuration(b.GetString("pingdelay"))
if err != nil || pingDelay == 0 {
pingDelay = time.Minute
}
b.Log.Debugf("setting pingdelay to %s", pingDelay)
i := girc.New(girc.Config{
Server: server,
ServerPass: b.GetString("Password"),
@@ -262,10 +248,7 @@ func (b *Birc) getClient() (*girc.Client, error) {
Name: b.GetString("Nick"),
SSL: b.GetBool("UseTLS"),
TLSConfig: &tls.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), ServerName: server}, //nolint:gosec
PingDelay: pingDelay,
// skip gIRC internal rate limiting, since we have our own throttling
AllowFlood: true,
Debug: debug,
PingDelay: time.Minute,
})
return i, nil
}

View File

@@ -1,59 +0,0 @@
package bkeybase
import (
"strconv"
"github.com/42wim/matterbridge/bridge/config"
"github.com/keybase/go-keybase-chat-bot/kbchat/types/chat1"
)
func (b *Bkeybase) handleKeybase() {
sub, err := b.kbc.ListenForNewTextMessages()
if err != nil {
b.Log.Errorf("Error listening: %s", err.Error())
}
go func() {
for {
msg, err := sub.Read()
if err != nil {
b.Log.Errorf("failed to read message: %s", err.Error())
}
if msg.Message.Content.TypeName != "text" {
continue
}
if msg.Message.Sender.Username == b.kbc.GetUsername() {
continue
}
b.handleMessage(msg.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
}
if msg.Sender.Username != b.kbc.GetUsername() {
// TODO download avatar
// Create our message
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.TypeName != "text" {
b.Log.Errorf("message is not text")
return
}
b.Log.Debugf("<= Sending message from %s on %s to gateway", msg.Sender.Username, msg.Channel.Name)
b.Remote <- rmsg
}
}

View File

@@ -1,106 +0,0 @@
package bkeybase
import (
"io/ioutil"
"os"
"path/filepath"
"strconv"
"github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config"
"github.com/keybase/go-keybase-chat-bot/kbchat"
)
// Bkeybase bridge structure
type Bkeybase struct {
kbc *kbchat.API
user string
channel string
team string
*bridge.Config
}
// New initializes Bkeybase object and sets team
func New(cfg *bridge.Config) bridge.Bridger {
b := &Bkeybase{Config: cfg}
b.team = b.Config.GetString("Team")
return b
}
// Connect starts keybase API and listener loop
func (b *Bkeybase) Connect() error {
var err error
b.Log.Infof("Connecting %s", b.GetString("Team"))
// use default keybase location (`keybase`)
b.kbc, err = kbchat.Start(kbchat.RunOptions{})
if err != nil {
return err
}
b.user = b.kbc.GetUsername()
b.Log.Info("Connection succeeded")
go b.handleKeybase()
return nil
}
// Disconnect doesn't do anything for now
func (b *Bkeybase) Disconnect() error {
return nil
}
// JoinChannel sets channel name in struct
func (b *Bkeybase) JoinChannel(channel config.ChannelInfo) error {
if _, err := b.kbc.JoinChannel(b.team, channel.Name); err != nil {
return err
}
b.channel = channel.Name
return nil
}
// Send receives bridge messages and sends them to Keybase chat room
func (b *Bkeybase) Send(msg config.Message) (string, error) {
b.Log.Debugf("=> Receiving %#v", msg)
// Handle /me events
if msg.Event == config.EventUserAction {
msg.Text = "_" + msg.Text + "_"
}
// Delete message if we have an ID
// Delete message not supported by keybase go library yet
// Edit message if we have an ID
// kbchat lib does not support message editing yet
if len(msg.Extra["file"]) > 0 {
// Upload a file
dir, err := ioutil.TempDir("", "matterbridge")
if err != nil {
return "", err
}
defer os.RemoveAll(dir)
for _, f := range msg.Extra["file"] {
fname := f.(config.FileInfo).Name
fdata := *f.(config.FileInfo).Data
fcaption := f.(config.FileInfo).Comment
fpath := filepath.Join(dir, fname)
if err = ioutil.WriteFile(fpath, fdata, 0600); err != nil {
return "", err
}
_, _ = b.kbc.SendAttachmentByTeam(b.team, &b.channel, fpath, fcaption)
}
return "", nil
}
// Send regular message
text := msg.Username + msg.Text
resp, err := b.kbc.SendMessageByTeamName(b.team, &b.channel, text)
if err != nil {
return "", err
}
return strconv.Itoa(int(*resp.Result.MessageID)), err
}

View File

@@ -1,166 +0,0 @@
package bmatrix
import (
"encoding/json"
"errors"
"html"
"strings"
"time"
matrix "github.com/matrix-org/gomatrix"
)
func newMatrixUsername(username string) *matrixUsername {
mUsername := new(matrixUsername)
// check if we have a </tag>. if we have, we don't escape HTML. #696
if htmlTag.MatchString(username) {
mUsername.formatted = username
// remove the HTML formatting for beautiful push messages #1188
mUsername.plain = htmlReplacementTag.ReplaceAllString(username, "")
} else {
mUsername.formatted = html.EscapeString(username)
mUsername.plain = username
}
return mUsername
}
// getRoomID retrieves a matching room ID from the channel name.
func (b *Bmatrix) getRoomID(channel string) string {
b.RLock()
defer b.RUnlock()
for ID, name := range b.RoomMap {
if name == channel {
return ID
}
}
return ""
}
// interface2Struct marshals and immediately unmarshals an interface.
// Useful for converting map[string]interface{} to a struct.
func interface2Struct(in interface{}, out interface{}) error {
jsonObj, err := json.Marshal(in)
if err != nil {
return err //nolint:wrapcheck
}
return json.Unmarshal(jsonObj, out)
}
// getDisplayName retrieves the displayName for mxid, querying the homserver if the mxid is not in the cache.
func (b *Bmatrix) getDisplayName(mxid string) string {
if b.GetBool("UseUserName") {
return mxid[1:]
}
b.RLock()
if val, present := b.NicknameMap[mxid]; present {
b.RUnlock()
return val.displayName
}
b.RUnlock()
displayName, err := b.mc.GetDisplayName(mxid)
var httpError *matrix.HTTPError
if errors.As(err, &httpError) {
b.Log.Warnf("Couldn't retrieve the display name for %s", mxid)
}
if err != nil {
return b.cacheDisplayName(mxid, mxid[1:])
}
return b.cacheDisplayName(mxid, displayName.DisplayName)
}
// cacheDisplayName stores the mapping between a mxid and a display name, to be reused later without performing a query to the homserver.
// Note that old entries are cleaned when this function is called.
func (b *Bmatrix) cacheDisplayName(mxid string, displayName string) string {
now := time.Now()
// scan to delete old entries, to stop memory usage from becoming too high with old entries
toDelete := []string{}
b.RLock()
for k, v := range b.NicknameMap {
if now.Sub(v.lastUpdated) > 10*time.Minute {
toDelete = append(toDelete, k)
}
}
b.RUnlock()
b.Lock()
for _, v := range toDelete {
delete(b.NicknameMap, v)
}
b.NicknameMap[mxid] = NicknameCacheEntry{
displayName: displayName,
lastUpdated: now,
}
b.Unlock()
return displayName
}
// handleError converts errors into httpError.
//nolint:exhaustivestruct
func handleError(err error) *httpError {
var mErr matrix.HTTPError
if !errors.As(err, &mErr) {
return &httpError{
Err: "not a HTTPError",
}
}
var httpErr httpError
if err := json.Unmarshal(mErr.Contents, &httpErr); err != nil {
return &httpError{
Err: "unmarshal failed",
}
}
return &httpErr
}
func (b *Bmatrix) containsAttachment(content map[string]interface{}) bool {
// Skip empty messages
if content["msgtype"] == nil {
return false
}
// Only allow image,video or file msgtypes
if !(content["msgtype"].(string) == "m.image" ||
content["msgtype"].(string) == "m.video" ||
content["msgtype"].(string) == "m.file") {
return false
}
return true
}
// getAvatarURL returns the avatar URL of the specified sender.
func (b *Bmatrix) getAvatarURL(sender string) string {
urlPath := b.mc.BuildURL("profile", sender, "avatar_url")
s := struct {
AvatarURL string `json:"avatar_url"`
}{}
err := b.mc.MakeRequest("GET", urlPath, nil, &s)
if err != nil {
b.Log.Errorf("getAvatarURL failed: %s", err)
return ""
}
url := strings.ReplaceAll(s.AvatarURL, "mxc://", b.GetString("Server")+"/_matrix/media/r0/thumbnail/")
if url != "" {
url += "?width=37&height=37&method=crop"
}
return url
}

View File

@@ -3,71 +3,31 @@ package bmatrix
import (
"bytes"
"fmt"
"html"
"mime"
"regexp"
"strings"
"sync"
"time"
"github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
matrix "github.com/matrix-org/gomatrix"
matrix "github.com/matterbridge/gomatrix"
)
var (
htmlTag = regexp.MustCompile("</.*?>")
htmlReplacementTag = regexp.MustCompile("<[^>]*>")
)
type NicknameCacheEntry struct {
displayName string
lastUpdated time.Time
}
type Bmatrix struct {
mc *matrix.Client
UserID string
NicknameMap map[string]NicknameCacheEntry
RoomMap map[string]string
sync.RWMutex
htmlTag *regexp.Regexp
*bridge.Config
}
type httpError struct {
Errcode string `json:"errcode"`
Err string `json:"error"`
RetryAfterMs int `json:"retry_after_ms"`
}
type matrixUsername struct {
plain string
formatted string
}
// SubTextMessage represents the new content of the message in edit messages.
type SubTextMessage struct {
MsgType string `json:"msgtype"`
Body string `json:"body"`
}
// MessageRelation explains how the current message relates to a previous message.
// Notably used for message edits.
type MessageRelation struct {
EventID string `json:"event_id"`
Type string `json:"rel_type"`
}
type EditedMessage struct {
NewContent SubTextMessage `json:"m.new_content"`
RelatedTo MessageRelation `json:"m.relates_to"`
matrix.TextMessage
}
func New(cfg *bridge.Config) bridge.Bridger {
b := &Bmatrix{Config: cfg}
b.htmlTag = regexp.MustCompile("</.*?>")
b.RoomMap = make(map[string]string)
b.NicknameMap = make(map[string]NicknameCacheEntry)
return b
}
@@ -82,7 +42,6 @@ func (b *Bmatrix) Connect() error {
Type: "m.login.password",
User: b.GetString("Login"),
Password: b.GetString("Password"),
Identifier: matrix.NewUserIdentifier(b.GetString("Login")),
})
if err != nil {
return err
@@ -99,25 +58,14 @@ func (b *Bmatrix) Disconnect() error {
}
func (b *Bmatrix) JoinChannel(channel config.ChannelInfo) error {
retry:
resp, err := b.mc.JoinRoom(channel.Name, "", nil)
if err != nil {
httpErr := handleError(err)
if httpErr.Errcode == "M_LIMIT_EXCEEDED" {
b.Log.Infof("getting ratelimited by matrix, sleeping approx %d seconds before joining %s", httpErr.RetryAfterMs/1000, channel.Name)
time.Sleep((time.Duration(httpErr.RetryAfterMs) * time.Millisecond))
goto retry
}
return err
}
b.Lock()
b.RoomMap[resp.RoomID] = channel.Name
b.Unlock()
return nil
return err
}
func (b *Bmatrix) Send(msg config.Message) (string, error) {
@@ -126,14 +74,11 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
channel := b.getRoomID(msg.Channel)
b.Log.Debugf("Channel %s maps to channel id %s", msg.Channel, channel)
username := newMatrixUsername(msg.Username)
// Make a action /me of the message
if msg.Event == config.EventUserAction {
m := matrix.TextMessage{
MsgType: "m.emote",
Body: username.plain + msg.Text,
FormattedBody: username.formatted + msg.Text,
Body: msg.Username + msg.Text,
}
resp, err := b.mc.SendMessageEvent(channel, "m.room.message", m)
if err != nil {
@@ -168,68 +113,45 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
}
// Edit message if we have an ID
if msg.ID != "" {
rmsg := EditedMessage{TextMessage: matrix.TextMessage{
Body: username.plain + msg.Text,
MsgType: "m.text",
}}
if b.GetBool("HTMLDisable") {
rmsg.TextMessage.FormattedBody = username.formatted + "* " + msg.Text
} else {
rmsg.Format = "org.matrix.custom.html"
rmsg.TextMessage.FormattedBody = username.formatted + "* " + helper.ParseMarkdown(msg.Text)
}
rmsg.NewContent = SubTextMessage{
Body: rmsg.TextMessage.Body,
MsgType: "m.text",
}
rmsg.RelatedTo = MessageRelation{
EventID: msg.ID,
Type: "m.replace",
}
_, err := b.mc.SendMessageEvent(channel, "m.room.message", rmsg)
if err != nil {
return "", err
}
return msg.ID, nil
}
// matrix has no editing support
// Use notices to send join/leave events
if msg.Event == config.EventJoinLeave {
m := matrix.TextMessage{
MsgType: "m.notice",
Body: username.plain + msg.Text,
FormattedBody: username.formatted + msg.Text,
}
resp, err := b.mc.SendMessageEvent(channel, "m.room.message", m)
resp, err := b.mc.SendNotice(channel, msg.Username+msg.Text)
if err != nil {
return "", err
}
return resp.EventID, err
}
if b.GetBool("HTMLDisable") {
resp, err := b.mc.SendText(channel, username.plain+msg.Text)
if err != nil {
return "", err
username := html.EscapeString(msg.Username)
// check if we have a </tag>. if we have, we don't escape HTML. #696
if b.htmlTag.MatchString(msg.Username) {
username = msg.Username
}
return resp.EventID, err
}
// Post normal message with HTML support (eg riot.im)
resp, err := b.mc.SendFormattedText(channel, username.plain+msg.Text, username.formatted+helper.ParseMarkdown(msg.Text))
resp, err := b.mc.SendHTML(channel, msg.Username+msg.Text, username+helper.ParseMarkdown(msg.Text))
if err != nil {
return "", err
}
return resp.EventID, err
}
func (b *Bmatrix) getRoomID(channel string) string {
b.RLock()
defer b.RUnlock()
for ID, name := range b.RoomMap {
if name == channel {
return ID
}
}
return ""
}
func (b *Bmatrix) handlematrix() {
syncer := b.mc.Syncer.(*matrix.DefaultSyncer)
syncer.OnEventType("m.room.redaction", b.handleEvent)
syncer.OnEventType("m.room.message", b.handleEvent)
syncer.OnEventType("m.room.member", b.handleMemberChange)
go func() {
for {
if err := b.mc.Sync(); err != nil {
@@ -239,45 +161,6 @@ func (b *Bmatrix) handlematrix() {
}()
}
func (b *Bmatrix) handleEdit(ev *matrix.Event, rmsg config.Message) bool {
relationInterface, present := ev.Content["m.relates_to"]
newContentInterface, present2 := ev.Content["m.new_content"]
if !(present && present2) {
return false
}
var relation MessageRelation
if err := interface2Struct(relationInterface, &relation); err != nil {
b.Log.Warnf("Couldn't parse 'm.relates_to' object with value %#v", relationInterface)
return false
}
var newContent SubTextMessage
if err := interface2Struct(newContentInterface, &newContent); err != nil {
b.Log.Warnf("Couldn't parse 'm.new_content' object with value %#v", newContentInterface)
return false
}
if relation.Type != "m.replace" {
return false
}
rmsg.ID = relation.EventID
rmsg.Text = newContent.Body
b.Remote <- rmsg
return true
}
func (b *Bmatrix) handleMemberChange(ev *matrix.Event) {
// Update the displayname on join messages, according to https://matrix.org/docs/spec/client_server/r0.6.1#events-on-change-of-profile-information
if ev.Content["membership"] == "join" {
if dn, ok := ev.Content["displayname"].(string); ok {
b.cacheDisplayName(ev.Sender, dn)
}
}
}
func (b *Bmatrix) handleEvent(ev *matrix.Event) {
b.Log.Debugf("== Receiving event: %#v", ev)
if ev.Sender != b.UserID {
@@ -289,15 +172,10 @@ func (b *Bmatrix) handleEvent(ev *matrix.Event) {
return
}
// TODO download avatar
// Create our message
rmsg := config.Message{
Username: b.getDisplayName(ev.Sender),
Channel: channel,
Account: b.Account,
UserID: ev.Sender,
ID: ev.ID,
Avatar: b.getAvatarURL(ev.Sender),
}
rmsg := config.Message{Username: ev.Sender[1:], Channel: channel, Account: b.Account, UserID: ev.Sender, ID: ev.ID}
// Text must be a string
if rmsg.Text, ok = ev.Content["body"].(string); !ok {
@@ -326,11 +204,6 @@ func (b *Bmatrix) handleEvent(ev *matrix.Event) {
rmsg.Event = config.EventUserAction
}
// Is it an edit?
if b.handleEdit(ev, rmsg) {
return
}
// Do we have attachments
if b.containsAttachment(ev.Content) {
err := b.handleDownloadFile(&rmsg, ev.Content)
@@ -415,16 +288,24 @@ func (b *Bmatrix) handleUploadFiles(msg *config.Message, channel string) (string
// handleUploadFile handles native upload of a file.
func (b *Bmatrix) handleUploadFile(msg *config.Message, channel string, fi *config.FileInfo) {
username := newMatrixUsername(msg.Username)
content := bytes.NewReader(*fi.Data)
sp := strings.Split(fi.Name, ".")
mtype := mime.TypeByExtension("." + sp[len(sp)-1])
// image and video uploads send no username, we have to do this ourself here #715
_, err := b.mc.SendFormattedText(channel, username.plain+fi.Comment, username.formatted+fi.Comment)
if !strings.Contains(mtype, "image") && !strings.Contains(mtype, "video") {
return
}
if fi.Comment != "" {
_, err := b.mc.SendText(channel, msg.Username+fi.Comment)
if err != nil {
b.Log.Errorf("file comment failed: %#v", err)
}
} else {
// image and video uploads send no username, we have to do this ourself here #715
_, err := b.mc.SendText(channel, msg.Username)
if err != nil {
b.Log.Errorf("file comment failed: %#v", err)
}
}
b.Log.Debugf("uploading file: %s %s", fi.Name, mtype)
res, err := b.mc.UploadToContentRepo(content, mtype, int64(len(*fi.Data)))
if err != nil {
@@ -445,34 +326,22 @@ func (b *Bmatrix) handleUploadFile(msg *config.Message, channel string, fi *conf
if err != nil {
b.Log.Errorf("sendImage failed: %#v", err)
}
case strings.Contains(mtype, "audio"):
b.Log.Debugf("sendAudio %s", res.ContentURI)
_, err = b.mc.SendMessageEvent(channel, "m.room.message", matrix.AudioMessage{
MsgType: "m.audio",
Body: fi.Name,
URL: res.ContentURI,
Info: matrix.AudioInfo{
Mimetype: mtype,
Size: uint(len(*fi.Data)),
},
})
if err != nil {
b.Log.Errorf("sendAudio failed: %#v", err)
}
default:
b.Log.Debugf("sendFile %s", res.ContentURI)
_, err = b.mc.SendMessageEvent(channel, "m.room.message", matrix.FileMessage{
MsgType: "m.file",
Body: fi.Name,
URL: res.ContentURI,
Info: matrix.FileInfo{
Mimetype: mtype,
Size: uint(len(*fi.Data)),
},
})
if err != nil {
b.Log.Errorf("sendFile failed: %#v", err)
}
}
b.Log.Debugf("result: %#v", res)
}
// skipMessages returns true if this message should not be handled
func (b *Bmatrix) containsAttachment(content map[string]interface{}) bool {
// Skip empty messages
if content["msgtype"] == nil {
return false
}
// Only allow image,video or file msgtypes
if !(content["msgtype"].(string) == "m.image" ||
content["msgtype"].(string) == "m.video" ||
content["msgtype"].(string) == "m.file") {
return false
}
return true
}

View File

@@ -1,28 +0,0 @@
package bmatrix
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestPlainUsername(t *testing.T) {
uut := newMatrixUsername("MyUser")
assert.Equal(t, "MyUser", uut.formatted)
assert.Equal(t, "MyUser", uut.plain)
}
func TestHTMLUsername(t *testing.T) {
uut := newMatrixUsername("<b>MyUser</b>")
assert.Equal(t, "<b>MyUser</b>", uut.formatted)
assert.Equal(t, "MyUser", uut.plain)
}
func TestFancyUsername(t *testing.T) {
uut := newMatrixUsername("<MyUser>")
assert.Equal(t, "&lt;MyUser&gt;", uut.formatted)
assert.Equal(t, "<MyUser>", uut.plain)
}

View File

@@ -4,7 +4,7 @@ import (
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
"github.com/42wim/matterbridge/matterclient"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/model"
)
// handleDownloadAvatar downloads the avatar of userid from channel
@@ -66,10 +66,6 @@ func (b *Bmattermost) handleMatter() {
} else {
b.Log.Debugf("Choosing login/password based receiving")
}
// if for some reason we only want to sent stuff to mattermost but not receive, return
if b.GetString("WebhookBindAddress") == "" && b.GetString("WebhookURL") != "" && b.GetString("Token") == "" && b.GetString("Login") == "" {
b.Log.Debugf("No WebhookBindAddress specified, only WebhookURL. You will not receive messages from mattermost, only sending is possible.")
}
go b.handleMatterClient(messages)
}
var ok bool

View File

@@ -7,7 +7,7 @@ import (
"github.com/42wim/matterbridge/bridge/helper"
"github.com/42wim/matterbridge/matterclient"
"github.com/42wim/matterbridge/matterhook"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/model"
)
func (b *Bmattermost) doConnectWebhookBind() error {

View File

@@ -1,101 +0,0 @@
package bmsteams
import (
"encoding/json"
"fmt"
"io/ioutil"
"strings"
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
msgraph "github.com/yaegashi/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"
}

View File

@@ -1,224 +0,0 @@
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"
"github.com/mattn/godown"
msgraph "github.com/yaegashi/msgraph.go/beta"
"github.com/yaegashi/msgraph.go/msauth"
"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 func(name string) {
for {
err := b.poll(name)
if err != nil {
b.Log.Errorf("polling failed for %s: %s. retrying in 5 seconds", name, err)
}
time.Sleep(time.Second * 5)
}
}(channel.Name)
return nil
}
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) error {
msgmap := make(map[string]time.Time)
b.Log.Debug("getting initial messages")
res, err := b.getMessages(channelName)
if err != nil {
return 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 {
return 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 b.GetBool("debug") {
b.Log.Debug("Msg dump: ", spew.Sdump(msg))
}
// skip non-user message for now.
if msg.From.User == nil {
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

@@ -1,90 +0,0 @@
package bmumble
import (
"strconv"
"time"
"layeh.com/gumble/gumble"
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
)
func (b *Bmumble) handleServerConfig(event *gumble.ServerConfigEvent) {
b.serverConfigUpdate <- *event
}
func (b *Bmumble) handleTextMessage(event *gumble.TextMessageEvent) {
sender := "unknown"
if event.TextMessage.Sender != nil {
sender = event.TextMessage.Sender.Name
}
// Convert Mumble HTML messages to markdown
parts, err := b.convertHTMLtoMarkdown(event.TextMessage.Message)
if err != nil {
b.Log.Error(err)
}
now := time.Now().UTC()
for i, part := range parts {
// Construct matterbridge message and pass on to the gateway
rmsg := config.Message{
Channel: strconv.FormatUint(uint64(event.Client.Self.Channel.ID), 10),
Username: sender,
UserID: sender + "@" + b.Host,
Account: b.Account,
}
if part.Image == nil {
rmsg.Text = part.Text
} else {
fname := b.Account + "_" + strconv.FormatInt(now.UnixNano(), 10) + "_" + strconv.Itoa(i) + part.FileExtension
rmsg.Extra = make(map[string][]interface{})
if err = helper.HandleDownloadSize(b.Log, &rmsg, fname, int64(len(part.Image)), b.General); err != nil {
b.Log.WithError(err).Warn("not including image in message")
continue
}
helper.HandleDownloadData(b.Log, &rmsg, fname, "", "", &part.Image, b.General)
}
b.Log.Debugf("Sending message to gateway: %+v", rmsg)
b.Remote <- rmsg
}
}
func (b *Bmumble) handleConnect(event *gumble.ConnectEvent) {
// Set the user's "bio"/comment
if comment := b.GetString("UserComment"); comment != "" && event.Client.Self != nil {
event.Client.Self.SetComment(comment)
}
// No need to talk or listen
event.Client.Self.SetSelfDeafened(true)
event.Client.Self.SetSelfMuted(true)
// if the Channel variable is set, this is a reconnect -> rejoin channel
if b.Channel != nil {
if err := b.doJoin(event.Client, *b.Channel); err != nil {
b.Log.Error(err)
}
b.Remote <- config.Message{
Username: "system",
Text: "rejoin",
Channel: "",
Account: b.Account,
Event: config.EventRejoinChannels,
}
}
}
func (b *Bmumble) handleUserChange(event *gumble.UserChangeEvent) {
// Only care about changes to self
if event.User != event.Client.Self {
return
}
// Someone attempted to move the user out of the configured channel; attempt to join back
if b.Channel != nil {
if err := b.doJoin(event.Client, *b.Channel); err != nil {
b.Log.Error(err)
}
}
}
func (b *Bmumble) handleDisconnect(event *gumble.DisconnectEvent) {
b.connected <- *event
}

View File

@@ -1,143 +0,0 @@
package bmumble
import (
"fmt"
"mime"
"net/http"
"regexp"
"strings"
"github.com/42wim/matterbridge/bridge/config"
"github.com/mattn/godown"
"github.com/vincent-petithory/dataurl"
)
type MessagePart struct {
Text string
FileExtension string
Image []byte
}
func (b *Bmumble) decodeImage(uri string, parts *[]MessagePart) error {
// Decode the data:image/... URI
image, err := dataurl.DecodeString(uri)
if err != nil {
b.Log.WithError(err).Info("No image extracted")
return err
}
// Determine the file extensions for that image
ext, err := mime.ExtensionsByType(image.MediaType.ContentType())
if err != nil || len(ext) == 0 {
b.Log.WithError(err).Infof("No file extension registered for MIME type '%s'", image.MediaType.ContentType())
return err
}
// Add the image to the MessagePart slice
*parts = append(*parts, MessagePart{"", ext[0], image.Data})
return nil
}
func (b *Bmumble) tokenize(t *string) ([]MessagePart, error) {
// `^(.*?)` matches everything before the image
// `!\[[^\]]*\]\(` matches the `![alt](` part of markdown images
// `(data:image\/[^)]+)` matches the data: URI used by Mumble
// `\)` matches the closing parenthesis after the URI
// `(.*)$` matches the remaining text to be examined in the next iteration
p := regexp.MustCompile(`^(?ms)(.*?)!\[[^\]]*\]\((data:image\/[^)]+)\)(.*)$`)
remaining := *t
var parts []MessagePart
for {
tokens := p.FindStringSubmatch(remaining)
if tokens == nil {
// no match -> remaining string is non-image text
pre := strings.TrimSpace(remaining)
if len(pre) > 0 {
parts = append(parts, MessagePart{pre, "", nil})
}
return parts, nil
}
// tokens[1] is the text before the image
if len(tokens[1]) > 0 {
pre := strings.TrimSpace(tokens[1])
parts = append(parts, MessagePart{pre, "", nil})
}
// tokens[2] is the image URL
uri, err := dataurl.UnescapeToString(strings.TrimSpace(strings.ReplaceAll(tokens[2], " ", "")))
if err != nil {
b.Log.WithError(err).Info("URL unescaping failed")
remaining = strings.TrimSpace(tokens[3])
continue
}
err = b.decodeImage(uri, &parts)
if err != nil {
b.Log.WithError(err).Info("Decoding the image failed")
}
// tokens[3] is the text after the image, processed in the next iteration
remaining = strings.TrimSpace(tokens[3])
}
}
func (b *Bmumble) convertHTMLtoMarkdown(html string) ([]MessagePart, error) {
var sb strings.Builder
err := godown.Convert(&sb, strings.NewReader(html), nil)
if err != nil {
return nil, err
}
markdown := sb.String()
b.Log.Debugf("### to markdown: %s", markdown)
return b.tokenize(&markdown)
}
func (b *Bmumble) extractFiles(msg *config.Message) []config.Message {
var messages []config.Message
if msg.Extra == nil || len(msg.Extra["file"]) == 0 {
return messages
}
// Create a separate message for each file
for _, f := range msg.Extra["file"] {
fi := f.(config.FileInfo)
imsg := config.Message{
Channel: msg.Channel,
Username: msg.Username,
UserID: msg.UserID,
Account: msg.Account,
Protocol: msg.Protocol,
Timestamp: msg.Timestamp,
Event: "mumble_image",
}
// If no data is present for the file, send a link instead
if fi.Data == nil || len(*fi.Data) == 0 {
if len(fi.URL) > 0 {
imsg.Text = fmt.Sprintf(`<a href="%s">%s</a>`, fi.URL, fi.URL)
messages = append(messages, imsg)
} else {
b.Log.Infof("Not forwarding file without local data")
}
continue
}
mimeType := http.DetectContentType(*fi.Data)
// Mumble only supports images natively, send a link instead
if !strings.HasPrefix(mimeType, "image/") {
if len(fi.URL) > 0 {
imsg.Text = fmt.Sprintf(`<a href="%s">%s</a>`, fi.URL, fi.URL)
messages = append(messages, imsg)
} else {
b.Log.Infof("Not forwarding file of type %s", mimeType)
}
continue
}
mimeType = strings.TrimSpace(strings.Split(mimeType, ";")[0])
// Build data:image/...;base64,... style image URL and embed image directly into the message
du := dataurl.New(*fi.Data, mimeType)
dataURL, err := du.MarshalText()
if err != nil {
b.Log.WithError(err).Infof("Image Serialization into data URL failed (type: %s, length: %d)", mimeType, len(*fi.Data))
continue
}
imsg.Text = fmt.Sprintf(`<img src="%s"/>`, dataURL)
messages = append(messages, imsg)
}
// Remove files from original message
msg.Extra["file"] = nil
return messages
}

View File

@@ -1,259 +0,0 @@
package bmumble
import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"net"
"strconv"
"time"
"layeh.com/gumble/gumble"
"layeh.com/gumble/gumbleutil"
"github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
stripmd "github.com/writeas/go-strip-markdown"
// We need to import the 'data' package as an implicit dependency.
// See: https://godoc.org/github.com/paulrosania/go-charset/charset
_ "github.com/paulrosania/go-charset/data"
)
type Bmumble struct {
client *gumble.Client
Nick string
Host string
Channel *uint32
local chan config.Message
running chan error
connected chan gumble.DisconnectEvent
serverConfigUpdate chan gumble.ServerConfigEvent
serverConfig gumble.ServerConfigEvent
tlsConfig tls.Config
*bridge.Config
}
func New(cfg *bridge.Config) bridge.Bridger {
b := &Bmumble{}
b.Config = cfg
b.Nick = b.GetString("Nick")
b.local = make(chan config.Message)
b.running = make(chan error)
b.connected = make(chan gumble.DisconnectEvent)
b.serverConfigUpdate = make(chan gumble.ServerConfigEvent)
return b
}
func (b *Bmumble) Connect() error {
b.Log.Infof("Connecting %s", b.GetString("Server"))
host, portstr, err := net.SplitHostPort(b.GetString("Server"))
if err != nil {
return err
}
b.Host = host
_, err = strconv.Atoi(portstr)
if err != nil {
return err
}
if err = b.buildTLSConfig(); err != nil {
return err
}
go b.doSend()
go b.connectLoop()
err = <-b.running
return err
}
func (b *Bmumble) Disconnect() error {
return b.client.Disconnect()
}
func (b *Bmumble) JoinChannel(channel config.ChannelInfo) error {
cid, err := strconv.ParseUint(channel.Name, 10, 32)
if err != nil {
return err
}
channelID := uint32(cid)
if b.Channel != nil && *b.Channel != channelID {
b.Log.Fatalf("Cannot join channel ID '%d', already joined to channel ID %d", channelID, *b.Channel)
return errors.New("the Mumble bridge can only join a single channel")
}
b.Channel = &channelID
return b.doJoin(b.client, channelID)
}
func (b *Bmumble) Send(msg config.Message) (string, error) {
// Only process text messages
b.Log.Debugf("=> Received local message %#v", msg)
if msg.Event != "" && msg.Event != config.EventUserAction {
return "", nil
}
attachments := b.extractFiles(&msg)
b.local <- msg
for _, a := range attachments {
b.local <- a
}
return "", nil
}
func (b *Bmumble) buildTLSConfig() error {
b.tlsConfig = tls.Config{}
// Load TLS client certificate keypair required for registered user authentication
if cpath := b.GetString("TLSClientCertificate"); cpath != "" {
if ckey := b.GetString("TLSClientKey"); ckey != "" {
cert, err := tls.LoadX509KeyPair(cpath, ckey)
if err != nil {
return err
}
b.tlsConfig.Certificates = []tls.Certificate{cert}
}
}
// Load TLS CA used for server verification. If not provided, the Go system trust anchor is used
if capath := b.GetString("TLSCACertificate"); capath != "" {
ca, err := ioutil.ReadFile(capath)
if err != nil {
return err
}
b.tlsConfig.RootCAs = x509.NewCertPool()
b.tlsConfig.RootCAs.AppendCertsFromPEM(ca)
}
b.tlsConfig.InsecureSkipVerify = b.GetBool("SkipTLSVerify")
return nil
}
func (b *Bmumble) connectLoop() {
firstConnect := true
for {
err := b.doConnect()
if firstConnect {
b.running <- err
}
if err != nil {
b.Log.Errorf("Connection to server failed: %#v", err)
if firstConnect {
break
} else {
b.Log.Info("Retrying in 10s")
time.Sleep(10 * time.Second)
continue
}
}
firstConnect = false
d := <-b.connected
switch d.Type {
case gumble.DisconnectError:
b.Log.Errorf("Lost connection to the server (%s), attempting reconnect", d.String)
continue
case gumble.DisconnectKicked:
b.Log.Errorf("Kicked from the server (%s), attempting reconnect", d.String)
continue
case gumble.DisconnectBanned:
b.Log.Errorf("Banned from the server (%s), not attempting reconnect", d.String)
close(b.connected)
close(b.running)
return
case gumble.DisconnectUser:
b.Log.Infof("Disconnect successful")
close(b.connected)
close(b.running)
return
}
}
}
func (b *Bmumble) doConnect() error {
// Create new gumble config and attach event handlers
gumbleConfig := gumble.NewConfig()
gumbleConfig.Attach(gumbleutil.Listener{
ServerConfig: b.handleServerConfig,
TextMessage: b.handleTextMessage,
Connect: b.handleConnect,
Disconnect: b.handleDisconnect,
UserChange: b.handleUserChange,
})
gumbleConfig.Username = b.GetString("Nick")
if password := b.GetString("Password"); password != "" {
gumbleConfig.Password = password
}
client, err := gumble.DialWithDialer(new(net.Dialer), b.GetString("Server"), gumbleConfig, &b.tlsConfig)
if err != nil {
return err
}
b.client = client
return nil
}
func (b *Bmumble) doJoin(client *gumble.Client, channelID uint32) error {
channel, ok := client.Channels[channelID]
if !ok {
return fmt.Errorf("no channel with ID %d", channelID)
}
client.Self.Move(channel)
return nil
}
func (b *Bmumble) doSend() {
// Message sending loop that makes sure server-side
// restrictions and client-side message traits don't conflict
// with each other.
for {
select {
case serverConfig := <-b.serverConfigUpdate:
b.Log.Debugf("Received server config update: AllowHTML=%#v, MaximumMessageLength=%#v", serverConfig.AllowHTML, serverConfig.MaximumMessageLength)
b.serverConfig = serverConfig
case msg := <-b.local:
b.processMessage(&msg)
}
}
}
func (b *Bmumble) processMessage(msg *config.Message) {
b.Log.Debugf("Processing message %s", msg.Text)
allowHTML := true
if b.serverConfig.AllowHTML != nil {
allowHTML = *b.serverConfig.AllowHTML
}
// If this is a specially generated image message, send it unmodified
if msg.Event == "mumble_image" {
if allowHTML {
b.client.Self.Channel.Send(msg.Username+msg.Text, false)
} else {
b.Log.Info("Can't send image, server does not allow HTML messages")
}
return
}
// Don't process empty messages
if len(msg.Text) == 0 {
return
}
// If HTML is allowed, convert markdown into HTML, otherwise strip markdown
if allowHTML {
msg.Text = helper.ParseMarkdown(msg.Text)
} else {
msg.Text = stripmd.Strip(msg.Text)
}
// If there is a maximum message length, split and truncate the lines
var msgLines []string
if maxLength := b.serverConfig.MaximumMessageLength; maxLength != nil {
msgLines = helper.GetSubLines(msg.Text, *maxLength-len(msg.Username))
} else {
msgLines = helper.GetSubLines(msg.Text, 0)
}
// Send the individual lindes
for i := range msgLines {
b.client.Self.Channel.Send(msg.Username+msgLines[i], false)
}
}

View File

@@ -1,203 +0,0 @@
package nctalk
import (
"context"
"crypto/tls"
"strconv"
"strings"
"github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config"
"gomod.garykim.dev/nc-talk/ocs"
"gomod.garykim.dev/nc-talk/room"
"gomod.garykim.dev/nc-talk/user"
)
type Btalk struct {
user *user.TalkUser
rooms []Broom
*bridge.Config
}
func New(cfg *bridge.Config) bridge.Bridger {
return &Btalk{Config: cfg}
}
type Broom struct {
room *room.TalkRoom
ctx context.Context
ctxCancel context.CancelFunc
}
func (b *Btalk) Connect() error {
b.Log.Info("Connecting")
tconfig := &user.TalkUserConfig{
TLSConfig: &tls.Config{
InsecureSkipVerify: b.GetBool("SkipTLSVerify"), //nolint:gosec
},
}
var err error
b.user, err = user.NewUser(b.GetString("Server"), b.GetString("Login"), b.GetString("Password"), tconfig)
if err != nil {
b.Log.Error("Config could not be used")
return err
}
_, err = b.user.Capabilities()
if err != nil {
b.Log.Error("Cannot Connect")
return err
}
b.Log.Info("Connected")
return nil
}
func (b *Btalk) Disconnect() error {
for _, r := range b.rooms {
r.ctxCancel()
}
return nil
}
func (b *Btalk) JoinChannel(channel config.ChannelInfo) error {
tr, err := room.NewTalkRoom(b.user, channel.Name)
if err != nil {
return err
}
newRoom := Broom{
room: tr,
}
newRoom.ctx, newRoom.ctxCancel = context.WithCancel(context.Background())
c, err := newRoom.room.ReceiveMessages(newRoom.ctx)
if err != nil {
return err
}
b.rooms = append(b.rooms, newRoom)
// Config
guestSuffix := " (Guest)"
if b.IsKeySet("GuestSuffix") {
guestSuffix = b.GetString("GuestSuffix")
}
go func() {
for msg := range c {
msg := msg
// ignore messages that are one of the following
// * not a message from a user
// * from ourselves
if msg.MessageType != ocs.MessageComment || msg.ActorID == b.user.User {
continue
}
remoteMessage := config.Message{
Text: formatRichObjectString(msg.Message, msg.MessageParameters),
Channel: newRoom.room.Token,
Username: DisplayName(msg, guestSuffix),
UserID: msg.ActorID,
Account: b.Account,
}
// It is possible for the ID to not be set on older versions of Talk so we only set it if
// the ID is not blank
if msg.ID != 0 {
remoteMessage.ID = strconv.Itoa(msg.ID)
}
// Handle Files
err = b.handleFiles(&remoteMessage, &msg)
if err != nil {
b.Log.Errorf("Error handling file: %#v", msg)
continue
}
b.Log.Debugf("<= Message is %#v", remoteMessage)
b.Remote <- remoteMessage
}
}()
return nil
}
func (b *Btalk) Send(msg config.Message) (string, error) {
r := b.getRoom(msg.Channel)
if r == nil {
b.Log.Errorf("Could not find room for %v", msg.Channel)
return "", nil
}
// Talk currently only supports sending normal messages
if msg.Event != "" {
return "", nil
}
sentMessage, err := r.room.SendMessage(msg.Username + msg.Text)
if err != nil {
b.Log.Errorf("Could not send message to room %v from %v: %v", msg.Channel, msg.Username, err)
return "", nil
}
return strconv.Itoa(sentMessage.ID), nil
}
func (b *Btalk) getRoom(token string) *Broom {
for _, r := range b.rooms {
if r.room.Token == token {
return &r
}
}
return nil
}
func (b *Btalk) handleFiles(mmsg *config.Message, message *ocs.TalkRoomMessageData) error {
for _, parameter := range message.MessageParameters {
if parameter.Type == ocs.ROSTypeFile {
// Get the file
file, err := b.user.DownloadFile(parameter.Path)
if err != nil {
return err
}
if mmsg.Extra == nil {
mmsg.Extra = make(map[string][]interface{})
}
mmsg.Extra["file"] = append(mmsg.Extra["file"], config.FileInfo{
Name: parameter.Name,
Data: file,
Size: int64(len(*file)),
Avatar: false,
})
}
}
return nil
}
// Spec: https://github.com/nextcloud/server/issues/1706#issue-182308785
func formatRichObjectString(message string, parameters map[string]ocs.RichObjectString) string {
for id, parameter := range parameters {
text := parameter.Name
switch parameter.Type {
case ocs.ROSTypeUser, ocs.ROSTypeGroup:
text = "@" + text
case ocs.ROSTypeFile:
if parameter.Link != "" {
text = parameter.Name
}
}
message = strings.ReplaceAll(message, "{"+id+"}", text)
}
return message
}
func DisplayName(msg ocs.TalkRoomMessageData, suffix string) string {
if msg.ActorType == ocs.ActorGuest {
if msg.ActorDisplayName == "" {
return "Guest"
}
return msg.ActorDisplayName + suffix
}
return msg.ActorDisplayName
}

View File

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

View File

@@ -58,9 +58,6 @@ func (b *Brocketchat) doConnectWebhookURL() error {
func (b *Brocketchat) apiLogin() error {
b.Log.Debugf("handling apiLogin()")
credentials := &models.UserCredentials{Email: b.GetString("login"), Password: b.GetString("password")}
if b.GetString("Token") != "" {
credentials = &models.UserCredentials{ID: b.GetString("Login"), Token: b.GetString("Token")}
}
myURL, err := url.Parse(b.GetString("server"))
if err != nil {
return err

View File

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

View File

@@ -1,22 +1,18 @@
package bslack
import (
"errors"
"fmt"
"html"
"time"
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
"github.com/slack-go/slack"
"github.com/nlopes/slack"
)
// ErrEventIgnored is for events that should be ignored
var ErrEventIgnored = errors.New("this event message should ignored")
func (b *Bslack) handleSlack() {
messages := make(chan *config.Message)
if b.GetString(incomingWebhookConfig) != "" && b.GetString(tokenConfig) == "" {
if b.GetString(incomingWebhookConfig) != "" {
b.Log.Debugf("Choosing webhooks based receiving")
go b.handleMatterHook(messages)
} else {
@@ -34,7 +30,6 @@ func (b *Bslack) handleSlack() {
message.Text = b.replaceVariable(message.Text)
message.Text = b.replaceChannel(message.Text)
message.Text = b.replaceURL(message.Text)
message.Text = b.replaceb0rkedMarkDown(message.Text)
message.Text = html.UnescapeString(message.Text)
// Add the avatar
@@ -48,7 +43,7 @@ func (b *Bslack) handleSlack() {
func (b *Bslack) handleSlackClient(messages chan *config.Message) {
for msg := range b.rtm.IncomingEvents {
if msg.Type != sUserTyping && msg.Type != sHello && msg.Type != sLatencyReport {
if msg.Type != sUserTyping && msg.Type != sLatencyReport {
b.Log.Debugf("== Receiving event %#v", msg.Data)
}
switch ev := msg.Data.(type) {
@@ -57,9 +52,7 @@ func (b *Bslack) handleSlackClient(messages chan *config.Message) {
continue
}
rmsg, err := b.handleTypingEvent(ev)
if err == ErrEventIgnored {
continue
} else if err != nil {
if err != nil {
b.Log.Errorf("%#v", err)
continue
}
@@ -93,7 +86,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, *slack.ConnectingEvent:
case *slack.LatencyReport:
continue
default:
b.Log.Debugf("Unhandled incoming event: %T", ev)
@@ -130,11 +123,11 @@ 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) {
return true
}
if ev.SubMessage != nil {
@@ -149,16 +142,6 @@ func (b *Bslack) skipMessageEvent(ev *slack.MessageEvent) bool {
if ev.SubType == "message_replied" && ev.Hidden {
return true
}
if len(ev.SubMessage.Blocks.BlockSet) == 1 {
block, ok := ev.SubMessage.Blocks.BlockSet[0].(*slack.SectionBlock)
hasOurCallbackID = ok && block.BlockID == "matterbridge_"+b.uuid
}
}
// Skip any messages that we made ourselves or from 'slackbot' (see #527).
if ev.Username == sSlackBotUser ||
(b.rtm != nil && ev.Username == b.si.User.Name) || hasOurCallbackID {
return true
}
if len(ev.Files) > 0 {
@@ -286,9 +269,6 @@ func (b *Bslack) handleAttachments(ev *slack.MessageEvent, rmsg *config.Message)
}
func (b *Bslack) handleTypingEvent(ev *slack.UserTypingEvent) (*config.Message, error) {
if ev.User == b.si.User.ID {
return nil, ErrEventIgnored
}
channelInfo, err := b.channels.getChannelByID(ev.Channel)
if err != nil {
return nil, err

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
@@ -188,36 +188,6 @@ func (b *Bslack) replaceURL(text string) string {
return text
}
func (b *Bslack) replaceb0rkedMarkDown(text string) string {
// taken from https://github.com/mattermost/mattermost-server/blob/master/app/slackimport.go
//
regexReplaceAllString := []struct {
regex *regexp.Regexp
rpl string
}{
// bold
{
regexp.MustCompile(`(^|[\s.;,])\*(\S[^*\n]+)\*`),
"$1**$2**",
},
// strikethrough
{
regexp.MustCompile(`(^|[\s.;,])\~(\S[^~\n]+)\~`),
"$1~~$2~~",
},
// single paragraph blockquote
// Slack converts > character to &gt;
{
regexp.MustCompile(`(?sm)^&gt;`),
">",
},
}
for _, rule := range regexReplaceAllString {
text = rule.regex.ReplaceAllString(text, rule.rpl)
}
return text
}
func (b *Bslack) replaceCodeFence(text string) string {
return codeFenceRE.ReplaceAllString(text, "```")
}

View File

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

View File

@@ -12,9 +12,9 @@ import (
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
"github.com/42wim/matterbridge/matterhook"
lru "github.com/hashicorp/golang-lru"
"github.com/hashicorp/golang-lru"
"github.com/nlopes/slack"
"github.com/rs/xid"
"github.com/slack-go/slack"
)
type Bslack struct {
@@ -36,7 +36,6 @@ type Bslack struct {
}
const (
sHello = "hello"
sChannelJoin = "channel_join"
sChannelLeave = "channel_leave"
sChannelJoined = "channel_joined"
@@ -64,7 +63,6 @@ const (
editSuffixConfig = "EditSuffix"
iconURLConfig = "iconurl"
noSendJoinConfig = "nosendjoinpart"
messageLength = 3000
)
func New(cfg *bridge.Config) bridge.Bridger {
@@ -195,7 +193,6 @@ func (b *Bslack) Send(msg config.Message) (string, error) {
b.Log.Debugf("=> Receiving %#v", msg)
}
msg.Text = helper.ClipMessage(msg.Text, messageLength)
msg.Text = b.replaceCodeFence(msg.Text)
// Make a action /me of the message
@@ -204,7 +201,7 @@ func (b *Bslack) Send(msg config.Message) (string, error) {
}
// Use webhook to send the message
if b.GetString(outgoingWebhookConfig) != "" && b.GetString(tokenConfig) == "" {
if b.GetString(outgoingWebhookConfig) != "" {
return "", b.sendWebhook(msg)
}
return b.sendRTM(msg)
@@ -410,6 +407,7 @@ 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
@@ -428,6 +426,11 @@ 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 {
@@ -493,6 +496,8 @@ 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)
@@ -503,19 +508,6 @@ 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,10 +130,6 @@ 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

@@ -85,7 +85,7 @@ func (b *Bsteam) handleEvents() {
func (b *Bsteam) handleLogOnFailed(e *steam.LogOnFailedEvent, myLoginInfo *steam.LogOnDetails) error {
switch e.Result {
case steamlang.EResult_AccountLoginDeniedNeedTwoFactor:
case steamlang.EResult_AccountLogonDeniedNeedTwoFactorCode:
b.Log.Info("Steam guard isn't letting me in! Enter 2FA code:")
var code string
fmt.Scanf("%s", &code)

View File

@@ -39,33 +39,23 @@ func (b *Btelegram) handleGroups(rmsg *config.Message, message *tgbotapi.Message
// handleForwarded handles forwarded messages
func (b *Btelegram) handleForwarded(rmsg *config.Message, message *tgbotapi.Message) {
if message.ForwardDate == 0 {
return
}
if message.ForwardFrom == nil {
rmsg.Text = "Forwarded from " + unknownUser + ": " + rmsg.Text
return
}
if message.ForwardFrom != nil {
usernameForward := ""
if b.GetBool("UseFirstName") {
usernameForward = message.ForwardFrom.FirstName
}
if usernameForward == "" {
usernameForward = message.ForwardFrom.UserName
if usernameForward == "" {
usernameForward = message.ForwardFrom.FirstName
}
}
if usernameForward == "" {
usernameForward = unknownUser
}
rmsg.Text = "Forwarded from " + usernameForward + ": " + rmsg.Text
}
}
// handleQuoting handles quoting of previous messages
func (b *Btelegram) handleQuoting(rmsg *config.Message, message *tgbotapi.Message) {
@@ -105,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 != "" || (b.General.MediaServerDownload != "" && b.General.MediaDownloadPath != "") {
if b.General.MediaServerUpload != "" {
b.handleDownloadAvatar(message.From.ID, rmsg.Channel)
}
}
@@ -217,46 +207,6 @@ func (b *Btelegram) handleDownloadAvatar(userid int, channel string) {
}
}
func (b *Btelegram) maybeConvertTgs(name *string, data *[]byte) {
var format string
switch b.GetString("MediaConvertTgs") {
case FormatWebp:
b.Log.Debugf("Tgs to WebP conversion enabled, converting %v", name)
format = FormatWebp
case FormatPng:
// The WebP to PNG converter can't handle animated webp files yet,
// and I'm not going to write a path for x/image/webp.
// The error message would be:
// conversion failed: webp: non-Alpha VP8X is not implemented
// So instead, we tell lottie to directly go to PNG.
b.Log.Debugf("Tgs to PNG conversion enabled, converting %v", name)
format = FormatPng
default:
// Otherwise, no conversion was requested. Trying to run the usual webp
// converter would fail, because '.tgs.webp' is actually a gzipped JSON
// file, and has nothing to do with WebP.
return
}
err := helper.ConvertTgsToX(data, format, b.Log)
if err != nil {
b.Log.Errorf("conversion failed: %v", err)
} else {
*name = strings.Replace(*name, "tgs.webp", format, 1)
}
}
func (b *Btelegram) maybeConvertWebp(name *string, data *[]byte) {
if b.GetBool("MediaConvertWebPToPNG") {
b.Log.Debugf("WebP to PNG conversion enabled, converting %v", name)
err := helper.ConvertWebPToPNG(data)
if err != nil {
b.Log.Errorf("conversion failed: %v", err)
} else {
*name = strings.Replace(*name, ".webp", ".png", 1)
}
}
}
// handleDownloadFile handles file download
func (b *Btelegram) handleDownload(rmsg *config.Message, message *tgbotapi.Message) error {
size := 0
@@ -304,13 +254,15 @@ func (b *Btelegram) handleDownload(rmsg *config.Message, message *tgbotapi.Messa
if err != nil {
return err
}
if strings.HasSuffix(name, ".tgs.webp") {
b.maybeConvertTgs(&name, data)
} else if strings.HasSuffix(name, ".webp") {
b.maybeConvertWebp(&name, data)
if strings.HasSuffix(name, ".webp") && b.GetBool("MediaConvertWebPToPNG") {
b.Log.Debugf("WebP to PNG conversion enabled, converting %s", name)
err := helper.ConvertWebPToPNG(data)
if err != nil {
b.Log.Errorf("conversion failed: %s", err)
} else {
name = strings.Replace(name, ".webp", ".png", 1)
}
}
helper.HandleDownloadData(b.Log, rmsg, name, message.Caption, "", data, b.General)
return nil
}
@@ -360,9 +312,6 @@ func (b *Btelegram) handleEdit(msg *config.Message, chatid int64) (string, error
case "Markdown":
b.Log.Debug("Using mode markdown")
m.ParseMode = tgbotapi.ModeMarkdown
case MarkdownV2:
b.Log.Debug("Using mode MarkdownV2")
m.ParseMode = MarkdownV2
}
if strings.ToLower(b.GetString("MessageFormat")) == HTMLNick {
b.Log.Debug("Using mode HTML - nick only")
@@ -408,14 +357,6 @@ 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

@@ -2,23 +2,19 @@ package btelegram
import (
"html"
"log"
"strconv"
"strings"
"github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
"github.com/go-telegram-bot-api/telegram-bot-api"
)
const (
unknownUser = "unknown"
HTMLFormat = "HTML"
HTMLNick = "htmlnick"
MarkdownV2 = "MarkdownV2"
FormatPng = "png"
FormatWebp = "webp"
)
type Btelegram struct {
@@ -28,16 +24,6 @@ type Btelegram struct {
}
func New(cfg *bridge.Config) bridge.Bridger {
tgsConvertFormat := cfg.GetString("MediaConvertTgs")
if tgsConvertFormat != "" {
err := helper.CanConvertTgsToX()
if err != nil {
log.Fatalf("Telegram bridge configured to convert .tgs files to '%s', but lottie does not appear to work:\n%#v", tgsConvertFormat, err)
}
if tgsConvertFormat != FormatPng && tgsConvertFormat != FormatWebp {
log.Fatalf("Telegram bridge configured to convert .tgs files to '%s', but only '%s' and '%s' are supported.", FormatPng, FormatWebp, tgsConvertFormat)
}
}
return &Btelegram{Config: cfg, avatarMap: make(map[string]string)}
}
@@ -95,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 _, msgErr := b.sendMessage(chatid, rmsg.Username, rmsg.Text); msgErr != nil {
b.Log.Errorf("sendMessage failed: %s", msgErr)
if _, err := b.sendMessage(chatid, rmsg.Username, rmsg.Text); err != nil {
b.Log.Errorf("sendMessage failed: %s", err)
}
}
// check if we have files to upload (from slack, telegram or mattermost)
@@ -111,16 +97,9 @@ func (b *Btelegram) Send(msg config.Message) (string, error) {
}
// Post normal message
// 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 {
res, err := b.c.GetFileDirectURL(id)
if err != nil {
@@ -140,18 +119,11 @@ func (b *Btelegram) sendMessage(chatid int64, username, text string) (string, er
b.Log.Debug("Using mode markdown")
m.ParseMode = tgbotapi.ModeMarkdown
}
if b.GetString("MessageFormat") == MarkdownV2 {
b.Log.Debug("Using mode MarkdownV2")
m.ParseMode = MarkdownV2
}
if strings.ToLower(b.GetString("MessageFormat")) == HTMLNick {
b.Log.Debug("Using mode HTML - nick only")
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,15 +1,11 @@
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"
)
/*
@@ -23,43 +19,10 @@ Check:
// HandleError received from WhatsApp
func (b *Bwhatsapp) HandleError(err error) {
// ignore received invalid data errors. https://github.com/42wim/matterbridge/issues/843
// ignore tag 174 errors. https://github.com/42wim/matterbridge/issues/1094
if strings.Contains(err.Error(), "error processing data: received invalid data") || strings.Contains(err.Error(), "invalid string with tag 174") {
if strings.Contains(err.Error(), "error processing data: received invalid data") {
return
}
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()
b.startedAt = uint64(time.Now().Unix())
return
}
}
b.Log.Errorf("%v", err) // TODO implement proper handling? at least respond to different error types
}
// HandleTextMessage sent from WhatsApp, relay it to the brige
@@ -73,18 +36,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
if message.Info.Source != nil && message.Info.Source.Participant != nil {
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
}
@@ -92,8 +53,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
@@ -105,13 +66,13 @@ 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{}),
@@ -120,7 +81,7 @@ func (b *Bwhatsapp) HandleTextMessage(message whatsapp.TextMessage) {
// 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
}
@@ -128,77 +89,11 @@ func (b *Bwhatsapp) HandleTextMessage(message whatsapp.TextMessage) {
b.Remote <- rmsg
}
// 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
if message.Info.Source != nil && message.Info.Source.Participant != nil {
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) HandleImageMessage(message whatsapp.ImageMessage) {
// fmt.Println(message) // TODO implement
//}
//
//func (b *Bwhatsapp) HandleVideoMessage(message whatsapp.VideoMessage) {
// fmt.Println(message) // TODO implement
//}

View File

@@ -80,33 +80,8 @@ func (b *Bwhatsapp) getSenderName(senderJid string) string {
// if user is not in phone contacts
// it is the most obvious scenario unless you sync your phone contacts with some remote updated source
// users can change it in their WhatsApp settings -> profile -> click on Avatar
if sender.Notify != "" {
return sender.Notify
}
if sender.Short != "" {
return sender.Short
}
}
// try to reload this contact
_, err := b.conn.Contacts()
if err != nil {
b.Log.Errorf("error on update of contacts: %v", err)
}
if contact, exists := b.conn.Store.Contacts[senderJid]; exists {
// Add it to the user map
b.users[senderJid] = contact
if contact.Name != "" {
return contact.Name
}
// if user is not in phone contacts
// same as above
return contact.Notify
}
return ""
}

View File

@@ -1,14 +1,11 @@
package bwhatsapp
import (
"bytes"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"mime"
"os"
"path/filepath"
"strings"
"time"
@@ -183,7 +180,7 @@ func (b *Bwhatsapp) Disconnect() error {
}
func isGroupJid(identifier string) bool {
return strings.HasSuffix(identifier, "@g.us") || strings.HasSuffix(identifier, "@temp") || strings.HasSuffix(identifier, "@broadcast")
return strings.HasSuffix(identifier, "@g.us") || strings.HasSuffix(identifier, "@temp")
}
// JoinChannel Join a WhatsApp group specified in gateway config as channel='number-id@g.us' or channel='Channel name'
@@ -233,66 +230,6 @@ 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
@@ -322,25 +259,18 @@ func (b *Bwhatsapp) Send(msg config.Message) (string, error) {
// TODO handle edit as a message reply with updated text
}
// 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)
}
}
//// 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)
// }
//}
// Post text message
message := whatsapp.TextMessage{
text := whatsapp.TextMessage{
Info: whatsapp.MessageInfo{
RemoteJid: msg.Channel, // which equals to group id
},
@@ -351,14 +281,15 @@ 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
idBytes := make([]byte, 10)
if _, err := rand.Read(idBytes); err != nil {
bytes := make([]byte, 10)
if _, err := rand.Read(bytes); err != nil {
b.Log.Warn(err.Error())
}
message.Info.Id = strings.ToUpper(hex.EncodeToString(idBytes))
_, err := b.conn.Send(message)
text.Info.Id = strings.ToUpper(hex.EncodeToString(bytes))
return message.Info.Id, err
_, err := b.conn.Send(text)
return text.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,34 +0,0 @@
package bxmpp
import (
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
"github.com/matterbridge/go-xmpp"
)
// handleDownloadAvatar downloads the avatar of userid from channel
// sends a EVENT_AVATAR_DOWNLOAD message to the gateway if successful.
// logs an error message if it fails
func (b *Bxmpp) handleDownloadAvatar(avatar xmpp.AvatarData) {
rmsg := config.Message{
Username: "system",
Text: "avatar",
Channel: b.parseChannel(avatar.From),
Account: b.Account,
UserID: avatar.From,
Event: config.EventAvatarDownload,
Extra: make(map[string][]interface{}),
}
if _, ok := b.avatarMap[avatar.From]; !ok {
b.Log.Debugf("Avatar.From: %s", avatar.From)
err := helper.HandleDownloadSize(b.Log, &rmsg, avatar.From+".png", int64(len(avatar.Data)), b.General)
if err != nil {
b.Log.Error(err)
return
}
helper.HandleDownloadData(b.Log, &rmsg, avatar.From+".png", rmsg.Text, "", &avatar.Data, b.General)
b.Log.Debugf("Avatar download complete")
b.Remote <- rmsg
}
}

View File

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

View File

@@ -23,17 +23,12 @@ type Bxmpp struct {
xmppMap map[string]string
connected bool
sync.RWMutex
avatarAvailability map[string]bool
avatarMap map[string]string
}
func New(cfg *bridge.Config) bridge.Bridger {
return &Bxmpp{
Config: cfg,
xmppMap: make(map[string]string),
avatarAvailability: make(map[string]bool),
avatarMap: make(map[string]string),
}
}
@@ -72,19 +67,8 @@ func (b *Bxmpp) Send(msg config.Message) (string, error) {
if msg.Event == config.EventMsgDelete {
return "", nil
}
b.Log.Debugf("=> Receiving %#v", msg)
if msg.Event == config.EventAvatarDownload {
return b.cacheAvatar(&msg), nil
}
// Make a action /me of the message, prepend the username with it.
// https://xmpp.org/extensions/xep-0245.html
if msg.Event == config.EventUserAction {
msg.Username = "/me " + msg.Username
}
// Upload a file (in XMPP case send the upload URL because XMPP has no native upload support).
if msg.Extra != nil {
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
@@ -123,29 +107,24 @@ func (b *Bxmpp) Send(msg config.Message) (string, error) {
}
func (b *Bxmpp) createXMPP() error {
if !strings.Contains(b.GetString("Jid"), "@") {
return fmt.Errorf("the Jid %s doesn't contain an @", b.GetString("Jid"))
}
tc := &tls.Config{
ServerName: strings.Split(b.GetString("Jid"), "@")[1],
InsecureSkipVerify: b.GetBool("SkipTLSVerify"), // nolint: gosec
}
xmpp.DebugWriter = b.Log.Writer()
options := xmpp.Options{
Host: b.GetString("Server"),
User: b.GetString("Jid"),
Password: b.GetString("Password"),
NoTLS: true,
StartTLS: !b.GetBool("NoTLS"),
StartTLS: true,
TLSConfig: tc,
Debug: b.GetBool("debug"),
Logger: b.Log.Writer(),
Session: true,
Status: "",
StatusMessage: "",
Resource: "",
InsecureAllowUnencryptedAuth: b.GetBool("NoTLS"),
InsecureAllowUnencryptedAuth: false,
}
var err error
b.xc, err = options.NewClient()
@@ -246,16 +225,6 @@ func (b *Bxmpp) handleXMPP() error {
event = config.EventTopicChange
}
available, sok := b.avatarAvailability[v.Remote]
avatar := ""
if !sok {
b.Log.Debugf("Requesting avatar data")
b.avatarAvailability[v.Remote] = false
b.xc.AvatarRequestData(v.Remote)
} else if available {
avatar = getAvatar(b.avatarMap, v.Remote, b.General)
}
msgID := v.ID
if v.ReplaceID != "" {
msgID = v.ReplaceID
@@ -265,7 +234,6 @@ func (b *Bxmpp) handleXMPP() error {
Text: v.Text,
Channel: b.parseChannel(v.Remote),
Account: b.Account,
Avatar: avatar,
UserID: v.Remote,
ID: msgID,
Event: event,
@@ -282,10 +250,6 @@ func (b *Bxmpp) handleXMPP() error {
b.Log.Debugf("<= Message is %#v", rmsg)
b.Remote <- rmsg
}
case xmpp.AvatarData:
b.handleDownloadAvatar(v)
b.avatarAvailability[v.From] = true
b.Log.Debugf("Avatar for %s is now available", v.From)
case xmpp.Presence:
// Do nothing.
}

View File

@@ -135,25 +135,19 @@ func (b *Bzulip) handleQueue() error {
if m.SenderEmail == b.GetString("login") {
continue
}
avatarURL := m.AvatarURL
if !strings.HasPrefix(avatarURL, "http") {
avatarURL = b.GetString("server") + avatarURL
}
rmsg := config.Message{
Username: m.SenderFullName,
Text: m.Content,
Channel: b.getChannel(m.StreamID) + "/topic:" + m.Subject,
Account: b.Account,
UserID: strconv.Itoa(m.SenderID),
Avatar: avatarURL,
Avatar: m.AvatarURL,
}
b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account)
b.Log.Debugf("<= Message is %#v", rmsg)
b.Remote <- rmsg
b.q.LastEventID = m.ID
}
time.Sleep(time.Second * 3)
}
}

View File

@@ -1,357 +1,12 @@
# v1.20.0
## Breaking
- matrix: Send the display name instead of the user name (matrix) (#1282)
Matrix now sends the displayname if set instead of the username. If you want to keep the username, add `UseUsername=true` to your matrix config. <https://github.com/42wim/matterbridge/wiki/Settings#useusername-1>
- discord: Disable webhook editing (discord) (#1296)
Because of issues with ratelimiting of webhook editing, this feature is now disabled. If you have multiple discord channels you bridge, you'll need to add a `webhookURL` to the `[gateway.inout.options]`. See <https://github.com/42wim/matterbridge/blob/master/matterbridge.toml.sample#L1864-L1870> for an example.
## New features
- general: Allow tengo to drop messages using msgDrop (#1272)
- general: Update libraries (whatsapp,markdown,mattermost,ssh-chat)
- irc: Add PingDelay option (irc) (#1269)
- matrix: Allow message edits on matrix (#1286)
- xmpp: add NoTLS option to allow plaintext XMPP connections (#1288)
## Enhancements
- discord: Edit messages via webhook (1287)
- general: Add extra debug to log time spent sending a message per bridge (#1299)
This release couldn't exist without the following contributors:
@nightmared, @zhoreeq
# v1.19.0
## New features
- mumble: new protocol added: Add Mumble support (#1245)
- nctalk: Add support for downloading files (nctalk) (#1249)
- nctalk: Append a suffix if user is a guest user (nctalk) (#1250)
## Enhancements
- irc: Add even more debug for irc (#1266)
- matrix: Add username formatting for all events (matrix) (#1233)
- matrix: Permit uploading files of other mimetypes (#1237)
- whatsapp: Use vendored whatsapp version (#1258)
- whatsapp: Add username for images from WhatsApp (#1232)
This release couldn't exist without the following contributors:
@Dellle, @42wim, @gary-kim, @s3lph, @BenWiederhake
# v1.18.3
## Enhancements
- nctalk: Add TLSConfig to nctalk (#1195)
- whatsapp: Handle broadcasts as groups in Whatsapp #1213
- matrix: switch to upstream gomatrix #1219
- api: support multiple websocket clients #1205
## Bugfix
- general: update vendor
- zulip: Check location of avatarURL (zulip). Fixes #1214 (#1227)
- nctalk: Fix issue with too many open files #1223
- nctalk: Fix mentions #1222
- nctalk: Fix message replays #1220
This release couldn't exist without the following contributors:
@gary-kim, @tilosp, @NikkyAI, @escoand, @42wim
# v1.18.2
## Bugfix
- zulip: Fix error loop (zulip) (#1210)
- whatsapp: Update whatsapp vendor and fix a panic (#1209)
This release couldn't exist without the following contributors:
@SuperSandro2000, @42wim
# v1.18.1
## New features
- telegram: Support Telegram animated stickers (tgs) format (#1173). See https://github.com/42wim/matterbridge/wiki/Settings#mediaConverttgs for more info
## Enhancements
- matrix: Remove HTML formatting for push messages (#1188) (#1189)
- mattermost: Use mattermost v5 module (#1192)
## Bugfix
- whatsapp: Handle panic in whatsapp. Fixes #1180 (#1184)
- nctalk: Fix Nextcloud Talk connection failure (#1179)
- matrix: Sleep when ratelimited on joins (matrix). Fixes #1201 (#1206)
This release couldn't exist without the following contributors:
@42wim, @BenWiederhake, @Dellle, @gary-kim
# v1.18.0
## New features
- nctalk: new protocol added. Add Nextcloud Talk support #1167
- general: Add an option to log into a file rather than stdout (#1168)
- api: Add websocket to API (#970)
## Enhancements
- telegram: Fix MarkdownV2 support in Telegram (#1169)
- whatsapp: Reload user information when a new contact is detected (whatsapp) (#1160)
- api: Add sane RemoteNickFormat default for API (#1157)
- irc: Skip gIRC built-in rate limiting (irc) (#1164)
- irc: Only colour IRC nicks if there is one. (#1161)
- docker: Combine runs to one layer (#1151)
## Bugfix
- general: Update dependencies for 1.18.0 release (#1175)
Discord users are encouraged to upgrade, this release works with the move to the discord.com domain.
This release couldn't exist without the following contributors:
@42wim, @jlu5, @qaisjp, @TheHolyRoger, @SuperSandro2000, @gary-kim, @z3bra, @greenx, @haykam821, @nathanaelhoun
# v1.17.5
## Enhancements
- irc: Add StripMarkdown option (irc). (#1145)
- general: Increase debug logging with function,file and linenumber (#1147)
- general: Update Dockerfile so inotify works (#1148)
- matrix: Add an option to disable sending HTML to matrix. Fixes #1022 (#1135)
- xmpp: Implement xep-0245 (xmpp). Closes #1137 (#1144)
## Bugfix
- discord: Fix #1120: replaceAction "_" crash (discord) (#1121)
- discord: Fix #1049: missing space before embeds (discord) (#1124)
- discord: Fix webhook EventUserAction messages being skipped (discord) (#1133)
- matrix: Avoid creating invalid url when the user doesn't have an avatar (matrix) (#1130)
- msteams: Ignore non-user messages (msteams). Fixes #1141 (#1149)
- slack: Do not use webhooks when token is configured (slack) (fixes #1123) (#1134)
- telegram: Fix forward from hidden users (telegram). Closes #1131 (#1143)
- xmpp: Prevent re-requesting avatar data (xmpp) (#1117)
This release couldn't exist without the following contributors:
@qaisjp, @xnaas, @42wim, @Polynomdivision, @tfve
# v1.17.4
## Bugfix
- general: Lowercase account names. Fixes #1108 (#1110)
- msteams: Remove panics and retry polling on failure (msteams). Fixes #1104 (#1105
- whatsapp: Update Rhymen/go-whatsapp. Fixes #1107 (#1109) (make whatsapp working again)
- discord: Add an ID cache (discord). Fixes #1106 (#1111) (fix delete/edits with webhooks)
# v1.17.3
## Enhancements
- xmpp: Implement User Avatar spoofing of XMPP users #1090
- rocketchat: Relay Joins/Topic changes in RocketChat bridge (#1085)
- irc: Add JoinDelay option (irc). Fixes #1084 (#1098)
- slack: Clip too long messages on 3000 length (slack). Fixes #1081 (#1102)
## Bugfix
- general: Fix the behavior of ShowTopicChange and SyncTopic (#1086)
- slack: Prevent image/message looping (slack). Fixes #1088 (#1096)
- whatsapp: Ignore non-critical errors (whatsapp). Fixes #1094 (#1100)
- irc: Add extra space before colon in attachments (irc). Fixes #1089 (#1101)
This release couldn't exist without the following contributors:
@42wim, @ldruschk, @qaisjp, @Polynomdivision
# v1.17.2
## Enhancements
- slack: Update vendor slack-go/slack (#1068)
- general: Update vendor d5/tengo (#1066)
- general: Clarify terminology used in mapping group chat IDs to channels in config (#1079)
## Bugfix
- whatsapp: Update Rhymen/go-whatsapp vendor and whatsapp version (#1078). Fixes Media upload #1074
- whatsapp: Reset start timestamp on reconnect (whatsapp). Fixes #1059 (#1064)
This release couldn't exist without the following contributors:
@42wim, @jheiselman
# v1.17.1
## Enhancements
- docker: Remove build dependencies from final image (multistage build) #1057
## Bugfix
- general: Don't transmit typing events from ourselves #1056
- general: Add support for build tags #1054
- discord: Strip extra info from emotes (discord) #1052
- msteams: fix macos build: Update vendor yaegashi/msgraph.go to v0.1.2 #1036
- whatsapp: Update client version whatsapp. Fixes #1061 #1062
This release couldn't exist without the following contributors:
@awigen, @qaisjp, @42wim
# 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
- slack: Fix issues with ratelimiting #959
- mattermost: Fix bug when using webhookURL and login/token together #960
# v1.16.2
## New features
- keybase: Add support for receiving attachments (keybase) (#923)
## Enhancements
- general: Switch to new emoji library kyokomi/emoji (#948)
- general: Update markdown parsing library to github.com/gomarkdown/markdown (#944)
- ssh-chat: Update shazow/ssh-chat dependency (#947)
## Bugfix
- slack: Fix issues with the slack block kit API #937 (#943).
This release couldn't exist without the following contributors:
@42wim, @bmpickford, @goncalor
# v1.16.1
## New features
* rocketchat: add token support #892
* matrix: Add support for uploading application/x and audio/x (matrix). #929
## Enhancements
* general: Do configuration validation on start-up. Fixes #888
* general: updated vendored libraries (discord/whatsapp) #932
* discord: user typing messages #914
* slack: Convert slack bold/strike to correct markdown (slack). Fixes #918
## Bugfix
* discord: fix Failed to fetch information for members message. #894
* discord: remove obsolete file upload links (discord). #931
* slack: suppress unhandled HelloEvent message #913
* mattermost: Fix panic on WebhookURL only setting (mattermost). #917
* matrix: fix corrupted links between slack and matrix #924
This release couldn't exist without the following contributors:
@qaisjp, @hramrach, @42wim
# v1.16.0
## New features
* keybase: new protocol added. Add initial Keybase Chat support #877 Thanks to @hyperobject
* discord: Support webhook files in discord #872
## Enhancements
* general: update dependencies
## Bugfix
* discord: Underscores from Discord don't arrive correctly #864
* xmpp: Fix possible panic at startup of the XMPP bridge #869
* mattermost: Make getChannelIdTeam behave like GetChannelId for groups (mattermost) #873
This release couldn't exist without the following contributors:
@hyperobject, @42wim, @bucko909, @MOZGIII
# dev
# v1.15.1
## New features
* discord: Support webhook message deletions (discord) (#853)
## Enhancements
* discord: Support bulk deletions #851
* discord: Support channels in categories #863 (use category/channel. See matterbridge.toml.sample for more info)
* mattermost: Add an option to skip the Mattermost server version check #849
## Bugfix
* xmpp: fix segfault when disconnected/reconnected #856
* telegram: fix panic in handleEntities #858

30
ci/bintray.sh Executable file
View File

@@ -0,0 +1,30 @@
#!/usr/bin/env bash
set -u -e -x -o pipefail
go version | grep go1.12 || exit
VERSION=$(git describe --tags)
mkdir ci/binaries
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-windows-amd64.exe
GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-linux-amd64
GOOS=linux GOARCH=arm go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-linux-arm
GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-darwin-amd64
cd ci
cat > deploy.json <<EOF
{
"package": {
"name": "Matterbridge",
"repo": "nightly",
"subject": "42wim"
},
"version": {
"name": "$VERSION"
},
"files":
[
{"includePattern": "ci/binaries/(.*)", "uploadPattern":"\$1"}
],
"publish": true
}
EOF

17
ci/lint.sh Executable file
View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -u -e -x -o pipefail
if [[ -n "${GOLANGCI_VERSION-}" ]]; then
# Retrieve the golangci-lint linter binary.
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b ${GOPATH}/bin ${GOLANGCI_VERSION}
fi
# Run the linter.
golangci-lint run
if [[ "${GO111MODULE-off}" == "on" ]]; then
# If Go modules are active then check that dependencies are correctly maintained.
go mod tidy
go mod vendor
git diff --exit-code --quiet || (echo "Please run 'go mod tidy' to clean up the 'go.mod' and 'go.sum' files."; false)
fi

17
ci/test.sh Executable file
View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -u -e -x -o pipefail
if [[ -n "${REPORT_COVERAGE+cover}" ]]; then
# Retrieve and prepare CodeClimate's test coverage reporter.
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
chmod +x ./cc-test-reporter
./cc-test-reporter before-build
fi
# Run all the tests with the race detector and generate coverage.
go test -v -race -coverprofile c.out ./...
if [[ -n "${REPORT_COVERAGE+cover}" && "${TRAVIS_SECURE_ENV_VARS}" == "true" ]]; then
# Upload test coverage to CodeClimate.
./cc-test-reporter after-build
fi

View File

@@ -1,10 +0,0 @@
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

@@ -1,14 +0,0 @@
// See https://github.com/42wim/matterbridge/issues/881
// Generates a colored nick for each msgUsername, with example to filter specific codes
text := import("text")
fmt := import("fmt")
if outProtocol == "irc" {
// generate a color for a nick, make sure it isn't 0 or 15
colorCode := len(msgUsername)+bytes(msgUsername)[0]%14 + 2
// example if we want to use colorCode 3 when we have calculated colorcode 14
if colorCode == 14 {
colorCode = 3
}
msgUsername=fmt.sprintf("\x03%02d%s\x0F", colorCode, msgUsername)
}

View File

@@ -1,11 +0,0 @@
// +build !noapi
package bridgemap
import (
"github.com/42wim/matterbridge/bridge/api"
)
func init() {
FullMap["api"] = api.New
}

View File

@@ -1,12 +0,0 @@
// +build !nodiscord
package bridgemap
import (
bdiscord "github.com/42wim/matterbridge/bridge/discord"
)
func init() {
FullMap["discord"] = bdiscord.New
UserTypingSupport["discord"] = struct{}{}
}

View File

@@ -1,11 +0,0 @@
// +build !nogitter
package bridgemap
import (
bgitter "github.com/42wim/matterbridge/bridge/gitter"
)
func init() {
FullMap["gitter"] = bgitter.New
}

View File

@@ -1,11 +0,0 @@
// +build !noirc
package bridgemap
import (
birc "github.com/42wim/matterbridge/bridge/irc"
)
func init() {
FullMap["irc"] = birc.New
}

View File

@@ -1,11 +0,0 @@
// +build !nokeybase
package bridgemap
import (
bkeybase "github.com/42wim/matterbridge/bridge/keybase"
)
func init() {
FullMap["keybase"] = bkeybase.New
}

View File

@@ -1,11 +0,0 @@
// +build !nomatrix
package bridgemap
import (
bmatrix "github.com/42wim/matterbridge/bridge/matrix"
)
func init() {
FullMap["matrix"] = bmatrix.New
}

View File

@@ -1,11 +0,0 @@
// +build !nomattermost
package bridgemap
import (
bmattermost "github.com/42wim/matterbridge/bridge/mattermost"
)
func init() {
FullMap["mattermost"] = bmattermost.New
}

View File

@@ -1,11 +0,0 @@
// +build !nomsteams
package bridgemap
import (
bmsteams "github.com/42wim/matterbridge/bridge/msteams"
)
func init() {
FullMap["msteams"] = bmsteams.New
}

View File

@@ -1,11 +0,0 @@
// +build !nomumble
package bridgemap
import (
bmumble "github.com/42wim/matterbridge/bridge/mumble"
)
func init() {
FullMap["mumble"] = bmumble.New
}

View File

@@ -1,11 +0,0 @@
// +build !nonctalk
package bridgemap
import (
btalk "github.com/42wim/matterbridge/bridge/nctalk"
)
func init() {
FullMap["nctalk"] = btalk.New
}

View File

@@ -2,9 +2,42 @@ package bridgemap
import (
"github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/api"
bdiscord "github.com/42wim/matterbridge/bridge/discord"
bgitter "github.com/42wim/matterbridge/bridge/gitter"
birc "github.com/42wim/matterbridge/bridge/irc"
bmatrix "github.com/42wim/matterbridge/bridge/matrix"
bmattermost "github.com/42wim/matterbridge/bridge/mattermost"
brocketchat "github.com/42wim/matterbridge/bridge/rocketchat"
bslack "github.com/42wim/matterbridge/bridge/slack"
bsshchat "github.com/42wim/matterbridge/bridge/sshchat"
bsteam "github.com/42wim/matterbridge/bridge/steam"
btelegram "github.com/42wim/matterbridge/bridge/telegram"
bwhatsapp "github.com/42wim/matterbridge/bridge/whatsapp"
bxmpp "github.com/42wim/matterbridge/bridge/xmpp"
bzulip "github.com/42wim/matterbridge/bridge/zulip"
)
var (
FullMap = map[string]bridge.Factory{}
UserTypingSupport = map[string]struct{}{}
FullMap = map[string]bridge.Factory{
"api": api.New,
"discord": bdiscord.New,
"gitter": bgitter.New,
"irc": birc.New,
"mattermost": bmattermost.New,
"matrix": bmatrix.New,
"rocketchat": brocketchat.New,
"slack-legacy": bslack.NewLegacy,
"slack": bslack.New,
"sshchat": bsshchat.New,
"steam": bsteam.New,
"telegram": btelegram.New,
"whatsapp": bwhatsapp.New,
"xmpp": bxmpp.New,
"zulip": bzulip.New,
}
UserTypingSupport = map[string]struct{}{
"slack": {},
}
)

View File

@@ -1,11 +0,0 @@
// +build !norocketchat
package bridgemap
import (
brocketchat "github.com/42wim/matterbridge/bridge/rocketchat"
)
func init() {
FullMap["rocketchat"] = brocketchat.New
}

View File

@@ -1,13 +0,0 @@
// +build !noslack
package bridgemap
import (
bslack "github.com/42wim/matterbridge/bridge/slack"
)
func init() {
FullMap["slack-legacy"] = bslack.NewLegacy
FullMap["slack"] = bslack.New
UserTypingSupport["slack"] = struct{}{}
}

View File

@@ -1,11 +0,0 @@
// +build !nosshchat
package bridgemap
import (
bsshchat "github.com/42wim/matterbridge/bridge/sshchat"
)
func init() {
FullMap["sshchat"] = bsshchat.New
}

View File

@@ -1,11 +0,0 @@
// +build !nosteam
package bridgemap
import (
bsteam "github.com/42wim/matterbridge/bridge/steam"
)
func init() {
FullMap["steam"] = bsteam.New
}

View File

@@ -1,11 +0,0 @@
// +build !notelegram
package bridgemap
import (
btelegram "github.com/42wim/matterbridge/bridge/telegram"
)
func init() {
FullMap["telegram"] = btelegram.New
}

View File

@@ -1,11 +0,0 @@
// +build !nowhatsapp
package bridgemap
import (
bwhatsapp "github.com/42wim/matterbridge/bridge/whatsapp"
)
func init() {
FullMap["whatsapp"] = bwhatsapp.New
}

View File

@@ -1,11 +0,0 @@
// +build !noxmpp
package bridgemap
import (
bxmpp "github.com/42wim/matterbridge/bridge/xmpp"
)
func init() {
FullMap["xmpp"] = bxmpp.New
}

View File

@@ -1,11 +0,0 @@
// +build !nozulip
package bridgemap
import (
bzulip "github.com/42wim/matterbridge/bridge/zulip"
)
func init() {
FullMap["zulip"] = bzulip.New
}

View File

@@ -1,7 +1,6 @@
package gateway
import (
"fmt"
"io/ioutil"
"os"
"regexp"
@@ -11,10 +10,10 @@ import (
"github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/internal"
"github.com/d5/tengo/v2"
"github.com/d5/tengo/v2/stdlib"
"github.com/d5/tengo/script"
"github.com/d5/tengo/stdlib"
lru "github.com/hashicorp/golang-lru"
"github.com/matterbridge/emoji"
"github.com/peterhellberg/emojilib"
"github.com/sirupsen/logrus"
)
@@ -86,7 +85,6 @@ func (gw *Gateway) FindCanonicalMsgID(protocol string, mID string) string {
func (gw *Gateway) AddBridge(cfg *config.Bridge) error {
br := gw.Router.getBridge(cfg.Account)
if br == nil {
gw.checkConfig(cfg)
br = bridge.New(cfg)
br.Config = gw.Router.Config
br.General = &gw.BridgeValues().General
@@ -106,19 +104,6 @@ func (gw *Gateway) AddBridge(cfg *config.Bridge) error {
return nil
}
func (gw *Gateway) checkConfig(cfg *config.Bridge) {
match := false
for _, key := range gw.Router.Config.Viper().AllKeys() {
if strings.HasPrefix(key, strings.ToLower(cfg.Account)) {
match = true
break
}
}
if !match {
gw.logger.Fatalf("Account %s defined in gateway %s but no configuration found, exiting.", cfg.Account, gw.Name)
}
}
// AddConfig associates a new configuration with the gateway object.
func (gw *Gateway) AddConfig(cfg *config.Gateway) error {
gw.Name = cfg.Name
@@ -307,6 +292,8 @@ 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, "")
@@ -314,7 +301,6 @@ 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]
@@ -364,28 +350,15 @@ func (gw *Gateway) modifyAvatar(msg *config.Message, dest *bridge.Bridge) string
}
func (gw *Gateway) modifyMessage(msg *config.Message) {
if gw.BridgeValues().General.TengoModifyMessage != "" {
gw.logger.Warnf("General TengoModifyMessage=%s is deprecated and will be removed in v1.20.0, please move to Tengo InMessage=%s", gw.BridgeValues().General.TengoModifyMessage, gw.BridgeValues().General.TengoModifyMessage)
}
if err := modifyInMessageTengo(gw.BridgeValues().General.TengoModifyMessage, msg); err != nil {
if err := modifyMessageTengo(gw.BridgeValues().General.TengoModifyMessage, msg); err != nil {
gw.logger.Errorf("TengoModifyMessage failed: %s", err)
}
inMessage := gw.BridgeValues().Tengo.InMessage
if inMessage == "" {
inMessage = gw.BridgeValues().Tengo.Message
if inMessage != "" {
gw.logger.Warnf("Tengo Message=%s is deprecated and will be removed in v1.20.0, please move to Tengo InMessage=%s", inMessage, inMessage)
}
}
if err := modifyInMessageTengo(inMessage, msg); err != nil {
if err := modifyMessageTengo(gw.BridgeValues().Tengo.Message, msg); err != nil {
gw.logger.Errorf("Tengo.Message failed: %s", err)
}
// replace :emoji: to unicode
msg.Text = emoji.Sprint(msg.Text)
msg.Text = emojilib.Replace(msg.Text)
br := gw.Bridges[msg.Account]
// loop to replace messages
@@ -431,9 +404,8 @@ func (gw *Gateway) SendMessage(
}
// Too noisy to log like other events
debugSendMessage := ""
if msg.Event != config.EventUserTyping {
debugSendMessage = fmt.Sprintf("=> Sending %#v from %s (%s) to %s (%s)", msg, msg.Account, rmsg.Channel, dest.Account, channel.Name)
gw.logger.Debugf("=> Sending %#v from %s (%s) to %s (%s)", msg, msg.Account, rmsg.Channel, dest.Account, channel.Name)
}
msg.Channel = channel.Name
@@ -458,29 +430,17 @@ func (gw *Gateway) SendMessage(
msg.ParentID = "msg-parent-not-found"
}
drop, err := gw.modifyOutMessageTengo(rmsg, &msg, dest)
err := gw.modifySendMessageTengo(rmsg, &msg, dest)
if err != nil {
gw.logger.Errorf("modifySendMessageTengo: %s", err)
}
if drop {
gw.logger.Debugf("=> Tengo dropping %#v from %s (%s) to %s (%s)", msg, msg.Account, rmsg.Channel, dest.Account, channel.Name)
return "", nil
}
if debugSendMessage != "" {
gw.logger.Debug(debugSendMessage)
}
// if we are using mattermost plugin account, send messages to MattermostPlugin channel
// that can be picked up by the mattermost matterbridge plugin
if dest.Account == "mattermost.plugin" {
gw.Router.MattermostPlugin <- msg
}
defer func(t time.Time) {
gw.logger.Debugf("=> Send from %s (%s) to %s (%s) took %s", msg.Account, rmsg.Channel, dest.Account, channel.Name, time.Since(t))
}(time.Now())
mID, err := dest.Send(msg)
if err != nil {
return mID, err
@@ -532,7 +492,7 @@ func getProtocol(msg *config.Message) string {
return p[0]
}
func modifyInMessageTengo(filename string, msg *config.Message) error {
func modifyMessageTengo(filename string, msg *config.Message) error {
if filename == "" {
return nil
}
@@ -540,7 +500,7 @@ func modifyInMessageTengo(filename string, msg *config.Message) error {
if err != nil {
return err
}
s := tengo.NewScript(res)
s := script.New(res)
s.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...))
_ = s.Add("msgText", msg.Text)
_ = s.Add("msgUsername", msg.Username)
@@ -567,7 +527,7 @@ func (gw *Gateway) modifyUsernameTengo(msg *config.Message, br *bridge.Bridge) (
if err != nil {
return "", err
}
s := tengo.NewScript(res)
s := script.New(res)
s.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...))
_ = s.Add("result", "")
_ = s.Add("msgText", msg.Text)
@@ -591,28 +551,22 @@ func (gw *Gateway) modifyUsernameTengo(msg *config.Message, br *bridge.Bridge) (
return c.Get("result").String(), nil
}
func (gw *Gateway) modifyOutMessageTengo(origmsg *config.Message, msg *config.Message, br *bridge.Bridge) (bool, error) {
func (gw *Gateway) modifySendMessageTengo(origmsg *config.Message, msg *config.Message, br *bridge.Bridge) error {
filename := gw.BridgeValues().Tengo.OutMessage
var (
res []byte
err error
drop bool
)
var res []byte
var err error
if filename == "" {
res, err = internal.Asset("tengo/outmessage.tengo")
if err != nil {
return drop, err
return err
}
} else {
res, err = ioutil.ReadFile(filename)
if err != nil {
return drop, err
return err
}
}
s := tengo.NewScript(res)
s := script.New(res)
s.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...))
_ = s.Add("inAccount", origmsg.Account)
_ = s.Add("inProtocol", origmsg.Protocol)
@@ -626,19 +580,14 @@ func (gw *Gateway) modifyOutMessageTengo(origmsg *config.Message, msg *config.Me
_ = s.Add("outEvent", msg.Event)
_ = s.Add("msgText", msg.Text)
_ = s.Add("msgUsername", msg.Username)
_ = s.Add("msgDrop", drop)
c, err := s.Compile()
if err != nil {
return drop, err
return err
}
if err := c.Run(); err != nil {
return drop, err
return err
}
drop = c.Get("msgDrop").Bool()
msg.Text = c.Get("msgText").String()
msg.Username = c.Get("msgUsername").String()
return drop, nil
return nil
}

View File

@@ -15,15 +15,10 @@ import (
var testconfig = []byte(`
[irc.freenode]
server=""
[mattermost.test]
server=""
[gitter.42wim]
server=""
[discord.test]
server=""
[slack.test]
server=""
[[gateway]]
name = "bridge1"
@@ -49,15 +44,10 @@ server=""
var testconfig2 = []byte(`
[irc.freenode]
server=""
[mattermost.test]
server=""
[gitter.42wim]
server=""
[discord.test]
server=""
[slack.test]
server=""
[[gateway]]
name = "bridge1"
@@ -97,11 +87,8 @@ server=""
var testconfig3 = []byte(`
[irc.zzz]
server=""
[telegram.zzz]
server=""
[slack.zzz]
server=""
[[gateway]]
name="bridge"
enable=true
@@ -189,6 +176,7 @@ func TestNewRouter(t *testing.T) {
assert.Equal(t, 1, len(r.Gateways))
assert.Equal(t, 4, len(r.Gateways["bridge1"].Bridges))
assert.Equal(t, 4, len(r.Gateways["bridge1"].Channels))
r = maketestRouter(testconfig2)
assert.Equal(t, 2, len(r.Gateways))
assert.Equal(t, 4, len(r.Gateways["bridge1"].Bridges))
@@ -533,7 +521,7 @@ func (s *ignoreTestSuite) TestIgnoreNicks() {
func BenchmarkTengo(b *testing.B) {
msg := &config.Message{Username: "user", Text: "blah testing", Account: "protocol.account", Channel: "mychannel"}
for n := 0; n < b.N; n++ {
err := modifyInMessageTengo("bench.tengo", msg)
err := modifyMessageTengo("bench.tengo", msg)
if err != nil {
return
}

View File

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

View File

@@ -59,14 +59,8 @@ func NewRouter(rootLogger *logrus.Logger, cfg config.Config, bridgeMap map[strin
// between them.
func (r *Router) Start() error {
m := make(map[string]*bridge.Bridge)
if len(r.Gateways) == 0 {
return fmt.Errorf("no [[gateway]] configured. See https://github.com/42wim/matterbridge/wiki/How-to-create-your-config for more info")
}
for _, gw := range r.Gateways {
r.logger.Infof("Parsing gateway %s", gw.Name)
if len(gw.Bridges) == 0 {
return fmt.Errorf("no bridges configured for gateway %s. See https://github.com/42wim/matterbridge/wiki/How-to-create-your-config for more info", gw.Name)
}
for _, br := range gw.Bridges {
m[br.Account] = br
}
@@ -132,9 +126,6 @@ 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

89
go.mod
View File

@@ -4,55 +4,66 @@ require (
github.com/42wim/go-gitter v0.0.0-20170828205020-017310c2d557
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.20200727090957-6ae9b3c0a560
github.com/Rhymen/go-whatsapp v0.1.2-0.20201122130733-6e5488ac98df
github.com/d5/tengo/v2 v2.6.2
github.com/davecgh/go-spew v1.1.1
github.com/fsnotify/fsnotify v1.4.9
github.com/go-telegram-bot-api/telegram-bot-api v1.0.1-0.20200524105306-7434b0456e81
github.com/gomarkdown/markdown v0.0.0-20201113031856-722100d81a8e
github.com/google/gops v0.3.13
github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329
github.com/Rhymen/go-whatsapp v0.0.2
github.com/bwmarrin/discordgo v0.19.0
// github.com/bwmarrin/discordgo v0.19.0
github.com/d5/tengo v1.24.1
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/google/gops v0.3.6
github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4 // indirect
github.com/gorilla/schema v1.2.0
github.com/gorilla/websocket v1.4.2
github.com/hashicorp/golang-lru v0.5.4
github.com/jpillora/backoff v1.0.0
github.com/keybase/go-keybase-chat-bot v0.0.0-20200505163032-5cacf52379da
github.com/labstack/echo/v4 v4.1.17
github.com/lrstanley/girc v0.0.0-20190801035559-4fc93959e1a7
github.com/matrix-org/gomatrix v0.0.0-20200827122206-7dd5e2a05bcd
github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20200411204219-d5c18ce75048
github.com/matterbridge/discordgo v0.22.0
github.com/matterbridge/emoji v2.1.1-0.20191117213217-af507f6b02db+incompatible
github.com/matterbridge/go-xmpp v0.0.0-20200418225040-c8a3a57b4050
github.com/matterbridge/gozulipbot v0.0.0-20200820220548-be5824faa913
github.com/matterbridge/logrus-prefixed-formatter v0.5.3-0.20200523233437-d971309a77ba
github.com/mattermost/mattermost-server/v5 v5.29.0
github.com/mattn/godown v0.0.0-20201027140031-2c7783b24de7
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f // indirect
github.com/gorilla/schema v1.1.0
github.com/gorilla/websocket v1.4.0
github.com/hashicorp/golang-lru v0.5.1
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/labstack/echo/v4 v4.1.6
github.com/lrstanley/girc v0.0.0-20190210212025-51b8e096d398
github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 // indirect
github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6 // indirect
github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20190210153444-cc9d05784d5d
github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91
github.com/matterbridge/gomatrix v0.0.0-20190102230110-6f9631ca6dea
github.com/matterbridge/gozulipbot v0.0.0-20190212232658-7aa251978a18
github.com/matterbridge/logrus-prefixed-formatter v0.0.0-20180806162718-01618749af61
github.com/mattermost/mattermost-server v5.5.0+incompatible
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/missdeer/golib v1.0.4
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.5.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
github.com/pborman/uuid v0.0.0-20160216163710-c55201b03606 // indirect
github.com/peterhellberg/emojilib v0.0.0-20190124112554-c18758d55320
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.10.1
github.com/sirupsen/logrus v1.7.0
github.com/slack-go/slack v0.7.2
github.com/spf13/viper v1.7.1
github.com/stretchr/testify v1.6.1
github.com/vincent-petithory/dataurl v0.0.0-20191104211930-d1553a71de50
github.com/writeas/go-strip-markdown v2.0.1+incompatible
github.com/shazow/ssh-chat v0.0.0-20190125184227-81d7e1686296
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/stretchr/testify v1.3.0
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect
github.com/yaegashi/msgraph.go v0.1.4
github.com/zfjagann/golang-ring v0.0.0-20190304061218-d34796e0a6c2
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58
gomod.garykim.dev/nc-talk v0.1.5
gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376
layeh.com/gumble v0.0.0-20200818122324-146f9205029b
gitlab.com/golang-commonmark/html v0.0.0-20180917080848-cfaf75183c4a // indirect
gitlab.com/golang-commonmark/linkify v0.0.0-20180917065525-c22b7bdb1179 // indirect
gitlab.com/golang-commonmark/markdown v0.0.0-20181102083822-772775880e1f
gitlab.com/golang-commonmark/mdurl v0.0.0-20180912090424-e5bce34c34f2 // indirect
gitlab.com/golang-commonmark/puny v0.0.0-20180912090636-2cd490539afe // indirect
gitlab.com/opennota/wd v0.0.0-20180912061657-c5d65f63c638 // indirect
golang.org/x/image v0.0.0-20190616094056-33659d3de4f5
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
)
go 1.15
replace github.com/bwmarrin/discordgo v0.19.0 => github.com/MOZGIII/discordgo v0.19.1-0.20190812115637-1e74183814f9

1296
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -12,14 +12,8 @@ text := import("text")
// start - strip irc colors
// if we're not sending to an irc bridge we strip the IRC colors
if inProtocol == "irc" && outProtocol != "irc" {
if inProtocol == "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

@@ -4,7 +4,6 @@ import (
"flag"
"fmt"
"os"
"runtime"
"strings"
"github.com/42wim/matterbridge/bridge/config"
@@ -16,7 +15,7 @@ import (
)
var (
version = "1.20.0"
version = "1.15.2-dev"
githash string
flagConfig = flag.String("conf", "matterbridge.toml", "config file")
@@ -51,15 +50,6 @@ func main() {
cfg := config.NewConfig(rootLogger, *flagConfig)
cfg.BridgeValues().General.Debug = *flagDebug
// if logging to a file, ensure it is closed when the program terminates
// nolint:errcheck
defer func() {
if f, ok := rootLogger.Out.(*os.File); ok {
f.Sync()
f.Close()
}
}()
r, err := gateway.NewRouter(rootLogger, cfg, bridgemap.FullMap)
if err != nil {
logger.Fatalf("Starting gateway failed: %s", err)
@@ -77,31 +67,17 @@ func setupLogger() *logrus.Logger {
Formatter: &prefixed.TextFormatter{
PrefixPadding: 13,
DisableColors: true,
FullTimestamp: true,
},
Level: logrus.InfoLevel,
}
if *flagDebug || os.Getenv("DEBUG") == "1" {
logger.SetReportCaller(true)
logger.Formatter = &prefixed.TextFormatter{
PrefixPadding: 13,
DisableColors: true,
FullTimestamp: false,
CallerFormatter: func(function, file string) string {
return fmt.Sprintf(" [%s:%s]", function, file)
},
CallerPrettyfier: func(f *runtime.Frame) (string, string) {
sp := strings.SplitAfter(f.File, "/matterbridge/")
filename := f.File
if len(sp) > 1 {
filename = sp[1]
ForceFormatting: true,
}
s := strings.Split(f.Function, ".")
funcName := s[len(s)-1]
return funcName, fmt.Sprintf("%s:%d", filename, f.Line)
},
}
logger.Level = logrus.DebugLevel
logger.WithFields(logrus.Fields{"prefix": "main"}).Info("Enabling debug logging.")
}

View File

@@ -103,16 +103,6 @@ ColorNicks=false
#OPTIONAL (default empty)
RunCommands=["PRIVMSG user hello","PRIVMSG chanserv something"]
#PingDelay specifies how long to wait to send a ping to the irc server.
#You can use s for second, m for minute
#String
#OPTIONAL (default 1m)
PingDelay="1m"
#StripMarkdown strips markdown from messages
#OPTIONAL (default false)
StripMarkdown=false
#Nicks you want to ignore.
#Regular expressions supported
#Messages from those users will not be sent to other bridges.
@@ -187,12 +177,6 @@ StripNick=false
#OPTIONAL (default false)
ShowTopicChange=false
#Delay in milliseconds between channel joins
#Only useful when you have a LOT of channels to join
#See https://github.com/42wim/matterbridge/issues/1084
#OPTIONAL (default 0)
JoinDelay=0
###################################################################
#XMPP section
###################################################################
@@ -227,10 +211,6 @@ Nick="xmppbot"
#OPTIONAL (default false)
SkipTLSVerify=true
#Enable to use plaintext connection to your XMPP server.
#OPTIONAL (default false)
NoTLS=true
## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file
@@ -542,119 +522,6 @@ StripNick=false
#OPTIONAL (default false)
ShowTopicChange=false
###################################################################
#
# Keybase
# You should have a separate bridge account on Keybase
# (it also needs to be logged in on the system you're running the bridge on)
#
###################################################################
[keybase.myteam]
# RemoteNickFormat defines how remote users appear on this bridge
# See [general] config section for default options
RemoteNickFormat="{NICK} ({PROTOCOL}): "
# extra label that can be used in the RemoteNickFormat
# optional (default empty)
Label=""
# Your team on Keybase.
# The bot user MUST be a member of this team
# REQUIRED
Team="myteam"
###################################################################
# Microsoft teams section
# See https://github.com/42wim/matterbridge/wiki/MS-Teams-setup
###################################################################
[msteams.myteam]
# TenantID
# See https://github.com/42wim/matterbridge/wiki/MS-Teams-setup#get-necessary-ids-for-matterbridge
TenantID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# ClientID
# See https://github.com/42wim/matterbridge/wiki/MS-Teams-setup#get-necessary-ids-for-matterbridge
ClientID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# TeamID
# See https://github.com/42wim/matterbridge/wiki/MS-Teams-setup#get-necessary-ids-for-matterbridge
TeamID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file
#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 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"
#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"] ]
#nicks you want to replace.
#see replacemessages for syntaxa
#optional (default empty)
ReplaceNicks=[ ["user--","user"] ]
#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+" ] ]
#extra label that can be used in the RemoteNickFormat
#optional (default empty)
Label=""
#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
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
#OPTIONAL (default false)
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=false
#Enable to show topic changes from other bridges
#Only works hiding/show topic changes from slack bridge for now
#OPTIONAL (default false)
ShowTopicChange=false
#Opportunistically preserve threaded replies between bridges
#that support threading
#OPTIONAL (default false)
PreserveThreading=false
###################################################################
#slack section
###################################################################
@@ -817,121 +684,110 @@ ShowUserTyping=false
#In this example we use [discord.game]
#REQUIRED
[discord.game]
# Token (REQUIRED) is the token to connect with Discord API
#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="Yourtokenhere"
# Server (REQUIRED) is the ID or name of the guild to connect to, selected from the guilds the bot has been invited to
#REQUIRED
Server="yourservername"
## RELOADABLE SETTINGS
## All settings below can be reloaded by editing the file.
## They are also all optional.
## Settings below can be reloaded by editing the file
# ShowEmbeds shows the title, description and URL of embedded messages (sent by other bots)
#Shows title, description and URL of embedded messages (sent by other bots)
#OPTIONAL (default false)
ShowEmbeds=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
#Shows the username instead of the server nickname
#OPTIONAL (default false)
UseUserName=false
# UseDiscriminator appends the `#xxxx` discriminator when used with UseUserName
#Show #xxxx discriminator with UseUserName
#OPTIONAL (default false)
UseDiscriminator=false
# WebhookURL sends messages in the style of puppets.
#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
# Example: "https://discordapp.com/api/webhooks/1234/abcd_xyzw"
WebhookURL=""
#OPTIONAL (default empty)
WebhookURL="Yourwebhooktokenhere"
# EditDisable disables sending of edits to other bridges
#Disable sending of edits to other bridges
#OPTIONAL (default false)
EditDisable=false
# EditSuffix specifies the message to be appended to every edited message
# Example: " (edited)"
EditSuffix=""
#Message to be appended to every edited message
#OPTIONAL (default empty)
EditSuffix=" (edited)"
# 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=""
#Nicks you want to ignore.
#Regular expressions supported
#Messages from those users will not be sent to other bridges.
#OPTIONAL
IgnoreNicks="ircspammer1 ircspammer2"
# IgnoreMessages mutes outgoing messages of a certain format.
# Messages matching this regular expression will not be transmitted sent to other bridges
#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
#
# Example that ignores messages starting with ~~ or messages containing badword:
# IgnoreMessages="^~~ badword"
IgnoreMessages=""
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
IgnoreMessages="^~~ badword"
# 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=[]
#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"] ]
# ReplaceNicks replaces substrings of usernames in outgoing messages.
# See the ReplaceMessages setting for examples.
# Example: [ ["user--","user"] ]
ReplaceNicks=[]
#nicks you want to replace.
#see replacemessages for syntaxa
#optional (default empty)
ReplaceNicks=[ ["user--","user"] ]
# 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 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+" ] ]
#
# 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:
#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+" ]
#
ExtractNicks=[]
#OPTIONAL (default empty)
ExtractNicks=[ ["otherbot","<(.*?)>\\s+" ] ]
# Label is as an extra identifier for use in the RemoteNickFormat setting.
#extra label that can be used in the RemoteNickFormat
#optional (default empty)
Label=""
# RemoteNickFormat formats how remote users appear on this bridge.
# See the [general] config section for default options
#RemoteNickFormat defines how remote users appear on this bridge
#See [general] config section for default options
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
# ShowJoinPart emits messages that show joins/parts from other bridges
# Supported from the following bridges: irc, mattermost, slack, discord
#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
# StripNick strips non-alphanumeric characters from nicknames.
# Recommended reading: https://github.com/42wim/matterbridge/issues/285
#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=false
# ShowTopicChange emits messages that show topic/purpose updates from other bridges
# Supported from the following bridges: slack
#Enable to show topic/purpose changes from other bridges
#Only works hiding/show topic changes from slack bridge for now
#OPTIONAL (default false)
ShowTopicChange=false
# SyncTopic synchronises topic/purpose updates from other bridges
# Supported from the following bridges: slack
#Enable to sync topic/purpose changes from other bridges
#Only works syncing topic changes from slack bridge for now
#OPTIONAL (default false)
SyncTopic=false
###################################################################
@@ -952,17 +808,12 @@ Token="Yourtokenhere"
## Settings below can be reloaded by editing the file
#OPTIONAL (default empty)
#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
#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
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 all names are empty, username will be "unknown"
@@ -979,10 +830,6 @@ 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})"
@@ -1086,11 +933,6 @@ Server="https://yourrocketchatserver.domain.com:443"
#REQUIRED (when not using webhooks)
Login="yourlogin@domain.com"
Password="yourpass"
# When using access token set Login to the User ID associated with your token and Token to your token.
# When Token is set Password is ignored.
# Login="yOurUSerID"
# Token="YoUrUsER_toKEN"
#### Settings for webhook matterbridge.
#USE DEDICATED BOT USER WHEN POSSIBLE! This allows you to use advanced features like message editing/deleting and uploads
@@ -1223,17 +1065,9 @@ Password="yourpass"
#OPTIONAL (default false)
NoHomeServerSuffix=false
#Whether to disable sending of HTML content to matrix
#See https://github.com/42wim/matterbridge/issues/1022
#OPTIONAL (default false)
HTMLDisable=false
## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file
# UseUserName shows the username instead of the server nickname
UseUserName=false
#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
@@ -1396,76 +1230,7 @@ StripNick=false
#OPTIONAL (default false)
ShowTopicChange=false
###################################################################
#
# NCTalk (Nextcloud Talk)
#
###################################################################
[nctalk.bridge]
# Url of your Nextcloud server
Server = "https://cloud.youdomain.me"
# Enable to not verify the certificate on your Nextcloud server.
# e.g. when using selfsigned certificates
# OPTIONAL (default false)
SkipTLSVerify=true
# Username of the bot
Login = "talkuser"
# Password of the bot
Password = "talkuserpass"
# Suffix for Guest Users
GuestSuffix = " (Guest)"
###################################################################
#
# Mumble
#
###################################################################
[mumble.bridge]
# Host and port of your Mumble server
Server = "mumble.yourdomain.me:64738"
# Nickname to log in as
Nick = "matterbridge"
# Some servers require a password
# OPTIONAL (default empty)
Password = "serverpasswordhere"
# User comment to set on the Mumble user, visible to other users.
# OPTIONAL (default empty)
UserComment="I am bridging text messages between this channel and #general on irc.yourdomain.me"
# Self-signed TLS client certificate + private key used to connect to
# Mumble. This is required if you want to register the matterbridge
# user on your Mumble server, so its nick becomes reserved.
# You can generate a keypair using e.g.
#
# openssl req -x509 -newkey rsa:2048 -nodes -days 10000 \
# -keyout mumble.key -out mumble.crt
#
# To actually register the matterbridege user, connect to Mumble as an
# admin, right click on the user and click "Register".
#
# OPTIONAL (default empty)
TLSClientCertificate="mumble.crt"
TLSClientKey="mumble.key"
# TLS CA certificate used to validate the Mumble server.
# OPTIONAL (defaults to Go system CA)
TLSCACertificate=mumble-ca.crt
# Enable to not verify the certificate on your Mumble server.
# e.g. when using selfsigned certificates
# OPTIONAL (default false)
SkipTLSVerify=false
###################################################################
#
@@ -1607,8 +1372,6 @@ Buffer=1000
#Bearer token used for authentication
#curl -H "Authorization: Bearer token" http://localhost:4242/api/messages
# https://github.com/vi/websocat
# websocat -H="Authorization: Bearer token" ws://127.0.0.1:4242/api/websocket
#OPTIONAL (no authorization if token is empty)
Token="mytoken"
@@ -1688,14 +1451,6 @@ MediaDownloadBlacklist=[".html$",".htm$"]
#OPTIONAL (default false)
IgnoreFailureOnStart=false
#LogFile defines the location of a file to write logs into, rather
#than stdout.
#Logging will still happen on stdout if the file cannot be open for
#writing, or if the value is empty. Note that the log won't roll, so
#you might want to use logrotate(8) with this feature.
#OPTIONAL (default empty)
LogFile="/var/log/matterbridge.log"
###################################################################
#Tengo configuration
###################################################################
@@ -1733,9 +1488,7 @@ InMessage="example.tengo"
#outAccount, outProtocol, outChannel, outGateway, outEvent
#
#read-write:
#msgText, msgUsername, msgDrop
#
#msgDrop is a bool which is default false, when set true this message will be dropped
#msgText, msgUsername
#
#The script is reloaded on every message, so you can modify the script on the fly.
#
@@ -1788,48 +1541,32 @@ enable=true
# REQUIRED
account="irc.freenode"
# The channel key in each gateway is mapped to a similar group chat ID on the chat platform
# To find the group chat ID for different platforms, refer to the table below
# channel to connect on that account
# How to specify them for the different bridges:
#
# Platform | Identifier name | Example | Description
# -------------------------------------------------------------------------------------------------------------------------------------
# | channel | general | Do not include the # symbol
# discord | channel id | ID:123456789 | See https://github.com/42wim/matterbridge/issues/57
# | category/channel | Media/gaming | Without # symbol. If you're using discord categories to group your channels
# -------------------------------------------------------------------------------------------------------------------------------------
# gitter | username/room | general | As seen in the gitter.im URL
# -------------------------------------------------------------------------------------------------------------------------------------
# hipchat | id_channel | example needed | See https://www.hipchat.com/account/xmpp for the correct channel
# -------------------------------------------------------------------------------------------------------------------------------------
# irc | channel | #general | The # symbol is required and should be lowercase!
# -------------------------------------------------------------------------------------------------------------------------------------
# mattermost | channel | general | This is the channel name as seen in the URL, not the display name
# -------------------------------------------------------------------------------------------------------------------------------------
# matrix | #channel:server | #yourchannel:matrix.org | Encrypted rooms are not supported in matrix
# -------------------------------------------------------------------------------------------------------------------------------------
# msteams | threadId | 19:82abcxx@thread.skype | You'll find the threadId in the URL
# -------------------------------------------------------------------------------------------------------------------------------------
# mumble | channel id | 42 | The channel ID, as shown in the channel's "Edit" window
# -------------------------------------------------------------------------------------------------------------------------------------
# rocketchat | channel | #channel | # is required for private channels too
# -------------------------------------------------------------------------------------------------------------------------------------
# slack | channel name | general | Do not include the # symbol
# | channel id | ID:C123456 | The underlying ID of a channel. This doesn't work with
# -------------------------------------------------------------------------------------------------------------------------------------
# steam | chatid | example needed | The number in the URL when you click "enter chat room" in the browser
# -------------------------------------------------------------------------------------------------------------------------------------
# nctalk | token | xs25tz5y | The token in the URL when you are in a chat. It will be the last part of the URL.
# -------------------------------------------------------------------------------------------------------------------------------------
# telegram | chatid | -123456789 | A large negative number. see https://www.linkedin.com/pulse/telegram-bots-beginners-marco-frau
# -------------------------------------------------------------------------------------------------------------------------------------
# whatsapp | group JID | 48111222333-123455678999@g.us | A unique group JID. If you specify an empty string, bridge will list all the possibilities
# | "Group Name" | "Family Chat" | if you specify a group name, the bridge will find hint the JID to specify. Names can change over time and are not stable.
# -------------------------------------------------------------------------------------------------------------------------------------
# xmpp | channel | general | The room name
# -------------------------------------------------------------------------------------------------------------------------------------
# zulip | stream/topic:topic | general/off-topic:food | Do not use the # when specifying a topic
# -------------------------------------------------------------------------------------------------------------------------------------
# irc - #channel (# is required) (this needs to be lowercase!)
# mattermost - channel (the channel name as seen in the URL, not the displayname)
# gitter - username/room
# xmpp - channel
# slack - channel (without the #)
# - ID:C123456 (where C123456 is the channel ID) does not work with webhook
# discord - channel (without the #)
# - ID:123456789 (where 123456789 is the channel ID)
# (https://github.com/42wim/matterbridge/issues/57)
# - category/channel (without the #) if you're using discord categories to group your channels
# telegram - chatid (a large negative number, eg -123456789)
# see (https://www.linkedin.com/pulse/telegram-bots-beginners-marco-frau)
# hipchat - id_channel (see https://www.hipchat.com/account/xmpp for the correct channel)
# rocketchat - #channel (# is required (also needed for private channels!)
# matrix - #channel:server (eg #yourchannel:matrix.org)
# - encrypted rooms are not supported in matrix
# steam - chatid (a large number).
# The number in the URL when you click "enter chat room" in the browser
# whatsapp - 48111222333-123455678999@g.us A unique group JID;
# if you specify an empty string bridge will list all the possibilities
# - "Group Name" if you specify a group name the bridge will hint its JID to specify
# as group names might change in time and contain weird emoticons
# zulip - stream/topic:topicname (without the #)
#
# REQUIRED
channel="#testing"

View File

@@ -4,7 +4,7 @@ import (
"errors"
"strings"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/model"
)
// GetChannels returns all channels we're members off
@@ -36,16 +36,6 @@ func (m *MMClient) GetChannelHeader(channelId string) string { //nolint:golint
return ""
}
func getNormalisedName(channel *model.Channel) string {
if channel.Type == model.CHANNEL_GROUP {
// (deprecated in favor of ReplaceAll in go 1.12)
res := strings.Replace(channel.DisplayName, ", ", "-", -1) //nolint: gocritic
res = strings.Replace(res, " ", "_", -1) //nolint: gocritic
return res
}
return channel.Name
}
func (m *MMClient) GetChannelId(name string, teamId string) string { //nolint:golint
m.RLock()
defer m.RUnlock()
@@ -55,7 +45,13 @@ func (m *MMClient) GetChannelId(name string, teamId string) string { //nolint:go
for _, t := range m.OtherTeams {
for _, channel := range append(t.Channels, t.MoreChannels...) {
if getNormalisedName(channel) == name {
if channel.Type == model.CHANNEL_GROUP {
res := strings.Replace(channel.DisplayName, ", ", "-", -1)
res = strings.Replace(res, " ", "_", -1)
if res == name {
return channel.Id
}
} else if channel.Name == name {
return channel.Id
}
}
@@ -67,7 +63,7 @@ func (m *MMClient) getChannelIdTeam(name string, teamId string) string { //nolin
for _, t := range m.OtherTeams {
if t.Id == teamId {
for _, channel := range append(t.Channels, t.MoreChannels...) {
if getNormalisedName(channel) == name {
if channel.Name == name {
return channel.Id
}
}
@@ -85,7 +81,12 @@ func (m *MMClient) GetChannelName(channelId string) string { //nolint:golint
}
for _, channel := range append(t.Channels, t.MoreChannels...) {
if channel.Id == channelId {
return getNormalisedName(channel)
if channel.Type == model.CHANNEL_GROUP {
res := strings.Replace(channel.DisplayName, ", ", "-", -1)
res = strings.Replace(res, " ", "_", -1)
return res
}
return channel.Name
}
}
}
@@ -167,7 +168,7 @@ func (m *MMClient) JoinChannel(channelId string) error { //nolint:golint
}
func (m *MMClient) UpdateChannelsTeam(teamID string) error {
mmchannels, resp := m.Client.GetChannelsForTeamForUser(teamID, m.User.Id, false, "")
mmchannels, resp := m.Client.GetChannelsForTeamForUser(teamID, m.User.Id, "")
if resp.Error != nil {
return errors.New(resp.Error.DetailedError)
}

View File

@@ -13,7 +13,7 @@ import (
"github.com/gorilla/websocket"
"github.com/jpillora/backoff"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/model"
)
func (m *MMClient) doLogin(firstConnection bool, b *backoff.Backoff) error {
@@ -154,7 +154,7 @@ func (m *MMClient) initUser() error {
t := &Team{Team: team, Users: usermap, Id: team.Id}
mmchannels, resp := m.Client.GetChannelsForTeamForUser(team.Id, m.User.Id, false, "")
mmchannels, resp := m.Client.GetChannelsForTeamForUser(team.Id, m.User.Id, "")
if resp.Error != nil {
return resp.Error
}

View File

@@ -11,7 +11,7 @@ import (
lru "github.com/hashicorp/golang-lru"
"github.com/jpillora/backoff"
prefixed "github.com/matterbridge/logrus-prefixed-formatter"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/model"
"github.com/sirupsen/logrus"
)
@@ -69,7 +69,6 @@ type MMClient struct {
logger *logrus.Entry
rootLogger *logrus.Logger
lruCache *lru.Cache
allevents bool
}
// New will instantiate a new Matterclient with the specified login details without connecting.
@@ -120,10 +119,6 @@ func (m *MMClient) SetLogLevel(level string) {
}
}
func (m *MMClient) EnableAllEvents() {
m.allevents = true
}
// Login tries to connect the client with the loging details with which it was initialized.
func (m *MMClient) Login() error {
// check if this is a first connect or a reconnection
@@ -225,10 +220,6 @@ func (m *MMClient) WsReceiver() {
continue
}
}
if m.allevents {
m.MessageChan <- msg
continue
}
switch msg.Raw.Event {
case model.WEBSOCKET_EVENT_USER_ADDED,
model.WEBSOCKET_EVENT_USER_REMOVED,

View File

@@ -3,7 +3,7 @@ package matterclient
import (
"strings"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/model"
)
func (m *MMClient) parseActionPost(rmsg *Message) {

View File

@@ -4,7 +4,7 @@ import (
"errors"
"time"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/model"
)
func (m *MMClient) GetNickName(userId string) string { //nolint:golint

View File

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

View File

@@ -1,38 +0,0 @@
FROM alpine AS builder
COPY . /go/src/github.com/42wim/matterbridge
RUN apk add \
go \
git \
gcc \
musl-dev \
&& cd /go/src/github.com/42wim/matterbridge \
&& export GOPATH=/go \
&& go get \
&& go build -x -ldflags "-X main.githash=$(git log --pretty=format:'%h' -n 1)" -o /bin/matterbridge
FROM alpine
RUN apk --no-cache add \
ca-certificates \
cairo \
libjpeg-turbo \
mailcap \
py3-webencodings \
python3 \
&& apk --no-cache add --virtual .compile \
gcc \
libffi-dev \
libjpeg-turbo-dev \
musl-dev \
py3-pip \
py3-wheel \
python3-dev \
zlib-dev \
&& pip3 install --no-cache-dir lottie[PNG] \
&& apk --no-cache del .compile
COPY --from=builder /bin/matterbridge /bin/matterbridge
RUN mkdir /etc/matterbridge \
&& touch /etc/matterbridge/matterbridge.toml \
&& ln -sf /matterbridge.toml /etc/matterbridge/matterbridge.toml
ENTRYPOINT ["/bin/matterbridge", "-conf", "/etc/matterbridge/matterbridge.toml"]

View File

@@ -1,6 +1,3 @@
[submodule "generator/SteamKit"]
path = generator/SteamKit
url = https://github.com/SteamRE/SteamKit.git
[submodule "generator/Protobufs"]
path = generator/Protobufs
url = https://github.com/SteamDatabase/Protobufs.git
url = https://github.com/Philipp15b/SteamKit.git

View File

@@ -45,10 +45,19 @@ Whether you want to develop your own Steam bot or directly work on go-steam itse
## Updating go-steam to a new SteamKit version
Go source code is generated with code in the `generator` directory.
Look at `generator/README.md` for more information on how to use the generator.
To update go-steam to a new version of SteamKit, do the following:
Then, after generating new Go source files, update `go-steam` as necessary.
go get github.com/golang/protobuf/protoc-gen-go/
git submodule init && git submodule update
cd generator
go run generator.go clean proto steamlang
Make sure that `$GOPATH/bin` / `protoc-gen-go` is in your `$PATH`. You'll also need [`protoc`](https://developers.google.com/protocol-buffers/docs/downloads), the protocol buffer compiler. At the moment, we use Protocol Buffers 2.6.1 with `proco-gen-go`-[2402d76](https://github.com/golang/protobuf/tree/2402d76f3d41f928c7902a765dfc872356dd3aad).
To compile the Steam Language files, you also need the [.NET Framework](https://www.microsoft.com/net/downloads)
on Windows or [mono](http://www.go-mono.com/mono-downloads/download.html) on other operating systems.
Apply the protocol changes where necessary.
## License

View File

@@ -94,6 +94,9 @@ func (a *Auth) HandlePacket(packet *Packet) {
a.handleUpdateMachineAuth(packet)
case EMsg_ClientAccountInfo:
a.handleAccountInfo(packet)
case EMsg_ClientWalletInfoUpdate:
case EMsg_ClientRequestWebAPIAuthenticateUserNonceResponse:
case EMsg_ClientMarketingMessageUpdate:
}
}
@@ -119,7 +122,7 @@ func (a *Auth) handleLogOnResponse(packet *Packet) {
ExtendedResult: EResult(body.GetEresultExtended()),
OutOfGameSecsPerHeartbeat: body.GetOutOfGameHeartbeatSeconds(),
InGameSecsPerHeartbeat: body.GetInGameHeartbeatSeconds(),
PublicIp: body.GetDeprecatedPublicIp(),
PublicIp: body.GetPublicIp(),
ServerTime: body.GetRtime32ServerTime(),
AccountFlags: EAccountFlags(body.GetAccountFlags()),
ClientSteamId: SteamId(body.GetClientSuppliedSteamid()),
@@ -127,7 +130,7 @@ func (a *Auth) handleLogOnResponse(packet *Packet) {
CellId: body.GetCellId(),
CellIdPingThreshold: body.GetCellIdPingThreshold(),
Steam2Ticket: body.GetSteam2Ticket(),
UsePics: body.GetDeprecatedUsePics(),
UsePics: body.GetUsePics(),
WebApiUserNonce: body.GetWebapiAuthenticateUserNonce(),
IpCountryCode: body.GetIpCountryCode(),
VanityUrl: body.GetVanityUrl(),

View File

@@ -133,17 +133,11 @@ func (c *Client) Connected() bool {
// If you want to connect to a specific server, use `ConnectTo`.
func (c *Client) Connect() *netutil.PortAddr {
var server *netutil.PortAddr
// try to initialize the directory cache
if !steamDirectoryCache.IsInitialized() {
_ = steamDirectoryCache.Initialize()
}
if steamDirectoryCache.IsInitialized() {
server = steamDirectoryCache.GetRandomCM()
} else {
server = GetRandomCM()
}
c.ConnectTo(server)
return server
}

View File

@@ -1,8 +0,0 @@
module github.com/Philipp15b/go-steam
go 1.14
require (
github.com/davecgh/go-spew v1.1.1
github.com/golang/protobuf v1.4.2
)

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