Compare commits
232 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a5353d768 | ||
|
|
eff41759bc | ||
|
|
c23252ab53 | ||
|
|
1a3c57a031 | ||
|
|
4cc2c914e6 | ||
|
|
cbb46293ab | ||
|
|
765e00c949 | ||
|
|
662359908b | ||
|
|
0d99766686 | ||
|
|
ae3bc3358b | ||
|
|
1e0b4532bd | ||
|
|
4f8b19c686 | ||
|
|
84ab223b81 | ||
|
|
2bb21262d4 | ||
|
|
3188a9ffe6 | ||
|
|
61569a8610 | ||
|
|
075a84427f | ||
|
|
950f2759bd | ||
|
|
25c82ddf02 | ||
|
|
2d98df6122 | ||
|
|
219a5453f9 | ||
|
|
214a6a1386 | ||
|
|
e7781dc79c | ||
|
|
10c4bd1ac8 | ||
|
|
a42e488e58 | ||
|
|
06eb89b05b | ||
|
|
91c58ec027 | ||
|
|
8b26e42a3a | ||
|
|
acca011f15 | ||
|
|
2f59abdda7 | ||
|
|
17747a5c88 | ||
|
|
cec63546ff | ||
|
|
75f67d2de4 | ||
|
|
712385ffd5 | ||
|
|
ad90cf09fe | ||
|
|
f9928c9e25 | ||
|
|
a0741d99b8 | ||
|
|
c63f08c811 | ||
|
|
58b6c4d277 | ||
|
|
27c02549c8 | ||
|
|
88d371c71c | ||
|
|
b339524613 | ||
|
|
d5feda5c8a | ||
|
|
2f506425c2 | ||
|
|
e8167ee3d7 | ||
|
|
2f5e211065 | ||
|
|
39f4fb3446 | ||
|
|
56159b9bce | ||
|
|
b2af76e7dc | ||
|
|
491fe35397 | ||
|
|
b451285af7 | ||
|
|
63a1847cdc | ||
|
|
4e50fd8649 | ||
|
|
dfdffa0027 | ||
|
|
ebd2073144 | ||
|
|
1e94b716fb | ||
|
|
2a41abb3d1 | ||
|
|
2d2bebe976 | ||
|
|
e1629994bd | ||
|
|
e3d8fe4fd8 | ||
|
|
23d8742f0d | ||
|
|
3b6a8be07b | ||
|
|
71a5b72aff | ||
|
|
213bf349c3 | ||
|
|
a94fe55886 | ||
|
|
9b22f16497 | ||
|
|
2977a5957e | ||
|
|
f70d1c897a | ||
|
|
a4a3525265 | ||
|
|
a6dd8446e4 | ||
|
|
7bf9e1cfb3 | ||
|
|
f291832a77 | ||
|
|
1fee323247 | ||
|
|
a41accd033 | ||
|
|
37f7caf7f3 | ||
|
|
5847f7758c | ||
|
|
bce736993e | ||
|
|
5636992446 | ||
|
|
f996a2b7ae | ||
|
|
587de96ab3 | ||
|
|
80eb1cd202 | ||
|
|
bbf594c815 | ||
|
|
2f0f2ee40d | ||
|
|
96022d3aaf | ||
|
|
8eb5e3cbf8 | ||
|
|
ddc2625934 | ||
|
|
7f7ca697a0 | ||
|
|
900375679b | ||
|
|
9440b9e313 | ||
|
|
393f9e998b | ||
|
|
ba0bfe70a8 | ||
|
|
3c4a3e3f75 | ||
|
|
274fb09ed4 | ||
|
|
d44598a900 | ||
|
|
c9cfa59f54 | ||
|
|
7062234331 | ||
|
|
9754569525 | ||
|
|
52a071e34d | ||
|
|
2d8f749e36 | ||
|
|
a18cb74f03 | ||
|
|
6c442e239d | ||
|
|
eaf92fca4d | ||
|
|
06b7bad714 | ||
|
|
19eec2ed03 | ||
|
|
d99c54343a | ||
|
|
308a110000 | ||
|
|
4f406b2ce6 | ||
|
|
e564c555d7 | ||
|
|
f7ec9af9e8 | ||
|
|
4d93a774ce | ||
|
|
2595dd30bf | ||
|
|
9190365289 | ||
|
|
57794b3b9f | ||
|
|
3c36f651be | ||
|
|
8e6ddadba2 | ||
|
|
8a87a71927 | ||
|
|
0047e6f523 | ||
|
|
7183095a28 | ||
|
|
13c90893c7 | ||
|
|
976fbcd07f | ||
|
|
d97b077e85 | ||
|
|
8950575bfb | ||
|
|
11fc4c286f | ||
|
|
8d08e348a9 | ||
|
|
a18807f19e | ||
|
|
29f658fd3c | ||
|
|
a30bb8fed0 | ||
|
|
092ca1cd67 | ||
|
|
0df2539641 | ||
|
|
0f2d8a599c | ||
|
|
54b3143a1d | ||
|
|
148f7d2a91 | ||
|
|
1aa662f763 | ||
|
|
0b86b88de7 | ||
|
|
98033b1ba7 | ||
|
|
2b7eab629d | ||
|
|
0e4973e15c | ||
|
|
af0acf0dae | ||
|
|
76e5fe5a87 | ||
|
|
802c80f40c | ||
|
|
a51c5bd905 | ||
|
|
8c68556f52 | ||
|
|
cca1ea2404 | ||
|
|
281016a501 | ||
|
|
d4acdf2f89 | ||
|
|
0951e75c85 | ||
|
|
6b017b226a | ||
|
|
9e3bd7398c | ||
|
|
79f764c7a8 | ||
|
|
b5dc4353fb | ||
|
|
2fbac73c29 | ||
|
|
6616d105d1 | ||
|
|
6b4b19194e | ||
|
|
9785edd263 | ||
|
|
2a0bc11b68 | ||
|
|
dd0325a88d | ||
|
|
20783c0978 | ||
|
|
3f06a40bd5 | ||
|
|
68f43985ad | ||
|
|
915ca8f817 | ||
|
|
a65a81610b | ||
|
|
8eb6ed5639 | ||
|
|
795a8705c3 | ||
|
|
3af0dc3b3a | ||
|
|
9cf9b958a3 | ||
|
|
3ac2ba8d5a | ||
|
|
d893421c7b | ||
|
|
250b3bb579 | ||
|
|
e9edbfc051 | ||
|
|
e343db6f72 | ||
|
|
4d57d66f85 | ||
|
|
54ed6320c2 | ||
|
|
23083f3ae0 | ||
|
|
1985873494 | ||
|
|
8ae5917659 | ||
|
|
c91bfd08d8 | ||
|
|
49110a5872 | ||
|
|
c01c8edeb8 | ||
|
|
ff8cf067b8 | ||
|
|
1420f68050 | ||
|
|
c0be3e585a | ||
|
|
3049ef9151 | ||
|
|
4be00bbe6b | ||
|
|
9382dde098 | ||
|
|
1bf46b7711 | ||
|
|
b85bae31d9 | ||
|
|
0898829313 | ||
|
|
f8ad877601 | ||
|
|
585d1556c1 | ||
|
|
7486555875 | ||
|
|
fc30b1bacc | ||
|
|
0dd19af6e8 | ||
|
|
4c44515f9d | ||
|
|
9d84d6dd64 | ||
|
|
0f708daf2d | ||
|
|
b9354de8fd | ||
|
|
c9d5f4c898 | ||
|
|
810c150781 | ||
|
|
31dd538c0b | ||
|
|
62e38e7c45 | ||
|
|
b9da28a29b | ||
|
|
84bfa8a6b1 | ||
|
|
1f830963f6 | ||
|
|
12d2c6fe89 | ||
|
|
f43faf15f8 | ||
|
|
173a38a374 | ||
|
|
1604ff15b5 | ||
|
|
214fe502cd | ||
|
|
aae45a8179 | ||
|
|
075ca9ca47 | ||
|
|
d4253d7a55 | ||
|
|
0917dc8766 | ||
|
|
aba86855b5 | ||
|
|
ed5386c213 | ||
|
|
455e75e92f | ||
|
|
c394de0c88 | ||
|
|
bad1990173 | ||
|
|
0bc159341d | ||
|
|
45bf1fd63a | ||
|
|
ff0de85817 | ||
|
|
727fa9f929 | ||
|
|
0b9bc18236 | ||
|
|
bad3b83d33 | ||
|
|
00967a98ac | ||
|
|
1d708ab351 | ||
|
|
ba6759010b | ||
|
|
da3868c104 | ||
|
|
0abf4d5d5d | ||
|
|
9b320cd43f | ||
|
|
28783a4146 | ||
|
|
f92927eae5 | ||
|
|
294139ce7a |
2
.dockerignore
Normal file
2
.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Dockerfile
|
||||||
|
tgs.Dockerfile
|
||||||
1
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
1
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: Bug report
|
name: Bug report
|
||||||
about: Create a report to help us improve. (Check the FAQ on the wiki first)
|
about: Create a report to help us improve. (Check the FAQ on the wiki first)
|
||||||
|
labels: bug
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
1
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
1
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: Feature request
|
name: Feature request
|
||||||
about: Suggest an idea for this project
|
about: Suggest an idea for this project
|
||||||
|
labels: enhancement
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
71
.github/workflows/codeql-analysis.yml
vendored
Normal file
71
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# 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
|
||||||
57
.github/workflows/development.yml
vendored
Normal file
57
.github/workflows/development.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
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
3
.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
# Exclude matterbridge binary
|
# Exclude matterbridge binary
|
||||||
matterbridge
|
/matterbridge
|
||||||
|
/matterbridge.exe
|
||||||
|
|
||||||
# Exclude configuration file
|
# Exclude configuration file
|
||||||
matterbridge.toml
|
matterbridge.toml
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ run:
|
|||||||
# default value is empty list, but next dirs are always skipped independently
|
# default value is empty list, but next dirs are always skipped independently
|
||||||
# from this option's value:
|
# from this option's value:
|
||||||
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
|
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
|
||||||
skip-dirs:
|
skip-dirs: gateway/bridgemap$
|
||||||
|
|
||||||
# which files to skip: they will be analyzed, but issues from them
|
# which files to skip: they will be analyzed, but issues from them
|
||||||
# won't be reported. Default value is empty list, but there is
|
# won't be reported. Default value is empty list, but there is
|
||||||
@@ -174,7 +174,15 @@ linters:
|
|||||||
- lll
|
- lll
|
||||||
- maligned
|
- maligned
|
||||||
- prealloc
|
- prealloc
|
||||||
|
- wsl
|
||||||
|
- gomnd
|
||||||
|
- godox
|
||||||
|
- goerr113
|
||||||
|
- testpackage
|
||||||
|
- godot
|
||||||
|
- interfacer
|
||||||
|
- goheader
|
||||||
|
- noctx
|
||||||
|
|
||||||
# rules to deal with reported isues
|
# rules to deal with reported isues
|
||||||
issues:
|
issues:
|
||||||
|
|||||||
@@ -21,14 +21,18 @@ builds:
|
|||||||
ldflags:
|
ldflags:
|
||||||
- -s -w -X main.githash={{.ShortCommit}}
|
- -s -w -X main.githash={{.ShortCommit}}
|
||||||
|
|
||||||
archive:
|
archives:
|
||||||
name_template: "{{ .Binary }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
|
-
|
||||||
format: binary
|
id: matterbridge
|
||||||
files:
|
builds:
|
||||||
- none*
|
- matterbridge
|
||||||
replacements:
|
name_template: "{{ .Binary }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
|
||||||
386: 32bit
|
format: binary
|
||||||
amd64: 64bit
|
files:
|
||||||
|
- none*
|
||||||
|
replacements:
|
||||||
|
386: 32bit
|
||||||
|
amd64: 64bit
|
||||||
|
|
||||||
checksum:
|
checksum:
|
||||||
name_template: 'checksums.txt'
|
name_template: 'checksums.txt'
|
||||||
|
|||||||
56
.travis.yml
56
.travis.yml
@@ -1,56 +0,0 @@
|
|||||||
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="
|
|
||||||
17
Dockerfile
17
Dockerfile
@@ -1,11 +1,16 @@
|
|||||||
FROM alpine:edge
|
FROM alpine AS builder
|
||||||
ENTRYPOINT ["/bin/matterbridge"]
|
|
||||||
|
|
||||||
COPY . /go/src/github.com/42wim/matterbridge
|
COPY . /go/src/github.com/42wim/matterbridge
|
||||||
RUN apk update && apk add go git gcc musl-dev ca-certificates \
|
RUN apk update && apk add go git gcc musl-dev \
|
||||||
&& cd /go/src/github.com/42wim/matterbridge \
|
&& cd /go/src/github.com/42wim/matterbridge \
|
||||||
&& export GOPATH=/go \
|
&& export GOPATH=/go \
|
||||||
&& go get \
|
&& go get \
|
||||||
&& go build -x -ldflags "-X main.githash=$(git log --pretty=format:'%h' -n 1)" -o /bin/matterbridge \
|
&& 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
|
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"]
|
||||||
|
|||||||
202
README.md
202
README.md
@@ -9,26 +9,26 @@ Letting people be where they want to be.<br />
|
|||||||
|
|
||||||
<sup>
|
<sup>
|
||||||
|
|
||||||
|
[Discord][mb-discord] |
|
||||||
[Gitter][mb-gitter] |
|
[Gitter][mb-gitter] |
|
||||||
[IRC][mb-irc] |
|
[IRC][mb-irc] |
|
||||||
[Discord][mb-discord] |
|
[Keybase][mb-keybase] |
|
||||||
[Matrix][mb-matrix] |
|
[Matrix][mb-matrix] |
|
||||||
[Slack][mb-slack] |
|
|
||||||
[Mattermost][mb-mattermost] |
|
[Mattermost][mb-mattermost] |
|
||||||
|
[MSTeams][mb-msteams] |
|
||||||
[Rocket.Chat][mb-rocketchat] |
|
[Rocket.Chat][mb-rocketchat] |
|
||||||
[XMPP][mb-xmpp] |
|
[Slack][mb-slack] |
|
||||||
|
[Telegram][mb-telegram] |
|
||||||
[Twitch][mb-twitch] |
|
[Twitch][mb-twitch] |
|
||||||
[WhatsApp][mb-whatsapp] |
|
[WhatsApp][mb-whatsapp] |
|
||||||
|
[XMPP][mb-xmpp] |
|
||||||
[Zulip][mb-zulip] |
|
[Zulip][mb-zulip] |
|
||||||
[Telegram][mb-telegram] |
|
|
||||||
[Keybase][mb-keybase] |
|
|
||||||
And more...
|
And more...
|
||||||
</sup>
|
</sup>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
[](https://github.com/42wim/matterbridge/releases/latest)
|
[](https://github.com/42wim/matterbridge/releases/latest)
|
||||||
[](https://bintray.com/42wim/nightly/Matterbridge/_latestVersion)
|
|
||||||
[](https://codeclimate.com/github/42wim/matterbridge/maintainability)
|
[](https://codeclimate.com/github/42wim/matterbridge/maintainability)
|
||||||
[](https://codeclimate.com/github/42wim/matterbridge/test_coverage)<br />
|
[](https://codeclimate.com/github/42wim/matterbridge/test_coverage)<br />
|
||||||
|
|
||||||
@@ -44,28 +44,34 @@ And more...
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
### Table of Contents
|
# Table of Contents
|
||||||
|
|
||||||
- [Features](https://github.com/42wim/matterbridge/wiki/Features)
|
- [matterbridge](#matterbridge)
|
||||||
- [Natively supported](#natively-supported)
|
- [Table of Contents](#table-of-contents)
|
||||||
- [3rd party via matterbridge api](#3rd-party-via-matterbridge-api)
|
- [Features](#features)
|
||||||
- [API](#API)
|
- [Natively supported](#natively-supported)
|
||||||
- [Chat with us](#chat-with-us)
|
- [3rd party via matterbridge api](#3rd-party-via-matterbridge-api)
|
||||||
- [Screenshots](https://github.com/42wim/matterbridge/wiki/)
|
- [API](#api)
|
||||||
- [Installing/upgrading](#installing--upgrading)
|
- [Chat with us](#chat-with-us)
|
||||||
- [Binaries](#binaries)
|
- [Screenshots](#screenshots)
|
||||||
- [Building](#building)
|
- [Installing / upgrading](#installing--upgrading)
|
||||||
- [Configuration](#configuration)
|
- [Binaries](#binaries)
|
||||||
- [Howto](https://github.com/42wim/matterbridge/wiki/How-to-create-your-config)
|
- [Packages](#packages)
|
||||||
- [Settings](#settings)
|
- [Building](#building)
|
||||||
- [Examples](#examples)
|
- [Configuration](#configuration)
|
||||||
- [Running](#running)
|
- [Basic configuration](#basic-configuration)
|
||||||
- [Docker](#docker)
|
- [Settings](#settings)
|
||||||
- [Changelog](#changelog)
|
- [Advanced configuration](#advanced-configuration)
|
||||||
- [FAQ](#faq)
|
- [Examples](#examples)
|
||||||
- [Related projects](#related-projects)
|
- [Bridge mattermost (off-topic) - irc (#testing)](#bridge-mattermost-off-topic---irc-testing)
|
||||||
- [Articles](#articles)
|
- [Bridge slack (#general) - discord (general)](#bridge-slack-general---discord-general)
|
||||||
- [Thanks](#thanks)
|
- [Running](#running)
|
||||||
|
- [Docker](#docker)
|
||||||
|
- [Changelog](#changelog)
|
||||||
|
- [FAQ](#faq)
|
||||||
|
- [Related projects](#related-projects)
|
||||||
|
- [Articles](#articles)
|
||||||
|
- [Thanks](#thanks)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@@ -80,28 +86,33 @@ And more...
|
|||||||
|
|
||||||
### Natively supported
|
### Natively supported
|
||||||
|
|
||||||
- [Mattermost](https://github.com/mattermost/mattermost-server/) 4.x, 5.x
|
|
||||||
- [IRC](http://www.mirc.com/servers.html)
|
|
||||||
- [XMPP](https://xmpp.org)
|
|
||||||
- [Gitter](https://gitter.im)
|
|
||||||
- [Slack](https://slack.com)
|
|
||||||
- [Discord](https://discordapp.com)
|
- [Discord](https://discordapp.com)
|
||||||
- [Telegram](https://telegram.org)
|
- [Gitter](https://gitter.im)
|
||||||
- [Rocket.chat](https://rocket.chat)
|
- [IRC](http://www.mirc.com/servers.html)
|
||||||
- [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)
|
|
||||||
- [Keybase](https://keybase.io)
|
- [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)
|
||||||
|
|
||||||
### 3rd party via matterbridge api
|
### 3rd party via matterbridge api
|
||||||
|
|
||||||
|
- [Discourse](https://github.com/DeclanHoare/matterbabble)
|
||||||
|
- [Facebook messenger](https://github.com/VictorNine/fbridge)
|
||||||
- [Minecraft](https://github.com/elytra/MatterLink)
|
- [Minecraft](https://github.com/elytra/MatterLink)
|
||||||
- [Reddit](https://github.com/bonehurtingjuice/mattereddit)
|
- [Reddit](https://github.com/bonehurtingjuice/mattereddit)
|
||||||
- [Facebook messenger](https://github.com/VictorNine/fbridge)
|
- [Counter-Strike, half-life and more](https://forums.alliedmods.net/showthread.php?t=319430)
|
||||||
- [Discourse](https://github.com/DeclanHoare/matterbabble)
|
- [MatterAMXX](https://github.com/GabeIggy/MatterAMXX)
|
||||||
|
|
||||||
### API
|
### API
|
||||||
|
|
||||||
@@ -115,58 +126,58 @@ Used by the projects below. Feel free to make a PR to add your project to this l
|
|||||||
- [Mattereddit](https://github.com/bonehurtingjuice/mattereddit) (Reddit chat support)
|
- [Mattereddit](https://github.com/bonehurtingjuice/mattereddit) (Reddit chat support)
|
||||||
- [fbridge](https://github.com/VictorNine/fbridge) (Facebook messenger support)
|
- [fbridge](https://github.com/VictorNine/fbridge) (Facebook messenger support)
|
||||||
- [matterbabble](https://github.com/DeclanHoare/matterbabble) (Discourse 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)
|
||||||
|
|
||||||
## Chat with us
|
## Chat with us
|
||||||
|
|
||||||
Questions or want to test on your favorite platform? Join below:
|
Questions or want to test on your favorite platform? Join below:
|
||||||
|
|
||||||
|
- [Discord][mb-discord]
|
||||||
- [Gitter][mb-gitter]
|
- [Gitter][mb-gitter]
|
||||||
- [IRC][mb-irc]
|
- [IRC][mb-irc]
|
||||||
- [Discord][mb-discord]
|
- [Keybase][mb-keybase]
|
||||||
- [Matrix][mb-matrix]
|
- [Matrix][mb-matrix]
|
||||||
- [Slack][mb-slack]
|
|
||||||
- [Mattermost][mb-mattermost]
|
- [Mattermost][mb-mattermost]
|
||||||
- [Rocket.Chat][mb-rocketchat]
|
- [Rocket.Chat][mb-rocketchat]
|
||||||
- [XMPP][mb-xmpp] (matterbridge@conference.jabber.de)
|
- [Slack][mb-slack]
|
||||||
- [Twitch][mb-twitch]
|
|
||||||
- [Zulip][mb-zulip]
|
|
||||||
- [Telegram][mb-telegram]
|
- [Telegram][mb-telegram]
|
||||||
|
- [Twitch][mb-twitch]
|
||||||
|
- [XMPP][mb-xmpp] (matterbridge@conference.jabber.de)
|
||||||
|
- [Zulip][mb-zulip]
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
See https://github.com/42wim/matterbridge/wiki
|
See <https://github.com/42wim/matterbridge/wiki>
|
||||||
|
|
||||||
## Installing / upgrading
|
## Installing / upgrading
|
||||||
|
|
||||||
### Binaries
|
### Binaries
|
||||||
|
|
||||||
- Latest stable release [v1.16.0](https://github.com/42wim/matterbridge/releases/latest)
|
- Latest stable release [v1.20.0](https://github.com/42wim/matterbridge/releases/latest)
|
||||||
- Development releases (follows master) can be downloaded [here](https://dl.bintray.com/42wim/nightly/)
|
- Development releases (follows master) can be downloaded [here](https://github.com/42wim/matterbridge/actions) selecting the latest green build and then artifacts.
|
||||||
|
|
||||||
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.
|
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
|
### Packages
|
||||||
|
|
||||||
- [Overview](https://repology.org/metapackage/matterbridge/versions)
|
- [Overview](https://repology.org/metapackage/matterbridge/versions)
|
||||||
|
- [snap](https://snapcraft.io/matterbridge)
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
Most people just want to use binaries, you can find those [here](https://github.com/42wim/matterbridge/releases/latest)
|
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:
|
If you really want to build from source, follow these instructions:
|
||||||
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).
|
Go 1.12+ is required. Make sure you have [Go](https://golang.org/doc/install) properly installed.
|
||||||
|
|
||||||
After Go is setup, download matterbridge to your \$GOPATH directory.
|
```bash
|
||||||
|
|
||||||
```
|
|
||||||
cd $GOPATH
|
|
||||||
go get github.com/42wim/matterbridge
|
go get github.com/42wim/matterbridge
|
||||||
```
|
```
|
||||||
|
|
||||||
You should now have matterbridge binary in the bin directory:
|
You should now have matterbridge binary in the ~/go/bin directory:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
$ ls bin/
|
$ ls ~/go/bin/
|
||||||
matterbridge
|
matterbridge
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -248,7 +259,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.
|
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:
|
Usage of ./matterbridge:
|
||||||
-conf string
|
-conf string
|
||||||
config file (default "matterbridge.toml")
|
config file (default "matterbridge.toml")
|
||||||
@@ -262,11 +273,7 @@ Usage of ./matterbridge:
|
|||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
|
|
||||||
Create your matterbridge.toml file locally eg in `/tmp/matterbridge.toml`
|
Please take a look at the [Docker Wiki page](https://github.com/42wim/matterbridge/wiki/Deploy:-Docker) for more information.
|
||||||
|
|
||||||
```
|
|
||||||
docker run -ti -v /tmp/matterbridge.toml:/matterbridge.toml 42wim/matterbridge
|
|
||||||
```
|
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
@@ -278,7 +285,7 @@ See [FAQ](https://github.com/42wim/matterbridge/wiki/FAQ)
|
|||||||
|
|
||||||
## Related projects
|
## Related projects
|
||||||
|
|
||||||
- [FOSSRIT/infrastructure - roles/matterbridge](https://github.com/FOSSRIT/infrastructure/tree/master/roles/matterbridge) (Ansible role used to automate deployments of Matterbridge)
|
- [jwflory/ansible-role-matterbridge](https://galaxy.ansible.com/jwflory/matterbridge) (Ansible role to simplify deploying Matterbridge)
|
||||||
- [matterbridge autoconfig](https://github.com/patcon/matterbridge-autoconfig)
|
- [matterbridge autoconfig](https://github.com/patcon/matterbridge-autoconfig)
|
||||||
- [matterbridge config viewer](https://github.com/patcon/matterbridge-heroku-viewer)
|
- [matterbridge config viewer](https://github.com/patcon/matterbridge-heroku-viewer)
|
||||||
- [matterbridge-heroku](https://github.com/cadecairos/matterbridge-heroku)
|
- [matterbridge-heroku](https://github.com/cadecairos/matterbridge-heroku)
|
||||||
@@ -293,14 +300,14 @@ See [FAQ](https://github.com/42wim/matterbridge/wiki/FAQ)
|
|||||||
## Articles
|
## Articles
|
||||||
|
|
||||||
- [matterbridge on kubernetes](https://medium.freecodecamp.org/using-kubernetes-to-deploy-a-chat-gateway-or-when-technology-works-like-its-supposed-to-a169a8cd69a3)
|
- [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://mattermost.com/blog/connect-irc-to-mattermost/>
|
||||||
- https://blog.valvin.fr/2016/09/17/mattermost-et-un-channel-irc-cest-possible/
|
- <https://blog.valvin.fr/2016/09/17/mattermost-et-un-channel-irc-cest-possible/>
|
||||||
- https://blog.brightscout.com/top-10-mattermost-integrations/
|
- <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://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://kopano.com/blog/matterbridge-bridging-mattermost-chat/
|
- <https://www.stitcher.com/s/?eid=52382713>
|
||||||
- https://www.stitcher.com/s/?eid=52382713
|
- <https://daniele.tech/2019/02/how-to-use-matterbridge-to-connect-2-different-slack-workspaces/>
|
||||||
- https://daniele.tech/2019/02/how-to-use-matterbridge-to-connect-2-different-slack-workspaces/
|
- <https://userlinux.net/mattermost-and-matterbridge.html>
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|
|
||||||
@@ -313,36 +320,41 @@ See [FAQ](https://github.com/42wim/matterbridge/wiki/FAQ)
|
|||||||
|
|
||||||
Matterbridge wouldn't exist without these libraries:
|
Matterbridge wouldn't exist without these libraries:
|
||||||
|
|
||||||
- discord - https://github.com/bwmarrin/discordgo
|
- discord - <https://github.com/bwmarrin/discordgo>
|
||||||
- echo - https://github.com/labstack/echo
|
- echo - <https://github.com/labstack/echo>
|
||||||
- gitter - https://github.com/sromku/go-gitter
|
- gitter - <https://github.com/sromku/go-gitter>
|
||||||
- gops - https://github.com/google/gops
|
- gops - <https://github.com/google/gops>
|
||||||
- gozulipbot - https://github.com/ifo/gozulipbot
|
- gozulipbot - <https://github.com/ifo/gozulipbot>
|
||||||
- irc - https://github.com/lrstanley/girc
|
- gumble - <https://github.com/layeh/gumble>
|
||||||
- mattermost - https://github.com/mattermost/mattermost-server
|
- irc - <https://github.com/lrstanley/girc>
|
||||||
- matrix - https://github.com/matrix-org/gomatrix
|
- keybase - <https://github.com/keybase/go-keybase-chat-bot>
|
||||||
- sshchat - https://github.com/shazow/ssh-chat
|
- matrix - <https://github.com/matrix-org/gomatrix>
|
||||||
- slack - https://github.com/nlopes/slack
|
- mattermost - <https://github.com/mattermost/mattermost-server>
|
||||||
- steam - https://github.com/Philipp15b/go-steam
|
- msgraph.go - <https://github.com/yaegashi/msgraph.go>
|
||||||
- telegram - https://github.com/go-telegram-bot-api/telegram-bot-api
|
- mumble - <https://github.com/layeh/gumble>
|
||||||
- xmpp - https://github.com/mattn/go-xmpp
|
- nctalk - <https://github.com/gary-kim/go-nc-talk>
|
||||||
- whatsapp - https://github.com/Rhymen/go-whatsapp/
|
- slack - <https://github.com/nlopes/slack>
|
||||||
- zulip - https://github.com/ifo/gozulipbot
|
- sshchat - <https://github.com/shazow/ssh-chat>
|
||||||
- tengo - https://github.com/d5/tengo
|
- steam - <https://github.com/Philipp15b/go-steam>
|
||||||
- keybase - https://github.com/keybase/go-keybase-chat-bot
|
- 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>
|
||||||
|
|
||||||
<!-- Links -->
|
<!-- Links -->
|
||||||
|
|
||||||
|
[mb-discord]: https://discord.gg/AkKPtrQ
|
||||||
[mb-gitter]: https://gitter.im/42wim/matterbridge
|
[mb-gitter]: https://gitter.im/42wim/matterbridge
|
||||||
[mb-irc]: https://webchat.freenode.net/?channels=matterbridgechat
|
[mb-irc]: https://webchat.freenode.net/?channels=matterbridgechat
|
||||||
[mb-discord]: https://discord.gg/AkKPtrQ
|
[mb-keybase]: https://keybase.io/team/matterbridge
|
||||||
[mb-matrix]: https://riot.im/app/#/room/#matterbridge:matrix.org
|
[mb-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-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-rocketchat]: https://open.rocket.chat/channel/matterbridge
|
||||||
[mb-xmpp]: https://inverse.chat/
|
[mb-slack]: https://join.slack.com/t/matterbridgechat/shared_invite/zt-2ourq2h2-7YvyYBq2WFGC~~zEzA68_Q
|
||||||
|
[mb-telegram]: https://t.me/Matterbridge
|
||||||
[mb-twitch]: https://www.twitch.tv/matterbridge
|
[mb-twitch]: https://www.twitch.tv/matterbridge
|
||||||
[mb-whatsapp]: https://www.whatsapp.com/
|
[mb-whatsapp]: https://www.whatsapp.com/
|
||||||
[mb-keybase]: https://keybase.io
|
[mb-xmpp]: https://inverse.chat/
|
||||||
[mb-zulip]: https://matterbridge.zulipchat.com/register/
|
[mb-zulip]: https://matterbridge.zulipchat.com/register/
|
||||||
[mb-telegram]: https://t.me/Matterbridge
|
|
||||||
|
|||||||
@@ -6,17 +6,20 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/olahol/melody.v1"
|
||||||
|
|
||||||
"github.com/42wim/matterbridge/bridge"
|
"github.com/42wim/matterbridge/bridge"
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/labstack/echo/v4/middleware"
|
"github.com/labstack/echo/v4/middleware"
|
||||||
"github.com/zfjagann/golang-ring"
|
ring "github.com/zfjagann/golang-ring"
|
||||||
)
|
)
|
||||||
|
|
||||||
type API struct {
|
type API struct {
|
||||||
Messages ring.Ring
|
Messages ring.Ring
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
*bridge.Config
|
*bridge.Config
|
||||||
|
mrouter *melody.Melody
|
||||||
}
|
}
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
@@ -32,6 +35,32 @@ func New(cfg *bridge.Config) bridge.Bridger {
|
|||||||
e := echo.New()
|
e := echo.New()
|
||||||
e.HideBanner = true
|
e.HideBanner = true
|
||||||
e.HidePort = 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{}
|
b.Messages = ring.Ring{}
|
||||||
if b.GetInt("Buffer") != 0 {
|
if b.GetInt("Buffer") != 0 {
|
||||||
b.Messages.SetCapacity(b.GetInt("Buffer"))
|
b.Messages.SetCapacity(b.GetInt("Buffer"))
|
||||||
@@ -41,9 +70,17 @@ func New(cfg *bridge.Config) bridge.Bridger {
|
|||||||
return key == b.GetString("Token"), nil
|
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/health", b.handleHealthcheck)
|
||||||
e.GET("/api/messages", b.handleMessages)
|
e.GET("/api/messages", b.handleMessages)
|
||||||
e.GET("/api/stream", b.handleStream)
|
e.GET("/api/stream", b.handleStream)
|
||||||
|
e.GET("/api/websocket", b.handleWebsocket)
|
||||||
e.POST("/api/message", b.handlePostMessage)
|
e.POST("/api/message", b.handlePostMessage)
|
||||||
go func() {
|
go func() {
|
||||||
if b.GetString("BindAddress") == "" {
|
if b.GetString("BindAddress") == "" {
|
||||||
@@ -58,13 +95,13 @@ func New(cfg *bridge.Config) bridge.Bridger {
|
|||||||
func (b *API) Connect() error {
|
func (b *API) Connect() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *API) Disconnect() error {
|
func (b *API) Disconnect() error {
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *API) JoinChannel(channel config.ChannelInfo) error {
|
func (b *API) JoinChannel(channel config.ChannelInfo) error {
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *API) Send(msg config.Message) (string, error) {
|
func (b *API) Send(msg config.Message) (string, error) {
|
||||||
@@ -74,7 +111,14 @@ func (b *API) Send(msg config.Message) (string, error) {
|
|||||||
if msg.Event == config.EventMsgDelete {
|
if msg.Event == config.EventMsgDelete {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
b.Messages.Enqueue(&msg)
|
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)
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,18 +150,23 @@ func (b *API) handleMessages(c echo.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *API) handleStream(c echo.Context) error {
|
func (b *API) getGreeting() config.Message {
|
||||||
c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
|
return config.Message{
|
||||||
c.Response().WriteHeader(http.StatusOK)
|
|
||||||
greet := config.Message{
|
|
||||||
Event: config.EventAPIConnected,
|
Event: config.EventAPIConnected,
|
||||||
Timestamp: time.Now(),
|
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()
|
||||||
if err := json.NewEncoder(c.Response()).Encode(greet); err != nil {
|
if err := json.NewEncoder(c.Response()).Encode(greet); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.Response().Flush()
|
c.Response().Flush()
|
||||||
for {
|
for {
|
||||||
|
// TODO: this causes issues, messages should be broadcasted to all connected clients
|
||||||
msg := b.Messages.Dequeue()
|
msg := b.Messages.Dequeue()
|
||||||
if msg != nil {
|
if msg != nil {
|
||||||
if err := json.NewEncoder(c.Response()).Encode(msg); err != nil {
|
if err := json.NewEncoder(c.Response()).Encode(msg); err != nil {
|
||||||
@@ -128,3 +177,31 @@ func (b *API) handleStream(c echo.Context) error {
|
|||||||
time.Sleep(200 * time.Millisecond)
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package bridge
|
package bridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
@@ -41,6 +43,10 @@ type Factory func(*Config) Bridger
|
|||||||
|
|
||||||
func New(bridge *config.Bridge) *Bridge {
|
func New(bridge *config.Bridge) *Bridge {
|
||||||
accInfo := strings.Split(bridge.Account, ".")
|
accInfo := strings.Split(bridge.Account, ".")
|
||||||
|
if len(accInfo) != 2 {
|
||||||
|
log.Fatalf("config failure, account incorrect: %s", bridge.Account)
|
||||||
|
}
|
||||||
|
|
||||||
protocol := accInfo[0]
|
protocol := accInfo[0]
|
||||||
name := accInfo[1]
|
name := accInfo[1]
|
||||||
|
|
||||||
@@ -69,6 +75,7 @@ func (b *Bridge) joinChannels(channels map[string]config.ChannelInfo, exists map
|
|||||||
for ID, channel := range channels {
|
for ID, channel := range channels {
|
||||||
if !exists[ID] {
|
if !exists[ID] {
|
||||||
b.Log.Infof("%s: joining %s (ID: %s)", b.Account, channel.Name, 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)
|
err := b.JoinChannel(channel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -79,8 +86,16 @@ func (b *Bridge) joinChannels(channels map[string]config.ChannelInfo, exists map
|
|||||||
return nil
|
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 {
|
func (b *Bridge) GetBool(key string) bool {
|
||||||
val, ok := b.Config.GetBool(b.Account + "." + key)
|
val, ok := b.Config.GetBool(b.GetConfigKey(key))
|
||||||
if !ok {
|
if !ok {
|
||||||
val, _ = b.Config.GetBool("general." + key)
|
val, _ = b.Config.GetBool("general." + key)
|
||||||
}
|
}
|
||||||
@@ -88,7 +103,7 @@ func (b *Bridge) GetBool(key string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bridge) GetInt(key string) int {
|
func (b *Bridge) GetInt(key string) int {
|
||||||
val, ok := b.Config.GetInt(b.Account + "." + key)
|
val, ok := b.Config.GetInt(b.GetConfigKey(key))
|
||||||
if !ok {
|
if !ok {
|
||||||
val, _ = b.Config.GetInt("general." + key)
|
val, _ = b.Config.GetInt("general." + key)
|
||||||
}
|
}
|
||||||
@@ -96,7 +111,7 @@ func (b *Bridge) GetInt(key string) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bridge) GetString(key string) string {
|
func (b *Bridge) GetString(key string) string {
|
||||||
val, ok := b.Config.GetString(b.Account + "." + key)
|
val, ok := b.Config.GetString(b.GetConfigKey(key))
|
||||||
if !ok {
|
if !ok {
|
||||||
val, _ = b.Config.GetString("general." + key)
|
val, _ = b.Config.GetString("general." + key)
|
||||||
}
|
}
|
||||||
@@ -104,7 +119,7 @@ func (b *Bridge) GetString(key string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bridge) GetStringSlice(key string) []string {
|
func (b *Bridge) GetStringSlice(key string) []string {
|
||||||
val, ok := b.Config.GetStringSlice(b.Account + "." + key)
|
val, ok := b.Config.GetStringSlice(b.GetConfigKey(key))
|
||||||
if !ok {
|
if !ok {
|
||||||
val, _ = b.Config.GetStringSlice("general." + key)
|
val, _ = b.Config.GetStringSlice("general." + key)
|
||||||
}
|
}
|
||||||
@@ -112,7 +127,7 @@ func (b *Bridge) GetStringSlice(key string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bridge) GetStringSlice2D(key string) [][]string {
|
func (b *Bridge) GetStringSlice2D(key string) [][]string {
|
||||||
val, ok := b.Config.GetStringSlice2D(b.Account + "." + key)
|
val, ok := b.Config.GetStringSlice2D(b.GetConfigKey(key))
|
||||||
if !ok {
|
if !ok {
|
||||||
val, _ = b.Config.GetStringSlice2D("general." + key)
|
val, _ = b.Config.GetStringSlice2D("general." + key)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package config
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -76,23 +78,29 @@ type Protocol struct {
|
|||||||
BindAddress string // mattermost, slack // DEPRECATED
|
BindAddress string // mattermost, slack // DEPRECATED
|
||||||
Buffer int // api
|
Buffer int // api
|
||||||
Charset string // irc
|
Charset string // irc
|
||||||
|
ClientID string // msteams
|
||||||
ColorNicks bool // only irc for now
|
ColorNicks bool // only irc for now
|
||||||
Debug bool // general
|
Debug bool // general
|
||||||
DebugLevel int // only for irc now
|
DebugLevel int // only for irc now
|
||||||
|
DisableWebPagePreview bool // telegram
|
||||||
EditSuffix string // mattermost, slack, discord, telegram, gitter
|
EditSuffix string // mattermost, slack, discord, telegram, gitter
|
||||||
EditDisable bool // mattermost, slack, discord, telegram, gitter
|
EditDisable bool // mattermost, slack, discord, telegram, gitter
|
||||||
|
HTMLDisable bool // matrix
|
||||||
IconURL string // mattermost, slack
|
IconURL string // mattermost, slack
|
||||||
IgnoreFailureOnStart bool // general
|
IgnoreFailureOnStart bool // general
|
||||||
IgnoreNicks string // all protocols
|
IgnoreNicks string // all protocols
|
||||||
IgnoreMessages string // all protocols
|
IgnoreMessages string // all protocols
|
||||||
Jid string // xmpp
|
Jid string // xmpp
|
||||||
|
JoinDelay string // all protocols
|
||||||
Label string // all protocols
|
Label string // all protocols
|
||||||
Login string // mattermost, matrix
|
Login string // mattermost, matrix
|
||||||
|
LogFile string // general
|
||||||
MediaDownloadBlackList []string
|
MediaDownloadBlackList []string
|
||||||
MediaDownloadPath string // Basically MediaServerUpload, but instead of uploading it, just write it to a file on the same server.
|
MediaDownloadPath string // Basically MediaServerUpload, but instead of uploading it, just write it to a file on the same server.
|
||||||
MediaDownloadSize int // all protocols
|
MediaDownloadSize int // all protocols
|
||||||
MediaServerDownload string
|
MediaServerDownload string
|
||||||
MediaServerUpload string
|
MediaServerUpload string
|
||||||
|
MediaConvertTgs string // telegram
|
||||||
MediaConvertWebPToPNG bool // telegram
|
MediaConvertWebPToPNG bool // telegram
|
||||||
MessageDelay int // IRC, time in millisecond to wait between messages
|
MessageDelay int // IRC, time in millisecond to wait between messages
|
||||||
MessageFormat string // telegram
|
MessageFormat string // telegram
|
||||||
@@ -109,19 +117,21 @@ type Protocol struct {
|
|||||||
NicksPerRow int // mattermost, slack
|
NicksPerRow int // mattermost, slack
|
||||||
NoHomeServerSuffix bool // matrix
|
NoHomeServerSuffix bool // matrix
|
||||||
NoSendJoinPart bool // all protocols
|
NoSendJoinPart bool // all protocols
|
||||||
NoTLS bool // mattermost
|
NoTLS bool // mattermost, xmpp
|
||||||
Password string // IRC,mattermost,XMPP,matrix
|
Password string // IRC,mattermost,XMPP,matrix
|
||||||
PrefixMessagesWithNick bool // mattemost, slack
|
PrefixMessagesWithNick bool // mattemost, slack
|
||||||
PreserveThreading bool // slack
|
PreserveThreading bool // slack
|
||||||
Protocol string // all protocols
|
Protocol string // all protocols
|
||||||
QuoteDisable bool // telegram
|
QuoteDisable bool // telegram
|
||||||
QuoteFormat string // telegram
|
QuoteFormat string // telegram
|
||||||
|
QuoteLengthLimit int // telegram
|
||||||
RejoinDelay int // IRC
|
RejoinDelay int // IRC
|
||||||
ReplaceMessages [][]string // all protocols
|
ReplaceMessages [][]string // all protocols
|
||||||
ReplaceNicks [][]string // all protocols
|
ReplaceNicks [][]string // all protocols
|
||||||
RemoteNickFormat string // all protocols
|
RemoteNickFormat string // all protocols
|
||||||
RunCommands []string // IRC
|
RunCommands []string // IRC
|
||||||
Server string // IRC,mattermost,XMPP,discord
|
Server string // IRC,mattermost,XMPP,discord
|
||||||
|
SessionFile string // msteams,whatsapp
|
||||||
ShowJoinPart bool // all protocols
|
ShowJoinPart bool // all protocols
|
||||||
ShowTopicChange bool // slack
|
ShowTopicChange bool // slack
|
||||||
ShowUserTyping bool // slack
|
ShowUserTyping bool // slack
|
||||||
@@ -129,18 +139,22 @@ type Protocol struct {
|
|||||||
SkipTLSVerify bool // IRC, mattermost
|
SkipTLSVerify bool // IRC, mattermost
|
||||||
SkipVersionCheck bool // mattermost
|
SkipVersionCheck bool // mattermost
|
||||||
StripNick bool // all protocols
|
StripNick bool // all protocols
|
||||||
|
StripMarkdown bool // irc
|
||||||
SyncTopic bool // slack
|
SyncTopic bool // slack
|
||||||
TengoModifyMessage string // general
|
TengoModifyMessage string // general
|
||||||
Team string // mattermost, keybase
|
Team string // mattermost, keybase
|
||||||
|
TeamID string // msteams
|
||||||
|
TenantID string // msteams
|
||||||
Token string // gitter, slack, discord, api
|
Token string // gitter, slack, discord, api
|
||||||
Topic string // zulip
|
Topic string // zulip
|
||||||
URL string // mattermost, slack // DEPRECATED
|
URL string // mattermost, slack // DEPRECATED
|
||||||
UseAPI bool // mattermost, slack
|
UseAPI bool // mattermost, slack
|
||||||
|
UseLocalAvatar []string // discord
|
||||||
UseSASL bool // IRC
|
UseSASL bool // IRC
|
||||||
UseTLS bool // IRC
|
UseTLS bool // IRC
|
||||||
UseDiscriminator bool // discord
|
UseDiscriminator bool // discord
|
||||||
UseFirstName bool // telegram
|
UseFirstName bool // telegram
|
||||||
UseUserName bool // discord
|
UseUserName bool // discord, matrix
|
||||||
UseInsecureURL bool // telegram
|
UseInsecureURL bool // telegram
|
||||||
VerboseJoinPart bool // IRC
|
VerboseJoinPart bool // IRC
|
||||||
WebhookBindAddress string // mattermost, slack
|
WebhookBindAddress string // mattermost, slack
|
||||||
@@ -199,6 +213,7 @@ type BridgeValues struct {
|
|||||||
WhatsApp map[string]Protocol // TODO is this struct used? Search for "SlackLegacy" for example didn't return any results
|
WhatsApp map[string]Protocol // TODO is this struct used? Search for "SlackLegacy" for example didn't return any results
|
||||||
Zulip map[string]Protocol
|
Zulip map[string]Protocol
|
||||||
Keybase map[string]Protocol
|
Keybase map[string]Protocol
|
||||||
|
Mumble map[string]Protocol
|
||||||
General Protocol
|
General Protocol
|
||||||
Tengo Tengo
|
Tengo Tengo
|
||||||
Gateway []Gateway
|
Gateway []Gateway
|
||||||
@@ -206,7 +221,9 @@ type BridgeValues struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Config interface {
|
type Config interface {
|
||||||
|
Viper() *viper.Viper
|
||||||
BridgeValues() *BridgeValues
|
BridgeValues() *BridgeValues
|
||||||
|
IsKeySet(key string) bool
|
||||||
GetBool(key string) (bool, bool)
|
GetBool(key string) (bool, bool)
|
||||||
GetInt(key string) (int, bool)
|
GetInt(key string) (int, bool)
|
||||||
GetString(key string) (string, bool)
|
GetString(key string) (string, bool)
|
||||||
@@ -232,7 +249,17 @@ func NewConfig(rootLogger *logrus.Logger, cfgfile string) Config {
|
|||||||
logger.Fatalf("Failed to read configuration file: %#v", err)
|
logger.Fatalf("Failed to read configuration file: %#v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mycfg := newConfigFromString(logger, input)
|
cfgtype := detectConfigType(cfgfile)
|
||||||
|
mycfg := newConfigFromString(logger, input, cfgtype)
|
||||||
|
if mycfg.cv.General.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
if mycfg.cv.General.MediaDownloadSize == 0 {
|
if mycfg.cv.General.MediaDownloadSize == 0 {
|
||||||
mycfg.cv.General.MediaDownloadSize = 1000000
|
mycfg.cv.General.MediaDownloadSize = 1000000
|
||||||
}
|
}
|
||||||
@@ -243,14 +270,26 @@ func NewConfig(rootLogger *logrus.Logger, cfgfile string) Config {
|
|||||||
return mycfg
|
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.
|
// NewConfigFromString instantiates a new configuration based on the specified string.
|
||||||
func NewConfigFromString(rootLogger *logrus.Logger, input []byte) Config {
|
func NewConfigFromString(rootLogger *logrus.Logger, input []byte) Config {
|
||||||
logger := rootLogger.WithFields(logrus.Fields{"prefix": "config"})
|
logger := rootLogger.WithFields(logrus.Fields{"prefix": "config"})
|
||||||
return newConfigFromString(logger, input)
|
return newConfigFromString(logger, input, "toml")
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConfigFromString(logger *logrus.Entry, input []byte) *config {
|
func newConfigFromString(logger *logrus.Entry, input []byte, cfgtype string) *config {
|
||||||
viper.SetConfigType("toml")
|
viper.SetConfigType(cfgtype)
|
||||||
viper.SetEnvPrefix("matterbridge")
|
viper.SetEnvPrefix("matterbridge")
|
||||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
|
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
|
||||||
viper.AutomaticEnv()
|
viper.AutomaticEnv()
|
||||||
@@ -274,6 +313,16 @@ func (c *config) BridgeValues() *BridgeValues {
|
|||||||
return c.cv
|
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) {
|
func (c *config) GetBool(key string) (bool, bool) {
|
||||||
c.RLock()
|
c.RLock()
|
||||||
defer c.RUnlock()
|
defer c.RUnlock()
|
||||||
@@ -333,6 +382,11 @@ type TestConfig struct {
|
|||||||
Overrides map[string]interface{}
|
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) {
|
func (c *TestConfig) GetBool(key string) (bool, bool) {
|
||||||
val, ok := c.Overrides[key]
|
val, ok := c.Overrides[key]
|
||||||
if ok {
|
if ok {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/42wim/matterbridge/bridge"
|
"github.com/42wim/matterbridge/bridge"
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/matterbridge/discordgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
const MessageLength = 1950
|
const MessageLength = 1950
|
||||||
@@ -21,7 +21,7 @@ type Bdiscord struct {
|
|||||||
c *discordgo.Session
|
c *discordgo.Session
|
||||||
|
|
||||||
nick string
|
nick string
|
||||||
useChannelID bool
|
userID string
|
||||||
guildID string
|
guildID string
|
||||||
webhookID string
|
webhookID string
|
||||||
webhookToken string
|
webhookToken string
|
||||||
@@ -72,6 +72,7 @@ func (b *Bdiscord) Connect() error {
|
|||||||
}
|
}
|
||||||
b.Log.Info("Connection succeeded")
|
b.Log.Info("Connection succeeded")
|
||||||
b.c.AddHandler(b.messageCreate)
|
b.c.AddHandler(b.messageCreate)
|
||||||
|
b.c.AddHandler(b.messageTyping)
|
||||||
b.c.AddHandler(b.memberUpdate)
|
b.c.AddHandler(b.memberUpdate)
|
||||||
b.c.AddHandler(b.messageUpdate)
|
b.c.AddHandler(b.messageUpdate)
|
||||||
b.c.AddHandler(b.messageDelete)
|
b.c.AddHandler(b.messageDelete)
|
||||||
@@ -92,6 +93,7 @@ func (b *Bdiscord) Connect() error {
|
|||||||
}
|
}
|
||||||
serverName := strings.Replace(b.GetString("Server"), "ID:", "", -1)
|
serverName := strings.Replace(b.GetString("Server"), "ID:", "", -1)
|
||||||
b.nick = userinfo.Username
|
b.nick = userinfo.Username
|
||||||
|
b.userID = userinfo.ID
|
||||||
b.channelsMutex.Lock()
|
b.channelsMutex.Lock()
|
||||||
for _, guild := range guilds {
|
for _, guild := range guilds {
|
||||||
if guild.Name == serverName || guild.ID == serverName {
|
if guild.Name == serverName || guild.ID == serverName {
|
||||||
@@ -114,31 +116,42 @@ func (b *Bdiscord) Connect() error {
|
|||||||
b.Log.Infof("Server=\"%s\" # Server ID", guild.ID)
|
b.Log.Infof("Server=\"%s\" # Server ID", guild.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
b.channelsMutex.RLock()
|
b.channelsMutex.RLock()
|
||||||
if b.GetString("WebhookURL") == "" {
|
if b.GetString("WebhookURL") == "" {
|
||||||
for _, channel := range b.channels {
|
for _, channel := range b.channels {
|
||||||
b.Log.Debugf("found channel %#v", channel)
|
b.Log.Debugf("found channel %#v", channel)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
b.canEditWebhooks = true
|
manageWebhooks := discordgo.PermissionManageWebhooks
|
||||||
for _, channel := range b.channels {
|
var channelsDenied []string
|
||||||
b.Log.Debugf("found channel %#v; verifying PermissionManageWebhooks", channel)
|
for _, info := range b.Channels {
|
||||||
perms, permsErr := b.c.State.UserChannelPermissions(userinfo.ID, channel.ID)
|
id := b.getChannelID(info.Name) // note(qaisjp): this readlocks channelsMutex
|
||||||
manageWebhooks := discordgo.PermissionManageWebhooks
|
b.Log.Debugf("Verifying PermissionManageWebhooks for %s with ID %s", info.ID, id)
|
||||||
if permsErr != nil || perms&manageWebhooks != manageWebhooks {
|
|
||||||
b.Log.Warnf("Can't manage webhooks in channel \"%s\"", channel.Name)
|
perms, permsErr := b.c.UserChannelPermissions(userinfo.ID, id)
|
||||||
b.canEditWebhooks = false
|
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))
|
||||||
}
|
}
|
||||||
if b.canEditWebhooks {
|
|
||||||
b.Log.Info("Can manage webhooks; will edit channel for global webhook on send")
|
b.canEditWebhooks = len(channelsDenied) == 0
|
||||||
} else {
|
b.canEditWebhooks = false
|
||||||
b.Log.Warn("Can't manage webhooks; won't edit channel for global webhook on send")
|
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()
|
b.channelsMutex.RUnlock()
|
||||||
|
|
||||||
@@ -173,10 +186,6 @@ func (b *Bdiscord) JoinChannel(channel config.ChannelInfo) error {
|
|||||||
defer b.channelsMutex.Unlock()
|
defer b.channelsMutex.Unlock()
|
||||||
|
|
||||||
b.channelInfoMap[channel.ID] = &channel
|
b.channelInfoMap[channel.ID] = &channel
|
||||||
idcheck := strings.Split(channel.Name, "ID:")
|
|
||||||
if len(idcheck) > 1 {
|
|
||||||
b.useChannelID = true
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,6 +197,14 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
|
|||||||
return "", fmt.Errorf("Could not find channelID for %v", msg.Channel)
|
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
|
// Make a action /me of the message
|
||||||
if msg.Event == config.EventUserAction {
|
if msg.Event == config.EventUserAction {
|
||||||
msg.Text = "_" + msg.Text + "_"
|
msg.Text = "_" + msg.Text + "_"
|
||||||
@@ -211,33 +228,10 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
|
|||||||
// Use webhook to send the message
|
// Use webhook to send the message
|
||||||
if wID != "" && msg.Event != config.EventMsgDelete {
|
if wID != "" && msg.Event != config.EventMsgDelete {
|
||||||
// skip events
|
// skip events
|
||||||
if msg.Event != "" && msg.Event != config.EventJoinLeave && msg.Event != config.EventTopicChange {
|
if msg.Event != "" && msg.Event != config.EventUserAction && msg.Event != config.EventJoinLeave && msg.Event != config.EventTopicChange {
|
||||||
return "", nil
|
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
|
// skip empty messages
|
||||||
if msg.Text == "" && (msg.Extra == nil || len(msg.Extra["file"]) == 0) {
|
if msg.Text == "" && (msg.Extra == nil || len(msg.Extra["file"]) == 0) {
|
||||||
b.Log.Debugf("Skipping empty message %#v", msg)
|
b.Log.Debugf("Skipping empty message %#v", msg)
|
||||||
@@ -250,11 +244,25 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
|
|||||||
if len(msg.Username) > 32 {
|
if len(msg.Username) > 32 {
|
||||||
msg.Username = msg.Username[0: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
|
// if we have a global webhook for this Discord account, and permission
|
||||||
// to modify webhooks (previously verified), then set its channel to
|
// 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 {
|
if isGlobalWebhook && b.canEditWebhooks {
|
||||||
b.Log.Debugf("Setting webhook channel to \"%s\"", msg.Channel)
|
b.Log.Debugf("Setting webhook channel to \"%s\"", msg.Channel)
|
||||||
_, err := b.c.WebhookEdit(wID, "", "", channelID)
|
_, err := b.c.WebhookEdit(wID, "", "", channelID)
|
||||||
@@ -272,6 +280,7 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
|
|||||||
if msg == nil {
|
if msg == nil {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return msg.ID, nil
|
return msg.ID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,6 +397,19 @@ func (b *Bdiscord) webhookSend(msg *config.Message, webhookID, token string) (*d
|
|||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// If avatar is unset, check if UseLocalAvatar contains the message's
|
||||||
|
// account or protocol, and if so, try to find a local avatar
|
||||||
|
if msg.Avatar == "" {
|
||||||
|
for _, val := range b.GetStringSlice("UseLocalAvatar") {
|
||||||
|
if msg.Protocol == val || msg.Account == val {
|
||||||
|
if avatar := b.findAvatar(msg); avatar != "" {
|
||||||
|
msg.Avatar = avatar
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WebhookParams can have either `Content` or `File`.
|
// WebhookParams can have either `Content` or `File`.
|
||||||
|
|
||||||
// We can't send empty messages.
|
// We can't send empty messages.
|
||||||
@@ -415,6 +437,10 @@ func (b *Bdiscord) webhookSend(msg *config.Message, webhookID, token string) (*d
|
|||||||
ContentType: "",
|
ContentType: "",
|
||||||
Reader: bytes.NewReader(*fi.Data),
|
Reader: bytes.NewReader(*fi.Data),
|
||||||
}
|
}
|
||||||
|
content := ""
|
||||||
|
if msg.Text == "" {
|
||||||
|
content = fi.Comment
|
||||||
|
}
|
||||||
_, e2 := b.c.WebhookExecute(
|
_, e2 := b.c.WebhookExecute(
|
||||||
webhookID,
|
webhookID,
|
||||||
token,
|
token,
|
||||||
@@ -423,6 +449,7 @@ func (b *Bdiscord) webhookSend(msg *config.Message, webhookID, token string) (*d
|
|||||||
Username: msg.Username,
|
Username: msg.Username,
|
||||||
AvatarURL: msg.Avatar,
|
AvatarURL: msg.Avatar,
|
||||||
File: &file,
|
File: &file,
|
||||||
|
Content: content,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if e2 != nil {
|
if e2 != nil {
|
||||||
@@ -432,3 +459,11 @@ func (b *Bdiscord) webhookSend(msg *config.Message, webhookID, token string) (*d
|
|||||||
}
|
}
|
||||||
return res, 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("")
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,15 +2,13 @@ package bdiscord
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/matterbridge/discordgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *Bdiscord) messageDelete(s *discordgo.Session, m *discordgo.MessageDelete) { //nolint:unparam
|
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 := config.Message{Account: b.Account, ID: m.ID, Event: config.EventMsgDelete, Text: config.EventMsgDelete}
|
||||||
rmsg.Channel = b.getChannelName(m.ChannelID)
|
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("<= Sending message from %s to gateway", b.Account)
|
||||||
b.Log.Debugf("<= Message is %#v", rmsg)
|
b.Log.Debugf("<= Message is %#v", rmsg)
|
||||||
b.Remote <- rmsg
|
b.Remote <- rmsg
|
||||||
@@ -24,11 +22,7 @@ func (b *Bdiscord) messageDeleteBulk(s *discordgo.Session, m *discordgo.MessageD
|
|||||||
ID: msgID,
|
ID: msgID,
|
||||||
Event: config.EventMsgDelete,
|
Event: config.EventMsgDelete,
|
||||||
Text: config.EventMsgDelete,
|
Text: config.EventMsgDelete,
|
||||||
Channel: "ID:" + m.ChannelID,
|
Channel: b.getChannelName(m.ChannelID),
|
||||||
}
|
|
||||||
|
|
||||||
if !b.useChannelID {
|
|
||||||
rmsg.Channel = b.getChannelName(m.ChannelID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Log.Debugf("<= Sending message from %s to gateway", b.Account)
|
b.Log.Debugf("<= Sending message from %s to gateway", b.Account)
|
||||||
@@ -37,6 +31,21 @@ 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
|
func (b *Bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) { //nolint:unparam
|
||||||
if b.GetBool("EditDisable") {
|
if b.GetBool("EditDisable") {
|
||||||
return
|
return
|
||||||
@@ -45,7 +54,10 @@ func (b *Bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdat
|
|||||||
if m.Message.EditedTimestamp != "" {
|
if m.Message.EditedTimestamp != "" {
|
||||||
b.Log.Debugf("Sending edit message")
|
b.Log.Debugf("Sending edit message")
|
||||||
m.Content += b.GetString("EditSuffix")
|
m.Content += b.GetString("EditSuffix")
|
||||||
b.messageCreate(s, (*discordgo.MessageCreate)(m))
|
msg := &discordgo.MessageCreate{
|
||||||
|
Message: m.Message,
|
||||||
|
}
|
||||||
|
b.messageCreate(s, msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +84,6 @@ func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
|
|||||||
|
|
||||||
if m.Content != "" {
|
if m.Content != "" {
|
||||||
b.Log.Debugf("== Receiving event %#v", m.Message)
|
b.Log.Debugf("== Receiving event %#v", m.Message)
|
||||||
m.Message.Content = b.stripCustomoji(m.Message.Content)
|
|
||||||
m.Message.Content = b.replaceChannelMentions(m.Message.Content)
|
m.Message.Content = b.replaceChannelMentions(m.Message.Content)
|
||||||
rmsg.Text, err = m.ContentWithMoreMentionsReplaced(b.c)
|
rmsg.Text, err = m.ContentWithMoreMentionsReplaced(b.c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -83,16 +94,13 @@ func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
|
|||||||
|
|
||||||
// set channel name
|
// set channel name
|
||||||
rmsg.Channel = b.getChannelName(m.ChannelID)
|
rmsg.Channel = b.getChannelName(m.ChannelID)
|
||||||
if b.useChannelID {
|
|
||||||
rmsg.Channel = "ID:" + m.ChannelID
|
|
||||||
}
|
|
||||||
|
|
||||||
// set username
|
fromWebhook := m.WebhookID != ""
|
||||||
if !b.GetBool("UseUserName") {
|
if !fromWebhook && !b.GetBool("UseUserName") {
|
||||||
rmsg.Username = b.getNick(m.Author)
|
rmsg.Username = b.getNick(m.Author, m.GuildID)
|
||||||
} else {
|
} else {
|
||||||
rmsg.Username = m.Author.Username
|
rmsg.Username = m.Author.Username
|
||||||
if b.GetBool("UseDiscriminator") {
|
if !fromWebhook && b.GetBool("UseDiscriminator") {
|
||||||
rmsg.Username += "#" + m.Author.Discriminator
|
rmsg.Username += "#" + m.Author.Discriminator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,7 +108,7 @@ func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
|
|||||||
// if we have embedded content add it to text
|
// if we have embedded content add it to text
|
||||||
if b.GetBool("ShowEmbeds") && m.Message.Embeds != nil {
|
if b.GetBool("ShowEmbeds") && m.Message.Embeds != nil {
|
||||||
for _, embed := range m.Message.Embeds {
|
for _, embed := range m.Message.Embeds {
|
||||||
rmsg.Text = rmsg.Text + "embed: " + embed.Title + " - " + embed.Description + " - " + embed.URL + "\n"
|
rmsg.Text += handleEmbed(embed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,6 +124,9 @@ func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
|
|||||||
rmsg.Event = config.EventUserAction
|
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("<= Sending message from %s on %s to gateway", m.Author.Username, b.Account)
|
||||||
b.Log.Debugf("<= Message is %#v", rmsg)
|
b.Log.Debugf("<= Message is %#v", rmsg)
|
||||||
b.Remote <- rmsg
|
b.Remote <- rmsg
|
||||||
@@ -189,3 +200,33 @@ func (b *Bdiscord) memberRemove(s *discordgo.Session, m *discordgo.GuildMemberRe
|
|||||||
b.Log.Debugf("<= Message is %#v", rmsg)
|
b.Log.Debugf("<= Message is %#v", rmsg)
|
||||||
b.Remote <- 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
|
||||||
|
}
|
||||||
|
|||||||
58
bridge/discord/handlers_test.go
Normal file
58
bridge/discord/handlers_test.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package bdiscord
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/matterbridge/discordgo"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHandleEmbed(t *testing.T) {
|
||||||
|
testcases := map[string]struct {
|
||||||
|
embed *discordgo.MessageEmbed
|
||||||
|
result string
|
||||||
|
}{
|
||||||
|
"allempty": {
|
||||||
|
embed: &discordgo.MessageEmbed{},
|
||||||
|
result: "",
|
||||||
|
},
|
||||||
|
"one": {
|
||||||
|
embed: &discordgo.MessageEmbed{
|
||||||
|
Title: "blah",
|
||||||
|
},
|
||||||
|
result: " embed: blah\n",
|
||||||
|
},
|
||||||
|
"two": {
|
||||||
|
embed: &discordgo.MessageEmbed{
|
||||||
|
Title: "blah",
|
||||||
|
Description: "blah2",
|
||||||
|
},
|
||||||
|
result: " embed: blah - blah2\n",
|
||||||
|
},
|
||||||
|
"three": {
|
||||||
|
embed: &discordgo.MessageEmbed{
|
||||||
|
Title: "blah",
|
||||||
|
Description: "blah2",
|
||||||
|
URL: "blah3",
|
||||||
|
},
|
||||||
|
result: " embed: blah - blah2 - blah3\n",
|
||||||
|
},
|
||||||
|
"twob": {
|
||||||
|
embed: &discordgo.MessageEmbed{
|
||||||
|
Description: "blah2",
|
||||||
|
URL: "blah3",
|
||||||
|
},
|
||||||
|
result: " embed: blah2 - blah3\n",
|
||||||
|
},
|
||||||
|
"oneb": {
|
||||||
|
embed: &discordgo.MessageEmbed{
|
||||||
|
URL: "blah3",
|
||||||
|
},
|
||||||
|
result: " embed: blah3\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testcases {
|
||||||
|
assert.Equalf(t, tc.result, handleEmbed(tc.embed), "Testcases %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,10 +6,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/matterbridge/discordgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *Bdiscord) getNick(user *discordgo.User) string {
|
func (b *Bdiscord) getNick(user *discordgo.User, guildID string) string {
|
||||||
b.membersMutex.RLock()
|
b.membersMutex.RLock()
|
||||||
defer b.membersMutex.RUnlock()
|
defer b.membersMutex.RUnlock()
|
||||||
|
|
||||||
@@ -23,9 +23,9 @@ func (b *Bdiscord) getNick(user *discordgo.User) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we didn't find nick, search for it.
|
// If we didn't find nick, search for it.
|
||||||
member, err := b.c.GuildMember(b.guildID, user.ID)
|
member, err := b.c.GuildMember(guildID, user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Warnf("Failed to fetch information for member %#v on guild %#v: %s", user, b.guildID, err)
|
b.Log.Warnf("Failed to fetch information for member %#v on guild %#v: %s", user, guildID, err)
|
||||||
return user.Username
|
return user.Username
|
||||||
} else if member == nil {
|
} else if member == nil {
|
||||||
b.Log.Warnf("Got no information for member %#v", user)
|
b.Log.Warnf("Got no information for member %#v", user)
|
||||||
@@ -96,6 +96,13 @@ func (b *Bdiscord) getChannelName(id string) string {
|
|||||||
b.channelsMutex.RLock()
|
b.channelsMutex.RLock()
|
||||||
defer b.channelsMutex.RUnlock()
|
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 {
|
for _, channel := range b.channels {
|
||||||
if channel.ID == id {
|
if channel.ID == id {
|
||||||
return b.getCategoryChannelName(channel.Name, channel.ParentID)
|
return b.getCategoryChannelName(channel.Name, channel.ParentID)
|
||||||
@@ -129,8 +136,8 @@ func (b *Bdiscord) getCategoryChannelName(name, parentID string) string {
|
|||||||
var (
|
var (
|
||||||
// See https://discordapp.com/developers/docs/reference#message-formatting.
|
// See https://discordapp.com/developers/docs/reference#message-formatting.
|
||||||
channelMentionRE = regexp.MustCompile("<#[0-9]+>")
|
channelMentionRE = regexp.MustCompile("<#[0-9]+>")
|
||||||
emojiRE = regexp.MustCompile("<(:.*?:)[0-9]+>")
|
|
||||||
userMentionRE = regexp.MustCompile("@[^@\n]{1,32}")
|
userMentionRE = regexp.MustCompile("@[^@\n]{1,32}")
|
||||||
|
emoteRE = regexp.MustCompile(`<a?(:\w+:)\d+>`)
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *Bdiscord) replaceChannelMentions(text string) string {
|
func (b *Bdiscord) replaceChannelMentions(text string) string {
|
||||||
@@ -176,13 +183,14 @@ func (b *Bdiscord) replaceUserMentions(text string) string {
|
|||||||
return userMentionRE.ReplaceAllStringFunc(text, replaceUserMentionFunc)
|
return userMentionRE.ReplaceAllStringFunc(text, replaceUserMentionFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bdiscord) stripCustomoji(text string) string {
|
func replaceEmotes(text string) string {
|
||||||
return emojiRE.ReplaceAllString(text, `$1`)
|
return emoteRE.ReplaceAllString(text, "$1")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bdiscord) replaceAction(text string) (string, bool) {
|
func (b *Bdiscord) replaceAction(text string) (string, bool) {
|
||||||
if strings.HasPrefix(text, "_") && strings.HasSuffix(text, "_") {
|
length := len(text)
|
||||||
return text[1 : len(text)-1], true
|
if length > 1 && text[0] == '_' && text[length-1] == '_' {
|
||||||
|
return text[1 : length-1], true
|
||||||
}
|
}
|
||||||
return text, false
|
return text, false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image/png"
|
"image/png"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -14,8 +17,10 @@ import (
|
|||||||
"golang.org/x/image/webp"
|
"golang.org/x/image/webp"
|
||||||
|
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
|
"github.com/gomarkdown/markdown"
|
||||||
|
"github.com/gomarkdown/markdown/html"
|
||||||
|
"github.com/gomarkdown/markdown/parser"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"gitlab.com/golang-commonmark/markdown"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DownloadFile downloads the given non-authenticated URL.
|
// DownloadFile downloads the given non-authenticated URL.
|
||||||
@@ -176,15 +181,21 @@ func ClipMessage(text string, length int) string {
|
|||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParseMarkdown takes in an input string as markdown and parses it to html
|
||||||
func ParseMarkdown(input string) string {
|
func ParseMarkdown(input string) string {
|
||||||
md := markdown.New(markdown.XHTMLOutput(true), markdown.Breaks(true))
|
extensions := parser.HardLineBreak | parser.NoIntraEmphasis
|
||||||
res := md.RenderToString([]byte(input))
|
markdownParser := parser.NewWithExtensions(extensions)
|
||||||
|
renderer := html.NewRenderer(html.RendererOptions{
|
||||||
|
Flags: 0,
|
||||||
|
})
|
||||||
|
parsedMarkdown := markdown.ToHTML([]byte(input), markdownParser, renderer)
|
||||||
|
res := string(parsedMarkdown)
|
||||||
res = strings.TrimPrefix(res, "<p>")
|
res = strings.TrimPrefix(res, "<p>")
|
||||||
res = strings.TrimSuffix(res, "</p>\n")
|
res = strings.TrimSuffix(res, "</p>\n")
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConvertWebPToPNG convert input data (which should be WebP format to PNG format)
|
// ConvertWebPToPNG converts input data (which should be WebP format) to PNG format
|
||||||
func ConvertWebPToPNG(data *[]byte) error {
|
func ConvertWebPToPNG(data *[]byte) error {
|
||||||
r := bytes.NewReader(*data)
|
r := bytes.NewReader(*data)
|
||||||
m, err := webp.Decode(r)
|
m, err := webp.Decode(r)
|
||||||
@@ -199,3 +210,49 @@ func ConvertWebPToPNG(data *[]byte) error {
|
|||||||
*data = w.Bytes()
|
*data = w.Bytes()
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import (
|
|||||||
|
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
"github.com/dfordsoft/golib/ic"
|
|
||||||
"github.com/lrstanley/girc"
|
"github.com/lrstanley/girc"
|
||||||
|
"github.com/missdeer/golib/ic"
|
||||||
"github.com/paulrosania/go-charset/charset"
|
"github.com/paulrosania/go-charset/charset"
|
||||||
"github.com/saintfish/chardet"
|
"github.com/saintfish/chardet"
|
||||||
|
|
||||||
@@ -54,12 +54,12 @@ func (b *Birc) handleFiles(msg *config.Message) bool {
|
|||||||
for _, f := range msg.Extra["file"] {
|
for _, f := range msg.Extra["file"] {
|
||||||
fi := f.(config.FileInfo)
|
fi := f.(config.FileInfo)
|
||||||
if fi.Comment != "" {
|
if fi.Comment != "" {
|
||||||
msg.Text += fi.Comment + ": "
|
msg.Text += fi.Comment + " : "
|
||||||
}
|
}
|
||||||
if fi.URL != "" {
|
if fi.URL != "" {
|
||||||
msg.Text = fi.URL
|
msg.Text = fi.URL
|
||||||
if fi.Comment != "" {
|
if fi.Comment != "" {
|
||||||
msg.Text = fi.Comment + ": " + fi.URL
|
msg.Text = fi.Comment + " : " + fi.URL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b.Local <- config.Message{Text: msg.Text, Username: msg.Username, Channel: msg.Channel, Event: msg.Event}
|
b.Local <- config.Message{Text: msg.Text, Username: msg.Username, Channel: msg.Channel, Event: msg.Event}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/crc32"
|
"hash/crc32"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -14,6 +15,7 @@ import (
|
|||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
"github.com/lrstanley/girc"
|
"github.com/lrstanley/girc"
|
||||||
|
stripmd "github.com/writeas/go-strip-markdown"
|
||||||
|
|
||||||
// We need to import the 'data' package as an implicit dependency.
|
// We need to import the 'data' package as an implicit dependency.
|
||||||
// See: https://godoc.org/github.com/paulrosania/go-charset/charset
|
// See: https://godoc.org/github.com/paulrosania/go-charset/charset
|
||||||
@@ -156,6 +158,10 @@ func (b *Birc) Send(msg config.Message) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var msgLines []string
|
var msgLines []string
|
||||||
|
if b.GetBool("StripMarkdown") {
|
||||||
|
msg.Text = stripmd.Strip(msg.Text)
|
||||||
|
}
|
||||||
|
|
||||||
if b.GetBool("MessageSplit") {
|
if b.GetBool("MessageSplit") {
|
||||||
msgLines = helper.GetSubLines(msg.Text, b.MessageLength)
|
msgLines = helper.GetSubLines(msg.Text, b.MessageLength)
|
||||||
} else {
|
} else {
|
||||||
@@ -167,12 +173,8 @@ func (b *Birc) Send(msg config.Message) (string, error) {
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Local <- config.Message{
|
msg.Text = msgLines[i]
|
||||||
Text: msgLines[i],
|
b.Local <- msg
|
||||||
Username: msg.Username,
|
|
||||||
Channel: msg.Channel,
|
|
||||||
Event: msg.Event,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
@@ -205,7 +207,7 @@ func (b *Birc) doSend() {
|
|||||||
for msg := range b.Local {
|
for msg := range b.Local {
|
||||||
<-throttle.C
|
<-throttle.C
|
||||||
username := msg.Username
|
username := msg.Username
|
||||||
if b.GetBool("Colornicks") {
|
if b.GetBool("Colornicks") && len(username) > 1 {
|
||||||
checksum := crc32.ChecksumIEEE([]byte(msg.Username))
|
checksum := crc32.ChecksumIEEE([]byte(msg.Username))
|
||||||
colorCode := checksum%14 + 2 // quick fix - prevent white or black color codes
|
colorCode := checksum%14 + 2 // quick fix - prevent white or black color codes
|
||||||
username = fmt.Sprintf("\x03%02d%s\x0F", colorCode, msg.Username)
|
username = fmt.Sprintf("\x03%02d%s\x0F", colorCode, msg.Username)
|
||||||
@@ -239,6 +241,18 @@ func (b *Birc) getClient() (*girc.Client, error) {
|
|||||||
user = user[1:]
|
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{
|
i := girc.New(girc.Config{
|
||||||
Server: server,
|
Server: server,
|
||||||
ServerPass: b.GetString("Password"),
|
ServerPass: b.GetString("Password"),
|
||||||
@@ -248,7 +262,10 @@ func (b *Birc) getClient() (*girc.Client, error) {
|
|||||||
Name: b.GetString("Nick"),
|
Name: b.GetString("Nick"),
|
||||||
SSL: b.GetBool("UseTLS"),
|
SSL: b.GetBool("UseTLS"),
|
||||||
TLSConfig: &tls.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), ServerName: server}, //nolint:gosec
|
TLSConfig: &tls.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), ServerName: server}, //nolint:gosec
|
||||||
PingDelay: time.Minute,
|
PingDelay: pingDelay,
|
||||||
|
// skip gIRC internal rate limiting, since we have our own throttling
|
||||||
|
AllowFlood: true,
|
||||||
|
Debug: debug,
|
||||||
})
|
})
|
||||||
return i, nil
|
return i, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/keybase/go-keybase-chat-bot/kbchat"
|
"github.com/keybase/go-keybase-chat-bot/kbchat/types/chat1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *Bkeybase) handleKeybase() {
|
func (b *Bkeybase) handleKeybase() {
|
||||||
@@ -20,7 +20,7 @@ func (b *Bkeybase) handleKeybase() {
|
|||||||
b.Log.Errorf("failed to read message: %s", err.Error())
|
b.Log.Errorf("failed to read message: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg.Message.Content.Type != "text" {
|
if msg.Message.Content.TypeName != "text" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ func (b *Bkeybase) handleKeybase() {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bkeybase) handleMessage(msg kbchat.Message) {
|
func (b *Bkeybase) handleMessage(msg chat1.MsgSummary) {
|
||||||
b.Log.Debugf("== Receiving event: %#v", msg)
|
b.Log.Debugf("== Receiving event: %#v", msg)
|
||||||
if msg.Channel.TopicName != b.channel || msg.Channel.Name != b.team {
|
if msg.Channel.TopicName != b.channel || msg.Channel.Name != b.team {
|
||||||
return
|
return
|
||||||
@@ -45,10 +45,10 @@ func (b *Bkeybase) handleMessage(msg kbchat.Message) {
|
|||||||
// TODO download avatar
|
// TODO download avatar
|
||||||
|
|
||||||
// Create our message
|
// Create our message
|
||||||
rmsg := config.Message{Username: msg.Sender.Username, Text: msg.Content.Text.Body, UserID: msg.Sender.Uid, Channel: msg.Channel.TopicName, ID: strconv.Itoa(msg.MsgID), Account: b.Account}
|
rmsg := config.Message{Username: msg.Sender.Username, Text: msg.Content.Text.Body, UserID: string(msg.Sender.Uid), Channel: msg.Channel.TopicName, ID: strconv.Itoa(int(msg.Id)), Account: b.Account}
|
||||||
|
|
||||||
// Text must be a string
|
// Text must be a string
|
||||||
if msg.Content.Type != "text" {
|
if msg.Content.TypeName != "text" {
|
||||||
b.Log.Errorf("message is not text")
|
b.Log.Errorf("message is not text")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package bkeybase
|
package bkeybase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/42wim/matterbridge/bridge"
|
"github.com/42wim/matterbridge/bridge"
|
||||||
@@ -66,17 +69,38 @@ func (b *Bkeybase) Send(msg config.Message) (string, error) {
|
|||||||
// Delete message if we have an ID
|
// Delete message if we have an ID
|
||||||
// Delete message not supported by keybase go library yet
|
// Delete message not supported by keybase go library yet
|
||||||
|
|
||||||
// Upload a file if it exists
|
|
||||||
// kbchat lib does not support attachments yet
|
|
||||||
|
|
||||||
// Edit message if we have an ID
|
// Edit message if we have an ID
|
||||||
// kbchat lib does not support message editing yet
|
// 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
|
// Send regular message
|
||||||
resp, err := b.kbc.SendMessageByTeamName(b.team, msg.Username+msg.Text, &b.channel)
|
text := msg.Username + msg.Text
|
||||||
|
resp, err := b.kbc.SendMessageByTeamName(b.team, &b.channel, text)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
return strconv.Itoa(int(*resp.Result.MessageID)), err
|
||||||
return strconv.Itoa(resp.Result.MsgID), err
|
|
||||||
}
|
}
|
||||||
|
|||||||
166
bridge/matrix/helpers.go
Normal file
166
bridge/matrix/helpers.go
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -3,31 +3,71 @@ package bmatrix
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
|
||||||
"mime"
|
"mime"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/42wim/matterbridge/bridge"
|
"github.com/42wim/matterbridge/bridge"
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
matrix "github.com/matterbridge/gomatrix"
|
matrix "github.com/matrix-org/gomatrix"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
htmlTag = regexp.MustCompile("</.*?>")
|
||||||
|
htmlReplacementTag = regexp.MustCompile("<[^>]*>")
|
||||||
|
)
|
||||||
|
|
||||||
|
type NicknameCacheEntry struct {
|
||||||
|
displayName string
|
||||||
|
lastUpdated time.Time
|
||||||
|
}
|
||||||
|
|
||||||
type Bmatrix struct {
|
type Bmatrix struct {
|
||||||
mc *matrix.Client
|
mc *matrix.Client
|
||||||
UserID string
|
UserID string
|
||||||
RoomMap map[string]string
|
NicknameMap map[string]NicknameCacheEntry
|
||||||
|
RoomMap map[string]string
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
htmlTag *regexp.Regexp
|
|
||||||
*bridge.Config
|
*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 {
|
func New(cfg *bridge.Config) bridge.Bridger {
|
||||||
b := &Bmatrix{Config: cfg}
|
b := &Bmatrix{Config: cfg}
|
||||||
b.htmlTag = regexp.MustCompile("</.*?>")
|
|
||||||
b.RoomMap = make(map[string]string)
|
b.RoomMap = make(map[string]string)
|
||||||
|
b.NicknameMap = make(map[string]NicknameCacheEntry)
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,9 +79,10 @@ func (b *Bmatrix) Connect() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
resp, err := b.mc.Login(&matrix.ReqLogin{
|
resp, err := b.mc.Login(&matrix.ReqLogin{
|
||||||
Type: "m.login.password",
|
Type: "m.login.password",
|
||||||
User: b.GetString("Login"),
|
User: b.GetString("Login"),
|
||||||
Password: b.GetString("Password"),
|
Password: b.GetString("Password"),
|
||||||
|
Identifier: matrix.NewUserIdentifier(b.GetString("Login")),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -58,14 +99,25 @@ func (b *Bmatrix) Disconnect() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bmatrix) JoinChannel(channel config.ChannelInfo) error {
|
func (b *Bmatrix) JoinChannel(channel config.ChannelInfo) error {
|
||||||
|
retry:
|
||||||
resp, err := b.mc.JoinRoom(channel.Name, "", nil)
|
resp, err := b.mc.JoinRoom(channel.Name, "", nil)
|
||||||
if err != 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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Lock()
|
b.Lock()
|
||||||
b.RoomMap[resp.RoomID] = channel.Name
|
b.RoomMap[resp.RoomID] = channel.Name
|
||||||
b.Unlock()
|
b.Unlock()
|
||||||
return err
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bmatrix) Send(msg config.Message) (string, error) {
|
func (b *Bmatrix) Send(msg config.Message) (string, error) {
|
||||||
@@ -74,11 +126,14 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
|
|||||||
channel := b.getRoomID(msg.Channel)
|
channel := b.getRoomID(msg.Channel)
|
||||||
b.Log.Debugf("Channel %s maps to channel id %s", msg.Channel, 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
|
// Make a action /me of the message
|
||||||
if msg.Event == config.EventUserAction {
|
if msg.Event == config.EventUserAction {
|
||||||
m := matrix.TextMessage{
|
m := matrix.TextMessage{
|
||||||
MsgType: "m.emote",
|
MsgType: "m.emote",
|
||||||
Body: msg.Username + msg.Text,
|
Body: username.plain + msg.Text,
|
||||||
|
FormattedBody: username.formatted + msg.Text,
|
||||||
}
|
}
|
||||||
resp, err := b.mc.SendMessageEvent(channel, "m.room.message", m)
|
resp, err := b.mc.SendMessageEvent(channel, "m.room.message", m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -113,45 +168,68 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Edit message if we have an ID
|
// Edit message if we have an ID
|
||||||
// matrix has no editing support
|
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
|
||||||
|
}
|
||||||
|
|
||||||
// Use notices to send join/leave events
|
// Use notices to send join/leave events
|
||||||
if msg.Event == config.EventJoinLeave {
|
if msg.Event == config.EventJoinLeave {
|
||||||
resp, err := b.mc.SendNotice(channel, msg.Username+msg.Text)
|
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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return resp.EventID, err
|
return resp.EventID, err
|
||||||
}
|
}
|
||||||
|
|
||||||
username := html.EscapeString(msg.Username)
|
if b.GetBool("HTMLDisable") {
|
||||||
// check if we have a </tag>. if we have, we don't escape HTML. #696
|
resp, err := b.mc.SendText(channel, username.plain+msg.Text)
|
||||||
if b.htmlTag.MatchString(msg.Username) {
|
if err != nil {
|
||||||
username = msg.Username
|
return "", err
|
||||||
|
}
|
||||||
|
return resp.EventID, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post normal message with HTML support (eg riot.im)
|
// Post normal message with HTML support (eg riot.im)
|
||||||
resp, err := b.mc.SendHTML(channel, msg.Username+msg.Text, username+helper.ParseMarkdown(msg.Text))
|
resp, err := b.mc.SendFormattedText(channel, username.plain+msg.Text, username.formatted+helper.ParseMarkdown(msg.Text))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return resp.EventID, 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() {
|
func (b *Bmatrix) handlematrix() {
|
||||||
syncer := b.mc.Syncer.(*matrix.DefaultSyncer)
|
syncer := b.mc.Syncer.(*matrix.DefaultSyncer)
|
||||||
syncer.OnEventType("m.room.redaction", b.handleEvent)
|
syncer.OnEventType("m.room.redaction", b.handleEvent)
|
||||||
syncer.OnEventType("m.room.message", b.handleEvent)
|
syncer.OnEventType("m.room.message", b.handleEvent)
|
||||||
|
syncer.OnEventType("m.room.member", b.handleMemberChange)
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
if err := b.mc.Sync(); err != nil {
|
if err := b.mc.Sync(); err != nil {
|
||||||
@@ -161,6 +239,45 @@ 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) {
|
func (b *Bmatrix) handleEvent(ev *matrix.Event) {
|
||||||
b.Log.Debugf("== Receiving event: %#v", ev)
|
b.Log.Debugf("== Receiving event: %#v", ev)
|
||||||
if ev.Sender != b.UserID {
|
if ev.Sender != b.UserID {
|
||||||
@@ -172,10 +289,15 @@ func (b *Bmatrix) handleEvent(ev *matrix.Event) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO download avatar
|
|
||||||
|
|
||||||
// Create our message
|
// Create our message
|
||||||
rmsg := config.Message{Username: ev.Sender[1:], Channel: channel, Account: b.Account, UserID: ev.Sender, ID: ev.ID}
|
rmsg := config.Message{
|
||||||
|
Username: b.getDisplayName(ev.Sender),
|
||||||
|
Channel: channel,
|
||||||
|
Account: b.Account,
|
||||||
|
UserID: ev.Sender,
|
||||||
|
ID: ev.ID,
|
||||||
|
Avatar: b.getAvatarURL(ev.Sender),
|
||||||
|
}
|
||||||
|
|
||||||
// Text must be a string
|
// Text must be a string
|
||||||
if rmsg.Text, ok = ev.Content["body"].(string); !ok {
|
if rmsg.Text, ok = ev.Content["body"].(string); !ok {
|
||||||
@@ -204,6 +326,11 @@ func (b *Bmatrix) handleEvent(ev *matrix.Event) {
|
|||||||
rmsg.Event = config.EventUserAction
|
rmsg.Event = config.EventUserAction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Is it an edit?
|
||||||
|
if b.handleEdit(ev, rmsg) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Do we have attachments
|
// Do we have attachments
|
||||||
if b.containsAttachment(ev.Content) {
|
if b.containsAttachment(ev.Content) {
|
||||||
err := b.handleDownloadFile(&rmsg, ev.Content)
|
err := b.handleDownloadFile(&rmsg, ev.Content)
|
||||||
@@ -288,24 +415,16 @@ func (b *Bmatrix) handleUploadFiles(msg *config.Message, channel string) (string
|
|||||||
|
|
||||||
// handleUploadFile handles native upload of a file.
|
// handleUploadFile handles native upload of a file.
|
||||||
func (b *Bmatrix) handleUploadFile(msg *config.Message, channel string, fi *config.FileInfo) {
|
func (b *Bmatrix) handleUploadFile(msg *config.Message, channel string, fi *config.FileInfo) {
|
||||||
|
username := newMatrixUsername(msg.Username)
|
||||||
content := bytes.NewReader(*fi.Data)
|
content := bytes.NewReader(*fi.Data)
|
||||||
sp := strings.Split(fi.Name, ".")
|
sp := strings.Split(fi.Name, ".")
|
||||||
mtype := mime.TypeByExtension("." + sp[len(sp)-1])
|
mtype := mime.TypeByExtension("." + sp[len(sp)-1])
|
||||||
if !strings.Contains(mtype, "image") && !strings.Contains(mtype, "video") {
|
// image and video uploads send no username, we have to do this ourself here #715
|
||||||
return
|
_, err := b.mc.SendFormattedText(channel, username.plain+fi.Comment, username.formatted+fi.Comment)
|
||||||
}
|
if err != nil {
|
||||||
if fi.Comment != "" {
|
b.Log.Errorf("file comment failed: %#v", err)
|
||||||
_, 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)
|
b.Log.Debugf("uploading file: %s %s", fi.Name, mtype)
|
||||||
res, err := b.mc.UploadToContentRepo(content, mtype, int64(len(*fi.Data)))
|
res, err := b.mc.UploadToContentRepo(content, mtype, int64(len(*fi.Data)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -326,22 +445,34 @@ func (b *Bmatrix) handleUploadFile(msg *config.Message, channel string, fi *conf
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Errorf("sendImage failed: %#v", err)
|
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)
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
28
bridge/matrix/matrix_test.go
Normal file
28
bridge/matrix/matrix_test.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
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, "<MyUser>", uut.formatted)
|
||||||
|
assert.Equal(t, "<MyUser>", uut.plain)
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
"github.com/42wim/matterbridge/matterclient"
|
"github.com/42wim/matterbridge/matterclient"
|
||||||
"github.com/mattermost/mattermost-server/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// handleDownloadAvatar downloads the avatar of userid from channel
|
// handleDownloadAvatar downloads the avatar of userid from channel
|
||||||
@@ -66,6 +66,10 @@ func (b *Bmattermost) handleMatter() {
|
|||||||
} else {
|
} else {
|
||||||
b.Log.Debugf("Choosing login/password based receiving")
|
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)
|
go b.handleMatterClient(messages)
|
||||||
}
|
}
|
||||||
var ok bool
|
var ok bool
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/42wim/matterbridge/bridge/helper"
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
"github.com/42wim/matterbridge/matterclient"
|
"github.com/42wim/matterbridge/matterclient"
|
||||||
"github.com/42wim/matterbridge/matterhook"
|
"github.com/42wim/matterbridge/matterhook"
|
||||||
"github.com/mattermost/mattermost-server/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *Bmattermost) doConnectWebhookBind() error {
|
func (b *Bmattermost) doConnectWebhookBind() error {
|
||||||
|
|||||||
101
bridge/msteams/handler.go
Normal file
101
bridge/msteams/handler.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package bmsteams
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
|
|
||||||
|
msgraph "github.com/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"
|
||||||
|
}
|
||||||
224
bridge/msteams/msteams.go
Normal file
224
bridge/msteams/msteams.go
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
90
bridge/mumble/handlers.go
Normal file
90
bridge/mumble/handlers.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
143
bridge/mumble/helpers.go
Normal file
143
bridge/mumble/helpers.go
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
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 `]+)` 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
|
||||||
|
}
|
||||||
259
bridge/mumble/mumble.go
Normal file
259
bridge/mumble/mumble.go
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
203
bridge/nctalk/nctalk.go
Normal file
203
bridge/nctalk/nctalk.go
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package brocketchat
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
|
"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *Brocketchat) handleRocket() {
|
func (b *Brocketchat) handleRocket() {
|
||||||
@@ -38,6 +39,23 @@ func (b *Brocketchat) handleRocketHook(messages chan *config.Message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Brocketchat) handleStatusEvent(ev models.Message, rmsg *config.Message) bool {
|
||||||
|
switch ev.Type {
|
||||||
|
case "":
|
||||||
|
// this is a normal message, no processing needed
|
||||||
|
// return true so the message is not dropped
|
||||||
|
return true
|
||||||
|
case sUserJoined, sUserLeft:
|
||||||
|
rmsg.Event = config.EventJoinLeave
|
||||||
|
return true
|
||||||
|
case sRoomChangedTopic:
|
||||||
|
rmsg.Event = config.EventTopicChange
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
b.Log.Debugf("Dropping message with unknown type: %s", ev.Type)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (b *Brocketchat) handleRocketClient(messages chan *config.Message) {
|
func (b *Brocketchat) handleRocketClient(messages chan *config.Message) {
|
||||||
for message := range b.messageChan {
|
for message := range b.messageChan {
|
||||||
// skip messages with same ID, apparently messages get duplicated for an unknown reason
|
// skip messages with same ID, apparently messages get duplicated for an unknown reason
|
||||||
@@ -59,7 +77,12 @@ func (b *Brocketchat) handleRocketClient(messages chan *config.Message) {
|
|||||||
UserID: message.User.ID,
|
UserID: message.User.ID,
|
||||||
ID: message.ID,
|
ID: message.ID,
|
||||||
}
|
}
|
||||||
messages <- rmsg
|
|
||||||
|
// handleStatusEvent returns false if the message should be dropped
|
||||||
|
// in that case it is probably some modification to the channel we do not want to relay
|
||||||
|
if b.handleStatusEvent(m, rmsg) {
|
||||||
|
messages <- rmsg
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,9 @@ func (b *Brocketchat) doConnectWebhookURL() error {
|
|||||||
func (b *Brocketchat) apiLogin() error {
|
func (b *Brocketchat) apiLogin() error {
|
||||||
b.Log.Debugf("handling apiLogin()")
|
b.Log.Debugf("handling apiLogin()")
|
||||||
credentials := &models.UserCredentials{Email: b.GetString("login"), Password: b.GetString("password")}
|
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"))
|
myURL, err := url.Parse(b.GetString("server"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ type Brocketchat struct {
|
|||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
sUserJoined = "uj"
|
||||||
|
sUserLeft = "ul"
|
||||||
|
sRoomChangedTopic = "room_changed_topic"
|
||||||
|
)
|
||||||
|
|
||||||
func New(cfg *bridge.Config) bridge.Bridger {
|
func New(cfg *bridge.Config) bridge.Bridger {
|
||||||
newCache, err := lru.New(100)
|
newCache, err := lru.New(100)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
package bslack
|
package bslack
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
"github.com/nlopes/slack"
|
"github.com/slack-go/slack"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ErrEventIgnored is for events that should be ignored
|
||||||
|
var ErrEventIgnored = errors.New("this event message should ignored")
|
||||||
|
|
||||||
func (b *Bslack) handleSlack() {
|
func (b *Bslack) handleSlack() {
|
||||||
messages := make(chan *config.Message)
|
messages := make(chan *config.Message)
|
||||||
if b.GetString(incomingWebhookConfig) != "" {
|
if b.GetString(incomingWebhookConfig) != "" && b.GetString(tokenConfig) == "" {
|
||||||
b.Log.Debugf("Choosing webhooks based receiving")
|
b.Log.Debugf("Choosing webhooks based receiving")
|
||||||
go b.handleMatterHook(messages)
|
go b.handleMatterHook(messages)
|
||||||
} else {
|
} else {
|
||||||
@@ -30,6 +34,7 @@ func (b *Bslack) handleSlack() {
|
|||||||
message.Text = b.replaceVariable(message.Text)
|
message.Text = b.replaceVariable(message.Text)
|
||||||
message.Text = b.replaceChannel(message.Text)
|
message.Text = b.replaceChannel(message.Text)
|
||||||
message.Text = b.replaceURL(message.Text)
|
message.Text = b.replaceURL(message.Text)
|
||||||
|
message.Text = b.replaceb0rkedMarkDown(message.Text)
|
||||||
message.Text = html.UnescapeString(message.Text)
|
message.Text = html.UnescapeString(message.Text)
|
||||||
|
|
||||||
// Add the avatar
|
// Add the avatar
|
||||||
@@ -43,7 +48,7 @@ func (b *Bslack) handleSlack() {
|
|||||||
|
|
||||||
func (b *Bslack) handleSlackClient(messages chan *config.Message) {
|
func (b *Bslack) handleSlackClient(messages chan *config.Message) {
|
||||||
for msg := range b.rtm.IncomingEvents {
|
for msg := range b.rtm.IncomingEvents {
|
||||||
if msg.Type != sUserTyping && msg.Type != sLatencyReport {
|
if msg.Type != sUserTyping && msg.Type != sHello && msg.Type != sLatencyReport {
|
||||||
b.Log.Debugf("== Receiving event %#v", msg.Data)
|
b.Log.Debugf("== Receiving event %#v", msg.Data)
|
||||||
}
|
}
|
||||||
switch ev := msg.Data.(type) {
|
switch ev := msg.Data.(type) {
|
||||||
@@ -52,7 +57,9 @@ func (b *Bslack) handleSlackClient(messages chan *config.Message) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
rmsg, err := b.handleTypingEvent(ev)
|
rmsg, err := b.handleTypingEvent(ev)
|
||||||
if err != nil {
|
if err == ErrEventIgnored {
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
b.Log.Errorf("%#v", err)
|
b.Log.Errorf("%#v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -86,7 +93,7 @@ func (b *Bslack) handleSlackClient(messages chan *config.Message) {
|
|||||||
b.Log.Errorf("Connection failed %#v %#v", ev.Error(), ev.ErrorObj)
|
b.Log.Errorf("Connection failed %#v %#v", ev.Error(), ev.ErrorObj)
|
||||||
case *slack.MemberJoinedChannelEvent:
|
case *slack.MemberJoinedChannelEvent:
|
||||||
b.users.populateUser(ev.User)
|
b.users.populateUser(ev.User)
|
||||||
case *slack.LatencyReport:
|
case *slack.HelloEvent, *slack.LatencyReport, *slack.ConnectingEvent:
|
||||||
continue
|
continue
|
||||||
default:
|
default:
|
||||||
b.Log.Debugf("Unhandled incoming event: %T", ev)
|
b.Log.Debugf("Unhandled incoming event: %T", ev)
|
||||||
@@ -123,11 +130,11 @@ func (b *Bslack) skipMessageEvent(ev *slack.MessageEvent) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip any messages that we made ourselves or from 'slackbot' (see #527).
|
// Check for our callback ID
|
||||||
if ev.Username == sSlackBotUser ||
|
hasOurCallbackID := false
|
||||||
(b.rtm != nil && ev.Username == b.si.User.Name) ||
|
if len(ev.Blocks.BlockSet) == 1 {
|
||||||
(len(ev.Attachments) > 0 && ev.Attachments[0].CallbackID == "matterbridge_"+b.uuid) {
|
block, ok := ev.Blocks.BlockSet[0].(*slack.SectionBlock)
|
||||||
return true
|
hasOurCallbackID = ok && block.BlockID == "matterbridge_"+b.uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
if ev.SubMessage != nil {
|
if ev.SubMessage != nil {
|
||||||
@@ -142,6 +149,16 @@ func (b *Bslack) skipMessageEvent(ev *slack.MessageEvent) bool {
|
|||||||
if ev.SubType == "message_replied" && ev.Hidden {
|
if ev.SubType == "message_replied" && ev.Hidden {
|
||||||
return true
|
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 {
|
if len(ev.Files) > 0 {
|
||||||
@@ -269,6 +286,9 @@ func (b *Bslack) handleAttachments(ev *slack.MessageEvent, rmsg *config.Message)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bslack) handleTypingEvent(ev *slack.UserTypingEvent) (*config.Message, error) {
|
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)
|
channelInfo, err := b.channels.getChannelByID(ev.Channel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/nlopes/slack"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/slack-go/slack"
|
||||||
)
|
)
|
||||||
|
|
||||||
// populateReceivedMessage shapes the initial Matterbridge message that we will forward to the
|
// populateReceivedMessage shapes the initial Matterbridge message that we will forward to the
|
||||||
@@ -188,6 +188,36 @@ func (b *Bslack) replaceURL(text string) string {
|
|||||||
return text
|
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 >
|
||||||
|
{
|
||||||
|
regexp.MustCompile(`(?sm)^>`),
|
||||||
|
">",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, rule := range regexReplaceAllString {
|
||||||
|
text = rule.regex.ReplaceAllString(text, rule.rpl)
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
func (b *Bslack) replaceCodeFence(text string) string {
|
func (b *Bslack) replaceCodeFence(text string) string {
|
||||||
return codeFenceRE.ReplaceAllString(text, "```")
|
return codeFenceRE.ReplaceAllString(text, "```")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/42wim/matterbridge/bridge"
|
"github.com/42wim/matterbridge/bridge"
|
||||||
"github.com/42wim/matterbridge/matterhook"
|
"github.com/42wim/matterbridge/matterhook"
|
||||||
"github.com/nlopes/slack"
|
"github.com/slack-go/slack"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BLegacy struct {
|
type BLegacy struct {
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ import (
|
|||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
"github.com/42wim/matterbridge/matterhook"
|
"github.com/42wim/matterbridge/matterhook"
|
||||||
"github.com/hashicorp/golang-lru"
|
lru "github.com/hashicorp/golang-lru"
|
||||||
"github.com/nlopes/slack"
|
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
|
"github.com/slack-go/slack"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Bslack struct {
|
type Bslack struct {
|
||||||
@@ -36,6 +36,7 @@ type Bslack struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
sHello = "hello"
|
||||||
sChannelJoin = "channel_join"
|
sChannelJoin = "channel_join"
|
||||||
sChannelLeave = "channel_leave"
|
sChannelLeave = "channel_leave"
|
||||||
sChannelJoined = "channel_joined"
|
sChannelJoined = "channel_joined"
|
||||||
@@ -63,6 +64,7 @@ const (
|
|||||||
editSuffixConfig = "EditSuffix"
|
editSuffixConfig = "EditSuffix"
|
||||||
iconURLConfig = "iconurl"
|
iconURLConfig = "iconurl"
|
||||||
noSendJoinConfig = "nosendjoinpart"
|
noSendJoinConfig = "nosendjoinpart"
|
||||||
|
messageLength = 3000
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(cfg *bridge.Config) bridge.Bridger {
|
func New(cfg *bridge.Config) bridge.Bridger {
|
||||||
@@ -193,6 +195,7 @@ func (b *Bslack) Send(msg config.Message) (string, error) {
|
|||||||
b.Log.Debugf("=> Receiving %#v", msg)
|
b.Log.Debugf("=> Receiving %#v", msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
msg.Text = helper.ClipMessage(msg.Text, messageLength)
|
||||||
msg.Text = b.replaceCodeFence(msg.Text)
|
msg.Text = b.replaceCodeFence(msg.Text)
|
||||||
|
|
||||||
// Make a action /me of the message
|
// Make a action /me of the message
|
||||||
@@ -201,7 +204,7 @@ func (b *Bslack) Send(msg config.Message) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Use webhook to send the message
|
// Use webhook to send the message
|
||||||
if b.GetString(outgoingWebhookConfig) != "" {
|
if b.GetString(outgoingWebhookConfig) != "" && b.GetString(tokenConfig) == "" {
|
||||||
return "", b.sendWebhook(msg)
|
return "", b.sendWebhook(msg)
|
||||||
}
|
}
|
||||||
return b.sendRTM(msg)
|
return b.sendRTM(msg)
|
||||||
@@ -407,7 +410,6 @@ func (b *Bslack) editMessage(msg *config.Message, channelInfo *slack.Channel) (b
|
|||||||
}
|
}
|
||||||
messageOptions := b.prepareMessageOptions(msg)
|
messageOptions := b.prepareMessageOptions(msg)
|
||||||
for {
|
for {
|
||||||
messageOptions = append(messageOptions, slack.MsgOptionText(msg.Text, false))
|
|
||||||
_, _, _, err := b.rtm.UpdateMessage(channelInfo.ID, msg.ID, messageOptions...)
|
_, _, _, err := b.rtm.UpdateMessage(channelInfo.ID, msg.ID, messageOptions...)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return true, nil
|
return true, nil
|
||||||
@@ -426,11 +428,6 @@ func (b *Bslack) postMessage(msg *config.Message, channelInfo *slack.Channel) (s
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
messageOptions := b.prepareMessageOptions(msg)
|
messageOptions := b.prepareMessageOptions(msg)
|
||||||
messageOptions = append(
|
|
||||||
messageOptions,
|
|
||||||
slack.MsgOptionText(msg.Text, false),
|
|
||||||
slack.MsgOptionEnableLinkUnfurl(),
|
|
||||||
)
|
|
||||||
for {
|
for {
|
||||||
_, id, err := b.rtm.PostMessage(channelInfo.ID, messageOptions...)
|
_, id, err := b.rtm.PostMessage(channelInfo.ID, messageOptions...)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -496,8 +493,6 @@ func (b *Bslack) prepareMessageOptions(msg *config.Message) []slack.MsgOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var attachments []slack.Attachment
|
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
|
// add file attachments
|
||||||
attachments = append(attachments, b.createAttach(msg.Extra)...)
|
attachments = append(attachments, b.createAttach(msg.Extra)...)
|
||||||
// add slack attachments (from another slack bridge)
|
// add slack attachments (from another slack bridge)
|
||||||
@@ -508,6 +503,19 @@ func (b *Bslack) prepareMessageOptions(msg *config.Message) []slack.MsgOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var opts []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.MsgOptionAttachments(attachments...))
|
||||||
opts = append(opts, slack.MsgOptionPostMessageParameters(params))
|
opts = append(opts, slack.MsgOptionPostMessageParameters(params))
|
||||||
return opts
|
return opts
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/nlopes/slack"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/slack-go/slack"
|
||||||
)
|
)
|
||||||
|
|
||||||
const minimumRefreshInterval = 10 * time.Second
|
const minimumRefreshInterval = 10 * time.Second
|
||||||
|
|||||||
@@ -130,6 +130,10 @@ func (b *Bsshchat) handleSSHChat() error {
|
|||||||
if strings.Contains(b.r.Text(), "Rate limiting is in effect") {
|
if strings.Contains(b.r.Text(), "Rate limiting is in effect") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// skip our own messages
|
||||||
|
if !strings.HasPrefix(b.r.Text(), "["+b.GetString("Nick")+"] \x1b") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
res := strings.Split(stripPrompt(b.r.Text()), ":")
|
res := strings.Split(stripPrompt(b.r.Text()), ":")
|
||||||
if res[0] == "-> Set theme" {
|
if res[0] == "-> Set theme" {
|
||||||
wait = false
|
wait = false
|
||||||
|
|||||||
@@ -39,22 +39,32 @@ func (b *Btelegram) handleGroups(rmsg *config.Message, message *tgbotapi.Message
|
|||||||
|
|
||||||
// handleForwarded handles forwarded messages
|
// handleForwarded handles forwarded messages
|
||||||
func (b *Btelegram) handleForwarded(rmsg *config.Message, message *tgbotapi.Message) {
|
func (b *Btelegram) handleForwarded(rmsg *config.Message, message *tgbotapi.Message) {
|
||||||
if message.ForwardFrom != nil {
|
if message.ForwardDate == 0 {
|
||||||
usernameForward := ""
|
return
|
||||||
if b.GetBool("UseFirstName") {
|
}
|
||||||
|
|
||||||
|
if message.ForwardFrom == nil {
|
||||||
|
rmsg.Text = "Forwarded from " + unknownUser + ": " + rmsg.Text
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
usernameForward := ""
|
||||||
|
if b.GetBool("UseFirstName") {
|
||||||
|
usernameForward = message.ForwardFrom.FirstName
|
||||||
|
}
|
||||||
|
|
||||||
|
if usernameForward == "" {
|
||||||
|
usernameForward = message.ForwardFrom.UserName
|
||||||
|
if usernameForward == "" {
|
||||||
usernameForward = message.ForwardFrom.FirstName
|
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if usernameForward == "" {
|
||||||
|
usernameForward = unknownUser
|
||||||
|
}
|
||||||
|
|
||||||
|
rmsg.Text = "Forwarded from " + usernameForward + ": " + rmsg.Text
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleQuoting handles quoting of previous messages
|
// handleQuoting handles quoting of previous messages
|
||||||
@@ -95,7 +105,7 @@ func (b *Btelegram) handleUsername(rmsg *config.Message, message *tgbotapi.Messa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// only download avatars if we have a place to upload them (configured mediaserver)
|
// only download avatars if we have a place to upload them (configured mediaserver)
|
||||||
if b.General.MediaServerUpload != "" {
|
if b.General.MediaServerUpload != "" || (b.General.MediaServerDownload != "" && b.General.MediaDownloadPath != "") {
|
||||||
b.handleDownloadAvatar(message.From.ID, rmsg.Channel)
|
b.handleDownloadAvatar(message.From.ID, rmsg.Channel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,6 +217,46 @@ 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
|
// handleDownloadFile handles file download
|
||||||
func (b *Btelegram) handleDownload(rmsg *config.Message, message *tgbotapi.Message) error {
|
func (b *Btelegram) handleDownload(rmsg *config.Message, message *tgbotapi.Message) error {
|
||||||
size := 0
|
size := 0
|
||||||
@@ -254,15 +304,13 @@ func (b *Btelegram) handleDownload(rmsg *config.Message, message *tgbotapi.Messa
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if strings.HasSuffix(name, ".webp") && b.GetBool("MediaConvertWebPToPNG") {
|
|
||||||
b.Log.Debugf("WebP to PNG conversion enabled, converting %s", name)
|
if strings.HasSuffix(name, ".tgs.webp") {
|
||||||
err := helper.ConvertWebPToPNG(data)
|
b.maybeConvertTgs(&name, data)
|
||||||
if err != nil {
|
} else if strings.HasSuffix(name, ".webp") {
|
||||||
b.Log.Errorf("conversion failed: %s", err)
|
b.maybeConvertWebp(&name, data)
|
||||||
} else {
|
|
||||||
name = strings.Replace(name, ".webp", ".png", 1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
helper.HandleDownloadData(b.Log, rmsg, name, message.Caption, "", data, b.General)
|
helper.HandleDownloadData(b.Log, rmsg, name, message.Caption, "", data, b.General)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -312,6 +360,9 @@ func (b *Btelegram) handleEdit(msg *config.Message, chatid int64) (string, error
|
|||||||
case "Markdown":
|
case "Markdown":
|
||||||
b.Log.Debug("Using mode markdown")
|
b.Log.Debug("Using mode markdown")
|
||||||
m.ParseMode = tgbotapi.ModeMarkdown
|
m.ParseMode = tgbotapi.ModeMarkdown
|
||||||
|
case MarkdownV2:
|
||||||
|
b.Log.Debug("Using mode MarkdownV2")
|
||||||
|
m.ParseMode = MarkdownV2
|
||||||
}
|
}
|
||||||
if strings.ToLower(b.GetString("MessageFormat")) == HTMLNick {
|
if strings.ToLower(b.GetString("MessageFormat")) == HTMLNick {
|
||||||
b.Log.Debug("Using mode HTML - nick only")
|
b.Log.Debug("Using mode HTML - nick only")
|
||||||
@@ -357,6 +408,14 @@ func (b *Btelegram) handleQuote(message, quoteNick, quoteMessage string) string
|
|||||||
if format == "" {
|
if format == "" {
|
||||||
format = "{MESSAGE} (re @{QUOTENICK}: {QUOTEMESSAGE})"
|
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, "{MESSAGE}", message, -1)
|
||||||
format = strings.Replace(format, "{QUOTENICK}", quoteNick, -1)
|
format = strings.Replace(format, "{QUOTENICK}", quoteNick, -1)
|
||||||
format = strings.Replace(format, "{QUOTEMESSAGE}", quoteMessage, -1)
|
format = strings.Replace(format, "{QUOTEMESSAGE}", quoteMessage, -1)
|
||||||
|
|||||||
@@ -2,19 +2,23 @@ package btelegram
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"html"
|
"html"
|
||||||
|
"log"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/42wim/matterbridge/bridge"
|
"github.com/42wim/matterbridge/bridge"
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
"github.com/go-telegram-bot-api/telegram-bot-api"
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
unknownUser = "unknown"
|
unknownUser = "unknown"
|
||||||
HTMLFormat = "HTML"
|
HTMLFormat = "HTML"
|
||||||
HTMLNick = "htmlnick"
|
HTMLNick = "htmlnick"
|
||||||
|
MarkdownV2 = "MarkdownV2"
|
||||||
|
FormatPng = "png"
|
||||||
|
FormatWebp = "webp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Btelegram struct {
|
type Btelegram struct {
|
||||||
@@ -24,6 +28,16 @@ type Btelegram struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func New(cfg *bridge.Config) bridge.Bridger {
|
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)}
|
return &Btelegram{Config: cfg, avatarMap: make(map[string]string)}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,8 +95,8 @@ func (b *Btelegram) Send(msg config.Message) (string, error) {
|
|||||||
// Upload a file if it exists
|
// Upload a file if it exists
|
||||||
if msg.Extra != nil {
|
if msg.Extra != nil {
|
||||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||||
if _, err := b.sendMessage(chatid, rmsg.Username, rmsg.Text); err != nil {
|
if _, msgErr := b.sendMessage(chatid, rmsg.Username, rmsg.Text); msgErr != nil {
|
||||||
b.Log.Errorf("sendMessage failed: %s", err)
|
b.Log.Errorf("sendMessage failed: %s", msgErr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// check if we have files to upload (from slack, telegram or mattermost)
|
// check if we have files to upload (from slack, telegram or mattermost)
|
||||||
@@ -97,7 +111,14 @@ func (b *Btelegram) Send(msg config.Message) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Post normal message
|
// Post normal message
|
||||||
return b.sendMessage(chatid, msg.Username, msg.Text)
|
// TODO: recheck it.
|
||||||
|
// Ignore empty text field needs for prevent double messages from whatsapp to telegram
|
||||||
|
// when sending media with text caption
|
||||||
|
if msg.Text != "" {
|
||||||
|
return b.sendMessage(chatid, msg.Username, msg.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Btelegram) getFileDirectURL(id string) string {
|
func (b *Btelegram) getFileDirectURL(id string) string {
|
||||||
@@ -119,11 +140,18 @@ func (b *Btelegram) sendMessage(chatid int64, username, text string) (string, er
|
|||||||
b.Log.Debug("Using mode markdown")
|
b.Log.Debug("Using mode markdown")
|
||||||
m.ParseMode = tgbotapi.ModeMarkdown
|
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 {
|
if strings.ToLower(b.GetString("MessageFormat")) == HTMLNick {
|
||||||
b.Log.Debug("Using mode HTML - nick only")
|
b.Log.Debug("Using mode HTML - nick only")
|
||||||
m.Text = username + html.EscapeString(text)
|
m.Text = username + html.EscapeString(text)
|
||||||
m.ParseMode = tgbotapi.ModeHTML
|
m.ParseMode = tgbotapi.ModeHTML
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m.DisableWebPagePreview = b.GetBool("DisableWebPagePreview")
|
||||||
|
|
||||||
res, err := b.c.Send(m)
|
res, err := b.c.Send(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
package bwhatsapp
|
package bwhatsapp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"mime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
"github.com/Rhymen/go-whatsapp"
|
"github.com/Rhymen/go-whatsapp"
|
||||||
|
"github.com/jpillora/backoff"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -19,10 +23,43 @@ Check:
|
|||||||
// HandleError received from WhatsApp
|
// HandleError received from WhatsApp
|
||||||
func (b *Bwhatsapp) HandleError(err error) {
|
func (b *Bwhatsapp) HandleError(err error) {
|
||||||
// ignore received invalid data errors. https://github.com/42wim/matterbridge/issues/843
|
// ignore received invalid data errors. https://github.com/42wim/matterbridge/issues/843
|
||||||
if strings.Contains(err.Error(), "error processing data: received invalid data") {
|
// ignore tag 174 errors. https://github.com/42wim/matterbridge/issues/1094
|
||||||
|
if strings.Contains(err.Error(), "error processing data: received invalid data") || strings.Contains(err.Error(), "invalid string with tag 174") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
b.Log.Errorf("%v", err) // TODO implement proper handling? at least respond to different error types
|
|
||||||
|
switch err.(type) {
|
||||||
|
case *whatsapp.ErrConnectionClosed, *whatsapp.ErrConnectionFailed:
|
||||||
|
b.reconnect(err)
|
||||||
|
default:
|
||||||
|
switch err {
|
||||||
|
case whatsapp.ErrConnectionTimeout:
|
||||||
|
b.reconnect(err)
|
||||||
|
default:
|
||||||
|
b.Log.Errorf("%v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bwhatsapp) reconnect(err error) {
|
||||||
|
bf := &backoff.Backoff{
|
||||||
|
Min: time.Second,
|
||||||
|
Max: 5 * time.Minute,
|
||||||
|
Jitter: true,
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
d := bf.Duration()
|
||||||
|
b.Log.Errorf("Connection failed, underlying error: %v", err)
|
||||||
|
b.Log.Infof("Waiting %s...", d)
|
||||||
|
time.Sleep(d)
|
||||||
|
b.Log.Info("Reconnecting...")
|
||||||
|
err := b.conn.Restore()
|
||||||
|
if err == nil {
|
||||||
|
bf.Reset()
|
||||||
|
b.startedAt = uint64(time.Now().Unix())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleTextMessage sent from WhatsApp, relay it to the brige
|
// HandleTextMessage sent from WhatsApp, relay it to the brige
|
||||||
@@ -36,16 +73,18 @@ func (b *Bwhatsapp) HandleTextMessage(message whatsapp.TextMessage) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
messageTime := time.Unix(int64(message.Info.Timestamp), 0) // TODO check how behaves between timezones
|
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
|
senderJID := message.Info.SenderJid
|
||||||
if len(senderJid) == 0 {
|
if len(senderJID) == 0 {
|
||||||
// TODO workaround till https://github.com/Rhymen/go-whatsapp/issues/86 resolved
|
// TODO workaround till https://github.com/Rhymen/go-whatsapp/issues/86 resolved
|
||||||
senderJid = *message.Info.Source.Participant
|
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
|
// translate sender's JID to the nicest username we can get
|
||||||
senderName := b.getSenderName(senderJid)
|
senderName := b.getSenderName(senderJID)
|
||||||
if senderName == "" {
|
if senderName == "" {
|
||||||
senderName = "Someone" // don't expose telephone number
|
senderName = "Someone" // don't expose telephone number
|
||||||
}
|
}
|
||||||
@@ -53,8 +92,8 @@ func (b *Bwhatsapp) HandleTextMessage(message whatsapp.TextMessage) {
|
|||||||
extText := message.Info.Source.Message.ExtendedTextMessage
|
extText := message.Info.Source.Message.ExtendedTextMessage
|
||||||
if extText != nil && extText.ContextInfo != nil && extText.ContextInfo.MentionedJid != nil {
|
if extText != nil && extText.ContextInfo != nil && extText.ContextInfo.MentionedJid != nil {
|
||||||
// handle user mentions
|
// handle user mentions
|
||||||
for _, mentionedJid := range extText.ContextInfo.MentionedJid {
|
for _, mentionedJID := range extText.ContextInfo.MentionedJid {
|
||||||
numberAndSuffix := strings.SplitN(mentionedJid, "@", 2)
|
numberAndSuffix := strings.SplitN(mentionedJID, "@", 2)
|
||||||
|
|
||||||
// mentions comes as telephone numbers and we don't want to expose it to other bridges
|
// mentions comes as telephone numbers and we don't want to expose it to other bridges
|
||||||
// replace it with something more meaninful to others
|
// replace it with something more meaninful to others
|
||||||
@@ -66,22 +105,22 @@ func (b *Bwhatsapp) HandleTextMessage(message whatsapp.TextMessage) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJid, b.Account)
|
b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account)
|
||||||
rmsg := config.Message{
|
rmsg := config.Message{
|
||||||
UserID: senderJid,
|
UserID: senderJID,
|
||||||
Username: senderName,
|
Username: senderName,
|
||||||
Text: message.Text,
|
Text: message.Text,
|
||||||
Timestamp: messageTime,
|
Timestamp: messageTime,
|
||||||
Channel: groupJid,
|
Channel: groupJID,
|
||||||
Account: b.Account,
|
Account: b.Account,
|
||||||
Protocol: b.Protocol,
|
Protocol: b.Protocol,
|
||||||
Extra: make(map[string][]interface{}),
|
Extra: make(map[string][]interface{}),
|
||||||
// ParentID: TODO, // TODO handle thread replies // map from Info.QuotedMessageID string
|
// ParentID: TODO, // TODO handle thread replies // map from Info.QuotedMessageID string
|
||||||
// Event string `json:"event"`
|
// Event string `json:"event"`
|
||||||
// Gateway string // will be added during message processing
|
// Gateway string // will be added during message processing
|
||||||
ID: message.Info.Id}
|
ID: message.Info.Id}
|
||||||
|
|
||||||
if avatarURL, exists := b.userAvatars[senderJid]; exists {
|
if avatarURL, exists := b.userAvatars[senderJID]; exists {
|
||||||
rmsg.Avatar = avatarURL
|
rmsg.Avatar = avatarURL
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,11 +128,77 @@ func (b *Bwhatsapp) HandleTextMessage(message whatsapp.TextMessage) {
|
|||||||
b.Remote <- rmsg
|
b.Remote <- rmsg
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
// HandleImageMessage sent from WhatsApp, relay it to the brige
|
||||||
//func (b *Bwhatsapp) HandleImageMessage(message whatsapp.ImageMessage) {
|
func (b *Bwhatsapp) HandleImageMessage(message whatsapp.ImageMessage) {
|
||||||
// fmt.Println(message) // TODO implement
|
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) HandleVideoMessage(message whatsapp.VideoMessage) {
|
//func (b *Bwhatsapp) HandleVideoMessage(message whatsapp.VideoMessage) {
|
||||||
// fmt.Println(message) // TODO implement
|
// fmt.Println(message) // TODO implement
|
||||||
//}
|
//}
|
||||||
|
|||||||
@@ -80,8 +80,33 @@ func (b *Bwhatsapp) getSenderName(senderJid string) string {
|
|||||||
// if user is not in phone contacts
|
// if user is not in phone contacts
|
||||||
// it is the most obvious scenario unless you sync your phone contacts with some remote updated source
|
// 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
|
// users can change it in their WhatsApp settings -> profile -> click on Avatar
|
||||||
return sender.Notify
|
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 ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package bwhatsapp
|
package bwhatsapp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"mime"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -180,7 +183,7 @@ func (b *Bwhatsapp) Disconnect() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isGroupJid(identifier string) bool {
|
func isGroupJid(identifier string) bool {
|
||||||
return strings.HasSuffix(identifier, "@g.us") || strings.HasSuffix(identifier, "@temp")
|
return strings.HasSuffix(identifier, "@g.us") || strings.HasSuffix(identifier, "@temp") || strings.HasSuffix(identifier, "@broadcast")
|
||||||
}
|
}
|
||||||
|
|
||||||
// JoinChannel Join a WhatsApp group specified in gateway config as channel='number-id@g.us' or channel='Channel name'
|
// JoinChannel Join a WhatsApp group specified in gateway config as channel='number-id@g.us' or channel='Channel name'
|
||||||
@@ -230,6 +233,66 @@ func (b *Bwhatsapp) JoinChannel(channel config.ChannelInfo) error {
|
|||||||
return nil
|
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
|
// Send a message from the bridge to WhatsApp
|
||||||
// Required implementation of the Bridger interface
|
// Required implementation of the Bridger interface
|
||||||
// https://github.com/42wim/matterbridge/blob/2cfd880cdb0df29771bf8f31df8d990ab897889d/bridge/bridge.go#L11-L16
|
// https://github.com/42wim/matterbridge/blob/2cfd880cdb0df29771bf8f31df8d990ab897889d/bridge/bridge.go#L11-L16
|
||||||
@@ -259,18 +322,25 @@ func (b *Bwhatsapp) Send(msg config.Message) (string, error) {
|
|||||||
// TODO handle edit as a message reply with updated text
|
// TODO handle edit as a message reply with updated text
|
||||||
}
|
}
|
||||||
|
|
||||||
//// TODO Handle Upload a file
|
// Handle Upload a file
|
||||||
//if msg.Extra != nil {
|
if msg.Extra["file"] != nil {
|
||||||
// for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
fi := msg.Extra["file"][0].(config.FileInfo)
|
||||||
// b.c.SendMessage(roomID, rmsg.Username+rmsg.Text)
|
filetype := mime.TypeByExtension(filepath.Ext(fi.Name))
|
||||||
// }
|
|
||||||
// if len(msg.Extra["file"]) > 0 {
|
b.Log.Debugf("Extra file is %#v", filetype)
|
||||||
// return b.handleUploadFile(&msg, roomID)
|
|
||||||
// }
|
// TODO: add different types
|
||||||
//}
|
// TODO: add webp conversion
|
||||||
|
switch filetype {
|
||||||
|
case "image/jpeg", "image/png", "image/gif":
|
||||||
|
return b.PostImageMessage(msg, filetype)
|
||||||
|
default:
|
||||||
|
return b.PostDocumentMessage(msg, filetype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Post text message
|
// Post text message
|
||||||
text := whatsapp.TextMessage{
|
message := whatsapp.TextMessage{
|
||||||
Info: whatsapp.MessageInfo{
|
Info: whatsapp.MessageInfo{
|
||||||
RemoteJid: msg.Channel, // which equals to group id
|
RemoteJid: msg.Channel, // which equals to group id
|
||||||
},
|
},
|
||||||
@@ -281,15 +351,14 @@ func (b *Bwhatsapp) Send(msg config.Message) (string, error) {
|
|||||||
|
|
||||||
// create message ID
|
// create message ID
|
||||||
// TODO follow and act if https://github.com/Rhymen/go-whatsapp/issues/101 implemented
|
// TODO follow and act if https://github.com/Rhymen/go-whatsapp/issues/101 implemented
|
||||||
bytes := make([]byte, 10)
|
idBytes := make([]byte, 10)
|
||||||
if _, err := rand.Read(bytes); err != nil {
|
if _, err := rand.Read(idBytes); err != nil {
|
||||||
b.Log.Warn(err.Error())
|
b.Log.Warn(err.Error())
|
||||||
}
|
}
|
||||||
text.Info.Id = strings.ToUpper(hex.EncodeToString(bytes))
|
message.Info.Id = strings.ToUpper(hex.EncodeToString(idBytes))
|
||||||
|
_, err := b.conn.Send(message)
|
||||||
|
|
||||||
_, err := b.conn.Send(text)
|
return message.Info.Id, err
|
||||||
|
|
||||||
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
|
// 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
|
||||||
|
|||||||
34
bridge/xmpp/handler.go
Normal file
34
bridge/xmpp/handler.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package bxmpp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
|
"github.com/matterbridge/go-xmpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// handleDownloadAvatar downloads the avatar of userid from channel
|
||||||
|
// sends a EVENT_AVATAR_DOWNLOAD message to the gateway if successful.
|
||||||
|
// logs an error message if it fails
|
||||||
|
func (b *Bxmpp) handleDownloadAvatar(avatar xmpp.AvatarData) {
|
||||||
|
rmsg := config.Message{
|
||||||
|
Username: "system",
|
||||||
|
Text: "avatar",
|
||||||
|
Channel: b.parseChannel(avatar.From),
|
||||||
|
Account: b.Account,
|
||||||
|
UserID: avatar.From,
|
||||||
|
Event: config.EventAvatarDownload,
|
||||||
|
Extra: make(map[string][]interface{}),
|
||||||
|
}
|
||||||
|
if _, ok := b.avatarMap[avatar.From]; !ok {
|
||||||
|
b.Log.Debugf("Avatar.From: %s", avatar.From)
|
||||||
|
|
||||||
|
err := helper.HandleDownloadSize(b.Log, &rmsg, avatar.From+".png", int64(len(avatar.Data)), b.General)
|
||||||
|
if err != nil {
|
||||||
|
b.Log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.HandleDownloadData(b.Log, &rmsg, avatar.From+".png", rmsg.Text, "", &avatar.Data, b.General)
|
||||||
|
b.Log.Debugf("Avatar download complete")
|
||||||
|
b.Remote <- rmsg
|
||||||
|
}
|
||||||
|
}
|
||||||
30
bridge/xmpp/helpers.go
Normal file
30
bridge/xmpp/helpers.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package bxmpp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
var pathRegex = regexp.MustCompile("[^a-zA-Z0-9]+")
|
||||||
|
|
||||||
|
// GetAvatar constructs a URL for a given user-avatar if it is available in the cache.
|
||||||
|
func getAvatar(av map[string]string, userid string, general *config.Protocol) string {
|
||||||
|
if hash, ok := av[userid]; ok {
|
||||||
|
// NOTE: This does not happen in bridge/helper/helper.go but messes up XMPP
|
||||||
|
id := pathRegex.ReplaceAllString(userid, "_")
|
||||||
|
return general.MediaServerDownload + "/" + hash + "/" + id + ".png"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bxmpp) cacheAvatar(msg *config.Message) string {
|
||||||
|
fi := msg.Extra["file"][0].(config.FileInfo)
|
||||||
|
/* if we have a sha we have successfully uploaded the file to the media server,
|
||||||
|
so we can now cache the sha */
|
||||||
|
if fi.SHA != "" {
|
||||||
|
b.Log.Debugf("Added %s to %s in avatarMap", fi.SHA, msg.UserID)
|
||||||
|
b.avatarMap[msg.UserID] = fi.SHA
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
@@ -23,12 +23,17 @@ type Bxmpp struct {
|
|||||||
xmppMap map[string]string
|
xmppMap map[string]string
|
||||||
connected bool
|
connected bool
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
|
|
||||||
|
avatarAvailability map[string]bool
|
||||||
|
avatarMap map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(cfg *bridge.Config) bridge.Bridger {
|
func New(cfg *bridge.Config) bridge.Bridger {
|
||||||
return &Bxmpp{
|
return &Bxmpp{
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
xmppMap: make(map[string]string),
|
xmppMap: make(map[string]string),
|
||||||
|
avatarAvailability: make(map[string]bool),
|
||||||
|
avatarMap: make(map[string]string),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,8 +72,19 @@ func (b *Bxmpp) Send(msg config.Message) (string, error) {
|
|||||||
if msg.Event == config.EventMsgDelete {
|
if msg.Event == config.EventMsgDelete {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Log.Debugf("=> Receiving %#v", msg)
|
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).
|
// Upload a file (in XMPP case send the upload URL because XMPP has no native upload support).
|
||||||
if msg.Extra != nil {
|
if msg.Extra != nil {
|
||||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||||
@@ -114,20 +130,22 @@ func (b *Bxmpp) createXMPP() error {
|
|||||||
ServerName: strings.Split(b.GetString("Jid"), "@")[1],
|
ServerName: strings.Split(b.GetString("Jid"), "@")[1],
|
||||||
InsecureSkipVerify: b.GetBool("SkipTLSVerify"), // nolint: gosec
|
InsecureSkipVerify: b.GetBool("SkipTLSVerify"), // nolint: gosec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
xmpp.DebugWriter = b.Log.Writer()
|
||||||
|
|
||||||
options := xmpp.Options{
|
options := xmpp.Options{
|
||||||
Host: b.GetString("Server"),
|
Host: b.GetString("Server"),
|
||||||
User: b.GetString("Jid"),
|
User: b.GetString("Jid"),
|
||||||
Password: b.GetString("Password"),
|
Password: b.GetString("Password"),
|
||||||
NoTLS: true,
|
NoTLS: true,
|
||||||
StartTLS: true,
|
StartTLS: !b.GetBool("NoTLS"),
|
||||||
TLSConfig: tc,
|
TLSConfig: tc,
|
||||||
Debug: b.GetBool("debug"),
|
Debug: b.GetBool("debug"),
|
||||||
Logger: b.Log.Writer(),
|
|
||||||
Session: true,
|
Session: true,
|
||||||
Status: "",
|
Status: "",
|
||||||
StatusMessage: "",
|
StatusMessage: "",
|
||||||
Resource: "",
|
Resource: "",
|
||||||
InsecureAllowUnencryptedAuth: false,
|
InsecureAllowUnencryptedAuth: b.GetBool("NoTLS"),
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
b.xc, err = options.NewClient()
|
b.xc, err = options.NewClient()
|
||||||
@@ -228,6 +246,16 @@ func (b *Bxmpp) handleXMPP() error {
|
|||||||
event = config.EventTopicChange
|
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
|
msgID := v.ID
|
||||||
if v.ReplaceID != "" {
|
if v.ReplaceID != "" {
|
||||||
msgID = v.ReplaceID
|
msgID = v.ReplaceID
|
||||||
@@ -237,6 +265,7 @@ func (b *Bxmpp) handleXMPP() error {
|
|||||||
Text: v.Text,
|
Text: v.Text,
|
||||||
Channel: b.parseChannel(v.Remote),
|
Channel: b.parseChannel(v.Remote),
|
||||||
Account: b.Account,
|
Account: b.Account,
|
||||||
|
Avatar: avatar,
|
||||||
UserID: v.Remote,
|
UserID: v.Remote,
|
||||||
ID: msgID,
|
ID: msgID,
|
||||||
Event: event,
|
Event: event,
|
||||||
@@ -253,6 +282,10 @@ func (b *Bxmpp) handleXMPP() error {
|
|||||||
b.Log.Debugf("<= Message is %#v", rmsg)
|
b.Log.Debugf("<= Message is %#v", rmsg)
|
||||||
b.Remote <- 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:
|
case xmpp.Presence:
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,19 +135,25 @@ func (b *Bzulip) handleQueue() error {
|
|||||||
if m.SenderEmail == b.GetString("login") {
|
if m.SenderEmail == b.GetString("login") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
avatarURL := m.AvatarURL
|
||||||
|
if !strings.HasPrefix(avatarURL, "http") {
|
||||||
|
avatarURL = b.GetString("server") + avatarURL
|
||||||
|
}
|
||||||
|
|
||||||
rmsg := config.Message{
|
rmsg := config.Message{
|
||||||
Username: m.SenderFullName,
|
Username: m.SenderFullName,
|
||||||
Text: m.Content,
|
Text: m.Content,
|
||||||
Channel: b.getChannel(m.StreamID) + "/topic:" + m.Subject,
|
Channel: b.getChannel(m.StreamID) + "/topic:" + m.Subject,
|
||||||
Account: b.Account,
|
Account: b.Account,
|
||||||
UserID: strconv.Itoa(m.SenderID),
|
UserID: strconv.Itoa(m.SenderID),
|
||||||
Avatar: m.AvatarURL,
|
Avatar: avatarURL,
|
||||||
}
|
}
|
||||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account)
|
b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account)
|
||||||
b.Log.Debugf("<= Message is %#v", rmsg)
|
b.Log.Debugf("<= Message is %#v", rmsg)
|
||||||
b.Remote <- rmsg
|
b.Remote <- rmsg
|
||||||
b.q.LastEventID = m.ID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(time.Second * 3)
|
time.Sleep(time.Second * 3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
324
changelog.md
324
changelog.md
@@ -1,3 +1,324 @@
|
|||||||
|
# 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
|
# v1.16.0
|
||||||
|
|
||||||
## New features
|
## New features
|
||||||
@@ -15,6 +336,9 @@
|
|||||||
* xmpp: Fix possible panic at startup of the XMPP bridge #869
|
* xmpp: Fix possible panic at startup of the XMPP bridge #869
|
||||||
* mattermost: Make getChannelIdTeam behave like GetChannelId for groups (mattermost) #873
|
* mattermost: Make getChannelIdTeam behave like GetChannelId for groups (mattermost) #873
|
||||||
|
|
||||||
|
This release couldn't exist without the following contributors:
|
||||||
|
@hyperobject, @42wim, @bucko909, @MOZGIII
|
||||||
|
|
||||||
# v1.15.1
|
# v1.15.1
|
||||||
|
|
||||||
## New features
|
## New features
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
#!/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
17
ci/lint.sh
@@ -1,17 +0,0 @@
|
|||||||
#!/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
17
ci/test.sh
@@ -1,17 +0,0 @@
|
|||||||
#!/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
|
|
||||||
10
contrib/outmessage-discordemoji.tengo
Normal file
10
contrib/outmessage-discordemoji.tengo
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
text := import("text")
|
||||||
|
|
||||||
|
// if we're not sending to a discord bridge,
|
||||||
|
// then convert custom emoji tags into url's
|
||||||
|
if (inProtocol == "discord" && outProtocol != "discord") {
|
||||||
|
rePNG := text.re_compile(`<:.*?:([0-9]+)>`)
|
||||||
|
msgText=rePNG.replace(msgText,"https://cdn.discordapp.com/emojis/$1.png")
|
||||||
|
reGIF := text.re_compile(`<a:.*?:([0-9]+)>`)
|
||||||
|
msgText=reGIF.replace(msgText,"https://cdn.discordapp.com/emojis/$1.gif")
|
||||||
|
}
|
||||||
11
gateway/bridgemap/api.go
Normal file
11
gateway/bridgemap/api.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// +build !noapi
|
||||||
|
|
||||||
|
package bridgemap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/42wim/matterbridge/bridge/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
FullMap["api"] = api.New
|
||||||
|
}
|
||||||
12
gateway/bridgemap/bdiscord.go
Normal file
12
gateway/bridgemap/bdiscord.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// +build !nodiscord
|
||||||
|
|
||||||
|
package bridgemap
|
||||||
|
|
||||||
|
import (
|
||||||
|
bdiscord "github.com/42wim/matterbridge/bridge/discord"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
FullMap["discord"] = bdiscord.New
|
||||||
|
UserTypingSupport["discord"] = struct{}{}
|
||||||
|
}
|
||||||
11
gateway/bridgemap/bgitter.go
Normal file
11
gateway/bridgemap/bgitter.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// +build !nogitter
|
||||||
|
|
||||||
|
package bridgemap
|
||||||
|
|
||||||
|
import (
|
||||||
|
bgitter "github.com/42wim/matterbridge/bridge/gitter"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
FullMap["gitter"] = bgitter.New
|
||||||
|
}
|
||||||
11
gateway/bridgemap/birc.go
Normal file
11
gateway/bridgemap/birc.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// +build !noirc
|
||||||
|
|
||||||
|
package bridgemap
|
||||||
|
|
||||||
|
import (
|
||||||
|
birc "github.com/42wim/matterbridge/bridge/irc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
FullMap["irc"] = birc.New
|
||||||
|
}
|
||||||
11
gateway/bridgemap/bkeybase.go
Normal file
11
gateway/bridgemap/bkeybase.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// +build !nokeybase
|
||||||
|
|
||||||
|
package bridgemap
|
||||||
|
|
||||||
|
import (
|
||||||
|
bkeybase "github.com/42wim/matterbridge/bridge/keybase"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
FullMap["keybase"] = bkeybase.New
|
||||||
|
}
|
||||||
11
gateway/bridgemap/bmatrix.go
Normal file
11
gateway/bridgemap/bmatrix.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// +build !nomatrix
|
||||||
|
|
||||||
|
package bridgemap
|
||||||
|
|
||||||
|
import (
|
||||||
|
bmatrix "github.com/42wim/matterbridge/bridge/matrix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
FullMap["matrix"] = bmatrix.New
|
||||||
|
}
|
||||||
11
gateway/bridgemap/bmattermost.go
Normal file
11
gateway/bridgemap/bmattermost.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// +build !nomattermost
|
||||||
|
|
||||||
|
package bridgemap
|
||||||
|
|
||||||
|
import (
|
||||||
|
bmattermost "github.com/42wim/matterbridge/bridge/mattermost"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
FullMap["mattermost"] = bmattermost.New
|
||||||
|
}
|
||||||
11
gateway/bridgemap/bmsteams.go
Normal file
11
gateway/bridgemap/bmsteams.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// +build !nomsteams
|
||||||
|
|
||||||
|
package bridgemap
|
||||||
|
|
||||||
|
import (
|
||||||
|
bmsteams "github.com/42wim/matterbridge/bridge/msteams"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
FullMap["msteams"] = bmsteams.New
|
||||||
|
}
|
||||||
11
gateway/bridgemap/bmumble.go
Normal file
11
gateway/bridgemap/bmumble.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// +build !nomumble
|
||||||
|
|
||||||
|
package bridgemap
|
||||||
|
|
||||||
|
import (
|
||||||
|
bmumble "github.com/42wim/matterbridge/bridge/mumble"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
FullMap["mumble"] = bmumble.New
|
||||||
|
}
|
||||||
11
gateway/bridgemap/bnctalk.go
Normal file
11
gateway/bridgemap/bnctalk.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// +build !nonctalk
|
||||||
|
|
||||||
|
package bridgemap
|
||||||
|
|
||||||
|
import (
|
||||||
|
btalk "github.com/42wim/matterbridge/bridge/nctalk"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
FullMap["nctalk"] = btalk.New
|
||||||
|
}
|
||||||
@@ -2,44 +2,9 @@ package bridgemap
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/42wim/matterbridge/bridge"
|
"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"
|
|
||||||
bkeybase "github.com/42wim/matterbridge/bridge/keybase"
|
|
||||||
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 (
|
var (
|
||||||
FullMap = map[string]bridge.Factory{
|
FullMap = map[string]bridge.Factory{}
|
||||||
"api": api.New,
|
UserTypingSupport = map[string]struct{}{}
|
||||||
"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,
|
|
||||||
"keybase": bkeybase.New,
|
|
||||||
}
|
|
||||||
|
|
||||||
UserTypingSupport = map[string]struct{}{
|
|
||||||
"slack": {},
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|||||||
11
gateway/bridgemap/brocketchat.go
Normal file
11
gateway/bridgemap/brocketchat.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// +build !norocketchat
|
||||||
|
|
||||||
|
package bridgemap
|
||||||
|
|
||||||
|
import (
|
||||||
|
brocketchat "github.com/42wim/matterbridge/bridge/rocketchat"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
FullMap["rocketchat"] = brocketchat.New
|
||||||
|
}
|
||||||
13
gateway/bridgemap/bslack.go
Normal file
13
gateway/bridgemap/bslack.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// +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{}{}
|
||||||
|
}
|
||||||
11
gateway/bridgemap/bsshchat.go
Normal file
11
gateway/bridgemap/bsshchat.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// +build !nosshchat
|
||||||
|
|
||||||
|
package bridgemap
|
||||||
|
|
||||||
|
import (
|
||||||
|
bsshchat "github.com/42wim/matterbridge/bridge/sshchat"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
FullMap["sshchat"] = bsshchat.New
|
||||||
|
}
|
||||||
11
gateway/bridgemap/bsteam.go
Normal file
11
gateway/bridgemap/bsteam.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// +build !nosteam
|
||||||
|
|
||||||
|
package bridgemap
|
||||||
|
|
||||||
|
import (
|
||||||
|
bsteam "github.com/42wim/matterbridge/bridge/steam"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
FullMap["steam"] = bsteam.New
|
||||||
|
}
|
||||||
11
gateway/bridgemap/btelegram.go
Normal file
11
gateway/bridgemap/btelegram.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// +build !notelegram
|
||||||
|
|
||||||
|
package bridgemap
|
||||||
|
|
||||||
|
import (
|
||||||
|
btelegram "github.com/42wim/matterbridge/bridge/telegram"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
FullMap["telegram"] = btelegram.New
|
||||||
|
}
|
||||||
11
gateway/bridgemap/bwhatsapp.go
Normal file
11
gateway/bridgemap/bwhatsapp.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// +build !nowhatsapp
|
||||||
|
|
||||||
|
package bridgemap
|
||||||
|
|
||||||
|
import (
|
||||||
|
bwhatsapp "github.com/42wim/matterbridge/bridge/whatsapp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
FullMap["whatsapp"] = bwhatsapp.New
|
||||||
|
}
|
||||||
11
gateway/bridgemap/bxmpp.go
Normal file
11
gateway/bridgemap/bxmpp.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// +build !noxmpp
|
||||||
|
|
||||||
|
package bridgemap
|
||||||
|
|
||||||
|
import (
|
||||||
|
bxmpp "github.com/42wim/matterbridge/bridge/xmpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
FullMap["xmpp"] = bxmpp.New
|
||||||
|
}
|
||||||
11
gateway/bridgemap/bzulip.go
Normal file
11
gateway/bridgemap/bzulip.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// +build !nozulip
|
||||||
|
|
||||||
|
package bridgemap
|
||||||
|
|
||||||
|
import (
|
||||||
|
bzulip "github.com/42wim/matterbridge/bridge/zulip"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
FullMap["zulip"] = bzulip.New
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package gateway
|
package gateway
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -10,10 +11,10 @@ import (
|
|||||||
"github.com/42wim/matterbridge/bridge"
|
"github.com/42wim/matterbridge/bridge"
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/internal"
|
"github.com/42wim/matterbridge/internal"
|
||||||
"github.com/d5/tengo/script"
|
"github.com/d5/tengo/v2"
|
||||||
"github.com/d5/tengo/stdlib"
|
"github.com/d5/tengo/v2/stdlib"
|
||||||
lru "github.com/hashicorp/golang-lru"
|
lru "github.com/hashicorp/golang-lru"
|
||||||
"github.com/peterhellberg/emojilib"
|
"github.com/matterbridge/emoji"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -85,6 +86,7 @@ func (gw *Gateway) FindCanonicalMsgID(protocol string, mID string) string {
|
|||||||
func (gw *Gateway) AddBridge(cfg *config.Bridge) error {
|
func (gw *Gateway) AddBridge(cfg *config.Bridge) error {
|
||||||
br := gw.Router.getBridge(cfg.Account)
|
br := gw.Router.getBridge(cfg.Account)
|
||||||
if br == nil {
|
if br == nil {
|
||||||
|
gw.checkConfig(cfg)
|
||||||
br = bridge.New(cfg)
|
br = bridge.New(cfg)
|
||||||
br.Config = gw.Router.Config
|
br.Config = gw.Router.Config
|
||||||
br.General = &gw.BridgeValues().General
|
br.General = &gw.BridgeValues().General
|
||||||
@@ -104,6 +106,19 @@ func (gw *Gateway) AddBridge(cfg *config.Bridge) error {
|
|||||||
return nil
|
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.
|
// AddConfig associates a new configuration with the gateway object.
|
||||||
func (gw *Gateway) AddConfig(cfg *config.Gateway) error {
|
func (gw *Gateway) AddConfig(cfg *config.Gateway) error {
|
||||||
gw.Name = cfg.Name
|
gw.Name = cfg.Name
|
||||||
@@ -292,8 +307,6 @@ func (gw *Gateway) ignoreMessage(msg *config.Message) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gw *Gateway) modifyUsername(msg *config.Message, dest *bridge.Bridge) string {
|
func (gw *Gateway) modifyUsername(msg *config.Message, dest *bridge.Bridge) string {
|
||||||
br := gw.Bridges[msg.Account]
|
|
||||||
msg.Protocol = br.Protocol
|
|
||||||
if dest.GetBool("StripNick") {
|
if dest.GetBool("StripNick") {
|
||||||
re := regexp.MustCompile("[^a-zA-Z0-9]+")
|
re := regexp.MustCompile("[^a-zA-Z0-9]+")
|
||||||
msg.Username = re.ReplaceAllString(msg.Username, "")
|
msg.Username = re.ReplaceAllString(msg.Username, "")
|
||||||
@@ -301,6 +314,7 @@ func (gw *Gateway) modifyUsername(msg *config.Message, dest *bridge.Bridge) stri
|
|||||||
nick := dest.GetString("RemoteNickFormat")
|
nick := dest.GetString("RemoteNickFormat")
|
||||||
|
|
||||||
// loop to replace nicks
|
// loop to replace nicks
|
||||||
|
br := gw.Bridges[msg.Account]
|
||||||
for _, outer := range br.GetStringSlice2D("ReplaceNicks") {
|
for _, outer := range br.GetStringSlice2D("ReplaceNicks") {
|
||||||
search := outer[0]
|
search := outer[0]
|
||||||
replace := outer[1]
|
replace := outer[1]
|
||||||
@@ -350,15 +364,28 @@ func (gw *Gateway) modifyAvatar(msg *config.Message, dest *bridge.Bridge) string
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gw *Gateway) modifyMessage(msg *config.Message) {
|
func (gw *Gateway) modifyMessage(msg *config.Message) {
|
||||||
if err := modifyMessageTengo(gw.BridgeValues().General.TengoModifyMessage, msg); err != nil {
|
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 {
|
||||||
gw.logger.Errorf("TengoModifyMessage failed: %s", err)
|
gw.logger.Errorf("TengoModifyMessage failed: %s", err)
|
||||||
}
|
}
|
||||||
if err := modifyMessageTengo(gw.BridgeValues().Tengo.Message, msg); err != nil {
|
|
||||||
|
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 {
|
||||||
gw.logger.Errorf("Tengo.Message failed: %s", err)
|
gw.logger.Errorf("Tengo.Message failed: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// replace :emoji: to unicode
|
// replace :emoji: to unicode
|
||||||
msg.Text = emojilib.Replace(msg.Text)
|
msg.Text = emoji.Sprint(msg.Text)
|
||||||
|
|
||||||
br := gw.Bridges[msg.Account]
|
br := gw.Bridges[msg.Account]
|
||||||
// loop to replace messages
|
// loop to replace messages
|
||||||
@@ -404,8 +431,9 @@ func (gw *Gateway) SendMessage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Too noisy to log like other events
|
// Too noisy to log like other events
|
||||||
|
debugSendMessage := ""
|
||||||
if msg.Event != config.EventUserTyping {
|
if msg.Event != config.EventUserTyping {
|
||||||
gw.logger.Debugf("=> Sending %#v from %s (%s) to %s (%s)", msg, msg.Account, rmsg.Channel, dest.Account, channel.Name)
|
debugSendMessage = fmt.Sprintf("=> Sending %#v from %s (%s) to %s (%s)", msg, msg.Account, rmsg.Channel, dest.Account, channel.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.Channel = channel.Name
|
msg.Channel = channel.Name
|
||||||
@@ -430,17 +458,29 @@ func (gw *Gateway) SendMessage(
|
|||||||
msg.ParentID = "msg-parent-not-found"
|
msg.ParentID = "msg-parent-not-found"
|
||||||
}
|
}
|
||||||
|
|
||||||
err := gw.modifySendMessageTengo(rmsg, &msg, dest)
|
drop, err := gw.modifyOutMessageTengo(rmsg, &msg, dest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
gw.logger.Errorf("modifySendMessageTengo: %s", err)
|
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
|
// if we are using mattermost plugin account, send messages to MattermostPlugin channel
|
||||||
// that can be picked up by the mattermost matterbridge plugin
|
// that can be picked up by the mattermost matterbridge plugin
|
||||||
if dest.Account == "mattermost.plugin" {
|
if dest.Account == "mattermost.plugin" {
|
||||||
gw.Router.MattermostPlugin <- msg
|
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)
|
mID, err := dest.Send(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mID, err
|
return mID, err
|
||||||
@@ -492,7 +532,7 @@ func getProtocol(msg *config.Message) string {
|
|||||||
return p[0]
|
return p[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func modifyMessageTengo(filename string, msg *config.Message) error {
|
func modifyInMessageTengo(filename string, msg *config.Message) error {
|
||||||
if filename == "" {
|
if filename == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -500,7 +540,7 @@ func modifyMessageTengo(filename string, msg *config.Message) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s := script.New(res)
|
s := tengo.NewScript(res)
|
||||||
s.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...))
|
s.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...))
|
||||||
_ = s.Add("msgText", msg.Text)
|
_ = s.Add("msgText", msg.Text)
|
||||||
_ = s.Add("msgUsername", msg.Username)
|
_ = s.Add("msgUsername", msg.Username)
|
||||||
@@ -527,7 +567,7 @@ func (gw *Gateway) modifyUsernameTengo(msg *config.Message, br *bridge.Bridge) (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
s := script.New(res)
|
s := tengo.NewScript(res)
|
||||||
s.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...))
|
s.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...))
|
||||||
_ = s.Add("result", "")
|
_ = s.Add("result", "")
|
||||||
_ = s.Add("msgText", msg.Text)
|
_ = s.Add("msgText", msg.Text)
|
||||||
@@ -551,22 +591,28 @@ func (gw *Gateway) modifyUsernameTengo(msg *config.Message, br *bridge.Bridge) (
|
|||||||
return c.Get("result").String(), nil
|
return c.Get("result").String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gw *Gateway) modifySendMessageTengo(origmsg *config.Message, msg *config.Message, br *bridge.Bridge) error {
|
func (gw *Gateway) modifyOutMessageTengo(origmsg *config.Message, msg *config.Message, br *bridge.Bridge) (bool, error) {
|
||||||
filename := gw.BridgeValues().Tengo.OutMessage
|
filename := gw.BridgeValues().Tengo.OutMessage
|
||||||
var res []byte
|
var (
|
||||||
var err error
|
res []byte
|
||||||
|
err error
|
||||||
|
drop bool
|
||||||
|
)
|
||||||
|
|
||||||
if filename == "" {
|
if filename == "" {
|
||||||
res, err = internal.Asset("tengo/outmessage.tengo")
|
res, err = internal.Asset("tengo/outmessage.tengo")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return drop, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
res, err = ioutil.ReadFile(filename)
|
res, err = ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return drop, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s := script.New(res)
|
|
||||||
|
s := tengo.NewScript(res)
|
||||||
|
|
||||||
s.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...))
|
s.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...))
|
||||||
_ = s.Add("inAccount", origmsg.Account)
|
_ = s.Add("inAccount", origmsg.Account)
|
||||||
_ = s.Add("inProtocol", origmsg.Protocol)
|
_ = s.Add("inProtocol", origmsg.Protocol)
|
||||||
@@ -580,14 +626,19 @@ func (gw *Gateway) modifySendMessageTengo(origmsg *config.Message, msg *config.M
|
|||||||
_ = s.Add("outEvent", msg.Event)
|
_ = s.Add("outEvent", msg.Event)
|
||||||
_ = s.Add("msgText", msg.Text)
|
_ = s.Add("msgText", msg.Text)
|
||||||
_ = s.Add("msgUsername", msg.Username)
|
_ = s.Add("msgUsername", msg.Username)
|
||||||
|
_ = s.Add("msgDrop", drop)
|
||||||
c, err := s.Compile()
|
c, err := s.Compile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return drop, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.Run(); err != nil {
|
if err := c.Run(); err != nil {
|
||||||
return err
|
return drop, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drop = c.Get("msgDrop").Bool()
|
||||||
msg.Text = c.Get("msgText").String()
|
msg.Text = c.Get("msgText").String()
|
||||||
msg.Username = c.Get("msgUsername").String()
|
msg.Username = c.Get("msgUsername").String()
|
||||||
return nil
|
|
||||||
|
return drop, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,10 +15,15 @@ import (
|
|||||||
|
|
||||||
var testconfig = []byte(`
|
var testconfig = []byte(`
|
||||||
[irc.freenode]
|
[irc.freenode]
|
||||||
|
server=""
|
||||||
[mattermost.test]
|
[mattermost.test]
|
||||||
|
server=""
|
||||||
[gitter.42wim]
|
[gitter.42wim]
|
||||||
|
server=""
|
||||||
[discord.test]
|
[discord.test]
|
||||||
|
server=""
|
||||||
[slack.test]
|
[slack.test]
|
||||||
|
server=""
|
||||||
|
|
||||||
[[gateway]]
|
[[gateway]]
|
||||||
name = "bridge1"
|
name = "bridge1"
|
||||||
@@ -44,10 +49,15 @@ var testconfig = []byte(`
|
|||||||
|
|
||||||
var testconfig2 = []byte(`
|
var testconfig2 = []byte(`
|
||||||
[irc.freenode]
|
[irc.freenode]
|
||||||
|
server=""
|
||||||
[mattermost.test]
|
[mattermost.test]
|
||||||
|
server=""
|
||||||
[gitter.42wim]
|
[gitter.42wim]
|
||||||
|
server=""
|
||||||
[discord.test]
|
[discord.test]
|
||||||
|
server=""
|
||||||
[slack.test]
|
[slack.test]
|
||||||
|
server=""
|
||||||
|
|
||||||
[[gateway]]
|
[[gateway]]
|
||||||
name = "bridge1"
|
name = "bridge1"
|
||||||
@@ -87,8 +97,11 @@ var testconfig2 = []byte(`
|
|||||||
|
|
||||||
var testconfig3 = []byte(`
|
var testconfig3 = []byte(`
|
||||||
[irc.zzz]
|
[irc.zzz]
|
||||||
|
server=""
|
||||||
[telegram.zzz]
|
[telegram.zzz]
|
||||||
|
server=""
|
||||||
[slack.zzz]
|
[slack.zzz]
|
||||||
|
server=""
|
||||||
[[gateway]]
|
[[gateway]]
|
||||||
name="bridge"
|
name="bridge"
|
||||||
enable=true
|
enable=true
|
||||||
@@ -176,7 +189,6 @@ func TestNewRouter(t *testing.T) {
|
|||||||
assert.Equal(t, 1, len(r.Gateways))
|
assert.Equal(t, 1, len(r.Gateways))
|
||||||
assert.Equal(t, 4, len(r.Gateways["bridge1"].Bridges))
|
assert.Equal(t, 4, len(r.Gateways["bridge1"].Bridges))
|
||||||
assert.Equal(t, 4, len(r.Gateways["bridge1"].Channels))
|
assert.Equal(t, 4, len(r.Gateways["bridge1"].Channels))
|
||||||
|
|
||||||
r = maketestRouter(testconfig2)
|
r = maketestRouter(testconfig2)
|
||||||
assert.Equal(t, 2, len(r.Gateways))
|
assert.Equal(t, 2, len(r.Gateways))
|
||||||
assert.Equal(t, 4, len(r.Gateways["bridge1"].Bridges))
|
assert.Equal(t, 4, len(r.Gateways["bridge1"].Bridges))
|
||||||
@@ -521,7 +533,7 @@ func (s *ignoreTestSuite) TestIgnoreNicks() {
|
|||||||
func BenchmarkTengo(b *testing.B) {
|
func BenchmarkTengo(b *testing.B) {
|
||||||
msg := &config.Message{Username: "user", Text: "blah testing", Account: "protocol.account", Channel: "mychannel"}
|
msg := &config.Message{Username: "user", Text: "blah testing", Account: "protocol.account", Channel: "mychannel"}
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
err := modifyMessageTengo("bench.tengo", msg)
|
err := modifyInMessageTengo("bench.tengo", msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ func (gw *Gateway) ignoreEvent(event string, dest *bridge.Bridge) bool {
|
|||||||
switch event {
|
switch event {
|
||||||
case config.EventAvatarDownload:
|
case config.EventAvatarDownload:
|
||||||
// Avatar downloads are only relevant for telegram and mattermost for now
|
// Avatar downloads are only relevant for telegram and mattermost for now
|
||||||
if dest.Protocol != "mattermost" && dest.Protocol != "telegram" {
|
if dest.Protocol != "mattermost" && dest.Protocol != "telegram" && dest.Protocol != "xmpp" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case config.EventJoinLeave:
|
case config.EventJoinLeave:
|
||||||
@@ -179,7 +179,7 @@ func (gw *Gateway) ignoreEvent(event string, dest *bridge.Bridge) bool {
|
|||||||
}
|
}
|
||||||
case config.EventTopicChange:
|
case config.EventTopicChange:
|
||||||
// only relay topic change when used in some way on other side
|
// 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
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,8 +59,14 @@ func NewRouter(rootLogger *logrus.Logger, cfg config.Config, bridgeMap map[strin
|
|||||||
// between them.
|
// between them.
|
||||||
func (r *Router) Start() error {
|
func (r *Router) Start() error {
|
||||||
m := make(map[string]*bridge.Bridge)
|
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 {
|
for _, gw := range r.Gateways {
|
||||||
r.logger.Infof("Parsing gateway %s", gw.Name)
|
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 {
|
for _, br := range gw.Bridges {
|
||||||
m[br.Account] = br
|
m[br.Account] = br
|
||||||
}
|
}
|
||||||
@@ -126,6 +132,9 @@ func (r *Router) handleReceive() {
|
|||||||
r.handleEventFailure(&msg)
|
r.handleEventFailure(&msg)
|
||||||
r.handleEventRejoinChannels(&msg)
|
r.handleEventRejoinChannels(&msg)
|
||||||
|
|
||||||
|
// Set message protocol based on the account it came from
|
||||||
|
msg.Protocol = r.getBridge(msg.Account).Protocol
|
||||||
|
|
||||||
filesHandled := false
|
filesHandled := false
|
||||||
for _, gw := range r.Gateways {
|
for _, gw := range r.Gateways {
|
||||||
// record all the message ID's of the different bridges
|
// record all the message ID's of the different bridges
|
||||||
|
|||||||
95
go.mod
95
go.mod
@@ -4,74 +4,55 @@ require (
|
|||||||
github.com/42wim/go-gitter v0.0.0-20170828205020-017310c2d557
|
github.com/42wim/go-gitter v0.0.0-20170828205020-017310c2d557
|
||||||
github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f
|
github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f
|
||||||
github.com/Jeffail/gabs v1.1.1 // indirect
|
github.com/Jeffail/gabs v1.1.1 // indirect
|
||||||
github.com/Philipp15b/go-steam v1.0.1-0.20190816133340-b04c5a83c1c0
|
github.com/Philipp15b/go-steam v1.0.1-0.20200727090957-6ae9b3c0a560
|
||||||
github.com/Rhymen/go-whatsapp v0.0.3-0.20190729104911-5c79b2cf277a
|
github.com/Rhymen/go-whatsapp v0.1.2-0.20201122130733-6e5488ac98df
|
||||||
github.com/bwmarrin/discordgo v0.19.0
|
github.com/d5/tengo/v2 v2.6.2
|
||||||
// github.com/bwmarrin/discordgo v0.19.0
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/d5/tengo v1.24.3
|
github.com/fsnotify/fsnotify v1.4.9
|
||||||
github.com/dfordsoft/golib v0.0.0-20180902042739-76ee6ab99bec
|
github.com/go-telegram-bot-api/telegram-bot-api v1.0.1-0.20200524105306-7434b0456e81
|
||||||
github.com/fsnotify/fsnotify v1.4.7
|
github.com/gomarkdown/markdown v0.0.0-20201113031856-722100d81a8e
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.5-0.20181225215658-ec221ba9ea45+incompatible
|
github.com/google/gops v0.3.13
|
||||||
github.com/google/gops v0.3.6
|
|
||||||
github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4 // indirect
|
github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4 // indirect
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f // indirect
|
github.com/gorilla/schema v1.2.0
|
||||||
github.com/gorilla/schema v1.1.0
|
github.com/gorilla/websocket v1.4.2
|
||||||
github.com/gorilla/websocket v1.4.1
|
github.com/hashicorp/golang-lru v0.5.4
|
||||||
github.com/hashicorp/golang-lru v0.5.3
|
github.com/jpillora/backoff v1.0.0
|
||||||
github.com/hpcloud/tail v1.0.0 // indirect
|
github.com/keybase/go-keybase-chat-bot v0.0.0-20200505163032-5cacf52379da
|
||||||
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7
|
github.com/labstack/echo/v4 v4.1.17
|
||||||
github.com/jtolds/gls v4.2.1+incompatible // indirect
|
|
||||||
github.com/keybase/go-keybase-chat-bot v0.0.0-20190816161829-561f10822eb2
|
|
||||||
github.com/labstack/echo/v4 v4.1.10
|
|
||||||
github.com/lrstanley/girc v0.0.0-20190801035559-4fc93959e1a7
|
github.com/lrstanley/girc v0.0.0-20190801035559-4fc93959e1a7
|
||||||
github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20190210153444-cc9d05784d5d
|
github.com/matrix-org/gomatrix v0.0.0-20200827122206-7dd5e2a05bcd
|
||||||
github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91
|
github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20200411204219-d5c18ce75048
|
||||||
github.com/matterbridge/gomatrix v0.0.0-20190102230110-6f9631ca6dea
|
github.com/matterbridge/discordgo v0.22.0
|
||||||
github.com/matterbridge/gozulipbot v0.0.0-20190212232658-7aa251978a18
|
github.com/matterbridge/emoji v2.1.1-0.20191117213217-af507f6b02db+incompatible
|
||||||
github.com/matterbridge/logrus-prefixed-formatter v0.0.0-20180806162718-01618749af61
|
github.com/matterbridge/go-xmpp v0.0.0-20200418225040-c8a3a57b4050
|
||||||
github.com/mattermost/mattermost-server v5.5.0+incompatible
|
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/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
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/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474 // indirect
|
||||||
github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff // indirect
|
github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff // indirect
|
||||||
github.com/nelsonken/gomf v0.0.0-20180504123937-a9dd2f9deae9
|
github.com/nelsonken/gomf v0.0.0-20180504123937-a9dd2f9deae9
|
||||||
github.com/nicksnyder/go-i18n v1.4.0 // indirect
|
|
||||||
github.com/nlopes/slack v0.6.0
|
|
||||||
github.com/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/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/rs/xid v1.2.1
|
||||||
github.com/russross/blackfriday v1.5.2
|
github.com/russross/blackfriday v1.5.2
|
||||||
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca
|
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca
|
||||||
github.com/shazow/ssh-chat v0.0.0-20190125184227-81d7e1686296
|
github.com/shazow/ssh-chat v1.10.1
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
github.com/sirupsen/logrus v1.7.0
|
||||||
github.com/sirupsen/logrus v1.4.2
|
github.com/slack-go/slack v0.7.2
|
||||||
github.com/smartystreets/assertions v0.0.0-20180803164922-886ec427f6b9 // indirect
|
github.com/spf13/viper v1.7.1
|
||||||
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a // indirect
|
github.com/stretchr/testify v1.6.1
|
||||||
github.com/spf13/viper v1.4.0
|
github.com/vincent-petithory/dataurl v0.0.0-20191104211930-d1553a71de50
|
||||||
github.com/stretchr/testify v1.4.0
|
github.com/writeas/go-strip-markdown v2.0.1+incompatible
|
||||||
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
|
|
||||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2 // 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
|
github.com/zfjagann/golang-ring v0.0.0-20190304061218-d34796e0a6c2
|
||||||
gitlab.com/golang-commonmark/html v0.0.0-20180917080848-cfaf75183c4a // indirect
|
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5
|
||||||
gitlab.com/golang-commonmark/linkify v0.0.0-20180917065525-c22b7bdb1179 // indirect
|
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58
|
||||||
gitlab.com/golang-commonmark/markdown v0.0.0-20181102083822-772775880e1f
|
gomod.garykim.dev/nc-talk v0.1.5
|
||||||
gitlab.com/golang-commonmark/mdurl v0.0.0-20180912090424-e5bce34c34f2 // indirect
|
gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376
|
||||||
gitlab.com/golang-commonmark/puny v0.0.0-20180912090636-2cd490539afe // indirect
|
layeh.com/gumble v0.0.0-20200818122324-146f9205029b
|
||||||
gitlab.com/opennota/wd v0.0.0-20180912061657-c5d65f63c638 // indirect
|
|
||||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 // indirect
|
|
||||||
golang.org/x/image v0.0.0-20190902063713-cb417be4ba39
|
|
||||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 // indirect
|
|
||||||
golang.org/x/text v0.3.2 // indirect
|
|
||||||
gopkg.in/fsnotify.v1 v1.4.7 // indirect
|
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
|
||||||
gopkg.in/russross/blackfriday.v2 v2.0.1 // indirect
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/bwmarrin/discordgo v0.19.0 => github.com/matterbridge/discordgo v0.0.0-20190818085008-57c6e0fc2f40
|
go 1.15
|
||||||
|
|
||||||
replace gopkg.in/russross/blackfriday.v2 v2.0.1 => github.com/russross/blackfriday/v2 v2.0.1
|
|
||||||
|
|
||||||
go 1.13
|
|
||||||
|
|||||||
@@ -12,8 +12,14 @@ text := import("text")
|
|||||||
|
|
||||||
// start - strip irc colors
|
// start - strip irc colors
|
||||||
// if we're not sending to an irc bridge we strip the IRC colors
|
// if we're not sending to an irc bridge we strip the IRC colors
|
||||||
if inProtocol == "irc" {
|
if inProtocol == "irc" && outProtocol != "irc" {
|
||||||
re := text.re_compile(`\x03(?:\d{1,2}(?:,\d{1,2})?)?|[[:cntrl:]]`)
|
re := text.re_compile(`\x03(?:\d{1,2}(?:,\d{1,2})?)?|[[:cntrl:]]`)
|
||||||
msgText=re.replace(msgText,"")
|
msgText=re.replace(msgText,"")
|
||||||
}
|
}
|
||||||
// end - strip irc colors
|
// end - strip irc colors
|
||||||
|
|
||||||
|
// strip custom emoji
|
||||||
|
if inProtocol == "discord" {
|
||||||
|
re := text.re_compile(`<a?(:.*?:)[0-9]+>`)
|
||||||
|
msgText=re.replace(msgText,"$1")
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
@@ -15,7 +16,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
version = "1.16.0"
|
version = "1.20.0"
|
||||||
githash string
|
githash string
|
||||||
|
|
||||||
flagConfig = flag.String("conf", "matterbridge.toml", "config file")
|
flagConfig = flag.String("conf", "matterbridge.toml", "config file")
|
||||||
@@ -50,6 +51,15 @@ func main() {
|
|||||||
cfg := config.NewConfig(rootLogger, *flagConfig)
|
cfg := config.NewConfig(rootLogger, *flagConfig)
|
||||||
cfg.BridgeValues().General.Debug = *flagDebug
|
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)
|
r, err := gateway.NewRouter(rootLogger, cfg, bridgemap.FullMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatalf("Starting gateway failed: %s", err)
|
logger.Fatalf("Starting gateway failed: %s", err)
|
||||||
@@ -67,17 +77,31 @@ func setupLogger() *logrus.Logger {
|
|||||||
Formatter: &prefixed.TextFormatter{
|
Formatter: &prefixed.TextFormatter{
|
||||||
PrefixPadding: 13,
|
PrefixPadding: 13,
|
||||||
DisableColors: true,
|
DisableColors: true,
|
||||||
FullTimestamp: true,
|
|
||||||
},
|
},
|
||||||
Level: logrus.InfoLevel,
|
Level: logrus.InfoLevel,
|
||||||
}
|
}
|
||||||
if *flagDebug || os.Getenv("DEBUG") == "1" {
|
if *flagDebug || os.Getenv("DEBUG") == "1" {
|
||||||
|
logger.SetReportCaller(true)
|
||||||
logger.Formatter = &prefixed.TextFormatter{
|
logger.Formatter = &prefixed.TextFormatter{
|
||||||
PrefixPadding: 13,
|
PrefixPadding: 13,
|
||||||
DisableColors: true,
|
DisableColors: true,
|
||||||
FullTimestamp: false,
|
FullTimestamp: false,
|
||||||
ForceFormatting: true,
|
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
s := strings.Split(f.Function, ".")
|
||||||
|
funcName := s[len(s)-1]
|
||||||
|
return funcName, fmt.Sprintf("%s:%d", filename, f.Line)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Level = logrus.DebugLevel
|
logger.Level = logrus.DebugLevel
|
||||||
logger.WithFields(logrus.Fields{"prefix": "main"}).Info("Enabling debug logging.")
|
logger.WithFields(logrus.Fields{"prefix": "main"}).Info("Enabling debug logging.")
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetChannels returns all channels we're members off
|
// GetChannels returns all channels we're members off
|
||||||
@@ -167,7 +167,7 @@ func (m *MMClient) JoinChannel(channelId string) error { //nolint:golint
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *MMClient) UpdateChannelsTeam(teamID string) error {
|
func (m *MMClient) UpdateChannelsTeam(teamID string) error {
|
||||||
mmchannels, resp := m.Client.GetChannelsForTeamForUser(teamID, m.User.Id, "")
|
mmchannels, resp := m.Client.GetChannelsForTeamForUser(teamID, m.User.Id, false, "")
|
||||||
if resp.Error != nil {
|
if resp.Error != nil {
|
||||||
return errors.New(resp.Error.DetailedError)
|
return errors.New(resp.Error.DetailedError)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/jpillora/backoff"
|
"github.com/jpillora/backoff"
|
||||||
"github.com/mattermost/mattermost-server/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (m *MMClient) doLogin(firstConnection bool, b *backoff.Backoff) error {
|
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}
|
t := &Team{Team: team, Users: usermap, Id: team.Id}
|
||||||
|
|
||||||
mmchannels, resp := m.Client.GetChannelsForTeamForUser(team.Id, m.User.Id, "")
|
mmchannels, resp := m.Client.GetChannelsForTeamForUser(team.Id, m.User.Id, false, "")
|
||||||
if resp.Error != nil {
|
if resp.Error != nil {
|
||||||
return resp.Error
|
return resp.Error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
lru "github.com/hashicorp/golang-lru"
|
lru "github.com/hashicorp/golang-lru"
|
||||||
"github.com/jpillora/backoff"
|
"github.com/jpillora/backoff"
|
||||||
prefixed "github.com/matterbridge/logrus-prefixed-formatter"
|
prefixed "github.com/matterbridge/logrus-prefixed-formatter"
|
||||||
"github.com/mattermost/mattermost-server/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -69,6 +69,7 @@ type MMClient struct {
|
|||||||
logger *logrus.Entry
|
logger *logrus.Entry
|
||||||
rootLogger *logrus.Logger
|
rootLogger *logrus.Logger
|
||||||
lruCache *lru.Cache
|
lruCache *lru.Cache
|
||||||
|
allevents bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// New will instantiate a new Matterclient with the specified login details without connecting.
|
// New will instantiate a new Matterclient with the specified login details without connecting.
|
||||||
@@ -119,6 +120,10 @@ 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.
|
// Login tries to connect the client with the loging details with which it was initialized.
|
||||||
func (m *MMClient) Login() error {
|
func (m *MMClient) Login() error {
|
||||||
// check if this is a first connect or a reconnection
|
// check if this is a first connect or a reconnection
|
||||||
@@ -220,6 +225,10 @@ func (m *MMClient) WsReceiver() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if m.allevents {
|
||||||
|
m.MessageChan <- msg
|
||||||
|
continue
|
||||||
|
}
|
||||||
switch msg.Raw.Event {
|
switch msg.Raw.Event {
|
||||||
case model.WEBSOCKET_EVENT_USER_ADDED,
|
case model.WEBSOCKET_EVENT_USER_ADDED,
|
||||||
model.WEBSOCKET_EVENT_USER_REMOVED,
|
model.WEBSOCKET_EVENT_USER_REMOVED,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package matterclient
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (m *MMClient) parseActionPost(rmsg *Message) {
|
func (m *MMClient) parseActionPost(rmsg *Message) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (m *MMClient) GetNickName(userId string) string { //nolint:golint
|
func (m *MMClient) GetNickName(userId string) string { //nolint:golint
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/schema"
|
"github.com/gorilla/schema"
|
||||||
"github.com/nlopes/slack"
|
"github.com/slack-go/slack"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OMessage for mattermost incoming webhook. (send to mattermost)
|
// OMessage for mattermost incoming webhook. (send to mattermost)
|
||||||
|
|||||||
38
tgs.Dockerfile
Normal file
38
tgs.Dockerfile
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
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"]
|
||||||
5
vendor/github.com/Philipp15b/go-steam/.gitmodules
generated
vendored
5
vendor/github.com/Philipp15b/go-steam/.gitmodules
generated
vendored
@@ -1,3 +1,6 @@
|
|||||||
[submodule "generator/SteamKit"]
|
[submodule "generator/SteamKit"]
|
||||||
path = generator/SteamKit
|
path = generator/SteamKit
|
||||||
url = https://github.com/Philipp15b/SteamKit.git
|
url = https://github.com/SteamRE/SteamKit.git
|
||||||
|
[submodule "generator/Protobufs"]
|
||||||
|
path = generator/Protobufs
|
||||||
|
url = https://github.com/SteamDatabase/Protobufs.git
|
||||||
|
|||||||
4
vendor/github.com/Philipp15b/go-steam/auth.go
generated
vendored
4
vendor/github.com/Philipp15b/go-steam/auth.go
generated
vendored
@@ -119,7 +119,7 @@ func (a *Auth) handleLogOnResponse(packet *Packet) {
|
|||||||
ExtendedResult: EResult(body.GetEresultExtended()),
|
ExtendedResult: EResult(body.GetEresultExtended()),
|
||||||
OutOfGameSecsPerHeartbeat: body.GetOutOfGameHeartbeatSeconds(),
|
OutOfGameSecsPerHeartbeat: body.GetOutOfGameHeartbeatSeconds(),
|
||||||
InGameSecsPerHeartbeat: body.GetInGameHeartbeatSeconds(),
|
InGameSecsPerHeartbeat: body.GetInGameHeartbeatSeconds(),
|
||||||
PublicIp: body.GetPublicIp(),
|
PublicIp: body.GetDeprecatedPublicIp(),
|
||||||
ServerTime: body.GetRtime32ServerTime(),
|
ServerTime: body.GetRtime32ServerTime(),
|
||||||
AccountFlags: EAccountFlags(body.GetAccountFlags()),
|
AccountFlags: EAccountFlags(body.GetAccountFlags()),
|
||||||
ClientSteamId: SteamId(body.GetClientSuppliedSteamid()),
|
ClientSteamId: SteamId(body.GetClientSuppliedSteamid()),
|
||||||
@@ -127,7 +127,7 @@ func (a *Auth) handleLogOnResponse(packet *Packet) {
|
|||||||
CellId: body.GetCellId(),
|
CellId: body.GetCellId(),
|
||||||
CellIdPingThreshold: body.GetCellIdPingThreshold(),
|
CellIdPingThreshold: body.GetCellIdPingThreshold(),
|
||||||
Steam2Ticket: body.GetSteam2Ticket(),
|
Steam2Ticket: body.GetSteam2Ticket(),
|
||||||
UsePics: body.GetUsePics(),
|
UsePics: body.GetDeprecatedUsePics(),
|
||||||
WebApiUserNonce: body.GetWebapiAuthenticateUserNonce(),
|
WebApiUserNonce: body.GetWebapiAuthenticateUserNonce(),
|
||||||
IpCountryCode: body.GetIpCountryCode(),
|
IpCountryCode: body.GetIpCountryCode(),
|
||||||
VanityUrl: body.GetVanityUrl(),
|
VanityUrl: body.GetVanityUrl(),
|
||||||
|
|||||||
8
vendor/github.com/Philipp15b/go-steam/go.mod
generated
vendored
Normal file
8
vendor/github.com/Philipp15b/go-steam/go.mod
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
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
|
||||||
|
)
|
||||||
22
vendor/github.com/Philipp15b/go-steam/go.sum
generated
vendored
Normal file
22
vendor/github.com/Philipp15b/go-steam/go.sum
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
32
vendor/github.com/Philipp15b/go-steam/protocol/protobuf/app_ticket.pb.go
generated
vendored
32
vendor/github.com/Philipp15b/go-steam/protocol/protobuf/app_ticket.pb.go
generated
vendored
@@ -21,11 +21,11 @@ var _ = math.Inf
|
|||||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||||
|
|
||||||
type EncryptedAppTicket struct {
|
type EncryptedAppTicket struct {
|
||||||
TicketVersionNo *uint32 `protobuf:"varint,1,opt,name=ticket_version_no" json:"ticket_version_no,omitempty"`
|
TicketVersionNo *uint32 `protobuf:"varint,1,opt,name=ticket_version_no,json=ticketVersionNo" json:"ticket_version_no,omitempty"`
|
||||||
CrcEncryptedticket *uint32 `protobuf:"varint,2,opt,name=crc_encryptedticket" json:"crc_encryptedticket,omitempty"`
|
CrcEncryptedticket *uint32 `protobuf:"varint,2,opt,name=crc_encryptedticket,json=crcEncryptedticket" json:"crc_encryptedticket,omitempty"`
|
||||||
CbEncrypteduserdata *uint32 `protobuf:"varint,3,opt,name=cb_encrypteduserdata" json:"cb_encrypteduserdata,omitempty"`
|
CbEncrypteduserdata *uint32 `protobuf:"varint,3,opt,name=cb_encrypteduserdata,json=cbEncrypteduserdata" json:"cb_encrypteduserdata,omitempty"`
|
||||||
CbEncryptedAppownershipticket *uint32 `protobuf:"varint,4,opt,name=cb_encrypted_appownershipticket" json:"cb_encrypted_appownershipticket,omitempty"`
|
CbEncryptedAppownershipticket *uint32 `protobuf:"varint,4,opt,name=cb_encrypted_appownershipticket,json=cbEncryptedAppownershipticket" json:"cb_encrypted_appownershipticket,omitempty"`
|
||||||
EncryptedTicket []byte `protobuf:"bytes,5,opt,name=encrypted_ticket" json:"encrypted_ticket,omitempty"`
|
EncryptedTicket []byte `protobuf:"bytes,5,opt,name=encrypted_ticket,json=encryptedTicket" json:"encrypted_ticket,omitempty"`
|
||||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
XXX_unrecognized []byte `json:"-"`
|
XXX_unrecognized []byte `json:"-"`
|
||||||
XXX_sizecache int32 `json:"-"`
|
XXX_sizecache int32 `json:"-"`
|
||||||
@@ -98,16 +98,18 @@ func init() {
|
|||||||
func init() { proto.RegisterFile("encrypted_app_ticket.proto", fileDescriptor_c6d69fd1cac4e8d5) }
|
func init() { proto.RegisterFile("encrypted_app_ticket.proto", fileDescriptor_c6d69fd1cac4e8d5) }
|
||||||
|
|
||||||
var fileDescriptor_c6d69fd1cac4e8d5 = []byte{
|
var fileDescriptor_c6d69fd1cac4e8d5 = []byte{
|
||||||
// 164 bytes of a gzipped FileDescriptorProto
|
// 202 bytes of a gzipped FileDescriptorProto
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4a, 0xcd, 0x4b, 0x2e,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4a, 0xcd, 0x4b, 0x2e,
|
||||||
0xaa, 0x2c, 0x28, 0x49, 0x4d, 0x89, 0x4f, 0x2c, 0x28, 0x88, 0x2f, 0xc9, 0x4c, 0xce, 0x4e, 0x2d,
|
0xaa, 0x2c, 0x28, 0x49, 0x4d, 0x89, 0x4f, 0x2c, 0x28, 0x88, 0x2f, 0xc9, 0x4c, 0xce, 0x4e, 0x2d,
|
||||||
0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x57, 0x5a, 0xcb, 0xc8, 0x25, 0xe4, 0x0a, 0x93, 0x76, 0x2c,
|
0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x57, 0x9a, 0xce, 0xc4, 0x25, 0xe4, 0x0a, 0x93, 0x76, 0x2c,
|
||||||
0x28, 0x08, 0x01, 0x4b, 0x0a, 0x49, 0x72, 0x09, 0x42, 0x94, 0xc5, 0x97, 0xa5, 0x16, 0x15, 0x67,
|
0x28, 0x08, 0x01, 0x4b, 0x0a, 0x69, 0x71, 0x09, 0x42, 0x94, 0xc5, 0x97, 0xa5, 0x16, 0x15, 0x67,
|
||||||
0xe6, 0xe7, 0xc5, 0xe7, 0xe5, 0x4b, 0x30, 0x2a, 0x30, 0x6a, 0xf0, 0x0a, 0x49, 0x73, 0x09, 0x27,
|
0xe6, 0xe7, 0xc5, 0xe7, 0xe5, 0x4b, 0x30, 0x2a, 0x30, 0x6a, 0xf0, 0x06, 0xf1, 0x43, 0x24, 0xc2,
|
||||||
0x17, 0x25, 0xc7, 0xc3, 0xcd, 0x84, 0xa8, 0x93, 0x60, 0x02, 0x4b, 0xca, 0x70, 0x89, 0x24, 0x27,
|
0x20, 0xe2, 0x7e, 0xf9, 0x42, 0xfa, 0x5c, 0xc2, 0xc9, 0x45, 0xc9, 0xf1, 0x70, 0x4b, 0x20, 0xf2,
|
||||||
0x21, 0xe4, 0x4a, 0x8b, 0x53, 0x8b, 0x52, 0x12, 0x4b, 0x12, 0x25, 0x98, 0xc1, 0xb2, 0xea, 0x5c,
|
0x12, 0x4c, 0x60, 0xd5, 0x42, 0xc9, 0x45, 0xc9, 0xae, 0xa8, 0x32, 0x42, 0x86, 0x5c, 0x22, 0xc9,
|
||||||
0xf2, 0xc8, 0xb2, 0x20, 0xd7, 0xe4, 0x97, 0xe7, 0xa5, 0x16, 0x15, 0x67, 0x64, 0x16, 0x40, 0x8d,
|
0x49, 0x08, 0xf5, 0xa5, 0xc5, 0xa9, 0x45, 0x29, 0x89, 0x25, 0x89, 0x12, 0xcc, 0x60, 0x1d, 0xc2,
|
||||||
0x61, 0x01, 0x2b, 0x94, 0xe0, 0x12, 0x40, 0xa8, 0x82, 0xca, 0xb0, 0x2a, 0x30, 0x6a, 0xf0, 0x38,
|
0xc9, 0x49, 0xae, 0xe8, 0x52, 0x42, 0x6e, 0x5c, 0xf2, 0xc8, 0x5a, 0x40, 0xfe, 0xc8, 0x2f, 0xcf,
|
||||||
0xb1, 0x7a, 0x30, 0x36, 0x30, 0x32, 0x00, 0x02, 0x00, 0x00, 0xff, 0xff, 0x03, 0x8c, 0xdb, 0x92,
|
0x4b, 0x2d, 0x2a, 0xce, 0xc8, 0x2c, 0x80, 0xda, 0xc7, 0x02, 0xd6, 0x2d, 0x8b, 0xa4, 0xdb, 0x11,
|
||||||
0xd3, 0x00, 0x00, 0x00,
|
0x43, 0x91, 0x90, 0x26, 0x97, 0x00, 0xc2, 0x10, 0xa8, 0x46, 0x56, 0x05, 0x46, 0x0d, 0x9e, 0x20,
|
||||||
|
0x7e, 0xb8, 0x38, 0x24, 0x08, 0x9c, 0x58, 0x3d, 0x18, 0x1b, 0x18, 0x19, 0x00, 0x01, 0x00, 0x00,
|
||||||
|
0xff, 0xff, 0xe8, 0x03, 0x98, 0x21, 0x3d, 0x01, 0x00, 0x00,
|
||||||
}
|
}
|
||||||
|
|||||||
1221
vendor/github.com/Philipp15b/go-steam/protocol/protobuf/base.pb.go
generated
vendored
1221
vendor/github.com/Philipp15b/go-steam/protocol/protobuf/base.pb.go
generated
vendored
File diff suppressed because it is too large
Load Diff
2801
vendor/github.com/Philipp15b/go-steam/protocol/protobuf/client_server.pb.go
generated
vendored
2801
vendor/github.com/Philipp15b/go-steam/protocol/protobuf/client_server.pb.go
generated
vendored
File diff suppressed because it is too large
Load Diff
2860
vendor/github.com/Philipp15b/go-steam/protocol/protobuf/client_server_2.pb.go
generated
vendored
2860
vendor/github.com/Philipp15b/go-steam/protocol/protobuf/client_server_2.pb.go
generated
vendored
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user