forked from lug/matterbridge
		
	Compare commits
	
		
			11 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					6d8cccccf1 | ||
| 
						 | 
					dd63438e7b | ||
| 
						 | 
					5f5d6c6e8a | ||
| 
						 | 
					aefa8a9341 | ||
| 
						 | 
					1c3e764d57 | ||
| 
						 | 
					0b03076a9d | ||
| 
						 | 
					1e6a2bc8f7 | ||
| 
						 | 
					82fe80e52f | ||
| 
						 | 
					db012bd9b7 | ||
| 
						 | 
					dd2374158b | ||
| 
						 | 
					6693157258 | 
@@ -1,2 +0,0 @@
 | 
			
		||||
Dockerfile
 | 
			
		||||
tgs.Dockerfile
 | 
			
		||||
@@ -1,3 +0,0 @@
 | 
			
		||||
go:
 | 
			
		||||
  comments:
 | 
			
		||||
    disabled: true
 | 
			
		||||
							
								
								
									
										1
									
								
								.github/ISSUE_TEMPLATE/Bug_report.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/ISSUE_TEMPLATE/Bug_report.md
									
									
									
									
										vendored
									
									
								
							@@ -1,7 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
name: Bug report
 | 
			
		||||
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,7 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
name: Feature request
 | 
			
		||||
about: Suggest an idea for this project
 | 
			
		||||
labels: enhancement
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										71
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										71
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,71 +0,0 @@
 | 
			
		||||
# For most projects, this workflow file will not need changing; you simply need
 | 
			
		||||
# to commit it to your repository.
 | 
			
		||||
#
 | 
			
		||||
# You may wish to alter this file to override the set of languages analyzed,
 | 
			
		||||
# or to provide custom queries or build logic.
 | 
			
		||||
name: "CodeQL"
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches: [master]
 | 
			
		||||
  pull_request:
 | 
			
		||||
    # The branches below must be a subset of the branches above
 | 
			
		||||
    branches: [master]
 | 
			
		||||
  schedule:
 | 
			
		||||
    - cron: '0 16 * * 1'
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  analyze:
 | 
			
		||||
    name: Analyze
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        # Override automatic language detection by changing the below list
 | 
			
		||||
        # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
 | 
			
		||||
        language: ['go']
 | 
			
		||||
        # Learn more...
 | 
			
		||||
        # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
    - name: Checkout repository
 | 
			
		||||
      uses: actions/checkout@v2
 | 
			
		||||
      with:
 | 
			
		||||
        # We must fetch at least the immediate parents so that if this is
 | 
			
		||||
        # a pull request then we can checkout the head.
 | 
			
		||||
        fetch-depth: 2
 | 
			
		||||
 | 
			
		||||
    # If this run was triggered by a pull request event, then checkout
 | 
			
		||||
    # the head of the pull request instead of the merge commit.
 | 
			
		||||
    - run: git checkout HEAD^2
 | 
			
		||||
      if: ${{ github.event_name == 'pull_request' }}
 | 
			
		||||
 | 
			
		||||
    # Initializes the CodeQL tools for scanning.
 | 
			
		||||
    - name: Initialize CodeQL
 | 
			
		||||
      uses: github/codeql-action/init@v1
 | 
			
		||||
      with:
 | 
			
		||||
        languages: ${{ matrix.language }}
 | 
			
		||||
        # If you wish to specify custom queries, you can do so here or in a config file.
 | 
			
		||||
        # By default, queries listed here will override any specified in a config file. 
 | 
			
		||||
        # Prefix the list here with "+" to use these queries and those in the config file.
 | 
			
		||||
        # queries: ./path/to/local/query, your-org/your-repo/queries@main
 | 
			
		||||
 | 
			
		||||
    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
 | 
			
		||||
    # If this step fails, then you should remove it and run the build manually (see below)
 | 
			
		||||
    - name: Autobuild
 | 
			
		||||
      uses: github/codeql-action/autobuild@v1
 | 
			
		||||
 | 
			
		||||
    # ℹ️ Command-line programs to run using the OS shell.
 | 
			
		||||
    # 📚 https://git.io/JvXDl
 | 
			
		||||
 | 
			
		||||
    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
 | 
			
		||||
    #    and modify them (or add more) to build your code if your project
 | 
			
		||||
    #    uses a compiled language
 | 
			
		||||
 | 
			
		||||
    #- run: |
 | 
			
		||||
    #   make bootstrap
 | 
			
		||||
    #   make release
 | 
			
		||||
 | 
			
		||||
    - name: Perform CodeQL Analysis
 | 
			
		||||
      uses: github/codeql-action/analyze@v1
 | 
			
		||||
							
								
								
									
										57
									
								
								.github/workflows/development.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										57
									
								
								.github/workflows/development.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,57 +0,0 @@
 | 
			
		||||
name: Development
 | 
			
		||||
on: [push, pull_request]
 | 
			
		||||
jobs:
 | 
			
		||||
  lint:
 | 
			
		||||
    name: golangci-lint
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
        with:
 | 
			
		||||
          fetch-depth: 20
 | 
			
		||||
      - name: Run golangci-lint
 | 
			
		||||
        uses: golangci/golangci-lint-action@v2
 | 
			
		||||
        with:
 | 
			
		||||
          version: v1.29
 | 
			
		||||
          args: "-v --new-from-rev HEAD~5"
 | 
			
		||||
  test-build-upload:
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        go-version: [1.14.x, 1.15.x]
 | 
			
		||||
        platform: [ubuntu-latest]
 | 
			
		||||
    runs-on: ${{ matrix.platform }}
 | 
			
		||||
    steps:
 | 
			
		||||
    - name: Install Go
 | 
			
		||||
      uses: actions/setup-go@v2
 | 
			
		||||
      with:
 | 
			
		||||
        go-version: ${{ matrix.go-version }}
 | 
			
		||||
    - name: Checkout code
 | 
			
		||||
      uses: actions/checkout@v2
 | 
			
		||||
      with:
 | 
			
		||||
          fetch-depth: 0
 | 
			
		||||
    - name: Test
 | 
			
		||||
      run: go test ./... -mod=vendor
 | 
			
		||||
    - name: Build
 | 
			
		||||
      run: |
 | 
			
		||||
        mkdir -p output/{win,lin,arm,mac}
 | 
			
		||||
        VERSION=$(git describe --tags)
 | 
			
		||||
        GOOS=linux GOARCH=amd64 go build -mod=vendor -ldflags "-s -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o output/lin/matterbridge-$VERSION-linux-amd64
 | 
			
		||||
        GOOS=windows GOARCH=amd64 go build -mod=vendor -ldflags "-s -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o output/win/matterbridge-$VERSION-windows-amd64.exe
 | 
			
		||||
        GOOS=darwin GOARCH=amd64 go build -mod=vendor -ldflags "-s -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o output/mac/matterbridge-$VERSION-darwin-amd64
 | 
			
		||||
    - name: Upload linux 64-bit
 | 
			
		||||
      if: startsWith(matrix.go-version,'1.15')
 | 
			
		||||
      uses: actions/upload-artifact@v2
 | 
			
		||||
      with:
 | 
			
		||||
        name: matterbridge-linux-64bit
 | 
			
		||||
        path: output/lin
 | 
			
		||||
    - name: Upload windows 64-bit
 | 
			
		||||
      if: startsWith(matrix.go-version,'1.15')
 | 
			
		||||
      uses: actions/upload-artifact@v2
 | 
			
		||||
      with:
 | 
			
		||||
        name: matterbridge-windows-64bit
 | 
			
		||||
        path: output/win
 | 
			
		||||
    - name: Upload darwin 64-bit
 | 
			
		||||
      if: startsWith(matrix.go-version,'1.15')
 | 
			
		||||
      uses: actions/upload-artifact@v2
 | 
			
		||||
      with:
 | 
			
		||||
        name: matterbridge-darwin-64bit
 | 
			
		||||
        path: output/mac
 | 
			
		||||
							
								
								
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,6 +0,0 @@
 | 
			
		||||
# Exclude matterbridge binary
 | 
			
		||||
/matterbridge
 | 
			
		||||
/matterbridge.exe
 | 
			
		||||
 | 
			
		||||
# Exclude configuration file
 | 
			
		||||
matterbridge.toml
 | 
			
		||||
@@ -23,7 +23,7 @@ run:
 | 
			
		||||
  # default value is empty list, but next dirs are always skipped independently
 | 
			
		||||
  # from this option's value:
 | 
			
		||||
  #   	vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
 | 
			
		||||
  skip-dirs: gateway/bridgemap$
 | 
			
		||||
  skip-dirs:
 | 
			
		||||
 | 
			
		||||
  # which files to skip: they will be analyzed, but issues from them
 | 
			
		||||
  # won't be reported. Default value is empty list, but there is
 | 
			
		||||
@@ -174,15 +174,7 @@ linters:
 | 
			
		||||
    - lll
 | 
			
		||||
    - maligned
 | 
			
		||||
    - prealloc
 | 
			
		||||
    - wsl
 | 
			
		||||
    - gomnd
 | 
			
		||||
    - godox
 | 
			
		||||
    - goerr113
 | 
			
		||||
    - testpackage
 | 
			
		||||
    - godot
 | 
			
		||||
    - interfacer
 | 
			
		||||
    - goheader
 | 
			
		||||
    - noctx
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# rules to deal with reported isues
 | 
			
		||||
issues:
 | 
			
		||||
 
 | 
			
		||||
@@ -21,11 +21,7 @@ builds:
 | 
			
		||||
  ldflags:
 | 
			
		||||
    - -s -w -X main.githash={{.ShortCommit}}
 | 
			
		||||
 | 
			
		||||
archives:
 | 
			
		||||
  -
 | 
			
		||||
    id: matterbridge
 | 
			
		||||
    builds:
 | 
			
		||||
    - matterbridge
 | 
			
		||||
archive:
 | 
			
		||||
  name_template: "{{ .Binary }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
 | 
			
		||||
  format: binary
 | 
			
		||||
  files:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										55
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
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.16.0"
 | 
			
		||||
  - 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,16 +1,11 @@
 | 
			
		||||
FROM alpine AS builder
 | 
			
		||||
FROM alpine:edge
 | 
			
		||||
ENTRYPOINT ["/bin/matterbridge"]
 | 
			
		||||
 | 
			
		||||
COPY . /go/src/github.com/42wim/matterbridge
 | 
			
		||||
RUN apk update && apk add go git gcc musl-dev \
 | 
			
		||||
RUN apk update && apk add go git gcc musl-dev ca-certificates \
 | 
			
		||||
        && cd /go/src/github.com/42wim/matterbridge \
 | 
			
		||||
        && export GOPATH=/go \
 | 
			
		||||
        && go get \
 | 
			
		||||
        && go build -x -ldflags "-X main.githash=$(git log --pretty=format:'%h' -n 1)" -o /bin/matterbridge
 | 
			
		||||
 | 
			
		||||
FROM alpine
 | 
			
		||||
RUN apk --no-cache add ca-certificates mailcap
 | 
			
		||||
COPY --from=builder /bin/matterbridge /bin/matterbridge
 | 
			
		||||
RUN mkdir /etc/matterbridge \
 | 
			
		||||
  && touch /etc/matterbridge/matterbridge.toml \
 | 
			
		||||
  && ln -sf /matterbridge.toml /etc/matterbridge/matterbridge.toml
 | 
			
		||||
ENTRYPOINT ["/bin/matterbridge", "-conf", "/etc/matterbridge/matterbridge.toml"]
 | 
			
		||||
        && go build -x -ldflags "-X main.githash=$(git log --pretty=format:'%h' -n 1)" -o /bin/matterbridge \
 | 
			
		||||
        && rm -rf /go \
 | 
			
		||||
        && apk del --purge git go gcc musl-dev
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										310
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										310
									
								
								README.md
									
									
									
									
									
								
							@@ -9,29 +9,26 @@ Letting people be where they want to be.<br />
 | 
			
		||||
 | 
			
		||||
   <sup>
 | 
			
		||||
 | 
			
		||||
[Discord][mb-discord] |
 | 
			
		||||
   [Gitter][mb-gitter] |
 | 
			
		||||
   [IRC][mb-irc] |
 | 
			
		||||
[Keybase][mb-keybase] |
 | 
			
		||||
      [Discord][mb-discord] |
 | 
			
		||||
      [Matrix][mb-matrix] |
 | 
			
		||||
[Mattermost][mb-mattermost] |
 | 
			
		||||
[MSTeams][mb-msteams] |
 | 
			
		||||
[Rocket.Chat][mb-rocketchat] |
 | 
			
		||||
      [Slack][mb-slack] |
 | 
			
		||||
[Telegram][mb-telegram] |
 | 
			
		||||
      [Mattermost][mb-mattermost] |
 | 
			
		||||
      [Rocket.Chat][mb-rocketchat] |
 | 
			
		||||
      [XMPP][mb-xmpp] |
 | 
			
		||||
      [Twitch][mb-twitch] |
 | 
			
		||||
      [WhatsApp][mb-whatsapp] |
 | 
			
		||||
[XMPP][mb-xmpp] |
 | 
			
		||||
      [Zulip][mb-zulip] |
 | 
			
		||||
      [Telegram][mb-telegram] |
 | 
			
		||||
      And more...
 | 
			
		||||
   </sup>
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
[](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/test_coverage)<br />
 | 
			
		||||
 | 
			
		||||
  <hr />
 | 
			
		||||
</div>
 | 
			
		||||
<div align="right"><sup>
 | 
			
		||||
@@ -44,161 +41,126 @@ And more...
 | 
			
		||||
  </a>
 | 
			
		||||
</p>
 | 
			
		||||
 | 
			
		||||
# Table of Contents
 | 
			
		||||
 | 
			
		||||
- [matterbridge](#matterbridge)
 | 
			
		||||
- [Table of Contents](#table-of-contents)
 | 
			
		||||
  - [Features](#features)
 | 
			
		||||
    - [Natively supported](#natively-supported)
 | 
			
		||||
    - [3rd party via matterbridge api](#3rd-party-via-matterbridge-api)
 | 
			
		||||
    - [API](#api)
 | 
			
		||||
  - [Chat with us](#chat-with-us)
 | 
			
		||||
  - [Screenshots](#screenshots)
 | 
			
		||||
  - [Installing / upgrading](#installing--upgrading)
 | 
			
		||||
    - [Binaries](#binaries)
 | 
			
		||||
    - [Packages](#packages)
 | 
			
		||||
  - [Building](#building)
 | 
			
		||||
  - [Configuration](#configuration)
 | 
			
		||||
    - [Basic configuration](#basic-configuration)
 | 
			
		||||
    - [Settings](#settings)
 | 
			
		||||
    - [Advanced configuration](#advanced-configuration)
 | 
			
		||||
    - [Examples](#examples)
 | 
			
		||||
      - [Bridge mattermost (off-topic) - irc (#testing)](#bridge-mattermost-off-topic---irc-testing)
 | 
			
		||||
      - [Bridge slack (#general) - discord (general)](#bridge-slack-general---discord-general)
 | 
			
		||||
  - [Running](#running)
 | 
			
		||||
    - [Docker](#docker)
 | 
			
		||||
  - [Changelog](#changelog)
 | 
			
		||||
  - [FAQ](#faq)
 | 
			
		||||
  - [Related projects](#related-projects)
 | 
			
		||||
  - [Articles](#articles)
 | 
			
		||||
  - [Thanks](#thanks)
 | 
			
		||||
### Table of Contents
 | 
			
		||||
 * [Features](https://github.com/42wim/matterbridge/wiki/Features)
 | 
			
		||||
   * [Natively supported](#natively-supported)
 | 
			
		||||
   * [3rd party via matterbridge api](#3rd-party-via-matterbridge-api)
 | 
			
		||||
   * [API](#API)
 | 
			
		||||
 * [Chat with us](#chat-with-us)
 | 
			
		||||
 * [Screenshots](https://github.com/42wim/matterbridge/wiki/)
 | 
			
		||||
 * [Installing](#installing)
 | 
			
		||||
   * [Binaries](#binaries)
 | 
			
		||||
   * [Building](#building)
 | 
			
		||||
 * [Configuration](#configuration)
 | 
			
		||||
   * [Howto](https://github.com/42wim/matterbridge/wiki/How-to-create-your-config)
 | 
			
		||||
   * [Examples](#examples)
 | 
			
		||||
 * [Running](#running)
 | 
			
		||||
   * [Docker](#docker)
 | 
			
		||||
 * [Changelog](#changelog)
 | 
			
		||||
 * [FAQ](#faq)
 | 
			
		||||
 * [Related projects](#related-projects)
 | 
			
		||||
 * [Articles](#articles)
 | 
			
		||||
 * [Thanks](#thanks)
 | 
			
		||||
 | 
			
		||||
## Features
 | 
			
		||||
 | 
			
		||||
- [Support bridging between any protocols](https://github.com/42wim/matterbridge/wiki/Features#support-bridging-between-any-protocols)
 | 
			
		||||
- [Support multiple gateways(bridges) for your protocols](https://github.com/42wim/matterbridge/wiki/Features#support-multiple-gatewaysbridges-for-your-protocols)
 | 
			
		||||
- [Message edits and deletes](https://github.com/42wim/matterbridge/wiki/Features#message-edits-and-deletes)
 | 
			
		||||
- Preserves threading when possible
 | 
			
		||||
- [Attachment / files handling](https://github.com/42wim/matterbridge/wiki/Features#attachment--files-handling)
 | 
			
		||||
- [Username and avatar spoofing](https://github.com/42wim/matterbridge/wiki/Features#username-and-avatar-spoofing)
 | 
			
		||||
- [Private groups](https://github.com/42wim/matterbridge/wiki/Features#private-groups)
 | 
			
		||||
- [API](https://github.com/42wim/matterbridge/wiki/Features#api)
 | 
			
		||||
* [Support bridging between any protocols](https://github.com/42wim/matterbridge/wiki/Features#support-bridging-between-any-protocols)
 | 
			
		||||
* [Support multiple gateways(bridges) for your protocols](https://github.com/42wim/matterbridge/wiki/Features#support-multiple-gatewaysbridges-for-your-protocols)
 | 
			
		||||
* [Message edits and deletes](https://github.com/42wim/matterbridge/wiki/Features#message-edits-and-deletes)
 | 
			
		||||
* Preserves threading when possible
 | 
			
		||||
* [Attachment / files handling](https://github.com/42wim/matterbridge/wiki/Features#attachment--files-handling)
 | 
			
		||||
* [Username and avatar spoofing](https://github.com/42wim/matterbridge/wiki/Features#username-and-avatar-spoofing)
 | 
			
		||||
* [Private groups](https://github.com/42wim/matterbridge/wiki/Features#private-groups)
 | 
			
		||||
* [API](https://github.com/42wim/matterbridge/wiki/Features#api)
 | 
			
		||||
 | 
			
		||||
### Natively supported
 | 
			
		||||
 | 
			
		||||
- [Discord](https://discordapp.com)
 | 
			
		||||
- [Gitter](https://gitter.im)
 | 
			
		||||
- [IRC](http://www.mirc.com/servers.html)
 | 
			
		||||
- [Keybase](https://keybase.io)
 | 
			
		||||
- [Matrix](https://matrix.org)
 | 
			
		||||
- [Mattermost](https://github.com/mattermost/mattermost-server/) 4.x, 5.x
 | 
			
		||||
- [Microsoft Teams](https://teams.microsoft.com)
 | 
			
		||||
- [Mumble](https://www.mumble.info/)
 | 
			
		||||
- [Nextcloud Talk](https://nextcloud.com/talk/)
 | 
			
		||||
- [Rocket.chat](https://rocket.chat)
 | 
			
		||||
- [Slack](https://slack.com)
 | 
			
		||||
- [Ssh-chat](https://github.com/shazow/ssh-chat)
 | 
			
		||||
- [Steam](https://store.steampowered.com/)
 | 
			
		||||
- [Telegram](https://telegram.org)
 | 
			
		||||
- [Twitch](https://twitch.tv)
 | 
			
		||||
- [WhatsApp](https://www.whatsapp.com/)
 | 
			
		||||
- [XMPP](https://xmpp.org)
 | 
			
		||||
- [Zulip](https://zulipchat.com)
 | 
			
		||||
* [Mattermost](https://github.com/mattermost/mattermost-server/) 4.x, 5.x
 | 
			
		||||
* [IRC](http://www.mirc.com/servers.html)
 | 
			
		||||
* [XMPP](https://xmpp.org)
 | 
			
		||||
* [Gitter](https://gitter.im)
 | 
			
		||||
* [Slack](https://slack.com)
 | 
			
		||||
* [Discord](https://discordapp.com)
 | 
			
		||||
* [Telegram](https://telegram.org)
 | 
			
		||||
* [Hipchat](https://www.hipchat.com)
 | 
			
		||||
* [Rocket.chat](https://rocket.chat)
 | 
			
		||||
* [Matrix](https://matrix.org)
 | 
			
		||||
* [Steam](https://store.steampowered.com/)
 | 
			
		||||
* [Twitch](https://twitch.tv)
 | 
			
		||||
* [Ssh-chat](https://github.com/shazow/ssh-chat)
 | 
			
		||||
* [WhatsApp](https://www.whatsapp.com/)
 | 
			
		||||
* [Zulip](https://zulipchat.com)
 | 
			
		||||
 | 
			
		||||
### 3rd party via matterbridge api
 | 
			
		||||
 | 
			
		||||
- [Discourse](https://github.com/DeclanHoare/matterbabble)
 | 
			
		||||
- [Facebook messenger](https://github.com/VictorNine/fbridge)
 | 
			
		||||
- [Minecraft](https://github.com/elytra/MatterLink)
 | 
			
		||||
- [Reddit](https://github.com/bonehurtingjuice/mattereddit)
 | 
			
		||||
- [Counter-Strike, half-life and more](https://forums.alliedmods.net/showthread.php?t=319430)
 | 
			
		||||
- [MatterAMXX](https://github.com/GabeIggy/MatterAMXX)
 | 
			
		||||
* [Minecraft](https://github.com/elytra/MatterLink)
 | 
			
		||||
* [Reddit](https://github.com/bonehurtingjuice/mattereddit)
 | 
			
		||||
* [Facebook messenger](https://github.com/VictorNine/fbridge)
 | 
			
		||||
* [Discourse](https://github.com/DeclanHoare/matterbabble)
 | 
			
		||||
 | 
			
		||||
### API
 | 
			
		||||
 | 
			
		||||
The API is basic at the moment.
 | 
			
		||||
The API is very basic at the moment.   
 | 
			
		||||
More info and examples on the [wiki](https://github.com/42wim/matterbridge/wiki/Api).
 | 
			
		||||
 | 
			
		||||
Used by the projects below. Feel free to make a PR to add your project to this list.
 | 
			
		||||
 | 
			
		||||
- [MatterLink](https://github.com/elytra/MatterLink) (Matterbridge link for Minecraft Server chat)
 | 
			
		||||
- [pyCord](https://github.com/NikkyAI/pyCord) (crossplatform chatbot)
 | 
			
		||||
- [Mattereddit](https://github.com/bonehurtingjuice/mattereddit) (Reddit chat support)
 | 
			
		||||
- [fbridge](https://github.com/VictorNine/fbridge) (Facebook messenger support)
 | 
			
		||||
- [matterbabble](https://github.com/DeclanHoare/matterbabble) (Discourse support)
 | 
			
		||||
- [MatterAMXX](https://forums.alliedmods.net/showthread.php?t=319430) (Counter-Strike, half-life and more via AMXX mod)
 | 
			
		||||
* [MatterLink](https://github.com/elytra/MatterLink) (Matterbridge link for Minecraft Server chat)
 | 
			
		||||
* [pyCord](https://github.com/NikkyAI/pyCord) (crossplatform chatbot)
 | 
			
		||||
* [Mattereddit](https://github.com/bonehurtingjuice/mattereddit) (Reddit chat support)
 | 
			
		||||
* [fbridge](https://github.com/VictorNine/fbridge) (Facebook messenger support)
 | 
			
		||||
* [matterbabble](https://github.com/DeclanHoare/matterbabble) (Discourse support)
 | 
			
		||||
 | 
			
		||||
## Chat with us
 | 
			
		||||
 | 
			
		||||
Questions or want to test on your favorite platform? Join below:
 | 
			
		||||
 | 
			
		||||
- [Discord][mb-discord]
 | 
			
		||||
- [Gitter][mb-gitter]
 | 
			
		||||
- [IRC][mb-irc]
 | 
			
		||||
- [Keybase][mb-keybase]
 | 
			
		||||
- [Matrix][mb-matrix]
 | 
			
		||||
- [Mattermost][mb-mattermost]
 | 
			
		||||
- [Rocket.Chat][mb-rocketchat]
 | 
			
		||||
- [Slack][mb-slack]
 | 
			
		||||
- [Telegram][mb-telegram]
 | 
			
		||||
- [Twitch][mb-twitch]
 | 
			
		||||
- [XMPP][mb-xmpp] (matterbridge@conference.jabber.de)
 | 
			
		||||
- [Zulip][mb-zulip]
 | 
			
		||||
* [Gitter][mb-gitter]
 | 
			
		||||
* [IRC][mb-irc]
 | 
			
		||||
* [Discord][mb-discord]
 | 
			
		||||
* [Matrix][mb-matrix]
 | 
			
		||||
* [Slack][mb-slack]
 | 
			
		||||
* [Mattermost][mb-mattermost]
 | 
			
		||||
* [Rocket.Chat][mb-rocketchat]
 | 
			
		||||
* [XMPP][mb-xmpp]
 | 
			
		||||
* [Twitch][mb-twitch]
 | 
			
		||||
* [Zulip][mb-zulip]
 | 
			
		||||
* [Telegram][mb-telegram]
 | 
			
		||||
 | 
			
		||||
## Screenshots
 | 
			
		||||
See https://github.com/42wim/matterbridge/wiki
 | 
			
		||||
 | 
			
		||||
See <https://github.com/42wim/matterbridge/wiki>
 | 
			
		||||
 | 
			
		||||
## Installing / upgrading
 | 
			
		||||
 | 
			
		||||
## Installing
 | 
			
		||||
### Binaries
 | 
			
		||||
 | 
			
		||||
- Latest stable release [v1.19.0](https://github.com/42wim/matterbridge/releases/latest)
 | 
			
		||||
- Development releases (follows master) can be downloaded [here](https://github.com/42wim/matterbridge/actions) selecting the latest green build and then artifacts.
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
* Latest stable release [v1.14.2](https://github.com/42wim/matterbridge/releases/latest)
 | 
			
		||||
* Development releases (follows master) can be downloaded [here](https://dl.bintray.com/42wim/nightly/)
 | 
			
		||||
 | 
			
		||||
### Packages
 | 
			
		||||
* [Overview](https://repology.org/metapackage/matterbridge/versions)
 | 
			
		||||
 | 
			
		||||
- [Overview](https://repology.org/metapackage/matterbridge/versions)
 | 
			
		||||
- [snap](https://snapcraft.io/matterbridge)
 | 
			
		||||
### Building
 | 
			
		||||
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).
 | 
			
		||||
 | 
			
		||||
## Building
 | 
			
		||||
After Go is setup, download matterbridge to your $GOPATH directory.
 | 
			
		||||
 | 
			
		||||
Most people just want to use binaries, you can find those [here](https://github.com/42wim/matterbridge/releases/latest)
 | 
			
		||||
 | 
			
		||||
If you really want to build from source, follow these instructions:
 | 
			
		||||
Go 1.12+ is required. Make sure you have [Go](https://golang.org/doc/install) properly installed.
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
```
 | 
			
		||||
cd $GOPATH
 | 
			
		||||
go get github.com/42wim/matterbridge
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
You should now have matterbridge binary in the ~/go/bin directory:
 | 
			
		||||
You should now have matterbridge binary in the bin directory:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
$ ls ~/go/bin/
 | 
			
		||||
```
 | 
			
		||||
$ ls bin/
 | 
			
		||||
matterbridge
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Configuration
 | 
			
		||||
 | 
			
		||||
### Basic configuration
 | 
			
		||||
 | 
			
		||||
See [howto](https://github.com/42wim/matterbridge/wiki/How-to-create-your-config) for a step by step walkthrough for creating your configuration.
 | 
			
		||||
 | 
			
		||||
### Settings
 | 
			
		||||
 | 
			
		||||
All possible [settings](https://github.com/42wim/matterbridge/wiki/Settings) for each bridge.
 | 
			
		||||
 | 
			
		||||
### Advanced configuration
 | 
			
		||||
 | 
			
		||||
- [matterbridge.toml.sample](https://github.com/42wim/matterbridge/blob/master/matterbridge.toml.sample) for documentation and an example.
 | 
			
		||||
* [matterbridge.toml.sample](https://github.com/42wim/matterbridge/blob/master/matterbridge.toml.sample) for documentation and an example.
 | 
			
		||||
 | 
			
		||||
### Examples
 | 
			
		||||
 | 
			
		||||
#### Bridge mattermost (off-topic) - irc (#testing)
 | 
			
		||||
 | 
			
		||||
```toml
 | 
			
		||||
[irc]
 | 
			
		||||
    [irc.freenode]
 | 
			
		||||
@@ -227,7 +189,6 @@ enable=true
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### Bridge slack (#general) - discord (general)
 | 
			
		||||
 | 
			
		||||
```toml
 | 
			
		||||
[slack]
 | 
			
		||||
[slack.test]
 | 
			
		||||
@@ -259,7 +220,7 @@ RemoteNickFormat="[{PROTOCOL}/{BRIDGE}] <{NICK}> "
 | 
			
		||||
 | 
			
		||||
See [howto](https://github.com/42wim/matterbridge/wiki/How-to-create-your-config) for a step by step walkthrough for creating your configuration.
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
```
 | 
			
		||||
Usage of ./matterbridge:
 | 
			
		||||
  -conf string
 | 
			
		||||
        config file (default "matterbridge.toml")
 | 
			
		||||
@@ -272,11 +233,12 @@ Usage of ./matterbridge:
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Docker
 | 
			
		||||
 | 
			
		||||
Please take a look at the [Docker Wiki page](https://github.com/42wim/matterbridge/wiki/Deploy:-Docker) for more information.
 | 
			
		||||
Create your matterbridge.toml file locally eg in `/tmp/matterbridge.toml`
 | 
			
		||||
```
 | 
			
		||||
docker run -ti -v /tmp/matterbridge.toml:/matterbridge.toml 42wim/matterbridge
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Changelog
 | 
			
		||||
 | 
			
		||||
See [changelog.md](https://github.com/42wim/matterbridge/blob/master/changelog.md)
 | 
			
		||||
 | 
			
		||||
## FAQ
 | 
			
		||||
@@ -284,30 +246,28 @@ See [changelog.md](https://github.com/42wim/matterbridge/blob/master/changelog.m
 | 
			
		||||
See [FAQ](https://github.com/42wim/matterbridge/wiki/FAQ)
 | 
			
		||||
 | 
			
		||||
## Related projects
 | 
			
		||||
 | 
			
		||||
- [jwflory/ansible-role-matterbridge](https://galaxy.ansible.com/jwflory/matterbridge) (Ansible role to simplify deploying Matterbridge)
 | 
			
		||||
- [matterbridge autoconfig](https://github.com/patcon/matterbridge-autoconfig)
 | 
			
		||||
- [matterbridge config viewer](https://github.com/patcon/matterbridge-heroku-viewer)
 | 
			
		||||
- [matterbridge-heroku](https://github.com/cadecairos/matterbridge-heroku)
 | 
			
		||||
- [mattereddit](https://github.com/bonehurtingjuice/mattereddit)
 | 
			
		||||
- [matterlink](https://github.com/elytra/MatterLink)
 | 
			
		||||
- [mattermost-plugin](https://github.com/matterbridge/mattermost-plugin) - Run matterbridge as a plugin in mattermost
 | 
			
		||||
- [pyCord](https://github.com/NikkyAI/pyCord) (crossplatform chatbot)
 | 
			
		||||
- [fbridge](https://github.com/VictorNine/fbridge) (Facebook messenger support)
 | 
			
		||||
- [isla](https://github.com/alphachung/isla) (Bot for Discord-Telegram groups used alongside matterbridge)
 | 
			
		||||
- [matterbabble](https://github.com/DeclanHoare/matterbabble) (Connect Discourse threads to Matterbridge)
 | 
			
		||||
* [FOSSRIT/infrastructure - roles/matterbridge](https://github.com/FOSSRIT/infrastructure/tree/master/roles/matterbridge) (Ansible role used to automate deployments of Matterbridge)
 | 
			
		||||
* [matterbridge autoconfig](https://github.com/patcon/matterbridge-autoconfig)
 | 
			
		||||
* [matterbridge config viewer](https://github.com/patcon/matterbridge-heroku-viewer)
 | 
			
		||||
* [matterbridge-heroku](https://github.com/cadecairos/matterbridge-heroku)
 | 
			
		||||
* [mattereddit](https://github.com/bonehurtingjuice/mattereddit)
 | 
			
		||||
* [matterlink](https://github.com/elytra/MatterLink)
 | 
			
		||||
* [mattermost-plugin](https://github.com/matterbridge/mattermost-plugin) - Run matterbridge as a plugin in mattermost
 | 
			
		||||
* [pyCord](https://github.com/NikkyAI/pyCord) (crossplatform chatbot)
 | 
			
		||||
* [fbridge](https://github.com/VictorNine/fbridge) (Facebook messenger support)
 | 
			
		||||
* [isla](https://github.com/alphachung/isla) (Bot for Discord-Telegram groups used alongside matterbridge)
 | 
			
		||||
* [matterbabble](https://github.com/DeclanHoare/matterbabble) (Connect Discourse threads to Matterbridge)
 | 
			
		||||
 | 
			
		||||
## Articles
 | 
			
		||||
 | 
			
		||||
- [matterbridge on kubernetes](https://medium.freecodecamp.org/using-kubernetes-to-deploy-a-chat-gateway-or-when-technology-works-like-its-supposed-to-a169a8cd69a3)
 | 
			
		||||
- <https://mattermost.com/blog/connect-irc-to-mattermost/>
 | 
			
		||||
- <https://blog.valvin.fr/2016/09/17/mattermost-et-un-channel-irc-cest-possible/>
 | 
			
		||||
- <https://blog.brightscout.com/top-10-mattermost-integrations/>
 | 
			
		||||
- <https://www.algoo.fr/blog/2018/01/19/recouvrez-votre-liberte-en-quittant-slack-pour-un-mattermost-auto-heberge/>
 | 
			
		||||
- <https://kopano.com/blog/matterbridge-bridging-mattermost-chat/>
 | 
			
		||||
- <https://www.stitcher.com/s/?eid=52382713>
 | 
			
		||||
- <https://daniele.tech/2019/02/how-to-use-matterbridge-to-connect-2-different-slack-workspaces/>
 | 
			
		||||
- <https://userlinux.net/mattermost-and-matterbridge.html>
 | 
			
		||||
* [matterbridge on kubernetes](https://medium.freecodecamp.org/using-kubernetes-to-deploy-a-chat-gateway-or-when-technology-works-like-its-supposed-to-a169a8cd69a3)
 | 
			
		||||
* https://mattermost.com/blog/connect-irc-to-mattermost/
 | 
			
		||||
* https://blog.valvin.fr/2016/09/17/mattermost-et-un-channel-irc-cest-possible/
 | 
			
		||||
* https://blog.brightscout.com/top-10-mattermost-integrations/
 | 
			
		||||
* http://bencey.co.nz/2018/09/17/bridge/
 | 
			
		||||
* https://www.algoo.fr/blog/2018/01/19/recouvrez-votre-liberte-en-quittant-slack-pour-un-mattermost-auto-heberge/
 | 
			
		||||
* https://kopano.com/blog/matterbridge-bridging-mattermost-chat/
 | 
			
		||||
* https://www.stitcher.com/s/?eid=52382713
 | 
			
		||||
* https://daniele.tech/2019/02/how-to-use-matterbridge-to-connect-2-different-slack-workspaces/
 | 
			
		||||
 | 
			
		||||
## Thanks
 | 
			
		||||
 | 
			
		||||
@@ -319,42 +279,34 @@ See [FAQ](https://github.com/42wim/matterbridge/wiki/FAQ)
 | 
			
		||||
</p>
 | 
			
		||||
 | 
			
		||||
Matterbridge wouldn't exist without these libraries:
 | 
			
		||||
 | 
			
		||||
- discord - <https://github.com/bwmarrin/discordgo>
 | 
			
		||||
- echo - <https://github.com/labstack/echo>
 | 
			
		||||
- gitter - <https://github.com/sromku/go-gitter>
 | 
			
		||||
- gops - <https://github.com/google/gops>
 | 
			
		||||
- gozulipbot - <https://github.com/ifo/gozulipbot>
 | 
			
		||||
- gumble - <https://github.com/layeh/gumble>
 | 
			
		||||
- irc - <https://github.com/lrstanley/girc>
 | 
			
		||||
- keybase - <https://github.com/keybase/go-keybase-chat-bot>
 | 
			
		||||
- matrix - <https://github.com/matrix-org/gomatrix>
 | 
			
		||||
- mattermost - <https://github.com/mattermost/mattermost-server>
 | 
			
		||||
- msgraph.go - <https://github.com/yaegashi/msgraph.go>
 | 
			
		||||
- mumble - <https://github.com/layeh/gumble>
 | 
			
		||||
- nctalk - <https://github.com/gary-kim/go-nc-talk>
 | 
			
		||||
- slack - <https://github.com/nlopes/slack>
 | 
			
		||||
- sshchat - <https://github.com/shazow/ssh-chat>
 | 
			
		||||
- steam - <https://github.com/Philipp15b/go-steam>
 | 
			
		||||
- telegram - <https://github.com/go-telegram-bot-api/telegram-bot-api>
 | 
			
		||||
- tengo - <https://github.com/d5/tengo>
 | 
			
		||||
- whatsapp - <https://github.com/Rhymen/go-whatsapp>
 | 
			
		||||
- xmpp - <https://github.com/mattn/go-xmpp>
 | 
			
		||||
- zulip - <https://github.com/ifo/gozulipbot>
 | 
			
		||||
* discord - https://github.com/bwmarrin/discordgo
 | 
			
		||||
* echo - https://github.com/labstack/echo
 | 
			
		||||
* gitter - https://github.com/sromku/go-gitter
 | 
			
		||||
* gops - https://github.com/google/gops
 | 
			
		||||
* gozulipbot - https://github.com/ifo/gozulipbot
 | 
			
		||||
* irc - https://github.com/lrstanley/girc
 | 
			
		||||
* mattermost - https://github.com/mattermost/mattermost-server
 | 
			
		||||
* matrix - https://github.com/matrix-org/gomatrix
 | 
			
		||||
* sshchat - https://github.com/shazow/ssh-chat
 | 
			
		||||
* slack - https://github.com/nlopes/slack
 | 
			
		||||
* steam - https://github.com/Philipp15b/go-steam
 | 
			
		||||
* telegram - https://github.com/go-telegram-bot-api/telegram-bot-api
 | 
			
		||||
* xmpp - https://github.com/mattn/go-xmpp
 | 
			
		||||
* whatsapp - https://github.com/Rhymen/go-whatsapp/
 | 
			
		||||
* zulip - https://github.com/ifo/gozulipbot
 | 
			
		||||
* tengo - https://github.com/d5/tengo
 | 
			
		||||
 | 
			
		||||
<!-- Links -->
 | 
			
		||||
 | 
			
		||||
[mb-discord]: https://discord.gg/AkKPtrQ
 | 
			
		||||
   [mb-gitter]: https://gitter.im/42wim/matterbridge
 | 
			
		||||
   [mb-irc]: https://webchat.freenode.net/?channels=matterbridgechat
 | 
			
		||||
[mb-keybase]: https://keybase.io/team/matterbridge
 | 
			
		||||
   [mb-discord]: https://discord.gg/AkKPtrQ
 | 
			
		||||
   [mb-matrix]: https://riot.im/app/#/room/#matterbridge:matrix.org
 | 
			
		||||
[mb-mattermost]: https://framateam.org/signup_user_complete/?id=tfqm33ggop8x3qgu4boeieta6e
 | 
			
		||||
[mb-msteams]: https://teams.microsoft.com/join/hj92x75gd3y7
 | 
			
		||||
[mb-rocketchat]: https://open.rocket.chat/channel/matterbridge
 | 
			
		||||
   [mb-slack]: https://join.slack.com/matterbridgechat/shared_invite/MjEwODMxNjU1NDMwLTE0OTk2MTU3NTMtMzZkZmRiNDZhOA
 | 
			
		||||
[mb-telegram]: https://t.me/Matterbridge
 | 
			
		||||
   [mb-mattermost]: https://framateam.org/signup_user_complete/?id=tfqm33ggop8x3qgu4boeieta6e
 | 
			
		||||
   [mb-rocketchat]: https://open.rocket.chat/channel/matterbridge
 | 
			
		||||
   [mb-xmpp]: https://inverse.chat/
 | 
			
		||||
   [mb-twitch]: https://www.twitch.tv/matterbridge
 | 
			
		||||
   [mb-whatsapp]: https://www.whatsapp.com/
 | 
			
		||||
[mb-xmpp]: https://inverse.chat/
 | 
			
		||||
   [mb-zulip]: https://matterbridge.zulipchat.com/register/
 | 
			
		||||
   [mb-telegram]: https://t.me/Matterbridge
 | 
			
		||||
 
 | 
			
		||||
@@ -6,20 +6,17 @@ import (
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"gopkg.in/olahol/melody.v1"
 | 
			
		||||
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/labstack/echo/v4"
 | 
			
		||||
	"github.com/labstack/echo/v4/middleware"
 | 
			
		||||
	ring "github.com/zfjagann/golang-ring"
 | 
			
		||||
	"github.com/zfjagann/golang-ring"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type API struct {
 | 
			
		||||
	Messages ring.Ring
 | 
			
		||||
	sync.RWMutex
 | 
			
		||||
	*bridge.Config
 | 
			
		||||
	mrouter *melody.Melody
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Message struct {
 | 
			
		||||
@@ -35,32 +32,6 @@ func New(cfg *bridge.Config) bridge.Bridger {
 | 
			
		||||
	e := echo.New()
 | 
			
		||||
	e.HideBanner = true
 | 
			
		||||
	e.HidePort = true
 | 
			
		||||
 | 
			
		||||
	b.mrouter = melody.New()
 | 
			
		||||
	b.mrouter.HandleMessage(func(s *melody.Session, msg []byte) {
 | 
			
		||||
		message := config.Message{}
 | 
			
		||||
		err := json.Unmarshal(msg, &message)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			b.Log.Errorf("failed to decode message from byte[] '%s'", string(msg))
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		b.handleWebsocketMessage(message, s)
 | 
			
		||||
	})
 | 
			
		||||
	b.mrouter.HandleConnect(func(session *melody.Session) {
 | 
			
		||||
		greet := b.getGreeting()
 | 
			
		||||
		data, err := json.Marshal(greet)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			b.Log.Errorf("failed to encode message '%v'", greet)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		err = session.Write(data)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			b.Log.Errorf("failed to write message '%s'", string(data))
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		// TODO: send message history buffer from `b.Messages` here
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	b.Messages = ring.Ring{}
 | 
			
		||||
	if b.GetInt("Buffer") != 0 {
 | 
			
		||||
		b.Messages.SetCapacity(b.GetInt("Buffer"))
 | 
			
		||||
@@ -70,17 +41,9 @@ func New(cfg *bridge.Config) bridge.Bridger {
 | 
			
		||||
			return key == b.GetString("Token"), nil
 | 
			
		||||
		}))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Set RemoteNickFormat to a sane default
 | 
			
		||||
	if !b.IsKeySet("RemoteNickFormat") {
 | 
			
		||||
		b.Log.Debugln("RemoteNickFormat is unset, defaulting to \"{NICK}\"")
 | 
			
		||||
		b.Config.Config.Viper().Set(b.GetConfigKey("RemoteNickFormat"), "{NICK}")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	e.GET("/api/health", b.handleHealthcheck)
 | 
			
		||||
	e.GET("/api/messages", b.handleMessages)
 | 
			
		||||
	e.GET("/api/stream", b.handleStream)
 | 
			
		||||
	e.GET("/api/websocket", b.handleWebsocket)
 | 
			
		||||
	e.POST("/api/message", b.handlePostMessage)
 | 
			
		||||
	go func() {
 | 
			
		||||
		if b.GetString("BindAddress") == "" {
 | 
			
		||||
@@ -95,13 +58,13 @@ func New(cfg *bridge.Config) bridge.Bridger {
 | 
			
		||||
func (b *API) Connect() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *API) Disconnect() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
func (b *API) JoinChannel(channel config.ChannelInfo) error {
 | 
			
		||||
	return nil
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *API) Send(msg config.Message) (string, error) {
 | 
			
		||||
@@ -111,14 +74,7 @@ func (b *API) Send(msg config.Message) (string, error) {
 | 
			
		||||
	if msg.Event == config.EventMsgDelete {
 | 
			
		||||
		return "", nil
 | 
			
		||||
	}
 | 
			
		||||
	b.Log.Debugf("enqueueing message from %s on ring buffer", msg.Username)
 | 
			
		||||
	b.Messages.Enqueue(msg)
 | 
			
		||||
 | 
			
		||||
	data, err := json.Marshal(msg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Log.Errorf("failed to encode message  '%s'", msg)
 | 
			
		||||
	}
 | 
			
		||||
	_ = b.mrouter.Broadcast(data)
 | 
			
		||||
	b.Messages.Enqueue(&msg)
 | 
			
		||||
	return "", nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -150,23 +106,18 @@ func (b *API) handleMessages(c echo.Context) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *API) getGreeting() config.Message {
 | 
			
		||||
	return config.Message{
 | 
			
		||||
		Event:     config.EventAPIConnected,
 | 
			
		||||
		Timestamp: time.Now(),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *API) handleStream(c echo.Context) error {
 | 
			
		||||
	c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
 | 
			
		||||
	c.Response().WriteHeader(http.StatusOK)
 | 
			
		||||
	greet := b.getGreeting()
 | 
			
		||||
	greet := config.Message{
 | 
			
		||||
		Event:     config.EventAPIConnected,
 | 
			
		||||
		Timestamp: time.Now(),
 | 
			
		||||
	}
 | 
			
		||||
	if err := json.NewEncoder(c.Response()).Encode(greet); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	c.Response().Flush()
 | 
			
		||||
	for {
 | 
			
		||||
		// TODO: this causes issues, messages should be broadcasted to all connected clients
 | 
			
		||||
		msg := b.Messages.Dequeue()
 | 
			
		||||
		if msg != nil {
 | 
			
		||||
			if err := json.NewEncoder(c.Response()).Encode(msg); err != nil {
 | 
			
		||||
@@ -177,31 +128,3 @@ func (b *API) handleStream(c echo.Context) error {
 | 
			
		||||
		time.Sleep(200 * time.Millisecond)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *API) handleWebsocketMessage(message config.Message, s *melody.Session) {
 | 
			
		||||
	message.Channel = "api"
 | 
			
		||||
	message.Protocol = "api"
 | 
			
		||||
	message.Account = b.Account
 | 
			
		||||
	message.ID = ""
 | 
			
		||||
	message.Timestamp = time.Now()
 | 
			
		||||
 | 
			
		||||
	data, err := json.Marshal(message)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Log.Errorf("failed to encode message for loopback '%v'", message)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	_ = b.mrouter.BroadcastOthers(data, s)
 | 
			
		||||
 | 
			
		||||
	b.Log.Debugf("Sending websocket message from %s on %s to gateway", message.Username, "api")
 | 
			
		||||
	b.Remote <- message
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *API) handleWebsocket(c echo.Context) error {
 | 
			
		||||
	err := b.mrouter.HandleRequest(c.Response(), c.Request())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Log.Errorf("error in websocket handling  '%v'", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,8 @@
 | 
			
		||||
package bridge
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"log"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
@@ -43,10 +41,6 @@ type Factory func(*Config) Bridger
 | 
			
		||||
 | 
			
		||||
func New(bridge *config.Bridge) *Bridge {
 | 
			
		||||
	accInfo := strings.Split(bridge.Account, ".")
 | 
			
		||||
	if len(accInfo) != 2 {
 | 
			
		||||
		log.Fatalf("config failure, account incorrect: %s", bridge.Account)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protocol := accInfo[0]
 | 
			
		||||
	name := accInfo[1]
 | 
			
		||||
 | 
			
		||||
@@ -75,7 +69,6 @@ func (b *Bridge) joinChannels(channels map[string]config.ChannelInfo, exists map
 | 
			
		||||
	for ID, channel := range channels {
 | 
			
		||||
		if !exists[ID] {
 | 
			
		||||
			b.Log.Infof("%s: joining %s (ID: %s)", b.Account, channel.Name, ID)
 | 
			
		||||
			time.Sleep(time.Duration(b.GetInt("JoinDelay")) * time.Millisecond)
 | 
			
		||||
			err := b.JoinChannel(channel)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
@@ -86,16 +79,8 @@ func (b *Bridge) joinChannels(channels map[string]config.ChannelInfo, exists map
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bridge) GetConfigKey(key string) string {
 | 
			
		||||
	return b.Account + "." + key
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bridge) IsKeySet(key string) bool {
 | 
			
		||||
	return b.Config.IsKeySet(b.GetConfigKey(key)) || b.Config.IsKeySet("general."+key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bridge) GetBool(key string) bool {
 | 
			
		||||
	val, ok := b.Config.GetBool(b.GetConfigKey(key))
 | 
			
		||||
	val, ok := b.Config.GetBool(b.Account + "." + key)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		val, _ = b.Config.GetBool("general." + key)
 | 
			
		||||
	}
 | 
			
		||||
@@ -103,7 +88,7 @@ func (b *Bridge) GetBool(key string) bool {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bridge) GetInt(key string) int {
 | 
			
		||||
	val, ok := b.Config.GetInt(b.GetConfigKey(key))
 | 
			
		||||
	val, ok := b.Config.GetInt(b.Account + "." + key)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		val, _ = b.Config.GetInt("general." + key)
 | 
			
		||||
	}
 | 
			
		||||
@@ -111,7 +96,7 @@ func (b *Bridge) GetInt(key string) int {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bridge) GetString(key string) string {
 | 
			
		||||
	val, ok := b.Config.GetString(b.GetConfigKey(key))
 | 
			
		||||
	val, ok := b.Config.GetString(b.Account + "." + key)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		val, _ = b.Config.GetString("general." + key)
 | 
			
		||||
	}
 | 
			
		||||
@@ -119,7 +104,7 @@ func (b *Bridge) GetString(key string) string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bridge) GetStringSlice(key string) []string {
 | 
			
		||||
	val, ok := b.Config.GetStringSlice(b.GetConfigKey(key))
 | 
			
		||||
	val, ok := b.Config.GetStringSlice(b.Account + "." + key)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		val, _ = b.Config.GetStringSlice("general." + key)
 | 
			
		||||
	}
 | 
			
		||||
@@ -127,7 +112,7 @@ func (b *Bridge) GetStringSlice(key string) []string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bridge) GetStringSlice2D(key string) [][]string {
 | 
			
		||||
	val, ok := b.Config.GetStringSlice2D(b.GetConfigKey(key))
 | 
			
		||||
	val, ok := b.Config.GetStringSlice2D(b.Account + "." + key)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		val, _ = b.Config.GetStringSlice2D("general." + key)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,8 +3,6 @@ package config
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
@@ -78,29 +76,23 @@ type Protocol struct {
 | 
			
		||||
	BindAddress            string // mattermost, slack // DEPRECATED
 | 
			
		||||
	Buffer                 int    // api
 | 
			
		||||
	Charset                string // irc
 | 
			
		||||
	ClientID               string // msteams
 | 
			
		||||
	ColorNicks             bool   // only irc for now
 | 
			
		||||
	Debug                  bool   // general
 | 
			
		||||
	DebugLevel             int    // only for irc now
 | 
			
		||||
	DisableWebPagePreview  bool   // telegram
 | 
			
		||||
	EditSuffix             string // mattermost, slack, discord, telegram, gitter
 | 
			
		||||
	EditDisable            bool   // mattermost, slack, discord, telegram, gitter
 | 
			
		||||
	HTMLDisable            bool   // matrix
 | 
			
		||||
	IconURL                string // mattermost, slack
 | 
			
		||||
	IgnoreFailureOnStart   bool   // general
 | 
			
		||||
	IgnoreNicks            string // all protocols
 | 
			
		||||
	IgnoreMessages         string // all protocols
 | 
			
		||||
	Jid                    string // xmpp
 | 
			
		||||
	JoinDelay              string // all protocols
 | 
			
		||||
	Label                  string // all protocols
 | 
			
		||||
	Login                  string // mattermost, matrix
 | 
			
		||||
	LogFile                string // general
 | 
			
		||||
	MediaDownloadBlackList []string
 | 
			
		||||
	MediaDownloadPath      string // Basically MediaServerUpload, but instead of uploading it, just write it to a file on the same server.
 | 
			
		||||
	MediaDownloadSize      int    // all protocols
 | 
			
		||||
	MediaServerDownload    string
 | 
			
		||||
	MediaServerUpload      string
 | 
			
		||||
	MediaConvertTgs        string     // telegram
 | 
			
		||||
	MediaConvertWebPToPNG  bool       // telegram
 | 
			
		||||
	MessageDelay           int        // IRC, time in millisecond to wait between messages
 | 
			
		||||
	MessageFormat          string     // telegram
 | 
			
		||||
@@ -124,39 +116,31 @@ type Protocol struct {
 | 
			
		||||
	Protocol               string     // all protocols
 | 
			
		||||
	QuoteDisable           bool       // telegram
 | 
			
		||||
	QuoteFormat            string     // telegram
 | 
			
		||||
	QuoteLengthLimit       int        // telegram
 | 
			
		||||
	RejoinDelay            int        // IRC
 | 
			
		||||
	ReplaceMessages        [][]string // all protocols
 | 
			
		||||
	ReplaceNicks           [][]string // all protocols
 | 
			
		||||
	RemoteNickFormat       string     // all protocols
 | 
			
		||||
	RunCommands            []string   // IRC
 | 
			
		||||
	RunCommands            []string   // irc
 | 
			
		||||
	Server                 string     // IRC,mattermost,XMPP,discord
 | 
			
		||||
	SessionFile            string     // msteams,whatsapp
 | 
			
		||||
	ShowJoinPart           bool       // all protocols
 | 
			
		||||
	ShowTopicChange        bool       // slack
 | 
			
		||||
	ShowUserTyping         bool       // slack
 | 
			
		||||
	ShowEmbeds             bool       // discord
 | 
			
		||||
	SkipTLSVerify          bool       // IRC, mattermost
 | 
			
		||||
	SkipVersionCheck       bool       // mattermost
 | 
			
		||||
	StripNick              bool       // all protocols
 | 
			
		||||
	StripMarkdown          bool       // irc
 | 
			
		||||
	SyncTopic              bool       // slack
 | 
			
		||||
	TengoModifyMessage     string     // general
 | 
			
		||||
	Team                   string     // mattermost, keybase
 | 
			
		||||
	TeamID                 string     // msteams
 | 
			
		||||
	TenantID               string     // msteams
 | 
			
		||||
	Team                   string     // mattermost
 | 
			
		||||
	Token                  string     // gitter, slack, discord, api
 | 
			
		||||
	Topic                  string     // zulip
 | 
			
		||||
	URL                    string     // mattermost, slack // DEPRECATED
 | 
			
		||||
	UseAPI                 bool       // mattermost, slack
 | 
			
		||||
	UseLocalAvatar         []string   // discord
 | 
			
		||||
	UseSASL                bool       // IRC
 | 
			
		||||
	UseTLS                 bool       // IRC
 | 
			
		||||
	UseDiscriminator       bool       // discord
 | 
			
		||||
	UseFirstName           bool       // telegram
 | 
			
		||||
	UseUserName            bool       // discord
 | 
			
		||||
	UseInsecureURL         bool       // telegram
 | 
			
		||||
	VerboseJoinPart        bool       // IRC
 | 
			
		||||
	WebhookBindAddress     string     // mattermost, slack
 | 
			
		||||
	WebhookURL             string     // mattermost, slack
 | 
			
		||||
}
 | 
			
		||||
@@ -182,13 +166,6 @@ type Gateway struct {
 | 
			
		||||
	InOut  []Bridge
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Tengo struct {
 | 
			
		||||
	InMessage        string
 | 
			
		||||
	Message          string
 | 
			
		||||
	RemoteNickFormat string
 | 
			
		||||
	OutMessage       string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SameChannelGateway struct {
 | 
			
		||||
	Name     string
 | 
			
		||||
	Enable   bool
 | 
			
		||||
@@ -212,18 +189,13 @@ type BridgeValues struct {
 | 
			
		||||
	SSHChat            map[string]Protocol
 | 
			
		||||
	WhatsApp           map[string]Protocol // TODO is this struct used? Search for "SlackLegacy" for example didn't return any results
 | 
			
		||||
	Zulip              map[string]Protocol
 | 
			
		||||
	Keybase            map[string]Protocol
 | 
			
		||||
	Mumble             map[string]Protocol
 | 
			
		||||
	General            Protocol
 | 
			
		||||
	Tengo              Tengo
 | 
			
		||||
	Gateway            []Gateway
 | 
			
		||||
	SameChannelGateway []SameChannelGateway
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Config interface {
 | 
			
		||||
	Viper() *viper.Viper
 | 
			
		||||
	BridgeValues() *BridgeValues
 | 
			
		||||
	IsKeySet(key string) bool
 | 
			
		||||
	GetBool(key string) (bool, bool)
 | 
			
		||||
	GetInt(key string) (int, bool)
 | 
			
		||||
	GetString(key string) (string, bool)
 | 
			
		||||
@@ -249,17 +221,7 @@ func NewConfig(rootLogger *logrus.Logger, cfgfile string) Config {
 | 
			
		||||
		logger.Fatalf("Failed to read configuration file: %#v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cfgtype := detectConfigType(cfgfile)
 | 
			
		||||
	mycfg := newConfigFromString(logger, input, cfgtype)
 | 
			
		||||
	if mycfg.cv.General.LogFile != "" {
 | 
			
		||||
		logfile, err := os.OpenFile(mycfg.cv.General.LogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			logger.Info("Opening log file ", mycfg.cv.General.LogFile)
 | 
			
		||||
			rootLogger.Out = logfile
 | 
			
		||||
		} else {
 | 
			
		||||
			logger.Warn("Failed to open ", mycfg.cv.General.LogFile)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	mycfg := newConfigFromString(logger, input)
 | 
			
		||||
	if mycfg.cv.General.MediaDownloadSize == 0 {
 | 
			
		||||
		mycfg.cv.General.MediaDownloadSize = 1000000
 | 
			
		||||
	}
 | 
			
		||||
@@ -270,37 +232,25 @@ func NewConfig(rootLogger *logrus.Logger, cfgfile string) Config {
 | 
			
		||||
	return mycfg
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// detectConfigType detects JSON and YAML formats, defaults to TOML.
 | 
			
		||||
func detectConfigType(cfgfile string) string {
 | 
			
		||||
	fileExt := filepath.Ext(cfgfile)
 | 
			
		||||
	switch fileExt {
 | 
			
		||||
	case ".json":
 | 
			
		||||
		return "json"
 | 
			
		||||
	case ".yaml", ".yml":
 | 
			
		||||
		return "yaml"
 | 
			
		||||
	}
 | 
			
		||||
	return "toml"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewConfigFromString instantiates a new configuration based on the specified string.
 | 
			
		||||
func NewConfigFromString(rootLogger *logrus.Logger, input []byte) Config {
 | 
			
		||||
	logger := rootLogger.WithFields(logrus.Fields{"prefix": "config"})
 | 
			
		||||
	return newConfigFromString(logger, input, "toml")
 | 
			
		||||
	return newConfigFromString(logger, input)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newConfigFromString(logger *logrus.Entry, input []byte, cfgtype string) *config {
 | 
			
		||||
	viper.SetConfigType(cfgtype)
 | 
			
		||||
func newConfigFromString(logger *logrus.Entry, input []byte) *config {
 | 
			
		||||
	viper.SetConfigType("toml")
 | 
			
		||||
	viper.SetEnvPrefix("matterbridge")
 | 
			
		||||
	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
 | 
			
		||||
	viper.AutomaticEnv()
 | 
			
		||||
 | 
			
		||||
	if err := viper.ReadConfig(bytes.NewBuffer(input)); err != nil {
 | 
			
		||||
		logger.Fatalf("Failed to parse the configuration: %s", err)
 | 
			
		||||
		logger.Fatalf("Failed to parse the configuration: %#v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cfg := &BridgeValues{}
 | 
			
		||||
	if err := viper.Unmarshal(cfg); err != nil {
 | 
			
		||||
		logger.Fatalf("Failed to load the configuration: %s", err)
 | 
			
		||||
		logger.Fatalf("Failed to load the configuration: %#v", err)
 | 
			
		||||
	}
 | 
			
		||||
	return &config{
 | 
			
		||||
		logger: logger,
 | 
			
		||||
@@ -313,16 +263,6 @@ func (c *config) BridgeValues() *BridgeValues {
 | 
			
		||||
	return c.cv
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *config) Viper() *viper.Viper {
 | 
			
		||||
	return c.v
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *config) IsKeySet(key string) bool {
 | 
			
		||||
	c.RLock()
 | 
			
		||||
	defer c.RUnlock()
 | 
			
		||||
	return c.v.IsSet(key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *config) GetBool(key string) (bool, bool) {
 | 
			
		||||
	c.RLock()
 | 
			
		||||
	defer c.RUnlock()
 | 
			
		||||
@@ -382,11 +322,6 @@ type TestConfig struct {
 | 
			
		||||
	Overrides map[string]interface{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *TestConfig) IsKeySet(key string) bool {
 | 
			
		||||
	_, ok := c.Overrides[key]
 | 
			
		||||
	return ok || c.Config.IsKeySet(key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *TestConfig) GetBool(key string) (bool, bool) {
 | 
			
		||||
	val, ok := c.Overrides[key]
 | 
			
		||||
	if ok {
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ import (
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/helper"
 | 
			
		||||
	"github.com/matterbridge/discordgo"
 | 
			
		||||
	"github.com/bwmarrin/discordgo"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const MessageLength = 1950
 | 
			
		||||
@@ -21,7 +21,7 @@ type Bdiscord struct {
 | 
			
		||||
	c *discordgo.Session
 | 
			
		||||
 | 
			
		||||
	nick            string
 | 
			
		||||
	userID          string
 | 
			
		||||
	useChannelID    bool
 | 
			
		||||
	guildID         string
 | 
			
		||||
	webhookID       string
 | 
			
		||||
	webhookToken    string
 | 
			
		||||
@@ -34,8 +34,6 @@ type Bdiscord struct {
 | 
			
		||||
	membersMutex  sync.RWMutex
 | 
			
		||||
	userMemberMap map[string]*discordgo.Member
 | 
			
		||||
	nickMemberMap map[string]*discordgo.Member
 | 
			
		||||
	webhookCache  map[string]string
 | 
			
		||||
	webhookMutex  sync.RWMutex
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func New(cfg *bridge.Config) bridge.Bridger {
 | 
			
		||||
@@ -43,7 +41,6 @@ func New(cfg *bridge.Config) bridge.Bridger {
 | 
			
		||||
	b.userMemberMap = make(map[string]*discordgo.Member)
 | 
			
		||||
	b.nickMemberMap = make(map[string]*discordgo.Member)
 | 
			
		||||
	b.channelInfoMap = make(map[string]*config.ChannelInfo)
 | 
			
		||||
	b.webhookCache = make(map[string]string)
 | 
			
		||||
	if b.GetString("WebhookURL") != "" {
 | 
			
		||||
		b.Log.Debug("Configuring Discord Incoming Webhook")
 | 
			
		||||
		b.webhookID, b.webhookToken = b.splitURL(b.GetString("WebhookURL"))
 | 
			
		||||
@@ -75,11 +72,9 @@ func (b *Bdiscord) Connect() error {
 | 
			
		||||
	}
 | 
			
		||||
	b.Log.Info("Connection succeeded")
 | 
			
		||||
	b.c.AddHandler(b.messageCreate)
 | 
			
		||||
	b.c.AddHandler(b.messageTyping)
 | 
			
		||||
	b.c.AddHandler(b.memberUpdate)
 | 
			
		||||
	b.c.AddHandler(b.messageUpdate)
 | 
			
		||||
	b.c.AddHandler(b.messageDelete)
 | 
			
		||||
	b.c.AddHandler(b.messageDeleteBulk)
 | 
			
		||||
	b.c.AddHandler(b.memberAdd)
 | 
			
		||||
	b.c.AddHandler(b.memberRemove)
 | 
			
		||||
	err = b.c.Open()
 | 
			
		||||
@@ -96,16 +91,15 @@ func (b *Bdiscord) Connect() error {
 | 
			
		||||
	}
 | 
			
		||||
	serverName := strings.Replace(b.GetString("Server"), "ID:", "", -1)
 | 
			
		||||
	b.nick = userinfo.Username
 | 
			
		||||
	b.userID = userinfo.ID
 | 
			
		||||
	b.channelsMutex.Lock()
 | 
			
		||||
	for _, guild := range guilds {
 | 
			
		||||
		if guild.Name == serverName || guild.ID == serverName {
 | 
			
		||||
			b.channels, err = b.c.GuildChannels(guild.ID)
 | 
			
		||||
			b.guildID = guild.ID
 | 
			
		||||
			guildFound = true
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			b.guildID = guild.ID
 | 
			
		||||
			guildFound = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	b.channelsMutex.Unlock()
 | 
			
		||||
@@ -119,37 +113,30 @@ func (b *Bdiscord) Connect() error {
 | 
			
		||||
			b.Log.Infof("Server=\"%s\" # Server ID", guild.ID)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b.channelsMutex.RLock()
 | 
			
		||||
	if b.GetString("WebhookURL") == "" {
 | 
			
		||||
		for _, channel := range b.channels {
 | 
			
		||||
			b.Log.Debugf("found channel %#v", channel)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		b.canEditWebhooks = true
 | 
			
		||||
		for _, channel := range b.channels {
 | 
			
		||||
			b.Log.Debugf("found channel %#v; verifying PermissionManageWebhooks", channel)
 | 
			
		||||
			perms, permsErr := b.c.State.UserChannelPermissions(userinfo.ID, channel.ID)
 | 
			
		||||
			manageWebhooks := discordgo.PermissionManageWebhooks
 | 
			
		||||
		var channelsDenied []string
 | 
			
		||||
		for _, info := range b.Channels {
 | 
			
		||||
			id := b.getChannelID(info.Name) // note(qaisjp): this readlocks channelsMutex
 | 
			
		||||
			b.Log.Debugf("Verifying PermissionManageWebhooks for %s with ID %s", info.ID, id)
 | 
			
		||||
 | 
			
		||||
			perms, permsErr := b.c.UserChannelPermissions(userinfo.ID, id)
 | 
			
		||||
			if permsErr != nil {
 | 
			
		||||
				b.Log.Warnf("Failed to check PermissionManageWebhooks in channel \"%s\": %s", info.Name, permsErr.Error())
 | 
			
		||||
			} else if perms&manageWebhooks == manageWebhooks {
 | 
			
		||||
				continue
 | 
			
		||||
			if permsErr != nil || perms&manageWebhooks != manageWebhooks {
 | 
			
		||||
				b.Log.Warnf("Can't manage webhooks in channel \"%s\"", channel.Name)
 | 
			
		||||
				b.canEditWebhooks = false
 | 
			
		||||
			}
 | 
			
		||||
			channelsDenied = append(channelsDenied, fmt.Sprintf("%#v", info.Name))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		b.canEditWebhooks = len(channelsDenied) == 0
 | 
			
		||||
		if b.canEditWebhooks {
 | 
			
		||||
			b.Log.Info("Can manage webhooks; will edit channel for global webhook on send")
 | 
			
		||||
		} else {
 | 
			
		||||
			b.Log.Warn("Can't manage webhooks; won't edit channel for global webhook on send")
 | 
			
		||||
			b.Log.Warn("Can't manage webhooks in channels: ", strings.Join(channelsDenied, ", "))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	b.channelsMutex.RUnlock()
 | 
			
		||||
@@ -185,27 +172,21 @@ func (b *Bdiscord) JoinChannel(channel config.ChannelInfo) error {
 | 
			
		||||
	defer b.channelsMutex.Unlock()
 | 
			
		||||
 | 
			
		||||
	b.channelInfoMap[channel.ID] = &channel
 | 
			
		||||
	idcheck := strings.Split(channel.Name, "ID:")
 | 
			
		||||
	if len(idcheck) > 1 {
 | 
			
		||||
		b.useChannelID = true
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bdiscord) Send(msg config.Message) (string, error) {
 | 
			
		||||
	b.Log.Debugf("=> Receiving %#v", msg)
 | 
			
		||||
 | 
			
		||||
	origMsgID := msg.ID
 | 
			
		||||
 | 
			
		||||
	channelID := b.getChannelID(msg.Channel)
 | 
			
		||||
	if channelID == "" {
 | 
			
		||||
		return "", fmt.Errorf("Could not find channelID for %v", msg.Channel)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if msg.Event == config.EventUserTyping {
 | 
			
		||||
		if b.GetBool("ShowUserTyping") {
 | 
			
		||||
			err := b.c.ChannelTyping(channelID)
 | 
			
		||||
			return "", err
 | 
			
		||||
		}
 | 
			
		||||
		return "", nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Make a action /me of the message
 | 
			
		||||
	if msg.Event == config.EventUserAction {
 | 
			
		||||
		msg.Text = "_" + msg.Text + "_"
 | 
			
		||||
@@ -227,27 +208,26 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
 | 
			
		||||
	b.channelsMutex.RUnlock()
 | 
			
		||||
 | 
			
		||||
	// Use webhook to send the message
 | 
			
		||||
	if wID != "" && msg.Event != config.EventMsgDelete {
 | 
			
		||||
	if wID != "" {
 | 
			
		||||
		// skip events
 | 
			
		||||
		if msg.Event != "" && msg.Event != config.EventUserAction && msg.Event != config.EventJoinLeave && msg.Event != config.EventTopicChange {
 | 
			
		||||
		if msg.Event != "" && msg.Event != config.EventJoinLeave && msg.Event != config.EventTopicChange {
 | 
			
		||||
			return "", nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// If we are editing a message, delete the old message
 | 
			
		||||
		if msg.ID != "" {
 | 
			
		||||
			msg.ID = b.getCacheID(msg.ID)
 | 
			
		||||
			b.Log.Debugf("Deleting edited webhook message")
 | 
			
		||||
			err := b.c.ChannelMessageDelete(channelID, msg.ID)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				b.Log.Errorf("Could not delete edited webhook message: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		b.Log.Debugf("Broadcasting using Webhook")
 | 
			
		||||
 | 
			
		||||
		for _, f := range msg.Extra["file"] {
 | 
			
		||||
			fi := f.(config.FileInfo)
 | 
			
		||||
			if fi.Comment != "" {
 | 
			
		||||
				msg.Text += fi.Comment + ": "
 | 
			
		||||
			}
 | 
			
		||||
			if fi.URL != "" {
 | 
			
		||||
				msg.Text = fi.URL
 | 
			
		||||
				if fi.Comment != "" {
 | 
			
		||||
					msg.Text = fi.Comment + ": " + fi.URL
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		// skip empty messages
 | 
			
		||||
		if msg.Text == "" && (msg.Extra == nil || len(msg.Extra["file"]) == 0) {
 | 
			
		||||
			b.Log.Debugf("Skipping empty message %#v", msg)
 | 
			
		||||
		if msg.Text == "" {
 | 
			
		||||
			return "", nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -270,19 +250,17 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		b.Log.Debugf("Processing webhook sending for message %#v", msg)
 | 
			
		||||
		msg, err := b.webhookSend(&msg, wID, wToken)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			b.Log.Errorf("Could not broadcast via webook for message %#v: %s", msg, err)
 | 
			
		||||
		err := b.c.WebhookExecute(
 | 
			
		||||
			wID,
 | 
			
		||||
			wToken,
 | 
			
		||||
			true,
 | 
			
		||||
			&discordgo.WebhookParams{
 | 
			
		||||
				Content:   msg.Text,
 | 
			
		||||
				Username:  msg.Username,
 | 
			
		||||
				AvatarURL: msg.Avatar,
 | 
			
		||||
			})
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
		if msg == nil {
 | 
			
		||||
			return "", nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		b.updateCacheID(origMsgID, msg.ID)
 | 
			
		||||
		return msg.ID, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b.Log.Debugf("Broadcasting using token (API)")
 | 
			
		||||
 | 
			
		||||
@@ -291,7 +269,6 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
 | 
			
		||||
		if msg.ID == "" {
 | 
			
		||||
			return "", nil
 | 
			
		||||
		}
 | 
			
		||||
		msg.ID = b.getCacheID(msg.ID)
 | 
			
		||||
		err := b.c.ChannelMessageDelete(channelID, msg.ID)
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
@@ -324,7 +301,7 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	return res.ID, nil
 | 
			
		||||
	return res.ID, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// useWebhook returns true if we have a webhook defined somewhere
 | 
			
		||||
@@ -388,83 +365,3 @@ func (b *Bdiscord) handleUploadFile(msg *config.Message, channelID string) (stri
 | 
			
		||||
	}
 | 
			
		||||
	return "", nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// webhookSend send one or more message via webhook, taking care of file
 | 
			
		||||
// uploads (from slack, telegram or mattermost).
 | 
			
		||||
// Returns messageID and error.
 | 
			
		||||
func (b *Bdiscord) webhookSend(msg *config.Message, webhookID, token string) (*discordgo.Message, error) {
 | 
			
		||||
	var (
 | 
			
		||||
		res *discordgo.Message
 | 
			
		||||
		err error
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	// If avatar is unset, check if UseLocalAvatar contains the message's
 | 
			
		||||
	// account or protocol, and if so, try to find a local avatar
 | 
			
		||||
	if msg.Avatar == "" {
 | 
			
		||||
		for _, val := range b.GetStringSlice("UseLocalAvatar") {
 | 
			
		||||
			if msg.Protocol == val || msg.Account == val {
 | 
			
		||||
				if avatar := b.findAvatar(msg); avatar != "" {
 | 
			
		||||
					msg.Avatar = avatar
 | 
			
		||||
				}
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// WebhookParams can have either `Content` or `File`.
 | 
			
		||||
 | 
			
		||||
	// We can't send empty messages.
 | 
			
		||||
	if msg.Text != "" {
 | 
			
		||||
		res, err = b.c.WebhookExecute(
 | 
			
		||||
			webhookID,
 | 
			
		||||
			token,
 | 
			
		||||
			true,
 | 
			
		||||
			&discordgo.WebhookParams{
 | 
			
		||||
				Content:   msg.Text,
 | 
			
		||||
				Username:  msg.Username,
 | 
			
		||||
				AvatarURL: msg.Avatar,
 | 
			
		||||
			},
 | 
			
		||||
		)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			b.Log.Errorf("Could not send text (%s) for message %#v: %s", msg.Text, msg, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if msg.Extra != nil {
 | 
			
		||||
		for _, f := range msg.Extra["file"] {
 | 
			
		||||
			fi := f.(config.FileInfo)
 | 
			
		||||
			file := discordgo.File{
 | 
			
		||||
				Name:        fi.Name,
 | 
			
		||||
				ContentType: "",
 | 
			
		||||
				Reader:      bytes.NewReader(*fi.Data),
 | 
			
		||||
			}
 | 
			
		||||
			content := ""
 | 
			
		||||
			if msg.Text == "" {
 | 
			
		||||
				content = fi.Comment
 | 
			
		||||
			}
 | 
			
		||||
			_, e2 := b.c.WebhookExecute(
 | 
			
		||||
				webhookID,
 | 
			
		||||
				token,
 | 
			
		||||
				false,
 | 
			
		||||
				&discordgo.WebhookParams{
 | 
			
		||||
					Username:  msg.Username,
 | 
			
		||||
					AvatarURL: msg.Avatar,
 | 
			
		||||
					File:      &file,
 | 
			
		||||
					Content:   content,
 | 
			
		||||
				},
 | 
			
		||||
			)
 | 
			
		||||
			if e2 != nil {
 | 
			
		||||
				b.Log.Errorf("Could not send file %#v for message %#v: %s", file, msg, e2)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	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,50 +2,20 @@ package bdiscord
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/matterbridge/discordgo"
 | 
			
		||||
	"github.com/bwmarrin/discordgo"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (b *Bdiscord) messageDelete(s *discordgo.Session, m *discordgo.MessageDelete) { //nolint:unparam
 | 
			
		||||
	rmsg := config.Message{Account: b.Account, ID: m.ID, Event: config.EventMsgDelete, Text: config.EventMsgDelete}
 | 
			
		||||
	rmsg.Channel = b.getChannelName(m.ChannelID)
 | 
			
		||||
 | 
			
		||||
	if b.useChannelID {
 | 
			
		||||
		rmsg.Channel = "ID:" + m.ChannelID
 | 
			
		||||
	}
 | 
			
		||||
	b.Log.Debugf("<= Sending message from %s to gateway", b.Account)
 | 
			
		||||
	b.Log.Debugf("<= Message is %#v", rmsg)
 | 
			
		||||
	b.Remote <- rmsg
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO(qaisjp): if other bridges support bulk deletions, it could be fanned out centrally
 | 
			
		||||
func (b *Bdiscord) messageDeleteBulk(s *discordgo.Session, m *discordgo.MessageDeleteBulk) { //nolint:unparam
 | 
			
		||||
	for _, msgID := range m.Messages {
 | 
			
		||||
		rmsg := config.Message{
 | 
			
		||||
			Account: b.Account,
 | 
			
		||||
			ID:      msgID,
 | 
			
		||||
			Event:   config.EventMsgDelete,
 | 
			
		||||
			Text:    config.EventMsgDelete,
 | 
			
		||||
			Channel: b.getChannelName(m.ChannelID),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		b.Log.Debugf("<= Sending message from %s to gateway", b.Account)
 | 
			
		||||
		b.Log.Debugf("<= Message is %#v", rmsg)
 | 
			
		||||
		b.Remote <- rmsg
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bdiscord) messageTyping(s *discordgo.Session, m *discordgo.TypingStart) {
 | 
			
		||||
	if !b.GetBool("ShowUserTyping") {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Ignore our own typing messages
 | 
			
		||||
	if m.UserID == b.userID {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rmsg := config.Message{Account: b.Account, Event: config.EventUserTyping}
 | 
			
		||||
	rmsg.Channel = b.getChannelName(m.ChannelID)
 | 
			
		||||
	b.Remote <- rmsg
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) { //nolint:unparam
 | 
			
		||||
	if b.GetBool("EditDisable") {
 | 
			
		||||
		return
 | 
			
		||||
@@ -54,10 +24,7 @@ func (b *Bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdat
 | 
			
		||||
	if m.Message.EditedTimestamp != "" {
 | 
			
		||||
		b.Log.Debugf("Sending edit message")
 | 
			
		||||
		m.Content += b.GetString("EditSuffix")
 | 
			
		||||
		msg := &discordgo.MessageCreate{
 | 
			
		||||
			Message: m.Message,
 | 
			
		||||
		}
 | 
			
		||||
		b.messageCreate(s, msg)
 | 
			
		||||
		b.messageCreate(s, (*discordgo.MessageCreate)(m))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -69,7 +36,7 @@ func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	// if using webhooks, do not relay if it's ours
 | 
			
		||||
	if b.useWebhook() && m.Author.Bot && b.isWebhookID(m.Author.ID) {
 | 
			
		||||
	if b.useWebhook() && m.Author.Bot { // && b.isWebhookID(m.Author.ID) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -84,6 +51,7 @@ func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
 | 
			
		||||
 | 
			
		||||
	if m.Content != "" {
 | 
			
		||||
		b.Log.Debugf("== Receiving event %#v", m.Message)
 | 
			
		||||
		m.Message.Content = b.stripCustomoji(m.Message.Content)
 | 
			
		||||
		m.Message.Content = b.replaceChannelMentions(m.Message.Content)
 | 
			
		||||
		rmsg.Text, err = m.ContentWithMoreMentionsReplaced(b.c)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@@ -94,13 +62,16 @@ func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
 | 
			
		||||
 | 
			
		||||
	// set channel name
 | 
			
		||||
	rmsg.Channel = b.getChannelName(m.ChannelID)
 | 
			
		||||
	if b.useChannelID {
 | 
			
		||||
		rmsg.Channel = "ID:" + m.ChannelID
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fromWebhook := m.WebhookID != ""
 | 
			
		||||
	if !fromWebhook && !b.GetBool("UseUserName") {
 | 
			
		||||
		rmsg.Username = b.getNick(m.Author, m.GuildID)
 | 
			
		||||
	// set username
 | 
			
		||||
	if !b.GetBool("UseUserName") {
 | 
			
		||||
		rmsg.Username = b.getNick(m.Author)
 | 
			
		||||
	} else {
 | 
			
		||||
		rmsg.Username = m.Author.Username
 | 
			
		||||
		if !fromWebhook && b.GetBool("UseDiscriminator") {
 | 
			
		||||
		if b.GetBool("UseDiscriminator") {
 | 
			
		||||
			rmsg.Username += "#" + m.Author.Discriminator
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -108,7 +79,7 @@ func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
 | 
			
		||||
	// if we have embedded content add it to text
 | 
			
		||||
	if b.GetBool("ShowEmbeds") && m.Message.Embeds != nil {
 | 
			
		||||
		for _, embed := range m.Message.Embeds {
 | 
			
		||||
			rmsg.Text += handleEmbed(embed)
 | 
			
		||||
			rmsg.Text = rmsg.Text + "embed: " + embed.Title + " - " + embed.Description + " - " + embed.URL + "\n"
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -124,9 +95,6 @@ func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
 | 
			
		||||
		rmsg.Event = config.EventUserAction
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Replace emotes
 | 
			
		||||
	rmsg.Text = replaceEmotes(rmsg.Text)
 | 
			
		||||
 | 
			
		||||
	b.Log.Debugf("<= Sending message from %s on %s to gateway", m.Author.Username, b.Account)
 | 
			
		||||
	b.Log.Debugf("<= Message is %#v", rmsg)
 | 
			
		||||
	b.Remote <- rmsg
 | 
			
		||||
@@ -200,33 +168,3 @@ func (b *Bdiscord) memberRemove(s *discordgo.Session, m *discordgo.GuildMemberRe
 | 
			
		||||
	b.Log.Debugf("<= Message is %#v", rmsg)
 | 
			
		||||
	b.Remote <- rmsg
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func handleEmbed(embed *discordgo.MessageEmbed) string {
 | 
			
		||||
	var t []string
 | 
			
		||||
	var result string
 | 
			
		||||
 | 
			
		||||
	t = append(t, embed.Title)
 | 
			
		||||
	t = append(t, embed.Description)
 | 
			
		||||
	t = append(t, embed.URL)
 | 
			
		||||
 | 
			
		||||
	i := 0
 | 
			
		||||
	for _, e := range t {
 | 
			
		||||
		if e == "" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		i++
 | 
			
		||||
		if i == 1 {
 | 
			
		||||
			result += " embed: " + e
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		result += " - " + e
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if result != "" {
 | 
			
		||||
		result += "\n"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return result
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,58 +0,0 @@
 | 
			
		||||
package bdiscord
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/matterbridge/discordgo"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestHandleEmbed(t *testing.T) {
 | 
			
		||||
	testcases := map[string]struct {
 | 
			
		||||
		embed  *discordgo.MessageEmbed
 | 
			
		||||
		result string
 | 
			
		||||
	}{
 | 
			
		||||
		"allempty": {
 | 
			
		||||
			embed:  &discordgo.MessageEmbed{},
 | 
			
		||||
			result: "",
 | 
			
		||||
		},
 | 
			
		||||
		"one": {
 | 
			
		||||
			embed: &discordgo.MessageEmbed{
 | 
			
		||||
				Title: "blah",
 | 
			
		||||
			},
 | 
			
		||||
			result: " embed: blah\n",
 | 
			
		||||
		},
 | 
			
		||||
		"two": {
 | 
			
		||||
			embed: &discordgo.MessageEmbed{
 | 
			
		||||
				Title:       "blah",
 | 
			
		||||
				Description: "blah2",
 | 
			
		||||
			},
 | 
			
		||||
			result: " embed: blah - blah2\n",
 | 
			
		||||
		},
 | 
			
		||||
		"three": {
 | 
			
		||||
			embed: &discordgo.MessageEmbed{
 | 
			
		||||
				Title:       "blah",
 | 
			
		||||
				Description: "blah2",
 | 
			
		||||
				URL:         "blah3",
 | 
			
		||||
			},
 | 
			
		||||
			result: " embed: blah - blah2 - blah3\n",
 | 
			
		||||
		},
 | 
			
		||||
		"twob": {
 | 
			
		||||
			embed: &discordgo.MessageEmbed{
 | 
			
		||||
				Description: "blah2",
 | 
			
		||||
				URL:         "blah3",
 | 
			
		||||
			},
 | 
			
		||||
			result: " embed: blah2 - blah3\n",
 | 
			
		||||
		},
 | 
			
		||||
		"oneb": {
 | 
			
		||||
			embed: &discordgo.MessageEmbed{
 | 
			
		||||
				URL: "blah3",
 | 
			
		||||
			},
 | 
			
		||||
			result: " embed: blah3\n",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for name, tc := range testcases {
 | 
			
		||||
		assert.Equalf(t, tc.result, handleEmbed(tc.embed), "Testcases %s", name)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -6,10 +6,10 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"unicode"
 | 
			
		||||
 | 
			
		||||
	"github.com/matterbridge/discordgo"
 | 
			
		||||
	"github.com/bwmarrin/discordgo"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (b *Bdiscord) getNick(user *discordgo.User, guildID string) string {
 | 
			
		||||
func (b *Bdiscord) getNick(user *discordgo.User) string {
 | 
			
		||||
	b.membersMutex.RLock()
 | 
			
		||||
	defer b.membersMutex.RUnlock()
 | 
			
		||||
 | 
			
		||||
@@ -23,9 +23,9 @@ func (b *Bdiscord) getNick(user *discordgo.User, guildID string) string {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If we didn't find nick, search for it.
 | 
			
		||||
	member, err := b.c.GuildMember(guildID, user.ID)
 | 
			
		||||
	member, err := b.c.GuildMember(b.guildID, user.ID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Log.Warnf("Failed to fetch information for member %#v on guild %#v: %s", user, guildID, err)
 | 
			
		||||
		b.Log.Warnf("Failed to fetch information for member %#v: %s", user, err)
 | 
			
		||||
		return user.Username
 | 
			
		||||
	} else if member == nil {
 | 
			
		||||
		b.Log.Warnf("Got no information for member %#v", user)
 | 
			
		||||
@@ -51,9 +51,6 @@ func (b *Bdiscord) getGuildMemberByNick(nick string) (*discordgo.Member, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bdiscord) getChannelID(name string) string {
 | 
			
		||||
	if strings.Contains(name, "/") {
 | 
			
		||||
		return b.getCategoryChannelID(name)
 | 
			
		||||
	}
 | 
			
		||||
	b.channelsMutex.RLock()
 | 
			
		||||
	defer b.channelsMutex.RUnlock()
 | 
			
		||||
 | 
			
		||||
@@ -62,92 +59,40 @@ func (b *Bdiscord) getChannelID(name string) string {
 | 
			
		||||
		return idcheck[1]
 | 
			
		||||
	}
 | 
			
		||||
	for _, channel := range b.channels {
 | 
			
		||||
		if channel.Name == name && channel.Type == discordgo.ChannelTypeGuildText {
 | 
			
		||||
		if channel.Name == name {
 | 
			
		||||
			return channel.ID
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bdiscord) getCategoryChannelID(name string) string {
 | 
			
		||||
	b.channelsMutex.RLock()
 | 
			
		||||
	defer b.channelsMutex.RUnlock()
 | 
			
		||||
	res := strings.Split(name, "/")
 | 
			
		||||
	// shouldn't happen because function should be only called from getChannelID
 | 
			
		||||
	if len(res) != 2 {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	catName, chanName := res[0], res[1]
 | 
			
		||||
	for _, channel := range b.channels {
 | 
			
		||||
		// if we have a parentID, lookup the name of that parent (category)
 | 
			
		||||
		// and if it matches return it
 | 
			
		||||
		if channel.Name == chanName && channel.ParentID != "" {
 | 
			
		||||
			for _, cat := range b.channels {
 | 
			
		||||
				if cat.ID == channel.ParentID && cat.Name == catName {
 | 
			
		||||
					return channel.ID
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bdiscord) getChannelName(id string) string {
 | 
			
		||||
	b.channelsMutex.RLock()
 | 
			
		||||
	defer b.channelsMutex.RUnlock()
 | 
			
		||||
 | 
			
		||||
	for _, c := range b.channelInfoMap {
 | 
			
		||||
		if c.Name == "ID:"+id {
 | 
			
		||||
			// if we have ID: specified in our gateway configuration return this
 | 
			
		||||
			return c.Name
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, channel := range b.channels {
 | 
			
		||||
		if channel.ID == id {
 | 
			
		||||
			return b.getCategoryChannelName(channel.Name, channel.ParentID)
 | 
			
		||||
			return channel.Name
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bdiscord) getCategoryChannelName(name, parentID string) string {
 | 
			
		||||
	var usesCat bool
 | 
			
		||||
	// do we have a category configuration in the channel config
 | 
			
		||||
	for _, c := range b.channelInfoMap {
 | 
			
		||||
		if strings.Contains(c.Name, "/") {
 | 
			
		||||
			usesCat = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// configuration without category, return the normal channel name
 | 
			
		||||
	if !usesCat {
 | 
			
		||||
		return name
 | 
			
		||||
	}
 | 
			
		||||
	// create a category/channel response
 | 
			
		||||
	for _, c := range b.channels {
 | 
			
		||||
		if c.ID == parentID {
 | 
			
		||||
			name = c.Name + "/" + name
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return name
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	// See https://discordapp.com/developers/docs/reference#message-formatting.
 | 
			
		||||
	channelMentionRE = regexp.MustCompile("<#[0-9]+>")
 | 
			
		||||
	emojiRE          = regexp.MustCompile("<(:.*?:)[0-9]+>")
 | 
			
		||||
	userMentionRE    = regexp.MustCompile("@[^@\n]{1,32}")
 | 
			
		||||
	emoteRE          = regexp.MustCompile(`<a?(:\w+:)\d+>`)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (b *Bdiscord) replaceChannelMentions(text string) string {
 | 
			
		||||
	replaceChannelMentionFunc := func(match string) string {
 | 
			
		||||
		var err error
 | 
			
		||||
		channelID := match[2 : len(match)-1]
 | 
			
		||||
		channelName := b.getChannelName(channelID)
 | 
			
		||||
 | 
			
		||||
		channelName := b.getChannelName(channelID)
 | 
			
		||||
		// If we don't have the channel refresh our list.
 | 
			
		||||
		if channelName == "" {
 | 
			
		||||
			var err error
 | 
			
		||||
			b.channels, err = b.c.GuildChannels(b.guildID)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "#unknownchannel"
 | 
			
		||||
@@ -183,14 +128,13 @@ func (b *Bdiscord) replaceUserMentions(text string) string {
 | 
			
		||||
	return userMentionRE.ReplaceAllStringFunc(text, replaceUserMentionFunc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func replaceEmotes(text string) string {
 | 
			
		||||
	return emoteRE.ReplaceAllString(text, "$1")
 | 
			
		||||
func (b *Bdiscord) stripCustomoji(text string) string {
 | 
			
		||||
	return emojiRE.ReplaceAllString(text, `$1`)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bdiscord) replaceAction(text string) (string, bool) {
 | 
			
		||||
	length := len(text)
 | 
			
		||||
	if length > 1 && text[0] == '_' && text[length-1] == '_' {
 | 
			
		||||
		return text[1 : length-1], true
 | 
			
		||||
	if strings.HasPrefix(text, "_") && strings.HasSuffix(text, "_") {
 | 
			
		||||
		return text[1:], true
 | 
			
		||||
	}
 | 
			
		||||
	return text, false
 | 
			
		||||
}
 | 
			
		||||
@@ -209,40 +153,6 @@ func (b *Bdiscord) splitURL(url string) (string, string) {
 | 
			
		||||
	return webhookURLSplit[webhookIdxID], webhookURLSplit[webhookIdxToken]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getcacheID tries to find a corresponding msgID in the webhook cache.
 | 
			
		||||
// if not found returns the original request.
 | 
			
		||||
func (b *Bdiscord) getCacheID(msgID string) string {
 | 
			
		||||
	b.webhookMutex.RLock()
 | 
			
		||||
	defer b.webhookMutex.RUnlock()
 | 
			
		||||
	for k, v := range b.webhookCache {
 | 
			
		||||
		if msgID == k {
 | 
			
		||||
			return v
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return msgID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// updateCacheID updates the cache so that the newID takes the place of
 | 
			
		||||
// the original ID. This is used for edit/deletes in combination with webhooks
 | 
			
		||||
// as editing a message via webhook means deleting the message and creating a
 | 
			
		||||
// new message (with a new ID). This ID needs to be set instead of the original ID
 | 
			
		||||
func (b *Bdiscord) updateCacheID(origID, newID string) {
 | 
			
		||||
	b.webhookMutex.Lock()
 | 
			
		||||
	match := false
 | 
			
		||||
	for k, v := range b.webhookCache {
 | 
			
		||||
		if v == origID {
 | 
			
		||||
			delete(b.webhookCache, k)
 | 
			
		||||
			b.webhookCache[origID] = newID
 | 
			
		||||
			match = true
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !match && origID != "" {
 | 
			
		||||
		b.webhookCache[origID] = newID
 | 
			
		||||
	}
 | 
			
		||||
	b.webhookMutex.Unlock()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func enumerateUsernames(s string) []string {
 | 
			
		||||
	onlySpace := true
 | 
			
		||||
	for _, r := range s {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,10 +5,7 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"image/png"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
@@ -17,10 +14,8 @@ import (
 | 
			
		||||
	"golang.org/x/image/webp"
 | 
			
		||||
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/gomarkdown/markdown"
 | 
			
		||||
	"github.com/gomarkdown/markdown/html"
 | 
			
		||||
	"github.com/gomarkdown/markdown/parser"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"gitlab.com/golang-commonmark/markdown"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// DownloadFile downloads the given non-authenticated URL.
 | 
			
		||||
@@ -181,21 +176,15 @@ func ClipMessage(text string, length int) string {
 | 
			
		||||
	return text
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParseMarkdown takes in an input string as markdown and parses it to html
 | 
			
		||||
func ParseMarkdown(input string) string {
 | 
			
		||||
	extensions := parser.HardLineBreak | parser.NoIntraEmphasis
 | 
			
		||||
	markdownParser := parser.NewWithExtensions(extensions)
 | 
			
		||||
	renderer := html.NewRenderer(html.RendererOptions{
 | 
			
		||||
		Flags: 0,
 | 
			
		||||
	})
 | 
			
		||||
	parsedMarkdown := markdown.ToHTML([]byte(input), markdownParser, renderer)
 | 
			
		||||
	res := string(parsedMarkdown)
 | 
			
		||||
	md := markdown.New(markdown.XHTMLOutput(true), markdown.Breaks(true))
 | 
			
		||||
	res := md.RenderToString([]byte(input))
 | 
			
		||||
	res = strings.TrimPrefix(res, "<p>")
 | 
			
		||||
	res = strings.TrimSuffix(res, "</p>\n")
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConvertWebPToPNG converts input data (which should be WebP format) to PNG format
 | 
			
		||||
// ConvertWebPToPNG convert input data (which should be WebP format to PNG format)
 | 
			
		||||
func ConvertWebPToPNG(data *[]byte) error {
 | 
			
		||||
	r := bytes.NewReader(*data)
 | 
			
		||||
	m, err := webp.Decode(r)
 | 
			
		||||
@@ -210,49 +199,3 @@ func ConvertWebPToPNG(data *[]byte) error {
 | 
			
		||||
	*data = w.Bytes()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CanConvertTgsToX Checks whether the external command necessary for ConvertTgsToX works.
 | 
			
		||||
func CanConvertTgsToX() error {
 | 
			
		||||
	// We depend on the fact that `lottie_convert.py --help` has exit status 0.
 | 
			
		||||
	// Hyrum's Law predicted this, and Murphy's Law predicts that this will break eventually.
 | 
			
		||||
	// However, there is no alternative like `lottie_convert.py --is-properly-installed`
 | 
			
		||||
	cmd := exec.Command("lottie_convert.py", "--help")
 | 
			
		||||
	return cmd.Run()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConvertTgsToWebP convert input data (which should be tgs format) to WebP format
 | 
			
		||||
// This relies on an external command, which is ugly, but works.
 | 
			
		||||
func ConvertTgsToX(data *[]byte, outputFormat string, logger *logrus.Entry) error {
 | 
			
		||||
	// lottie can't handle input from a pipe, so write to a temporary file:
 | 
			
		||||
	tmpFile, err := ioutil.TempFile(os.TempDir(), "matterbridge-lottie-*.tgs")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	tmpFileName := tmpFile.Name()
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if removeErr := os.Remove(tmpFileName); removeErr != nil {
 | 
			
		||||
			logger.Errorf("Could not delete temporary file %s: %v", tmpFileName, removeErr)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	if _, writeErr := tmpFile.Write(*data); writeErr != nil {
 | 
			
		||||
		return writeErr
 | 
			
		||||
	}
 | 
			
		||||
	// Must close before calling lottie to avoid data races:
 | 
			
		||||
	if closeErr := tmpFile.Close(); closeErr != nil {
 | 
			
		||||
		return closeErr
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Call lottie to transform:
 | 
			
		||||
	cmd := exec.Command("lottie_convert.py", "--input-format", "lottie", "--output-format", outputFormat, tmpFileName, "/dev/stdout")
 | 
			
		||||
	cmd.Stderr = nil
 | 
			
		||||
	// NB: lottie writes progress into to stderr in all cases.
 | 
			
		||||
	stdout, stderr := cmd.Output()
 | 
			
		||||
	if stderr != nil {
 | 
			
		||||
		// 'stderr' already contains some parts of Stderr, because it was set to 'nil'.
 | 
			
		||||
		return stderr
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	*data = stdout
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,14 +4,15 @@ import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/helper"
 | 
			
		||||
	"github.com/dfordsoft/golib/ic"
 | 
			
		||||
	"github.com/lrstanley/girc"
 | 
			
		||||
	"github.com/missdeer/golib/ic"
 | 
			
		||||
	"github.com/paulrosania/go-charset/charset"
 | 
			
		||||
	"github.com/saintfish/chardet"
 | 
			
		||||
 | 
			
		||||
@@ -90,13 +91,8 @@ func (b *Birc) handleJoinPart(client *girc.Client, event girc.Event) {
 | 
			
		||||
		if b.GetBool("nosendjoinpart") {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		msg := config.Message{Username: "system", Text: event.Source.Name + " " + strings.ToLower(event.Command) + "s", Channel: channel, Account: b.Account, Event: config.EventJoinLeave}
 | 
			
		||||
		if b.GetBool("verbosejoinpart") {
 | 
			
		||||
			b.Log.Debugf("<= Sending verbose JOIN_LEAVE event from %s to gateway", b.Account)
 | 
			
		||||
			msg = config.Message{Username: "system", Text: event.Source.Name + " (" + event.Source.Ident + "@" + event.Source.Host + ") " + strings.ToLower(event.Command) + "s", Channel: channel, Account: b.Account, Event: config.EventJoinLeave}
 | 
			
		||||
		} else {
 | 
			
		||||
		b.Log.Debugf("<= Sending JOIN_LEAVE event from %s to gateway", b.Account)
 | 
			
		||||
		}
 | 
			
		||||
		msg := config.Message{Username: "system", Text: event.Source.Name + " " + strings.ToLower(event.Command) + "s", Channel: channel, Account: b.Account, Event: config.EventJoinLeave}
 | 
			
		||||
		b.Log.Debugf("<= Message is %#v", msg)
 | 
			
		||||
		b.Remote <- msg
 | 
			
		||||
		return
 | 
			
		||||
@@ -181,6 +177,10 @@ func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) {
 | 
			
		||||
	// strip action, we made an event if it was an action
 | 
			
		||||
	rmsg.Text += event.StripAction()
 | 
			
		||||
 | 
			
		||||
	// strip IRC colors
 | 
			
		||||
	re := regexp.MustCompile(`\x03(?:\d{1,2}(?:,\d{1,2})?)?|[[:cntrl:]]`)
 | 
			
		||||
	rmsg.Text = re.ReplaceAllString(rmsg.Text, "")
 | 
			
		||||
 | 
			
		||||
	// start detecting the charset
 | 
			
		||||
	mycharset := b.GetString("Charset")
 | 
			
		||||
	if mycharset == "" {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@ import (
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"hash/crc32"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strconv"
 | 
			
		||||
@@ -15,7 +14,6 @@ import (
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/helper"
 | 
			
		||||
	"github.com/lrstanley/girc"
 | 
			
		||||
	stripmd "github.com/writeas/go-strip-markdown"
 | 
			
		||||
 | 
			
		||||
	// We need to import the 'data' package as an implicit dependency.
 | 
			
		||||
	// See: https://godoc.org/github.com/paulrosania/go-charset/charset
 | 
			
		||||
@@ -158,10 +156,6 @@ func (b *Birc) Send(msg config.Message) (string, error) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var msgLines []string
 | 
			
		||||
	if b.GetBool("StripMarkdown") {
 | 
			
		||||
		msg.Text = stripmd.Strip(msg.Text)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if b.GetBool("MessageSplit") {
 | 
			
		||||
		msgLines = helper.GetSubLines(msg.Text, b.MessageLength)
 | 
			
		||||
	} else {
 | 
			
		||||
@@ -173,8 +167,12 @@ func (b *Birc) Send(msg config.Message) (string, error) {
 | 
			
		||||
			return "", nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		msg.Text = msgLines[i]
 | 
			
		||||
		b.Local <- msg
 | 
			
		||||
		b.Local <- config.Message{
 | 
			
		||||
			Text:     msgLines[i],
 | 
			
		||||
			Username: msg.Username,
 | 
			
		||||
			Channel:  msg.Channel,
 | 
			
		||||
			Event:    msg.Event,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return "", nil
 | 
			
		||||
}
 | 
			
		||||
@@ -207,7 +205,7 @@ func (b *Birc) doSend() {
 | 
			
		||||
	for msg := range b.Local {
 | 
			
		||||
		<-throttle.C
 | 
			
		||||
		username := msg.Username
 | 
			
		||||
		if b.GetBool("Colornicks") && len(username) > 1 {
 | 
			
		||||
		if b.GetBool("Colornicks") {
 | 
			
		||||
			checksum := crc32.ChecksumIEEE([]byte(msg.Username))
 | 
			
		||||
			colorCode := checksum%14 + 2 // quick fix - prevent white or black color codes
 | 
			
		||||
			username = fmt.Sprintf("\x03%02d%s\x0F", colorCode, msg.Username)
 | 
			
		||||
@@ -241,11 +239,6 @@ func (b *Birc) getClient() (*girc.Client, error) {
 | 
			
		||||
		user = user[1:]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	debug := ioutil.Discard
 | 
			
		||||
	if b.GetInt("DebugLevel") == 2 {
 | 
			
		||||
		debug = b.Log.Writer()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	i := girc.New(girc.Config{
 | 
			
		||||
		Server:     server,
 | 
			
		||||
		ServerPass: b.GetString("Password"),
 | 
			
		||||
@@ -256,9 +249,6 @@ func (b *Birc) getClient() (*girc.Client, error) {
 | 
			
		||||
		SSL:        b.GetBool("UseTLS"),
 | 
			
		||||
		TLSConfig:  &tls.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), ServerName: server}, //nolint:gosec
 | 
			
		||||
		PingDelay:  time.Minute,
 | 
			
		||||
		// skip gIRC internal rate limiting, since we have our own throttling
 | 
			
		||||
		AllowFlood: true,
 | 
			
		||||
		Debug:      debug,
 | 
			
		||||
	})
 | 
			
		||||
	return i, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,59 +0,0 @@
 | 
			
		||||
package bkeybase
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/keybase/go-keybase-chat-bot/kbchat/types/chat1"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (b *Bkeybase) handleKeybase() {
 | 
			
		||||
	sub, err := b.kbc.ListenForNewTextMessages()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Log.Errorf("Error listening: %s", err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		for {
 | 
			
		||||
			msg, err := sub.Read()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				b.Log.Errorf("failed to read message: %s", err.Error())
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if msg.Message.Content.TypeName != "text" {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if msg.Message.Sender.Username == b.kbc.GetUsername() {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			b.handleMessage(msg.Message)
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bkeybase) handleMessage(msg chat1.MsgSummary) {
 | 
			
		||||
	b.Log.Debugf("== Receiving event: %#v", msg)
 | 
			
		||||
	if msg.Channel.TopicName != b.channel || msg.Channel.Name != b.team {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if msg.Sender.Username != b.kbc.GetUsername() {
 | 
			
		||||
 | 
			
		||||
		// TODO download avatar
 | 
			
		||||
 | 
			
		||||
		// Create our message
 | 
			
		||||
		rmsg := config.Message{Username: msg.Sender.Username, Text: msg.Content.Text.Body, UserID: string(msg.Sender.Uid), Channel: msg.Channel.TopicName, ID: strconv.Itoa(int(msg.Id)), Account: b.Account}
 | 
			
		||||
 | 
			
		||||
		// Text must be a string
 | 
			
		||||
		if msg.Content.TypeName != "text" {
 | 
			
		||||
			b.Log.Errorf("message is not text")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		b.Log.Debugf("<= Sending message from %s on %s to gateway", msg.Sender.Username, msg.Channel.Name)
 | 
			
		||||
		b.Remote <- rmsg
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,106 +0,0 @@
 | 
			
		||||
package bkeybase
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/keybase/go-keybase-chat-bot/kbchat"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Bkeybase bridge structure
 | 
			
		||||
type Bkeybase struct {
 | 
			
		||||
	kbc     *kbchat.API
 | 
			
		||||
	user    string
 | 
			
		||||
	channel string
 | 
			
		||||
	team    string
 | 
			
		||||
	*bridge.Config
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New initializes Bkeybase object and sets team
 | 
			
		||||
func New(cfg *bridge.Config) bridge.Bridger {
 | 
			
		||||
	b := &Bkeybase{Config: cfg}
 | 
			
		||||
	b.team = b.Config.GetString("Team")
 | 
			
		||||
	return b
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Connect starts keybase API and listener loop
 | 
			
		||||
func (b *Bkeybase) Connect() error {
 | 
			
		||||
	var err error
 | 
			
		||||
	b.Log.Infof("Connecting %s", b.GetString("Team"))
 | 
			
		||||
 | 
			
		||||
	// use default keybase location (`keybase`)
 | 
			
		||||
	b.kbc, err = kbchat.Start(kbchat.RunOptions{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	b.user = b.kbc.GetUsername()
 | 
			
		||||
	b.Log.Info("Connection succeeded")
 | 
			
		||||
	go b.handleKeybase()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Disconnect doesn't do anything for now
 | 
			
		||||
func (b *Bkeybase) Disconnect() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// JoinChannel sets channel name in struct
 | 
			
		||||
func (b *Bkeybase) JoinChannel(channel config.ChannelInfo) error {
 | 
			
		||||
	if _, err := b.kbc.JoinChannel(b.team, channel.Name); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	b.channel = channel.Name
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Send receives bridge messages and sends them to Keybase chat room
 | 
			
		||||
func (b *Bkeybase) Send(msg config.Message) (string, error) {
 | 
			
		||||
	b.Log.Debugf("=> Receiving %#v", msg)
 | 
			
		||||
 | 
			
		||||
	// Handle /me events
 | 
			
		||||
	if msg.Event == config.EventUserAction {
 | 
			
		||||
		msg.Text = "_" + msg.Text + "_"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Delete message if we have an ID
 | 
			
		||||
	// Delete message not supported by keybase go library yet
 | 
			
		||||
 | 
			
		||||
	// Edit message if we have an ID
 | 
			
		||||
	// kbchat lib does not support message editing yet
 | 
			
		||||
 | 
			
		||||
	if len(msg.Extra["file"]) > 0 {
 | 
			
		||||
		// Upload a file
 | 
			
		||||
		dir, err := ioutil.TempDir("", "matterbridge")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", err
 | 
			
		||||
		}
 | 
			
		||||
		defer os.RemoveAll(dir)
 | 
			
		||||
 | 
			
		||||
		for _, f := range msg.Extra["file"] {
 | 
			
		||||
			fname := f.(config.FileInfo).Name
 | 
			
		||||
			fdata := *f.(config.FileInfo).Data
 | 
			
		||||
			fcaption := f.(config.FileInfo).Comment
 | 
			
		||||
			fpath := filepath.Join(dir, fname)
 | 
			
		||||
 | 
			
		||||
			if err = ioutil.WriteFile(fpath, fdata, 0600); err != nil {
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			_, _ = b.kbc.SendAttachmentByTeam(b.team, &b.channel, fpath, fcaption)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return "", nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Send regular message
 | 
			
		||||
	text := msg.Username + msg.Text
 | 
			
		||||
	resp, err := b.kbc.SendMessageByTeamName(b.team, &b.channel, text)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	return strconv.Itoa(int(*resp.Result.MessageID)), err
 | 
			
		||||
}
 | 
			
		||||
@@ -2,24 +2,17 @@ package bmatrix
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"html"
 | 
			
		||||
	"mime"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/helper"
 | 
			
		||||
	matrix "github.com/matrix-org/gomatrix"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	htmlTag            = regexp.MustCompile("</.*?>")
 | 
			
		||||
	htmlReplacementTag = regexp.MustCompile("<[^>]*>")
 | 
			
		||||
	matrix "github.com/matterbridge/gomatrix"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Bmatrix struct {
 | 
			
		||||
@@ -27,38 +20,13 @@ type Bmatrix struct {
 | 
			
		||||
	UserID  string
 | 
			
		||||
	RoomMap map[string]string
 | 
			
		||||
	sync.RWMutex
 | 
			
		||||
	htmlTag *regexp.Regexp
 | 
			
		||||
	*bridge.Config
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type httpError struct {
 | 
			
		||||
	Errcode      string `json:"errcode"`
 | 
			
		||||
	Err          string `json:"error"`
 | 
			
		||||
	RetryAfterMs int    `json:"retry_after_ms"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type matrixUsername struct {
 | 
			
		||||
	plain     string
 | 
			
		||||
	formatted string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func New(cfg *bridge.Config) bridge.Bridger {
 | 
			
		||||
	b := &Bmatrix{Config: cfg}
 | 
			
		||||
	b.htmlTag = regexp.MustCompile("</.*?>")
 | 
			
		||||
	b.RoomMap = make(map[string]string)
 | 
			
		||||
	return b
 | 
			
		||||
}
 | 
			
		||||
@@ -74,7 +42,6 @@ func (b *Bmatrix) Connect() error {
 | 
			
		||||
		Type:     "m.login.password",
 | 
			
		||||
		User:     b.GetString("Login"),
 | 
			
		||||
		Password: b.GetString("Password"),
 | 
			
		||||
		Identifier: matrix.NewUserIdentifier(b.GetString("Login")),
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
@@ -91,25 +58,14 @@ func (b *Bmatrix) Disconnect() error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmatrix) JoinChannel(channel config.ChannelInfo) error {
 | 
			
		||||
retry:
 | 
			
		||||
	resp, err := b.mc.JoinRoom(channel.Name, "", nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		httpErr := handleError(err)
 | 
			
		||||
		if httpErr.Errcode == "M_LIMIT_EXCEEDED" {
 | 
			
		||||
			b.Log.Infof("getting ratelimited by matrix, sleeping approx %d seconds before joining %s", httpErr.RetryAfterMs/1000, channel.Name)
 | 
			
		||||
			time.Sleep((time.Duration(httpErr.RetryAfterMs) * time.Millisecond))
 | 
			
		||||
 | 
			
		||||
			goto retry
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b.Lock()
 | 
			
		||||
	b.RoomMap[resp.RoomID] = channel.Name
 | 
			
		||||
	b.Unlock()
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmatrix) Send(msg config.Message) (string, error) {
 | 
			
		||||
@@ -118,14 +74,11 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
 | 
			
		||||
	channel := b.getRoomID(msg.Channel)
 | 
			
		||||
	b.Log.Debugf("Channel %s maps to channel id %s", msg.Channel, channel)
 | 
			
		||||
 | 
			
		||||
	username := newMatrixUsername(msg.Username)
 | 
			
		||||
 | 
			
		||||
	// Make a action /me of the message
 | 
			
		||||
	if msg.Event == config.EventUserAction {
 | 
			
		||||
		m := matrix.TextMessage{
 | 
			
		||||
			MsgType: "m.emote",
 | 
			
		||||
			Body:          username.plain + msg.Text,
 | 
			
		||||
			FormattedBody: username.formatted + msg.Text,
 | 
			
		||||
			Body:    msg.Username + msg.Text,
 | 
			
		||||
		}
 | 
			
		||||
		resp, err := b.mc.SendMessageEvent(channel, "m.room.message", m)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@@ -164,28 +117,20 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
 | 
			
		||||
 | 
			
		||||
	// Use notices to send join/leave events
 | 
			
		||||
	if msg.Event == config.EventJoinLeave {
 | 
			
		||||
		m := matrix.TextMessage{
 | 
			
		||||
			MsgType:       "m.notice",
 | 
			
		||||
			Body:          username.plain + msg.Text,
 | 
			
		||||
			FormattedBody: username.formatted + msg.Text,
 | 
			
		||||
		}
 | 
			
		||||
		resp, err := b.mc.SendMessageEvent(channel, "m.room.message", m)
 | 
			
		||||
		resp, err := b.mc.SendNotice(channel, msg.Username+msg.Text)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", err
 | 
			
		||||
		}
 | 
			
		||||
		return resp.EventID, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if b.GetBool("HTMLDisable") {
 | 
			
		||||
		resp, err := b.mc.SendText(channel, username.plain+msg.Text)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", err
 | 
			
		||||
	username := html.EscapeString(msg.Username)
 | 
			
		||||
	// check if we have a </tag>. if we have, we don't escape HTML. #696
 | 
			
		||||
	if b.htmlTag.MatchString(msg.Username) {
 | 
			
		||||
		username = msg.Username
 | 
			
		||||
	}
 | 
			
		||||
		return resp.EventID, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Post normal message with HTML support (eg riot.im)
 | 
			
		||||
	resp, err := b.mc.SendFormattedText(channel, username.plain+msg.Text, username.formatted+helper.ParseMarkdown(msg.Text))
 | 
			
		||||
	resp, err := b.mc.SendHTML(channel, msg.Username+msg.Text, username+helper.ParseMarkdown(msg.Text))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
@@ -227,15 +172,10 @@ func (b *Bmatrix) handleEvent(ev *matrix.Event) {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// TODO download avatar
 | 
			
		||||
 | 
			
		||||
		// Create our message
 | 
			
		||||
		rmsg := config.Message{
 | 
			
		||||
			Username: ev.Sender[1:],
 | 
			
		||||
			Channel:  channel,
 | 
			
		||||
			Account:  b.Account,
 | 
			
		||||
			UserID:   ev.Sender,
 | 
			
		||||
			ID:       ev.ID,
 | 
			
		||||
			Avatar:   b.getAvatarURL(ev.Sender),
 | 
			
		||||
		}
 | 
			
		||||
		rmsg := config.Message{Username: ev.Sender[1:], Channel: channel, Account: b.Account, UserID: ev.Sender, ID: ev.ID}
 | 
			
		||||
 | 
			
		||||
		// Text must be a string
 | 
			
		||||
		if rmsg.Text, ok = ev.Content["body"].(string); !ok {
 | 
			
		||||
@@ -348,16 +288,24 @@ func (b *Bmatrix) handleUploadFiles(msg *config.Message, channel string) (string
 | 
			
		||||
 | 
			
		||||
// handleUploadFile handles native upload of a file.
 | 
			
		||||
func (b *Bmatrix) handleUploadFile(msg *config.Message, channel string, fi *config.FileInfo) {
 | 
			
		||||
	username := newMatrixUsername(msg.Username)
 | 
			
		||||
	content := bytes.NewReader(*fi.Data)
 | 
			
		||||
	sp := strings.Split(fi.Name, ".")
 | 
			
		||||
	mtype := mime.TypeByExtension("." + sp[len(sp)-1])
 | 
			
		||||
	// image and video uploads send no username, we have to do this ourself here #715
 | 
			
		||||
	_, err := b.mc.SendFormattedText(channel, username.plain+fi.Comment, username.formatted+fi.Comment)
 | 
			
		||||
	if !strings.Contains(mtype, "image") && !strings.Contains(mtype, "video") {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if fi.Comment != "" {
 | 
			
		||||
		_, err := b.mc.SendText(channel, msg.Username+fi.Comment)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			b.Log.Errorf("file comment failed: %#v", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	} else {
 | 
			
		||||
		// image and video uploads send no username, we have to do this ourself here #715
 | 
			
		||||
		_, err := b.mc.SendText(channel, msg.Username)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			b.Log.Errorf("file comment failed: %#v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	b.Log.Debugf("uploading file: %s %s", fi.Name, mtype)
 | 
			
		||||
	res, err := b.mc.UploadToContentRepo(content, mtype, int64(len(*fi.Data)))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -378,34 +326,6 @@ func (b *Bmatrix) handleUploadFile(msg *config.Message, channel string, fi *conf
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			b.Log.Errorf("sendImage failed: %#v", err)
 | 
			
		||||
		}
 | 
			
		||||
	case strings.Contains(mtype, "audio"):
 | 
			
		||||
		b.Log.Debugf("sendAudio %s", res.ContentURI)
 | 
			
		||||
		_, err = b.mc.SendMessageEvent(channel, "m.room.message", matrix.AudioMessage{
 | 
			
		||||
			MsgType: "m.audio",
 | 
			
		||||
			Body:    fi.Name,
 | 
			
		||||
			URL:     res.ContentURI,
 | 
			
		||||
			Info: matrix.AudioInfo{
 | 
			
		||||
				Mimetype: mtype,
 | 
			
		||||
				Size:     uint(len(*fi.Data)),
 | 
			
		||||
			},
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			b.Log.Errorf("sendAudio failed: %#v", err)
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		b.Log.Debugf("sendFile %s", res.ContentURI)
 | 
			
		||||
		_, err = b.mc.SendMessageEvent(channel, "m.room.message", matrix.FileMessage{
 | 
			
		||||
			MsgType: "m.file",
 | 
			
		||||
			Body:    fi.Name,
 | 
			
		||||
			URL:     res.ContentURI,
 | 
			
		||||
			Info: matrix.FileInfo{
 | 
			
		||||
				Mimetype: mtype,
 | 
			
		||||
				Size:     uint(len(*fi.Data)),
 | 
			
		||||
			},
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			b.Log.Errorf("sendFile failed: %#v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	b.Log.Debugf("result: %#v", res)
 | 
			
		||||
}
 | 
			
		||||
@@ -425,42 +345,3 @@ func (b *Bmatrix) containsAttachment(content map[string]interface{}) bool {
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getAvatarURL returns the avatar URL of the specified sender
 | 
			
		||||
func (b *Bmatrix) getAvatarURL(sender string) string {
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func handleError(err error) *httpError {
 | 
			
		||||
	mErr, ok := err.(matrix.HTTPError)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,28 +0,0 @@
 | 
			
		||||
package bmatrix
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestPlainUsername(t *testing.T) {
 | 
			
		||||
	uut := newMatrixUsername("MyUser")
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, "MyUser", uut.formatted)
 | 
			
		||||
	assert.Equal(t, "MyUser", uut.plain)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestHTMLUsername(t *testing.T) {
 | 
			
		||||
	uut := newMatrixUsername("<b>MyUser</b>")
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, "<b>MyUser</b>", uut.formatted)
 | 
			
		||||
	assert.Equal(t, "MyUser", uut.plain)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFancyUsername(t *testing.T) {
 | 
			
		||||
	uut := newMatrixUsername("<MyUser>")
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, "<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/helper"
 | 
			
		||||
	"github.com/42wim/matterbridge/matterclient"
 | 
			
		||||
	"github.com/mattermost/mattermost-server/v5/model"
 | 
			
		||||
	"github.com/mattermost/mattermost-server/model"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// handleDownloadAvatar downloads the avatar of userid from channel
 | 
			
		||||
@@ -66,10 +66,6 @@ func (b *Bmattermost) handleMatter() {
 | 
			
		||||
		} else {
 | 
			
		||||
			b.Log.Debugf("Choosing login/password based receiving")
 | 
			
		||||
		}
 | 
			
		||||
		// if for some reason we only want to sent stuff to mattermost but not receive, return
 | 
			
		||||
		if b.GetString("WebhookBindAddress") == "" && b.GetString("WebhookURL") != "" && b.GetString("Token") == "" && b.GetString("Login") == "" {
 | 
			
		||||
			b.Log.Debugf("No WebhookBindAddress specified, only WebhookURL. You will not receive messages from mattermost, only sending is possible.")
 | 
			
		||||
		}
 | 
			
		||||
		go b.handleMatterClient(messages)
 | 
			
		||||
	}
 | 
			
		||||
	var ok bool
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ import (
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/helper"
 | 
			
		||||
	"github.com/42wim/matterbridge/matterclient"
 | 
			
		||||
	"github.com/42wim/matterbridge/matterhook"
 | 
			
		||||
	"github.com/mattermost/mattermost-server/v5/model"
 | 
			
		||||
	"github.com/mattermost/mattermost-server/model"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (b *Bmattermost) doConnectWebhookBind() error {
 | 
			
		||||
@@ -70,7 +70,6 @@ func (b *Bmattermost) apiLogin() error {
 | 
			
		||||
		b.mc.SetLogLevel("debug")
 | 
			
		||||
	}
 | 
			
		||||
	b.mc.SkipTLSVerify = b.GetBool("SkipTLSVerify")
 | 
			
		||||
	b.mc.SkipVersionCheck = b.GetBool("SkipVersionCheck")
 | 
			
		||||
	b.mc.NoTLS = b.GetBool("NoTLS")
 | 
			
		||||
	b.Log.Infof("Connecting %s (team: %s) on %s", b.GetString("Login"), b.GetString("Team"), b.GetString("Server"))
 | 
			
		||||
	err := b.mc.Login()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,101 +0,0 @@
 | 
			
		||||
package bmsteams
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/helper"
 | 
			
		||||
 | 
			
		||||
	msgraph "github.com/yaegashi/msgraph.go/beta"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (b *Bmsteams) findFile(weburl string) (string, error) {
 | 
			
		||||
	itemRB, err := b.gc.GetDriveItemByURL(b.ctx, weburl)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	itemRB.Workbook().Worksheets()
 | 
			
		||||
	b.gc.Workbooks()
 | 
			
		||||
	item, err := itemRB.Request().Get(b.ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	if url, ok := item.GetAdditionalData("@microsoft.graph.downloadUrl"); ok {
 | 
			
		||||
		return url.(string), nil
 | 
			
		||||
	}
 | 
			
		||||
	return "", nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// handleDownloadFile handles file download
 | 
			
		||||
func (b *Bmsteams) handleDownloadFile(rmsg *config.Message, filename, weburl string) error {
 | 
			
		||||
	realURL, err := b.findFile(weburl)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	// Actually download the file.
 | 
			
		||||
	data, err := helper.DownloadFile(realURL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("download %s failed %#v", weburl, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If a comment is attached to the file(s) it is in the 'Text' field of the teams messge event
 | 
			
		||||
	// and should be added as comment to only one of the files. We reset the 'Text' field to ensure
 | 
			
		||||
	// that the comment is not duplicated.
 | 
			
		||||
	comment := rmsg.Text
 | 
			
		||||
	rmsg.Text = ""
 | 
			
		||||
	helper.HandleDownloadData(b.Log, rmsg, filename, comment, weburl, data, b.General)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmsteams) handleAttachments(rmsg *config.Message, msg msgraph.ChatMessage) {
 | 
			
		||||
	for _, a := range msg.Attachments {
 | 
			
		||||
		//remove the attachment tags from the text
 | 
			
		||||
		rmsg.Text = attachRE.ReplaceAllString(rmsg.Text, "")
 | 
			
		||||
 | 
			
		||||
		//handle a code snippet (code block)
 | 
			
		||||
		if *a.ContentType == "application/vnd.microsoft.card.codesnippet" {
 | 
			
		||||
			b.handleCodeSnippet(rmsg, a)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//handle the download
 | 
			
		||||
		err := b.handleDownloadFile(rmsg, *a.Name, *a.ContentURL)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			b.Log.Errorf("download of %s failed: %s", *a.Name, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AttachContent struct {
 | 
			
		||||
	Language       string `json:"language"`
 | 
			
		||||
	CodeSnippetURL string `json:"codeSnippetUrl"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmsteams) handleCodeSnippet(rmsg *config.Message, attach msgraph.ChatMessageAttachment) {
 | 
			
		||||
	var content AttachContent
 | 
			
		||||
	err := json.Unmarshal([]byte(*attach.Content), &content)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Log.Errorf("unmarshal codesnippet failed: %s", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	s := strings.Split(content.CodeSnippetURL, "/")
 | 
			
		||||
	if len(s) != 13 {
 | 
			
		||||
		b.Log.Errorf("codesnippetUrl has unexpected size: %s", content.CodeSnippetURL)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	resp, err := b.gc.Teams().Request().Client().Get(content.CodeSnippetURL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Log.Errorf("retrieving snippet content failed:%s", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
	res, err := ioutil.ReadAll(resp.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Log.Errorf("reading snippet data failed: %s", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	rmsg.Text = rmsg.Text + "\n```" + content.Language + "\n" + string(res) + "\n```\n"
 | 
			
		||||
}
 | 
			
		||||
@@ -1,224 +0,0 @@
 | 
			
		||||
package bmsteams
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/davecgh/go-spew/spew"
 | 
			
		||||
 | 
			
		||||
	"github.com/mattn/godown"
 | 
			
		||||
	msgraph "github.com/yaegashi/msgraph.go/beta"
 | 
			
		||||
	"github.com/yaegashi/msgraph.go/msauth"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/oauth2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var defaultScopes = []string{"openid", "profile", "offline_access", "Group.Read.All", "Group.ReadWrite.All"}
 | 
			
		||||
var attachRE = regexp.MustCompile(`<attachment id=.*?attachment>`)
 | 
			
		||||
 | 
			
		||||
type Bmsteams struct {
 | 
			
		||||
	gc    *msgraph.GraphServiceRequestBuilder
 | 
			
		||||
	ctx   context.Context
 | 
			
		||||
	botID string
 | 
			
		||||
	*bridge.Config
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func New(cfg *bridge.Config) bridge.Bridger {
 | 
			
		||||
	return &Bmsteams{Config: cfg}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmsteams) Connect() error {
 | 
			
		||||
	tokenCachePath := b.GetString("sessionFile")
 | 
			
		||||
	if tokenCachePath == "" {
 | 
			
		||||
		tokenCachePath = "msteams_session.json"
 | 
			
		||||
	}
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	m := msauth.NewManager()
 | 
			
		||||
	m.LoadFile(tokenCachePath) //nolint:errcheck
 | 
			
		||||
	ts, err := m.DeviceAuthorizationGrant(ctx, b.GetString("TenantID"), b.GetString("ClientID"), defaultScopes, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	err = m.SaveFile(tokenCachePath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Log.Errorf("Couldn't save sessionfile in %s: %s", tokenCachePath, err)
 | 
			
		||||
	}
 | 
			
		||||
	// make file readable only for matterbridge user
 | 
			
		||||
	err = os.Chmod(tokenCachePath, 0600)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Log.Errorf("Couldn't change permissions for %s: %s", tokenCachePath, err)
 | 
			
		||||
	}
 | 
			
		||||
	httpClient := oauth2.NewClient(ctx, ts)
 | 
			
		||||
	graphClient := msgraph.NewClient(httpClient)
 | 
			
		||||
	b.gc = graphClient
 | 
			
		||||
	b.ctx = ctx
 | 
			
		||||
 | 
			
		||||
	err = b.setBotID()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	b.Log.Info("Connection succeeded")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmsteams) Disconnect() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmsteams) JoinChannel(channel config.ChannelInfo) error {
 | 
			
		||||
	go func(name string) {
 | 
			
		||||
		for {
 | 
			
		||||
			err := b.poll(name)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				b.Log.Errorf("polling failed for %s: %s. retrying in 5 seconds", name, err)
 | 
			
		||||
			}
 | 
			
		||||
			time.Sleep(time.Second * 5)
 | 
			
		||||
		}
 | 
			
		||||
	}(channel.Name)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmsteams) Send(msg config.Message) (string, error) {
 | 
			
		||||
	b.Log.Debugf("=> Receiving %#v", msg)
 | 
			
		||||
	if msg.ParentID != "" && msg.ParentID != "msg-parent-not-found" {
 | 
			
		||||
		return b.sendReply(msg)
 | 
			
		||||
	}
 | 
			
		||||
	if msg.ParentID == "msg-parent-not-found" {
 | 
			
		||||
		msg.ParentID = ""
 | 
			
		||||
		msg.Text = fmt.Sprintf("[thread]: %s", msg.Text)
 | 
			
		||||
	}
 | 
			
		||||
	ct := b.gc.Teams().ID(b.GetString("TeamID")).Channels().ID(msg.Channel).Messages().Request()
 | 
			
		||||
	text := msg.Username + msg.Text
 | 
			
		||||
	content := &msgraph.ItemBody{Content: &text}
 | 
			
		||||
	rmsg := &msgraph.ChatMessage{Body: content}
 | 
			
		||||
	res, err := ct.Add(b.ctx, rmsg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	return *res.ID, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmsteams) sendReply(msg config.Message) (string, error) {
 | 
			
		||||
	ct := b.gc.Teams().ID(b.GetString("TeamID")).Channels().ID(msg.Channel).Messages().ID(msg.ParentID).Replies().Request()
 | 
			
		||||
	// Handle prefix hint for unthreaded messages.
 | 
			
		||||
 | 
			
		||||
	text := msg.Username + msg.Text
 | 
			
		||||
	content := &msgraph.ItemBody{Content: &text}
 | 
			
		||||
	rmsg := &msgraph.ChatMessage{Body: content}
 | 
			
		||||
	res, err := ct.Add(b.ctx, rmsg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	return *res.ID, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmsteams) getMessages(channel string) ([]msgraph.ChatMessage, error) {
 | 
			
		||||
	ct := b.gc.Teams().ID(b.GetString("TeamID")).Channels().ID(channel).Messages().Request()
 | 
			
		||||
	rct, err := ct.Get(b.ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	b.Log.Debugf("got %#v messages", len(rct))
 | 
			
		||||
	return rct, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//nolint:gocognit
 | 
			
		||||
func (b *Bmsteams) poll(channelName string) error {
 | 
			
		||||
	msgmap := make(map[string]time.Time)
 | 
			
		||||
	b.Log.Debug("getting initial messages")
 | 
			
		||||
	res, err := b.getMessages(channelName)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	for _, msg := range res {
 | 
			
		||||
		msgmap[*msg.ID] = *msg.CreatedDateTime
 | 
			
		||||
		if msg.LastModifiedDateTime != nil {
 | 
			
		||||
			msgmap[*msg.ID] = *msg.LastModifiedDateTime
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	time.Sleep(time.Second * 5)
 | 
			
		||||
	b.Log.Debug("polling for messages")
 | 
			
		||||
	for {
 | 
			
		||||
		res, err := b.getMessages(channelName)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		for i := len(res) - 1; i >= 0; i-- {
 | 
			
		||||
			msg := res[i]
 | 
			
		||||
			if mtime, ok := msgmap[*msg.ID]; ok {
 | 
			
		||||
				if mtime == *msg.CreatedDateTime && msg.LastModifiedDateTime == nil {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				if msg.LastModifiedDateTime != nil && mtime == *msg.LastModifiedDateTime {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if b.GetBool("debug") {
 | 
			
		||||
				b.Log.Debug("Msg dump: ", spew.Sdump(msg))
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// skip non-user message for now.
 | 
			
		||||
			if msg.From.User == nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if *msg.From.User.ID == b.botID {
 | 
			
		||||
				b.Log.Debug("skipping own message")
 | 
			
		||||
				msgmap[*msg.ID] = *msg.CreatedDateTime
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			msgmap[*msg.ID] = *msg.CreatedDateTime
 | 
			
		||||
			if msg.LastModifiedDateTime != nil {
 | 
			
		||||
				msgmap[*msg.ID] = *msg.LastModifiedDateTime
 | 
			
		||||
			}
 | 
			
		||||
			b.Log.Debugf("<= Sending message from %s on %s to gateway", *msg.From.User.DisplayName, b.Account)
 | 
			
		||||
			text := b.convertToMD(*msg.Body.Content)
 | 
			
		||||
			rmsg := config.Message{
 | 
			
		||||
				Username: *msg.From.User.DisplayName,
 | 
			
		||||
				Text:     text,
 | 
			
		||||
				Channel:  channelName,
 | 
			
		||||
				Account:  b.Account,
 | 
			
		||||
				Avatar:   "",
 | 
			
		||||
				UserID:   *msg.From.User.ID,
 | 
			
		||||
				ID:       *msg.ID,
 | 
			
		||||
				Extra:    make(map[string][]interface{}),
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			b.handleAttachments(&rmsg, msg)
 | 
			
		||||
			b.Log.Debugf("<= Message is %#v", rmsg)
 | 
			
		||||
			b.Remote <- rmsg
 | 
			
		||||
		}
 | 
			
		||||
		time.Sleep(time.Second * 5)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmsteams) setBotID() error {
 | 
			
		||||
	req := b.gc.Me().Request()
 | 
			
		||||
	r, err := req.Get(b.ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	b.botID = *r.ID
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmsteams) convertToMD(text string) string {
 | 
			
		||||
	if !strings.Contains(text, "<div>") {
 | 
			
		||||
		return text
 | 
			
		||||
	}
 | 
			
		||||
	var sb strings.Builder
 | 
			
		||||
	err := godown.Convert(&sb, strings.NewReader(text), nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Log.Errorf("Couldn't convert message to markdown %s", text)
 | 
			
		||||
		return text
 | 
			
		||||
	}
 | 
			
		||||
	return sb.String()
 | 
			
		||||
}
 | 
			
		||||
@@ -1,90 +0,0 @@
 | 
			
		||||
package bmumble
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"layeh.com/gumble/gumble"
 | 
			
		||||
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/helper"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (b *Bmumble) handleServerConfig(event *gumble.ServerConfigEvent) {
 | 
			
		||||
	b.serverConfigUpdate <- *event
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmumble) handleTextMessage(event *gumble.TextMessageEvent) {
 | 
			
		||||
	sender := "unknown"
 | 
			
		||||
	if event.TextMessage.Sender != nil {
 | 
			
		||||
		sender = event.TextMessage.Sender.Name
 | 
			
		||||
	}
 | 
			
		||||
	// Convert Mumble HTML messages to markdown
 | 
			
		||||
	parts, err := b.convertHTMLtoMarkdown(event.TextMessage.Message)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Log.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
	now := time.Now().UTC()
 | 
			
		||||
	for i, part := range parts {
 | 
			
		||||
		// Construct matterbridge message and pass on to the gateway
 | 
			
		||||
		rmsg := config.Message{
 | 
			
		||||
			Channel:  strconv.FormatUint(uint64(event.Client.Self.Channel.ID), 10),
 | 
			
		||||
			Username: sender,
 | 
			
		||||
			UserID:   sender + "@" + b.Host,
 | 
			
		||||
			Account:  b.Account,
 | 
			
		||||
		}
 | 
			
		||||
		if part.Image == nil {
 | 
			
		||||
			rmsg.Text = part.Text
 | 
			
		||||
		} else {
 | 
			
		||||
			fname := b.Account + "_" + strconv.FormatInt(now.UnixNano(), 10) + "_" + strconv.Itoa(i) + part.FileExtension
 | 
			
		||||
			rmsg.Extra = make(map[string][]interface{})
 | 
			
		||||
			if err = helper.HandleDownloadSize(b.Log, &rmsg, fname, int64(len(part.Image)), b.General); err != nil {
 | 
			
		||||
				b.Log.WithError(err).Warn("not including image in message")
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			helper.HandleDownloadData(b.Log, &rmsg, fname, "", "", &part.Image, b.General)
 | 
			
		||||
		}
 | 
			
		||||
		b.Log.Debugf("Sending message to gateway: %+v", rmsg)
 | 
			
		||||
		b.Remote <- rmsg
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmumble) handleConnect(event *gumble.ConnectEvent) {
 | 
			
		||||
	// Set the user's "bio"/comment
 | 
			
		||||
	if comment := b.GetString("UserComment"); comment != "" && event.Client.Self != nil {
 | 
			
		||||
		event.Client.Self.SetComment(comment)
 | 
			
		||||
	}
 | 
			
		||||
	// No need to talk or listen
 | 
			
		||||
	event.Client.Self.SetSelfDeafened(true)
 | 
			
		||||
	event.Client.Self.SetSelfMuted(true)
 | 
			
		||||
	// if the Channel variable is set, this is a reconnect -> rejoin channel
 | 
			
		||||
	if b.Channel != nil {
 | 
			
		||||
		if err := b.doJoin(event.Client, *b.Channel); err != nil {
 | 
			
		||||
			b.Log.Error(err)
 | 
			
		||||
		}
 | 
			
		||||
		b.Remote <- config.Message{
 | 
			
		||||
			Username: "system",
 | 
			
		||||
			Text:     "rejoin",
 | 
			
		||||
			Channel:  "",
 | 
			
		||||
			Account:  b.Account,
 | 
			
		||||
			Event:    config.EventRejoinChannels,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmumble) handleUserChange(event *gumble.UserChangeEvent) {
 | 
			
		||||
	// Only care about changes to self
 | 
			
		||||
	if event.User != event.Client.Self {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	// Someone attempted to move the user out of the configured channel; attempt to join back
 | 
			
		||||
	if b.Channel != nil {
 | 
			
		||||
		if err := b.doJoin(event.Client, *b.Channel); err != nil {
 | 
			
		||||
			b.Log.Error(err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmumble) handleDisconnect(event *gumble.DisconnectEvent) {
 | 
			
		||||
	b.connected <- *event
 | 
			
		||||
}
 | 
			
		||||
@@ -1,143 +0,0 @@
 | 
			
		||||
package bmumble
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"mime"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/mattn/godown"
 | 
			
		||||
	"github.com/vincent-petithory/dataurl"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type MessagePart struct {
 | 
			
		||||
	Text          string
 | 
			
		||||
	FileExtension string
 | 
			
		||||
	Image         []byte
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmumble) decodeImage(uri string, parts *[]MessagePart) error {
 | 
			
		||||
	// Decode the data:image/... URI
 | 
			
		||||
	image, err := dataurl.DecodeString(uri)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Log.WithError(err).Info("No image extracted")
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	// Determine the file extensions for that image
 | 
			
		||||
	ext, err := mime.ExtensionsByType(image.MediaType.ContentType())
 | 
			
		||||
	if err != nil || len(ext) == 0 {
 | 
			
		||||
		b.Log.WithError(err).Infof("No file extension registered for MIME type '%s'", image.MediaType.ContentType())
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	// Add the image to the MessagePart slice
 | 
			
		||||
	*parts = append(*parts, MessagePart{"", ext[0], image.Data})
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmumble) tokenize(t *string) ([]MessagePart, error) {
 | 
			
		||||
	// `^(.*?)` matches everything before the image
 | 
			
		||||
	// `!\[[^\]]*\]\(` matches the `]+)` 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
 | 
			
		||||
}
 | 
			
		||||
@@ -1,259 +0,0 @@
 | 
			
		||||
package bmumble
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"crypto/x509"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"layeh.com/gumble/gumble"
 | 
			
		||||
	"layeh.com/gumble/gumbleutil"
 | 
			
		||||
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/helper"
 | 
			
		||||
	stripmd "github.com/writeas/go-strip-markdown"
 | 
			
		||||
 | 
			
		||||
	// We need to import the 'data' package as an implicit dependency.
 | 
			
		||||
	// See: https://godoc.org/github.com/paulrosania/go-charset/charset
 | 
			
		||||
	_ "github.com/paulrosania/go-charset/data"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Bmumble struct {
 | 
			
		||||
	client             *gumble.Client
 | 
			
		||||
	Nick               string
 | 
			
		||||
	Host               string
 | 
			
		||||
	Channel            *uint32
 | 
			
		||||
	local              chan config.Message
 | 
			
		||||
	running            chan error
 | 
			
		||||
	connected          chan gumble.DisconnectEvent
 | 
			
		||||
	serverConfigUpdate chan gumble.ServerConfigEvent
 | 
			
		||||
	serverConfig       gumble.ServerConfigEvent
 | 
			
		||||
	tlsConfig          tls.Config
 | 
			
		||||
 | 
			
		||||
	*bridge.Config
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func New(cfg *bridge.Config) bridge.Bridger {
 | 
			
		||||
	b := &Bmumble{}
 | 
			
		||||
	b.Config = cfg
 | 
			
		||||
	b.Nick = b.GetString("Nick")
 | 
			
		||||
	b.local = make(chan config.Message)
 | 
			
		||||
	b.running = make(chan error)
 | 
			
		||||
	b.connected = make(chan gumble.DisconnectEvent)
 | 
			
		||||
	b.serverConfigUpdate = make(chan gumble.ServerConfigEvent)
 | 
			
		||||
	return b
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmumble) Connect() error {
 | 
			
		||||
	b.Log.Infof("Connecting %s", b.GetString("Server"))
 | 
			
		||||
	host, portstr, err := net.SplitHostPort(b.GetString("Server"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	b.Host = host
 | 
			
		||||
	_, err = strconv.Atoi(portstr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = b.buildTLSConfig(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	go b.doSend()
 | 
			
		||||
	go b.connectLoop()
 | 
			
		||||
	err = <-b.running
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmumble) Disconnect() error {
 | 
			
		||||
	return b.client.Disconnect()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmumble) JoinChannel(channel config.ChannelInfo) error {
 | 
			
		||||
	cid, err := strconv.ParseUint(channel.Name, 10, 32)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	channelID := uint32(cid)
 | 
			
		||||
	if b.Channel != nil && *b.Channel != channelID {
 | 
			
		||||
		b.Log.Fatalf("Cannot join channel ID '%d', already joined to channel ID %d", channelID, *b.Channel)
 | 
			
		||||
		return errors.New("the Mumble bridge can only join a single channel")
 | 
			
		||||
	}
 | 
			
		||||
	b.Channel = &channelID
 | 
			
		||||
	return b.doJoin(b.client, channelID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmumble) Send(msg config.Message) (string, error) {
 | 
			
		||||
	// Only process text messages
 | 
			
		||||
	b.Log.Debugf("=> Received local message %#v", msg)
 | 
			
		||||
	if msg.Event != "" && msg.Event != config.EventUserAction {
 | 
			
		||||
		return "", nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	attachments := b.extractFiles(&msg)
 | 
			
		||||
	b.local <- msg
 | 
			
		||||
	for _, a := range attachments {
 | 
			
		||||
		b.local <- a
 | 
			
		||||
	}
 | 
			
		||||
	return "", nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmumble) buildTLSConfig() error {
 | 
			
		||||
	b.tlsConfig = tls.Config{}
 | 
			
		||||
	// Load TLS client certificate keypair required for registered user authentication
 | 
			
		||||
	if cpath := b.GetString("TLSClientCertificate"); cpath != "" {
 | 
			
		||||
		if ckey := b.GetString("TLSClientKey"); ckey != "" {
 | 
			
		||||
			cert, err := tls.LoadX509KeyPair(cpath, ckey)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			b.tlsConfig.Certificates = []tls.Certificate{cert}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// Load TLS CA used for server verification.  If not provided, the Go system trust anchor is used
 | 
			
		||||
	if capath := b.GetString("TLSCACertificate"); capath != "" {
 | 
			
		||||
		ca, err := ioutil.ReadFile(capath)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		b.tlsConfig.RootCAs = x509.NewCertPool()
 | 
			
		||||
		b.tlsConfig.RootCAs.AppendCertsFromPEM(ca)
 | 
			
		||||
	}
 | 
			
		||||
	b.tlsConfig.InsecureSkipVerify = b.GetBool("SkipTLSVerify")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmumble) connectLoop() {
 | 
			
		||||
	firstConnect := true
 | 
			
		||||
	for {
 | 
			
		||||
		err := b.doConnect()
 | 
			
		||||
		if firstConnect {
 | 
			
		||||
			b.running <- err
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			b.Log.Errorf("Connection to server failed: %#v", err)
 | 
			
		||||
			if firstConnect {
 | 
			
		||||
				break
 | 
			
		||||
			} else {
 | 
			
		||||
				b.Log.Info("Retrying in 10s")
 | 
			
		||||
				time.Sleep(10 * time.Second)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		firstConnect = false
 | 
			
		||||
		d := <-b.connected
 | 
			
		||||
		switch d.Type {
 | 
			
		||||
		case gumble.DisconnectError:
 | 
			
		||||
			b.Log.Errorf("Lost connection to the server (%s), attempting reconnect", d.String)
 | 
			
		||||
			continue
 | 
			
		||||
		case gumble.DisconnectKicked:
 | 
			
		||||
			b.Log.Errorf("Kicked from the server (%s), attempting reconnect", d.String)
 | 
			
		||||
			continue
 | 
			
		||||
		case gumble.DisconnectBanned:
 | 
			
		||||
			b.Log.Errorf("Banned from the server (%s), not attempting reconnect", d.String)
 | 
			
		||||
			close(b.connected)
 | 
			
		||||
			close(b.running)
 | 
			
		||||
			return
 | 
			
		||||
		case gumble.DisconnectUser:
 | 
			
		||||
			b.Log.Infof("Disconnect successful")
 | 
			
		||||
			close(b.connected)
 | 
			
		||||
			close(b.running)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmumble) doConnect() error {
 | 
			
		||||
	// Create new gumble config and attach event handlers
 | 
			
		||||
	gumbleConfig := gumble.NewConfig()
 | 
			
		||||
	gumbleConfig.Attach(gumbleutil.Listener{
 | 
			
		||||
		ServerConfig: b.handleServerConfig,
 | 
			
		||||
		TextMessage:  b.handleTextMessage,
 | 
			
		||||
		Connect:      b.handleConnect,
 | 
			
		||||
		Disconnect:   b.handleDisconnect,
 | 
			
		||||
		UserChange:   b.handleUserChange,
 | 
			
		||||
	})
 | 
			
		||||
	gumbleConfig.Username = b.GetString("Nick")
 | 
			
		||||
	if password := b.GetString("Password"); password != "" {
 | 
			
		||||
		gumbleConfig.Password = password
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client, err := gumble.DialWithDialer(new(net.Dialer), b.GetString("Server"), gumbleConfig, &b.tlsConfig)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	b.client = client
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmumble) doJoin(client *gumble.Client, channelID uint32) error {
 | 
			
		||||
	channel, ok := client.Channels[channelID]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return fmt.Errorf("no channel with ID %d", channelID)
 | 
			
		||||
	}
 | 
			
		||||
	client.Self.Move(channel)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmumble) doSend() {
 | 
			
		||||
	// Message sending loop that makes sure server-side
 | 
			
		||||
	// restrictions and client-side message traits don't conflict
 | 
			
		||||
	// with each other.
 | 
			
		||||
	for {
 | 
			
		||||
		select {
 | 
			
		||||
		case serverConfig := <-b.serverConfigUpdate:
 | 
			
		||||
			b.Log.Debugf("Received server config update: AllowHTML=%#v, MaximumMessageLength=%#v", serverConfig.AllowHTML, serverConfig.MaximumMessageLength)
 | 
			
		||||
			b.serverConfig = serverConfig
 | 
			
		||||
		case msg := <-b.local:
 | 
			
		||||
			b.processMessage(&msg)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmumble) processMessage(msg *config.Message) {
 | 
			
		||||
	b.Log.Debugf("Processing message %s", msg.Text)
 | 
			
		||||
 | 
			
		||||
	allowHTML := true
 | 
			
		||||
	if b.serverConfig.AllowHTML != nil {
 | 
			
		||||
		allowHTML = *b.serverConfig.AllowHTML
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If this is a specially generated image message, send it unmodified
 | 
			
		||||
	if msg.Event == "mumble_image" {
 | 
			
		||||
		if allowHTML {
 | 
			
		||||
			b.client.Self.Channel.Send(msg.Username+msg.Text, false)
 | 
			
		||||
		} else {
 | 
			
		||||
			b.Log.Info("Can't send image, server does not allow HTML messages")
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Don't process empty messages
 | 
			
		||||
	if len(msg.Text) == 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	// If HTML is allowed, convert markdown into HTML, otherwise strip markdown
 | 
			
		||||
	if allowHTML {
 | 
			
		||||
		msg.Text = helper.ParseMarkdown(msg.Text)
 | 
			
		||||
	} else {
 | 
			
		||||
		msg.Text = stripmd.Strip(msg.Text)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If there is a maximum message length, split and truncate the lines
 | 
			
		||||
	var msgLines []string
 | 
			
		||||
	if maxLength := b.serverConfig.MaximumMessageLength; maxLength != nil {
 | 
			
		||||
		msgLines = helper.GetSubLines(msg.Text, *maxLength-len(msg.Username))
 | 
			
		||||
	} else {
 | 
			
		||||
		msgLines = helper.GetSubLines(msg.Text, 0)
 | 
			
		||||
	}
 | 
			
		||||
	// Send the individual lindes
 | 
			
		||||
	for i := range msgLines {
 | 
			
		||||
		b.client.Self.Channel.Send(msg.Username+msgLines[i], false)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,203 +0,0 @@
 | 
			
		||||
package nctalk
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
 | 
			
		||||
	"gomod.garykim.dev/nc-talk/ocs"
 | 
			
		||||
	"gomod.garykim.dev/nc-talk/room"
 | 
			
		||||
	"gomod.garykim.dev/nc-talk/user"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Btalk struct {
 | 
			
		||||
	user  *user.TalkUser
 | 
			
		||||
	rooms []Broom
 | 
			
		||||
	*bridge.Config
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func New(cfg *bridge.Config) bridge.Bridger {
 | 
			
		||||
	return &Btalk{Config: cfg}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Broom struct {
 | 
			
		||||
	room      *room.TalkRoom
 | 
			
		||||
	ctx       context.Context
 | 
			
		||||
	ctxCancel context.CancelFunc
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Btalk) Connect() error {
 | 
			
		||||
	b.Log.Info("Connecting")
 | 
			
		||||
	tconfig := &user.TalkUserConfig{
 | 
			
		||||
		TLSConfig: &tls.Config{
 | 
			
		||||
			InsecureSkipVerify: b.GetBool("SkipTLSVerify"), //nolint:gosec
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	var err error
 | 
			
		||||
	b.user, err = user.NewUser(b.GetString("Server"), b.GetString("Login"), b.GetString("Password"), tconfig)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Log.Error("Config could not be used")
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	_, err = b.user.Capabilities()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Log.Error("Cannot Connect")
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	b.Log.Info("Connected")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Btalk) Disconnect() error {
 | 
			
		||||
	for _, r := range b.rooms {
 | 
			
		||||
		r.ctxCancel()
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Btalk) JoinChannel(channel config.ChannelInfo) error {
 | 
			
		||||
	tr, err := room.NewTalkRoom(b.user, channel.Name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	newRoom := Broom{
 | 
			
		||||
		room: tr,
 | 
			
		||||
	}
 | 
			
		||||
	newRoom.ctx, newRoom.ctxCancel = context.WithCancel(context.Background())
 | 
			
		||||
	c, err := newRoom.room.ReceiveMessages(newRoom.ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	b.rooms = append(b.rooms, newRoom)
 | 
			
		||||
 | 
			
		||||
	// Config
 | 
			
		||||
	guestSuffix := " (Guest)"
 | 
			
		||||
	if b.IsKeySet("GuestSuffix") {
 | 
			
		||||
		guestSuffix = b.GetString("GuestSuffix")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		for msg := range c {
 | 
			
		||||
			msg := msg
 | 
			
		||||
			// ignore messages that are one of the following
 | 
			
		||||
			// * not a message from a user
 | 
			
		||||
			// * from ourselves
 | 
			
		||||
			if msg.MessageType != ocs.MessageComment || msg.ActorID == b.user.User {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			remoteMessage := config.Message{
 | 
			
		||||
				Text:     formatRichObjectString(msg.Message, msg.MessageParameters),
 | 
			
		||||
				Channel:  newRoom.room.Token,
 | 
			
		||||
				Username: DisplayName(msg, guestSuffix),
 | 
			
		||||
				UserID:   msg.ActorID,
 | 
			
		||||
				Account:  b.Account,
 | 
			
		||||
			}
 | 
			
		||||
			// It is possible for the ID to not be set on older versions of Talk so we only set it if
 | 
			
		||||
			// the ID is not blank
 | 
			
		||||
			if msg.ID != 0 {
 | 
			
		||||
				remoteMessage.ID = strconv.Itoa(msg.ID)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Handle Files
 | 
			
		||||
			err = b.handleFiles(&remoteMessage, &msg)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				b.Log.Errorf("Error handling file: %#v", msg)
 | 
			
		||||
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			b.Log.Debugf("<= Message is %#v", remoteMessage)
 | 
			
		||||
			b.Remote <- remoteMessage
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Btalk) Send(msg config.Message) (string, error) {
 | 
			
		||||
	r := b.getRoom(msg.Channel)
 | 
			
		||||
	if r == nil {
 | 
			
		||||
		b.Log.Errorf("Could not find room for %v", msg.Channel)
 | 
			
		||||
		return "", nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Talk currently only supports sending normal messages
 | 
			
		||||
	if msg.Event != "" {
 | 
			
		||||
		return "", nil
 | 
			
		||||
	}
 | 
			
		||||
	sentMessage, err := r.room.SendMessage(msg.Username + msg.Text)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Log.Errorf("Could not send message to room %v from %v: %v", msg.Channel, msg.Username, err)
 | 
			
		||||
		return "", nil
 | 
			
		||||
	}
 | 
			
		||||
	return strconv.Itoa(sentMessage.ID), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Btalk) getRoom(token string) *Broom {
 | 
			
		||||
	for _, r := range b.rooms {
 | 
			
		||||
		if r.room.Token == token {
 | 
			
		||||
			return &r
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Btalk) handleFiles(mmsg *config.Message, message *ocs.TalkRoomMessageData) error {
 | 
			
		||||
	for _, parameter := range message.MessageParameters {
 | 
			
		||||
		if parameter.Type == ocs.ROSTypeFile {
 | 
			
		||||
			// Get the file
 | 
			
		||||
			file, err := b.user.DownloadFile(parameter.Path)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if mmsg.Extra == nil {
 | 
			
		||||
				mmsg.Extra = make(map[string][]interface{})
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			mmsg.Extra["file"] = append(mmsg.Extra["file"], config.FileInfo{
 | 
			
		||||
				Name:   parameter.Name,
 | 
			
		||||
				Data:   file,
 | 
			
		||||
				Size:   int64(len(*file)),
 | 
			
		||||
				Avatar: false,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Spec: https://github.com/nextcloud/server/issues/1706#issue-182308785
 | 
			
		||||
func formatRichObjectString(message string, parameters map[string]ocs.RichObjectString) string {
 | 
			
		||||
	for id, parameter := range parameters {
 | 
			
		||||
		text := parameter.Name
 | 
			
		||||
 | 
			
		||||
		switch parameter.Type {
 | 
			
		||||
		case ocs.ROSTypeUser, ocs.ROSTypeGroup:
 | 
			
		||||
			text = "@" + text
 | 
			
		||||
		case ocs.ROSTypeFile:
 | 
			
		||||
			if parameter.Link != "" {
 | 
			
		||||
				text = parameter.Name
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		message = strings.ReplaceAll(message, "{"+id+"}", text)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return message
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func DisplayName(msg ocs.TalkRoomMessageData, suffix string) string {
 | 
			
		||||
	if msg.ActorType == ocs.ActorGuest {
 | 
			
		||||
		if msg.ActorDisplayName == "" {
 | 
			
		||||
			return "Guest"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return msg.ActorDisplayName + suffix
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return msg.ActorDisplayName
 | 
			
		||||
}
 | 
			
		||||
@@ -2,7 +2,6 @@ package brocketchat
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (b *Brocketchat) handleRocket() {
 | 
			
		||||
@@ -39,23 +38,6 @@ func (b *Brocketchat) handleRocketHook(messages chan *config.Message) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Brocketchat) handleStatusEvent(ev models.Message, rmsg *config.Message) bool {
 | 
			
		||||
	switch ev.Type {
 | 
			
		||||
	case "":
 | 
			
		||||
		// this is a normal message, no processing needed
 | 
			
		||||
		// return true so the message is not dropped
 | 
			
		||||
		return true
 | 
			
		||||
	case sUserJoined, sUserLeft:
 | 
			
		||||
		rmsg.Event = config.EventJoinLeave
 | 
			
		||||
		return true
 | 
			
		||||
	case sRoomChangedTopic:
 | 
			
		||||
		rmsg.Event = config.EventTopicChange
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	b.Log.Debugf("Dropping message with unknown type: %s", ev.Type)
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Brocketchat) handleRocketClient(messages chan *config.Message) {
 | 
			
		||||
	for message := range b.messageChan {
 | 
			
		||||
		// skip messages with same ID, apparently messages get duplicated for an unknown reason
 | 
			
		||||
@@ -77,14 +59,9 @@ func (b *Brocketchat) handleRocketClient(messages chan *config.Message) {
 | 
			
		||||
			UserID:   message.User.ID,
 | 
			
		||||
			ID:       message.ID,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// handleStatusEvent returns false if the message should be dropped
 | 
			
		||||
		// in that case it is probably some modification to the channel we do not want to relay
 | 
			
		||||
		if b.handleStatusEvent(m, rmsg) {
 | 
			
		||||
		messages <- rmsg
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Brocketchat) handleUploadFile(msg *config.Message) error {
 | 
			
		||||
	for _, f := range msg.Extra["file"] {
 | 
			
		||||
 
 | 
			
		||||
@@ -58,9 +58,6 @@ func (b *Brocketchat) doConnectWebhookURL() error {
 | 
			
		||||
func (b *Brocketchat) apiLogin() error {
 | 
			
		||||
	b.Log.Debugf("handling apiLogin()")
 | 
			
		||||
	credentials := &models.UserCredentials{Email: b.GetString("login"), Password: b.GetString("password")}
 | 
			
		||||
	if b.GetString("Token") != "" {
 | 
			
		||||
		credentials = &models.UserCredentials{ID: b.GetString("Login"), Token: b.GetString("Token")}
 | 
			
		||||
	}
 | 
			
		||||
	myURL, err := url.Parse(b.GetString("server"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
 
 | 
			
		||||
@@ -29,12 +29,6 @@ type Brocketchat struct {
 | 
			
		||||
	sync.RWMutex
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	sUserJoined       = "uj"
 | 
			
		||||
	sUserLeft         = "ul"
 | 
			
		||||
	sRoomChangedTopic = "room_changed_topic"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func New(cfg *bridge.Config) bridge.Bridger {
 | 
			
		||||
	newCache, err := lru.New(100)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -114,11 +108,6 @@ func (b *Brocketchat) Send(msg config.Message) (string, error) {
 | 
			
		||||
	msg.Channel = strings.TrimPrefix(msg.Channel, "#")
 | 
			
		||||
	channel := &models.Channel{ID: b.getChannelID(msg.Channel), Name: msg.Channel}
 | 
			
		||||
 | 
			
		||||
	// Make a action /me of the message
 | 
			
		||||
	if msg.Event == config.EventUserAction {
 | 
			
		||||
		msg.Text = "_" + msg.Text + "_"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Delete message
 | 
			
		||||
	if msg.Event == config.EventMsgDelete {
 | 
			
		||||
		if msg.ID == "" {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,22 +1,18 @@
 | 
			
		||||
package bslack
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"html"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/helper"
 | 
			
		||||
	"github.com/slack-go/slack"
 | 
			
		||||
	"github.com/nlopes/slack"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ErrEventIgnored is for events that should be ignored
 | 
			
		||||
var ErrEventIgnored = errors.New("this event message should ignored")
 | 
			
		||||
 | 
			
		||||
func (b *Bslack) handleSlack() {
 | 
			
		||||
	messages := make(chan *config.Message)
 | 
			
		||||
	if b.GetString(incomingWebhookConfig) != "" && b.GetString(tokenConfig) == "" {
 | 
			
		||||
	if b.GetString(incomingWebhookConfig) != "" {
 | 
			
		||||
		b.Log.Debugf("Choosing webhooks based receiving")
 | 
			
		||||
		go b.handleMatterHook(messages)
 | 
			
		||||
	} else {
 | 
			
		||||
@@ -34,7 +30,6 @@ func (b *Bslack) handleSlack() {
 | 
			
		||||
			message.Text = b.replaceVariable(message.Text)
 | 
			
		||||
			message.Text = b.replaceChannel(message.Text)
 | 
			
		||||
			message.Text = b.replaceURL(message.Text)
 | 
			
		||||
			message.Text = b.replaceb0rkedMarkDown(message.Text)
 | 
			
		||||
			message.Text = html.UnescapeString(message.Text)
 | 
			
		||||
 | 
			
		||||
			// Add the avatar
 | 
			
		||||
@@ -48,7 +43,7 @@ func (b *Bslack) handleSlack() {
 | 
			
		||||
 | 
			
		||||
func (b *Bslack) handleSlackClient(messages chan *config.Message) {
 | 
			
		||||
	for msg := range b.rtm.IncomingEvents {
 | 
			
		||||
		if msg.Type != sUserTyping && msg.Type != sHello && msg.Type != sLatencyReport {
 | 
			
		||||
		if msg.Type != sUserTyping && msg.Type != sLatencyReport {
 | 
			
		||||
			b.Log.Debugf("== Receiving event %#v", msg.Data)
 | 
			
		||||
		}
 | 
			
		||||
		switch ev := msg.Data.(type) {
 | 
			
		||||
@@ -57,9 +52,7 @@ func (b *Bslack) handleSlackClient(messages chan *config.Message) {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			rmsg, err := b.handleTypingEvent(ev)
 | 
			
		||||
			if err == ErrEventIgnored {
 | 
			
		||||
				continue
 | 
			
		||||
			} else if err != nil {
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				b.Log.Errorf("%#v", err)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
@@ -93,7 +86,7 @@ func (b *Bslack) handleSlackClient(messages chan *config.Message) {
 | 
			
		||||
			b.Log.Errorf("Connection failed %#v %#v", ev.Error(), ev.ErrorObj)
 | 
			
		||||
		case *slack.MemberJoinedChannelEvent:
 | 
			
		||||
			b.users.populateUser(ev.User)
 | 
			
		||||
		case *slack.HelloEvent, *slack.LatencyReport, *slack.ConnectingEvent:
 | 
			
		||||
		case *slack.LatencyReport:
 | 
			
		||||
			continue
 | 
			
		||||
		default:
 | 
			
		||||
			b.Log.Debugf("Unhandled incoming event: %T", ev)
 | 
			
		||||
@@ -130,11 +123,11 @@ func (b *Bslack) skipMessageEvent(ev *slack.MessageEvent) bool {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check for our callback ID
 | 
			
		||||
	hasOurCallbackID := false
 | 
			
		||||
	if len(ev.Blocks.BlockSet) == 1 {
 | 
			
		||||
		block, ok := ev.Blocks.BlockSet[0].(*slack.SectionBlock)
 | 
			
		||||
		hasOurCallbackID = ok && block.BlockID == "matterbridge_"+b.uuid
 | 
			
		||||
	// Skip any messages that we made ourselves or from 'slackbot' (see #527).
 | 
			
		||||
	if ev.Username == sSlackBotUser ||
 | 
			
		||||
		(b.rtm != nil && ev.Username == b.si.User.Name) ||
 | 
			
		||||
		(len(ev.Attachments) > 0 && ev.Attachments[0].CallbackID == "matterbridge_"+b.uuid) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ev.SubMessage != nil {
 | 
			
		||||
@@ -149,16 +142,6 @@ func (b *Bslack) skipMessageEvent(ev *slack.MessageEvent) bool {
 | 
			
		||||
		if ev.SubType == "message_replied" && ev.Hidden {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		if len(ev.SubMessage.Blocks.BlockSet) == 1 {
 | 
			
		||||
			block, ok := ev.SubMessage.Blocks.BlockSet[0].(*slack.SectionBlock)
 | 
			
		||||
			hasOurCallbackID = ok && block.BlockID == "matterbridge_"+b.uuid
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Skip any messages that we made ourselves or from 'slackbot' (see #527).
 | 
			
		||||
	if ev.Username == sSlackBotUser ||
 | 
			
		||||
		(b.rtm != nil && ev.Username == b.si.User.Name) || hasOurCallbackID {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(ev.Files) > 0 {
 | 
			
		||||
@@ -286,9 +269,6 @@ func (b *Bslack) handleAttachments(ev *slack.MessageEvent, rmsg *config.Message)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bslack) handleTypingEvent(ev *slack.UserTypingEvent) (*config.Message, error) {
 | 
			
		||||
	if ev.User == b.si.User.ID {
 | 
			
		||||
		return nil, ErrEventIgnored
 | 
			
		||||
	}
 | 
			
		||||
	channelInfo, err := b.channels.getChannelByID(ev.Channel)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
 
 | 
			
		||||
@@ -7,8 +7,8 @@ import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/nlopes/slack"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/slack-go/slack"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// populateReceivedMessage shapes the initial Matterbridge message that we will forward to the
 | 
			
		||||
@@ -188,36 +188,6 @@ func (b *Bslack) replaceURL(text string) string {
 | 
			
		||||
	return text
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bslack) replaceb0rkedMarkDown(text string) string {
 | 
			
		||||
	// taken from https://github.com/mattermost/mattermost-server/blob/master/app/slackimport.go
 | 
			
		||||
	//
 | 
			
		||||
	regexReplaceAllString := []struct {
 | 
			
		||||
		regex *regexp.Regexp
 | 
			
		||||
		rpl   string
 | 
			
		||||
	}{
 | 
			
		||||
		// bold
 | 
			
		||||
		{
 | 
			
		||||
			regexp.MustCompile(`(^|[\s.;,])\*(\S[^*\n]+)\*`),
 | 
			
		||||
			"$1**$2**",
 | 
			
		||||
		},
 | 
			
		||||
		// strikethrough
 | 
			
		||||
		{
 | 
			
		||||
			regexp.MustCompile(`(^|[\s.;,])\~(\S[^~\n]+)\~`),
 | 
			
		||||
			"$1~~$2~~",
 | 
			
		||||
		},
 | 
			
		||||
		// single paragraph blockquote
 | 
			
		||||
		// Slack converts > character to >
 | 
			
		||||
		{
 | 
			
		||||
			regexp.MustCompile(`(?sm)^>`),
 | 
			
		||||
			">",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, rule := range regexReplaceAllString {
 | 
			
		||||
		text = rule.regex.ReplaceAllString(text, rule.rpl)
 | 
			
		||||
	}
 | 
			
		||||
	return text
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bslack) replaceCodeFence(text string) string {
 | 
			
		||||
	return codeFenceRE.ReplaceAllString(text, "```")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ import (
 | 
			
		||||
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge"
 | 
			
		||||
	"github.com/42wim/matterbridge/matterhook"
 | 
			
		||||
	"github.com/slack-go/slack"
 | 
			
		||||
	"github.com/nlopes/slack"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type BLegacy struct {
 | 
			
		||||
@@ -13,9 +13,7 @@ type BLegacy struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewLegacy(cfg *bridge.Config) bridge.Bridger {
 | 
			
		||||
	b := &BLegacy{Bslack: newBridge(cfg)}
 | 
			
		||||
	b.legacy = true
 | 
			
		||||
	return b
 | 
			
		||||
	return &BLegacy{Bslack: newBridge(cfg)}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *BLegacy) Connect() error {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,9 +12,9 @@ import (
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/helper"
 | 
			
		||||
	"github.com/42wim/matterbridge/matterhook"
 | 
			
		||||
	lru "github.com/hashicorp/golang-lru"
 | 
			
		||||
	"github.com/hashicorp/golang-lru"
 | 
			
		||||
	"github.com/nlopes/slack"
 | 
			
		||||
	"github.com/rs/xid"
 | 
			
		||||
	"github.com/slack-go/slack"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Bslack struct {
 | 
			
		||||
@@ -32,11 +32,9 @@ type Bslack struct {
 | 
			
		||||
 | 
			
		||||
	channels *channels
 | 
			
		||||
	users    *users
 | 
			
		||||
	legacy   bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	sHello           = "hello"
 | 
			
		||||
	sChannelJoin     = "channel_join"
 | 
			
		||||
	sChannelLeave    = "channel_leave"
 | 
			
		||||
	sChannelJoined   = "channel_joined"
 | 
			
		||||
@@ -64,7 +62,6 @@ const (
 | 
			
		||||
	editSuffixConfig      = "EditSuffix"
 | 
			
		||||
	iconURLConfig         = "iconurl"
 | 
			
		||||
	noSendJoinConfig      = "nosendjoinpart"
 | 
			
		||||
	messageLength         = 3000
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func New(cfg *bridge.Config) bridge.Bridger {
 | 
			
		||||
@@ -154,18 +151,6 @@ func (b *Bslack) JoinChannel(channel config.ChannelInfo) error {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// try to join a channel when in legacy
 | 
			
		||||
	if b.legacy {
 | 
			
		||||
		_, err := b.sc.JoinChannel(channel.Name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			switch err.Error() {
 | 
			
		||||
			case "name_taken", "restricted_action":
 | 
			
		||||
			case "default":
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b.channels.populateChannels(false)
 | 
			
		||||
 | 
			
		||||
	channelInfo, err := b.channels.getChannel(channel.Name)
 | 
			
		||||
@@ -178,8 +163,7 @@ func (b *Bslack) JoinChannel(channel config.ChannelInfo) error {
 | 
			
		||||
		channel.Name = channelInfo.Name
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// we can't join a channel unless we are using legacy tokens #651
 | 
			
		||||
	if !channelInfo.IsMember && !b.legacy {
 | 
			
		||||
	if !channelInfo.IsMember {
 | 
			
		||||
		return fmt.Errorf("slack integration that matterbridge is using is not member of channel '%s', please add it manually", channelInfo.Name)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
@@ -195,7 +179,6 @@ func (b *Bslack) Send(msg config.Message) (string, error) {
 | 
			
		||||
		b.Log.Debugf("=> Receiving %#v", msg)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	msg.Text = helper.ClipMessage(msg.Text, messageLength)
 | 
			
		||||
	msg.Text = b.replaceCodeFence(msg.Text)
 | 
			
		||||
 | 
			
		||||
	// Make a action /me of the message
 | 
			
		||||
@@ -204,7 +187,7 @@ func (b *Bslack) Send(msg config.Message) (string, error) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Use webhook to send the message
 | 
			
		||||
	if b.GetString(outgoingWebhookConfig) != "" && b.GetString(tokenConfig) == "" {
 | 
			
		||||
	if b.GetString(outgoingWebhookConfig) != "" {
 | 
			
		||||
		return "", b.sendWebhook(msg)
 | 
			
		||||
	}
 | 
			
		||||
	return b.sendRTM(msg)
 | 
			
		||||
@@ -410,6 +393,7 @@ func (b *Bslack) editMessage(msg *config.Message, channelInfo *slack.Channel) (b
 | 
			
		||||
	}
 | 
			
		||||
	messageOptions := b.prepareMessageOptions(msg)
 | 
			
		||||
	for {
 | 
			
		||||
		messageOptions = append(messageOptions, slack.MsgOptionText(msg.Text, false))
 | 
			
		||||
		_, _, _, err := b.rtm.UpdateMessage(channelInfo.ID, msg.ID, messageOptions...)
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			return true, nil
 | 
			
		||||
@@ -428,6 +412,11 @@ func (b *Bslack) postMessage(msg *config.Message, channelInfo *slack.Channel) (s
 | 
			
		||||
		return "", nil
 | 
			
		||||
	}
 | 
			
		||||
	messageOptions := b.prepareMessageOptions(msg)
 | 
			
		||||
	messageOptions = append(
 | 
			
		||||
		messageOptions,
 | 
			
		||||
		slack.MsgOptionText(msg.Text, false),
 | 
			
		||||
		slack.MsgOptionEnableLinkUnfurl(),
 | 
			
		||||
	)
 | 
			
		||||
	for {
 | 
			
		||||
		_, id, err := b.rtm.PostMessage(channelInfo.ID, messageOptions...)
 | 
			
		||||
		if err == nil {
 | 
			
		||||
@@ -493,6 +482,8 @@ func (b *Bslack) prepareMessageOptions(msg *config.Message) []slack.MsgOption {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var attachments []slack.Attachment
 | 
			
		||||
	// add a callback ID so we can see we created it
 | 
			
		||||
	attachments = append(attachments, slack.Attachment{CallbackID: "matterbridge_" + b.uuid})
 | 
			
		||||
	// add file attachments
 | 
			
		||||
	attachments = append(attachments, b.createAttach(msg.Extra)...)
 | 
			
		||||
	// add slack attachments (from another slack bridge)
 | 
			
		||||
@@ -503,19 +494,6 @@ func (b *Bslack) prepareMessageOptions(msg *config.Message) []slack.MsgOption {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var opts []slack.MsgOption
 | 
			
		||||
	opts = append(opts,
 | 
			
		||||
		// provide regular text field (fallback used in Slack notifications, etc.)
 | 
			
		||||
		slack.MsgOptionText(msg.Text, false),
 | 
			
		||||
 | 
			
		||||
		// add a callback ID so we can see we created it
 | 
			
		||||
		slack.MsgOptionBlocks(slack.NewSectionBlock(
 | 
			
		||||
			slack.NewTextBlockObject(slack.MarkdownType, msg.Text, false, false),
 | 
			
		||||
			nil, nil,
 | 
			
		||||
			slack.SectionBlockOptionBlockID("matterbridge_"+b.uuid),
 | 
			
		||||
		)),
 | 
			
		||||
 | 
			
		||||
		slack.MsgOptionEnableLinkUnfurl(),
 | 
			
		||||
	)
 | 
			
		||||
	opts = append(opts, slack.MsgOptionAttachments(attachments...))
 | 
			
		||||
	opts = append(opts, slack.MsgOptionPostMessageParameters(params))
 | 
			
		||||
	return opts
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,8 @@ import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/nlopes/slack"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/slack-go/slack"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const minimumRefreshInterval = 10 * time.Second
 | 
			
		||||
 
 | 
			
		||||
@@ -130,10 +130,6 @@ func (b *Bsshchat) handleSSHChat() error {
 | 
			
		||||
			if strings.Contains(b.r.Text(), "Rate limiting is in effect") {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			// skip our own messages
 | 
			
		||||
			if !strings.HasPrefix(b.r.Text(), "["+b.GetString("Nick")+"] \x1b") {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			res := strings.Split(stripPrompt(b.r.Text()), ":")
 | 
			
		||||
			if res[0] == "-> Set theme" {
 | 
			
		||||
				wait = false
 | 
			
		||||
 
 | 
			
		||||
@@ -85,7 +85,7 @@ func (b *Bsteam) handleEvents() {
 | 
			
		||||
 | 
			
		||||
func (b *Bsteam) handleLogOnFailed(e *steam.LogOnFailedEvent, myLoginInfo *steam.LogOnDetails) error {
 | 
			
		||||
	switch e.Result {
 | 
			
		||||
	case steamlang.EResult_AccountLoginDeniedNeedTwoFactor:
 | 
			
		||||
	case steamlang.EResult_AccountLogonDeniedNeedTwoFactorCode:
 | 
			
		||||
		b.Log.Info("Steam guard isn't letting me in! Enter 2FA code:")
 | 
			
		||||
		var code string
 | 
			
		||||
		fmt.Scanf("%s", &code)
 | 
			
		||||
 
 | 
			
		||||
@@ -5,11 +5,10 @@ import (
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"unicode/utf16"
 | 
			
		||||
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/helper"
 | 
			
		||||
	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
 | 
			
		||||
	"github.com/go-telegram-bot-api/telegram-bot-api"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (b *Btelegram) handleUpdate(rmsg *config.Message, message, posted, edited *tgbotapi.Message) *tgbotapi.Message {
 | 
			
		||||
@@ -39,33 +38,23 @@ func (b *Btelegram) handleGroups(rmsg *config.Message, message *tgbotapi.Message
 | 
			
		||||
 | 
			
		||||
// handleForwarded handles forwarded messages
 | 
			
		||||
func (b *Btelegram) handleForwarded(rmsg *config.Message, message *tgbotapi.Message) {
 | 
			
		||||
	if message.ForwardDate == 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if message.ForwardFrom == nil {
 | 
			
		||||
		rmsg.Text = "Forwarded from " + unknownUser + ": " + rmsg.Text
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if message.ForwardFrom != nil {
 | 
			
		||||
		usernameForward := ""
 | 
			
		||||
		if b.GetBool("UseFirstName") {
 | 
			
		||||
			usernameForward = message.ForwardFrom.FirstName
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if usernameForward == "" {
 | 
			
		||||
			usernameForward = message.ForwardFrom.UserName
 | 
			
		||||
			if usernameForward == "" {
 | 
			
		||||
				usernameForward = message.ForwardFrom.FirstName
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if usernameForward == "" {
 | 
			
		||||
			usernameForward = unknownUser
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		rmsg.Text = "Forwarded from " + usernameForward + ": " + rmsg.Text
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// handleQuoting handles quoting of previous messages
 | 
			
		||||
func (b *Btelegram) handleQuoting(rmsg *config.Message, message *tgbotapi.Message) {
 | 
			
		||||
@@ -105,7 +94,7 @@ func (b *Btelegram) handleUsername(rmsg *config.Message, message *tgbotapi.Messa
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		// only download avatars if we have a place to upload them (configured mediaserver)
 | 
			
		||||
		if b.General.MediaServerUpload != "" || (b.General.MediaServerDownload != "" && b.General.MediaDownloadPath != "") {
 | 
			
		||||
		if b.General.MediaServerUpload != "" {
 | 
			
		||||
			b.handleDownloadAvatar(message.From.ID, rmsg.Channel)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -217,46 +206,6 @@ func (b *Btelegram) handleDownloadAvatar(userid int, channel string) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Btelegram) maybeConvertTgs(name *string, data *[]byte) {
 | 
			
		||||
	var format string
 | 
			
		||||
	switch b.GetString("MediaConvertTgs") {
 | 
			
		||||
	case FormatWebp:
 | 
			
		||||
		b.Log.Debugf("Tgs to WebP conversion enabled, converting %v", name)
 | 
			
		||||
		format = FormatWebp
 | 
			
		||||
	case FormatPng:
 | 
			
		||||
		// The WebP to PNG converter can't handle animated webp files yet,
 | 
			
		||||
		// and I'm not going to write a path for x/image/webp.
 | 
			
		||||
		// The error message would be:
 | 
			
		||||
		//     conversion failed: webp: non-Alpha VP8X is not implemented
 | 
			
		||||
		// So instead, we tell lottie to directly go to PNG.
 | 
			
		||||
		b.Log.Debugf("Tgs to PNG conversion enabled, converting %v", name)
 | 
			
		||||
		format = FormatPng
 | 
			
		||||
	default:
 | 
			
		||||
		// Otherwise, no conversion was requested. Trying to run the usual webp
 | 
			
		||||
		// converter would fail, because '.tgs.webp' is actually a gzipped JSON
 | 
			
		||||
		// file, and has nothing to do with WebP.
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	err := helper.ConvertTgsToX(data, format, b.Log)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Log.Errorf("conversion failed: %v", err)
 | 
			
		||||
	} else {
 | 
			
		||||
		*name = strings.Replace(*name, "tgs.webp", format, 1)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Btelegram) maybeConvertWebp(name *string, data *[]byte) {
 | 
			
		||||
	if b.GetBool("MediaConvertWebPToPNG") {
 | 
			
		||||
		b.Log.Debugf("WebP to PNG conversion enabled, converting %v", name)
 | 
			
		||||
		err := helper.ConvertWebPToPNG(data)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			b.Log.Errorf("conversion failed: %v", err)
 | 
			
		||||
		} else {
 | 
			
		||||
			*name = strings.Replace(*name, ".webp", ".png", 1)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// handleDownloadFile handles file download
 | 
			
		||||
func (b *Btelegram) handleDownload(rmsg *config.Message, message *tgbotapi.Message) error {
 | 
			
		||||
	size := 0
 | 
			
		||||
@@ -304,13 +253,15 @@ func (b *Btelegram) handleDownload(rmsg *config.Message, message *tgbotapi.Messa
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if strings.HasSuffix(name, ".tgs.webp") {
 | 
			
		||||
		b.maybeConvertTgs(&name, data)
 | 
			
		||||
	} else if strings.HasSuffix(name, ".webp") {
 | 
			
		||||
		b.maybeConvertWebp(&name, data)
 | 
			
		||||
	if strings.HasSuffix(name, ".webp") && b.GetBool("MediaConvertWebPToPNG") {
 | 
			
		||||
		b.Log.Debugf("WebP to PNG conversion enabled, converting %s", name)
 | 
			
		||||
		err := helper.ConvertWebPToPNG(data)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			b.Log.Errorf("conversion failed: %s", err)
 | 
			
		||||
		} else {
 | 
			
		||||
			name = strings.Replace(name, ".webp", ".png", 1)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	helper.HandleDownloadData(b.Log, rmsg, name, message.Caption, "", data, b.General)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -360,9 +311,6 @@ func (b *Btelegram) handleEdit(msg *config.Message, chatid int64) (string, error
 | 
			
		||||
	case "Markdown":
 | 
			
		||||
		b.Log.Debug("Using mode markdown")
 | 
			
		||||
		m.ParseMode = tgbotapi.ModeMarkdown
 | 
			
		||||
	case MarkdownV2:
 | 
			
		||||
		b.Log.Debug("Using mode MarkdownV2")
 | 
			
		||||
		m.ParseMode = MarkdownV2
 | 
			
		||||
	}
 | 
			
		||||
	if strings.ToLower(b.GetString("MessageFormat")) == HTMLNick {
 | 
			
		||||
		b.Log.Debug("Using mode HTML - nick only")
 | 
			
		||||
@@ -408,14 +356,6 @@ func (b *Btelegram) handleQuote(message, quoteNick, quoteMessage string) string
 | 
			
		||||
	if format == "" {
 | 
			
		||||
		format = "{MESSAGE} (re @{QUOTENICK}: {QUOTEMESSAGE})"
 | 
			
		||||
	}
 | 
			
		||||
	quoteMessagelength := len(quoteMessage)
 | 
			
		||||
	if b.GetInt("QuoteLengthLimit") != 0 && quoteMessagelength >= b.GetInt("QuoteLengthLimit") {
 | 
			
		||||
		runes := []rune(quoteMessage)
 | 
			
		||||
		quoteMessage = string(runes[0:b.GetInt("QuoteLengthLimit")])
 | 
			
		||||
		if quoteMessagelength > b.GetInt("QuoteLengthLimit") {
 | 
			
		||||
			quoteMessage += "..."
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	format = strings.Replace(format, "{MESSAGE}", message, -1)
 | 
			
		||||
	format = strings.Replace(format, "{QUOTENICK}", quoteNick, -1)
 | 
			
		||||
	format = strings.Replace(format, "{QUOTEMESSAGE}", quoteMessage, -1)
 | 
			
		||||
@@ -435,13 +375,8 @@ func (b *Btelegram) handleEntities(rmsg *config.Message, message *tgbotapi.Messa
 | 
			
		||||
				b.Log.Errorf("entity text_link url parse failed: %s", err)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			utfEncodedString := utf16.Encode([]rune(rmsg.Text))
 | 
			
		||||
			if e.Offset+e.Length > len(utfEncodedString) {
 | 
			
		||||
				b.Log.Errorf("entity length is too long %d > %d", e.Offset+e.Length, len(utfEncodedString))
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			link := utf16.Decode(utfEncodedString[e.Offset : e.Offset+e.Length])
 | 
			
		||||
			rmsg.Text = strings.Replace(rmsg.Text, string(link), url.String(), 1)
 | 
			
		||||
			link := rmsg.Text[e.Offset : e.Offset+e.Length]
 | 
			
		||||
			rmsg.Text = strings.Replace(rmsg.Text, link, url.String(), 1)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,23 +2,19 @@ package btelegram
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"html"
 | 
			
		||||
	"log"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/helper"
 | 
			
		||||
	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
 | 
			
		||||
	"github.com/go-telegram-bot-api/telegram-bot-api"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	unknownUser = "unknown"
 | 
			
		||||
	HTMLFormat  = "HTML"
 | 
			
		||||
	HTMLNick    = "htmlnick"
 | 
			
		||||
	MarkdownV2  = "MarkdownV2"
 | 
			
		||||
	FormatPng   = "png"
 | 
			
		||||
	FormatWebp  = "webp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Btelegram struct {
 | 
			
		||||
@@ -28,16 +24,6 @@ type Btelegram struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func New(cfg *bridge.Config) bridge.Bridger {
 | 
			
		||||
	tgsConvertFormat := cfg.GetString("MediaConvertTgs")
 | 
			
		||||
	if tgsConvertFormat != "" {
 | 
			
		||||
		err := helper.CanConvertTgsToX()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Fatalf("Telegram bridge configured to convert .tgs files to '%s', but lottie does not appear to work:\n%#v", tgsConvertFormat, err)
 | 
			
		||||
		}
 | 
			
		||||
		if tgsConvertFormat != FormatPng && tgsConvertFormat != FormatWebp {
 | 
			
		||||
			log.Fatalf("Telegram bridge configured to convert .tgs files to '%s', but only '%s' and '%s' are supported.", FormatPng, FormatWebp, tgsConvertFormat)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return &Btelegram{Config: cfg, avatarMap: make(map[string]string)}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -95,8 +81,8 @@ func (b *Btelegram) Send(msg config.Message) (string, error) {
 | 
			
		||||
	// Upload a file if it exists
 | 
			
		||||
	if msg.Extra != nil {
 | 
			
		||||
		for _, rmsg := range helper.HandleExtra(&msg, b.General) {
 | 
			
		||||
			if _, msgErr := b.sendMessage(chatid, rmsg.Username, rmsg.Text); msgErr != nil {
 | 
			
		||||
				b.Log.Errorf("sendMessage failed: %s", msgErr)
 | 
			
		||||
			if _, err := b.sendMessage(chatid, rmsg.Username, rmsg.Text); err != nil {
 | 
			
		||||
				b.Log.Errorf("sendMessage failed: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		// check if we have files to upload (from slack, telegram or mattermost)
 | 
			
		||||
@@ -111,16 +97,9 @@ func (b *Btelegram) Send(msg config.Message) (string, error) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Post normal message
 | 
			
		||||
	// TODO: recheck it.
 | 
			
		||||
	// Ignore empty text field needs for prevent double messages from whatsapp to telegram
 | 
			
		||||
	// when sending media with text caption
 | 
			
		||||
	if msg.Text != "" {
 | 
			
		||||
	return b.sendMessage(chatid, msg.Username, msg.Text)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	return "", nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Btelegram) getFileDirectURL(id string) string {
 | 
			
		||||
	res, err := b.c.GetFileDirectURL(id)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -140,18 +119,11 @@ func (b *Btelegram) sendMessage(chatid int64, username, text string) (string, er
 | 
			
		||||
		b.Log.Debug("Using mode markdown")
 | 
			
		||||
		m.ParseMode = tgbotapi.ModeMarkdown
 | 
			
		||||
	}
 | 
			
		||||
	if b.GetString("MessageFormat") == MarkdownV2 {
 | 
			
		||||
		b.Log.Debug("Using mode MarkdownV2")
 | 
			
		||||
		m.ParseMode = MarkdownV2
 | 
			
		||||
	}
 | 
			
		||||
	if strings.ToLower(b.GetString("MessageFormat")) == HTMLNick {
 | 
			
		||||
		b.Log.Debug("Using mode HTML - nick only")
 | 
			
		||||
		m.Text = username + html.EscapeString(text)
 | 
			
		||||
		m.ParseMode = tgbotapi.ModeHTML
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m.DisableWebPagePreview = b.GetBool("DisableWebPagePreview")
 | 
			
		||||
 | 
			
		||||
	res, err := b.c.Send(m)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,14 @@
 | 
			
		||||
package bwhatsapp
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"mime"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/helper"
 | 
			
		||||
	"github.com/Rhymen/go-whatsapp"
 | 
			
		||||
	"github.com/jpillora/backoff"
 | 
			
		||||
 | 
			
		||||
	"github.com/matterbridge/go-whatsapp"
 | 
			
		||||
 | 
			
		||||
	whatsappExt "github.com/matterbridge/mautrix-whatsapp/whatsapp-ext"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
@@ -22,44 +21,7 @@ Check:
 | 
			
		||||
 | 
			
		||||
// HandleError received from WhatsApp
 | 
			
		||||
func (b *Bwhatsapp) HandleError(err error) {
 | 
			
		||||
	// ignore received invalid data errors. https://github.com/42wim/matterbridge/issues/843
 | 
			
		||||
	// ignore tag 174 errors. https://github.com/42wim/matterbridge/issues/1094
 | 
			
		||||
	if strings.Contains(err.Error(), "error processing data: received invalid data") || strings.Contains(err.Error(), "invalid string with tag 174") {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch err.(type) {
 | 
			
		||||
	case *whatsapp.ErrConnectionClosed, *whatsapp.ErrConnectionFailed:
 | 
			
		||||
		b.reconnect(err)
 | 
			
		||||
	default:
 | 
			
		||||
		switch err {
 | 
			
		||||
		case whatsapp.ErrConnectionTimeout:
 | 
			
		||||
			b.reconnect(err)
 | 
			
		||||
		default:
 | 
			
		||||
			b.Log.Errorf("%v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bwhatsapp) reconnect(err error) {
 | 
			
		||||
	bf := &backoff.Backoff{
 | 
			
		||||
		Min:    time.Second,
 | 
			
		||||
		Max:    5 * time.Minute,
 | 
			
		||||
		Jitter: true,
 | 
			
		||||
	}
 | 
			
		||||
	for {
 | 
			
		||||
		d := bf.Duration()
 | 
			
		||||
		b.Log.Errorf("Connection failed, underlying error: %v", err)
 | 
			
		||||
		b.Log.Infof("Waiting %s...", d)
 | 
			
		||||
		time.Sleep(d)
 | 
			
		||||
		b.Log.Info("Reconnecting...")
 | 
			
		||||
		err := b.conn.Restore()
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			bf.Reset()
 | 
			
		||||
			b.startedAt = uint64(time.Now().Unix())
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	b.Log.Errorf("%v", err) // TODO implement proper handling? at least respond to different error types
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HandleTextMessage sent from WhatsApp, relay it to the brige
 | 
			
		||||
@@ -73,18 +35,16 @@ func (b *Bwhatsapp) HandleTextMessage(message whatsapp.TextMessage) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	messageTime := time.Unix(int64(message.Info.Timestamp), 0) // TODO check how behaves between timezones
 | 
			
		||||
	groupJID := message.Info.RemoteJid
 | 
			
		||||
	groupJid := message.Info.RemoteJid
 | 
			
		||||
 | 
			
		||||
	senderJID := message.Info.SenderJid
 | 
			
		||||
	if len(senderJID) == 0 {
 | 
			
		||||
	senderJid := message.Info.SenderJid
 | 
			
		||||
	if len(senderJid) == 0 {
 | 
			
		||||
		// TODO workaround till https://github.com/Rhymen/go-whatsapp/issues/86 resolved
 | 
			
		||||
		if message.Info.Source != nil && message.Info.Source.Participant != nil {
 | 
			
		||||
			senderJID = *message.Info.Source.Participant
 | 
			
		||||
		}
 | 
			
		||||
		senderJid = *message.Info.Source.Participant
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// translate sender's JID to the nicest username we can get
 | 
			
		||||
	senderName := b.getSenderName(senderJID)
 | 
			
		||||
	// translate sender's Jid to the nicest username we can get
 | 
			
		||||
	senderName := b.getSenderName(senderJid)
 | 
			
		||||
	if senderName == "" {
 | 
			
		||||
		senderName = "Someone" // don't expose telephone number
 | 
			
		||||
	}
 | 
			
		||||
@@ -92,12 +52,12 @@ func (b *Bwhatsapp) HandleTextMessage(message whatsapp.TextMessage) {
 | 
			
		||||
	extText := message.Info.Source.Message.ExtendedTextMessage
 | 
			
		||||
	if extText != nil && extText.ContextInfo != nil && extText.ContextInfo.MentionedJid != nil {
 | 
			
		||||
		// handle user mentions
 | 
			
		||||
		for _, mentionedJID := range extText.ContextInfo.MentionedJid {
 | 
			
		||||
			numberAndSuffix := strings.SplitN(mentionedJID, "@", 2)
 | 
			
		||||
		for _, mentionedJid := range extText.ContextInfo.MentionedJid {
 | 
			
		||||
			numberAndSuffix := strings.SplitN(mentionedJid, "@", 2)
 | 
			
		||||
 | 
			
		||||
			// mentions comes as telephone numbers and we don't want to expose it to other bridges
 | 
			
		||||
			// replace it with something more meaninful to others
 | 
			
		||||
			mention := b.getSenderNotify(numberAndSuffix[0] + "@s.whatsapp.net")
 | 
			
		||||
			mention := b.getSenderNotify(numberAndSuffix[0] + whatsappExt.NewUserSuffix)
 | 
			
		||||
			if mention == "" {
 | 
			
		||||
				mention = "someone"
 | 
			
		||||
			}
 | 
			
		||||
@@ -105,13 +65,13 @@ func (b *Bwhatsapp) HandleTextMessage(message whatsapp.TextMessage) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account)
 | 
			
		||||
	b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJid, b.Account)
 | 
			
		||||
	rmsg := config.Message{
 | 
			
		||||
		UserID:    senderJID,
 | 
			
		||||
		UserID:    senderJid,
 | 
			
		||||
		Username:  senderName,
 | 
			
		||||
		Text:      message.Text,
 | 
			
		||||
		Timestamp: messageTime,
 | 
			
		||||
		Channel:   groupJID,
 | 
			
		||||
		Channel:   groupJid,
 | 
			
		||||
		Account:   b.Account,
 | 
			
		||||
		Protocol:  b.Protocol,
 | 
			
		||||
		Extra:     make(map[string][]interface{}),
 | 
			
		||||
@@ -120,7 +80,7 @@ func (b *Bwhatsapp) HandleTextMessage(message whatsapp.TextMessage) {
 | 
			
		||||
		//	Gateway   string  // will be added during message processing
 | 
			
		||||
		ID: message.Info.Id}
 | 
			
		||||
 | 
			
		||||
	if avatarURL, exists := b.userAvatars[senderJID]; exists {
 | 
			
		||||
	if avatarURL, exists := b.userAvatars[senderJid]; exists {
 | 
			
		||||
		rmsg.Avatar = avatarURL
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -128,77 +88,11 @@ func (b *Bwhatsapp) HandleTextMessage(message whatsapp.TextMessage) {
 | 
			
		||||
	b.Remote <- rmsg
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HandleImageMessage sent from WhatsApp, relay it to the brige
 | 
			
		||||
func (b *Bwhatsapp) HandleImageMessage(message whatsapp.ImageMessage) {
 | 
			
		||||
	if message.Info.FromMe { // || !strings.Contains(strings.ToLower(message.Text), "@echo") {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// whatsapp sends last messages to show context , cut them
 | 
			
		||||
	if message.Info.Timestamp < b.startedAt {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	messageTime := time.Unix(int64(message.Info.Timestamp), 0) // TODO check how behaves between timezones
 | 
			
		||||
	groupJID := message.Info.RemoteJid
 | 
			
		||||
 | 
			
		||||
	senderJID := message.Info.SenderJid
 | 
			
		||||
	if len(senderJID) == 0 {
 | 
			
		||||
		// TODO workaround till https://github.com/Rhymen/go-whatsapp/issues/86 resolved
 | 
			
		||||
		if message.Info.Source != nil && message.Info.Source.Participant != nil {
 | 
			
		||||
			senderJID = *message.Info.Source.Participant
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// translate sender's Jid to the nicest username we can get
 | 
			
		||||
	senderName := b.getSenderName(senderJID)
 | 
			
		||||
	if senderName == "" {
 | 
			
		||||
		senderName = "Someone" // don't expose telephone number
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account)
 | 
			
		||||
	rmsg := config.Message{
 | 
			
		||||
		UserID:    senderJID,
 | 
			
		||||
		Username:  senderName,
 | 
			
		||||
		Timestamp: messageTime,
 | 
			
		||||
		Channel:   groupJID,
 | 
			
		||||
		Account:   b.Account,
 | 
			
		||||
		Protocol:  b.Protocol,
 | 
			
		||||
		Extra:     make(map[string][]interface{}),
 | 
			
		||||
		//  ParentID: TODO,      // TODO handle thread replies  // map from Info.QuotedMessageID string
 | 
			
		||||
		//  Event     string    `json:"event"`
 | 
			
		||||
		//  Gateway   string     // will be added during message processing
 | 
			
		||||
		ID: message.Info.Id}
 | 
			
		||||
 | 
			
		||||
	if avatarURL, exists := b.userAvatars[senderJID]; exists {
 | 
			
		||||
		rmsg.Avatar = avatarURL
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Download and unencrypt content
 | 
			
		||||
	data, err := message.Download()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Log.Errorf("%v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Get file extension by mimetype
 | 
			
		||||
	fileExt, err := mime.ExtensionsByType(message.Type)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Log.Errorf("%v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	filename := fmt.Sprintf("%v%v", message.Info.Id, fileExt[0])
 | 
			
		||||
 | 
			
		||||
	b.Log.Debugf("<= Image downloaded and unencrypted")
 | 
			
		||||
 | 
			
		||||
	// Move file to bridge storage
 | 
			
		||||
	helper.HandleDownloadData(b.Log, &rmsg, filename, message.Caption, "", &data, b.General)
 | 
			
		||||
 | 
			
		||||
	b.Log.Debugf("<= Image Message is %#v", rmsg)
 | 
			
		||||
	b.Remote <- rmsg
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
//func (b *Bwhatsapp) HandleImageMessage(message whatsapp.ImageMessage) {
 | 
			
		||||
//	fmt.Println(message) // TODO implement
 | 
			
		||||
//}
 | 
			
		||||
//
 | 
			
		||||
//func (b *Bwhatsapp) HandleVideoMessage(message whatsapp.VideoMessage) {
 | 
			
		||||
//	fmt.Println(message) // TODO implement
 | 
			
		||||
//}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,22 +2,13 @@ package bwhatsapp
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/gob"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	qrcodeTerminal "github.com/Baozisoftware/qrcode-terminal-go"
 | 
			
		||||
	"github.com/Rhymen/go-whatsapp"
 | 
			
		||||
	"github.com/matterbridge/go-whatsapp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ProfilePicInfo struct {
 | 
			
		||||
	URL string `json:"eurl"`
 | 
			
		||||
	Tag string `json:"tag"`
 | 
			
		||||
 | 
			
		||||
	Status int16 `json:"status"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func qrFromTerminal(invert bool) chan string {
 | 
			
		||||
	qr := make(chan string)
 | 
			
		||||
	go func() {
 | 
			
		||||
@@ -80,33 +71,8 @@ func (b *Bwhatsapp) getSenderName(senderJid string) string {
 | 
			
		||||
		// if user is not in phone contacts
 | 
			
		||||
		// it is the most obvious scenario unless you sync your phone contacts with some remote updated source
 | 
			
		||||
		// users can change it in their WhatsApp settings -> profile -> click on Avatar
 | 
			
		||||
		if sender.Notify != "" {
 | 
			
		||||
		return sender.Notify
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		if sender.Short != "" {
 | 
			
		||||
			return sender.Short
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// try to reload this contact
 | 
			
		||||
	_, err := b.conn.Contacts()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Log.Errorf("error on update of contacts: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if contact, exists := b.conn.Store.Contacts[senderJid]; exists {
 | 
			
		||||
		// Add it to the user map
 | 
			
		||||
		b.users[senderJid] = contact
 | 
			
		||||
 | 
			
		||||
		if contact.Name != "" {
 | 
			
		||||
			return contact.Name
 | 
			
		||||
		}
 | 
			
		||||
		// if user is not in phone contacts
 | 
			
		||||
		// same as above
 | 
			
		||||
		return contact.Notify
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -116,17 +82,3 @@ func (b *Bwhatsapp) getSenderNotify(senderJid string) string {
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bwhatsapp) GetProfilePicThumb(jid string) (*ProfilePicInfo, error) {
 | 
			
		||||
	data, err := b.conn.GetProfilePicThumb(jid)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to get avatar: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	content := <-data
 | 
			
		||||
	info := &ProfilePicInfo{}
 | 
			
		||||
	err = json.Unmarshal([]byte(content), info)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return info, fmt.Errorf("failed to unmarshal avatar info: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	return info, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,20 @@
 | 
			
		||||
package bwhatsapp
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto/rand"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"mime"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/Rhymen/go-whatsapp"
 | 
			
		||||
 | 
			
		||||
	"github.com/matterbridge/go-whatsapp"
 | 
			
		||||
 | 
			
		||||
	whatsappExt "github.com/matterbridge/mautrix-whatsapp/whatsapp-ext"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
@@ -31,6 +31,8 @@ type Bwhatsapp struct {
 | 
			
		||||
	// https://github.com/Rhymen/go-whatsapp/blob/c31092027237441cffba1b9cb148eadf7c83c3d2/session.go#L18-L21
 | 
			
		||||
	session *whatsapp.Session
 | 
			
		||||
	conn    *whatsapp.Conn
 | 
			
		||||
	// https://github.com/tulir/mautrix-whatsapp/blob/master/whatsapp-ext/whatsapp.go
 | 
			
		||||
	connExt   *whatsappExt.ExtendedConn
 | 
			
		||||
	startedAt uint64
 | 
			
		||||
 | 
			
		||||
	users       map[string]whatsapp.Contact
 | 
			
		||||
@@ -72,6 +74,8 @@ func (b *Bwhatsapp) Connect() error {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b.conn = conn
 | 
			
		||||
	b.connExt = whatsappExt.ExtendConn(b.conn)
 | 
			
		||||
	// TODO do we want to use it? b.connExt.SetClientName("Matterbridge WhatsApp bridge", "mb-wa")
 | 
			
		||||
 | 
			
		||||
	b.conn.AddHandler(b)
 | 
			
		||||
	b.Log.Debugln("WhatsApp connection successful")
 | 
			
		||||
@@ -85,7 +89,7 @@ func (b *Bwhatsapp) Connect() error {
 | 
			
		||||
			b.Log.Debugln("Restoring WhatsApp session..")
 | 
			
		||||
 | 
			
		||||
			// https://github.com/Rhymen/go-whatsapp#restore
 | 
			
		||||
			session, err = b.conn.RestoreWithSession(session)
 | 
			
		||||
			session, err = b.conn.RestoreSession(session)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				// TODO return or continue to normal login?
 | 
			
		||||
				// restore session connection timed out (I couldn't get over it without logging in again)
 | 
			
		||||
@@ -126,7 +130,7 @@ func (b *Bwhatsapp) Connect() error {
 | 
			
		||||
		b.Log.Debug("Getting user avatars..")
 | 
			
		||||
 | 
			
		||||
		for jid := range b.users {
 | 
			
		||||
			info, err := b.GetProfilePicThumb(jid)
 | 
			
		||||
			info, err := b.connExt.GetProfilePicThumb(jid)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				b.Log.Warnf("Could not get profile photo of %s: %v", jid, err)
 | 
			
		||||
 | 
			
		||||
@@ -183,7 +187,7 @@ func (b *Bwhatsapp) Disconnect() error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isGroupJid(identifier string) bool {
 | 
			
		||||
	return strings.HasSuffix(identifier, "@g.us") || strings.HasSuffix(identifier, "@temp") || strings.HasSuffix(identifier, "@broadcast")
 | 
			
		||||
	return strings.HasSuffix(identifier, "@g.us") || strings.HasSuffix(identifier, "@temp")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// JoinChannel Join a WhatsApp group specified in gateway config as channel='number-id@g.us' or channel='Channel name'
 | 
			
		||||
@@ -233,66 +237,6 @@ func (b *Bwhatsapp) JoinChannel(channel config.ChannelInfo) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Post a document message from the bridge to WhatsApp
 | 
			
		||||
func (b *Bwhatsapp) PostDocumentMessage(msg config.Message, filetype string) (string, error) {
 | 
			
		||||
	fi := msg.Extra["file"][0].(config.FileInfo)
 | 
			
		||||
 | 
			
		||||
	// Post document message
 | 
			
		||||
	message := whatsapp.DocumentMessage{
 | 
			
		||||
		Info: whatsapp.MessageInfo{
 | 
			
		||||
			RemoteJid: msg.Channel,
 | 
			
		||||
		},
 | 
			
		||||
		Title:    fi.Name,
 | 
			
		||||
		FileName: fi.Name,
 | 
			
		||||
		Type:     filetype,
 | 
			
		||||
		Content:  bytes.NewReader(*fi.Data),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b.Log.Debugf("=> Sending %#v", msg)
 | 
			
		||||
 | 
			
		||||
	// create message ID
 | 
			
		||||
	// TODO follow and act if https://github.com/Rhymen/go-whatsapp/issues/101 implemented
 | 
			
		||||
	idBytes := make([]byte, 10)
 | 
			
		||||
	if _, err := rand.Read(idBytes); err != nil {
 | 
			
		||||
		b.Log.Warn(err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	message.Info.Id = strings.ToUpper(hex.EncodeToString(idBytes))
 | 
			
		||||
	_, err := b.conn.Send(message)
 | 
			
		||||
 | 
			
		||||
	return message.Info.Id, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Post an image message from the bridge to WhatsApp
 | 
			
		||||
// Handle, for sure image/jpeg, image/png and image/gif MIME types
 | 
			
		||||
func (b *Bwhatsapp) PostImageMessage(msg config.Message, filetype string) (string, error) {
 | 
			
		||||
	fi := msg.Extra["file"][0].(config.FileInfo)
 | 
			
		||||
 | 
			
		||||
	// Post image message
 | 
			
		||||
	message := whatsapp.ImageMessage{
 | 
			
		||||
		Info: whatsapp.MessageInfo{
 | 
			
		||||
			RemoteJid: msg.Channel,
 | 
			
		||||
		},
 | 
			
		||||
		Type:    filetype,
 | 
			
		||||
		Caption: msg.Username + fi.Comment,
 | 
			
		||||
		Content: bytes.NewReader(*fi.Data),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b.Log.Debugf("=> Sending %#v", msg)
 | 
			
		||||
 | 
			
		||||
	// create message ID
 | 
			
		||||
	// TODO follow and act if https://github.com/Rhymen/go-whatsapp/issues/101 implemented
 | 
			
		||||
	idBytes := make([]byte, 10)
 | 
			
		||||
	if _, err := rand.Read(idBytes); err != nil {
 | 
			
		||||
		b.Log.Warn(err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	message.Info.Id = strings.ToUpper(hex.EncodeToString(idBytes))
 | 
			
		||||
	_, err := b.conn.Send(message)
 | 
			
		||||
 | 
			
		||||
	return message.Info.Id, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Send a message from the bridge to WhatsApp
 | 
			
		||||
// Required implementation of the Bridger interface
 | 
			
		||||
// https://github.com/42wim/matterbridge/blob/2cfd880cdb0df29771bf8f31df8d990ab897889d/bridge/bridge.go#L11-L16
 | 
			
		||||
@@ -322,25 +266,18 @@ func (b *Bwhatsapp) Send(msg config.Message) (string, error) {
 | 
			
		||||
		// TODO handle edit as a message reply with updated text
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Handle Upload a file
 | 
			
		||||
	if msg.Extra["file"] != nil {
 | 
			
		||||
		fi := msg.Extra["file"][0].(config.FileInfo)
 | 
			
		||||
		filetype := mime.TypeByExtension(filepath.Ext(fi.Name))
 | 
			
		||||
 | 
			
		||||
		b.Log.Debugf("Extra file is %#v", filetype)
 | 
			
		||||
 | 
			
		||||
		// TODO: add different types
 | 
			
		||||
		// TODO: add webp conversion
 | 
			
		||||
		switch filetype {
 | 
			
		||||
		case "image/jpeg", "image/png", "image/gif":
 | 
			
		||||
			return b.PostImageMessage(msg, filetype)
 | 
			
		||||
		default:
 | 
			
		||||
			return b.PostDocumentMessage(msg, filetype)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	//// TODO Handle Upload a file
 | 
			
		||||
	//if msg.Extra != nil {
 | 
			
		||||
	//	for _, rmsg := range helper.HandleExtra(&msg, b.General) {
 | 
			
		||||
	//		b.c.SendMessage(roomID, rmsg.Username+rmsg.Text)
 | 
			
		||||
	//	}
 | 
			
		||||
	//	if len(msg.Extra["file"]) > 0 {
 | 
			
		||||
	//		return b.handleUploadFile(&msg, roomID)
 | 
			
		||||
	//	}
 | 
			
		||||
	//}
 | 
			
		||||
 | 
			
		||||
	// Post text message
 | 
			
		||||
	message := whatsapp.TextMessage{
 | 
			
		||||
	text := whatsapp.TextMessage{
 | 
			
		||||
		Info: whatsapp.MessageInfo{
 | 
			
		||||
			RemoteJid: msg.Channel, // which equals to group id
 | 
			
		||||
		},
 | 
			
		||||
@@ -351,14 +288,15 @@ func (b *Bwhatsapp) Send(msg config.Message) (string, error) {
 | 
			
		||||
 | 
			
		||||
	// create message ID
 | 
			
		||||
	// TODO follow and act if https://github.com/Rhymen/go-whatsapp/issues/101 implemented
 | 
			
		||||
	idBytes := make([]byte, 10)
 | 
			
		||||
	if _, err := rand.Read(idBytes); err != nil {
 | 
			
		||||
	bytes := make([]byte, 10)
 | 
			
		||||
	if _, err := rand.Read(bytes); err != nil {
 | 
			
		||||
		b.Log.Warn(err.Error())
 | 
			
		||||
	}
 | 
			
		||||
	message.Info.Id = strings.ToUpper(hex.EncodeToString(idBytes))
 | 
			
		||||
	_, err := b.conn.Send(message)
 | 
			
		||||
	text.Info.Id = strings.ToUpper(hex.EncodeToString(bytes))
 | 
			
		||||
 | 
			
		||||
	return message.Info.Id, err
 | 
			
		||||
	err := b.conn.Send(text)
 | 
			
		||||
 | 
			
		||||
	return text.Info.Id, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO do we want that? to allow login with QR code from a bridged channel? https://github.com/tulir/mautrix-whatsapp/blob/513eb18e2d59bada0dd515ee1abaaf38a3bfe3d5/commands.go#L76
 | 
			
		||||
 
 | 
			
		||||
@@ -1,34 +0,0 @@
 | 
			
		||||
package bxmpp
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/helper"
 | 
			
		||||
	"github.com/matterbridge/go-xmpp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// handleDownloadAvatar downloads the avatar of userid from channel
 | 
			
		||||
// sends a EVENT_AVATAR_DOWNLOAD message to the gateway if successful.
 | 
			
		||||
// logs an error message if it fails
 | 
			
		||||
func (b *Bxmpp) handleDownloadAvatar(avatar xmpp.AvatarData) {
 | 
			
		||||
	rmsg := config.Message{
 | 
			
		||||
		Username: "system",
 | 
			
		||||
		Text:     "avatar",
 | 
			
		||||
		Channel:  b.parseChannel(avatar.From),
 | 
			
		||||
		Account:  b.Account,
 | 
			
		||||
		UserID:   avatar.From,
 | 
			
		||||
		Event:    config.EventAvatarDownload,
 | 
			
		||||
		Extra:    make(map[string][]interface{}),
 | 
			
		||||
	}
 | 
			
		||||
	if _, ok := b.avatarMap[avatar.From]; !ok {
 | 
			
		||||
		b.Log.Debugf("Avatar.From: %s", avatar.From)
 | 
			
		||||
 | 
			
		||||
		err := helper.HandleDownloadSize(b.Log, &rmsg, avatar.From+".png", int64(len(avatar.Data)), b.General)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			b.Log.Error(err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		helper.HandleDownloadData(b.Log, &rmsg, avatar.From+".png", rmsg.Text, "", &avatar.Data, b.General)
 | 
			
		||||
		b.Log.Debugf("Avatar download complete")
 | 
			
		||||
		b.Remote <- rmsg
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,30 +0,0 @@
 | 
			
		||||
package bxmpp
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"regexp"
 | 
			
		||||
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var pathRegex = regexp.MustCompile("[^a-zA-Z0-9]+")
 | 
			
		||||
 | 
			
		||||
// GetAvatar constructs a URL for a given user-avatar if it is available in the cache.
 | 
			
		||||
func getAvatar(av map[string]string, userid string, general *config.Protocol) string {
 | 
			
		||||
	if hash, ok := av[userid]; ok {
 | 
			
		||||
		// NOTE: This does not happen in bridge/helper/helper.go but messes up XMPP
 | 
			
		||||
		id := pathRegex.ReplaceAllString(userid, "_")
 | 
			
		||||
		return general.MediaServerDownload + "/" + hash + "/" + id + ".png"
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bxmpp) cacheAvatar(msg *config.Message) string {
 | 
			
		||||
	fi := msg.Extra["file"][0].(config.FileInfo)
 | 
			
		||||
	/* if we have a sha we have successfully uploaded the file to the media server,
 | 
			
		||||
	so we can now cache the sha */
 | 
			
		||||
	if fi.SHA != "" {
 | 
			
		||||
		b.Log.Debugf("Added %s to %s in avatarMap", fi.SHA, msg.UserID)
 | 
			
		||||
		b.avatarMap[msg.UserID] = fi.SHA
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
@@ -2,9 +2,7 @@ package bxmpp
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge"
 | 
			
		||||
@@ -16,36 +14,50 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Bxmpp struct {
 | 
			
		||||
	*bridge.Config
 | 
			
		||||
 | 
			
		||||
	startTime time.Time
 | 
			
		||||
	xc      *xmpp.Client
 | 
			
		||||
	xmppMap map[string]string
 | 
			
		||||
	connected bool
 | 
			
		||||
	sync.RWMutex
 | 
			
		||||
 | 
			
		||||
	avatarAvailability map[string]bool
 | 
			
		||||
	avatarMap          map[string]string
 | 
			
		||||
	*bridge.Config
 | 
			
		||||
	startTime time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func New(cfg *bridge.Config) bridge.Bridger {
 | 
			
		||||
	return &Bxmpp{
 | 
			
		||||
		Config:             cfg,
 | 
			
		||||
		xmppMap:            make(map[string]string),
 | 
			
		||||
		avatarAvailability: make(map[string]bool),
 | 
			
		||||
		avatarMap:          make(map[string]string),
 | 
			
		||||
	}
 | 
			
		||||
	b := &Bxmpp{Config: cfg}
 | 
			
		||||
	b.xmppMap = make(map[string]string)
 | 
			
		||||
	return b
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bxmpp) Connect() error {
 | 
			
		||||
	var err error
 | 
			
		||||
	b.Log.Infof("Connecting %s", b.GetString("Server"))
 | 
			
		||||
	if err := b.createXMPP(); err != nil {
 | 
			
		||||
	b.xc, err = b.createXMPP()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Log.Debugf("%#v", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b.Log.Info("Connection succeeded")
 | 
			
		||||
	go b.manageConnection()
 | 
			
		||||
	go func() {
 | 
			
		||||
		initial := true
 | 
			
		||||
		bf := &backoff.Backoff{
 | 
			
		||||
			Min:    time.Second,
 | 
			
		||||
			Max:    5 * time.Minute,
 | 
			
		||||
			Jitter: true,
 | 
			
		||||
		}
 | 
			
		||||
		for {
 | 
			
		||||
			if initial {
 | 
			
		||||
				b.handleXMPP()
 | 
			
		||||
				initial = false
 | 
			
		||||
			}
 | 
			
		||||
			d := bf.Duration()
 | 
			
		||||
			b.Log.Infof("Disconnected. Reconnecting in %s", d)
 | 
			
		||||
			time.Sleep(d)
 | 
			
		||||
			b.xc, err = b.createXMPP()
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				b.Remote <- config.Message{Username: "system", Text: "rejoin", Channel: "", Account: b.Account, Event: config.EventRejoinChannels}
 | 
			
		||||
				b.handleXMPP()
 | 
			
		||||
				bf.Reset()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -64,75 +76,40 @@ func (b *Bxmpp) JoinChannel(channel config.ChannelInfo) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bxmpp) Send(msg config.Message) (string, error) {
 | 
			
		||||
	// should be fixed by using a cache instead of dropping
 | 
			
		||||
	if !b.Connected() {
 | 
			
		||||
		return "", fmt.Errorf("bridge %s not connected, dropping message %#v to bridge", b.Account, msg)
 | 
			
		||||
	}
 | 
			
		||||
	// ignore delete messages
 | 
			
		||||
	if msg.Event == config.EventMsgDelete {
 | 
			
		||||
		return "", nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b.Log.Debugf("=> Receiving %#v", msg)
 | 
			
		||||
 | 
			
		||||
	if msg.Event == config.EventAvatarDownload {
 | 
			
		||||
		return b.cacheAvatar(&msg), nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Make a action /me of the message, prepend the username with it.
 | 
			
		||||
	// https://xmpp.org/extensions/xep-0245.html
 | 
			
		||||
	if msg.Event == config.EventUserAction {
 | 
			
		||||
		msg.Username = "/me " + msg.Username
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Upload a file (in XMPP case send the upload URL because XMPP has no native upload support).
 | 
			
		||||
	// Upload a file (in xmpp case send the upload URL because xmpp has no native upload support)
 | 
			
		||||
	if msg.Extra != nil {
 | 
			
		||||
		for _, rmsg := range helper.HandleExtra(&msg, b.General) {
 | 
			
		||||
			b.Log.Debugf("=> Sending attachement message %#v", rmsg)
 | 
			
		||||
			if _, err := b.xc.Send(xmpp.Chat{
 | 
			
		||||
				Type:   "groupchat",
 | 
			
		||||
				Remote: rmsg.Channel + "@" + b.GetString("Muc"),
 | 
			
		||||
				Text:   rmsg.Username + rmsg.Text,
 | 
			
		||||
			}); err != nil {
 | 
			
		||||
				b.Log.WithError(err).Error("Unable to send message with share URL.")
 | 
			
		||||
			}
 | 
			
		||||
			b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: rmsg.Channel + "@" + b.GetString("Muc"), Text: rmsg.Username + rmsg.Text})
 | 
			
		||||
		}
 | 
			
		||||
		if len(msg.Extra["file"]) > 0 {
 | 
			
		||||
			return "", b.handleUploadFile(&msg)
 | 
			
		||||
			return b.handleUploadFile(&msg)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var msgReplaceID string
 | 
			
		||||
	msgID := xid.New().String()
 | 
			
		||||
	var msgreplaceid string
 | 
			
		||||
	msgid := xid.New().String()
 | 
			
		||||
	if msg.ID != "" {
 | 
			
		||||
		msgID = msg.ID
 | 
			
		||||
		msgReplaceID = msg.ID
 | 
			
		||||
		msgid = msg.ID
 | 
			
		||||
		msgreplaceid = msg.ID
 | 
			
		||||
	}
 | 
			
		||||
	// Post normal message.
 | 
			
		||||
	b.Log.Debugf("=> Sending message %#v", msg)
 | 
			
		||||
	if _, err := b.xc.Send(xmpp.Chat{
 | 
			
		||||
		Type:      "groupchat",
 | 
			
		||||
		Remote:    msg.Channel + "@" + b.GetString("Muc"),
 | 
			
		||||
		Text:      msg.Username + msg.Text,
 | 
			
		||||
		ID:        msgID,
 | 
			
		||||
		ReplaceID: msgReplaceID,
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
	// Post normal message
 | 
			
		||||
	_, err := b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.GetString("Muc"), Text: msg.Username + msg.Text, ID: msgid, ReplaceID: msgreplaceid})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	return msgID, nil
 | 
			
		||||
	return msgid, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bxmpp) createXMPP() error {
 | 
			
		||||
	if !strings.Contains(b.GetString("Jid"), "@") {
 | 
			
		||||
		return fmt.Errorf("the Jid %s doesn't contain an @", b.GetString("Jid"))
 | 
			
		||||
	}
 | 
			
		||||
	tc := &tls.Config{
 | 
			
		||||
		ServerName:         strings.Split(b.GetString("Jid"), "@")[1],
 | 
			
		||||
		InsecureSkipVerify: b.GetBool("SkipTLSVerify"), // nolint: gosec
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	xmpp.DebugWriter = b.Log.Writer()
 | 
			
		||||
 | 
			
		||||
func (b *Bxmpp) createXMPP() (*xmpp.Client, error) {
 | 
			
		||||
	tc := new(tls.Config)
 | 
			
		||||
	tc.InsecureSkipVerify = b.GetBool("SkipTLSVerify")
 | 
			
		||||
	tc.ServerName = strings.Split(b.GetString("Server"), ":")[0]
 | 
			
		||||
	options := xmpp.Options{
 | 
			
		||||
		Host:                         b.GetString("Server"),
 | 
			
		||||
		User:                         b.GetString("Jid"),
 | 
			
		||||
@@ -141,6 +118,7 @@ func (b *Bxmpp) createXMPP() error {
 | 
			
		||||
		StartTLS:                     true,
 | 
			
		||||
		TLSConfig:                    tc,
 | 
			
		||||
		Debug:                        b.GetBool("debug"),
 | 
			
		||||
		Logger:                       b.Log.Writer(),
 | 
			
		||||
		Session:                      true,
 | 
			
		||||
		Status:                       "",
 | 
			
		||||
		StatusMessage:                "",
 | 
			
		||||
@@ -149,54 +127,7 @@ func (b *Bxmpp) createXMPP() error {
 | 
			
		||||
	}
 | 
			
		||||
	var err error
 | 
			
		||||
	b.xc, err = options.NewClient()
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bxmpp) manageConnection() {
 | 
			
		||||
	b.setConnected(true)
 | 
			
		||||
	initial := true
 | 
			
		||||
	bf := &backoff.Backoff{
 | 
			
		||||
		Min:    time.Second,
 | 
			
		||||
		Max:    5 * time.Minute,
 | 
			
		||||
		Jitter: true,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Main connection loop. Each iteration corresponds to a successful
 | 
			
		||||
	// connection attempt and the subsequent handling of the connection.
 | 
			
		||||
	for {
 | 
			
		||||
		if initial {
 | 
			
		||||
			initial = false
 | 
			
		||||
		} else {
 | 
			
		||||
			b.Remote <- config.Message{
 | 
			
		||||
				Username: "system",
 | 
			
		||||
				Text:     "rejoin",
 | 
			
		||||
				Channel:  "",
 | 
			
		||||
				Account:  b.Account,
 | 
			
		||||
				Event:    config.EventRejoinChannels,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := b.handleXMPP(); err != nil {
 | 
			
		||||
			b.Log.WithError(err).Error("Disconnected.")
 | 
			
		||||
			b.setConnected(false)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Reconnection loop using an exponential back-off strategy. We
 | 
			
		||||
		// only break out of the loop if we have successfully reconnected.
 | 
			
		||||
		for {
 | 
			
		||||
			d := bf.Duration()
 | 
			
		||||
			b.Log.Infof("Reconnecting in %s.", d)
 | 
			
		||||
			time.Sleep(d)
 | 
			
		||||
 | 
			
		||||
			b.Log.Infof("Reconnecting now.")
 | 
			
		||||
			if err := b.createXMPP(); err == nil {
 | 
			
		||||
				b.setConnected(true)
 | 
			
		||||
				bf.Reset()
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			b.Log.Warn("Failed to reconnect.")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return b.xc, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bxmpp) xmppKeepAlive() chan bool {
 | 
			
		||||
@@ -208,7 +139,8 @@ func (b *Bxmpp) xmppKeepAlive() chan bool {
 | 
			
		||||
			select {
 | 
			
		||||
			case <-ticker.C:
 | 
			
		||||
				b.Log.Debugf("PING")
 | 
			
		||||
				if err := b.xc.PingC2S("", ""); err != nil {
 | 
			
		||||
				err := b.xc.PingC2S("", "")
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					b.Log.Debugf("PING failed %#v", err)
 | 
			
		||||
				}
 | 
			
		||||
			case <-done:
 | 
			
		||||
@@ -220,74 +152,53 @@ func (b *Bxmpp) xmppKeepAlive() chan bool {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bxmpp) handleXMPP() error {
 | 
			
		||||
	var ok bool
 | 
			
		||||
	var msgid string
 | 
			
		||||
	b.startTime = time.Now()
 | 
			
		||||
 | 
			
		||||
	done := b.xmppKeepAlive()
 | 
			
		||||
	defer close(done)
 | 
			
		||||
 | 
			
		||||
	for {
 | 
			
		||||
		m, err := b.xc.Recv()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch v := m.(type) {
 | 
			
		||||
		case xmpp.Chat:
 | 
			
		||||
			if v.Type == "groupchat" {
 | 
			
		||||
				b.Log.Debugf("== Receiving %#v", v)
 | 
			
		||||
 | 
			
		||||
				// Skip invalid messages.
 | 
			
		||||
				event := ""
 | 
			
		||||
				// skip invalid messages
 | 
			
		||||
				if b.skipMessage(v) {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				var event string
 | 
			
		||||
				if strings.Contains(v.Text, "has set the subject to:") {
 | 
			
		||||
					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 != "" {
 | 
			
		||||
					msgID = v.ReplaceID
 | 
			
		||||
					msgid = v.ReplaceID
 | 
			
		||||
				}
 | 
			
		||||
				rmsg := config.Message{
 | 
			
		||||
					Username: b.parseNick(v.Remote),
 | 
			
		||||
					Text:     v.Text,
 | 
			
		||||
					Channel:  b.parseChannel(v.Remote),
 | 
			
		||||
					Account:  b.Account,
 | 
			
		||||
					Avatar:   avatar,
 | 
			
		||||
					UserID:   v.Remote,
 | 
			
		||||
					ID:       msgID,
 | 
			
		||||
					ID:       msgid,
 | 
			
		||||
					Event:    event,
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// Check if we have an action event.
 | 
			
		||||
				var ok bool
 | 
			
		||||
				// check if we have an action event
 | 
			
		||||
				rmsg.Text, ok = b.replaceAction(rmsg.Text)
 | 
			
		||||
				if ok {
 | 
			
		||||
					rmsg.Event = config.EventUserAction
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account)
 | 
			
		||||
				b.Log.Debugf("<= Message is %#v", rmsg)
 | 
			
		||||
				b.Remote <- rmsg
 | 
			
		||||
			}
 | 
			
		||||
		case xmpp.AvatarData:
 | 
			
		||||
			b.handleDownloadAvatar(v)
 | 
			
		||||
			b.avatarAvailability[v.From] = true
 | 
			
		||||
			b.Log.Debugf("Avatar for %s is now available", v.From)
 | 
			
		||||
		case xmpp.Presence:
 | 
			
		||||
			// Do nothing.
 | 
			
		||||
			// do nothing
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -300,41 +211,30 @@ func (b *Bxmpp) replaceAction(text string) (string, bool) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// handleUploadFile handles native upload of files
 | 
			
		||||
func (b *Bxmpp) handleUploadFile(msg *config.Message) error {
 | 
			
		||||
	var urlDesc string
 | 
			
		||||
func (b *Bxmpp) handleUploadFile(msg *config.Message) (string, error) {
 | 
			
		||||
	var urldesc = ""
 | 
			
		||||
 | 
			
		||||
	for _, file := range msg.Extra["file"] {
 | 
			
		||||
		fileInfo := file.(config.FileInfo)
 | 
			
		||||
		if fileInfo.Comment != "" {
 | 
			
		||||
			msg.Text += fileInfo.Comment + ": "
 | 
			
		||||
	for _, f := range msg.Extra["file"] {
 | 
			
		||||
		fi := f.(config.FileInfo)
 | 
			
		||||
		if fi.Comment != "" {
 | 
			
		||||
			msg.Text += fi.Comment + ": "
 | 
			
		||||
		}
 | 
			
		||||
		if fileInfo.URL != "" {
 | 
			
		||||
			msg.Text = fileInfo.URL
 | 
			
		||||
			if fileInfo.Comment != "" {
 | 
			
		||||
				msg.Text = fileInfo.Comment + ": " + fileInfo.URL
 | 
			
		||||
				urlDesc = fileInfo.Comment
 | 
			
		||||
		if fi.URL != "" {
 | 
			
		||||
			msg.Text = fi.URL
 | 
			
		||||
			if fi.Comment != "" {
 | 
			
		||||
				msg.Text = fi.Comment + ": " + fi.URL
 | 
			
		||||
				urldesc = fi.Comment
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if _, err := b.xc.Send(xmpp.Chat{
 | 
			
		||||
			Type:   "groupchat",
 | 
			
		||||
			Remote: msg.Channel + "@" + b.GetString("Muc"),
 | 
			
		||||
			Text:   msg.Username + msg.Text,
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		_, err := b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.GetString("Muc"), Text: msg.Username + msg.Text})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if fileInfo.URL != "" {
 | 
			
		||||
			if _, err := b.xc.SendOOB(xmpp.Chat{
 | 
			
		||||
				Type:    "groupchat",
 | 
			
		||||
				Remote:  msg.Channel + "@" + b.GetString("Muc"),
 | 
			
		||||
				Ooburl:  fileInfo.URL,
 | 
			
		||||
				Oobdesc: urlDesc,
 | 
			
		||||
			}); err != nil {
 | 
			
		||||
				b.Log.WithError(err).Warn("Failed to send share URL.")
 | 
			
		||||
		if fi.URL != "" {
 | 
			
		||||
			b.xc.SendOOB(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.GetString("Muc"), Ooburl: fi.URL, Oobdesc: urldesc})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
	return "", nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bxmpp) parseNick(remote string) string {
 | 
			
		||||
@@ -379,17 +279,6 @@ func (b *Bxmpp) skipMessage(message xmpp.Chat) bool {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// skip delayed messages
 | 
			
		||||
	return !message.Stamp.IsZero() && time.Since(message.Stamp).Minutes() > 5
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bxmpp) setConnected(state bool) {
 | 
			
		||||
	b.Lock()
 | 
			
		||||
	b.connected = state
 | 
			
		||||
	defer b.Unlock()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bxmpp) Connected() bool {
 | 
			
		||||
	b.RLock()
 | 
			
		||||
	defer b.RUnlock()
 | 
			
		||||
	return b.connected
 | 
			
		||||
	t := time.Time{}
 | 
			
		||||
	return message.Stamp != t
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -135,25 +135,19 @@ func (b *Bzulip) handleQueue() error {
 | 
			
		||||
			if m.SenderEmail == b.GetString("login") {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			avatarURL := m.AvatarURL
 | 
			
		||||
			if !strings.HasPrefix(avatarURL, "http") {
 | 
			
		||||
				avatarURL = b.GetString("server") + avatarURL
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			rmsg := config.Message{
 | 
			
		||||
				Username: m.SenderFullName,
 | 
			
		||||
				Text:     m.Content,
 | 
			
		||||
				Channel:  b.getChannel(m.StreamID) + "/topic:" + m.Subject,
 | 
			
		||||
				Account:  b.Account,
 | 
			
		||||
				UserID:   strconv.Itoa(m.SenderID),
 | 
			
		||||
				Avatar:   avatarURL,
 | 
			
		||||
				Avatar:   m.AvatarURL,
 | 
			
		||||
			}
 | 
			
		||||
			b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account)
 | 
			
		||||
			b.Log.Debugf("<= Message is %#v", rmsg)
 | 
			
		||||
			b.Remote <- rmsg
 | 
			
		||||
			b.q.LastEventID = m.ID
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		time.Sleep(time.Second * 3)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										363
									
								
								changelog.md
									
									
									
									
									
								
							
							
						
						
									
										363
									
								
								changelog.md
									
									
									
									
									
								
							@@ -1,366 +1,3 @@
 | 
			
		||||
# v1.19.0
 | 
			
		||||
 | 
			
		||||
## New features
 | 
			
		||||
 | 
			
		||||
- mumble: new protocol added: Add Mumble support (#1245)
 | 
			
		||||
- nctalk: Add support for downloading files (nctalk) (#1249)
 | 
			
		||||
- nctalk: Append a suffix if user is a guest user (nctalk) (#1250)
 | 
			
		||||
 | 
			
		||||
## Enhancements
 | 
			
		||||
 | 
			
		||||
- irc: Add even more debug for irc (#1266)
 | 
			
		||||
- matrix: Add username formatting for all events (matrix) (#1233)
 | 
			
		||||
- matrix: Permit uploading files of other mimetypes (#1237)
 | 
			
		||||
- whatsapp: Use vendored whatsapp version (#1258)
 | 
			
		||||
- whatsapp: Add username for images from WhatsApp (#1232)
 | 
			
		||||
 | 
			
		||||
This release couldn't exist without the following contributors:
 | 
			
		||||
@Dellle, @42wim, @gary-kim, @s3lph, @BenWiederhake
 | 
			
		||||
 | 
			
		||||
# v1.18.3
 | 
			
		||||
 | 
			
		||||
## Enhancements
 | 
			
		||||
 | 
			
		||||
- nctalk: Add TLSConfig to nctalk (#1195)
 | 
			
		||||
- whatsapp: Handle broadcasts as groups in Whatsapp #1213
 | 
			
		||||
- matrix: switch to upstream gomatrix #1219
 | 
			
		||||
- api: support multiple websocket clients #1205
 | 
			
		||||
 | 
			
		||||
## Bugfix
 | 
			
		||||
 | 
			
		||||
- general: update vendor
 | 
			
		||||
- zulip: Check location of avatarURL (zulip). Fixes #1214 (#1227)
 | 
			
		||||
- nctalk: Fix issue with too many open files #1223
 | 
			
		||||
- nctalk: Fix mentions #1222
 | 
			
		||||
- nctalk: Fix message replays #1220
 | 
			
		||||
 | 
			
		||||
This release couldn't exist without the following contributors:
 | 
			
		||||
@gary-kim, @tilosp, @NikkyAI, @escoand, @42wim
 | 
			
		||||
 | 
			
		||||
# v1.18.2
 | 
			
		||||
 | 
			
		||||
## Bugfix
 | 
			
		||||
 | 
			
		||||
- zulip: Fix error loop (zulip) (#1210)
 | 
			
		||||
- whatsapp: Update whatsapp vendor and fix a panic (#1209)
 | 
			
		||||
 | 
			
		||||
This release couldn't exist without the following contributors:
 | 
			
		||||
@SuperSandro2000, @42wim
 | 
			
		||||
 | 
			
		||||
# v1.18.1
 | 
			
		||||
 | 
			
		||||
## New features
 | 
			
		||||
 | 
			
		||||
- telegram: Support Telegram animated stickers (tgs) format (#1173). See https://github.com/42wim/matterbridge/wiki/Settings#mediaConverttgs for more info
 | 
			
		||||
 | 
			
		||||
## Enhancements
 | 
			
		||||
 | 
			
		||||
- matrix: Remove HTML formatting for push messages (#1188) (#1189)
 | 
			
		||||
- mattermost: Use mattermost v5 module (#1192)
 | 
			
		||||
 | 
			
		||||
## Bugfix
 | 
			
		||||
 | 
			
		||||
- whatsapp: Handle panic in whatsapp. Fixes #1180 (#1184)
 | 
			
		||||
- nctalk: Fix Nextcloud Talk connection failure (#1179)
 | 
			
		||||
- matrix: Sleep when ratelimited on joins (matrix). Fixes #1201 (#1206)
 | 
			
		||||
 | 
			
		||||
This release couldn't exist without the following contributors:
 | 
			
		||||
@42wim, @BenWiederhake, @Dellle, @gary-kim
 | 
			
		||||
 | 
			
		||||
# v1.18.0
 | 
			
		||||
 | 
			
		||||
## New features
 | 
			
		||||
 | 
			
		||||
- nctalk: new protocol added. Add Nextcloud Talk support #1167
 | 
			
		||||
- general: Add an option to log into a file rather than stdout (#1168)
 | 
			
		||||
- api: Add websocket to API (#970)
 | 
			
		||||
 | 
			
		||||
## Enhancements
 | 
			
		||||
 | 
			
		||||
- telegram: Fix MarkdownV2 support in Telegram (#1169)
 | 
			
		||||
- whatsapp: Reload user information when a new contact is detected (whatsapp) (#1160)
 | 
			
		||||
- api: Add sane RemoteNickFormat default for API (#1157)
 | 
			
		||||
- irc: Skip gIRC built-in rate limiting (irc) (#1164)
 | 
			
		||||
- irc: Only colour IRC nicks if there is one. (#1161)
 | 
			
		||||
- docker: Combine runs to one layer (#1151)
 | 
			
		||||
 | 
			
		||||
## Bugfix
 | 
			
		||||
 | 
			
		||||
- general: Update dependencies for 1.18.0 release (#1175)
 | 
			
		||||
 | 
			
		||||
Discord users are encouraged to upgrade, this release works with the move to the discord.com domain.
 | 
			
		||||
 | 
			
		||||
This release couldn't exist without the following contributors:
 | 
			
		||||
@42wim, @jlu5, @qaisjp, @TheHolyRoger, @SuperSandro2000, @gary-kim, @z3bra, @greenx, @haykam821, @nathanaelhoun
 | 
			
		||||
 | 
			
		||||
# v1.17.5
 | 
			
		||||
 | 
			
		||||
## Enhancements
 | 
			
		||||
 | 
			
		||||
- irc: Add StripMarkdown option (irc). (#1145)
 | 
			
		||||
- general: Increase debug logging with function,file and linenumber (#1147)
 | 
			
		||||
- general: Update Dockerfile so inotify works (#1148)
 | 
			
		||||
- matrix: Add an option to disable sending HTML to matrix. Fixes #1022 (#1135)
 | 
			
		||||
- xmpp: Implement xep-0245 (xmpp). Closes #1137 (#1144)
 | 
			
		||||
 | 
			
		||||
## Bugfix
 | 
			
		||||
 | 
			
		||||
- discord: Fix #1120: replaceAction "_" crash (discord) (#1121)
 | 
			
		||||
- discord: Fix #1049: missing space before embeds (discord) (#1124)
 | 
			
		||||
- discord: Fix webhook EventUserAction messages being skipped (discord) (#1133)
 | 
			
		||||
- matrix: Avoid creating invalid url when the user doesn't have an avatar (matrix) (#1130)
 | 
			
		||||
- msteams: Ignore non-user messages (msteams). Fixes #1141 (#1149)
 | 
			
		||||
- slack: Do not use webhooks when token is configured (slack) (fixes #1123) (#1134)
 | 
			
		||||
- telegram: Fix forward from hidden users (telegram). Closes #1131 (#1143)
 | 
			
		||||
- xmpp: Prevent re-requesting avatar data (xmpp) (#1117)
 | 
			
		||||
 | 
			
		||||
This release couldn't exist without the following contributors:
 | 
			
		||||
@qaisjp, @xnaas, @42wim, @Polynomdivision, @tfve
 | 
			
		||||
 | 
			
		||||
# v1.17.4
 | 
			
		||||
 | 
			
		||||
## Bugfix
 | 
			
		||||
 | 
			
		||||
- general: Lowercase account names. Fixes #1108 (#1110)
 | 
			
		||||
- msteams: Remove panics and retry polling on failure (msteams). Fixes #1104 (#1105
 | 
			
		||||
- whatsapp: Update Rhymen/go-whatsapp. Fixes #1107 (#1109) (make whatsapp working again)
 | 
			
		||||
- discord: Add an ID cache (discord). Fixes #1106 (#1111) (fix delete/edits with webhooks)
 | 
			
		||||
 | 
			
		||||
# v1.17.3
 | 
			
		||||
 | 
			
		||||
## Enhancements
 | 
			
		||||
 | 
			
		||||
- xmpp: Implement User Avatar spoofing of XMPP users #1090
 | 
			
		||||
- rocketchat: Relay Joins/Topic changes in RocketChat bridge (#1085)
 | 
			
		||||
- irc: Add JoinDelay option (irc). Fixes #1084 (#1098)
 | 
			
		||||
- slack: Clip too long messages on 3000 length (slack). Fixes #1081 (#1102)
 | 
			
		||||
 | 
			
		||||
## Bugfix
 | 
			
		||||
 | 
			
		||||
- general: Fix the behavior of ShowTopicChange and SyncTopic (#1086)
 | 
			
		||||
- slack: Prevent image/message looping (slack). Fixes #1088 (#1096)
 | 
			
		||||
- whatsapp: Ignore non-critical errors (whatsapp). Fixes #1094 (#1100)
 | 
			
		||||
- irc: Add extra space before colon in attachments (irc). Fixes #1089 (#1101)
 | 
			
		||||
 | 
			
		||||
This release couldn't exist without the following contributors:
 | 
			
		||||
@42wim, @ldruschk, @qaisjp, @Polynomdivision
 | 
			
		||||
 | 
			
		||||
# v1.17.2
 | 
			
		||||
 | 
			
		||||
## Enhancements
 | 
			
		||||
 | 
			
		||||
- slack: Update vendor slack-go/slack (#1068)
 | 
			
		||||
- general: Update vendor d5/tengo (#1066)
 | 
			
		||||
- general: Clarify terminology used in mapping group chat IDs to channels in config (#1079)
 | 
			
		||||
 | 
			
		||||
## Bugfix
 | 
			
		||||
 | 
			
		||||
- whatsapp: Update Rhymen/go-whatsapp vendor and whatsapp version (#1078). Fixes Media upload #1074
 | 
			
		||||
- whatsapp: Reset start timestamp on reconnect (whatsapp). Fixes #1059 (#1064)
 | 
			
		||||
 | 
			
		||||
This release couldn't exist without the following contributors:
 | 
			
		||||
@42wim, @jheiselman
 | 
			
		||||
 | 
			
		||||
# v1.17.1
 | 
			
		||||
 | 
			
		||||
## Enhancements
 | 
			
		||||
 | 
			
		||||
- docker: Remove build dependencies from final image (multistage build) #1057
 | 
			
		||||
 | 
			
		||||
## Bugfix
 | 
			
		||||
 | 
			
		||||
- general: Don't transmit typing events from ourselves #1056
 | 
			
		||||
- general: Add support for build tags #1054
 | 
			
		||||
- discord: Strip extra info from emotes (discord) #1052
 | 
			
		||||
- msteams: fix macos build: Update vendor yaegashi/msgraph.go to v0.1.2 #1036
 | 
			
		||||
- whatsapp: Update client version whatsapp. Fixes #1061 #1062
 | 
			
		||||
 | 
			
		||||
This release couldn't exist without the following contributors:
 | 
			
		||||
@awigen, @qaisjp, @42wim
 | 
			
		||||
 | 
			
		||||
# v1.17.0
 | 
			
		||||
 | 
			
		||||
## New features
 | 
			
		||||
 | 
			
		||||
- msteams: new protocol added. Add initial Microsoft Teams support #967
 | 
			
		||||
  See https://github.com/42wim/matterbridge/wiki/MS-Teams-setup for a complete walkthrough
 | 
			
		||||
- discord: Add ability to procure avatars from the destination bridge #1000
 | 
			
		||||
- matrix: Add support for avatars from matrix. #1007
 | 
			
		||||
- general: support JSON and YAML config formats #1045
 | 
			
		||||
 | 
			
		||||
## Enhancements
 | 
			
		||||
 | 
			
		||||
- discord: Check only bridged channels for PermManageWebhooks #1001
 | 
			
		||||
- irc: Be less lossy when throttling IRC messages #1004
 | 
			
		||||
- keybase: updated library #1002, #1019
 | 
			
		||||
- matrix: Rebase gomatrix vendor with upstream #1006
 | 
			
		||||
- slack: Use upstream slack-go/slack again #1018
 | 
			
		||||
- slack: Ignore ConnectingEvent #1041
 | 
			
		||||
- slack: use blocks not attachments #1048
 | 
			
		||||
- sshchat: Update vendor shazow/ssh-chat #1029
 | 
			
		||||
- telegram: added markdownv2 mode for telegram #1037
 | 
			
		||||
- whatsapp: Implement basic reconnect (whatsapp). Fixes #987 #1003
 | 
			
		||||
 | 
			
		||||
## Bugfix
 | 
			
		||||
 | 
			
		||||
- discord: Fix webhook permission checks sometimes failing #1043
 | 
			
		||||
- discord: Fix #1027: warning when handling inbound webhooks #1044
 | 
			
		||||
- discord: Fix duplicate separator on empty description/url (discord) #1035
 | 
			
		||||
- matrix: Fix issue with underscores in links #999
 | 
			
		||||
- slack: Fix #1039: messages sent to Slack being synced back #1046
 | 
			
		||||
- telegram: Make avatars download work with mediaserverdownload (telegram). Fixes #920 
 | 
			
		||||
 | 
			
		||||
This release couldn't exist without the following contributors:
 | 
			
		||||
@qaisjp, @jakubgs, @burner1024, @notpushkin, @MartijnBraam, @42wim
 | 
			
		||||
 | 
			
		||||
# v1.16.5
 | 
			
		||||
 | 
			
		||||
- Fix version bump
 | 
			
		||||
 | 
			
		||||
# v1.16.4
 | 
			
		||||
 | 
			
		||||
## New features
 | 
			
		||||
 | 
			
		||||
- whatsapp: Add support for WhatsApp media (jpeg/png/gif) bridging (#974)
 | 
			
		||||
- telegram: Add QuoteLengthLimit option (telegram) fixes #963 (#985)
 | 
			
		||||
- telegram: Add DisableWebPagePreview option (telegram). Closes #980 (#994)
 | 
			
		||||
 | 
			
		||||
## Enhancements
 | 
			
		||||
 | 
			
		||||
- general: update dependencies
 | 
			
		||||
- tengo: update to tengo v2
 | 
			
		||||
- general: Add Docker Compose configuration (#990)
 | 
			
		||||
 | 
			
		||||
## Bugfix
 | 
			
		||||
 | 
			
		||||
- general: Fail with message instead of panic. #988 (#991)
 | 
			
		||||
- telegram: Add extra mimetypes to docker image. Fixes #969
 | 
			
		||||
- discord: Fix channel ID problem with multiple gateways (discord). Fixes #953 (#977)
 | 
			
		||||
- discord: Show file comment in webhook if normal message is empty (discord). Fixes #962 (#995)
 | 
			
		||||
- matrix: Fix parsing issues - Disable smartypants in markdown parser. Fixes #989, #983 (#993)
 | 
			
		||||
- sshchat: Fix duplicated messages (sshchat). Fixes #950 (#996)
 | 
			
		||||
 | 
			
		||||
This release couldn't exist without the following contributors:
 | 
			
		||||
@jwflory, @42wim, @pbek, @Humorhenker, @c0ncord2, @glazzara
 | 
			
		||||
 | 
			
		||||
# v1.16.3
 | 
			
		||||
 | 
			
		||||
## Bugfix
 | 
			
		||||
 | 
			
		||||
- slack: Fix issues with ratelimiting #959
 | 
			
		||||
- mattermost: Fix bug when using webhookURL and login/token together #960
 | 
			
		||||
 | 
			
		||||
# v1.16.2
 | 
			
		||||
 | 
			
		||||
## New features
 | 
			
		||||
 | 
			
		||||
- keybase: Add support for receiving attachments (keybase) (#923)
 | 
			
		||||
 | 
			
		||||
## Enhancements
 | 
			
		||||
 | 
			
		||||
- general: Switch to new emoji library kyokomi/emoji (#948)
 | 
			
		||||
- general: Update markdown parsing library to github.com/gomarkdown/markdown (#944)
 | 
			
		||||
- ssh-chat: Update shazow/ssh-chat dependency (#947)
 | 
			
		||||
 | 
			
		||||
## Bugfix
 | 
			
		||||
 | 
			
		||||
- slack: Fix issues with the slack block kit API #937 (#943).
 | 
			
		||||
 | 
			
		||||
This release couldn't exist without the following contributors:
 | 
			
		||||
@42wim, @bmpickford, @goncalor
 | 
			
		||||
 | 
			
		||||
# v1.16.1
 | 
			
		||||
 | 
			
		||||
## New features
 | 
			
		||||
 | 
			
		||||
* rocketchat: add token support #892
 | 
			
		||||
* matrix: Add support for uploading application/x and audio/x (matrix). #929
 | 
			
		||||
 | 
			
		||||
## Enhancements
 | 
			
		||||
 | 
			
		||||
* general: Do configuration validation on start-up. Fixes #888
 | 
			
		||||
* general: updated vendored libraries (discord/whatsapp) #932
 | 
			
		||||
* discord: user typing messages #914
 | 
			
		||||
* slack: Convert slack bold/strike to correct markdown (slack). Fixes #918
 | 
			
		||||
 | 
			
		||||
## Bugfix
 | 
			
		||||
 | 
			
		||||
* discord: fix Failed to fetch information for members message. #894
 | 
			
		||||
* discord: remove obsolete file upload links (discord). #931
 | 
			
		||||
* slack: suppress unhandled HelloEvent message #913
 | 
			
		||||
* mattermost: Fix panic on WebhookURL only setting (mattermost). #917
 | 
			
		||||
* matrix: fix corrupted links between slack and matrix #924
 | 
			
		||||
 | 
			
		||||
This release couldn't exist without the following contributors:
 | 
			
		||||
@qaisjp, @hramrach, @42wim
 | 
			
		||||
 | 
			
		||||
# v1.16.0
 | 
			
		||||
 | 
			
		||||
## New features
 | 
			
		||||
 | 
			
		||||
* keybase: new protocol added. Add initial Keybase Chat support #877 Thanks to @hyperobject
 | 
			
		||||
* discord: Support webhook files in discord #872
 | 
			
		||||
 | 
			
		||||
## Enhancements
 | 
			
		||||
 | 
			
		||||
* general: update dependencies
 | 
			
		||||
 | 
			
		||||
## Bugfix
 | 
			
		||||
 | 
			
		||||
* discord: Underscores from Discord don't arrive correctly #864
 | 
			
		||||
* xmpp: Fix possible panic at startup of the XMPP bridge #869
 | 
			
		||||
* mattermost: Make getChannelIdTeam behave like GetChannelId for groups (mattermost) #873
 | 
			
		||||
 | 
			
		||||
This release couldn't exist without the following contributors:
 | 
			
		||||
@hyperobject, @42wim, @bucko909, @MOZGIII
 | 
			
		||||
 | 
			
		||||
# v1.15.1
 | 
			
		||||
 | 
			
		||||
## New features
 | 
			
		||||
* discord: Support webhook message deletions (discord) (#853)
 | 
			
		||||
 | 
			
		||||
## Enhancements
 | 
			
		||||
 | 
			
		||||
* discord: Support bulk deletions #851
 | 
			
		||||
* discord: Support channels in categories #863 (use category/channel. See matterbridge.toml.sample for more info)
 | 
			
		||||
* mattermost: Add an option to skip the Mattermost server version check #849
 | 
			
		||||
 | 
			
		||||
## Bugfix
 | 
			
		||||
 | 
			
		||||
* xmpp: fix segfault when disconnected/reconnected #856
 | 
			
		||||
* telegram: fix panic in handleEntities #858
 | 
			
		||||
 | 
			
		||||
This release couldn't exist without the following contributors:
 | 
			
		||||
@42wim, @qaisjp, @joohoi
 | 
			
		||||
 | 
			
		||||
# v1.15.0
 | 
			
		||||
## New features
 | 
			
		||||
* Add scripting (tengo) support for every outgoing message (#806)
 | 
			
		||||
  See https://github.com/42wim/matterbridge/wiki/Settings#tengo and 
 | 
			
		||||
  https://github.com/42wim/matterbridge/wiki/Settings#outmessage for more information
 | 
			
		||||
* Add tengo support to RemoteNickFormat (#793)
 | 
			
		||||
  See https://github.com/42wim/matterbridge/wiki/Settings#remotenickformat-2
 | 
			
		||||
  * Deprecated `Message` under `[tengo]` to `InMessage`
 | 
			
		||||
 | 
			
		||||
## Enhancements
 | 
			
		||||
  * general: Forward only user-typing messages if supported by protocol (#832)
 | 
			
		||||
  * general: updated wiki with all possible settings: https://github.com/42wim/matterbridge/wiki/Settings
 | 
			
		||||
  * tengo: Add msg event to tengo
 | 
			
		||||
  * xmpp: Verify TLS against JID domain, not the host. (xmpp) (#834)
 | 
			
		||||
  * xmpp: Allow messages with timestamp (xmpp). Fixes #835 (#847)
 | 
			
		||||
  * irc: Add verbose IRC joins/parts (ident@host) (#805)
 | 
			
		||||
  See https://github.com/42wim/matterbridge/wiki/Settings#verbosejoinpart
 | 
			
		||||
  * rocketchat: Add useraction support (rocketchat). Closes #772 (#794)
 | 
			
		||||
 | 
			
		||||
## Bugfix
 | 
			
		||||
  * slack: Fix regression in autojoining with legacy tokens (slack). Fixes #651 (#848)
 | 
			
		||||
  * xmpp: Revert xmpp to orig behaviour. Closes #844
 | 
			
		||||
* whatsapp: Update github.com/Rhymen/go-whatsapp vendor. Fixes #843
 | 
			
		||||
* mattermost: Update channels of all teams (mattermost)
 | 
			
		||||
 | 
			
		||||
This release couldn't exist without the following contributors:
 | 
			
		||||
@42wim, @Helcaraxan, @chotaire, @qaisjp, @dajohi, @kousu
 | 
			
		||||
 | 
			
		||||
# v1.14.4
 | 
			
		||||
 | 
			
		||||
## Bugfix
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										30
									
								
								ci/bintray.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										30
									
								
								ci/bintray.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
set -u -e -x -o pipefail
 | 
			
		||||
 | 
			
		||||
go version | grep go1.12 || exit
 | 
			
		||||
 | 
			
		||||
VERSION=$(git describe --tags)
 | 
			
		||||
mkdir ci/binaries
 | 
			
		||||
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-windows-amd64.exe
 | 
			
		||||
GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-linux-amd64
 | 
			
		||||
GOOS=linux GOARCH=arm go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-linux-arm
 | 
			
		||||
GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-darwin-amd64
 | 
			
		||||
cd ci
 | 
			
		||||
cat > deploy.json <<EOF
 | 
			
		||||
{
 | 
			
		||||
    "package": {
 | 
			
		||||
        "name": "Matterbridge",
 | 
			
		||||
        "repo": "nightly",
 | 
			
		||||
        "subject": "42wim"
 | 
			
		||||
    },
 | 
			
		||||
    "version": {
 | 
			
		||||
        "name": "$VERSION"
 | 
			
		||||
    },
 | 
			
		||||
    "files":
 | 
			
		||||
        [
 | 
			
		||||
        {"includePattern": "ci/binaries/(.*)", "uploadPattern":"\$1"}
 | 
			
		||||
        ],
 | 
			
		||||
    "publish": true
 | 
			
		||||
}
 | 
			
		||||
EOF
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										17
									
								
								ci/lint.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										17
									
								
								ci/lint.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
set -u -e -x -o pipefail
 | 
			
		||||
 | 
			
		||||
if [[ -n "${GOLANGCI_VERSION-}" ]]; then
 | 
			
		||||
  # Retrieve the golangci-lint linter binary.
 | 
			
		||||
  curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b ${GOPATH}/bin ${GOLANGCI_VERSION}
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Run the linter.
 | 
			
		||||
golangci-lint run
 | 
			
		||||
 | 
			
		||||
if [[ "${GO111MODULE-off}" == "on" ]]; then
 | 
			
		||||
  # If Go modules are active then check that dependencies are correctly maintained.
 | 
			
		||||
  go mod tidy
 | 
			
		||||
  go mod vendor
 | 
			
		||||
  git diff --exit-code --quiet || (echo "Please run 'go mod tidy' to clean up the 'go.mod' and 'go.sum' files."; false)
 | 
			
		||||
fi
 | 
			
		||||
							
								
								
									
										17
									
								
								ci/test.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										17
									
								
								ci/test.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
set -u -e -x -o pipefail
 | 
			
		||||
 | 
			
		||||
if [[ -n "${REPORT_COVERAGE+cover}" ]]; then
 | 
			
		||||
  # Retrieve and prepare CodeClimate's test coverage reporter.
 | 
			
		||||
  curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
 | 
			
		||||
  chmod +x ./cc-test-reporter
 | 
			
		||||
  ./cc-test-reporter before-build
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Run all the tests with the race detector and generate coverage.
 | 
			
		||||
go test -v -race -coverprofile c.out ./...
 | 
			
		||||
 | 
			
		||||
if [[ -n "${REPORT_COVERAGE+cover}" && "${TRAVIS_SECURE_ENV_VARS}" == "true" ]]; then
 | 
			
		||||
  # Upload test coverage to CodeClimate.
 | 
			
		||||
  ./cc-test-reporter after-build
 | 
			
		||||
fi
 | 
			
		||||
@@ -1,10 +0,0 @@
 | 
			
		||||
text := import("text")
 | 
			
		||||
 | 
			
		||||
// if we're not sending to a discord bridge,
 | 
			
		||||
// then convert custom emoji tags into url's
 | 
			
		||||
if (inProtocol == "discord" && outProtocol != "discord") {
 | 
			
		||||
    rePNG := text.re_compile(`<:.*?:([0-9]+)>`)
 | 
			
		||||
    msgText=rePNG.replace(msgText,"https://cdn.discordapp.com/emojis/$1.png")
 | 
			
		||||
    reGIF := text.re_compile(`<a:.*?:([0-9]+)>`)
 | 
			
		||||
    msgText=reGIF.replace(msgText,"https://cdn.discordapp.com/emojis/$1.gif")
 | 
			
		||||
}
 | 
			
		||||
@@ -1,14 +0,0 @@
 | 
			
		||||
// See https://github.com/42wim/matterbridge/issues/881
 | 
			
		||||
// Generates a colored nick for each msgUsername, with example to filter specific codes 
 | 
			
		||||
 | 
			
		||||
text := import("text")
 | 
			
		||||
fmt := import("fmt")
 | 
			
		||||
if outProtocol == "irc" {
 | 
			
		||||
    // generate a color for a nick, make sure it isn't 0 or 15
 | 
			
		||||
    colorCode := len(msgUsername)+bytes(msgUsername)[0]%14 + 2
 | 
			
		||||
    // example if we want to use colorCode 3 when we have calculated colorcode 14
 | 
			
		||||
    if colorCode == 14 {
 | 
			
		||||
        colorCode = 3
 | 
			
		||||
    }
 | 
			
		||||
    msgUsername=fmt.sprintf("\x03%02d%s\x0F", colorCode, msgUsername)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,7 +0,0 @@
 | 
			
		||||
// See https://github.com/42wim/matterbridge/issues/798
 | 
			
		||||
 | 
			
		||||
// if we're not sending to an irc bridge we strip the IRC colors
 | 
			
		||||
if outProtocol != "irc" {
 | 
			
		||||
    re := text.re_compile(`\x03(?:\d{1,2}(?:,\d{1,2})?)?|[[:cntrl:]]`)
 | 
			
		||||
    msgText=re.replace(msgText,"")
 | 
			
		||||
}
 | 
			
		||||
@@ -1,16 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
This script will return the nick except with multi-character usernames
 | 
			
		||||
containing a zero-width space between the first and second character letter.
 | 
			
		||||
 | 
			
		||||
Single character usernames will be left untouched.
 | 
			
		||||
 | 
			
		||||
This is useful to prevent remote users from nickalerting
 | 
			
		||||
IRC users of the same name when the remote user speaks.
 | 
			
		||||
 | 
			
		||||
This result can be used in {TENGO} in RemoteNickFormat.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
result = nick
 | 
			
		||||
if len(nick) > 1 {
 | 
			
		||||
    result = string(nick[0]) + "" + nick[1:]
 | 
			
		||||
}
 | 
			
		||||
@@ -1,9 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
This script will return the current time in kitchen format if the protocol (of the remote bridge) isn't irc
 | 
			
		||||
See https://github.com/d5/tengo/blob/master/docs/stdlib-times.md
 | 
			
		||||
This result can be used in {TENGO} in RemoteNickFormat
 | 
			
		||||
*/
 | 
			
		||||
times := import("times")
 | 
			
		||||
if protocol != "irc" {
 | 
			
		||||
   result=times.time_format(times.now(),times.format_kitchen)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
// +build !noapi
 | 
			
		||||
 | 
			
		||||
package bridgemap
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/api"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	FullMap["api"] = api.New
 | 
			
		||||
}
 | 
			
		||||
@@ -1,12 +0,0 @@
 | 
			
		||||
// +build !nodiscord
 | 
			
		||||
 | 
			
		||||
package bridgemap
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	bdiscord "github.com/42wim/matterbridge/bridge/discord"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	FullMap["discord"] = bdiscord.New
 | 
			
		||||
	UserTypingSupport["discord"] = struct{}{}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
// +build !nogitter
 | 
			
		||||
 | 
			
		||||
package bridgemap
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	bgitter "github.com/42wim/matterbridge/bridge/gitter"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	FullMap["gitter"] = bgitter.New
 | 
			
		||||
}
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
// +build !noirc
 | 
			
		||||
 | 
			
		||||
package bridgemap
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	birc "github.com/42wim/matterbridge/bridge/irc"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	FullMap["irc"] = birc.New
 | 
			
		||||
}
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
// +build !nokeybase
 | 
			
		||||
 | 
			
		||||
package bridgemap
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	bkeybase "github.com/42wim/matterbridge/bridge/keybase"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	FullMap["keybase"] = bkeybase.New
 | 
			
		||||
}
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
// +build !nomatrix
 | 
			
		||||
 | 
			
		||||
package bridgemap
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	bmatrix "github.com/42wim/matterbridge/bridge/matrix"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	FullMap["matrix"] = bmatrix.New
 | 
			
		||||
}
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
// +build !nomattermost
 | 
			
		||||
 | 
			
		||||
package bridgemap
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	bmattermost "github.com/42wim/matterbridge/bridge/mattermost"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	FullMap["mattermost"] = bmattermost.New
 | 
			
		||||
}
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
// +build !nomsteams
 | 
			
		||||
 | 
			
		||||
package bridgemap
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	bmsteams "github.com/42wim/matterbridge/bridge/msteams"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	FullMap["msteams"] = bmsteams.New
 | 
			
		||||
}
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
// +build !nomumble
 | 
			
		||||
 | 
			
		||||
package bridgemap
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	bmumble "github.com/42wim/matterbridge/bridge/mumble"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	FullMap["mumble"] = bmumble.New
 | 
			
		||||
}
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
// +build !nonctalk
 | 
			
		||||
 | 
			
		||||
package bridgemap
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	btalk "github.com/42wim/matterbridge/bridge/nctalk"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	FullMap["nctalk"] = btalk.New
 | 
			
		||||
}
 | 
			
		||||
@@ -2,9 +2,36 @@ package bridgemap
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/api"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/discord"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/gitter"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/irc"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/matrix"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/mattermost"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/rocketchat"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/slack"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/sshchat"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/steam"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/telegram"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/whatsapp"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/xmpp"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/zulip"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	FullMap           = map[string]bridge.Factory{}
 | 
			
		||||
	UserTypingSupport = map[string]struct{}{}
 | 
			
		||||
)
 | 
			
		||||
var FullMap = map[string]bridge.Factory{
 | 
			
		||||
	"api":          api.New,
 | 
			
		||||
	"discord":      bdiscord.New,
 | 
			
		||||
	"gitter":       bgitter.New,
 | 
			
		||||
	"irc":          birc.New,
 | 
			
		||||
	"mattermost":   bmattermost.New,
 | 
			
		||||
	"matrix":       bmatrix.New,
 | 
			
		||||
	"rocketchat":   brocketchat.New,
 | 
			
		||||
	"slack-legacy": bslack.NewLegacy,
 | 
			
		||||
	"slack":        bslack.New,
 | 
			
		||||
	"sshchat":      bsshchat.New,
 | 
			
		||||
	"steam":        bsteam.New,
 | 
			
		||||
	"telegram":     btelegram.New,
 | 
			
		||||
	"whatsapp":     bwhatsapp.New,
 | 
			
		||||
	"xmpp":         bxmpp.New,
 | 
			
		||||
	"zulip":        bzulip.New,
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
// +build !norocketchat
 | 
			
		||||
 | 
			
		||||
package bridgemap
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	brocketchat "github.com/42wim/matterbridge/bridge/rocketchat"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	FullMap["rocketchat"] = brocketchat.New
 | 
			
		||||
}
 | 
			
		||||
@@ -1,13 +0,0 @@
 | 
			
		||||
// +build !noslack
 | 
			
		||||
 | 
			
		||||
package bridgemap
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	bslack "github.com/42wim/matterbridge/bridge/slack"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	FullMap["slack-legacy"] = bslack.NewLegacy
 | 
			
		||||
	FullMap["slack"] = bslack.New
 | 
			
		||||
	UserTypingSupport["slack"] = struct{}{}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
// +build !nosshchat
 | 
			
		||||
 | 
			
		||||
package bridgemap
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	bsshchat "github.com/42wim/matterbridge/bridge/sshchat"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	FullMap["sshchat"] = bsshchat.New
 | 
			
		||||
}
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
// +build !nosteam
 | 
			
		||||
 | 
			
		||||
package bridgemap
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	bsteam "github.com/42wim/matterbridge/bridge/steam"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	FullMap["steam"] = bsteam.New
 | 
			
		||||
}
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
// +build !notelegram
 | 
			
		||||
 | 
			
		||||
package bridgemap
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	btelegram "github.com/42wim/matterbridge/bridge/telegram"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	FullMap["telegram"] = btelegram.New
 | 
			
		||||
}
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
// +build !nowhatsapp
 | 
			
		||||
 | 
			
		||||
package bridgemap
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	bwhatsapp "github.com/42wim/matterbridge/bridge/whatsapp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	FullMap["whatsapp"] = bwhatsapp.New
 | 
			
		||||
}
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
// +build !noxmpp
 | 
			
		||||
 | 
			
		||||
package bridgemap
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	bxmpp "github.com/42wim/matterbridge/bridge/xmpp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	FullMap["xmpp"] = bxmpp.New
 | 
			
		||||
}
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
// +build !nozulip
 | 
			
		||||
 | 
			
		||||
package bridgemap
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	bzulip "github.com/42wim/matterbridge/bridge/zulip"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	FullMap["zulip"] = bzulip.New
 | 
			
		||||
}
 | 
			
		||||
@@ -9,11 +9,10 @@ import (
 | 
			
		||||
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/42wim/matterbridge/internal"
 | 
			
		||||
	"github.com/d5/tengo/v2"
 | 
			
		||||
	"github.com/d5/tengo/v2/stdlib"
 | 
			
		||||
	"github.com/d5/tengo/script"
 | 
			
		||||
	"github.com/d5/tengo/stdlib"
 | 
			
		||||
	lru "github.com/hashicorp/golang-lru"
 | 
			
		||||
	"github.com/matterbridge/emoji"
 | 
			
		||||
	"github.com/peterhellberg/emojilib"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -85,7 +84,6 @@ func (gw *Gateway) FindCanonicalMsgID(protocol string, mID string) string {
 | 
			
		||||
func (gw *Gateway) AddBridge(cfg *config.Bridge) error {
 | 
			
		||||
	br := gw.Router.getBridge(cfg.Account)
 | 
			
		||||
	if br == nil {
 | 
			
		||||
		gw.checkConfig(cfg)
 | 
			
		||||
		br = bridge.New(cfg)
 | 
			
		||||
		br.Config = gw.Router.Config
 | 
			
		||||
		br.General = &gw.BridgeValues().General
 | 
			
		||||
@@ -105,19 +103,6 @@ func (gw *Gateway) AddBridge(cfg *config.Bridge) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (gw *Gateway) checkConfig(cfg *config.Bridge) {
 | 
			
		||||
	match := false
 | 
			
		||||
	for _, key := range gw.Router.Config.Viper().AllKeys() {
 | 
			
		||||
		if strings.HasPrefix(key, strings.ToLower(cfg.Account)) {
 | 
			
		||||
			match = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !match {
 | 
			
		||||
		gw.logger.Fatalf("Account %s defined in gateway %s but no configuration found, exiting.", cfg.Account, gw.Name)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AddConfig associates a new configuration with the gateway object.
 | 
			
		||||
func (gw *Gateway) AddConfig(cfg *config.Gateway) error {
 | 
			
		||||
	gw.Name = cfg.Name
 | 
			
		||||
@@ -306,6 +291,8 @@ func (gw *Gateway) ignoreMessage(msg *config.Message) bool {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (gw *Gateway) modifyUsername(msg *config.Message, dest *bridge.Bridge) string {
 | 
			
		||||
	br := gw.Bridges[msg.Account]
 | 
			
		||||
	msg.Protocol = br.Protocol
 | 
			
		||||
	if dest.GetBool("StripNick") {
 | 
			
		||||
		re := regexp.MustCompile("[^a-zA-Z0-9]+")
 | 
			
		||||
		msg.Username = re.ReplaceAllString(msg.Username, "")
 | 
			
		||||
@@ -313,7 +300,6 @@ func (gw *Gateway) modifyUsername(msg *config.Message, dest *bridge.Bridge) stri
 | 
			
		||||
	nick := dest.GetString("RemoteNickFormat")
 | 
			
		||||
 | 
			
		||||
	// loop to replace nicks
 | 
			
		||||
	br := gw.Bridges[msg.Account]
 | 
			
		||||
	for _, outer := range br.GetStringSlice2D("ReplaceNicks") {
 | 
			
		||||
		search := outer[0]
 | 
			
		||||
		replace := outer[1]
 | 
			
		||||
@@ -345,11 +331,6 @@ func (gw *Gateway) modifyUsername(msg *config.Message, dest *bridge.Bridge) stri
 | 
			
		||||
	nick = strings.Replace(nick, "{LABEL}", br.GetString("Label"), -1)
 | 
			
		||||
	nick = strings.Replace(nick, "{NICK}", msg.Username, -1)
 | 
			
		||||
	nick = strings.Replace(nick, "{CHANNEL}", msg.Channel, -1)
 | 
			
		||||
	tengoNick, err := gw.modifyUsernameTengo(msg, br)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		gw.logger.Errorf("modifyUsernameTengo error: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	nick = strings.Replace(nick, "{TENGO}", tengoNick, -1) //nolint:gocritic
 | 
			
		||||
	return nick
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -366,12 +347,9 @@ func (gw *Gateway) modifyMessage(msg *config.Message) {
 | 
			
		||||
	if err := modifyMessageTengo(gw.BridgeValues().General.TengoModifyMessage, msg); err != nil {
 | 
			
		||||
		gw.logger.Errorf("TengoModifyMessage failed: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := modifyMessageTengo(gw.BridgeValues().Tengo.Message, msg); err != nil {
 | 
			
		||||
		gw.logger.Errorf("Tengo.Message failed: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// replace :emoji: to unicode
 | 
			
		||||
	msg.Text = emoji.Sprint(msg.Text)
 | 
			
		||||
	msg.Text = emojilib.Replace(msg.Text)
 | 
			
		||||
 | 
			
		||||
	br := gw.Bridges[msg.Account]
 | 
			
		||||
	// loop to replace messages
 | 
			
		||||
@@ -443,11 +421,6 @@ func (gw *Gateway) SendMessage(
 | 
			
		||||
		msg.ParentID = "msg-parent-not-found"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := gw.modifySendMessageTengo(rmsg, &msg, dest)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		gw.logger.Errorf("modifySendMessageTengo: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// if we are using mattermost plugin account, send messages to MattermostPlugin channel
 | 
			
		||||
	// that can be picked up by the mattermost matterbridge plugin
 | 
			
		||||
	if dest.Account == "mattermost.plugin" {
 | 
			
		||||
@@ -513,7 +486,7 @@ func modifyMessageTengo(filename string, msg *config.Message) error {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	s := tengo.NewScript(res)
 | 
			
		||||
	s := script.New(res)
 | 
			
		||||
	s.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...))
 | 
			
		||||
	_ = s.Add("msgText", msg.Text)
 | 
			
		||||
	_ = s.Add("msgUsername", msg.Username)
 | 
			
		||||
@@ -530,77 +503,3 @@ func modifyMessageTengo(filename string, msg *config.Message) error {
 | 
			
		||||
	msg.Username = c.Get("msgUsername").String()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (gw *Gateway) modifyUsernameTengo(msg *config.Message, br *bridge.Bridge) (string, error) {
 | 
			
		||||
	filename := gw.BridgeValues().Tengo.RemoteNickFormat
 | 
			
		||||
	if filename == "" {
 | 
			
		||||
		return "", nil
 | 
			
		||||
	}
 | 
			
		||||
	res, err := ioutil.ReadFile(filename)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	s := tengo.NewScript(res)
 | 
			
		||||
	s.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...))
 | 
			
		||||
	_ = s.Add("result", "")
 | 
			
		||||
	_ = s.Add("msgText", msg.Text)
 | 
			
		||||
	_ = s.Add("msgUsername", msg.Username)
 | 
			
		||||
	_ = s.Add("nick", msg.Username)
 | 
			
		||||
	_ = s.Add("msgAccount", msg.Account)
 | 
			
		||||
	_ = s.Add("msgChannel", msg.Channel)
 | 
			
		||||
	_ = s.Add("channel", msg.Channel)
 | 
			
		||||
	_ = s.Add("msgProtocol", msg.Protocol)
 | 
			
		||||
	_ = s.Add("remoteAccount", br.Account)
 | 
			
		||||
	_ = s.Add("protocol", br.Protocol)
 | 
			
		||||
	_ = s.Add("bridge", br.Name)
 | 
			
		||||
	_ = s.Add("gateway", gw.Name)
 | 
			
		||||
	c, err := s.Compile()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.Run(); err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	return c.Get("result").String(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (gw *Gateway) modifySendMessageTengo(origmsg *config.Message, msg *config.Message, br *bridge.Bridge) error {
 | 
			
		||||
	filename := gw.BridgeValues().Tengo.OutMessage
 | 
			
		||||
	var res []byte
 | 
			
		||||
	var err error
 | 
			
		||||
	if filename == "" {
 | 
			
		||||
		res, err = internal.Asset("tengo/outmessage.tengo")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		res, err = ioutil.ReadFile(filename)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	s := tengo.NewScript(res)
 | 
			
		||||
	s.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...))
 | 
			
		||||
	_ = s.Add("inAccount", origmsg.Account)
 | 
			
		||||
	_ = s.Add("inProtocol", origmsg.Protocol)
 | 
			
		||||
	_ = s.Add("inChannel", origmsg.Channel)
 | 
			
		||||
	_ = s.Add("inGateway", origmsg.Gateway)
 | 
			
		||||
	_ = s.Add("inEvent", origmsg.Event)
 | 
			
		||||
	_ = s.Add("outAccount", br.Account)
 | 
			
		||||
	_ = s.Add("outProtocol", br.Protocol)
 | 
			
		||||
	_ = s.Add("outChannel", msg.Channel)
 | 
			
		||||
	_ = s.Add("outGateway", gw.Name)
 | 
			
		||||
	_ = s.Add("outEvent", msg.Event)
 | 
			
		||||
	_ = s.Add("msgText", msg.Text)
 | 
			
		||||
	_ = s.Add("msgUsername", msg.Username)
 | 
			
		||||
	c, err := s.Compile()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.Run(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	msg.Text = c.Get("msgText").String()
 | 
			
		||||
	msg.Username = c.Get("msgUsername").String()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,15 +15,10 @@ import (
 | 
			
		||||
 | 
			
		||||
var testconfig = []byte(`
 | 
			
		||||
[irc.freenode]
 | 
			
		||||
server=""
 | 
			
		||||
[mattermost.test]
 | 
			
		||||
server=""
 | 
			
		||||
[gitter.42wim]
 | 
			
		||||
server=""
 | 
			
		||||
[discord.test]
 | 
			
		||||
server=""
 | 
			
		||||
[slack.test]
 | 
			
		||||
server=""
 | 
			
		||||
 | 
			
		||||
[[gateway]]
 | 
			
		||||
    name = "bridge1"
 | 
			
		||||
@@ -49,15 +44,10 @@ server=""
 | 
			
		||||
 | 
			
		||||
var testconfig2 = []byte(`
 | 
			
		||||
[irc.freenode]
 | 
			
		||||
server=""
 | 
			
		||||
[mattermost.test]
 | 
			
		||||
server=""
 | 
			
		||||
[gitter.42wim]
 | 
			
		||||
server=""
 | 
			
		||||
[discord.test]
 | 
			
		||||
server=""
 | 
			
		||||
[slack.test]
 | 
			
		||||
server=""
 | 
			
		||||
 | 
			
		||||
[[gateway]]
 | 
			
		||||
    name = "bridge1"
 | 
			
		||||
@@ -97,11 +87,8 @@ server=""
 | 
			
		||||
 | 
			
		||||
var testconfig3 = []byte(`
 | 
			
		||||
[irc.zzz]
 | 
			
		||||
server=""
 | 
			
		||||
[telegram.zzz]
 | 
			
		||||
server=""
 | 
			
		||||
[slack.zzz]
 | 
			
		||||
server=""
 | 
			
		||||
[[gateway]]
 | 
			
		||||
name="bridge"
 | 
			
		||||
enable=true
 | 
			
		||||
@@ -189,6 +176,7 @@ func TestNewRouter(t *testing.T) {
 | 
			
		||||
	assert.Equal(t, 1, len(r.Gateways))
 | 
			
		||||
	assert.Equal(t, 4, len(r.Gateways["bridge1"].Bridges))
 | 
			
		||||
	assert.Equal(t, 4, len(r.Gateways["bridge1"].Channels))
 | 
			
		||||
 | 
			
		||||
	r = maketestRouter(testconfig2)
 | 
			
		||||
	assert.Equal(t, 2, len(r.Gateways))
 | 
			
		||||
	assert.Equal(t, 4, len(r.Gateways["bridge1"].Bridges))
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,6 @@ import (
 | 
			
		||||
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/42wim/matterbridge/gateway/bridgemap"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// handleEventFailure handles failures and reconnects bridges.
 | 
			
		||||
@@ -169,7 +168,7 @@ func (gw *Gateway) ignoreEvent(event string, dest *bridge.Bridge) bool {
 | 
			
		||||
	switch event {
 | 
			
		||||
	case config.EventAvatarDownload:
 | 
			
		||||
		// Avatar downloads are only relevant for telegram and mattermost for now
 | 
			
		||||
		if dest.Protocol != "mattermost" && dest.Protocol != "telegram" && dest.Protocol != "xmpp" {
 | 
			
		||||
		if dest.Protocol != "mattermost" && dest.Protocol != "telegram" {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	case config.EventJoinLeave:
 | 
			
		||||
@@ -179,7 +178,7 @@ func (gw *Gateway) ignoreEvent(event string, dest *bridge.Bridge) bool {
 | 
			
		||||
		}
 | 
			
		||||
	case config.EventTopicChange:
 | 
			
		||||
		// only relay topic change when used in some way on other side
 | 
			
		||||
		if !dest.GetBool("ShowTopicChange") && !dest.GetBool("SyncTopic") {
 | 
			
		||||
		if dest.GetBool("ShowTopicChange") && dest.GetBool("SyncTopic") {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -191,14 +190,6 @@ func (gw *Gateway) ignoreEvent(event string, dest *bridge.Bridge) bool {
 | 
			
		||||
func (gw *Gateway) handleMessage(rmsg *config.Message, dest *bridge.Bridge) []*BrMsgID {
 | 
			
		||||
	var brMsgIDs []*BrMsgID
 | 
			
		||||
 | 
			
		||||
	// Not all bridges support "user is typing" indications so skip the message
 | 
			
		||||
	// if the targeted bridge does not support it.
 | 
			
		||||
	if rmsg.Event == config.EventUserTyping {
 | 
			
		||||
		if _, ok := bridgemap.UserTypingSupport[dest.Protocol]; !ok {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// if we have an attached file, or other info
 | 
			
		||||
	if rmsg.Extra != nil && len(rmsg.Extra[config.EventFileFailureSize]) != 0 && rmsg.Text == "" {
 | 
			
		||||
		return brMsgIDs
 | 
			
		||||
 
 | 
			
		||||
@@ -59,14 +59,8 @@ func NewRouter(rootLogger *logrus.Logger, cfg config.Config, bridgeMap map[strin
 | 
			
		||||
// between them.
 | 
			
		||||
func (r *Router) Start() error {
 | 
			
		||||
	m := make(map[string]*bridge.Bridge)
 | 
			
		||||
	if len(r.Gateways) == 0 {
 | 
			
		||||
		return fmt.Errorf("no [[gateway]] configured. See https://github.com/42wim/matterbridge/wiki/How-to-create-your-config for more info")
 | 
			
		||||
	}
 | 
			
		||||
	for _, gw := range r.Gateways {
 | 
			
		||||
		r.logger.Infof("Parsing gateway %s", gw.Name)
 | 
			
		||||
		if len(gw.Bridges) == 0 {
 | 
			
		||||
			return fmt.Errorf("no bridges configured for gateway %s. See https://github.com/42wim/matterbridge/wiki/How-to-create-your-config for more info", gw.Name)
 | 
			
		||||
		}
 | 
			
		||||
		for _, br := range gw.Bridges {
 | 
			
		||||
			m[br.Account] = br
 | 
			
		||||
		}
 | 
			
		||||
@@ -131,11 +125,7 @@ func (r *Router) handleReceive() {
 | 
			
		||||
		r.handleEventGetChannelMembers(&msg)
 | 
			
		||||
		r.handleEventFailure(&msg)
 | 
			
		||||
		r.handleEventRejoinChannels(&msg)
 | 
			
		||||
 | 
			
		||||
		// Set message protocol based on the account it came from
 | 
			
		||||
		msg.Protocol = r.getBridge(msg.Account).Protocol
 | 
			
		||||
 | 
			
		||||
		filesHandled := false
 | 
			
		||||
		idx := 0
 | 
			
		||||
		for _, gw := range r.Gateways {
 | 
			
		||||
			// record all the message ID's of the different bridges
 | 
			
		||||
			var msgIDs []*BrMsgID
 | 
			
		||||
@@ -144,26 +134,17 @@ func (r *Router) handleReceive() {
 | 
			
		||||
			}
 | 
			
		||||
			msg.Timestamp = time.Now()
 | 
			
		||||
			gw.modifyMessage(&msg)
 | 
			
		||||
			if !filesHandled {
 | 
			
		||||
			if idx == 0 {
 | 
			
		||||
				gw.handleFiles(&msg)
 | 
			
		||||
				filesHandled = true
 | 
			
		||||
			}
 | 
			
		||||
			for _, br := range gw.Bridges {
 | 
			
		||||
				msgIDs = append(msgIDs, gw.handleMessage(&msg, br)...)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if msg.ID != "" {
 | 
			
		||||
				_, exists := gw.Messages.Get(msg.Protocol + " " + msg.ID)
 | 
			
		||||
 | 
			
		||||
				// Only add the message ID if it doesn't already exist
 | 
			
		||||
				//
 | 
			
		||||
				// For some bridges we always add/update the message ID.
 | 
			
		||||
				// This is necessary as msgIDs will change if a bridge returns
 | 
			
		||||
				// a different ID in response to edits.
 | 
			
		||||
				if !exists || msg.Protocol == "discord" {
 | 
			
		||||
			// only add the message ID if it doesn't already exists
 | 
			
		||||
			if _, ok := gw.Messages.Get(msg.Protocol + " " + msg.ID); !ok && msg.ID != "" {
 | 
			
		||||
				gw.Messages.Add(msg.Protocol+" "+msg.ID, msgIDs)
 | 
			
		||||
			}
 | 
			
		||||
			}
 | 
			
		||||
			idx++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										100
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										100
									
								
								go.mod
									
									
									
									
									
								
							@@ -3,56 +3,72 @@ module github.com/42wim/matterbridge
 | 
			
		||||
require (
 | 
			
		||||
	github.com/42wim/go-gitter v0.0.0-20170828205020-017310c2d557
 | 
			
		||||
	github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f
 | 
			
		||||
	github.com/BurntSushi/toml v0.0.0-20170318202913-d94612f9fc14 // indirect
 | 
			
		||||
	github.com/Jeffail/gabs v1.1.1 // indirect
 | 
			
		||||
	github.com/Philipp15b/go-steam v1.0.1-0.20200727090957-6ae9b3c0a560
 | 
			
		||||
	github.com/Rhymen/go-whatsapp v0.1.1-0.20201007125822-005103751b7a
 | 
			
		||||
	github.com/d5/tengo/v2 v2.6.2
 | 
			
		||||
	github.com/davecgh/go-spew v1.1.1
 | 
			
		||||
	github.com/fsnotify/fsnotify v1.4.9
 | 
			
		||||
	github.com/go-telegram-bot-api/telegram-bot-api v1.0.1-0.20200524105306-7434b0456e81
 | 
			
		||||
	github.com/gomarkdown/markdown v0.0.0-20200824053859-8c8b3816f167
 | 
			
		||||
	github.com/google/gops v0.3.11
 | 
			
		||||
	github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329
 | 
			
		||||
	github.com/bwmarrin/discordgo v0.19.0
 | 
			
		||||
	github.com/d5/tengo v1.20.0
 | 
			
		||||
	github.com/dfordsoft/golib v0.0.0-20180902042739-76ee6ab99bec
 | 
			
		||||
	github.com/fsnotify/fsnotify v1.4.7
 | 
			
		||||
	github.com/go-telegram-bot-api/telegram-bot-api v4.6.5-0.20181225215658-ec221ba9ea45+incompatible
 | 
			
		||||
	github.com/google/gops v0.3.5
 | 
			
		||||
	github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4 // indirect
 | 
			
		||||
	github.com/gorilla/schema v1.2.0
 | 
			
		||||
	github.com/gorilla/websocket v1.4.2
 | 
			
		||||
	github.com/hashicorp/golang-lru v0.5.4
 | 
			
		||||
	github.com/jpillora/backoff v1.0.0
 | 
			
		||||
	github.com/keybase/go-keybase-chat-bot v0.0.0-20200505163032-5cacf52379da
 | 
			
		||||
	github.com/labstack/echo/v4 v4.1.17
 | 
			
		||||
	github.com/lrstanley/girc v0.0.0-20190801035559-4fc93959e1a7
 | 
			
		||||
	github.com/matrix-org/gomatrix v0.0.0-20200827122206-7dd5e2a05bcd
 | 
			
		||||
	github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20200411204219-d5c18ce75048
 | 
			
		||||
	github.com/matterbridge/discordgo v0.22.0
 | 
			
		||||
	github.com/matterbridge/emoji v2.1.1-0.20191117213217-af507f6b02db+incompatible
 | 
			
		||||
	github.com/matterbridge/go-xmpp v0.0.0-20200418225040-c8a3a57b4050
 | 
			
		||||
	github.com/matterbridge/gozulipbot v0.0.0-20200820220548-be5824faa913
 | 
			
		||||
	github.com/matterbridge/logrus-prefixed-formatter v0.5.3-0.20200523233437-d971309a77ba
 | 
			
		||||
	github.com/mattermost/mattermost-server/v5 v5.28.1
 | 
			
		||||
	github.com/mattn/godown v0.0.0-20200217152941-afc959f6a561
 | 
			
		||||
	github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f // indirect
 | 
			
		||||
	github.com/gorilla/schema v1.0.2
 | 
			
		||||
	github.com/gorilla/websocket v1.4.0
 | 
			
		||||
	github.com/hashicorp/golang-lru v0.5.0
 | 
			
		||||
	github.com/hpcloud/tail v1.0.0 // indirect
 | 
			
		||||
	github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7
 | 
			
		||||
	github.com/jtolds/gls v4.2.1+incompatible // indirect
 | 
			
		||||
	github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 // indirect
 | 
			
		||||
	github.com/kr/pretty v0.1.0 // indirect
 | 
			
		||||
	github.com/labstack/echo/v4 v4.0.0
 | 
			
		||||
	github.com/lrstanley/girc v0.0.0-20190210212025-51b8e096d398
 | 
			
		||||
	github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 // indirect
 | 
			
		||||
	github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6 // indirect
 | 
			
		||||
	github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20190210153444-cc9d05784d5d
 | 
			
		||||
	github.com/matterbridge/go-whatsapp v0.0.1-0.20190301204034-f2f1b29d441b
 | 
			
		||||
	github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91
 | 
			
		||||
	github.com/matterbridge/gomatrix v0.0.0-20190102230110-6f9631ca6dea
 | 
			
		||||
	github.com/matterbridge/gozulipbot v0.0.0-20190212232658-7aa251978a18
 | 
			
		||||
	github.com/matterbridge/logrus-prefixed-formatter v0.0.0-20180806162718-01618749af61
 | 
			
		||||
	github.com/matterbridge/mautrix-whatsapp v0.0.0-20190301210046-3539cf52ed6e
 | 
			
		||||
	github.com/mattermost/mattermost-server v5.5.0+incompatible
 | 
			
		||||
	github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
 | 
			
		||||
	github.com/missdeer/golib v1.0.3
 | 
			
		||||
	github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474 // indirect
 | 
			
		||||
	github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff // indirect
 | 
			
		||||
	github.com/nelsonken/gomf v0.0.0-20180504123937-a9dd2f9deae9
 | 
			
		||||
	github.com/paulrosania/go-charset v0.0.0-20190326053356-55c9d7a5834c
 | 
			
		||||
	github.com/nicksnyder/go-i18n v1.4.0 // indirect
 | 
			
		||||
	github.com/nlopes/slack v0.5.0
 | 
			
		||||
	github.com/onsi/ginkgo v1.6.0 // indirect
 | 
			
		||||
	github.com/onsi/gomega v1.4.1 // indirect
 | 
			
		||||
	github.com/paulrosania/go-charset v0.0.0-20151028000031-621bb39fcc83
 | 
			
		||||
	github.com/pborman/uuid v0.0.0-20160216163710-c55201b03606 // indirect
 | 
			
		||||
	github.com/peterhellberg/emojilib v0.0.0-20190124112554-c18758d55320
 | 
			
		||||
	github.com/pkg/errors v0.8.0 // indirect
 | 
			
		||||
	github.com/rs/xid v1.2.1
 | 
			
		||||
	github.com/russross/blackfriday v1.5.2
 | 
			
		||||
	github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca
 | 
			
		||||
	github.com/shazow/ssh-chat v1.8.3-0.20200308224626-80ddf1f43a98
 | 
			
		||||
	github.com/sirupsen/logrus v1.7.0
 | 
			
		||||
	github.com/slack-go/slack v0.7.2
 | 
			
		||||
	github.com/spf13/viper v1.7.1
 | 
			
		||||
	github.com/stretchr/testify v1.6.1
 | 
			
		||||
	github.com/vincent-petithory/dataurl v0.0.0-20191104211930-d1553a71de50
 | 
			
		||||
	github.com/writeas/go-strip-markdown v2.0.1+incompatible
 | 
			
		||||
	github.com/shazow/ssh-chat v0.0.0-20190125184227-81d7e1686296
 | 
			
		||||
	github.com/sirupsen/logrus v1.3.0
 | 
			
		||||
	github.com/smartystreets/assertions v0.0.0-20180803164922-886ec427f6b9 // indirect
 | 
			
		||||
	github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a // indirect
 | 
			
		||||
	github.com/spf13/viper v1.3.1
 | 
			
		||||
	github.com/stretchr/testify v1.3.0
 | 
			
		||||
	github.com/technoweenie/multipartstreamer v1.0.1 // indirect
 | 
			
		||||
	github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect
 | 
			
		||||
	github.com/yaegashi/msgraph.go v0.1.4
 | 
			
		||||
	github.com/zfjagann/golang-ring v0.0.0-20190304061218-d34796e0a6c2
 | 
			
		||||
	golang.org/x/image v0.0.0-20200927104501-e162460cd6b5
 | 
			
		||||
	golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
 | 
			
		||||
	gomod.garykim.dev/nc-talk v0.1.5
 | 
			
		||||
	gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376
 | 
			
		||||
	layeh.com/gumble v0.0.0-20200818122324-146f9205029b
 | 
			
		||||
	github.com/zfjagann/golang-ring v0.0.0-20190106091943-a88bb6aef447
 | 
			
		||||
	gitlab.com/golang-commonmark/html v0.0.0-20180917080848-cfaf75183c4a // indirect
 | 
			
		||||
	gitlab.com/golang-commonmark/linkify v0.0.0-20180917065525-c22b7bdb1179 // indirect
 | 
			
		||||
	gitlab.com/golang-commonmark/markdown v0.0.0-20181102083822-772775880e1f
 | 
			
		||||
	gitlab.com/golang-commonmark/mdurl v0.0.0-20180912090424-e5bce34c34f2 // indirect
 | 
			
		||||
	gitlab.com/golang-commonmark/puny v0.0.0-20180912090636-2cd490539afe // indirect
 | 
			
		||||
	gitlab.com/opennota/wd v0.0.0-20180912061657-c5d65f63c638 // indirect
 | 
			
		||||
	go.uber.org/atomic v1.3.2 // indirect
 | 
			
		||||
	go.uber.org/multierr v1.1.0 // indirect
 | 
			
		||||
	go.uber.org/zap v1.9.1 // indirect
 | 
			
		||||
	golang.org/x/image v0.0.0-20190220214146-31aff87c08e9
 | 
			
		||||
	gopkg.in/fsnotify.v1 v1.4.7 // indirect
 | 
			
		||||
	gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
 | 
			
		||||
	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
go 1.13
 | 
			
		||||
 
 | 
			
		||||
@@ -1,288 +0,0 @@
 | 
			
		||||
// Code generated by go-bindata. DO NOT EDIT.
 | 
			
		||||
// sources:
 | 
			
		||||
// tengo/outmessage.tengo
 | 
			
		||||
 | 
			
		||||
package internal
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"compress/gzip"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func bindataRead(data []byte, name string) ([]byte, error) {
 | 
			
		||||
	gz, err := gzip.NewReader(bytes.NewBuffer(data))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Read %q: %v", name, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var buf bytes.Buffer
 | 
			
		||||
	_, err = io.Copy(&buf, gz)
 | 
			
		||||
	clErr := gz.Close()
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Read %q: %v", name, err)
 | 
			
		||||
	}
 | 
			
		||||
	if clErr != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return buf.Bytes(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
type asset struct {
 | 
			
		||||
	bytes []byte
 | 
			
		||||
	info  fileInfoEx
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type fileInfoEx interface {
 | 
			
		||||
	os.FileInfo
 | 
			
		||||
	MD5Checksum() string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type bindataFileInfo struct {
 | 
			
		||||
	name        string
 | 
			
		||||
	size        int64
 | 
			
		||||
	mode        os.FileMode
 | 
			
		||||
	modTime     time.Time
 | 
			
		||||
	md5checksum string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (fi bindataFileInfo) Name() string {
 | 
			
		||||
	return fi.name
 | 
			
		||||
}
 | 
			
		||||
func (fi bindataFileInfo) Size() int64 {
 | 
			
		||||
	return fi.size
 | 
			
		||||
}
 | 
			
		||||
func (fi bindataFileInfo) Mode() os.FileMode {
 | 
			
		||||
	return fi.mode
 | 
			
		||||
}
 | 
			
		||||
func (fi bindataFileInfo) ModTime() time.Time {
 | 
			
		||||
	return fi.modTime
 | 
			
		||||
}
 | 
			
		||||
func (fi bindataFileInfo) MD5Checksum() string {
 | 
			
		||||
	return fi.md5checksum
 | 
			
		||||
}
 | 
			
		||||
func (fi bindataFileInfo) IsDir() bool {
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
func (fi bindataFileInfo) Sys() interface{} {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _bindataTengoOutmessagetengo = []byte(
 | 
			
		||||
	"\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x91\x3d\x8f\xda\x40\x10\x86\xfb\xfd\x15\x13\x37\xb1\x2d\x07\xe7\xa3" +
 | 
			
		||||
	"\xb3\x64\x59\x11\x45\x94\x2e\x8a\x92\x0a\xd0\xb1\xac\x07\x33\xd2\x7a\xc7\x1a\x8f\x31\x88\xe3\xbf\x9f\xcc\x01\x47" +
 | 
			
		||||
	"\x7f\xc5\x75\xef\xae\x9e\x9d\x77\x1f\x4d\x9e\x9a\xbd\x15\xb2\x1b\x8f\x3d\xd8\xbd\x25\x3f\x45\x30\x82\xb6\xfe\xc2" +
 | 
			
		||||
	"\xc1\x1f\x0b\x43\xe1\xa7\x73\x3c\x04\xcd\x80\xc2\x1f\x61\x65\xc7\x7e\xca\xf3\x9d\x0d\x01\x2f\xf1\x97\x55\x1c\xed" +
 | 
			
		||||
	"\xd1\xf0\xa0\x77\x98\x07\x7d\xa3\x79\xd0\x3b\xce\x83\xde\xf8\xd7\x9e\x51\x48\xb1\x30\x6d\xdf\xfc\xc3\x83\x66\xd0" +
 | 
			
		||||
	"\xf6\xcd\xff\x1e\x25\xd8\x16\x4d\x9a\x1b\xa3\x78\x50\x28\x4a\xa0\xb6\x63\xd1\x38\x9a\xce\x51\x62\x4c\x9e\x43\xaf" +
 | 
			
		||||
	"\x42\x1d\x90\x38\x70\xec\x59\xfa\xe9\x8e\xb6\x30\xe2\x67\x41\x08\xac\xd0\x63\xa8\x29\x34\xa0\x0c\x36\x5c\xc0\x8d" +
 | 
			
		||||
	"\x50\xdd\x20\x8c\x78\x7d\xac\x3b\x84\xdf\x7f\xe7\xb7\x01\xb4\x7d\xd0\x84\xb2\x84\x88\xc4\x45\x70\x32\x00\x00\x82" +
 | 
			
		||||
	"\xd3\x3f\xa6\xfe\x99\xe0\x93\xe3\xb6\x23\x8f\xf1\x7a\x79\xf8\xfa\x23\xae\x8a\x65\x7d\xfa\x96\x7d\x3f\xc7\x55\x91" +
 | 
			
		||||
	"\x5d\x63\x52\x25\xd5\xf3\x62\x51\xb8\xa0\xe2\x8b\xd5\x6a\x9d\x5c\xc6\x5c\x4d\x4b\xc1\x99\x60\xe7\xad\xc3\xf8\x26" +
 | 
			
		||||
	"\x1f\x45\x89\x39\x9b\xf7\x6b\xe4\x29\x6d\x1f\x57\x00\x9f\x3e\xc6\x24\xcd\xcd\x4b\x00\x00\x00\xff\xff\x40\xb8\x54" +
 | 
			
		||||
	"\xb8\x64\x02\x00\x00")
 | 
			
		||||
 | 
			
		||||
func bindataTengoOutmessagetengoBytes() ([]byte, error) {
 | 
			
		||||
	return bindataRead(
 | 
			
		||||
		_bindataTengoOutmessagetengo,
 | 
			
		||||
		"tengo/outmessage.tengo",
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func bindataTengoOutmessagetengo() (*asset, error) {
 | 
			
		||||
	bytes, err := bindataTengoOutmessagetengoBytes()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	info := bindataFileInfo{
 | 
			
		||||
		name: "tengo/outmessage.tengo",
 | 
			
		||||
		size: 612,
 | 
			
		||||
		md5checksum: "",
 | 
			
		||||
		mode: os.FileMode(420),
 | 
			
		||||
		modTime: time.Unix(1555622139, 0),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	a := &asset{bytes: bytes, info: info}
 | 
			
		||||
 | 
			
		||||
	return a, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// Asset loads and returns the asset for the given name.
 | 
			
		||||
// It returns an error if the asset could not be found or
 | 
			
		||||
// could not be loaded.
 | 
			
		||||
//
 | 
			
		||||
func Asset(name string) ([]byte, error) {
 | 
			
		||||
	cannonicalName := strings.Replace(name, "\\", "/", -1)
 | 
			
		||||
	if f, ok := _bindata[cannonicalName]; ok {
 | 
			
		||||
		a, err := f()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
 | 
			
		||||
		}
 | 
			
		||||
		return a.bytes, nil
 | 
			
		||||
	}
 | 
			
		||||
	return nil, &os.PathError{Op: "open", Path: name, Err: os.ErrNotExist}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// MustAsset is like Asset but panics when Asset would return an error.
 | 
			
		||||
// It simplifies safe initialization of global variables.
 | 
			
		||||
// nolint: deadcode
 | 
			
		||||
//
 | 
			
		||||
func MustAsset(name string) []byte {
 | 
			
		||||
	a, err := Asset(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic("asset: Asset(" + name + "): " + err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return a
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// AssetInfo loads and returns the asset info for the given name.
 | 
			
		||||
// It returns an error if the asset could not be found or could not be loaded.
 | 
			
		||||
//
 | 
			
		||||
func AssetInfo(name string) (os.FileInfo, error) {
 | 
			
		||||
	cannonicalName := strings.Replace(name, "\\", "/", -1)
 | 
			
		||||
	if f, ok := _bindata[cannonicalName]; ok {
 | 
			
		||||
		a, err := f()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
 | 
			
		||||
		}
 | 
			
		||||
		return a.info, nil
 | 
			
		||||
	}
 | 
			
		||||
	return nil, &os.PathError{Op: "open", Path: name, Err: os.ErrNotExist}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// AssetNames returns the names of the assets.
 | 
			
		||||
// nolint: deadcode
 | 
			
		||||
//
 | 
			
		||||
func AssetNames() []string {
 | 
			
		||||
	names := make([]string, 0, len(_bindata))
 | 
			
		||||
	for name := range _bindata {
 | 
			
		||||
		names = append(names, name)
 | 
			
		||||
	}
 | 
			
		||||
	return names
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// _bindata is a table, holding each asset generator, mapped to its name.
 | 
			
		||||
//
 | 
			
		||||
var _bindata = map[string]func() (*asset, error){
 | 
			
		||||
	"tengo/outmessage.tengo": bindataTengoOutmessagetengo,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// AssetDir returns the file names below a certain
 | 
			
		||||
// directory embedded in the file by go-bindata.
 | 
			
		||||
// For example if you run go-bindata on data/... and data contains the
 | 
			
		||||
// following hierarchy:
 | 
			
		||||
//     data/
 | 
			
		||||
//       foo.txt
 | 
			
		||||
//       img/
 | 
			
		||||
//         a.png
 | 
			
		||||
//         b.png
 | 
			
		||||
// then AssetDir("data") would return []string{"foo.txt", "img"}
 | 
			
		||||
// AssetDir("data/img") would return []string{"a.png", "b.png"}
 | 
			
		||||
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
 | 
			
		||||
// AssetDir("") will return []string{"data"}.
 | 
			
		||||
//
 | 
			
		||||
func AssetDir(name string) ([]string, error) {
 | 
			
		||||
	node := _bintree
 | 
			
		||||
	if len(name) != 0 {
 | 
			
		||||
		cannonicalName := strings.Replace(name, "\\", "/", -1)
 | 
			
		||||
		pathList := strings.Split(cannonicalName, "/")
 | 
			
		||||
		for _, p := range pathList {
 | 
			
		||||
			node = node.Children[p]
 | 
			
		||||
			if node == nil {
 | 
			
		||||
				return nil, &os.PathError{
 | 
			
		||||
					Op: "open",
 | 
			
		||||
					Path: name,
 | 
			
		||||
					Err: os.ErrNotExist,
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if node.Func != nil {
 | 
			
		||||
		return nil, &os.PathError{
 | 
			
		||||
			Op: "open",
 | 
			
		||||
			Path: name,
 | 
			
		||||
			Err: os.ErrNotExist,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	rv := make([]string, 0, len(node.Children))
 | 
			
		||||
	for childName := range node.Children {
 | 
			
		||||
		rv = append(rv, childName)
 | 
			
		||||
	}
 | 
			
		||||
	return rv, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
type bintree struct {
 | 
			
		||||
	Func     func() (*asset, error)
 | 
			
		||||
	Children map[string]*bintree
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _bintree = &bintree{Func: nil, Children: map[string]*bintree{
 | 
			
		||||
	"tengo": {Func: nil, Children: map[string]*bintree{
 | 
			
		||||
		"outmessage.tengo": {Func: bindataTengoOutmessagetengo, Children: map[string]*bintree{}},
 | 
			
		||||
	}},
 | 
			
		||||
}}
 | 
			
		||||
 | 
			
		||||
// RestoreAsset restores an asset under the given directory
 | 
			
		||||
func RestoreAsset(dir, name string) error {
 | 
			
		||||
	data, err := Asset(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	info, err := AssetInfo(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RestoreAssets restores an asset under the given directory recursively
 | 
			
		||||
func RestoreAssets(dir, name string) error {
 | 
			
		||||
	children, err := AssetDir(name)
 | 
			
		||||
	// File
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return RestoreAsset(dir, name)
 | 
			
		||||
	}
 | 
			
		||||
	// Dir
 | 
			
		||||
	for _, child := range children {
 | 
			
		||||
		err = RestoreAssets(dir, filepath.Join(name, child))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func _filePath(dir, name string) string {
 | 
			
		||||
	cannonicalName := strings.Replace(name, "\\", "/", -1)
 | 
			
		||||
	return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,25 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
variables available 
 | 
			
		||||
read-only:
 | 
			
		||||
inAccount, inProtocol, inChannel, inGateway
 | 
			
		||||
outAccount, outProtocol, outChannel, outGateway
 | 
			
		||||
 | 
			
		||||
read-write:
 | 
			
		||||
msgText, msgUsername
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
text := import("text")
 | 
			
		||||
 | 
			
		||||
// start - strip irc colors 
 | 
			
		||||
// if we're not sending to an irc bridge we strip the IRC colors
 | 
			
		||||
if inProtocol == "irc" && outProtocol != "irc" {
 | 
			
		||||
    re := text.re_compile(`\x03(?:\d{1,2}(?:,\d{1,2})?)?|[[:cntrl:]]`)
 | 
			
		||||
    msgText=re.replace(msgText,"")
 | 
			
		||||
}
 | 
			
		||||
// end - strip irc colors
 | 
			
		||||
 | 
			
		||||
// strip custom emoji
 | 
			
		||||
if inProtocol == "discord" {
 | 
			
		||||
    re := text.re_compile(`<a?(:.*?:)[0-9]+>`)
 | 
			
		||||
    msgText=re.replace(msgText,"$1")
 | 
			
		||||
}
 | 
			
		||||
@@ -4,7 +4,6 @@ import (
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
@@ -16,7 +15,7 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	version = "1.19.0"
 | 
			
		||||
	version = "1.14.4"
 | 
			
		||||
	githash string
 | 
			
		||||
 | 
			
		||||
	flagConfig  = flag.String("conf", "matterbridge.toml", "config file")
 | 
			
		||||
@@ -51,15 +50,6 @@ func main() {
 | 
			
		||||
	cfg := config.NewConfig(rootLogger, *flagConfig)
 | 
			
		||||
	cfg.BridgeValues().General.Debug = *flagDebug
 | 
			
		||||
 | 
			
		||||
	// if logging to a file, ensure it is closed when the program terminates
 | 
			
		||||
	// nolint:errcheck
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if f, ok := rootLogger.Out.(*os.File); ok {
 | 
			
		||||
			f.Sync()
 | 
			
		||||
			f.Close()
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	r, err := gateway.NewRouter(rootLogger, cfg, bridgemap.FullMap)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Fatalf("Starting gateway failed: %s", err)
 | 
			
		||||
@@ -77,31 +67,17 @@ func setupLogger() *logrus.Logger {
 | 
			
		||||
		Formatter: &prefixed.TextFormatter{
 | 
			
		||||
			PrefixPadding: 13,
 | 
			
		||||
			DisableColors: true,
 | 
			
		||||
			FullTimestamp: true,
 | 
			
		||||
		},
 | 
			
		||||
		Level: logrus.InfoLevel,
 | 
			
		||||
	}
 | 
			
		||||
	if *flagDebug || os.Getenv("DEBUG") == "1" {
 | 
			
		||||
		logger.SetReportCaller(true)
 | 
			
		||||
		logger.Formatter = &prefixed.TextFormatter{
 | 
			
		||||
			PrefixPadding:   13,
 | 
			
		||||
			DisableColors:   true,
 | 
			
		||||
			FullTimestamp:   false,
 | 
			
		||||
 | 
			
		||||
			CallerFormatter: func(function, file string) string {
 | 
			
		||||
				return fmt.Sprintf(" [%s:%s]", function, file)
 | 
			
		||||
			},
 | 
			
		||||
			CallerPrettyfier: func(f *runtime.Frame) (string, string) {
 | 
			
		||||
				sp := strings.SplitAfter(f.File, "/matterbridge/")
 | 
			
		||||
				filename := f.File
 | 
			
		||||
				if len(sp) > 1 {
 | 
			
		||||
					filename = sp[1]
 | 
			
		||||
			ForceFormatting: true,
 | 
			
		||||
		}
 | 
			
		||||
				s := strings.Split(f.Function, ".")
 | 
			
		||||
				funcName := s[len(s)-1]
 | 
			
		||||
				return funcName, fmt.Sprintf("%s:%d", filename, f.Line)
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		logger.Level = logrus.DebugLevel
 | 
			
		||||
		logger.WithFields(logrus.Fields{"prefix": "main"}).Info("Enabling debug logging.")
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,5 @@
 | 
			
		||||
#This is configuration for matterbridge.
 | 
			
		||||
#WARNING: as this file contains credentials, be sure to set correct file permissions
 | 
			
		||||
#See https://github.com/42wim/matterbridge/wiki/How-to-create-your-config for how to create your config
 | 
			
		||||
#See https://github.com/42wim/matterbridge/wiki/Settings for all settings
 | 
			
		||||
###################################################################
 | 
			
		||||
#IRC section
 | 
			
		||||
###################################################################
 | 
			
		||||
@@ -103,10 +101,6 @@ ColorNicks=false
 | 
			
		||||
#OPTIONAL (default empty)
 | 
			
		||||
RunCommands=["PRIVMSG user hello","PRIVMSG chanserv something"]
 | 
			
		||||
 | 
			
		||||
#StripMarkdown strips markdown from messages
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
StripMarkdown=false
 | 
			
		||||
 | 
			
		||||
#Nicks you want to ignore. 
 | 
			
		||||
#Regular expressions supported
 | 
			
		||||
#Messages from those users will not be sent to other bridges.
 | 
			
		||||
@@ -161,11 +155,6 @@ RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
ShowJoinPart=false
 | 
			
		||||
 | 
			
		||||
#Enable to show verbose users joins/parts (ident@host) from other bridges
 | 
			
		||||
#Currently works for messages from the following bridges: irc
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
VerboseJoinPart=false
 | 
			
		||||
 | 
			
		||||
#Do not send joins/parts to other bridges
 | 
			
		||||
#Currently works for messages from the following bridges: irc, mattermost, slack
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
@@ -181,12 +170,6 @@ StripNick=false
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
ShowTopicChange=false
 | 
			
		||||
 | 
			
		||||
#Delay in milliseconds between channel joins
 | 
			
		||||
#Only useful when you have a LOT of channels to join
 | 
			
		||||
#See https://github.com/42wim/matterbridge/issues/1084
 | 
			
		||||
#OPTIONAL (default 0)
 | 
			
		||||
JoinDelay=0
 | 
			
		||||
 | 
			
		||||
###################################################################
 | 
			
		||||
#XMPP section
 | 
			
		||||
###################################################################
 | 
			
		||||
@@ -287,6 +270,98 @@ StripNick=false
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
ShowTopicChange=false
 | 
			
		||||
 | 
			
		||||
###################################################################
 | 
			
		||||
#hipchat section
 | 
			
		||||
###################################################################
 | 
			
		||||
#Go to https://www.hipchat.com/account/xmpp this will show you the necessary data
 | 
			
		||||
#to fill in the section below
 | 
			
		||||
[xmpp.hipchat]
 | 
			
		||||
#xmpp server to connect to. 
 | 
			
		||||
#REQUIRED
 | 
			
		||||
Server="chat.hipchat.com:5222"
 | 
			
		||||
 | 
			
		||||
#Jabber ID
 | 
			
		||||
#REQUIRED
 | 
			
		||||
Jid="12345_12345@chat.hipchat.com"
 | 
			
		||||
 | 
			
		||||
#Password (your hipchat password)
 | 
			
		||||
#REQUIRED
 | 
			
		||||
Password="yourpass"
 | 
			
		||||
 | 
			
		||||
#Conference (MUC) domain
 | 
			
		||||
#REQUIRED
 | 
			
		||||
Muc="conf.hipchat.com"
 | 
			
		||||
 | 
			
		||||
#Room nickname
 | 
			
		||||
#REQUIRED
 | 
			
		||||
Nick="yourlogin"
 | 
			
		||||
 | 
			
		||||
## RELOADABLE SETTINGS
 | 
			
		||||
## Settings below can be reloaded by editing the file
 | 
			
		||||
 | 
			
		||||
#Nicks you want to ignore. 
 | 
			
		||||
#Regular expressions supported
 | 
			
		||||
#Messages from those users will not be sent to other bridges.
 | 
			
		||||
#OPTIONAL
 | 
			
		||||
IgnoreNicks="spammer1 spammer2"
 | 
			
		||||
 | 
			
		||||
#Messages you want to ignore. 
 | 
			
		||||
#Messages matching these regexp will be ignored and not sent to other bridges
 | 
			
		||||
#See https://regex-golang.appspot.com/assets/html/index.html for more regex info
 | 
			
		||||
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
 | 
			
		||||
IgnoreMessages="^~~ badword"
 | 
			
		||||
 | 
			
		||||
#messages you want to replace.
 | 
			
		||||
#it replaces outgoing messages from the bridge.
 | 
			
		||||
#so you need to place it by the sending bridge definition.
 | 
			
		||||
#regular expressions supported
 | 
			
		||||
#some examples:
 | 
			
		||||
#this replaces cat => dog and sleep => awake
 | 
			
		||||
#replacemessages=[ ["cat","dog"], ["sleep","awake"] ]
 | 
			
		||||
#this replaces every number with number.  123 => numbernumbernumber
 | 
			
		||||
#replacemessages=[ ["[0-9]","number"] ]
 | 
			
		||||
#optional (default empty)
 | 
			
		||||
ReplaceMessages=[ ["cat","dog"] ]
 | 
			
		||||
 | 
			
		||||
#nicks you want to replace.
 | 
			
		||||
#see replacemessages for syntaxa
 | 
			
		||||
#optional (default empty)
 | 
			
		||||
ReplaceNicks=[ ["user--","user"] ]
 | 
			
		||||
 | 
			
		||||
#Extractnicks is used to for example rewrite messages from other relaybots
 | 
			
		||||
#See https://github.com/42wim/matterbridge/issues/713 and https://github.com/42wim/matterbridge/issues/466
 | 
			
		||||
#some examples:
 | 
			
		||||
#this replaces a message like "Relaybot: <relayeduser> something interesting" to "relayeduser: something interesting"
 | 
			
		||||
#ExtractNicks=[ [ "Relaybot", "<(.*?)>\\s+" ] ]
 | 
			
		||||
#you can use multiple entries for multiplebots
 | 
			
		||||
#this also replaces a message like "otherbot: (relayeduser) something else" to "relayeduser: something else"
 | 
			
		||||
#ExtractNicks=[ [ "Relaybot", "<(.*?)>\\s+" ],[ "otherbot","\\((.*?)\\)\\s+" ]
 | 
			
		||||
#OPTIONAL (default empty)
 | 
			
		||||
ExtractNicks=[ ["otherbot","<(.*?)>\\s+" ] ]
 | 
			
		||||
 | 
			
		||||
#extra label that can be used in the RemoteNickFormat
 | 
			
		||||
#optional (default empty)
 | 
			
		||||
Label=""
 | 
			
		||||
 | 
			
		||||
#RemoteNickFormat defines how remote users appear on this bridge 
 | 
			
		||||
#See [general] config section for default options
 | 
			
		||||
RemoteNickFormat="[{PROTOCOL}/{BRIDGE}] <{NICK}> "
 | 
			
		||||
 | 
			
		||||
#Enable to show users joins/parts from other bridges 
 | 
			
		||||
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
ShowJoinPart=false
 | 
			
		||||
 | 
			
		||||
#StripNick only allows alphanumerical nicks. See https://github.com/42wim/matterbridge/issues/285
 | 
			
		||||
#It will strip other characters from the nick
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
StripNick=false
 | 
			
		||||
 | 
			
		||||
#Enable to show topic changes from other bridges 
 | 
			
		||||
#Only works hiding/show topic changes from slack bridge for now
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
ShowTopicChange=false
 | 
			
		||||
 | 
			
		||||
###################################################################
 | 
			
		||||
#mattermost section
 | 
			
		||||
###################################################################
 | 
			
		||||
@@ -360,12 +435,6 @@ NickFormatter="plain"
 | 
			
		||||
#OPTIONAL (default 4)
 | 
			
		||||
NicksPerRow=4
 | 
			
		||||
 | 
			
		||||
#Skip the Mattermost server version checks that are normally done when connecting.
 | 
			
		||||
#The usage scenario for this feature would be when the Mattermost instance is hosted behind a
 | 
			
		||||
#reverse proxy that suppresses "non-standard" response headers in flight.
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
SkipVersionCheck=false
 | 
			
		||||
 | 
			
		||||
#Whether to prefix messages from other bridges to mattermost with the sender's nick. 
 | 
			
		||||
#Useful if username overrides for incoming webhooks isn't enabled on the 
 | 
			
		||||
#mattermost server. If you set PrefixMessagesWithNick to true, each message 
 | 
			
		||||
@@ -532,119 +601,6 @@ StripNick=false
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
ShowTopicChange=false
 | 
			
		||||
 | 
			
		||||
###################################################################
 | 
			
		||||
#
 | 
			
		||||
# Keybase
 | 
			
		||||
# You should have a separate bridge account on Keybase
 | 
			
		||||
# (it also needs to be logged in on the system you're running the bridge on)
 | 
			
		||||
#
 | 
			
		||||
###################################################################
 | 
			
		||||
 | 
			
		||||
[keybase.myteam]
 | 
			
		||||
 | 
			
		||||
# RemoteNickFormat defines how remote users appear on this bridge
 | 
			
		||||
# See [general] config section for default options
 | 
			
		||||
RemoteNickFormat="{NICK} ({PROTOCOL}): "
 | 
			
		||||
 | 
			
		||||
# extra label that can be used in the RemoteNickFormat
 | 
			
		||||
# optional (default empty)
 | 
			
		||||
Label=""
 | 
			
		||||
 | 
			
		||||
# Your team on Keybase.
 | 
			
		||||
# The bot user MUST be a member of this team
 | 
			
		||||
# REQUIRED
 | 
			
		||||
Team="myteam"
 | 
			
		||||
 | 
			
		||||
###################################################################
 | 
			
		||||
# Microsoft teams section
 | 
			
		||||
# See https://github.com/42wim/matterbridge/wiki/MS-Teams-setup
 | 
			
		||||
###################################################################
 | 
			
		||||
 | 
			
		||||
[msteams.myteam]
 | 
			
		||||
 | 
			
		||||
# TenantID
 | 
			
		||||
# See https://github.com/42wim/matterbridge/wiki/MS-Teams-setup#get-necessary-ids-for-matterbridge
 | 
			
		||||
TenantID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
 | 
			
		||||
 | 
			
		||||
# ClientID
 | 
			
		||||
# See https://github.com/42wim/matterbridge/wiki/MS-Teams-setup#get-necessary-ids-for-matterbridge
 | 
			
		||||
ClientID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
 | 
			
		||||
 | 
			
		||||
# TeamID
 | 
			
		||||
# See https://github.com/42wim/matterbridge/wiki/MS-Teams-setup#get-necessary-ids-for-matterbridge
 | 
			
		||||
TeamID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
 | 
			
		||||
 | 
			
		||||
## RELOADABLE SETTINGS
 | 
			
		||||
## Settings below can be reloaded by editing the file
 | 
			
		||||
 | 
			
		||||
#Nicks you want to ignore.
 | 
			
		||||
#Regular expressions supported
 | 
			
		||||
#Messages from those users will not be sent to other bridges.
 | 
			
		||||
#OPTIONAL
 | 
			
		||||
IgnoreNicks="ircspammer1 ircspammer2"
 | 
			
		||||
 | 
			
		||||
#Messages you want to ignore.
 | 
			
		||||
#Messages matching these regexp will be ignored and not sent to other bridges
 | 
			
		||||
#See https://regex-golang.appspot.com/assets/html/index.html for more regex info
 | 
			
		||||
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
 | 
			
		||||
IgnoreMessages="^~~ badword"
 | 
			
		||||
 | 
			
		||||
#messages you want to replace.
 | 
			
		||||
#it replaces outgoing messages from the bridge.
 | 
			
		||||
#so you need to place it by the sending bridge definition.
 | 
			
		||||
#regular expressions supported
 | 
			
		||||
#some examples:
 | 
			
		||||
#this replaces cat => dog and sleep => awake
 | 
			
		||||
#replacemessages=[ ["cat","dog"], ["sleep","awake"] ]
 | 
			
		||||
#this replaces every number with number.  123 => numbernumbernumber
 | 
			
		||||
#replacemessages=[ ["[0-9]","number"] ]
 | 
			
		||||
#optional (default empty)
 | 
			
		||||
ReplaceMessages=[ ["cat","dog"] ]
 | 
			
		||||
 | 
			
		||||
#nicks you want to replace.
 | 
			
		||||
#see replacemessages for syntaxa
 | 
			
		||||
#optional (default empty)
 | 
			
		||||
ReplaceNicks=[ ["user--","user"] ]
 | 
			
		||||
 | 
			
		||||
#Extractnicks is used to for example rewrite messages from other relaybots
 | 
			
		||||
#See https://github.com/42wim/matterbridge/issues/713 and https://github.com/42wim/matterbridge/issues/466
 | 
			
		||||
#some examples:
 | 
			
		||||
#this replaces a message like "Relaybot: <relayeduser> something interesting" to "relayeduser: something interesting"
 | 
			
		||||
#ExtractNicks=[ [ "Relaybot", "<(.*?)>\\s+" ] ]
 | 
			
		||||
#you can use multiple entries for multiplebots
 | 
			
		||||
#this also replaces a message like "otherbot: (relayeduser) something else" to "relayeduser: something else"
 | 
			
		||||
#ExtractNicks=[ [ "Relaybot", "<(.*?)>\\s+" ],[ "otherbot","\\((.*?)\\)\\s+" ]
 | 
			
		||||
#OPTIONAL (default empty)
 | 
			
		||||
ExtractNicks=[ ["otherbot","<(.*?)>\\s+" ] ]
 | 
			
		||||
 | 
			
		||||
#extra label that can be used in the RemoteNickFormat
 | 
			
		||||
#optional (default empty)
 | 
			
		||||
Label=""
 | 
			
		||||
 | 
			
		||||
#RemoteNickFormat defines how remote users appear on this bridge
 | 
			
		||||
#See [general] config section for default options
 | 
			
		||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
 | 
			
		||||
 | 
			
		||||
#Enable to show users joins/parts from other bridges
 | 
			
		||||
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
ShowJoinPart=false
 | 
			
		||||
 | 
			
		||||
#StripNick only allows alphanumerical nicks. See https://github.com/42wim/matterbridge/issues/285
 | 
			
		||||
#It will strip other characters from the nick
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
StripNick=false
 | 
			
		||||
 | 
			
		||||
#Enable to show topic changes from other bridges
 | 
			
		||||
#Only works hiding/show topic changes from slack bridge for now
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
ShowTopicChange=false
 | 
			
		||||
 | 
			
		||||
#Opportunistically preserve threaded replies between bridges
 | 
			
		||||
#that support threading
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
PreserveThreading=false
 | 
			
		||||
 | 
			
		||||
###################################################################
 | 
			
		||||
#slack section
 | 
			
		||||
###################################################################
 | 
			
		||||
@@ -807,121 +763,110 @@ ShowUserTyping=false
 | 
			
		||||
#In this example we use [discord.game]
 | 
			
		||||
#REQUIRED
 | 
			
		||||
[discord.game]
 | 
			
		||||
# Token (REQUIRED) is the token to connect with Discord API
 | 
			
		||||
#Token to connect with Discord API
 | 
			
		||||
#You can get your token by following the instructions on 
 | 
			
		||||
#https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token
 | 
			
		||||
#If you want roles/groups mentions to be shown with names instead of ID, you'll need to give your bot the "Manage Roles" permission.
 | 
			
		||||
#REQUIRED
 | 
			
		||||
Token="Yourtokenhere"
 | 
			
		||||
 | 
			
		||||
# Server (REQUIRED) is the ID or name of the guild to connect to, selected from the guilds the bot has been invited to
 | 
			
		||||
#REQUIRED
 | 
			
		||||
Server="yourservername"
 | 
			
		||||
 | 
			
		||||
## RELOADABLE SETTINGS
 | 
			
		||||
## All settings below can be reloaded by editing the file.
 | 
			
		||||
## They are also all optional.
 | 
			
		||||
## Settings below can be reloaded by editing the file
 | 
			
		||||
 | 
			
		||||
# ShowEmbeds shows the title, description and URL of embedded messages (sent by other bots)
 | 
			
		||||
#Shows title, description and URL of embedded messages (sent by other bots)
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
ShowEmbeds=false
 | 
			
		||||
 | 
			
		||||
# UseLocalAvatar specifies source bridges for which an avatar should be 'guessed' when an incoming message has no avatar.
 | 
			
		||||
# This works by comparing the username of the message to an existing Discord user, and using the avatar of the Discord user.
 | 
			
		||||
#
 | 
			
		||||
# This only works if WebhookURL is set (AND the message has no avatar).
 | 
			
		||||
# Example: ["irc"]
 | 
			
		||||
UseLocalAvatar=[]
 | 
			
		||||
 | 
			
		||||
# UseUserName shows the username instead of the server nickname
 | 
			
		||||
#Shows the username instead of the server nickname
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
UseUserName=false
 | 
			
		||||
 | 
			
		||||
# UseDiscriminator appends the `#xxxx` discriminator when used with UseUserName
 | 
			
		||||
#Show #xxxx discriminator with UseUserName
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
UseDiscriminator=false
 | 
			
		||||
 | 
			
		||||
# WebhookURL sends messages in the style of puppets.
 | 
			
		||||
#Specify WebhookURL. If given, will relay messages using the Webhook, which gives a better look to messages.
 | 
			
		||||
#This only works if you have one discord channel, if you have multiple discord channels you'll have to specify it in the gateway config
 | 
			
		||||
# Example: "https://discordapp.com/api/webhooks/1234/abcd_xyzw"
 | 
			
		||||
WebhookURL=""
 | 
			
		||||
#OPTIONAL (default empty)
 | 
			
		||||
WebhookURL="Yourwebhooktokenhere"
 | 
			
		||||
 | 
			
		||||
# EditDisable disables sending of edits to other bridges
 | 
			
		||||
#Disable sending of edits to other bridges
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
EditDisable=false
 | 
			
		||||
 | 
			
		||||
# EditSuffix specifies the message to be appended to every edited message
 | 
			
		||||
# Example: " (edited)"
 | 
			
		||||
EditSuffix=""
 | 
			
		||||
#Message to be appended to every edited message
 | 
			
		||||
#OPTIONAL (default empty)
 | 
			
		||||
EditSuffix=" (edited)"
 | 
			
		||||
 | 
			
		||||
# IgnoreNicks mutes outgoing messages from certain users.
 | 
			
		||||
# Messages from these users will not be transmitted to other bridges.
 | 
			
		||||
# Regular expressions are also supported.
 | 
			
		||||
# Example: "ircspammer1 ircspammer2"
 | 
			
		||||
IgnoreNicks=""
 | 
			
		||||
#Nicks you want to ignore. 
 | 
			
		||||
#Regular expressions supported
 | 
			
		||||
#Messages from those users will not be sent to other bridges.
 | 
			
		||||
#OPTIONAL
 | 
			
		||||
IgnoreNicks="ircspammer1 ircspammer2"
 | 
			
		||||
 | 
			
		||||
# IgnoreMessages mutes outgoing messages of a certain format.
 | 
			
		||||
# Messages matching this regular expression will not be transmitted sent to other bridges
 | 
			
		||||
#Messages you want to ignore. 
 | 
			
		||||
#Messages matching these regexp will be ignored and not sent to other bridges
 | 
			
		||||
#See https://regex-golang.appspot.com/assets/html/index.html for more regex info
 | 
			
		||||
#
 | 
			
		||||
# Example that ignores messages starting with ~~ or messages containing badword:
 | 
			
		||||
#   IgnoreMessages="^~~ badword"
 | 
			
		||||
IgnoreMessages=""
 | 
			
		||||
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
 | 
			
		||||
IgnoreMessages="^~~ badword"
 | 
			
		||||
 | 
			
		||||
# ReplaceMessages replaces substrings of messages in outgoing messages.
 | 
			
		||||
# Regular expressions are supported.
 | 
			
		||||
#
 | 
			
		||||
# Example that replaces 'cat' => 'dog' and 'sleep' => 'awake':
 | 
			
		||||
#   ReplaceMessages=[ ["cat","dog"], ["sleep","awake"] ]
 | 
			
		||||
# Example that replaces all digits with the letter 'X', so 'hello123' becomes 'helloXXX':
 | 
			
		||||
#   ReplaceMessages=[ ["[0-9]","X"] ]
 | 
			
		||||
ReplaceMessages=[]
 | 
			
		||||
#messages you want to replace.
 | 
			
		||||
#it replaces outgoing messages from the bridge.
 | 
			
		||||
#so you need to place it by the sending bridge definition.
 | 
			
		||||
#regular expressions supported
 | 
			
		||||
#some examples:
 | 
			
		||||
#this replaces cat => dog and sleep => awake
 | 
			
		||||
#replacemessages=[ ["cat","dog"], ["sleep","awake"] ]
 | 
			
		||||
#this replaces every number with number.  123 => numbernumbernumber
 | 
			
		||||
#replacemessages=[ ["[0-9]","number"] ]
 | 
			
		||||
#optional (default empty)
 | 
			
		||||
ReplaceMessages=[ ["cat","dog"] ]
 | 
			
		||||
 | 
			
		||||
# ReplaceNicks replaces substrings of usernames in outgoing messages.
 | 
			
		||||
# See the ReplaceMessages setting for examples.
 | 
			
		||||
# Example: [ ["user--","user"] ]
 | 
			
		||||
ReplaceNicks=[]
 | 
			
		||||
#nicks you want to replace.
 | 
			
		||||
#see replacemessages for syntaxa
 | 
			
		||||
#optional (default empty)
 | 
			
		||||
ReplaceNicks=[ ["user--","user"] ]
 | 
			
		||||
 | 
			
		||||
# ExtractNicks allows for interoperability with other bridge software by rewriting messages and extracting usernames.
 | 
			
		||||
#
 | 
			
		||||
# Recommended reading:
 | 
			
		||||
# - https://github.com/42wim/matterbridge/issues/466
 | 
			
		||||
# - https://github.com/42wim/matterbridge/issues/713
 | 
			
		||||
#
 | 
			
		||||
# This example translates the following message
 | 
			
		||||
#   "Relaybot: <relayeduser> something interesting"
 | 
			
		||||
# into this message
 | 
			
		||||
#   "relayeduser: something interesting"
 | 
			
		||||
# like so:
 | 
			
		||||
#Extractnicks is used to for example rewrite messages from other relaybots
 | 
			
		||||
#See https://github.com/42wim/matterbridge/issues/713 and https://github.com/42wim/matterbridge/issues/466
 | 
			
		||||
#some examples:
 | 
			
		||||
#this replaces a message like "Relaybot: <relayeduser> something interesting" to "relayeduser: something interesting"
 | 
			
		||||
#ExtractNicks=[ [ "Relaybot", "<(.*?)>\\s+" ] ]
 | 
			
		||||
#
 | 
			
		||||
# This example translates the following message
 | 
			
		||||
#   "otherbot: (relayeduser) something else"
 | 
			
		||||
# into this message
 | 
			
		||||
#   "relayeduser: something else"
 | 
			
		||||
# like so:
 | 
			
		||||
#   ExtractNicks=[ [ "otherbot","\\((.*?)\\)\\s+" ] ]
 | 
			
		||||
#
 | 
			
		||||
# This example combines both of the above examples into one:
 | 
			
		||||
#you can use multiple entries for multiplebots
 | 
			
		||||
#this also replaces a message like "otherbot: (relayeduser) something else" to "relayeduser: something else"
 | 
			
		||||
#ExtractNicks=[ [ "Relaybot", "<(.*?)>\\s+" ],[ "otherbot","\\((.*?)\\)\\s+" ]
 | 
			
		||||
#
 | 
			
		||||
ExtractNicks=[]
 | 
			
		||||
#OPTIONAL (default empty)
 | 
			
		||||
ExtractNicks=[ ["otherbot","<(.*?)>\\s+" ] ]
 | 
			
		||||
 | 
			
		||||
# Label is as an extra identifier for use in the RemoteNickFormat setting.
 | 
			
		||||
#extra label that can be used in the RemoteNickFormat
 | 
			
		||||
#optional (default empty)
 | 
			
		||||
Label=""
 | 
			
		||||
 | 
			
		||||
# RemoteNickFormat formats how remote users appear on this bridge.
 | 
			
		||||
# See the [general] config section for default options
 | 
			
		||||
#RemoteNickFormat defines how remote users appear on this bridge 
 | 
			
		||||
#See [general] config section for default options
 | 
			
		||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
 | 
			
		||||
 | 
			
		||||
# ShowJoinPart emits messages that show joins/parts from other bridges
 | 
			
		||||
# Supported from the following bridges: irc, mattermost, slack, discord
 | 
			
		||||
#Enable to show users joins/parts from other bridges 
 | 
			
		||||
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
ShowJoinPart=false
 | 
			
		||||
 | 
			
		||||
# StripNick strips non-alphanumeric characters from nicknames.
 | 
			
		||||
# Recommended reading: https://github.com/42wim/matterbridge/issues/285
 | 
			
		||||
#StripNick only allows alphanumerical nicks. See https://github.com/42wim/matterbridge/issues/285
 | 
			
		||||
#It will strip other characters from the nick
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
StripNick=false
 | 
			
		||||
 | 
			
		||||
# ShowTopicChange emits messages that show topic/purpose updates from other bridges
 | 
			
		||||
# Supported from the following bridges: slack
 | 
			
		||||
#Enable to show topic/purpose changes from other bridges
 | 
			
		||||
#Only works hiding/show topic changes from slack bridge for now
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
ShowTopicChange=false
 | 
			
		||||
 | 
			
		||||
# SyncTopic synchronises topic/purpose updates from other bridges
 | 
			
		||||
# Supported from the following bridges: slack
 | 
			
		||||
#Enable to sync topic/purpose changes from other bridges
 | 
			
		||||
#Only works syncing topic changes from slack bridge for now
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
SyncTopic=false
 | 
			
		||||
 | 
			
		||||
###################################################################
 | 
			
		||||
@@ -942,17 +887,12 @@ Token="Yourtokenhere"
 | 
			
		||||
## Settings below can be reloaded by editing the file
 | 
			
		||||
 | 
			
		||||
#OPTIONAL (default empty)
 | 
			
		||||
#Supported formats are:
 | 
			
		||||
#"HTML" https://core.telegram.org/bots/api#html-style
 | 
			
		||||
#"Markdown" https://core.telegram.org/bots/api#markdown-style - deprecated, doesn't display links with underscores correctly
 | 
			
		||||
#"MarkdownV2" https://core.telegram.org/bots/api#markdownv2-style
 | 
			
		||||
#"HTMLNick" - only allows HTML for the nick, the message itself will be html-escaped
 | 
			
		||||
#Supported formats are "HTML", "Markdown" and "HTMLNick"
 | 
			
		||||
#See https://core.telegram.org/bots/api#html-style
 | 
			
		||||
#See https://core.telegram.org/bots/api#markdown-style
 | 
			
		||||
#HTMLNick only allows HTML for the nick, the message itself will be html-escaped
 | 
			
		||||
MessageFormat=""
 | 
			
		||||
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
#Disables link previews for links in messages
 | 
			
		||||
DisableWebPagePreview=false
 | 
			
		||||
 | 
			
		||||
#If enabled use the "First Name" as username. If this is empty use the Username
 | 
			
		||||
#If disabled use the "Username" as username. If this is empty use the First Name 
 | 
			
		||||
#If all names are empty, username will be "unknown"
 | 
			
		||||
@@ -969,10 +909,6 @@ UseInsecureURL=false
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
QuoteDisable=false
 | 
			
		||||
 | 
			
		||||
#Set the max. quoted length if 0 the whole message will be quoted
 | 
			
		||||
#OPTIONAL (default 0)
 | 
			
		||||
QuoteLengthLimit=0
 | 
			
		||||
 | 
			
		||||
#Format quoted/reply messages
 | 
			
		||||
#OPTIONAL (default "{MESSAGE} (re @{QUOTENICK}: {QUOTEMESSAGE})")
 | 
			
		||||
QuoteFormat="{MESSAGE} (re @{QUOTENICK}: {QUOTEMESSAGE})"
 | 
			
		||||
@@ -1076,11 +1012,6 @@ Server="https://yourrocketchatserver.domain.com:443"
 | 
			
		||||
#REQUIRED (when not using webhooks)
 | 
			
		||||
Login="yourlogin@domain.com"
 | 
			
		||||
Password="yourpass"
 | 
			
		||||
# When using access token set Login to the User ID associated with your token and Token to your token.
 | 
			
		||||
# When Token is set Password is ignored.
 | 
			
		||||
# Login="yOurUSerID"
 | 
			
		||||
# Token="YoUrUsER_toKEN"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### Settings for webhook matterbridge.
 | 
			
		||||
#USE DEDICATED BOT USER WHEN POSSIBLE! This allows you to use advanced features like message editing/deleting and uploads
 | 
			
		||||
@@ -1213,11 +1144,6 @@ Password="yourpass"
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
NoHomeServerSuffix=false
 | 
			
		||||
 | 
			
		||||
#Whether to disable sending of HTML content to matrix
 | 
			
		||||
#See https://github.com/42wim/matterbridge/issues/1022
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
HTMLDisable=false
 | 
			
		||||
 | 
			
		||||
## RELOADABLE SETTINGS
 | 
			
		||||
## Settings below can be reloaded by editing the file
 | 
			
		||||
 | 
			
		||||
@@ -1383,76 +1309,7 @@ StripNick=false
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
ShowTopicChange=false
 | 
			
		||||
 | 
			
		||||
###################################################################
 | 
			
		||||
#
 | 
			
		||||
# NCTalk (Nextcloud Talk)
 | 
			
		||||
#
 | 
			
		||||
###################################################################
 | 
			
		||||
 | 
			
		||||
[nctalk.bridge]
 | 
			
		||||
 | 
			
		||||
# Url of your Nextcloud server
 | 
			
		||||
Server = "https://cloud.youdomain.me"
 | 
			
		||||
 | 
			
		||||
# Enable to not verify the certificate on your Nextcloud server.
 | 
			
		||||
# e.g. when using selfsigned certificates
 | 
			
		||||
# OPTIONAL (default false)
 | 
			
		||||
SkipTLSVerify=true
 | 
			
		||||
 | 
			
		||||
# Username of the bot
 | 
			
		||||
Login = "talkuser"
 | 
			
		||||
 | 
			
		||||
# Password of the bot
 | 
			
		||||
Password = "talkuserpass"
 | 
			
		||||
 | 
			
		||||
# Suffix for Guest Users
 | 
			
		||||
GuestSuffix = " (Guest)"
 | 
			
		||||
 | 
			
		||||
###################################################################
 | 
			
		||||
#
 | 
			
		||||
# Mumble
 | 
			
		||||
#
 | 
			
		||||
###################################################################
 | 
			
		||||
 | 
			
		||||
[mumble.bridge]
 | 
			
		||||
 | 
			
		||||
# Host and port of your Mumble server
 | 
			
		||||
Server = "mumble.yourdomain.me:64738"
 | 
			
		||||
 | 
			
		||||
# Nickname to log in as
 | 
			
		||||
Nick = "matterbridge"
 | 
			
		||||
 | 
			
		||||
# Some servers require a password 
 | 
			
		||||
# OPTIONAL (default empty)
 | 
			
		||||
Password = "serverpasswordhere"
 | 
			
		||||
 | 
			
		||||
# User comment to set on the Mumble user, visible to other users.
 | 
			
		||||
# OPTIONAL (default empty)
 | 
			
		||||
UserComment="I am bridging text messages between this channel and #general on irc.yourdomain.me"
 | 
			
		||||
 | 
			
		||||
# Self-signed TLS client certificate + private key used to connect to
 | 
			
		||||
# Mumble.  This is required if you want to register the matterbridge
 | 
			
		||||
# user on your Mumble server, so its nick becomes reserved.
 | 
			
		||||
# You can generate a keypair using e.g.
 | 
			
		||||
#
 | 
			
		||||
#     openssl req -x509 -newkey rsa:2048 -nodes -days 10000 \
 | 
			
		||||
#             -keyout mumble.key -out mumble.crt
 | 
			
		||||
#
 | 
			
		||||
# To actually register the matterbridege user, connect to Mumble as an
 | 
			
		||||
# admin, right click on the user and click "Register".
 | 
			
		||||
#
 | 
			
		||||
# OPTIONAL (default empty)
 | 
			
		||||
TLSClientCertificate="mumble.crt"
 | 
			
		||||
TLSClientKey="mumble.key"
 | 
			
		||||
 | 
			
		||||
# TLS CA certificate used to validate the Mumble server.
 | 
			
		||||
# OPTIONAL (defaults to Go system CA)
 | 
			
		||||
TLSCACertificate=mumble-ca.crt
 | 
			
		||||
 | 
			
		||||
# Enable to not verify the certificate on your Mumble server.
 | 
			
		||||
# e.g. when using selfsigned certificates
 | 
			
		||||
# OPTIONAL (default false)
 | 
			
		||||
SkipTLSVerify=false
 | 
			
		||||
 | 
			
		||||
###################################################################
 | 
			
		||||
#
 | 
			
		||||
@@ -1594,8 +1451,6 @@ Buffer=1000
 | 
			
		||||
 | 
			
		||||
#Bearer token used for authentication
 | 
			
		||||
#curl -H "Authorization: Bearer token" http://localhost:4242/api/messages
 | 
			
		||||
# https://github.com/vi/websocat
 | 
			
		||||
# websocat -H="Authorization: Bearer token" ws://127.0.0.1:4242/api/websocket
 | 
			
		||||
#OPTIONAL (no authorization if token is empty)
 | 
			
		||||
Token="mytoken"
 | 
			
		||||
 | 
			
		||||
@@ -1625,7 +1480,6 @@ RemoteNickFormat="{NICK}"
 | 
			
		||||
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
 | 
			
		||||
#The string "{GATEWAY}" (case sensitive) will be replaced by the origin gateway name that is replicating the message.
 | 
			
		||||
#The string "{CHANNEL}" (case sensitive) will be replaced by the origin channel name used by the bridge
 | 
			
		||||
#The string "{TENGO}" (case sensitive) will be replaced by the output of the RemoteNickFormat script under [tengo]
 | 
			
		||||
#OPTIONAL (default empty)
 | 
			
		||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
 | 
			
		||||
 | 
			
		||||
@@ -1675,22 +1529,8 @@ MediaDownloadBlacklist=[".html$",".htm$"]
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
IgnoreFailureOnStart=false
 | 
			
		||||
 | 
			
		||||
#LogFile defines the location of a file to write logs into, rather
 | 
			
		||||
#than stdout.
 | 
			
		||||
#Logging will still happen on stdout if the file cannot be open for
 | 
			
		||||
#writing, or if the value is empty. Note that the log won't roll, so
 | 
			
		||||
#you might want to use logrotate(8) with this feature.
 | 
			
		||||
#OPTIONAL (default empty)
 | 
			
		||||
LogFile="/var/log/matterbridge.log"
 | 
			
		||||
 | 
			
		||||
###################################################################
 | 
			
		||||
#Tengo configuration
 | 
			
		||||
###################################################################
 | 
			
		||||
#More information about tengo on: https://github.com/d5/tengo/blob/master/docs/tutorial.md and
 | 
			
		||||
#https://github.com/d5/tengo/blob/master/docs/stdlib.md
 | 
			
		||||
 | 
			
		||||
[tengo]
 | 
			
		||||
#InMessage allows you to specify the location of a tengo (https://github.com/d5/tengo/) script.
 | 
			
		||||
#TengoModifyMessage allows you to specify the location of a tengo (https://github.com/d5/tengo/) script.
 | 
			
		||||
#This script will receive every incoming message and can be used to modify the Username and the Text of that message.
 | 
			
		||||
#The script will have the following global variables:
 | 
			
		||||
#to modify: msgUsername and msgText
 | 
			
		||||
@@ -1707,42 +1547,10 @@ LogFile="/var/log/matterbridge.log"
 | 
			
		||||
#    msgText="replaced by this"
 | 
			
		||||
#    msgUsername="fakeuser"
 | 
			
		||||
#}
 | 
			
		||||
#More information about tengo on: https://github.com/d5/tengo/blob/master/docs/tutorial.md and
 | 
			
		||||
#https://github.com/d5/tengo/blob/master/docs/stdlib.md
 | 
			
		||||
#OPTIONAL (default empty)
 | 
			
		||||
InMessage="example.tengo"
 | 
			
		||||
 | 
			
		||||
#OutMessage allows you to specify the location of the script that
 | 
			
		||||
#will be invoked on each message being sent to a bridge and can be used to modify the Username
 | 
			
		||||
#and the Text of that message.
 | 
			
		||||
#
 | 
			
		||||
#The script will have the following global variables:
 | 
			
		||||
#read-only:
 | 
			
		||||
#inAccount, inProtocol, inChannel, inGateway, inEvent
 | 
			
		||||
#outAccount, outProtocol, outChannel, outGateway, outEvent
 | 
			
		||||
#
 | 
			
		||||
#read-write:
 | 
			
		||||
#msgText, msgUsername
 | 
			
		||||
#
 | 
			
		||||
#The script is reloaded on every message, so you can modify the script on the fly.
 | 
			
		||||
#
 | 
			
		||||
#The default script in https://github.com/42wim/matterbridge/tree/master/internal/tengo/outmessage.tengo
 | 
			
		||||
#is compiled in and will be executed if no script is specified.
 | 
			
		||||
#OPTIONAL (default empty)
 | 
			
		||||
OutMessage="example.tengo"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#RemoteNickFormat allows you to specify the location of a tengo (https://github.com/d5/tengo/) script.
 | 
			
		||||
#The script will have the following global variables:
 | 
			
		||||
#to modify: result
 | 
			
		||||
#to read: channel, bridge, gateway, protocol, nick
 | 
			
		||||
#
 | 
			
		||||
#The result will be set in {TENGO} in the RemoteNickFormat key of every bridge where {TENGO} is specified
 | 
			
		||||
#
 | 
			
		||||
#The script is reloaded on every message, so you can modify the script on the fly.
 | 
			
		||||
#
 | 
			
		||||
#Example script can be found in https://github.com/42wim/matterbridge/tree/master/contrib/remotenickformat.tengo
 | 
			
		||||
#
 | 
			
		||||
#OPTIONAL (default empty)
 | 
			
		||||
RemoteNickFormat="remotenickformat.tengo"
 | 
			
		||||
TengoModifyMessage="example.tengo"
 | 
			
		||||
 | 
			
		||||
###################################################################
 | 
			
		||||
#Gateway configuration
 | 
			
		||||
@@ -1773,48 +1581,31 @@ enable=true
 | 
			
		||||
    # REQUIRED
 | 
			
		||||
    account="irc.freenode"
 | 
			
		||||
 | 
			
		||||
    # The channel key in each gateway is mapped to a similar group chat ID on the chat platform
 | 
			
		||||
    # To find the group chat ID for different platforms, refer to the table below
 | 
			
		||||
    # channel to connect on that account
 | 
			
		||||
    # How to specify them for the different bridges:
 | 
			
		||||
    #
 | 
			
		||||
    # Platform   |   Identifier name  |            Example            | Description
 | 
			
		||||
    # -------------------------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
    #            |      channel       |            general            | Do not include the # symbol
 | 
			
		||||
    #  discord   |    channel id      |          ID:123456789         | See https://github.com/42wim/matterbridge/issues/57
 | 
			
		||||
    #            | category/channel   |          Media/gaming         | Without # symbol. If you're using discord categories to group your channels
 | 
			
		||||
    # -------------------------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
    #   gitter   |  username/room     |            general            | As seen in the gitter.im URL
 | 
			
		||||
    # -------------------------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
    #   hipchat  |    id_channel      |         example needed        | See https://www.hipchat.com/account/xmpp for the correct channel
 | 
			
		||||
    # -------------------------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
    #    irc     |      channel       |            #general           | The # symbol is required and should be lowercase!
 | 
			
		||||
    # -------------------------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
    # mattermost |      channel       |            general            | This is the channel name as seen in the URL, not the display name
 | 
			
		||||
    # -------------------------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
    #   matrix   | #channel:server    |    #yourchannel:matrix.org    | Encrypted rooms are not supported in matrix
 | 
			
		||||
    # -------------------------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
    #   msteams  |      threadId      |    19:82abcxx@thread.skype    | You'll find the threadId in the URL
 | 
			
		||||
    # -------------------------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
    #   mumble   |    channel id      |              42               | The channel ID, as shown in the channel's "Edit" window
 | 
			
		||||
    # -------------------------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
    # rocketchat |      channel       |            #channel           | # is required for private channels too
 | 
			
		||||
    # -------------------------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
    #   slack    |   channel name     |            general            | Do not include the # symbol
 | 
			
		||||
    #            |    channel id      |           ID:C123456          | The underlying ID of a channel. This doesn't work with
 | 
			
		||||
    # -------------------------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
    #   steam    |      chatid        |         example needed        | The number in the URL when you click "enter chat room" in the browser
 | 
			
		||||
    # -------------------------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
    #   nctalk   |      token         |           xs25tz5y            | The token in the URL when you are in a chat. It will be the last part of the URL.
 | 
			
		||||
    # -------------------------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
    #  telegram  |      chatid        |          -123456789           | A large negative number. see https://www.linkedin.com/pulse/telegram-bots-beginners-marco-frau
 | 
			
		||||
    # -------------------------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
    #  whatsapp  |     group JID      | 48111222333-123455678999@g.us | A unique group JID. If you specify an empty string, bridge will list all the possibilities
 | 
			
		||||
    #            |    "Group Name"    |         "Family Chat"         | if you specify a group name, the bridge will find hint the JID to specify. Names can change over time and are not stable.
 | 
			
		||||
    # -------------------------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
    #    xmpp    |      channel       |            general            | The room name
 | 
			
		||||
    # -------------------------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
    #   zulip    | stream/topic:topic |     general/off-topic:food    | Do not use the # when specifying a topic
 | 
			
		||||
    # -------------------------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
    # irc        - #channel (# is required) (this needs to be lowercase!)
 | 
			
		||||
    # mattermost - channel (the channel name as seen in the URL, not the displayname)
 | 
			
		||||
    # gitter     - username/room
 | 
			
		||||
    # xmpp       - channel
 | 
			
		||||
    # slack      - channel (without the #)
 | 
			
		||||
    #            - ID:C123456 (where C123456 is the channel ID) does not work with webhook
 | 
			
		||||
    # discord    - channel (without the #)
 | 
			
		||||
    #            - ID:123456789 (where 123456789 is the channel ID)
 | 
			
		||||
    #               (https://github.com/42wim/matterbridge/issues/57)
 | 
			
		||||
    # telegram   - chatid (a large negative number, eg -123456789)
 | 
			
		||||
    #             see (https://www.linkedin.com/pulse/telegram-bots-beginners-marco-frau)
 | 
			
		||||
    # hipchat    - id_channel (see https://www.hipchat.com/account/xmpp for the correct channel)
 | 
			
		||||
    # rocketchat - #channel (# is required (also needed for private channels!)
 | 
			
		||||
    # matrix     - #channel:server (eg #yourchannel:matrix.org)
 | 
			
		||||
    #            - encrypted rooms are not supported in matrix
 | 
			
		||||
    # steam      - chatid (a large number).
 | 
			
		||||
    #             The number in the URL when you click "enter chat room" in the browser
 | 
			
		||||
    # whatsapp   - 48111222333-123455678999@g.us A unique group JID;
 | 
			
		||||
    #              if you specify an empty string bridge will list all the possibilities
 | 
			
		||||
    #            - "Group Name" if you specify a group name the bridge will hint its JID to specify
 | 
			
		||||
    #              as group names might change in time and contain weird emoticons
 | 
			
		||||
    # zulip      - stream/topic:topicname (without the #)
 | 
			
		||||
    #                  
 | 
			
		||||
    # REQUIRED
 | 
			
		||||
    channel="#testing"
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/mattermost/mattermost-server/v5/model"
 | 
			
		||||
	"github.com/mattermost/mattermost-server/model"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GetChannels returns all channels we're members off
 | 
			
		||||
@@ -36,16 +36,6 @@ func (m *MMClient) GetChannelHeader(channelId string) string { //nolint:golint
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getNormalisedName(channel *model.Channel) string {
 | 
			
		||||
	if channel.Type == model.CHANNEL_GROUP {
 | 
			
		||||
		// (deprecated in favor of ReplaceAll in go 1.12)
 | 
			
		||||
		res := strings.Replace(channel.DisplayName, ", ", "-", -1) //nolint: gocritic
 | 
			
		||||
		res = strings.Replace(res, " ", "_", -1)                   //nolint: gocritic
 | 
			
		||||
		return res
 | 
			
		||||
	}
 | 
			
		||||
	return channel.Name
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MMClient) GetChannelId(name string, teamId string) string { //nolint:golint
 | 
			
		||||
	m.RLock()
 | 
			
		||||
	defer m.RUnlock()
 | 
			
		||||
@@ -55,7 +45,13 @@ func (m *MMClient) GetChannelId(name string, teamId string) string { //nolint:go
 | 
			
		||||
 | 
			
		||||
	for _, t := range m.OtherTeams {
 | 
			
		||||
		for _, channel := range append(t.Channels, t.MoreChannels...) {
 | 
			
		||||
			if getNormalisedName(channel) == name {
 | 
			
		||||
			if channel.Type == model.CHANNEL_GROUP {
 | 
			
		||||
				res := strings.Replace(channel.DisplayName, ", ", "-", -1)
 | 
			
		||||
				res = strings.Replace(res, " ", "_", -1)
 | 
			
		||||
				if res == name {
 | 
			
		||||
					return channel.Id
 | 
			
		||||
				}
 | 
			
		||||
			} else if channel.Name == name {
 | 
			
		||||
				return channel.Id
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@@ -67,7 +63,7 @@ func (m *MMClient) getChannelIdTeam(name string, teamId string) string { //nolin
 | 
			
		||||
	for _, t := range m.OtherTeams {
 | 
			
		||||
		if t.Id == teamId {
 | 
			
		||||
			for _, channel := range append(t.Channels, t.MoreChannels...) {
 | 
			
		||||
				if getNormalisedName(channel) == name {
 | 
			
		||||
				if channel.Name == name {
 | 
			
		||||
					return channel.Id
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
@@ -85,7 +81,12 @@ func (m *MMClient) GetChannelName(channelId string) string { //nolint:golint
 | 
			
		||||
		}
 | 
			
		||||
		for _, channel := range append(t.Channels, t.MoreChannels...) {
 | 
			
		||||
			if channel.Id == channelId {
 | 
			
		||||
				return getNormalisedName(channel)
 | 
			
		||||
				if channel.Type == model.CHANNEL_GROUP {
 | 
			
		||||
					res := strings.Replace(channel.DisplayName, ", ", "-", -1)
 | 
			
		||||
					res = strings.Replace(res, " ", "_", -1)
 | 
			
		||||
					return res
 | 
			
		||||
				}
 | 
			
		||||
				return channel.Name
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -166,42 +167,23 @@ func (m *MMClient) JoinChannel(channelId string) error { //nolint:golint
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MMClient) UpdateChannelsTeam(teamID string) error {
 | 
			
		||||
	mmchannels, resp := m.Client.GetChannelsForTeamForUser(teamID, m.User.Id, false, "")
 | 
			
		||||
	if resp.Error != nil {
 | 
			
		||||
		return errors.New(resp.Error.DetailedError)
 | 
			
		||||
	}
 | 
			
		||||
	for idx, t := range m.OtherTeams {
 | 
			
		||||
		if t.Id == teamID {
 | 
			
		||||
			m.Lock()
 | 
			
		||||
			m.OtherTeams[idx].Channels = mmchannels
 | 
			
		||||
			m.Unlock()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mmchannels, resp = m.Client.GetPublicChannelsForTeam(teamID, 0, 5000, "")
 | 
			
		||||
	if resp.Error != nil {
 | 
			
		||||
		return errors.New(resp.Error.DetailedError)
 | 
			
		||||
	}
 | 
			
		||||
	for idx, t := range m.OtherTeams {
 | 
			
		||||
		if t.Id == teamID {
 | 
			
		||||
			m.Lock()
 | 
			
		||||
			m.OtherTeams[idx].MoreChannels = mmchannels
 | 
			
		||||
			m.Unlock()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MMClient) UpdateChannels() error {
 | 
			
		||||
	if err := m.UpdateChannelsTeam(m.Team.Id); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	for _, t := range m.OtherTeams {
 | 
			
		||||
		if err := m.UpdateChannelsTeam(t.Id); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
	mmchannels, resp := m.Client.GetChannelsForTeamForUser(m.Team.Id, m.User.Id, "")
 | 
			
		||||
	if resp.Error != nil {
 | 
			
		||||
		return errors.New(resp.Error.DetailedError)
 | 
			
		||||
	}
 | 
			
		||||
	m.Lock()
 | 
			
		||||
	m.Team.Channels = mmchannels
 | 
			
		||||
	m.Unlock()
 | 
			
		||||
 | 
			
		||||
	mmchannels, resp = m.Client.GetPublicChannelsForTeam(m.Team.Id, 0, 5000, "")
 | 
			
		||||
	if resp.Error != nil {
 | 
			
		||||
		return errors.New(resp.Error.DetailedError)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m.Lock()
 | 
			
		||||
	m.Team.MoreChannels = mmchannels
 | 
			
		||||
	m.Unlock()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ import (
 | 
			
		||||
 | 
			
		||||
	"github.com/gorilla/websocket"
 | 
			
		||||
	"github.com/jpillora/backoff"
 | 
			
		||||
	"github.com/mattermost/mattermost-server/v5/model"
 | 
			
		||||
	"github.com/mattermost/mattermost-server/model"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (m *MMClient) doLogin(firstConnection bool, b *backoff.Backoff) error {
 | 
			
		||||
@@ -154,7 +154,7 @@ func (m *MMClient) initUser() error {
 | 
			
		||||
 | 
			
		||||
		t := &Team{Team: team, Users: usermap, Id: team.Id}
 | 
			
		||||
 | 
			
		||||
		mmchannels, resp := m.Client.GetChannelsForTeamForUser(team.Id, m.User.Id, false, "")
 | 
			
		||||
		mmchannels, resp := m.Client.GetChannelsForTeamForUser(team.Id, m.User.Id, "")
 | 
			
		||||
		if resp.Error != nil {
 | 
			
		||||
			return resp.Error
 | 
			
		||||
		}
 | 
			
		||||
@@ -186,10 +186,9 @@ func (m *MMClient) serverAlive(firstConnection bool, b *backoff.Backoff) error {
 | 
			
		||||
		if resp.Error != nil {
 | 
			
		||||
			return fmt.Errorf("%#v", resp.Error.Error())
 | 
			
		||||
		}
 | 
			
		||||
		if firstConnection && !m.SkipVersionCheck && !supportedVersion(resp.ServerVersion) {
 | 
			
		||||
		if firstConnection && !supportedVersion(resp.ServerVersion) {
 | 
			
		||||
			return fmt.Errorf("unsupported mattermost version: %s", resp.ServerVersion)
 | 
			
		||||
		}
 | 
			
		||||
		if !m.SkipVersionCheck {
 | 
			
		||||
		m.ServerVersion = resp.ServerVersion
 | 
			
		||||
		if m.ServerVersion == "" {
 | 
			
		||||
			m.logger.Debugf("Server not up yet, reconnecting in %s", d)
 | 
			
		||||
@@ -198,9 +197,6 @@ func (m *MMClient) serverAlive(firstConnection bool, b *backoff.Backoff) error {
 | 
			
		||||
			m.logger.Infof("Found version %s", m.ServerVersion)
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		} else {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ import (
 | 
			
		||||
	lru "github.com/hashicorp/golang-lru"
 | 
			
		||||
	"github.com/jpillora/backoff"
 | 
			
		||||
	prefixed "github.com/matterbridge/logrus-prefixed-formatter"
 | 
			
		||||
	"github.com/mattermost/mattermost-server/v5/model"
 | 
			
		||||
	"github.com/mattermost/mattermost-server/model"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -24,7 +24,6 @@ type Credentials struct {
 | 
			
		||||
	Server        string
 | 
			
		||||
	NoTLS         bool
 | 
			
		||||
	SkipTLSVerify bool
 | 
			
		||||
	SkipVersionCheck bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Message struct {
 | 
			
		||||
@@ -69,7 +68,6 @@ type MMClient struct {
 | 
			
		||||
	logger     *logrus.Entry
 | 
			
		||||
	rootLogger *logrus.Logger
 | 
			
		||||
	lruCache   *lru.Cache
 | 
			
		||||
	allevents  bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New will instantiate a new Matterclient with the specified login details without connecting.
 | 
			
		||||
@@ -120,10 +118,6 @@ func (m *MMClient) SetLogLevel(level string) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MMClient) EnableAllEvents() {
 | 
			
		||||
	m.allevents = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Login tries to connect the client with the loging details with which it was initialized.
 | 
			
		||||
func (m *MMClient) Login() error {
 | 
			
		||||
	// check if this is a first connect or a reconnection
 | 
			
		||||
@@ -225,15 +219,8 @@ func (m *MMClient) WsReceiver() {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if m.allevents {
 | 
			
		||||
				m.MessageChan <- msg
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			switch msg.Raw.Event {
 | 
			
		||||
			case model.WEBSOCKET_EVENT_USER_ADDED,
 | 
			
		||||
				model.WEBSOCKET_EVENT_USER_REMOVED,
 | 
			
		||||
				model.WEBSOCKET_EVENT_CHANNEL_CREATED,
 | 
			
		||||
				model.WEBSOCKET_EVENT_CHANNEL_DELETED:
 | 
			
		||||
			case model.WEBSOCKET_EVENT_USER_ADDED, model.WEBSOCKET_EVENT_USER_REMOVED:
 | 
			
		||||
				m.MessageChan <- msg
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ package matterclient
 | 
			
		||||
import (
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/mattermost/mattermost-server/v5/model"
 | 
			
		||||
	"github.com/mattermost/mattermost-server/model"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (m *MMClient) parseActionPost(rmsg *Message) {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/mattermost/mattermost-server/v5/model"
 | 
			
		||||
	"github.com/mattermost/mattermost-server/model"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (m *MMClient) GetNickName(userId string) string { //nolint:golint
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/gorilla/schema"
 | 
			
		||||
	"github.com/slack-go/slack"
 | 
			
		||||
	"github.com/nlopes/slack"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// OMessage for mattermost incoming webhook. (send to mattermost)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,38 +0,0 @@
 | 
			
		||||
FROM alpine AS builder
 | 
			
		||||
 | 
			
		||||
COPY . /go/src/github.com/42wim/matterbridge
 | 
			
		||||
RUN apk add \
 | 
			
		||||
    go \
 | 
			
		||||
    git \
 | 
			
		||||
    gcc \
 | 
			
		||||
    musl-dev \
 | 
			
		||||
  && cd /go/src/github.com/42wim/matterbridge \
 | 
			
		||||
  && export GOPATH=/go \
 | 
			
		||||
  && go get \
 | 
			
		||||
  && go build -x -ldflags "-X main.githash=$(git log --pretty=format:'%h' -n 1)" -o /bin/matterbridge
 | 
			
		||||
 | 
			
		||||
FROM alpine
 | 
			
		||||
RUN apk --no-cache add \
 | 
			
		||||
    ca-certificates \
 | 
			
		||||
    cairo \
 | 
			
		||||
    libjpeg-turbo \
 | 
			
		||||
    mailcap \
 | 
			
		||||
    py3-webencodings \
 | 
			
		||||
    python3 \
 | 
			
		||||
  && apk --no-cache add --virtual .compile \
 | 
			
		||||
    gcc \
 | 
			
		||||
    libffi-dev \
 | 
			
		||||
    libjpeg-turbo-dev \
 | 
			
		||||
    musl-dev \
 | 
			
		||||
    py3-pip \
 | 
			
		||||
    py3-wheel \
 | 
			
		||||
    python3-dev \
 | 
			
		||||
    zlib-dev \
 | 
			
		||||
  && pip3 install --no-cache-dir lottie[PNG] \
 | 
			
		||||
  && apk --no-cache del .compile
 | 
			
		||||
 | 
			
		||||
COPY --from=builder /bin/matterbridge /bin/matterbridge
 | 
			
		||||
RUN mkdir /etc/matterbridge \
 | 
			
		||||
  && touch /etc/matterbridge/matterbridge.toml \
 | 
			
		||||
  && ln -sf /matterbridge.toml /etc/matterbridge/matterbridge.toml
 | 
			
		||||
ENTRYPOINT ["/bin/matterbridge", "-conf", "/etc/matterbridge/matterbridge.toml"]
 | 
			
		||||
							
								
								
									
										5
									
								
								vendor/github.com/Philipp15b/go-steam/.gitmodules
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								vendor/github.com/Philipp15b/go-steam/.gitmodules
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -1,6 +1,3 @@
 | 
			
		||||
[submodule "generator/SteamKit"]
 | 
			
		||||
	path = generator/SteamKit
 | 
			
		||||
	url = https://github.com/SteamRE/SteamKit.git
 | 
			
		||||
[submodule "generator/Protobufs"]
 | 
			
		||||
	path = generator/Protobufs
 | 
			
		||||
	url = https://github.com/SteamDatabase/Protobufs.git
 | 
			
		||||
	url = https://github.com/Philipp15b/SteamKit.git
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										15
									
								
								vendor/github.com/Philipp15b/go-steam/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								vendor/github.com/Philipp15b/go-steam/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -45,10 +45,19 @@ Whether you want to develop your own Steam bot or directly work on go-steam itse
 | 
			
		||||
 | 
			
		||||
## Updating go-steam to a new SteamKit version
 | 
			
		||||
 | 
			
		||||
Go source code is generated with code in the `generator` directory.
 | 
			
		||||
Look at `generator/README.md` for more information on how to use the generator.
 | 
			
		||||
To update go-steam to a new version of SteamKit, do the following:
 | 
			
		||||
 | 
			
		||||
Then, after generating new Go source files, update `go-steam` as necessary.
 | 
			
		||||
	go get github.com/golang/protobuf/protoc-gen-go/
 | 
			
		||||
    git submodule init && git submodule update
 | 
			
		||||
    cd generator
 | 
			
		||||
    go run generator.go clean proto steamlang
 | 
			
		||||
 | 
			
		||||
Make sure that `$GOPATH/bin` / `protoc-gen-go` is in your `$PATH`. You'll also need [`protoc`](https://developers.google.com/protocol-buffers/docs/downloads), the protocol buffer compiler. At the moment, we use Protocol Buffers 2.6.1 with `proco-gen-go`-[2402d76](https://github.com/golang/protobuf/tree/2402d76f3d41f928c7902a765dfc872356dd3aad).
 | 
			
		||||
 | 
			
		||||
To compile the Steam Language files, you also need the [.NET Framework](https://www.microsoft.com/net/downloads)
 | 
			
		||||
on Windows or [mono](http://www.go-mono.com/mono-downloads/download.html) on other operating systems.
 | 
			
		||||
 | 
			
		||||
Apply the protocol changes where necessary.
 | 
			
		||||
 | 
			
		||||
## License
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user