Compare commits
1 Commits
v1.24.1
...
addtengodo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1c732b87a |
17
.github/workflows/development.yml
vendored
@@ -11,12 +11,12 @@ jobs:
|
|||||||
- name: Run golangci-lint
|
- name: Run golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v2
|
uses: golangci/golangci-lint-action@v2
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: v1.29
|
||||||
args: "-v --new-from-rev HEAD~5"
|
args: "-v --new-from-rev HEAD~5"
|
||||||
test-build-upload:
|
test-build-upload:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.17.x]
|
go-version: [1.14.x, 1.15.x]
|
||||||
platform: [ubuntu-latest]
|
platform: [ubuntu-latest]
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
steps:
|
steps:
|
||||||
@@ -24,7 +24,6 @@ jobs:
|
|||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
stable: false
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
@@ -35,23 +34,23 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
mkdir -p output/{win,lin,arm,mac}
|
mkdir -p output/{win,lin,arm,mac}
|
||||||
VERSION=$(git describe --tags)
|
VERSION=$(git describe --tags)
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -X github.com/42wim/matterbridge/version.GitHash=$(git log --pretty=format:'%h' -n 1)" -o output/lin/matterbridge-$VERSION-linux-amd64
|
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
|
||||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-s -X github.com/42wim/matterbridge/version.GitHash=$(git log --pretty=format:'%h' -n 1)" -o output/win/matterbridge-$VERSION-windows-amd64.exe
|
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
|
||||||
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-s -X github.com/42wim/matterbridge/version.GitHash=$(git log --pretty=format:'%h' -n 1)" -o output/mac/matterbridge-$VERSION-darwin-amd64
|
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
|
- name: Upload linux 64-bit
|
||||||
if: startsWith(matrix.go-version,'1.17')
|
if: startsWith(matrix.go-version,'1.15')
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: matterbridge-linux-64bit
|
name: matterbridge-linux-64bit
|
||||||
path: output/lin
|
path: output/lin
|
||||||
- name: Upload windows 64-bit
|
- name: Upload windows 64-bit
|
||||||
if: startsWith(matrix.go-version,'1.17')
|
if: startsWith(matrix.go-version,'1.15')
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: matterbridge-windows-64bit
|
name: matterbridge-windows-64bit
|
||||||
path: output/win
|
path: output/win
|
||||||
- name: Upload darwin 64-bit
|
- name: Upload darwin 64-bit
|
||||||
if: startsWith(matrix.go-version,'1.17')
|
if: startsWith(matrix.go-version,'1.15')
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: matterbridge-darwin-64bit
|
name: matterbridge-darwin-64bit
|
||||||
|
|||||||
68
.github/workflows/docker.yml
vendored
@@ -1,68 +0,0 @@
|
|||||||
name: docker
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- 'master'
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- 'master'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
docker:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
-
|
|
||||||
name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
-
|
|
||||||
name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
with:
|
|
||||||
platforms: amd64,arm64
|
|
||||||
-
|
|
||||||
name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
-
|
|
||||||
name: Docker meta
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v3
|
|
||||||
with:
|
|
||||||
images: 42wim/matterbridge,ghcr.io/42wim/matterbridge
|
|
||||||
flavor: |
|
|
||||||
latest=true
|
|
||||||
tags: |
|
|
||||||
type=ref,event=branch
|
|
||||||
type=ref,event=pr
|
|
||||||
type=semver,pattern={{version}}
|
|
||||||
type=semver,pattern=stable
|
|
||||||
type=semver,pattern={{major}}
|
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
|
||||||
-
|
|
||||||
name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
-
|
|
||||||
name: Log into registry ghcr.io
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
-
|
|
||||||
name: Build and push
|
|
||||||
id: docker_build
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
|
|
||||||
3
.gitignore
vendored
@@ -4,6 +4,3 @@
|
|||||||
|
|
||||||
# Exclude configuration file
|
# Exclude configuration file
|
||||||
matterbridge.toml
|
matterbridge.toml
|
||||||
|
|
||||||
# Exclude IDE Files
|
|
||||||
.vscode
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ run:
|
|||||||
# concurrency: 4
|
# concurrency: 4
|
||||||
|
|
||||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||||
deadline: 5m
|
deadline: 2m
|
||||||
|
|
||||||
# exit code when at least one issue was found, default is 1
|
# exit code when at least one issue was found, default is 1
|
||||||
issues-exit-code: 1
|
issues-exit-code: 1
|
||||||
@@ -91,6 +91,7 @@ linters-settings:
|
|||||||
# Correct spellings using locale preferences for US or UK.
|
# Correct spellings using locale preferences for US or UK.
|
||||||
# Default is to use a neutral variety of English.
|
# Default is to use a neutral variety of English.
|
||||||
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
|
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
|
||||||
|
locale: US
|
||||||
lll:
|
lll:
|
||||||
# max line length, lines longer will be reported. Default is 120.
|
# max line length, lines longer will be reported. Default is 120.
|
||||||
# '\t' is counted as 1 character by default, and can be changed with the tab-width option
|
# '\t' is counted as 1 character by default, and can be changed with the tab-width option
|
||||||
@@ -182,28 +183,7 @@ linters:
|
|||||||
- interfacer
|
- interfacer
|
||||||
- goheader
|
- goheader
|
||||||
- noctx
|
- noctx
|
||||||
- gci
|
|
||||||
- errorlint
|
|
||||||
- nlreturn
|
|
||||||
- exhaustivestruct
|
|
||||||
- forbidigo
|
|
||||||
- wrapcheck
|
|
||||||
- varnamelen
|
|
||||||
- ireturn
|
|
||||||
- errorlint
|
|
||||||
- tparallel
|
|
||||||
- wrapcheck
|
|
||||||
- paralleltest
|
|
||||||
- makezero
|
|
||||||
- thelper
|
|
||||||
- cyclop
|
|
||||||
- revive
|
|
||||||
- importas
|
|
||||||
- gomoddirectives
|
|
||||||
- promlinter
|
|
||||||
- tagliatelle
|
|
||||||
- errname
|
|
||||||
- typecheck
|
|
||||||
# rules to deal with reported isues
|
# rules to deal with reported isues
|
||||||
issues:
|
issues:
|
||||||
# List of regexps of issue texts to exclude, empty list by default.
|
# List of regexps of issue texts to exclude, empty list by default.
|
||||||
|
|||||||
@@ -18,11 +18,8 @@ builds:
|
|||||||
- arm
|
- arm
|
||||||
- arm64
|
- arm64
|
||||||
- 386
|
- 386
|
||||||
goarm:
|
|
||||||
- 6
|
|
||||||
- 7
|
|
||||||
ldflags:
|
ldflags:
|
||||||
- -s -w -X github.com/42wim/matterbridge/version.GitHash={{.ShortCommit}}
|
- -s -w -X main.githash={{.ShortCommit}}
|
||||||
|
|
||||||
archives:
|
archives:
|
||||||
-
|
-
|
||||||
|
|||||||
10
Dockerfile
@@ -1,9 +1,11 @@
|
|||||||
FROM alpine AS builder
|
FROM alpine AS builder
|
||||||
|
|
||||||
COPY . /go/src/matterbridge
|
COPY . /go/src/github.com/42wim/matterbridge
|
||||||
RUN apk --no-cache add go git \
|
RUN apk update && apk add go git gcc musl-dev \
|
||||||
&& cd /go/src/matterbridge \
|
&& cd /go/src/github.com/42wim/matterbridge \
|
||||||
&& CGO_ENABLED=0 go build -mod vendor -ldflags "-X github.com/42wim/matterbridge/version.GitHash=$(git log --pretty=format:'%h' -n 1)" -o /bin/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
|
FROM alpine
|
||||||
RUN apk --no-cache add ca-certificates mailcap
|
RUN apk --no-cache add ca-certificates mailcap
|
||||||
|
|||||||
57
README.md
@@ -67,11 +67,10 @@ And more...
|
|||||||
- [Bridge slack (#general) - discord (general)](#bridge-slack-general---discord-general)
|
- [Bridge slack (#general) - discord (general)](#bridge-slack-general---discord-general)
|
||||||
- [Running](#running)
|
- [Running](#running)
|
||||||
- [Docker](#docker)
|
- [Docker](#docker)
|
||||||
- [Systemd](#systemd)
|
|
||||||
- [Changelog](#changelog)
|
- [Changelog](#changelog)
|
||||||
- [FAQ](#faq)
|
- [FAQ](#faq)
|
||||||
- [Related projects](#related-projects)
|
- [Related projects](#related-projects)
|
||||||
- [Articles / Tutorials](#articles--tutorials)
|
- [Articles](#articles)
|
||||||
- [Thanks](#thanks)
|
- [Thanks](#thanks)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@@ -92,18 +91,16 @@ And more...
|
|||||||
- [IRC](http://www.mirc.com/servers.html)
|
- [IRC](http://www.mirc.com/servers.html)
|
||||||
- [Keybase](https://keybase.io)
|
- [Keybase](https://keybase.io)
|
||||||
- [Matrix](https://matrix.org)
|
- [Matrix](https://matrix.org)
|
||||||
- [Mattermost](https://github.com/mattermost/mattermost-server/)
|
- [Mattermost](https://github.com/mattermost/mattermost-server/) 4.x, 5.x
|
||||||
- [Microsoft Teams](https://teams.microsoft.com)
|
- [Microsoft Teams](https://teams.microsoft.com)
|
||||||
- [Mumble](https://www.mumble.info/)
|
- [Mumble](https://www.mumble.info/)
|
||||||
- [Nextcloud Talk](https://nextcloud.com/talk/)
|
- [Nextcloud Talk](https://nextcloud.com/talk/)
|
||||||
- [Rocket.chat](https://rocket.chat)
|
- [Rocket.chat](https://rocket.chat)
|
||||||
- [Slack](https://slack.com)
|
- [Slack](https://slack.com)
|
||||||
- [Ssh-chat](https://github.com/shazow/ssh-chat)
|
- [Ssh-chat](https://github.com/shazow/ssh-chat)
|
||||||
- ~~[Steam](https://store.steampowered.com/)~~
|
- [Steam](https://store.steampowered.com/)
|
||||||
- Not supported anymore, see [here](https://github.com/Philipp15b/go-steam/issues/94) for more info.
|
|
||||||
- [Telegram](https://telegram.org)
|
- [Telegram](https://telegram.org)
|
||||||
- [Twitch](https://twitch.tv)
|
- [Twitch](https://twitch.tv)
|
||||||
- [VK](https://vk.com/)
|
|
||||||
- [WhatsApp](https://www.whatsapp.com/)
|
- [WhatsApp](https://www.whatsapp.com/)
|
||||||
- [XMPP](https://xmpp.org)
|
- [XMPP](https://xmpp.org)
|
||||||
- [Zulip](https://zulipchat.com)
|
- [Zulip](https://zulipchat.com)
|
||||||
@@ -111,15 +108,11 @@ And more...
|
|||||||
### 3rd party via matterbridge api
|
### 3rd party via matterbridge api
|
||||||
|
|
||||||
- [Discourse](https://github.com/DeclanHoare/matterbabble)
|
- [Discourse](https://github.com/DeclanHoare/matterbabble)
|
||||||
- [Facebook messenger](https://github.com/powerjungle/fbridge-asyncio)
|
|
||||||
- [Facebook messenger](https://github.com/VictorNine/fbridge)
|
- [Facebook messenger](https://github.com/VictorNine/fbridge)
|
||||||
- [Minecraft](https://github.com/elytra/MatterLink)
|
- [Minecraft](https://github.com/elytra/MatterLink)
|
||||||
- [Minecraft](https://github.com/raws/mattercraft)
|
|
||||||
- [Minecraft](https://gitlab.com/Programie/MatterBukkit)
|
|
||||||
- [Reddit](https://github.com/bonehurtingjuice/mattereddit)
|
- [Reddit](https://github.com/bonehurtingjuice/mattereddit)
|
||||||
- [Counter-Strike, half-life and more](https://forums.alliedmods.net/showthread.php?t=319430)
|
- [Counter-Strike, half-life and more](https://forums.alliedmods.net/showthread.php?t=319430)
|
||||||
- [MatterAMXX](https://github.com/GabeIggy/MatterAMXX)
|
- [MatterAMXX](https://github.com/GabeIggy/MatterAMXX)
|
||||||
- [Vintage Story](https://github.com/NikkyAI/vs-matterbridge)
|
|
||||||
|
|
||||||
### API
|
### API
|
||||||
|
|
||||||
@@ -128,16 +121,12 @@ More info and examples on the [wiki](https://github.com/42wim/matterbridge/wiki/
|
|||||||
|
|
||||||
Used by the projects below. Feel free to make a PR to add your project to this list.
|
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 Forge server chat, archived)
|
- [MatterLink](https://github.com/elytra/MatterLink) (Matterbridge link for Minecraft Server chat)
|
||||||
- [MatterCraft](https://github.com/raws/mattercraft) (Matterbridge link for Minecraft Forge server chat)
|
|
||||||
- [MatterBukkit](https://gitlab.com/Programie/MatterBukkit) (Matterbridge link for Minecraft Bukkit/Spigot server chat)
|
|
||||||
- [pyCord](https://github.com/NikkyAI/pyCord) (crossplatform chatbot)
|
- [pyCord](https://github.com/NikkyAI/pyCord) (crossplatform chatbot)
|
||||||
- [Mattereddit](https://github.com/bonehurtingjuice/mattereddit) (Reddit chat support)
|
- [Mattereddit](https://github.com/bonehurtingjuice/mattereddit) (Reddit chat support)
|
||||||
- [fbridge-asyncio](https://github.com/powerjungle/fbridge-asyncio) (Facebook messenger support)
|
|
||||||
- [fbridge](https://github.com/VictorNine/fbridge) (Facebook messenger support)
|
- [fbridge](https://github.com/VictorNine/fbridge) (Facebook messenger support)
|
||||||
- [matterbabble](https://github.com/DeclanHoare/matterbabble) (Discourse support)
|
- [matterbabble](https://github.com/DeclanHoare/matterbabble) (Discourse support)
|
||||||
- [MatterAMXX](https://forums.alliedmods.net/showthread.php?t=319430) (Counter-Strike, half-life and more via AMXX mod)
|
- [MatterAMXX](https://forums.alliedmods.net/showthread.php?t=319430) (Counter-Strike, half-life and more via AMXX mod)
|
||||||
- [Vintage Story](https://github.com/NikkyAI/vs-matterbridge)
|
|
||||||
|
|
||||||
## Chat with us
|
## Chat with us
|
||||||
|
|
||||||
@@ -164,34 +153,25 @@ See <https://github.com/42wim/matterbridge/wiki>
|
|||||||
|
|
||||||
### Binaries
|
### Binaries
|
||||||
|
|
||||||
- Latest stable release [v1.24.1](https://github.com/42wim/matterbridge/releases/latest)
|
- Latest stable release [v1.20.0](https://github.com/42wim/matterbridge/releases/latest)
|
||||||
- Development releases (follows master) can be downloaded [here](https://github.com/42wim/matterbridge/actions) selecting the latest green build and then artifacts.
|
- 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). On \*nix platforms you may need to make the binary executable - you can do this by running `chmod a+x` on the binary (example: `chmod a+x matterbridge-1.20.0-linux-64bit`). After downloading (and making the binary executable, if necessary), follow the instructions on the [howto](https://github.com/42wim/matterbridge/wiki/How-to-create-your-config) for a step by step walkthrough for creating your configuration.
|
To install or upgrade just download the latest [binary](https://github.com/42wim/matterbridge/releases/latest) and follow the instructions on the [howto](https://github.com/42wim/matterbridge/wiki/How-to-create-your-config) for a step by step walkthrough for creating your configuration.
|
||||||
|
|
||||||
### Packages
|
### Packages
|
||||||
|
|
||||||
- [Overview](https://repology.org/metapackage/matterbridge/versions)
|
- [Overview](https://repology.org/metapackage/matterbridge/versions)
|
||||||
- [snap](https://snapcraft.io/matterbridge)
|
- [snap](https://snapcraft.io/matterbridge)
|
||||||
- [scoop](https://github.com/42wim/scoop-bucket)
|
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
Most people just want to use binaries, you can find those [here](https://github.com/42wim/matterbridge/releases/latest)
|
Most people just want to use binaries, you can find those [here](https://github.com/42wim/matterbridge/releases/latest)
|
||||||
|
|
||||||
If you really want to build from source, follow these instructions:
|
If you really want to build from source, follow these instructions:
|
||||||
Go 1.17+ is required. Make sure you have [Go](https://golang.org/doc/install) properly installed.
|
Go 1.12+ is required. Make sure you have [Go](https://golang.org/doc/install) properly installed.
|
||||||
|
|
||||||
To install the latest stable run:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go install github.com/42wim/matterbridge@v1.24.1
|
go get github.com/42wim/matterbridge
|
||||||
```
|
|
||||||
|
|
||||||
To install the latest dev run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go install github.com/42wim/matterbridge@latest
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You should now have matterbridge binary in the ~/go/bin directory:
|
You should now have matterbridge binary in the ~/go/bin directory:
|
||||||
@@ -221,8 +201,8 @@ All possible [settings](https://github.com/42wim/matterbridge/wiki/Settings) for
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[irc]
|
[irc]
|
||||||
[irc.libera]
|
[irc.freenode]
|
||||||
Server="irc.libera.chat:6667"
|
Server="irc.freenode.net:6667"
|
||||||
Nick="yourbotname"
|
Nick="yourbotname"
|
||||||
|
|
||||||
[mattermost]
|
[mattermost]
|
||||||
@@ -238,7 +218,7 @@ All possible [settings](https://github.com/42wim/matterbridge/wiki/Settings) for
|
|||||||
name="mygateway"
|
name="mygateway"
|
||||||
enable=true
|
enable=true
|
||||||
[[gateway.inout]]
|
[[gateway.inout]]
|
||||||
account="irc.libera"
|
account="irc.freenode"
|
||||||
channel="#testing"
|
channel="#testing"
|
||||||
|
|
||||||
[[gateway.inout]]
|
[[gateway.inout]]
|
||||||
@@ -295,10 +275,6 @@ Usage of ./matterbridge:
|
|||||||
|
|
||||||
Please take a look at the [Docker Wiki page](https://github.com/42wim/matterbridge/wiki/Deploy:-Docker) for more information.
|
Please take a look at the [Docker Wiki page](https://github.com/42wim/matterbridge/wiki/Deploy:-Docker) for more information.
|
||||||
|
|
||||||
### Systemd
|
|
||||||
|
|
||||||
Please take a look at the [Service Files page](https://github.com/42wim/matterbridge/wiki/Service-files) for more information.
|
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
See [changelog.md](https://github.com/42wim/matterbridge/blob/master/changelog.md)
|
See [changelog.md](https://github.com/42wim/matterbridge/blob/master/changelog.md)
|
||||||
@@ -320,11 +296,8 @@ See [FAQ](https://github.com/42wim/matterbridge/wiki/FAQ)
|
|||||||
- [fbridge](https://github.com/VictorNine/fbridge) (Facebook messenger support)
|
- [fbridge](https://github.com/VictorNine/fbridge) (Facebook messenger support)
|
||||||
- [isla](https://github.com/alphachung/isla) (Bot for Discord-Telegram groups used alongside matterbridge)
|
- [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)
|
- [matterbabble](https://github.com/DeclanHoare/matterbabble) (Connect Discourse threads to Matterbridge)
|
||||||
- [nextcloud talk](https://github.com/nextcloud/talk_matterbridge) (Integrates matterbridge in Nextcloud Talk)
|
|
||||||
- [mattercraft](https://github.com/raws/mattercraft) (Minecraft bridge)
|
|
||||||
- [vs-matterbridge](https://github.com/NikkyAI/vs-matterbridge) (Vintage Story bridge)
|
|
||||||
|
|
||||||
## Articles / Tutorials
|
## Articles
|
||||||
|
|
||||||
- [matterbridge on kubernetes](https://medium.freecodecamp.org/using-kubernetes-to-deploy-a-chat-gateway-or-when-technology-works-like-its-supposed-to-a169a8cd69a3)
|
- [matterbridge on kubernetes](https://medium.freecodecamp.org/using-kubernetes-to-deploy-a-chat-gateway-or-when-technology-works-like-its-supposed-to-a169a8cd69a3)
|
||||||
- <https://mattermost.com/blog/connect-irc-to-mattermost/>
|
- <https://mattermost.com/blog/connect-irc-to-mattermost/>
|
||||||
@@ -335,9 +308,6 @@ See [FAQ](https://github.com/42wim/matterbridge/wiki/FAQ)
|
|||||||
- <https://www.stitcher.com/s/?eid=52382713>
|
- <https://www.stitcher.com/s/?eid=52382713>
|
||||||
- <https://daniele.tech/2019/02/how-to-use-matterbridge-to-connect-2-different-slack-workspaces/>
|
- <https://daniele.tech/2019/02/how-to-use-matterbridge-to-connect-2-different-slack-workspaces/>
|
||||||
- <https://userlinux.net/mattermost-and-matterbridge.html>
|
- <https://userlinux.net/mattermost-and-matterbridge.html>
|
||||||
- <https://nextcloud.com/blog/bridging-chat-services-in-talk/>
|
|
||||||
- <https://minecraftchest1.wordpress.com/2021/06/05/how-to-install-and-setup-matterbridge/>
|
|
||||||
- Youtube: [whatsapp - telegram bridging](https://www.youtube.com/watch?v=W-VXISoKtNc)
|
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|
|
||||||
@@ -368,7 +338,6 @@ Matterbridge wouldn't exist without these libraries:
|
|||||||
- steam - <https://github.com/Philipp15b/go-steam>
|
- steam - <https://github.com/Philipp15b/go-steam>
|
||||||
- telegram - <https://github.com/go-telegram-bot-api/telegram-bot-api>
|
- telegram - <https://github.com/go-telegram-bot-api/telegram-bot-api>
|
||||||
- tengo - <https://github.com/d5/tengo>
|
- tengo - <https://github.com/d5/tengo>
|
||||||
- vk - <https://github.com/SevereCloud/vksdk>
|
|
||||||
- whatsapp - <https://github.com/Rhymen/go-whatsapp>
|
- whatsapp - <https://github.com/Rhymen/go-whatsapp>
|
||||||
- xmpp - <https://github.com/mattn/go-xmpp>
|
- xmpp - <https://github.com/mattn/go-xmpp>
|
||||||
- zulip - <https://github.com/ifo/gozulipbot>
|
- zulip - <https://github.com/ifo/gozulipbot>
|
||||||
@@ -377,7 +346,7 @@ Matterbridge wouldn't exist without these libraries:
|
|||||||
|
|
||||||
[mb-discord]: https://discord.gg/AkKPtrQ
|
[mb-discord]: https://discord.gg/AkKPtrQ
|
||||||
[mb-gitter]: https://gitter.im/42wim/matterbridge
|
[mb-gitter]: https://gitter.im/42wim/matterbridge
|
||||||
[mb-irc]: https://web.libera.chat/#matterbridge
|
[mb-irc]: https://webchat.freenode.net/?channels=matterbridgechat
|
||||||
[mb-keybase]: https://keybase.io/team/matterbridge
|
[mb-keybase]: https://keybase.io/team/matterbridge
|
||||||
[mb-matrix]: https://riot.im/app/#/room/#matterbridge:matrix.org
|
[mb-matrix]: https://riot.im/app/#/room/#matterbridge:matrix.org
|
||||||
[mb-mattermost]: https://framateam.org/signup_user_complete/?id=tfqm33ggop8x3qgu4boeieta6e
|
[mb-mattermost]: https://framateam.org/signup_user_complete/?id=tfqm33ggop8x3qgu4boeieta6e
|
||||||
|
|||||||
@@ -23,15 +23,12 @@ const (
|
|||||||
EventRejoinChannels = "rejoin_channels"
|
EventRejoinChannels = "rejoin_channels"
|
||||||
EventUserAction = "user_action"
|
EventUserAction = "user_action"
|
||||||
EventMsgDelete = "msg_delete"
|
EventMsgDelete = "msg_delete"
|
||||||
EventFileDelete = "file_delete"
|
|
||||||
EventAPIConnected = "api_connected"
|
EventAPIConnected = "api_connected"
|
||||||
EventUserTyping = "user_typing"
|
EventUserTyping = "user_typing"
|
||||||
EventGetChannelMembers = "get_channel_members"
|
EventGetChannelMembers = "get_channel_members"
|
||||||
EventNoticeIRC = "notice_irc"
|
EventNoticeIRC = "notice_irc"
|
||||||
)
|
)
|
||||||
|
|
||||||
const ParentIDNotFound = "msg-parent-not-found"
|
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
Channel string `json:"channel"`
|
Channel string `json:"channel"`
|
||||||
@@ -48,23 +45,14 @@ type Message struct {
|
|||||||
Extra map[string][]interface{}
|
Extra map[string][]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Message) ParentNotFound() bool {
|
|
||||||
return m.ParentID == ParentIDNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Message) ParentValid() bool {
|
|
||||||
return m.ParentID != "" && !m.ParentNotFound()
|
|
||||||
}
|
|
||||||
|
|
||||||
type FileInfo struct {
|
type FileInfo struct {
|
||||||
Name string
|
Name string
|
||||||
Data *[]byte
|
Data *[]byte
|
||||||
Comment string
|
Comment string
|
||||||
URL string
|
URL string
|
||||||
Size int64
|
Size int64
|
||||||
Avatar bool
|
Avatar bool
|
||||||
SHA string
|
SHA string
|
||||||
NativeID string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChannelInfo struct {
|
type ChannelInfo struct {
|
||||||
@@ -87,28 +75,27 @@ type ChannelMember struct {
|
|||||||
type ChannelMembers []ChannelMember
|
type ChannelMembers []ChannelMember
|
||||||
|
|
||||||
type Protocol struct {
|
type Protocol struct {
|
||||||
AllowMention []string // discord
|
AuthCode string // steam
|
||||||
AuthCode string // steam
|
BindAddress string // mattermost, slack // DEPRECATED
|
||||||
BindAddress string // mattermost, slack // DEPRECATED
|
Buffer int // api
|
||||||
Buffer int // api
|
Charset string // irc
|
||||||
Charset string // irc
|
ClientID string // msteams
|
||||||
ClientID string // msteams
|
ColorNicks bool // only irc for now
|
||||||
ColorNicks bool // only irc for now
|
Debug bool // general
|
||||||
Debug bool // general
|
DebugLevel int // only for irc now
|
||||||
DebugLevel int // only for irc now
|
DisableWebPagePreview bool // telegram
|
||||||
DisableWebPagePreview bool // telegram
|
EditSuffix string // mattermost, slack, discord, telegram, gitter
|
||||||
EditSuffix string // mattermost, slack, discord, telegram, gitter
|
EditDisable bool // mattermost, slack, discord, telegram, gitter
|
||||||
EditDisable bool // mattermost, slack, discord, telegram, gitter
|
HTMLDisable bool // matrix
|
||||||
HTMLDisable bool // matrix
|
IconURL string // mattermost, slack
|
||||||
IconURL string // mattermost, slack
|
IgnoreFailureOnStart bool // general
|
||||||
IgnoreFailureOnStart bool // general
|
IgnoreNicks string // all protocols
|
||||||
IgnoreNicks string // all protocols
|
IgnoreMessages string // all protocols
|
||||||
IgnoreMessages string // all protocols
|
Jid string // xmpp
|
||||||
Jid string // xmpp
|
JoinDelay string // all protocols
|
||||||
JoinDelay string // all protocols
|
Label string // all protocols
|
||||||
Label string // all protocols
|
Login string // mattermost, matrix
|
||||||
Login string // mattermost, matrix
|
LogFile string // general
|
||||||
LogFile string // general
|
|
||||||
MediaDownloadBlackList []string
|
MediaDownloadBlackList []string
|
||||||
MediaDownloadPath string // Basically MediaServerUpload, but instead of uploading it, just write it to a file on the same server.
|
MediaDownloadPath string // Basically MediaServerUpload, but instead of uploading it, just write it to a file on the same server.
|
||||||
MediaDownloadSize int // all protocols
|
MediaDownloadSize int // all protocols
|
||||||
@@ -122,7 +109,6 @@ type Protocol struct {
|
|||||||
MessageQueue int // IRC, size of message queue for flood control
|
MessageQueue int // IRC, size of message queue for flood control
|
||||||
MessageSplit bool // IRC, split long messages with newlines on MessageLength instead of clipping
|
MessageSplit bool // IRC, split long messages with newlines on MessageLength instead of clipping
|
||||||
Muc string // xmpp
|
Muc string // xmpp
|
||||||
MxID string // matrix
|
|
||||||
Name string // all protocols
|
Name string // all protocols
|
||||||
Nick string // all protocols
|
Nick string // all protocols
|
||||||
NickFormatter string // mattermost, slack
|
NickFormatter string // mattermost, slack
|
||||||
@@ -140,13 +126,12 @@ type Protocol struct {
|
|||||||
QuoteDisable bool // telegram
|
QuoteDisable bool // telegram
|
||||||
QuoteFormat string // telegram
|
QuoteFormat string // telegram
|
||||||
QuoteLengthLimit int // telegram
|
QuoteLengthLimit int // telegram
|
||||||
RealName string // IRC
|
|
||||||
RejoinDelay int // IRC
|
RejoinDelay int // IRC
|
||||||
ReplaceMessages [][]string // all protocols
|
ReplaceMessages [][]string // all protocols
|
||||||
ReplaceNicks [][]string // all protocols
|
ReplaceNicks [][]string // all protocols
|
||||||
RemoteNickFormat string // all protocols
|
RemoteNickFormat string // all protocols
|
||||||
RunCommands []string // IRC
|
RunCommands []string // IRC
|
||||||
Server string // IRC,mattermost,XMPP,discord,matrix
|
Server string // IRC,mattermost,XMPP,discord
|
||||||
SessionFile string // msteams,whatsapp
|
SessionFile string // msteams,whatsapp
|
||||||
ShowJoinPart bool // all protocols
|
ShowJoinPart bool // all protocols
|
||||||
ShowTopicChange bool // slack
|
ShowTopicChange bool // slack
|
||||||
@@ -161,7 +146,7 @@ type Protocol struct {
|
|||||||
Team string // mattermost, keybase
|
Team string // mattermost, keybase
|
||||||
TeamID string // msteams
|
TeamID string // msteams
|
||||||
TenantID string // msteams
|
TenantID string // msteams
|
||||||
Token string // gitter, slack, discord, api, matrix
|
Token string // gitter, slack, discord, api
|
||||||
Topic string // zulip
|
Topic string // zulip
|
||||||
URL string // mattermost, slack // DEPRECATED
|
URL string // mattermost, slack // DEPRECATED
|
||||||
UseAPI bool // mattermost, slack
|
UseAPI bool // mattermost, slack
|
||||||
@@ -170,9 +155,8 @@ type Protocol struct {
|
|||||||
UseTLS bool // IRC
|
UseTLS bool // IRC
|
||||||
UseDiscriminator bool // discord
|
UseDiscriminator bool // discord
|
||||||
UseFirstName bool // telegram
|
UseFirstName bool // telegram
|
||||||
UseUserName bool // discord, matrix, mattermost
|
UseUserName bool // discord, matrix
|
||||||
UseInsecureURL bool // telegram
|
UseInsecureURL bool // telegram
|
||||||
UserName string // IRC
|
|
||||||
VerboseJoinPart bool // IRC
|
VerboseJoinPart bool // IRC
|
||||||
WebhookBindAddress string // mattermost, slack
|
WebhookBindAddress string // mattermost, slack
|
||||||
WebhookURL string // mattermost, slack
|
WebhookURL string // mattermost, slack
|
||||||
|
|||||||
@@ -2,31 +2,30 @@ package bdiscord
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/42wim/matterbridge/bridge"
|
"github.com/42wim/matterbridge/bridge"
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/discord/transmitter"
|
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/matterbridge/discordgo"
|
||||||
lru "github.com/hashicorp/golang-lru"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const MessageLength = 1950
|
||||||
MessageLength = 1950
|
|
||||||
cFileUpload = "file_upload"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Bdiscord struct {
|
type Bdiscord struct {
|
||||||
*bridge.Config
|
*bridge.Config
|
||||||
|
|
||||||
c *discordgo.Session
|
c *discordgo.Session
|
||||||
|
|
||||||
nick string
|
nick string
|
||||||
userID string
|
userID string
|
||||||
guildID string
|
guildID string
|
||||||
|
webhookID string
|
||||||
|
webhookToken string
|
||||||
|
canEditWebhooks bool
|
||||||
|
|
||||||
channelsMutex sync.RWMutex
|
channelsMutex sync.RWMutex
|
||||||
channels []*discordgo.Channel
|
channels []*discordgo.Channel
|
||||||
@@ -35,39 +34,30 @@ type Bdiscord struct {
|
|||||||
membersMutex sync.RWMutex
|
membersMutex sync.RWMutex
|
||||||
userMemberMap map[string]*discordgo.Member
|
userMemberMap map[string]*discordgo.Member
|
||||||
nickMemberMap map[string]*discordgo.Member
|
nickMemberMap map[string]*discordgo.Member
|
||||||
|
|
||||||
// Webhook specific logic
|
|
||||||
useAutoWebhooks bool
|
|
||||||
transmitter *transmitter.Transmitter
|
|
||||||
cache *lru.Cache
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(cfg *bridge.Config) bridge.Bridger {
|
func New(cfg *bridge.Config) bridge.Bridger {
|
||||||
newCache, err := lru.New(5000)
|
b := &Bdiscord{Config: cfg}
|
||||||
if err != nil {
|
|
||||||
cfg.Log.Fatalf("Could not create LRU cache: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b := &Bdiscord{
|
|
||||||
Config: cfg,
|
|
||||||
cache: newCache,
|
|
||||||
}
|
|
||||||
|
|
||||||
b.userMemberMap = make(map[string]*discordgo.Member)
|
b.userMemberMap = make(map[string]*discordgo.Member)
|
||||||
b.nickMemberMap = make(map[string]*discordgo.Member)
|
b.nickMemberMap = make(map[string]*discordgo.Member)
|
||||||
b.channelInfoMap = make(map[string]*config.ChannelInfo)
|
b.channelInfoMap = make(map[string]*config.ChannelInfo)
|
||||||
|
if b.GetString("WebhookURL") != "" {
|
||||||
b.useAutoWebhooks = b.GetBool("AutoWebhooks")
|
b.Log.Debug("Configuring Discord Incoming Webhook")
|
||||||
if b.useAutoWebhooks {
|
b.webhookID, b.webhookToken = b.splitURL(b.GetString("WebhookURL"))
|
||||||
b.Log.Debug("Using automatic webhooks")
|
|
||||||
}
|
}
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bdiscord) Connect() error {
|
func (b *Bdiscord) Connect() error {
|
||||||
var err error
|
var err error
|
||||||
|
var guildFound bool
|
||||||
token := b.GetString("Token")
|
token := b.GetString("Token")
|
||||||
b.Log.Info("Connecting")
|
b.Log.Info("Connecting")
|
||||||
|
if b.GetString("WebhookURL") == "" {
|
||||||
|
b.Log.Info("Connecting using token")
|
||||||
|
} else {
|
||||||
|
b.Log.Info("Connecting using webhookurl (for posting) and token")
|
||||||
|
}
|
||||||
if !strings.HasPrefix(b.GetString("Token"), "Bot ") {
|
if !strings.HasPrefix(b.GetString("Token"), "Bot ") {
|
||||||
token = "Bot " + b.GetString("Token")
|
token = "Bot " + b.GetString("Token")
|
||||||
}
|
}
|
||||||
@@ -89,14 +79,6 @@ func (b *Bdiscord) Connect() error {
|
|||||||
b.c.AddHandler(b.messageDeleteBulk)
|
b.c.AddHandler(b.messageDeleteBulk)
|
||||||
b.c.AddHandler(b.memberAdd)
|
b.c.AddHandler(b.memberAdd)
|
||||||
b.c.AddHandler(b.memberRemove)
|
b.c.AddHandler(b.memberRemove)
|
||||||
if b.GetInt("debuglevel") == 1 {
|
|
||||||
b.c.AddHandler(b.messageEvent)
|
|
||||||
}
|
|
||||||
// Add privileged intent for guild member tracking. This is needed to track nicks
|
|
||||||
// for display names and @mention translation
|
|
||||||
b.c.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsAllWithoutPrivileged |
|
|
||||||
discordgo.IntentsGuildMembers)
|
|
||||||
|
|
||||||
err = b.c.Open()
|
err = b.c.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -112,107 +94,66 @@ func (b *Bdiscord) Connect() error {
|
|||||||
serverName := strings.Replace(b.GetString("Server"), "ID:", "", -1)
|
serverName := strings.Replace(b.GetString("Server"), "ID:", "", -1)
|
||||||
b.nick = userinfo.Username
|
b.nick = userinfo.Username
|
||||||
b.userID = userinfo.ID
|
b.userID = userinfo.ID
|
||||||
|
|
||||||
// Try and find this account's guild, and populate channels
|
|
||||||
b.channelsMutex.Lock()
|
b.channelsMutex.Lock()
|
||||||
for _, guild := range guilds {
|
for _, guild := range guilds {
|
||||||
// Skip, if the server name does not match the visible name or the ID
|
if guild.Name == serverName || guild.ID == serverName {
|
||||||
if guild.Name != serverName && guild.ID != serverName {
|
b.channels, err = b.c.GuildChannels(guild.ID)
|
||||||
continue
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
b.guildID = guild.ID
|
||||||
|
guildFound = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Complain about an ambiguous Server setting. Two Discord servers could have the same title!
|
|
||||||
// For IDs, practically this will never happen. It would only trigger if some server's name is also an ID.
|
|
||||||
if b.guildID != "" {
|
|
||||||
return fmt.Errorf("found multiple Discord servers with the same name %#v, expected to see only one", serverName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getting this guild's channel could result in a permission error
|
|
||||||
b.channels, err = b.c.GuildChannels(guild.ID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not get %#v's channels: %w", b.GetString("Server"), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.guildID = guild.ID
|
|
||||||
}
|
}
|
||||||
b.channelsMutex.Unlock()
|
b.channelsMutex.Unlock()
|
||||||
|
if !guildFound {
|
||||||
// If we couldn't find a guild, we print extra debug information and return a nice error
|
msg := fmt.Sprintf("Server \"%s\" not found", b.GetString("Server"))
|
||||||
if b.guildID == "" {
|
err = errors.New(msg)
|
||||||
err = fmt.Errorf("could not find Discord server %#v", b.GetString("Server"))
|
b.Log.Error(msg)
|
||||||
b.Log.Error(err.Error())
|
b.Log.Info("Possible values:")
|
||||||
|
|
||||||
// Print all of the possible server values
|
|
||||||
b.Log.Info("Possible server values:")
|
|
||||||
for _, guild := range guilds {
|
for _, guild := range guilds {
|
||||||
b.Log.Infof("\t- Server=%#v # by name", guild.Name)
|
b.Log.Infof("Server=\"%s\" # Server name", guild.Name)
|
||||||
b.Log.Infof("\t- Server=%#v # by ID", guild.ID)
|
b.Log.Infof("Server=\"%s\" # Server ID", guild.ID)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// If there are no results, we should say that
|
if err != nil {
|
||||||
if len(guilds) == 0 {
|
|
||||||
b.Log.Info("\t- (none found)")
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy note: WebhookURL used to have an actual webhook URL that we would edit,
|
b.channelsMutex.RLock()
|
||||||
// but we stopped doing that due to Discord making rate limits more aggressive.
|
if b.GetString("WebhookURL") == "" {
|
||||||
//
|
for _, channel := range b.channels {
|
||||||
// Even older: the same WebhookURL used to be used by every channel, which is usually unexpected.
|
b.Log.Debugf("found channel %#v", channel)
|
||||||
// This is no longer possible.
|
}
|
||||||
if b.GetString("WebhookURL") != "" {
|
} else {
|
||||||
message := "The global WebhookURL setting has been removed. "
|
manageWebhooks := discordgo.PermissionManageWebhooks
|
||||||
message += "You can get similar \"webhook editing\" behaviour by replacing this line with `AutoWebhooks=true`. "
|
var channelsDenied []string
|
||||||
message += "If you rely on the old-OLD (non-editing) behaviour, can move the WebhookURL to specific channel sections."
|
for _, info := range b.Channels {
|
||||||
b.Log.Errorln(message)
|
id := b.getChannelID(info.Name) // note(qaisjp): this readlocks channelsMutex
|
||||||
return fmt.Errorf("use of removed WebhookURL setting")
|
b.Log.Debugf("Verifying PermissionManageWebhooks for %s with ID %s", info.ID, id)
|
||||||
}
|
|
||||||
|
|
||||||
if b.GetInt("debuglevel") == 2 {
|
perms, permsErr := b.c.UserChannelPermissions(userinfo.ID, id)
|
||||||
b.Log.Debug("enabling even more discord debug")
|
if permsErr != nil {
|
||||||
b.c.Debug = true
|
b.Log.Warnf("Failed to check PermissionManageWebhooks in channel \"%s\": %s", info.Name, permsErr.Error())
|
||||||
}
|
} else if perms&manageWebhooks == manageWebhooks {
|
||||||
|
continue
|
||||||
// Initialise webhook management
|
|
||||||
b.transmitter = transmitter.New(b.c, b.guildID, "matterbridge", b.useAutoWebhooks)
|
|
||||||
b.transmitter.Log = b.Log
|
|
||||||
|
|
||||||
var webhookChannelIDs []string
|
|
||||||
for _, channel := range b.Channels {
|
|
||||||
channelID := b.getChannelID(channel.Name) // note(qaisjp): this readlocks channelsMutex
|
|
||||||
|
|
||||||
// If a WebhookURL was not explicitly provided for this channel,
|
|
||||||
// there are two options: just a regular bot message (ugly) or this is should be webhook sent
|
|
||||||
if channel.Options.WebhookURL == "" {
|
|
||||||
// If it should be webhook sent, we should enforce this via the transmitter
|
|
||||||
if b.useAutoWebhooks {
|
|
||||||
webhookChannelIDs = append(webhookChannelIDs, channelID)
|
|
||||||
}
|
}
|
||||||
continue
|
channelsDenied = append(channelsDenied, fmt.Sprintf("%#v", info.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
whID, whToken, ok := b.splitURL(channel.Options.WebhookURL)
|
b.canEditWebhooks = len(channelsDenied) == 0
|
||||||
if !ok {
|
b.canEditWebhooks = false
|
||||||
return fmt.Errorf("failed to parse WebhookURL %#v for channel %#v", channel.Options.WebhookURL, channel.ID)
|
b.Log.Info("Webhook editing is disabled because of ratelimit issues")
|
||||||
}
|
/*
|
||||||
|
if b.canEditWebhooks {
|
||||||
b.transmitter.AddWebhook(channelID, &discordgo.Webhook{
|
b.Log.Info("Can manage webhooks; will edit channel for global webhook on send")
|
||||||
ID: whID,
|
} else {
|
||||||
Token: whToken,
|
b.Log.Warn("Can't manage webhooks; won't edit channel for global webhook on send")
|
||||||
GuildID: b.guildID,
|
b.Log.Warn("Can't manage webhooks in channels: ", strings.Join(channelsDenied, ", "))
|
||||||
ChannelID: channelID,
|
}
|
||||||
})
|
*/
|
||||||
}
|
|
||||||
|
|
||||||
if b.useAutoWebhooks {
|
|
||||||
err = b.transmitter.RefreshGuildWebhooks(webhookChannelIDs)
|
|
||||||
if err != nil {
|
|
||||||
b.Log.WithError(err).Println("transmitter could not refresh guild webhooks")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
b.channelsMutex.RUnlock()
|
||||||
|
|
||||||
// Obtaining guild members and initializing nickname mapping.
|
// Obtaining guild members and initializing nickname mapping.
|
||||||
b.membersMutex.Lock()
|
b.membersMutex.Lock()
|
||||||
@@ -269,23 +210,80 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
|
|||||||
msg.Text = "_" + msg.Text + "_"
|
msg.Text = "_" + msg.Text + "_"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle prefix hint for unthreaded messages.
|
// use initial webhook configured for the entire Discord account
|
||||||
if msg.ParentNotFound() {
|
isGlobalWebhook := true
|
||||||
msg.ParentID = ""
|
wID := b.webhookID
|
||||||
msg.Text = fmt.Sprintf("[thread]: %s", msg.Text)
|
wToken := b.webhookToken
|
||||||
|
|
||||||
|
// check if have a channel specific webhook
|
||||||
|
b.channelsMutex.RLock()
|
||||||
|
if ci, ok := b.channelInfoMap[msg.Channel+b.Account]; ok {
|
||||||
|
if ci.Options.WebhookURL != "" {
|
||||||
|
wID, wToken = b.splitURL(ci.Options.WebhookURL)
|
||||||
|
isGlobalWebhook = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
b.channelsMutex.RUnlock()
|
||||||
|
|
||||||
// Use webhook to send the message
|
// Use webhook to send the message
|
||||||
useWebhooks := b.shouldMessageUseWebhooks(&msg)
|
if wID != "" && msg.Event != config.EventMsgDelete {
|
||||||
if useWebhooks && msg.Event != config.EventMsgDelete && msg.ParentID == "" {
|
// skip events
|
||||||
return b.handleEventWebhook(&msg, channelID)
|
if msg.Event != "" && msg.Event != config.EventUserAction && msg.Event != config.EventJoinLeave && msg.Event != config.EventTopicChange {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip empty messages
|
||||||
|
if msg.Text == "" && (msg.Extra == nil || len(msg.Extra["file"]) == 0) {
|
||||||
|
b.Log.Debugf("Skipping empty message %#v", msg)
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Text = helper.ClipMessage(msg.Text, MessageLength)
|
||||||
|
msg.Text = b.replaceUserMentions(msg.Text)
|
||||||
|
// discord username must be [0..32] max
|
||||||
|
if len(msg.Username) > 32 {
|
||||||
|
msg.Username = msg.Username[0:32]
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.ID != "" {
|
||||||
|
b.Log.Debugf("Editing webhook message")
|
||||||
|
uri := discordgo.EndpointWebhookToken(wID, wToken) + "/messages/" + msg.ID
|
||||||
|
_, err := b.c.RequestWithBucketID("PATCH", uri, discordgo.WebhookParams{
|
||||||
|
Content: msg.Text,
|
||||||
|
Username: msg.Username,
|
||||||
|
}, discordgo.EndpointWebhookToken("", ""))
|
||||||
|
if err == nil {
|
||||||
|
return msg.ID, nil
|
||||||
|
}
|
||||||
|
b.Log.Errorf("Could not edit webhook message: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Log.Debugf("Broadcasting using Webhook")
|
||||||
|
|
||||||
|
// if we have a global webhook for this Discord account, and permission
|
||||||
|
// to modify webhooks (previously verified), then set its channel to
|
||||||
|
// the message channel before using it.
|
||||||
|
if isGlobalWebhook && b.canEditWebhooks {
|
||||||
|
b.Log.Debugf("Setting webhook channel to \"%s\"", msg.Channel)
|
||||||
|
_, err := b.c.WebhookEdit(wID, "", "", channelID)
|
||||||
|
if err != nil {
|
||||||
|
b.Log.Errorf("Could not set webhook channel: %s", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.Log.Debugf("Processing webhook sending for message %#v", msg)
|
||||||
|
msg, err := b.webhookSend(&msg, wID, wToken)
|
||||||
|
if err != nil {
|
||||||
|
b.Log.Errorf("Could not broadcast via webook for message %#v: %s", msg, err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if msg == nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg.ID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.handleEventBotUser(&msg, channelID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleEventDirect handles events via the bot user
|
|
||||||
func (b *Bdiscord) handleEventBotUser(msg *config.Message, channelID string) (string, error) {
|
|
||||||
b.Log.Debugf("Broadcasting using token (API)")
|
b.Log.Debugf("Broadcasting using token (API)")
|
||||||
|
|
||||||
// Delete message
|
// Delete message
|
||||||
@@ -297,36 +295,21 @@ func (b *Bdiscord) handleEventBotUser(msg *config.Message, channelID string) (st
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete a file
|
|
||||||
if msg.Event == config.EventFileDelete {
|
|
||||||
if msg.ID == "" {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if fi, ok := b.cache.Get(cFileUpload + msg.ID); ok {
|
|
||||||
err := b.c.ChannelMessageDelete(channelID, fi.(string)) // nolint:forcetypeassert
|
|
||||||
b.cache.Remove(cFileUpload + msg.ID)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", fmt.Errorf("file %s not found", msg.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload a file if it exists
|
// Upload a file if it exists
|
||||||
if msg.Extra != nil {
|
if msg.Extra != nil {
|
||||||
for _, rmsg := range helper.HandleExtra(msg, b.General) {
|
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||||
rmsg.Text = helper.ClipMessage(rmsg.Text, MessageLength, b.GetString("MessageClipped"))
|
rmsg.Text = helper.ClipMessage(rmsg.Text, MessageLength)
|
||||||
if _, err := b.c.ChannelMessageSend(channelID, rmsg.Username+rmsg.Text); err != nil {
|
if _, err := b.c.ChannelMessageSend(channelID, rmsg.Username+rmsg.Text); err != nil {
|
||||||
b.Log.Errorf("Could not send message %#v: %s", rmsg, err)
|
b.Log.Errorf("Could not send message %#v: %s", rmsg, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// check if we have files to upload (from slack, telegram or mattermost)
|
// check if we have files to upload (from slack, telegram or mattermost)
|
||||||
if len(msg.Extra["file"]) > 0 {
|
if len(msg.Extra["file"]) > 0 {
|
||||||
return b.handleUploadFile(msg, channelID)
|
return b.handleUploadFile(&msg, channelID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.Text = helper.ClipMessage(msg.Text, MessageLength, b.GetString("MessageClipped"))
|
msg.Text = helper.ClipMessage(msg.Text, MessageLength)
|
||||||
msg.Text = b.replaceUserMentions(msg.Text)
|
msg.Text = b.replaceUserMentions(msg.Text)
|
||||||
|
|
||||||
// Edit message
|
// Edit message
|
||||||
@@ -335,30 +318,57 @@ func (b *Bdiscord) handleEventBotUser(msg *config.Message, channelID string) (st
|
|||||||
return msg.ID, err
|
return msg.ID, err
|
||||||
}
|
}
|
||||||
|
|
||||||
m := discordgo.MessageSend{
|
|
||||||
Content: msg.Username + msg.Text,
|
|
||||||
AllowedMentions: b.getAllowedMentions(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg.ParentValid() {
|
|
||||||
m.Reference = &discordgo.MessageReference{
|
|
||||||
MessageID: msg.ParentID,
|
|
||||||
ChannelID: channelID,
|
|
||||||
GuildID: b.guildID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Post normal message
|
// Post normal message
|
||||||
res, err := b.c.ChannelMessageSendComplex(channelID, &m)
|
res, err := b.c.ChannelMessageSend(channelID, msg.Username+msg.Text)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.ID, nil
|
return res.ID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// useWebhook returns true if we have a webhook defined somewhere
|
||||||
|
func (b *Bdiscord) useWebhook() bool {
|
||||||
|
if b.GetString("WebhookURL") != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
b.channelsMutex.RLock()
|
||||||
|
defer b.channelsMutex.RUnlock()
|
||||||
|
|
||||||
|
for _, channel := range b.channelInfoMap {
|
||||||
|
if channel.Options.WebhookURL != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// isWebhookID returns true if the specified id is used in a defined webhook
|
||||||
|
func (b *Bdiscord) isWebhookID(id string) bool {
|
||||||
|
if b.GetString("WebhookURL") != "" {
|
||||||
|
wID, _ := b.splitURL(b.GetString("WebhookURL"))
|
||||||
|
if wID == id {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.channelsMutex.RLock()
|
||||||
|
defer b.channelsMutex.RUnlock()
|
||||||
|
|
||||||
|
for _, channel := range b.channelInfoMap {
|
||||||
|
if channel.Options.WebhookURL != "" {
|
||||||
|
wID, _ := b.splitURL(channel.Options.WebhookURL)
|
||||||
|
if wID == id {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// handleUploadFile handles native upload of files
|
// handleUploadFile handles native upload of files
|
||||||
func (b *Bdiscord) handleUploadFile(msg *config.Message, channelID string) (string, error) {
|
func (b *Bdiscord) handleUploadFile(msg *config.Message, channelID string) (string, error) {
|
||||||
|
var err error
|
||||||
for _, f := range msg.Extra["file"] {
|
for _, f := range msg.Extra["file"] {
|
||||||
fi := f.(config.FileInfo)
|
fi := f.(config.FileInfo)
|
||||||
file := discordgo.File{
|
file := discordgo.File{
|
||||||
@@ -367,19 +377,93 @@ func (b *Bdiscord) handleUploadFile(msg *config.Message, channelID string) (stri
|
|||||||
Reader: bytes.NewReader(*fi.Data),
|
Reader: bytes.NewReader(*fi.Data),
|
||||||
}
|
}
|
||||||
m := discordgo.MessageSend{
|
m := discordgo.MessageSend{
|
||||||
Content: msg.Username + fi.Comment,
|
Content: msg.Username + fi.Comment,
|
||||||
Files: []*discordgo.File{&file},
|
Files: []*discordgo.File{&file},
|
||||||
AllowedMentions: b.getAllowedMentions(),
|
|
||||||
}
|
}
|
||||||
res, err := b.c.ChannelMessageSendComplex(channelID, &m)
|
_, err = b.c.ChannelMessageSendComplex(channelID, &m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("file upload failed: %s", err)
|
return "", fmt.Errorf("file upload failed: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// link file_upload_nativeID (file ID from the original bridge) to our upload id
|
|
||||||
// so that we can remove this later when it eg needs to be deleted
|
|
||||||
b.cache.Add(cFileUpload+fi.NativeID, res.ID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", nil
|
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,8 +2,7 @@ package bdiscord
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/matterbridge/discordgo"
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *Bdiscord) messageDelete(s *discordgo.Session, m *discordgo.MessageDelete) { //nolint:unparam
|
func (b *Bdiscord) messageDelete(s *discordgo.Session, m *discordgo.MessageDelete) { //nolint:unparam
|
||||||
@@ -32,10 +31,6 @@ func (b *Bdiscord) messageDeleteBulk(s *discordgo.Session, m *discordgo.MessageD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bdiscord) messageEvent(s *discordgo.Session, m *discordgo.Event) {
|
|
||||||
b.Log.Debug(spew.Sdump(m.Struct))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bdiscord) messageTyping(s *discordgo.Session, m *discordgo.TypingStart) {
|
func (b *Bdiscord) messageTyping(s *discordgo.Session, m *discordgo.TypingStart) {
|
||||||
if !b.GetBool("ShowUserTyping") {
|
if !b.GetBool("ShowUserTyping") {
|
||||||
return
|
return
|
||||||
@@ -56,7 +51,7 @@ func (b *Bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdat
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// only when message is actually edited
|
// only when message is actually edited
|
||||||
if m.Message.EditedTimestamp != nil {
|
if m.Message.EditedTimestamp != "" {
|
||||||
b.Log.Debugf("Sending edit message")
|
b.Log.Debugf("Sending edit message")
|
||||||
m.Content += b.GetString("EditSuffix")
|
m.Content += b.GetString("EditSuffix")
|
||||||
msg := &discordgo.MessageCreate{
|
msg := &discordgo.MessageCreate{
|
||||||
@@ -74,7 +69,7 @@ func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// if using webhooks, do not relay if it's ours
|
// if using webhooks, do not relay if it's ours
|
||||||
if m.Author.Bot && b.transmitter.HasWebhook(m.Author.ID) {
|
if b.useWebhook() && m.Author.Bot && b.isWebhookID(m.Author.ID) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,9 +82,8 @@ func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
|
|||||||
|
|
||||||
rmsg := config.Message{Account: b.Account, Avatar: "https://cdn.discordapp.com/avatars/" + m.Author.ID + "/" + m.Author.Avatar + ".jpg", UserID: m.Author.ID, ID: m.ID}
|
rmsg := config.Message{Account: b.Account, Avatar: "https://cdn.discordapp.com/avatars/" + m.Author.ID + "/" + m.Author.Avatar + ".jpg", UserID: m.Author.ID, ID: m.ID}
|
||||||
|
|
||||||
b.Log.Debugf("== Receiving event %#v", m.Message)
|
|
||||||
|
|
||||||
if m.Content != "" {
|
if m.Content != "" {
|
||||||
|
b.Log.Debugf("== Receiving event %#v", m.Message)
|
||||||
m.Message.Content = b.replaceChannelMentions(m.Message.Content)
|
m.Message.Content = b.replaceChannelMentions(m.Message.Content)
|
||||||
rmsg.Text, err = m.ContentWithMoreMentionsReplaced(b.c)
|
rmsg.Text, err = m.ContentWithMoreMentionsReplaced(b.c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -133,11 +127,6 @@ func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
|
|||||||
// Replace emotes
|
// Replace emotes
|
||||||
rmsg.Text = replaceEmotes(rmsg.Text)
|
rmsg.Text = replaceEmotes(rmsg.Text)
|
||||||
|
|
||||||
// Add our parent id if it exists, and if it's not referring to a message in another channel
|
|
||||||
if ref := m.MessageReference; ref != nil && ref.ChannelID == m.ChannelID {
|
|
||||||
rmsg.ParentID = ref.MessageID
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", m.Author.Username, b.Account)
|
b.Log.Debugf("<= Sending message from %s on %s to gateway", m.Author.Username, b.Account)
|
||||||
b.Log.Debugf("<= Message is %#v", rmsg)
|
b.Log.Debugf("<= Message is %#v", rmsg)
|
||||||
b.Remote <- rmsg
|
b.Remote <- rmsg
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package bdiscord
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/matterbridge/discordgo"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -6,33 +6,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/matterbridge/discordgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *Bdiscord) getAllowedMentions() *discordgo.MessageAllowedMentions {
|
|
||||||
// If AllowMention is not specified, then allow all mentions (default Discord behavior)
|
|
||||||
if !b.IsKeySet("AllowMention") {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, allow only the mentions that are specified
|
|
||||||
allowedMentionTypes := make([]discordgo.AllowedMentionType, 0, 3)
|
|
||||||
for _, m := range b.GetStringSlice("AllowMention") {
|
|
||||||
switch m {
|
|
||||||
case "everyone":
|
|
||||||
allowedMentionTypes = append(allowedMentionTypes, discordgo.AllowedMentionTypeEveryone)
|
|
||||||
case "roles":
|
|
||||||
allowedMentionTypes = append(allowedMentionTypes, discordgo.AllowedMentionTypeRoles)
|
|
||||||
case "users":
|
|
||||||
allowedMentionTypes = append(allowedMentionTypes, discordgo.AllowedMentionTypeUsers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &discordgo.MessageAllowedMentions{
|
|
||||||
Parse: allowedMentionTypes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bdiscord) getNick(user *discordgo.User, guildID string) string {
|
func (b *Bdiscord) getNick(user *discordgo.User, guildID string) string {
|
||||||
b.membersMutex.RLock()
|
b.membersMutex.RLock()
|
||||||
defer b.membersMutex.RUnlock()
|
defer b.membersMutex.RUnlock()
|
||||||
@@ -220,7 +196,7 @@ func (b *Bdiscord) replaceAction(text string) (string, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// splitURL splits a webhookURL and returns the ID and token.
|
// splitURL splits a webhookURL and returns the ID and token.
|
||||||
func (b *Bdiscord) splitURL(url string) (string, string, bool) {
|
func (b *Bdiscord) splitURL(url string) (string, string) {
|
||||||
const (
|
const (
|
||||||
expectedWebhookSplitCount = 7
|
expectedWebhookSplitCount = 7
|
||||||
webhookIdxID = 5
|
webhookIdxID = 5
|
||||||
@@ -228,9 +204,9 @@ func (b *Bdiscord) splitURL(url string) (string, string, bool) {
|
|||||||
)
|
)
|
||||||
webhookURLSplit := strings.Split(url, "/")
|
webhookURLSplit := strings.Split(url, "/")
|
||||||
if len(webhookURLSplit) != expectedWebhookSplitCount {
|
if len(webhookURLSplit) != expectedWebhookSplitCount {
|
||||||
return "", "", false
|
b.Log.Fatalf("%s is no correct discord WebhookURL", url)
|
||||||
}
|
}
|
||||||
return webhookURLSplit[webhookIdxID], webhookURLSplit[webhookIdxToken], true
|
return webhookURLSplit[webhookIdxID], webhookURLSplit[webhookIdxToken]
|
||||||
}
|
}
|
||||||
|
|
||||||
func enumerateUsernames(s string) []string {
|
func enumerateUsernames(s string) []string {
|
||||||
|
|||||||
@@ -1,257 +0,0 @@
|
|||||||
// Package transmitter provides functionality for transmitting
|
|
||||||
// arbitrary webhook messages to Discord.
|
|
||||||
//
|
|
||||||
// The package provides the following functionality:
|
|
||||||
//
|
|
||||||
// - Creating new webhooks, whenever necessary
|
|
||||||
// - Loading webhooks that we have previously created
|
|
||||||
// - Sending new messages
|
|
||||||
// - Editing messages, via message ID
|
|
||||||
// - Deleting messages, via message ID
|
|
||||||
//
|
|
||||||
// The package has been designed for matterbridge, but with other
|
|
||||||
// Go bots in mind. The public API should be matterbridge-agnostic.
|
|
||||||
package transmitter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Transmitter represents a message manager for a single guild.
|
|
||||||
type Transmitter struct {
|
|
||||||
session *discordgo.Session
|
|
||||||
guild string
|
|
||||||
title string
|
|
||||||
autoCreate bool
|
|
||||||
|
|
||||||
// channelWebhooks maps from a channel ID to a webhook instance
|
|
||||||
channelWebhooks map[string]*discordgo.Webhook
|
|
||||||
|
|
||||||
mutex sync.RWMutex
|
|
||||||
|
|
||||||
Log *log.Entry
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrWebhookNotFound is returned when a valid webhook for this channel/message combination does not exist
|
|
||||||
var ErrWebhookNotFound = errors.New("webhook for this channel and message does not exist")
|
|
||||||
|
|
||||||
// ErrPermissionDenied is returned if the bot does not have permission to manage webhooks.
|
|
||||||
//
|
|
||||||
// Bots can be granted a guild-wide permission and channel-specific permissions to manage webhooks.
|
|
||||||
// Despite potentially having guild-wide permission, channel specific overrides could deny a bot's permission to manage webhooks.
|
|
||||||
var ErrPermissionDenied = errors.New("missing 'Manage Webhooks' permission")
|
|
||||||
|
|
||||||
// New returns a new Transmitter given a Discord session, guild ID, and title.
|
|
||||||
func New(session *discordgo.Session, guild string, title string, autoCreate bool) *Transmitter {
|
|
||||||
return &Transmitter{
|
|
||||||
session: session,
|
|
||||||
guild: guild,
|
|
||||||
title: title,
|
|
||||||
autoCreate: autoCreate,
|
|
||||||
|
|
||||||
channelWebhooks: make(map[string]*discordgo.Webhook),
|
|
||||||
|
|
||||||
Log: log.NewEntry(log.StandardLogger()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send transmits a message to the given channel with the provided webhook data, and waits until Discord responds with message data.
|
|
||||||
func (t *Transmitter) Send(channelID string, params *discordgo.WebhookParams) (*discordgo.Message, error) {
|
|
||||||
wh, err := t.getOrCreateWebhook(channelID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
msg, err := t.session.WebhookExecute(wh.ID, wh.Token, true, params)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("execute failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return msg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Edit will edit a message in a channel, if possible.
|
|
||||||
func (t *Transmitter) Edit(channelID string, messageID string, params *discordgo.WebhookParams) error {
|
|
||||||
wh := t.getWebhook(channelID)
|
|
||||||
|
|
||||||
if wh == nil {
|
|
||||||
return ErrWebhookNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
uri := discordgo.EndpointWebhookToken(wh.ID, wh.Token) + "/messages/" + messageID
|
|
||||||
_, err := t.session.RequestWithBucketID("PATCH", uri, params, discordgo.EndpointWebhookToken("", ""))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasWebhook checks whether the transmitter is using a particular webhook.
|
|
||||||
func (t *Transmitter) HasWebhook(id string) bool {
|
|
||||||
t.mutex.RLock()
|
|
||||||
defer t.mutex.RUnlock()
|
|
||||||
|
|
||||||
for _, wh := range t.channelWebhooks {
|
|
||||||
if wh.ID == id {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddWebhook allows you to register a channel's webhook with the transmitter.
|
|
||||||
func (t *Transmitter) AddWebhook(channelID string, webhook *discordgo.Webhook) bool {
|
|
||||||
t.Log.Debugf("Manually added webhook %#v to channel %#v", webhook.ID, channelID)
|
|
||||||
t.mutex.Lock()
|
|
||||||
defer t.mutex.Unlock()
|
|
||||||
|
|
||||||
_, replaced := t.channelWebhooks[channelID]
|
|
||||||
t.channelWebhooks[channelID] = webhook
|
|
||||||
return replaced
|
|
||||||
}
|
|
||||||
|
|
||||||
// RefreshGuildWebhooks loads "relevant" webhooks into the transmitter, with careful permission handling.
|
|
||||||
//
|
|
||||||
// Notes:
|
|
||||||
//
|
|
||||||
// - A webhook is "relevant" if it was created by this bot -- the ApplicationID should match the bot's ID.
|
|
||||||
// - The term "having permission" means having the "Manage Webhooks" permission. See ErrPermissionDenied for more information.
|
|
||||||
// - This function is additive and will not unload previously loaded webhooks.
|
|
||||||
// - A nil channelIDs slice is treated the same as an empty one.
|
|
||||||
//
|
|
||||||
// If the bot has guild-wide permission:
|
|
||||||
//
|
|
||||||
// 1. it will load any "relevant" webhooks from the entire guild
|
|
||||||
// 2. the given slice is ignored
|
|
||||||
//
|
|
||||||
// If the bot does not have guild-wide permission:
|
|
||||||
//
|
|
||||||
// 1. it will load any "relevant" webhooks in each channel
|
|
||||||
// 2. a single error will be returned if any error occurs (incl. if there is no permission for any of these channels)
|
|
||||||
//
|
|
||||||
// If any channel has more than one "relevant" webhook, it will randomly pick one.
|
|
||||||
func (t *Transmitter) RefreshGuildWebhooks(channelIDs []string) error {
|
|
||||||
t.Log.Debugln("Refreshing guild webhooks")
|
|
||||||
|
|
||||||
botID, err := getDiscordUserID(t.session)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not get current user: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all existing webhooks
|
|
||||||
hooks, err := t.session.GuildWebhooks(t.guild)
|
|
||||||
if err != nil {
|
|
||||||
switch {
|
|
||||||
case isDiscordPermissionError(err):
|
|
||||||
// We fallback on manually fetching hooks from individual channels
|
|
||||||
// if we don't have the "Manage Webhooks" permission globally.
|
|
||||||
// We can only do this if we were provided channelIDs, though.
|
|
||||||
if len(channelIDs) == 0 {
|
|
||||||
return ErrPermissionDenied
|
|
||||||
}
|
|
||||||
t.Log.Debugln("Missing global 'Manage Webhooks' permission, falling back on per-channel permission")
|
|
||||||
return t.fetchChannelsHooks(channelIDs, botID)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("could not get webhooks: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Log.Debugln("Refreshing guild webhooks using global permission")
|
|
||||||
t.assignHooksByAppID(hooks, botID, false)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// createWebhook creates a webhook for a specific channel.
|
|
||||||
func (t *Transmitter) createWebhook(channel string) (*discordgo.Webhook, error) {
|
|
||||||
t.mutex.Lock()
|
|
||||||
defer t.mutex.Unlock()
|
|
||||||
|
|
||||||
wh, err := t.session.WebhookCreate(channel, t.title+time.Now().Format(" 3:04:05PM"), "")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
t.channelWebhooks[channel] = wh
|
|
||||||
return wh, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transmitter) getWebhook(channel string) *discordgo.Webhook {
|
|
||||||
t.mutex.RLock()
|
|
||||||
defer t.mutex.RUnlock()
|
|
||||||
|
|
||||||
return t.channelWebhooks[channel]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transmitter) getOrCreateWebhook(channelID string) (*discordgo.Webhook, error) {
|
|
||||||
// If we have a webhook for this channel, immediately return it
|
|
||||||
wh := t.getWebhook(channelID)
|
|
||||||
if wh != nil {
|
|
||||||
return wh, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Early exit if we don't want to automatically create one
|
|
||||||
if !t.autoCreate {
|
|
||||||
return nil, ErrWebhookNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Log.Infof("Creating a webhook for %s\n", channelID)
|
|
||||||
wh, err := t.createWebhook(channelID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not create webhook: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return wh, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetchChannelsHooks fetches hooks for the given channelIDs and calls assignHooksByAppID for each channel's hooks
|
|
||||||
func (t *Transmitter) fetchChannelsHooks(channelIDs []string, botID string) error {
|
|
||||||
// For each channel, search for relevant hooks
|
|
||||||
var failedHooks []string
|
|
||||||
for _, channelID := range channelIDs {
|
|
||||||
hooks, err := t.session.ChannelWebhooks(channelID)
|
|
||||||
if err != nil {
|
|
||||||
failedHooks = append(failedHooks, "\n- "+channelID+": "+err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
t.assignHooksByAppID(hooks, botID, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compose an error if any hooks failed
|
|
||||||
if len(failedHooks) > 0 {
|
|
||||||
return errors.New("failed to fetch hooks:" + strings.Join(failedHooks, ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transmitter) assignHooksByAppID(hooks []*discordgo.Webhook, appID string, channelTargeted bool) {
|
|
||||||
logLine := "Picking up webhook"
|
|
||||||
if channelTargeted {
|
|
||||||
logLine += " (channel targeted)"
|
|
||||||
}
|
|
||||||
|
|
||||||
t.mutex.Lock()
|
|
||||||
defer t.mutex.Unlock()
|
|
||||||
|
|
||||||
for _, wh := range hooks {
|
|
||||||
if wh.ApplicationID != appID {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
t.channelWebhooks[wh.ChannelID] = wh
|
|
||||||
t.Log.WithFields(log.Fields{
|
|
||||||
"id": wh.ID,
|
|
||||||
"name": wh.Name,
|
|
||||||
"channel": wh.ChannelID,
|
|
||||||
}).Println(logLine)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package transmitter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/bwmarrin/discordgo"
|
|
||||||
)
|
|
||||||
|
|
||||||
// isDiscordPermissionError returns false for nil, and true if a Discord RESTError with code discordgo.ErrorCodeMissionPermissions
|
|
||||||
func isDiscordPermissionError(err error) bool {
|
|
||||||
if err == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
restErr, ok := err.(*discordgo.RESTError)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return restErr.Message != nil && restErr.Message.Code == discordgo.ErrCodeMissingPermissions
|
|
||||||
}
|
|
||||||
|
|
||||||
// getDiscordUserID gets own user ID from state, and fallback on API request
|
|
||||||
func getDiscordUserID(session *discordgo.Session) (string, error) {
|
|
||||||
if user := session.State.User; user != nil {
|
|
||||||
return user.ID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := session.User("@me")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return user.ID, nil
|
|
||||||
}
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
package bdiscord
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
|
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
|
||||||
"github.com/bwmarrin/discordgo"
|
|
||||||
)
|
|
||||||
|
|
||||||
// shouldMessageUseWebhooks checks if have a channel specific webhook, if we're not using auto webhooks
|
|
||||||
func (b *Bdiscord) shouldMessageUseWebhooks(msg *config.Message) bool {
|
|
||||||
if b.useAutoWebhooks {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
b.channelsMutex.RLock()
|
|
||||||
defer b.channelsMutex.RUnlock()
|
|
||||||
if ci, ok := b.channelInfoMap[msg.Channel+b.Account]; ok {
|
|
||||||
if ci.Options.WebhookURL != "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// maybeGetLocalAvatar checks if UseLocalAvatar contains the message's
|
|
||||||
// account or protocol, and if so, returns the Discord avatar (if exists)
|
|
||||||
func (b *Bdiscord) maybeGetLocalAvatar(msg *config.Message) string {
|
|
||||||
for _, val := range b.GetStringSlice("UseLocalAvatar") {
|
|
||||||
if msg.Protocol != val && msg.Account != val {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
member, err := b.getGuildMemberByNick(msg.Username)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return member.User.AvatarURL("")
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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, channelID string) (*discordgo.Message, error) {
|
|
||||||
var (
|
|
||||||
res *discordgo.Message
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
// If avatar is unset, mutate the message to include the local avatar (but only if settings say we should do this)
|
|
||||||
if msg.Avatar == "" {
|
|
||||||
msg.Avatar = b.maybeGetLocalAvatar(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebhookParams can have either `Content` or `File`.
|
|
||||||
|
|
||||||
// We can't send empty messages.
|
|
||||||
if msg.Text != "" {
|
|
||||||
res, err = b.transmitter.Send(
|
|
||||||
channelID,
|
|
||||||
&discordgo.WebhookParams{
|
|
||||||
Content: msg.Text,
|
|
||||||
Username: msg.Username,
|
|
||||||
AvatarURL: msg.Avatar,
|
|
||||||
AllowedMentions: b.getAllowedMentions(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
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 := fi.Comment
|
|
||||||
|
|
||||||
_, e2 := b.transmitter.Send(
|
|
||||||
channelID,
|
|
||||||
&discordgo.WebhookParams{
|
|
||||||
Username: msg.Username,
|
|
||||||
AvatarURL: msg.Avatar,
|
|
||||||
Files: []*discordgo.File{&file},
|
|
||||||
Content: content,
|
|
||||||
AllowedMentions: b.getAllowedMentions(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if e2 != nil {
|
|
||||||
b.Log.Errorf("Could not send file %#v for message %#v: %s", file, msg, e2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bdiscord) handleEventWebhook(msg *config.Message, channelID string) (string, error) {
|
|
||||||
// skip events
|
|
||||||
if msg.Event != "" && msg.Event != config.EventUserAction && msg.Event != config.EventJoinLeave && msg.Event != config.EventTopicChange {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip empty messages
|
|
||||||
if msg.Text == "" && (msg.Extra == nil || len(msg.Extra["file"]) == 0) {
|
|
||||||
b.Log.Debugf("Skipping empty message %#v", msg)
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.Text = helper.ClipMessage(msg.Text, MessageLength, b.GetString("MessageClipped"))
|
|
||||||
msg.Text = b.replaceUserMentions(msg.Text)
|
|
||||||
// discord username must be [0..32] max
|
|
||||||
if len(msg.Username) > 32 {
|
|
||||||
msg.Username = msg.Username[0:32]
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg.ID != "" {
|
|
||||||
b.Log.Debugf("Editing webhook message")
|
|
||||||
err := b.transmitter.Edit(channelID, msg.ID, &discordgo.WebhookParams{
|
|
||||||
Content: msg.Text,
|
|
||||||
Username: msg.Username,
|
|
||||||
AllowedMentions: b.getAllowedMentions(),
|
|
||||||
})
|
|
||||||
if err == nil {
|
|
||||||
return msg.ID, nil
|
|
||||||
}
|
|
||||||
b.Log.Errorf("Could not edit webhook message: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Log.Debugf("Processing webhook sending for message %#v", msg)
|
|
||||||
discordMsg, err := b.webhookSend(msg, channelID)
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("Could not broadcast via webhook for message %#v: %s", msg, err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if discordMsg == nil {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return discordMsg.ID, nil
|
|
||||||
}
|
|
||||||
@@ -1,252 +0,0 @@
|
|||||||
package harmony
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/42wim/matterbridge/bridge"
|
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
|
||||||
"github.com/harmony-development/shibshib"
|
|
||||||
chatv1 "github.com/harmony-development/shibshib/gen/chat/v1"
|
|
||||||
typesv1 "github.com/harmony-development/shibshib/gen/harmonytypes/v1"
|
|
||||||
profilev1 "github.com/harmony-development/shibshib/gen/profile/v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
type cachedProfile struct {
|
|
||||||
data *profilev1.GetProfileResponse
|
|
||||||
lastUpdated time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type Bharmony struct {
|
|
||||||
*bridge.Config
|
|
||||||
|
|
||||||
c *shibshib.Client
|
|
||||||
profileCache map[uint64]cachedProfile
|
|
||||||
}
|
|
||||||
|
|
||||||
func uToStr(in uint64) string {
|
|
||||||
return strconv.FormatUint(in, 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
func strToU(in string) (uint64, error) {
|
|
||||||
return strconv.ParseUint(in, 10, 64)
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(cfg *bridge.Config) bridge.Bridger {
|
|
||||||
b := &Bharmony{
|
|
||||||
Config: cfg,
|
|
||||||
profileCache: map[uint64]cachedProfile{},
|
|
||||||
}
|
|
||||||
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bharmony) getProfile(u uint64) (*profilev1.GetProfileResponse, error) {
|
|
||||||
if v, ok := b.profileCache[u]; ok && time.Since(v.lastUpdated) < time.Minute*10 {
|
|
||||||
return v.data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := b.c.ProfileKit.GetProfile(&profilev1.GetProfileRequest{
|
|
||||||
UserId: u,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
if v, ok := b.profileCache[u]; ok {
|
|
||||||
return v.data, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b.profileCache[u] = cachedProfile{
|
|
||||||
data: resp,
|
|
||||||
lastUpdated: time.Now(),
|
|
||||||
}
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bharmony) avatarFor(m *chatv1.Message) string {
|
|
||||||
if m.Overrides != nil {
|
|
||||||
return m.Overrides.GetAvatar()
|
|
||||||
}
|
|
||||||
|
|
||||||
profi, err := b.getProfile(m.AuthorId)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.c.TransformHMCURL(profi.Profile.GetUserAvatar())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bharmony) usernameFor(m *chatv1.Message) string {
|
|
||||||
if m.Overrides != nil {
|
|
||||||
return m.Overrides.GetUsername()
|
|
||||||
}
|
|
||||||
|
|
||||||
profi, err := b.getProfile(m.AuthorId)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return profi.Profile.UserName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bharmony) toMessage(msg *shibshib.LocatedMessage) config.Message {
|
|
||||||
message := config.Message{}
|
|
||||||
message.Account = b.Account
|
|
||||||
message.UserID = uToStr(msg.Message.AuthorId)
|
|
||||||
message.Avatar = b.avatarFor(msg.Message)
|
|
||||||
message.Username = b.usernameFor(msg.Message)
|
|
||||||
message.Channel = uToStr(msg.ChannelID)
|
|
||||||
message.ID = uToStr(msg.MessageId)
|
|
||||||
|
|
||||||
switch content := msg.Message.Content.Content.(type) {
|
|
||||||
case *chatv1.Content_EmbedMessage:
|
|
||||||
message.Text = "Embed"
|
|
||||||
case *chatv1.Content_AttachmentMessage:
|
|
||||||
var s strings.Builder
|
|
||||||
for idx, attach := range content.AttachmentMessage.Files {
|
|
||||||
s.WriteString(b.c.TransformHMCURL(attach.Id))
|
|
||||||
if idx < len(content.AttachmentMessage.Files)-1 {
|
|
||||||
s.WriteString(", ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
message.Text = s.String()
|
|
||||||
case *chatv1.Content_PhotoMessage:
|
|
||||||
var s strings.Builder
|
|
||||||
for idx, attach := range content.PhotoMessage.GetPhotos() {
|
|
||||||
s.WriteString(attach.GetCaption().GetText())
|
|
||||||
s.WriteString("\n")
|
|
||||||
s.WriteString(b.c.TransformHMCURL(attach.GetHmc()))
|
|
||||||
if idx < len(content.PhotoMessage.GetPhotos())-1 {
|
|
||||||
s.WriteString("\n\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
message.Text = s.String()
|
|
||||||
case *chatv1.Content_TextMessage:
|
|
||||||
message.Text = content.TextMessage.Content.Text
|
|
||||||
}
|
|
||||||
|
|
||||||
return message
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bharmony) outputMessages() {
|
|
||||||
for {
|
|
||||||
msg := <-b.c.EventsStream()
|
|
||||||
|
|
||||||
if msg.Message.AuthorId == b.c.UserID {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Remote <- b.toMessage(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bharmony) GetUint64(conf string) uint64 {
|
|
||||||
num, err := strToU(b.GetString(conf))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return num
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bharmony) Connect() (err error) {
|
|
||||||
b.c, err = shibshib.NewClient(b.GetString("Homeserver"), b.GetString("Token"), b.GetUint64("UserID"))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
b.c.SubscribeToGuild(b.GetUint64("Community"))
|
|
||||||
|
|
||||||
go b.outputMessages()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bharmony) send(msg config.Message) (id string, err error) {
|
|
||||||
msgChan, err := strToU(msg.Channel)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
retID, err := b.c.ChatKit.SendMessage(&chatv1.SendMessageRequest{
|
|
||||||
GuildId: b.GetUint64("Community"),
|
|
||||||
ChannelId: msgChan,
|
|
||||||
Content: &chatv1.Content{
|
|
||||||
Content: &chatv1.Content_TextMessage{
|
|
||||||
TextMessage: &chatv1.Content_TextContent{
|
|
||||||
Content: &chatv1.FormattedText{
|
|
||||||
Text: msg.Text,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Overrides: &chatv1.Overrides{
|
|
||||||
Username: &msg.Username,
|
|
||||||
Avatar: &msg.Avatar,
|
|
||||||
Reason: &chatv1.Overrides_Bridge{Bridge: &typesv1.Empty{}},
|
|
||||||
},
|
|
||||||
InReplyTo: nil,
|
|
||||||
EchoId: nil,
|
|
||||||
Metadata: nil,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("send: error sending message: %w", err)
|
|
||||||
log.Println(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return uToStr(retID.MessageId), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bharmony) delete(msg config.Message) (id string, err error) {
|
|
||||||
msgChan, err := strToU(msg.Channel)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
msgID, err := strToU(msg.ID)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = b.c.ChatKit.DeleteMessage(&chatv1.DeleteMessageRequest{
|
|
||||||
GuildId: b.GetUint64("Community"),
|
|
||||||
ChannelId: msgChan,
|
|
||||||
MessageId: msgID,
|
|
||||||
})
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bharmony) typing(msg config.Message) (id string, err error) {
|
|
||||||
msgChan, err := strToU(msg.Channel)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = b.c.ChatKit.Typing(&chatv1.TypingRequest{
|
|
||||||
GuildId: b.GetUint64("Community"),
|
|
||||||
ChannelId: msgChan,
|
|
||||||
})
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bharmony) Send(msg config.Message) (id string, err error) {
|
|
||||||
switch msg.Event {
|
|
||||||
case "":
|
|
||||||
return b.send(msg)
|
|
||||||
case config.EventMsgDelete:
|
|
||||||
return b.delete(msg)
|
|
||||||
case config.EventUserTyping:
|
|
||||||
return b.typing(msg)
|
|
||||||
default:
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bharmony) JoinChannel(channel config.ChannelInfo) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bharmony) Disconnect() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image/png"
|
"image/png"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -13,7 +16,11 @@ import (
|
|||||||
|
|
||||||
"golang.org/x/image/webp"
|
"golang.org/x/image/webp"
|
||||||
|
|
||||||
|
"github.com/42wim/matterbridge/bridge"
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
|
"github.com/42wim/matterbridge/internal"
|
||||||
|
"github.com/d5/tengo/v2"
|
||||||
|
"github.com/d5/tengo/v2/stdlib"
|
||||||
"github.com/gomarkdown/markdown"
|
"github.com/gomarkdown/markdown"
|
||||||
"github.com/gomarkdown/markdown/html"
|
"github.com/gomarkdown/markdown/html"
|
||||||
"github.com/gomarkdown/markdown/parser"
|
"github.com/gomarkdown/markdown/parser"
|
||||||
@@ -48,30 +55,6 @@ func DownloadFileAuth(url string, auth string) (*[]byte, error) {
|
|||||||
return &data, nil
|
return &data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DownloadFileAuthRocket downloads the given URL using the specified Rocket user ID and authentication token.
|
|
||||||
func DownloadFileAuthRocket(url, token, userID string) (*[]byte, error) {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
client := &http.Client{
|
|
||||||
Timeout: time.Second * 5,
|
|
||||||
}
|
|
||||||
req, err := http.NewRequest("GET", url, nil)
|
|
||||||
|
|
||||||
req.Header.Add("X-Auth-Token", token)
|
|
||||||
req.Header.Add("X-User-Id", userID)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
_, err = io.Copy(&buf, resp.Body)
|
|
||||||
data := buf.Bytes()
|
|
||||||
return &data, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSubLines splits messages in newline-delimited lines. If maxLineLength is
|
// GetSubLines splits messages in newline-delimited lines. If maxLineLength is
|
||||||
// specified as non-zero GetSubLines will also clip long lines to the maximum
|
// specified as non-zero GetSubLines will also clip long lines to the maximum
|
||||||
// length and insert a warning marker that the line was clipped.
|
// length and insert a warning marker that the line was clipped.
|
||||||
@@ -79,10 +62,8 @@ func DownloadFileAuthRocket(url, token, userID string) (*[]byte, error) {
|
|||||||
// TODO: The current implementation has the inconvenient that it disregards
|
// TODO: The current implementation has the inconvenient that it disregards
|
||||||
// word boundaries when splitting but this is hard to solve without potentially
|
// word boundaries when splitting but this is hard to solve without potentially
|
||||||
// breaking formatting and other stylistic effects.
|
// breaking formatting and other stylistic effects.
|
||||||
func GetSubLines(message string, maxLineLength int, clippingMessage string) []string {
|
func GetSubLines(message string, maxLineLength int) []string {
|
||||||
if clippingMessage == "" {
|
const clippingMessage = " <clipped message>"
|
||||||
clippingMessage = " <clipped message>"
|
|
||||||
}
|
|
||||||
|
|
||||||
var lines []string
|
var lines []string
|
||||||
for _, line := range strings.Split(strings.TrimSpace(message), "\n") {
|
for _, line := range strings.Split(strings.TrimSpace(message), "\n") {
|
||||||
@@ -137,6 +118,62 @@ func GetAvatar(av map[string]string, userid string, general *config.Protocol) st
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleDownloadTengo(br *bridge.Bridge, msg *config.Message, name string, size int64, general *config.Protocol) (bool, error) {
|
||||||
|
var (
|
||||||
|
res []byte
|
||||||
|
err error
|
||||||
|
drop bool
|
||||||
|
)
|
||||||
|
|
||||||
|
filename := br.GetString("tengo.download")
|
||||||
|
|
||||||
|
if filename == "" {
|
||||||
|
res, err = internal.Asset("tengo/download.tengo")
|
||||||
|
if err != nil {
|
||||||
|
return drop, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res, err = ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return drop, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s := tengo.NewScript(res)
|
||||||
|
|
||||||
|
s.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...))
|
||||||
|
|
||||||
|
_ = s.Add("inAccount", msg.Account)
|
||||||
|
_ = s.Add("inProtocol", msg.Protocol)
|
||||||
|
_ = s.Add("inChannel", msg.Channel)
|
||||||
|
_ = s.Add("inGateway", msg.Gateway)
|
||||||
|
_ = s.Add("inEvent", msg.Event)
|
||||||
|
_ = s.Add("outAccount", br.Account)
|
||||||
|
_ = s.Add("outProtocol", br.Protocol)
|
||||||
|
_ = s.Add("outChannel", msg.Channel)
|
||||||
|
_ = s.Add("outEvent", msg.Event)
|
||||||
|
_ = s.Add("msgText", msg.Text)
|
||||||
|
_ = s.Add("msgUsername", msg.Username)
|
||||||
|
_ = s.Add("msgDrop", drop)
|
||||||
|
_ = s.Add("downloadName", name)
|
||||||
|
_ = s.Add("downloadSize", size)
|
||||||
|
|
||||||
|
c, err := s.Compile()
|
||||||
|
if err != nil {
|
||||||
|
return drop, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Run(); err != nil {
|
||||||
|
return drop, err
|
||||||
|
}
|
||||||
|
|
||||||
|
drop = c.Get("msgDrop").Bool()
|
||||||
|
msg.Text = c.Get("msgText").String()
|
||||||
|
msg.Username = c.Get("msgUsername").String()
|
||||||
|
|
||||||
|
return drop, nil
|
||||||
|
}
|
||||||
|
|
||||||
// HandleDownloadSize checks a specified filename against the configured download blacklist
|
// HandleDownloadSize checks a specified filename against the configured download blacklist
|
||||||
// and checks a specified file-size against the configure limit.
|
// and checks a specified file-size against the configure limit.
|
||||||
func HandleDownloadSize(logger *logrus.Entry, msg *config.Message, name string, size int64, general *config.Protocol) error {
|
func HandleDownloadSize(logger *logrus.Entry, msg *config.Message, name string, size int64, general *config.Protocol) error {
|
||||||
@@ -168,23 +205,17 @@ func HandleDownloadSize(logger *logrus.Entry, msg *config.Message, name string,
|
|||||||
|
|
||||||
// HandleDownloadData adds the data for a remote file into a Matterbridge gateway message.
|
// HandleDownloadData adds the data for a remote file into a Matterbridge gateway message.
|
||||||
func HandleDownloadData(logger *logrus.Entry, msg *config.Message, name, comment, url string, data *[]byte, general *config.Protocol) {
|
func HandleDownloadData(logger *logrus.Entry, msg *config.Message, name, comment, url string, data *[]byte, general *config.Protocol) {
|
||||||
HandleDownloadData2(logger, msg, name, "", comment, url, data, general)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleDownloadData adds the data for a remote file into a Matterbridge gateway message.
|
|
||||||
func HandleDownloadData2(logger *logrus.Entry, msg *config.Message, name, id, comment, url string, data *[]byte, general *config.Protocol) {
|
|
||||||
var avatar bool
|
var avatar bool
|
||||||
logger.Debugf("Download OK %#v %#v", name, len(*data))
|
logger.Debugf("Download OK %#v %#v", name, len(*data))
|
||||||
if msg.Event == config.EventAvatarDownload {
|
if msg.Event == config.EventAvatarDownload {
|
||||||
avatar = true
|
avatar = true
|
||||||
}
|
}
|
||||||
msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{
|
msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{
|
||||||
Name: name,
|
Name: name,
|
||||||
Data: data,
|
Data: data,
|
||||||
URL: url,
|
URL: url,
|
||||||
Comment: comment,
|
Comment: comment,
|
||||||
Avatar: avatar,
|
Avatar: avatar,
|
||||||
NativeID: id,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,11 +229,8 @@ func RemoveEmptyNewLines(msg string) string {
|
|||||||
|
|
||||||
// ClipMessage trims a message to the specified length if it exceeds it and adds a warning
|
// ClipMessage trims a message to the specified length if it exceeds it and adds a warning
|
||||||
// to the message in case it does so.
|
// to the message in case it does so.
|
||||||
func ClipMessage(text string, length int, clippingMessage string) string {
|
func ClipMessage(text string, length int) string {
|
||||||
if clippingMessage == "" {
|
const clippingMessage = " <clipped message>"
|
||||||
clippingMessage = " <clipped message>"
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(text) > length {
|
if len(text) > length {
|
||||||
text = text[:length-len(clippingMessage)]
|
text = text[:length-len(clippingMessage)]
|
||||||
if r, size := utf8.DecodeLastRuneInString(text); r == utf8.RuneError {
|
if r, size := utf8.DecodeLastRuneInString(text); r == utf8.RuneError {
|
||||||
@@ -215,7 +243,7 @@ func ClipMessage(text string, length int, clippingMessage string) string {
|
|||||||
|
|
||||||
// ParseMarkdown takes in an input string as markdown and parses it to html
|
// ParseMarkdown takes in an input string as markdown and parses it to html
|
||||||
func ParseMarkdown(input string) string {
|
func ParseMarkdown(input string) string {
|
||||||
extensions := parser.HardLineBreak | parser.NoIntraEmphasis | parser.FencedCode
|
extensions := parser.HardLineBreak | parser.NoIntraEmphasis
|
||||||
markdownParser := parser.NewWithExtensions(extensions)
|
markdownParser := parser.NewWithExtensions(extensions)
|
||||||
renderer := html.NewRenderer(html.RendererOptions{
|
renderer := html.NewRenderer(html.RendererOptions{
|
||||||
Flags: 0,
|
Flags: 0,
|
||||||
@@ -242,3 +270,49 @@ func ConvertWebPToPNG(data *[]byte) error {
|
|||||||
*data = w.Bytes()
|
*data = w.Bytes()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CanConvertTgsToX Checks whether the external command necessary for ConvertTgsToX works.
|
||||||
|
func CanConvertTgsToX() error {
|
||||||
|
// We depend on the fact that `lottie_convert.py --help` has exit status 0.
|
||||||
|
// Hyrum's Law predicted this, and Murphy's Law predicts that this will break eventually.
|
||||||
|
// However, there is no alternative like `lottie_convert.py --is-properly-installed`
|
||||||
|
cmd := exec.Command("lottie_convert.py", "--help")
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertTgsToWebP convert input data (which should be tgs format) to WebP format
|
||||||
|
// This relies on an external command, which is ugly, but works.
|
||||||
|
func ConvertTgsToX(data *[]byte, outputFormat string, logger *logrus.Entry) error {
|
||||||
|
// lottie can't handle input from a pipe, so write to a temporary file:
|
||||||
|
tmpFile, err := ioutil.TempFile(os.TempDir(), "matterbridge-lottie-*.tgs")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tmpFileName := tmpFile.Name()
|
||||||
|
defer func() {
|
||||||
|
if removeErr := os.Remove(tmpFileName); removeErr != nil {
|
||||||
|
logger.Errorf("Could not delete temporary file %s: %v", tmpFileName, removeErr)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if _, writeErr := tmpFile.Write(*data); writeErr != nil {
|
||||||
|
return writeErr
|
||||||
|
}
|
||||||
|
// Must close before calling lottie to avoid data races:
|
||||||
|
if closeErr := tmpFile.Close(); closeErr != nil {
|
||||||
|
return closeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call lottie to transform:
|
||||||
|
cmd := exec.Command("lottie_convert.py", "--input-format", "lottie", "--output-format", outputFormat, tmpFileName, "/dev/stdout")
|
||||||
|
cmd.Stderr = nil
|
||||||
|
// NB: lottie writes progress into to stderr in all cases.
|
||||||
|
stdout, stderr := cmd.Output()
|
||||||
|
if stderr != nil {
|
||||||
|
// 'stderr' already contains some parts of Stderr, because it was set to 'nil'.
|
||||||
|
return stderr
|
||||||
|
}
|
||||||
|
|
||||||
|
*data = stdout
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,96 +10,98 @@ import (
|
|||||||
|
|
||||||
const testLineLength = 64
|
const testLineLength = 64
|
||||||
|
|
||||||
var lineSplittingTestCases = map[string]struct {
|
var (
|
||||||
input string
|
lineSplittingTestCases = map[string]struct {
|
||||||
splitOutput []string
|
input string
|
||||||
nonSplitOutput []string
|
splitOutput []string
|
||||||
}{
|
nonSplitOutput []string
|
||||||
"Short single-line message": {
|
}{
|
||||||
input: "short",
|
"Short single-line message": {
|
||||||
splitOutput: []string{"short"},
|
input: "short",
|
||||||
nonSplitOutput: []string{"short"},
|
splitOutput: []string{"short"},
|
||||||
},
|
nonSplitOutput: []string{"short"},
|
||||||
"Long single-line message": {
|
|
||||||
input: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
|
||||||
splitOutput: []string{
|
|
||||||
"Lorem ipsum dolor sit amet, consectetur adipis <clipped message>",
|
|
||||||
"cing elit, sed do eiusmod tempor incididunt ut <clipped message>",
|
|
||||||
" labore et dolore magna aliqua.",
|
|
||||||
},
|
},
|
||||||
nonSplitOutput: []string{"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."},
|
"Long single-line message": {
|
||||||
},
|
input: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
||||||
"Short multi-line message": {
|
splitOutput: []string{
|
||||||
input: "I\ncan't\nget\nno\nsatisfaction!",
|
"Lorem ipsum dolor sit amet, consectetur adipis <clipped message>",
|
||||||
splitOutput: []string{
|
"cing elit, sed do eiusmod tempor incididunt ut <clipped message>",
|
||||||
"I",
|
" labore et dolore magna aliqua.",
|
||||||
"can't",
|
},
|
||||||
"get",
|
nonSplitOutput: []string{"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."},
|
||||||
"no",
|
|
||||||
"satisfaction!",
|
|
||||||
},
|
},
|
||||||
nonSplitOutput: []string{
|
"Short multi-line message": {
|
||||||
"I",
|
input: "I\ncan't\nget\nno\nsatisfaction!",
|
||||||
"can't",
|
splitOutput: []string{
|
||||||
"get",
|
"I",
|
||||||
"no",
|
"can't",
|
||||||
"satisfaction!",
|
"get",
|
||||||
|
"no",
|
||||||
|
"satisfaction!",
|
||||||
|
},
|
||||||
|
nonSplitOutput: []string{
|
||||||
|
"I",
|
||||||
|
"can't",
|
||||||
|
"get",
|
||||||
|
"no",
|
||||||
|
"satisfaction!",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
"Long multi-line message": {
|
||||||
"Long multi-line message": {
|
input: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n" +
|
||||||
input: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n" +
|
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n" +
|
||||||
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n" +
|
"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n" +
|
||||||
"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n" +
|
"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
|
||||||
"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
|
splitOutput: []string{
|
||||||
splitOutput: []string{
|
"Lorem ipsum dolor sit amet, consectetur adipis <clipped message>",
|
||||||
"Lorem ipsum dolor sit amet, consectetur adipis <clipped message>",
|
"cing elit, sed do eiusmod tempor incididunt ut <clipped message>",
|
||||||
"cing elit, sed do eiusmod tempor incididunt ut <clipped message>",
|
" labore et dolore magna aliqua.",
|
||||||
" labore et dolore magna aliqua.",
|
"Ut enim ad minim veniam, quis nostrud exercita <clipped message>",
|
||||||
"Ut enim ad minim veniam, quis nostrud exercita <clipped message>",
|
"tion ullamco laboris nisi ut aliquip ex ea com <clipped message>",
|
||||||
"tion ullamco laboris nisi ut aliquip ex ea com <clipped message>",
|
"modo consequat.",
|
||||||
"modo consequat.",
|
"Duis aute irure dolor in reprehenderit in volu <clipped message>",
|
||||||
"Duis aute irure dolor in reprehenderit in volu <clipped message>",
|
"ptate velit esse cillum dolore eu fugiat nulla <clipped message>",
|
||||||
"ptate velit esse cillum dolore eu fugiat nulla <clipped message>",
|
" pariatur.",
|
||||||
" pariatur.",
|
"Excepteur sint occaecat cupidatat non proident <clipped message>",
|
||||||
"Excepteur sint occaecat cupidatat non proident <clipped message>",
|
", sunt in culpa qui officia deserunt mollit an <clipped message>",
|
||||||
", sunt in culpa qui officia deserunt mollit an <clipped message>",
|
"im id est laborum.",
|
||||||
"im id est laborum.",
|
},
|
||||||
|
nonSplitOutput: []string{
|
||||||
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
||||||
|
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
|
||||||
|
"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
|
||||||
|
"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
nonSplitOutput: []string{
|
"Message ending with new-line.": {
|
||||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
input: "Newline ending\n",
|
||||||
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
|
splitOutput: []string{"Newline ending"},
|
||||||
"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
|
nonSplitOutput: []string{"Newline ending"},
|
||||||
"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
|
|
||||||
},
|
},
|
||||||
},
|
"Long message containing UTF-8 multi-byte runes": {
|
||||||
"Message ending with new-line.": {
|
input: "不布人個我此而及單石業喜資富下我河下日沒一我臺空達的常景便物沒為……子大我別名解成?生賣的全直黑,我自我結毛分洲了世當,是政福那是東;斯說",
|
||||||
input: "Newline ending\n",
|
splitOutput: []string{
|
||||||
splitOutput: []string{"Newline ending"},
|
"不布人個我此而及單石業喜資富下 <clipped message>",
|
||||||
nonSplitOutput: []string{"Newline ending"},
|
"我河下日沒一我臺空達的常景便物 <clipped message>",
|
||||||
},
|
"沒為……子大我別名解成?生賣的 <clipped message>",
|
||||||
"Long message containing UTF-8 multi-byte runes": {
|
"全直黑,我自我結毛分洲了世當, <clipped message>",
|
||||||
input: "不布人個我此而及單石業喜資富下我河下日沒一我臺空達的常景便物沒為……子大我別名解成?生賣的全直黑,我自我結毛分洲了世當,是政福那是東;斯說",
|
"是政福那是東;斯說",
|
||||||
splitOutput: []string{
|
},
|
||||||
"不布人個我此而及單石業喜資富下 <clipped message>",
|
nonSplitOutput: []string{"不布人個我此而及單石業喜資富下我河下日沒一我臺空達的常景便物沒為……子大我別名解成?生賣的全直黑,我自我結毛分洲了世當,是政福那是東;斯說"},
|
||||||
"我河下日沒一我臺空達的常景便物 <clipped message>",
|
|
||||||
"沒為……子大我別名解成?生賣的 <clipped message>",
|
|
||||||
"全直黑,我自我結毛分洲了世當, <clipped message>",
|
|
||||||
"是政福那是東;斯說",
|
|
||||||
},
|
},
|
||||||
nonSplitOutput: []string{"不布人個我此而及單石業喜資富下我河下日沒一我臺空達的常景便物沒為……子大我別名解成?生賣的全直黑,我自我結毛分洲了世當,是政福那是東;斯說"},
|
}
|
||||||
},
|
)
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetSubLines(t *testing.T) {
|
func TestGetSubLines(t *testing.T) {
|
||||||
for testname, testcase := range lineSplittingTestCases {
|
for testname, testcase := range lineSplittingTestCases {
|
||||||
splitLines := GetSubLines(testcase.input, testLineLength, "")
|
splitLines := GetSubLines(testcase.input, testLineLength)
|
||||||
assert.Equalf(t, testcase.splitOutput, splitLines, "'%s' testcase should give expected lines with splitting.", testname)
|
assert.Equalf(t, testcase.splitOutput, splitLines, "'%s' testcase should give expected lines with splitting.", testname)
|
||||||
for _, splitLine := range splitLines {
|
for _, splitLine := range splitLines {
|
||||||
byteLength := len([]byte(splitLine))
|
byteLength := len([]byte(splitLine))
|
||||||
assert.True(t, byteLength <= testLineLength, "Splitted line '%s' of testcase '%s' should not exceed the maximum byte-length (%d vs. %d).", splitLine, testcase, byteLength, testLineLength)
|
assert.True(t, byteLength <= testLineLength, "Splitted line '%s' of testcase '%s' should not exceed the maximum byte-length (%d vs. %d).", splitLine, testcase, byteLength, testLineLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
nonSplitLines := GetSubLines(testcase.input, 0, "")
|
nonSplitLines := GetSubLines(testcase.input, 0)
|
||||||
assert.Equalf(t, testcase.nonSplitOutput, nonSplitLines, "'%s' testcase should give expected lines without splitting.", testname)
|
assert.Equalf(t, testcase.nonSplitOutput, nonSplitLines, "'%s' testcase should give expected lines without splitting.", testname)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,19 +110,16 @@ func TestConvertWebPToPNG(t *testing.T) {
|
|||||||
if os.Getenv("LOCAL_TEST") == "" {
|
if os.Getenv("LOCAL_TEST") == "" {
|
||||||
t.Skip()
|
t.Skip()
|
||||||
}
|
}
|
||||||
|
|
||||||
input, err := ioutil.ReadFile("test.webp")
|
input, err := ioutil.ReadFile("test.webp")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
|
|
||||||
d := &input
|
d := &input
|
||||||
err = ConvertWebPToPNG(d)
|
err = ConvertWebPToPNG(d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
|
err = ioutil.WriteFile("test.png", *d, 0644)
|
||||||
err = ioutil.WriteFile("test.png", *d, 0o644) // nolint:gosec
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
//go:build cgo
|
|
||||||
// +build cgo
|
|
||||||
|
|
||||||
package helper
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/Benau/tgsconverter/libtgsconverter"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func CanConvertTgsToX() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConvertTgsToX convert input data (which should be tgs format) to any format supported by libtgsconverter
|
|
||||||
func ConvertTgsToX(data *[]byte, outputFormat string, logger *logrus.Entry) error {
|
|
||||||
options := libtgsconverter.NewConverterOptions()
|
|
||||||
options.SetExtension(outputFormat)
|
|
||||||
blob, err := libtgsconverter.ImportFromData(*data, options)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to run libtgsconverter.ImportFromData: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
*data = blob
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SupportsFormat(format string) bool {
|
|
||||||
return libtgsconverter.SupportsExtension(format)
|
|
||||||
}
|
|
||||||
|
|
||||||
func LottieBackend() string {
|
|
||||||
return "libtgsconverter"
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
// +build !cgo
|
|
||||||
|
|
||||||
package helper
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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:
|
|
||||||
tmpInFile, err := ioutil.TempFile(os.TempDir(), "matterbridge-lottie-input-*.tgs")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tmpInFileName := tmpInFile.Name()
|
|
||||||
defer func() {
|
|
||||||
if removeErr := os.Remove(tmpInFileName); removeErr != nil {
|
|
||||||
logger.Errorf("Could not delete temporary (input) file %s: %v", tmpInFileName, removeErr)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
// lottie can handle writing to a pipe, but there is no way to do that platform-independently.
|
|
||||||
// "/dev/stdout" won't work on Windows, and "-" upsets Cairo for some reason. So we need another file:
|
|
||||||
tmpOutFile, err := ioutil.TempFile(os.TempDir(), "matterbridge-lottie-output-*.data")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tmpOutFileName := tmpOutFile.Name()
|
|
||||||
defer func() {
|
|
||||||
if removeErr := os.Remove(tmpOutFileName); removeErr != nil {
|
|
||||||
logger.Errorf("Could not delete temporary (output) file %s: %v", tmpOutFileName, removeErr)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if _, writeErr := tmpInFile.Write(*data); writeErr != nil {
|
|
||||||
return writeErr
|
|
||||||
}
|
|
||||||
// Must close before calling lottie to avoid data races:
|
|
||||||
if closeErr := tmpInFile.Close(); closeErr != nil {
|
|
||||||
return closeErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call lottie to transform:
|
|
||||||
cmd := exec.Command("lottie_convert.py", "--input-format", "lottie", "--output-format", outputFormat, tmpInFileName, tmpOutFileName)
|
|
||||||
cmd.Stdout = nil
|
|
||||||
cmd.Stderr = nil
|
|
||||||
// NB: lottie writes progress into to stderr in all cases.
|
|
||||||
_, stderr := cmd.Output()
|
|
||||||
if stderr != nil {
|
|
||||||
// 'stderr' already contains some parts of Stderr, because it was set to 'nil'.
|
|
||||||
return stderr
|
|
||||||
}
|
|
||||||
dataContents, err := ioutil.ReadFile(tmpOutFileName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
*data = dataContents
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SupportsFormat(format string) bool {
|
|
||||||
switch format {
|
|
||||||
case "png":
|
|
||||||
fallthrough
|
|
||||||
case "webp":
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func LottieBackend() string {
|
|
||||||
return "lottie_convert.py"
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package birc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/text/encoding"
|
|
||||||
"golang.org/x/text/encoding/japanese"
|
|
||||||
"golang.org/x/text/encoding/korean"
|
|
||||||
"golang.org/x/text/encoding/simplifiedchinese"
|
|
||||||
"golang.org/x/text/encoding/traditionalchinese"
|
|
||||||
"golang.org/x/text/encoding/unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
var encoders = map[string]encoding.Encoding{
|
|
||||||
"utf-8": unicode.UTF8,
|
|
||||||
"iso-2022-jp": japanese.ISO2022JP,
|
|
||||||
"big5": traditionalchinese.Big5,
|
|
||||||
"gbk": simplifiedchinese.GBK,
|
|
||||||
"euc-kr": korean.EUCKR,
|
|
||||||
"gb2312": simplifiedchinese.HZGB2312,
|
|
||||||
"shift-jis": japanese.ShiftJIS,
|
|
||||||
"euc-jp": japanese.EUCJP,
|
|
||||||
"gb18030": simplifiedchinese.GB18030,
|
|
||||||
}
|
|
||||||
|
|
||||||
func toUTF8(from string, input string) string {
|
|
||||||
enc, ok := encoders[from]
|
|
||||||
if !ok {
|
|
||||||
return input
|
|
||||||
}
|
|
||||||
|
|
||||||
res, _ := enc.NewDecoder().String(input)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
"github.com/lrstanley/girc"
|
"github.com/lrstanley/girc"
|
||||||
|
"github.com/missdeer/golib/ic"
|
||||||
"github.com/paulrosania/go-charset/charset"
|
"github.com/paulrosania/go-charset/charset"
|
||||||
"github.com/saintfish/chardet"
|
"github.com/saintfish/chardet"
|
||||||
|
|
||||||
@@ -23,12 +24,12 @@ func (b *Birc) handleCharset(msg *config.Message) error {
|
|||||||
if b.GetString("Charset") != "" {
|
if b.GetString("Charset") != "" {
|
||||||
switch b.GetString("Charset") {
|
switch b.GetString("Charset") {
|
||||||
case "gbk", "gb18030", "gb2312", "big5", "euc-kr", "euc-jp", "shift-jis", "iso-2022-jp":
|
case "gbk", "gb18030", "gb2312", "big5", "euc-kr", "euc-jp", "shift-jis", "iso-2022-jp":
|
||||||
msg.Text = toUTF8(b.GetString("Charset"), msg.Text)
|
msg.Text = ic.ConvertString("utf-8", b.GetString("Charset"), msg.Text)
|
||||||
default:
|
default:
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
w, err := charset.NewWriter(b.GetString("Charset"), buf)
|
w, err := charset.NewWriter(b.GetString("Charset"), buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Errorf("charset to utf-8 conversion failed: %s", err)
|
b.Log.Errorf("charset from utf-8 conversion failed: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprint(w, msg.Text)
|
fmt.Fprint(w, msg.Text)
|
||||||
@@ -66,20 +67,6 @@ func (b *Birc) handleFiles(msg *config.Message) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Birc) handleInvite(client *girc.Client, event girc.Event) {
|
|
||||||
if len(event.Params) != 2 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
channel := event.Params[1]
|
|
||||||
|
|
||||||
b.Log.Debugf("got invite for %s", channel)
|
|
||||||
|
|
||||||
if _, ok := b.channels[channel]; ok {
|
|
||||||
b.i.Cmd.Join(channel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Birc) handleJoinPart(client *girc.Client, event girc.Event) {
|
func (b *Birc) handleJoinPart(client *girc.Client, event girc.Event) {
|
||||||
if len(event.Params) == 0 {
|
if len(event.Params) == 0 {
|
||||||
b.Log.Debugf("handleJoinPart: empty Params? %#v", event)
|
b.Log.Debugf("handleJoinPart: empty Params? %#v", event)
|
||||||
@@ -122,15 +109,14 @@ func (b *Birc) handleNewConnection(client *girc.Client, event girc.Event) {
|
|||||||
i := b.i
|
i := b.i
|
||||||
b.Nick = event.Params[0]
|
b.Nick = event.Params[0]
|
||||||
|
|
||||||
i.Handlers.AddBg("PRIVMSG", b.handlePrivMsg)
|
i.Handlers.Add("PRIVMSG", b.handlePrivMsg)
|
||||||
i.Handlers.AddBg("CTCP_ACTION", b.handlePrivMsg)
|
i.Handlers.Add("CTCP_ACTION", b.handlePrivMsg)
|
||||||
i.Handlers.Add(girc.RPL_TOPICWHOTIME, b.handleTopicWhoTime)
|
i.Handlers.Add(girc.RPL_TOPICWHOTIME, b.handleTopicWhoTime)
|
||||||
i.Handlers.AddBg(girc.NOTICE, b.handleNotice)
|
i.Handlers.Add(girc.NOTICE, b.handleNotice)
|
||||||
i.Handlers.AddBg("JOIN", b.handleJoinPart)
|
i.Handlers.Add("JOIN", b.handleJoinPart)
|
||||||
i.Handlers.AddBg("PART", b.handleJoinPart)
|
i.Handlers.Add("PART", b.handleJoinPart)
|
||||||
i.Handlers.AddBg("QUIT", b.handleJoinPart)
|
i.Handlers.Add("QUIT", b.handleJoinPart)
|
||||||
i.Handlers.AddBg("KICK", b.handleJoinPart)
|
i.Handlers.Add("KICK", b.handleJoinPart)
|
||||||
i.Handlers.Add("INVITE", b.handleInvite)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Birc) handleNickServ() {
|
func (b *Birc) handleNickServ() {
|
||||||
@@ -226,7 +212,7 @@ func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) {
|
|||||||
}
|
}
|
||||||
switch mycharset {
|
switch mycharset {
|
||||||
case "gbk", "gb18030", "gb2312", "big5", "euc-kr", "euc-jp", "shift-jis", "iso-2022-jp":
|
case "gbk", "gb18030", "gb2312", "big5", "euc-kr", "euc-jp", "shift-jis", "iso-2022-jp":
|
||||||
rmsg.Text = toUTF8(b.GetString("Charset"), rmsg.Text)
|
rmsg.Text = ic.ConvertString("utf-8", b.GetString("Charset"), rmsg.Text)
|
||||||
default:
|
default:
|
||||||
r, err := charset.NewReader(mycharset, strings.NewReader(rmsg.Text))
|
r, err := charset.NewReader(mycharset, strings.NewReader(rmsg.Text))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package birc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/crc32"
|
"hash/crc32"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@@ -31,7 +30,6 @@ type Birc struct {
|
|||||||
Local chan config.Message // local queue for flood control
|
Local chan config.Message // local queue for flood control
|
||||||
FirstConnection, authDone bool
|
FirstConnection, authDone bool
|
||||||
MessageDelay, MessageQueue, MessageLength int
|
MessageDelay, MessageQueue, MessageLength int
|
||||||
channels map[string]bool
|
|
||||||
|
|
||||||
*bridge.Config
|
*bridge.Config
|
||||||
}
|
}
|
||||||
@@ -42,8 +40,6 @@ func New(cfg *bridge.Config) bridge.Bridger {
|
|||||||
b.Nick = b.GetString("Nick")
|
b.Nick = b.GetString("Nick")
|
||||||
b.names = make(map[string][]string)
|
b.names = make(map[string][]string)
|
||||||
b.connected = make(chan error)
|
b.connected = make(chan error)
|
||||||
b.channels = make(map[string]bool)
|
|
||||||
|
|
||||||
if b.GetInt("MessageDelay") == 0 {
|
if b.GetInt("MessageDelay") == 0 {
|
||||||
b.MessageDelay = 1300
|
b.MessageDelay = 1300
|
||||||
} else {
|
} else {
|
||||||
@@ -73,10 +69,6 @@ func (b *Birc) Command(msg *config.Message) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Birc) Connect() error {
|
func (b *Birc) Connect() error {
|
||||||
if b.GetBool("UseSASL") && b.GetString("TLSClientCertificate") != "" {
|
|
||||||
return errors.New("you can't enable SASL and TLSClientCertificate at the same time")
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Local = make(chan config.Message, b.MessageQueue+10)
|
b.Local = make(chan config.Message, b.MessageQueue+10)
|
||||||
b.Log.Infof("Connecting %s", b.GetString("Server"))
|
b.Log.Infof("Connecting %s", b.GetString("Server"))
|
||||||
|
|
||||||
@@ -120,7 +112,6 @@ func (b *Birc) Disconnect() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Birc) JoinChannel(channel config.ChannelInfo) error {
|
func (b *Birc) JoinChannel(channel config.ChannelInfo) error {
|
||||||
b.channels[channel.Name] = true
|
|
||||||
// need to check if we have nickserv auth done before joining channels
|
// need to check if we have nickserv auth done before joining channels
|
||||||
for {
|
for {
|
||||||
if b.authDone {
|
if b.authDone {
|
||||||
@@ -172,9 +163,9 @@ func (b *Birc) Send(msg config.Message) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if b.GetBool("MessageSplit") {
|
if b.GetBool("MessageSplit") {
|
||||||
msgLines = helper.GetSubLines(msg.Text, b.MessageLength, b.GetString("MessageClipped"))
|
msgLines = helper.GetSubLines(msg.Text, b.MessageLength)
|
||||||
} else {
|
} else {
|
||||||
msgLines = helper.GetSubLines(msg.Text, 0, b.GetString("MessageClipped"))
|
msgLines = helper.GetSubLines(msg.Text, 0)
|
||||||
}
|
}
|
||||||
for i := range msgLines {
|
for i := range msgLines {
|
||||||
if len(b.Local) >= b.MessageQueue {
|
if len(b.Local) >= b.MessageQueue {
|
||||||
@@ -210,58 +201,27 @@ func (b *Birc) doConnect() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanitize nicks for RELAYMSG: replace IRC characters with special meanings with "-"
|
|
||||||
func sanitizeNick(nick string) string {
|
|
||||||
sanitize := func(r rune) rune {
|
|
||||||
if strings.ContainsRune("!+%@&#$:'\"?*,. ", r) {
|
|
||||||
return '-'
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
return strings.Map(sanitize, nick)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Birc) doSend() {
|
func (b *Birc) doSend() {
|
||||||
rate := time.Millisecond * time.Duration(b.MessageDelay)
|
rate := time.Millisecond * time.Duration(b.MessageDelay)
|
||||||
throttle := time.NewTicker(rate)
|
throttle := time.NewTicker(rate)
|
||||||
for msg := range b.Local {
|
for msg := range b.Local {
|
||||||
<-throttle.C
|
<-throttle.C
|
||||||
username := msg.Username
|
username := msg.Username
|
||||||
// Optional support for the proposed RELAYMSG extension, described at
|
if b.GetBool("Colornicks") && len(username) > 1 {
|
||||||
// https://github.com/jlu5/ircv3-specifications/blob/master/extensions/relaymsg.md
|
checksum := crc32.ChecksumIEEE([]byte(msg.Username))
|
||||||
// nolint:nestif
|
colorCode := checksum%14 + 2 // quick fix - prevent white or black color codes
|
||||||
if (b.i.HasCapability("overdrivenetworks.com/relaymsg") || b.i.HasCapability("draft/relaymsg")) &&
|
username = fmt.Sprintf("\x03%02d%s\x0F", colorCode, msg.Username)
|
||||||
b.GetBool("UseRelayMsg") {
|
}
|
||||||
username = sanitizeNick(username)
|
|
||||||
text := msg.Text
|
|
||||||
|
|
||||||
// Work around girc chomping leading commas on single word messages?
|
switch msg.Event {
|
||||||
if strings.HasPrefix(text, ":") && !strings.ContainsRune(text, ' ') {
|
case config.EventUserAction:
|
||||||
text = ":" + text
|
b.i.Cmd.Action(msg.Channel, username+msg.Text)
|
||||||
}
|
case config.EventNoticeIRC:
|
||||||
|
b.Log.Debugf("Sending notice to channel %s", msg.Channel)
|
||||||
if msg.Event == config.EventUserAction {
|
b.i.Cmd.Notice(msg.Channel, username+msg.Text)
|
||||||
b.i.Cmd.SendRawf("RELAYMSG %s %s :\x01ACTION %s\x01", msg.Channel, username, text) //nolint:errcheck
|
default:
|
||||||
} else {
|
b.Log.Debugf("Sending to channel %s", msg.Channel)
|
||||||
b.Log.Debugf("Sending RELAYMSG to channel %s: nick=%s", msg.Channel, username)
|
b.i.Cmd.Message(msg.Channel, username+msg.Text)
|
||||||
b.i.Cmd.SendRawf("RELAYMSG %s %s :%s", msg.Channel, username, text) //nolint:errcheck
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
switch msg.Event {
|
|
||||||
case config.EventUserAction:
|
|
||||||
b.i.Cmd.Action(msg.Channel, username+msg.Text)
|
|
||||||
case config.EventNoticeIRC:
|
|
||||||
b.Log.Debugf("Sending notice to channel %s", msg.Channel)
|
|
||||||
b.i.Cmd.Notice(msg.Channel, username+msg.Text)
|
|
||||||
default:
|
|
||||||
b.Log.Debugf("Sending to channel %s", msg.Channel)
|
|
||||||
b.i.Cmd.Message(msg.Channel, username+msg.Text)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -276,11 +236,8 @@ func (b *Birc) getClient() (*girc.Client, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
user := b.GetString("UserName")
|
|
||||||
if user == "" {
|
|
||||||
user = b.GetString("Nick")
|
|
||||||
}
|
|
||||||
// fix strict user handling of girc
|
// fix strict user handling of girc
|
||||||
|
user := b.GetString("Nick")
|
||||||
for !girc.IsValidUser(user) {
|
for !girc.IsValidUser(user) {
|
||||||
if len(user) == 1 || len(user) == 0 {
|
if len(user) == 1 || len(user) == 0 {
|
||||||
user = "matterbridge"
|
user = "matterbridge"
|
||||||
@@ -288,10 +245,6 @@ func (b *Birc) getClient() (*girc.Client, error) {
|
|||||||
}
|
}
|
||||||
user = user[1:]
|
user = user[1:]
|
||||||
}
|
}
|
||||||
realName := b.GetString("RealName")
|
|
||||||
if realName == "" {
|
|
||||||
realName = b.GetString("Nick")
|
|
||||||
}
|
|
||||||
|
|
||||||
debug := ioutil.Discard
|
debug := ioutil.Discard
|
||||||
if b.GetInt("DebugLevel") == 2 {
|
if b.GetInt("DebugLevel") == 2 {
|
||||||
@@ -305,26 +258,19 @@ func (b *Birc) getClient() (*girc.Client, error) {
|
|||||||
|
|
||||||
b.Log.Debugf("setting pingdelay to %s", pingDelay)
|
b.Log.Debugf("setting pingdelay to %s", pingDelay)
|
||||||
|
|
||||||
tlsConfig, err := b.getTLSConfig()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
i := girc.New(girc.Config{
|
i := girc.New(girc.Config{
|
||||||
Server: server,
|
Server: server,
|
||||||
ServerPass: b.GetString("Password"),
|
ServerPass: b.GetString("Password"),
|
||||||
Port: port,
|
Port: port,
|
||||||
Nick: b.GetString("Nick"),
|
Nick: b.GetString("Nick"),
|
||||||
User: user,
|
User: user,
|
||||||
Name: realName,
|
Name: b.GetString("Nick"),
|
||||||
SSL: b.GetBool("UseTLS"),
|
SSL: b.GetBool("UseTLS"),
|
||||||
Bind: b.GetString("Bind"),
|
TLSConfig: &tls.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), ServerName: server}, //nolint:gosec
|
||||||
TLSConfig: tlsConfig,
|
|
||||||
PingDelay: pingDelay,
|
PingDelay: pingDelay,
|
||||||
// skip gIRC internal rate limiting, since we have our own throttling
|
// skip gIRC internal rate limiting, since we have our own throttling
|
||||||
AllowFlood: true,
|
AllowFlood: true,
|
||||||
Debug: debug,
|
Debug: debug,
|
||||||
SupportedCaps: map[string][]string{"overdrivenetworks.com/relaymsg": nil, "draft/relaymsg": nil},
|
|
||||||
})
|
})
|
||||||
return i, nil
|
return i, nil
|
||||||
}
|
}
|
||||||
@@ -334,16 +280,12 @@ func (b *Birc) endNames(client *girc.Client, event girc.Event) {
|
|||||||
sort.Strings(b.names[channel])
|
sort.Strings(b.names[channel])
|
||||||
maxNamesPerPost := (300 / b.nicksPerRow()) * b.nicksPerRow()
|
maxNamesPerPost := (300 / b.nicksPerRow()) * b.nicksPerRow()
|
||||||
for len(b.names[channel]) > maxNamesPerPost {
|
for len(b.names[channel]) > maxNamesPerPost {
|
||||||
b.Remote <- config.Message{
|
b.Remote <- config.Message{Username: b.Nick, Text: b.formatnicks(b.names[channel][0:maxNamesPerPost]),
|
||||||
Username: b.Nick, Text: b.formatnicks(b.names[channel][0:maxNamesPerPost]),
|
Channel: channel, Account: b.Account}
|
||||||
Channel: channel, Account: b.Account,
|
|
||||||
}
|
|
||||||
b.names[channel] = b.names[channel][maxNamesPerPost:]
|
b.names[channel] = b.names[channel][maxNamesPerPost:]
|
||||||
}
|
}
|
||||||
b.Remote <- config.Message{
|
b.Remote <- config.Message{Username: b.Nick, Text: b.formatnicks(b.names[channel]),
|
||||||
Username: b.Nick, Text: b.formatnicks(b.names[channel]),
|
Channel: channel, Account: b.Account}
|
||||||
Channel: channel, Account: b.Account,
|
|
||||||
}
|
|
||||||
b.names[channel] = nil
|
b.names[channel] = nil
|
||||||
b.i.Handlers.Clear(girc.RPL_NAMREPLY)
|
b.i.Handlers.Clear(girc.RPL_NAMREPLY)
|
||||||
b.i.Handlers.Clear(girc.RPL_ENDOFNAMES)
|
b.i.Handlers.Clear(girc.RPL_ENDOFNAMES)
|
||||||
@@ -362,18 +304,7 @@ func (b *Birc) skipPrivMsg(event girc.Event) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// don't forward message from ourself
|
// don't forward message from ourself
|
||||||
if event.Source != nil {
|
if event.Source.Name == b.Nick {
|
||||||
if event.Source.Name == b.Nick {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// don't forward messages we sent via RELAYMSG
|
|
||||||
if relayedNick, ok := event.Tags.Get("draft/relaymsg"); ok && relayedNick == b.Nick {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// This is the old name of the cap sent in spoofed messages; I've kept this in
|
|
||||||
// for compatibility reasons
|
|
||||||
if relayedNick, ok := event.Tags.Get("relaymsg"); ok && relayedNick == b.Nick {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -393,23 +324,3 @@ func (b *Birc) storeNames(client *girc.Client, event girc.Event) {
|
|||||||
func (b *Birc) formatnicks(nicks []string) string {
|
func (b *Birc) formatnicks(nicks []string) string {
|
||||||
return strings.Join(nicks, ", ") + " currently on IRC"
|
return strings.Join(nicks, ", ") + " currently on IRC"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Birc) getTLSConfig() (*tls.Config, error) {
|
|
||||||
server, _, _ := net.SplitHostPort(b.GetString("server"))
|
|
||||||
|
|
||||||
tlsConfig := &tls.Config{
|
|
||||||
InsecureSkipVerify: b.GetBool("skiptlsverify"), //nolint:gosec
|
|
||||||
ServerName: server,
|
|
||||||
}
|
|
||||||
|
|
||||||
if filename := b.GetString("TLSClientCertificate"); filename != "" {
|
|
||||||
cert, err := tls.LoadX509KeyPair(filename, filename)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConfig.Certificates = []tls.Certificate{cert}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tlsConfig, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package bmatrix
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"html"
|
"html"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -83,36 +82,20 @@ func (b *Bmatrix) getDisplayName(mxid string) string {
|
|||||||
func (b *Bmatrix) cacheDisplayName(mxid string, displayName string) string {
|
func (b *Bmatrix) cacheDisplayName(mxid string, displayName string) string {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
// scan to delete old entries, to stop memory usage from becoming too high with old entries.
|
// scan to delete old entries, to stop memory usage from becoming too high with old entries
|
||||||
// In addition, we also detect if another user have the same username, and if so, we append their mxids to their usernames to differentiate them.
|
|
||||||
toDelete := []string{}
|
toDelete := []string{}
|
||||||
conflict := false
|
b.RLock()
|
||||||
|
for k, v := range b.NicknameMap {
|
||||||
|
if now.Sub(v.lastUpdated) > 10*time.Minute {
|
||||||
|
toDelete = append(toDelete, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.RUnlock()
|
||||||
|
|
||||||
b.Lock()
|
b.Lock()
|
||||||
for mxid, v := range b.NicknameMap {
|
|
||||||
// to prevent username reuse across matrix servers - or even on the same server, append
|
|
||||||
// the mxid to the username when there is a conflict
|
|
||||||
if v.displayName == displayName {
|
|
||||||
conflict = true
|
|
||||||
// TODO: it would be nice to be able to rename previous messages from this user.
|
|
||||||
// The current behavior is that only users with clashing usernames and *that have spoken since the bridge last started* will get their mxids shown, and I don't know if that's the expected behavior.
|
|
||||||
v.displayName = fmt.Sprintf("%s (%s)", displayName, mxid)
|
|
||||||
b.NicknameMap[mxid] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
if now.Sub(v.lastUpdated) > 10*time.Minute {
|
|
||||||
toDelete = append(toDelete, mxid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if conflict {
|
|
||||||
displayName = fmt.Sprintf("%s (%s)", displayName, mxid)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range toDelete {
|
for _, v := range toDelete {
|
||||||
delete(b.NicknameMap, v)
|
delete(b.NicknameMap, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
b.NicknameMap[mxid] = NicknameCacheEntry{
|
b.NicknameMap[mxid] = NicknameCacheEntry{
|
||||||
displayName: displayName,
|
displayName: displayName,
|
||||||
lastUpdated: now,
|
lastUpdated: now,
|
||||||
@@ -181,35 +164,3 @@ func (b *Bmatrix) getAvatarURL(sender string) string {
|
|||||||
|
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleRatelimit handles the ratelimit errors and return if we're ratelimited and the amount of time to sleep
|
|
||||||
func (b *Bmatrix) handleRatelimit(err error) (time.Duration, bool) {
|
|
||||||
httpErr := handleError(err)
|
|
||||||
if httpErr.Errcode != "M_LIMIT_EXCEEDED" {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Log.Debugf("ratelimited: %s", httpErr.Err)
|
|
||||||
b.Log.Infof("getting ratelimited by matrix, sleeping approx %d seconds before retrying", httpErr.RetryAfterMs/1000)
|
|
||||||
|
|
||||||
return time.Duration(httpErr.RetryAfterMs) * time.Millisecond, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// retry function will check if we're ratelimited and retries again when backoff time expired
|
|
||||||
// returns original error if not 429 ratelimit
|
|
||||||
func (b *Bmatrix) retry(f func() error) error {
|
|
||||||
b.rateMutex.Lock()
|
|
||||||
defer b.rateMutex.Unlock()
|
|
||||||
|
|
||||||
for {
|
|
||||||
if err := f(); err != nil {
|
|
||||||
if backoff, ok := b.handleRatelimit(err); ok {
|
|
||||||
time.Sleep(backoff)
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ type Bmatrix struct {
|
|||||||
UserID string
|
UserID string
|
||||||
NicknameMap map[string]NicknameCacheEntry
|
NicknameMap map[string]NicknameCacheEntry
|
||||||
RoomMap map[string]string
|
RoomMap map[string]string
|
||||||
rateMutex sync.RWMutex
|
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
*bridge.Config
|
*bridge.Config
|
||||||
}
|
}
|
||||||
@@ -48,10 +47,8 @@ type matrixUsername struct {
|
|||||||
|
|
||||||
// SubTextMessage represents the new content of the message in edit messages.
|
// SubTextMessage represents the new content of the message in edit messages.
|
||||||
type SubTextMessage struct {
|
type SubTextMessage struct {
|
||||||
MsgType string `json:"msgtype"`
|
MsgType string `json:"msgtype"`
|
||||||
Body string `json:"body"`
|
Body string `json:"body"`
|
||||||
FormattedBody string `json:"formatted_body,omitempty"`
|
|
||||||
Format string `json:"format,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MessageRelation explains how the current message relates to a previous message.
|
// MessageRelation explains how the current message relates to a previous message.
|
||||||
@@ -67,19 +64,6 @@ type EditedMessage struct {
|
|||||||
matrix.TextMessage
|
matrix.TextMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
type InReplyToRelationContent struct {
|
|
||||||
EventID string `json:"event_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type InReplyToRelation struct {
|
|
||||||
InReplyTo InReplyToRelationContent `json:"m.in_reply_to"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReplyMessage struct {
|
|
||||||
RelatedTo InReplyToRelation `json:"m.relates_to"`
|
|
||||||
matrix.TextMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(cfg *bridge.Config) bridge.Bridger {
|
func New(cfg *bridge.Config) bridge.Bridger {
|
||||||
b := &Bmatrix{Config: cfg}
|
b := &Bmatrix{Config: cfg}
|
||||||
b.RoomMap = make(map[string]string)
|
b.RoomMap = make(map[string]string)
|
||||||
@@ -90,33 +74,22 @@ func New(cfg *bridge.Config) bridge.Bridger {
|
|||||||
func (b *Bmatrix) Connect() error {
|
func (b *Bmatrix) Connect() error {
|
||||||
var err error
|
var err error
|
||||||
b.Log.Infof("Connecting %s", b.GetString("Server"))
|
b.Log.Infof("Connecting %s", b.GetString("Server"))
|
||||||
if b.GetString("MxID") != "" && b.GetString("Token") != "" {
|
b.mc, err = matrix.NewClient(b.GetString("Server"), "", "")
|
||||||
b.mc, err = matrix.NewClient(
|
if err != nil {
|
||||||
b.GetString("Server"), b.GetString("MxID"), b.GetString("Token"),
|
return err
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b.UserID = b.GetString("MxID")
|
|
||||||
b.Log.Info("Using existing Matrix credentials")
|
|
||||||
} else {
|
|
||||||
b.mc, err = matrix.NewClient(b.GetString("Server"), "", "")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp, err := b.mc.Login(&matrix.ReqLogin{
|
|
||||||
Type: "m.login.password",
|
|
||||||
User: b.GetString("Login"),
|
|
||||||
Password: b.GetString("Password"),
|
|
||||||
Identifier: matrix.NewUserIdentifier(b.GetString("Login")),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b.mc.SetCredentials(resp.UserID, resp.AccessToken)
|
|
||||||
b.UserID = resp.UserID
|
|
||||||
b.Log.Info("Connection succeeded")
|
|
||||||
}
|
}
|
||||||
|
resp, err := b.mc.Login(&matrix.ReqLogin{
|
||||||
|
Type: "m.login.password",
|
||||||
|
User: b.GetString("Login"),
|
||||||
|
Password: b.GetString("Password"),
|
||||||
|
Identifier: matrix.NewUserIdentifier(b.GetString("Login")),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.mc.SetCredentials(resp.UserID, resp.AccessToken)
|
||||||
|
b.UserID = resp.UserID
|
||||||
|
b.Log.Info("Connection succeeded")
|
||||||
go b.handlematrix()
|
go b.handlematrix()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -126,18 +99,25 @@ func (b *Bmatrix) Disconnect() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bmatrix) JoinChannel(channel config.ChannelInfo) error {
|
func (b *Bmatrix) JoinChannel(channel config.ChannelInfo) error {
|
||||||
return b.retry(func() error {
|
retry:
|
||||||
resp, err := b.mc.JoinRoom(channel.Name, "", nil)
|
resp, err := b.mc.JoinRoom(channel.Name, "", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Lock()
|
return err
|
||||||
b.RoomMap[resp.RoomID] = channel.Name
|
}
|
||||||
b.Unlock()
|
|
||||||
|
|
||||||
return nil
|
b.Lock()
|
||||||
})
|
b.RoomMap[resp.RoomID] = channel.Name
|
||||||
|
b.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bmatrix) Send(msg config.Message) (string, error) {
|
func (b *Bmatrix) Send(msg config.Message) (string, error) {
|
||||||
@@ -153,29 +133,13 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
|
|||||||
m := matrix.TextMessage{
|
m := matrix.TextMessage{
|
||||||
MsgType: "m.emote",
|
MsgType: "m.emote",
|
||||||
Body: username.plain + msg.Text,
|
Body: username.plain + msg.Text,
|
||||||
FormattedBody: username.formatted + helper.ParseMarkdown(msg.Text),
|
FormattedBody: username.formatted + msg.Text,
|
||||||
Format: "org.matrix.custom.html",
|
|
||||||
}
|
}
|
||||||
|
resp, err := b.mc.SendMessageEvent(channel, "m.room.message", m)
|
||||||
if b.GetBool("HTMLDisable") {
|
if err != nil {
|
||||||
m.Format = ""
|
return "", err
|
||||||
m.FormattedBody = ""
|
|
||||||
}
|
}
|
||||||
|
return resp.EventID, err
|
||||||
msgID := ""
|
|
||||||
|
|
||||||
err := b.retry(func() error {
|
|
||||||
resp, err := b.mc.SendMessageEvent(channel, "m.room.message", m)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
msgID = resp.EventID
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
|
|
||||||
return msgID, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete message
|
// Delete message
|
||||||
@@ -183,34 +147,17 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
|
|||||||
if msg.ID == "" {
|
if msg.ID == "" {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
resp, err := b.mc.RedactEvent(channel, msg.ID, &matrix.ReqRedact{})
|
||||||
msgID := ""
|
if err != nil {
|
||||||
|
return "", err
|
||||||
err := b.retry(func() error {
|
}
|
||||||
resp, err := b.mc.RedactEvent(channel, msg.ID, &matrix.ReqRedact{})
|
return resp.EventID, err
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
msgID = resp.EventID
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
|
|
||||||
return msgID, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload a file if it exists
|
// Upload a file if it exists
|
||||||
if msg.Extra != nil {
|
if msg.Extra != nil {
|
||||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||||
rmsg := rmsg
|
if _, err := b.mc.SendText(channel, rmsg.Username+rmsg.Text); err != nil {
|
||||||
|
|
||||||
err := b.retry(func() error {
|
|
||||||
_, err := b.mc.SendText(channel, rmsg.Username+rmsg.Text)
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("sendText failed: %s", err)
|
b.Log.Errorf("sendText failed: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -222,39 +169,25 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
|
|||||||
|
|
||||||
// Edit message if we have an ID
|
// Edit message if we have an ID
|
||||||
if msg.ID != "" {
|
if msg.ID != "" {
|
||||||
rmsg := EditedMessage{
|
rmsg := EditedMessage{TextMessage: matrix.TextMessage{
|
||||||
TextMessage: matrix.TextMessage{
|
Body: username.plain + msg.Text,
|
||||||
Body: username.plain + msg.Text,
|
MsgType: "m.text",
|
||||||
MsgType: "m.text",
|
}}
|
||||||
Format: "org.matrix.custom.html",
|
|
||||||
FormattedBody: username.formatted + helper.ParseMarkdown(msg.Text),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
rmsg.NewContent = SubTextMessage{
|
|
||||||
Body: rmsg.TextMessage.Body,
|
|
||||||
FormattedBody: rmsg.TextMessage.FormattedBody,
|
|
||||||
Format: rmsg.TextMessage.Format,
|
|
||||||
MsgType: "m.text",
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.GetBool("HTMLDisable") {
|
if b.GetBool("HTMLDisable") {
|
||||||
rmsg.TextMessage.Format = ""
|
rmsg.TextMessage.FormattedBody = username.formatted + "* " + msg.Text
|
||||||
rmsg.TextMessage.FormattedBody = ""
|
} else {
|
||||||
rmsg.NewContent.Format = ""
|
rmsg.Format = "org.matrix.custom.html"
|
||||||
rmsg.NewContent.FormattedBody = ""
|
rmsg.TextMessage.FormattedBody = username.formatted + "* " + helper.ParseMarkdown(msg.Text)
|
||||||
|
}
|
||||||
|
rmsg.NewContent = SubTextMessage{
|
||||||
|
Body: rmsg.TextMessage.Body,
|
||||||
|
MsgType: "m.text",
|
||||||
}
|
}
|
||||||
|
|
||||||
rmsg.RelatedTo = MessageRelation{
|
rmsg.RelatedTo = MessageRelation{
|
||||||
EventID: msg.ID,
|
EventID: msg.ID,
|
||||||
Type: "m.replace",
|
Type: "m.replace",
|
||||||
}
|
}
|
||||||
|
_, err := b.mc.SendMessageEvent(channel, "m.room.message", rmsg)
|
||||||
err := b.retry(func() error {
|
|
||||||
_, err := b.mc.SendMessageEvent(channel, "m.room.message", rmsg)
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -268,103 +201,27 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
|
|||||||
MsgType: "m.notice",
|
MsgType: "m.notice",
|
||||||
Body: username.plain + msg.Text,
|
Body: username.plain + msg.Text,
|
||||||
FormattedBody: username.formatted + msg.Text,
|
FormattedBody: username.formatted + msg.Text,
|
||||||
Format: "org.matrix.custom.html",
|
|
||||||
}
|
}
|
||||||
|
resp, err := b.mc.SendMessageEvent(channel, "m.room.message", m)
|
||||||
if b.GetBool("HTMLDisable") {
|
|
||||||
m.Format = ""
|
|
||||||
m.FormattedBody = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
resp *matrix.RespSendEvent
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
err = b.retry(func() error {
|
|
||||||
resp, err = b.mc.SendMessageEvent(channel, "m.room.message", m)
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp.EventID, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg.ParentValid() {
|
|
||||||
m := ReplyMessage{
|
|
||||||
TextMessage: matrix.TextMessage{
|
|
||||||
MsgType: "m.text",
|
|
||||||
Body: username.plain + msg.Text,
|
|
||||||
FormattedBody: username.formatted + helper.ParseMarkdown(msg.Text),
|
|
||||||
Format: "org.matrix.custom.html",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.GetBool("HTMLDisable") {
|
|
||||||
m.TextMessage.Format = ""
|
|
||||||
m.TextMessage.FormattedBody = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
m.RelatedTo = InReplyToRelation{
|
|
||||||
InReplyTo: InReplyToRelationContent{
|
|
||||||
EventID: msg.ParentID,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
resp *matrix.RespSendEvent
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
err = b.retry(func() error {
|
|
||||||
resp, err = b.mc.SendMessageEvent(channel, "m.room.message", m)
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp.EventID, err
|
return resp.EventID, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.GetBool("HTMLDisable") {
|
if b.GetBool("HTMLDisable") {
|
||||||
var (
|
resp, err := b.mc.SendText(channel, username.plain+msg.Text)
|
||||||
resp *matrix.RespSendEvent
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
err = b.retry(func() error {
|
|
||||||
resp, err = b.mc.SendText(channel, username.plain+msg.Text)
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp.EventID, err
|
return resp.EventID, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post normal message with HTML support (eg riot.im)
|
// Post normal message with HTML support (eg riot.im)
|
||||||
var (
|
resp, err := b.mc.SendFormattedText(channel, username.plain+msg.Text, username.formatted+helper.ParseMarkdown(msg.Text))
|
||||||
resp *matrix.RespSendEvent
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
err = b.retry(func() error {
|
|
||||||
resp, err = b.mc.SendFormattedText(channel, username.plain+msg.Text,
|
|
||||||
username.formatted+helper.ParseMarkdown(msg.Text))
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp.EventID, err
|
return resp.EventID, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,9 +232,6 @@ func (b *Bmatrix) handlematrix() {
|
|||||||
syncer.OnEventType("m.room.member", b.handleMemberChange)
|
syncer.OnEventType("m.room.member", b.handleMemberChange)
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
if b == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := b.mc.Sync(); err != nil {
|
if err := b.mc.Sync(); err != nil {
|
||||||
b.Log.Println("Sync() returned ", err)
|
b.Log.Println("Sync() returned ", err)
|
||||||
}
|
}
|
||||||
@@ -415,35 +269,6 @@ func (b *Bmatrix) handleEdit(ev *matrix.Event, rmsg config.Message) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bmatrix) handleReply(ev *matrix.Event, rmsg config.Message) bool {
|
|
||||||
relationInterface, present := ev.Content["m.relates_to"]
|
|
||||||
if !present {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var relation InReplyToRelation
|
|
||||||
if err := interface2Struct(relationInterface, &relation); err != nil {
|
|
||||||
// probably fine
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
body := rmsg.Text
|
|
||||||
for strings.HasPrefix(body, "> ") {
|
|
||||||
lineIdx := strings.IndexRune(body, '\n')
|
|
||||||
if lineIdx == -1 {
|
|
||||||
body = ""
|
|
||||||
} else {
|
|
||||||
body = body[(lineIdx + 1):]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rmsg.Text = body
|
|
||||||
rmsg.ParentID = relation.InReplyTo.EventID
|
|
||||||
b.Remote <- rmsg
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bmatrix) handleMemberChange(ev *matrix.Event) {
|
func (b *Bmatrix) handleMemberChange(ev *matrix.Event) {
|
||||||
// Update the displayname on join messages, according to https://matrix.org/docs/spec/client_server/r0.6.1#events-on-change-of-profile-information
|
// Update the displayname on join messages, according to https://matrix.org/docs/spec/client_server/r0.6.1#events-on-change-of-profile-information
|
||||||
if ev.Content["membership"] == "join" {
|
if ev.Content["membership"] == "join" {
|
||||||
@@ -474,6 +299,13 @@ func (b *Bmatrix) handleEvent(ev *matrix.Event) {
|
|||||||
Avatar: b.getAvatarURL(ev.Sender),
|
Avatar: b.getAvatarURL(ev.Sender),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Text must be a string
|
||||||
|
if rmsg.Text, ok = ev.Content["body"].(string); !ok {
|
||||||
|
b.Log.Errorf("Content[body] is not a string: %T\n%#v",
|
||||||
|
ev.Content["body"], ev.Content)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Remove homeserver suffix if configured
|
// Remove homeserver suffix if configured
|
||||||
if b.GetBool("NoHomeServerSuffix") {
|
if b.GetBool("NoHomeServerSuffix") {
|
||||||
re := regexp.MustCompile("(.*?):.*")
|
re := regexp.MustCompile("(.*?):.*")
|
||||||
@@ -489,13 +321,6 @@ func (b *Bmatrix) handleEvent(ev *matrix.Event) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text must be a string
|
|
||||||
if rmsg.Text, ok = ev.Content["body"].(string); !ok {
|
|
||||||
b.Log.Errorf("Content[body] is not a string: %T\n%#v",
|
|
||||||
ev.Content["body"], ev.Content)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do we have a /me action
|
// Do we have a /me action
|
||||||
if ev.Content["msgtype"].(string) == "m.emote" {
|
if ev.Content["msgtype"].(string) == "m.emote" {
|
||||||
rmsg.Event = config.EventUserAction
|
rmsg.Event = config.EventUserAction
|
||||||
@@ -506,11 +331,6 @@ func (b *Bmatrix) handleEvent(ev *matrix.Event) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is it a reply?
|
|
||||||
if b.handleReply(ev, rmsg) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do we have attachments
|
// Do we have attachments
|
||||||
if b.containsAttachment(ev.Content) {
|
if b.containsAttachment(ev.Content) {
|
||||||
err := b.handleDownloadFile(&rmsg, ev.Content)
|
err := b.handleDownloadFile(&rmsg, ev.Content)
|
||||||
@@ -521,11 +341,6 @@ func (b *Bmatrix) handleEvent(ev *matrix.Event) {
|
|||||||
|
|
||||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", ev.Sender, b.Account)
|
b.Log.Debugf("<= Sending message from %s on %s to gateway", ev.Sender, b.Account)
|
||||||
b.Remote <- rmsg
|
b.Remote <- rmsg
|
||||||
|
|
||||||
// not crucial, so no ratelimit check here
|
|
||||||
if err := b.mc.MarkRead(ev.RoomID, ev.ID); err != nil {
|
|
||||||
b.Log.Errorf("couldn't mark message as read %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -605,25 +420,13 @@ func (b *Bmatrix) handleUploadFile(msg *config.Message, channel string, fi *conf
|
|||||||
sp := strings.Split(fi.Name, ".")
|
sp := strings.Split(fi.Name, ".")
|
||||||
mtype := mime.TypeByExtension("." + sp[len(sp)-1])
|
mtype := mime.TypeByExtension("." + sp[len(sp)-1])
|
||||||
// image and video uploads send no username, we have to do this ourself here #715
|
// image and video uploads send no username, we have to do this ourself here #715
|
||||||
err := b.retry(func() error {
|
_, err := b.mc.SendFormattedText(channel, username.plain+fi.Comment, username.formatted+fi.Comment)
|
||||||
_, err := b.mc.SendFormattedText(channel, username.plain+fi.Comment, username.formatted+fi.Comment)
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Errorf("file comment failed: %#v", err)
|
b.Log.Errorf("file comment failed: %#v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Log.Debugf("uploading file: %s %s", fi.Name, mtype)
|
b.Log.Debugf("uploading file: %s %s", fi.Name, mtype)
|
||||||
|
res, err := b.mc.UploadToContentRepo(content, mtype, int64(len(*fi.Data)))
|
||||||
var res *matrix.RespMediaUpload
|
|
||||||
|
|
||||||
err = b.retry(func() error {
|
|
||||||
res, err = b.mc.UploadToContentRepo(content, mtype, int64(len(*fi.Data)))
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Errorf("file upload failed: %#v", err)
|
b.Log.Errorf("file upload failed: %#v", err)
|
||||||
return
|
return
|
||||||
@@ -632,56 +435,40 @@ func (b *Bmatrix) handleUploadFile(msg *config.Message, channel string, fi *conf
|
|||||||
switch {
|
switch {
|
||||||
case strings.Contains(mtype, "video"):
|
case strings.Contains(mtype, "video"):
|
||||||
b.Log.Debugf("sendVideo %s", res.ContentURI)
|
b.Log.Debugf("sendVideo %s", res.ContentURI)
|
||||||
err = b.retry(func() error {
|
_, err = b.mc.SendVideo(channel, fi.Name, res.ContentURI)
|
||||||
_, err = b.mc.SendVideo(channel, fi.Name, res.ContentURI)
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Errorf("sendVideo failed: %#v", err)
|
b.Log.Errorf("sendVideo failed: %#v", err)
|
||||||
}
|
}
|
||||||
case strings.Contains(mtype, "image"):
|
case strings.Contains(mtype, "image"):
|
||||||
b.Log.Debugf("sendImage %s", res.ContentURI)
|
b.Log.Debugf("sendImage %s", res.ContentURI)
|
||||||
err = b.retry(func() error {
|
_, err = b.mc.SendImage(channel, fi.Name, res.ContentURI)
|
||||||
_, err = b.mc.SendImage(channel, fi.Name, res.ContentURI)
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Errorf("sendImage failed: %#v", err)
|
b.Log.Errorf("sendImage failed: %#v", err)
|
||||||
}
|
}
|
||||||
case strings.Contains(mtype, "audio"):
|
case strings.Contains(mtype, "audio"):
|
||||||
b.Log.Debugf("sendAudio %s", res.ContentURI)
|
b.Log.Debugf("sendAudio %s", res.ContentURI)
|
||||||
err = b.retry(func() error {
|
_, err = b.mc.SendMessageEvent(channel, "m.room.message", matrix.AudioMessage{
|
||||||
_, err = b.mc.SendMessageEvent(channel, "m.room.message", matrix.AudioMessage{
|
MsgType: "m.audio",
|
||||||
MsgType: "m.audio",
|
Body: fi.Name,
|
||||||
Body: fi.Name,
|
URL: res.ContentURI,
|
||||||
URL: res.ContentURI,
|
Info: matrix.AudioInfo{
|
||||||
Info: matrix.AudioInfo{
|
Mimetype: mtype,
|
||||||
Mimetype: mtype,
|
Size: uint(len(*fi.Data)),
|
||||||
Size: uint(len(*fi.Data)),
|
},
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Errorf("sendAudio failed: %#v", err)
|
b.Log.Errorf("sendAudio failed: %#v", err)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
b.Log.Debugf("sendFile %s", res.ContentURI)
|
b.Log.Debugf("sendFile %s", res.ContentURI)
|
||||||
err = b.retry(func() error {
|
_, err = b.mc.SendMessageEvent(channel, "m.room.message", matrix.FileMessage{
|
||||||
_, err = b.mc.SendMessageEvent(channel, "m.room.message", matrix.FileMessage{
|
MsgType: "m.file",
|
||||||
MsgType: "m.file",
|
Body: fi.Name,
|
||||||
Body: fi.Name,
|
URL: res.ContentURI,
|
||||||
URL: res.ContentURI,
|
Info: matrix.FileInfo{
|
||||||
Info: matrix.FileInfo{
|
Mimetype: mtype,
|
||||||
Mimetype: mtype,
|
Size: uint(len(*fi.Data)),
|
||||||
Size: uint(len(*fi.Data)),
|
},
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Errorf("sendFile failed: %#v", err)
|
b.Log.Errorf("sendFile failed: %#v", err)
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import (
|
|||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
"github.com/42wim/matterbridge/matterclient"
|
"github.com/42wim/matterbridge/matterclient"
|
||||||
matterclient6 "github.com/matterbridge/matterclient"
|
|
||||||
"github.com/mattermost/mattermost-server/v5/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
model6 "github.com/mattermost/mattermost-server/v6/model"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// handleDownloadAvatar downloads the avatar of userid from channel
|
// handleDownloadAvatar downloads the avatar of userid from channel
|
||||||
@@ -23,26 +21,12 @@ func (b *Bmattermost) handleDownloadAvatar(userid string, channel string) {
|
|||||||
Extra: make(map[string][]interface{}),
|
Extra: make(map[string][]interface{}),
|
||||||
}
|
}
|
||||||
if _, ok := b.avatarMap[userid]; !ok {
|
if _, ok := b.avatarMap[userid]; !ok {
|
||||||
var (
|
data, resp := b.mc.Client.GetProfileImage(userid, "")
|
||||||
data []byte
|
if resp.Error != nil {
|
||||||
err error
|
b.Log.Errorf("ProfileImage download failed for %#v %s", userid, resp.Error)
|
||||||
resp *model.Response
|
return
|
||||||
)
|
|
||||||
if b.mc6 != nil {
|
|
||||||
data, _, err = b.mc6.Client.GetProfileImage(userid, "")
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("ProfileImage download failed for %#v %s", userid, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
data, resp = b.mc.Client.GetProfileImage(userid, "")
|
|
||||||
if resp.Error != nil {
|
|
||||||
b.Log.Errorf("ProfileImage download failed for %#v %s", userid, resp.Error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
err := helper.HandleDownloadSize(b.Log, &rmsg, userid+".png", int64(len(data)), b.General)
|
||||||
err = helper.HandleDownloadSize(b.Log, &rmsg, userid+".png", int64(len(data)), b.General)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Error(err)
|
b.Log.Error(err)
|
||||||
return
|
return
|
||||||
@@ -54,10 +38,6 @@ func (b *Bmattermost) handleDownloadAvatar(userid string, channel string) {
|
|||||||
|
|
||||||
// handleDownloadFile handles file download
|
// handleDownloadFile handles file download
|
||||||
func (b *Bmattermost) handleDownloadFile(rmsg *config.Message, id string) error {
|
func (b *Bmattermost) handleDownloadFile(rmsg *config.Message, id string) error {
|
||||||
if b.mc6 != nil {
|
|
||||||
return b.handleDownloadFile6(rmsg, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
url, _ := b.mc.Client.GetFileLink(id)
|
url, _ := b.mc.Client.GetFileLink(id)
|
||||||
finfo, resp := b.mc.Client.GetFileInfo(id)
|
finfo, resp := b.mc.Client.GetFileInfo(id)
|
||||||
if resp.Error != nil {
|
if resp.Error != nil {
|
||||||
@@ -75,25 +55,6 @@ func (b *Bmattermost) handleDownloadFile(rmsg *config.Message, id string) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint:wrapcheck
|
|
||||||
func (b *Bmattermost) handleDownloadFile6(rmsg *config.Message, id string) error {
|
|
||||||
url, _, _ := b.mc6.Client.GetFileLink(id)
|
|
||||||
finfo, _, err := b.mc6.Client.GetFileInfo(id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = helper.HandleDownloadSize(b.Log, rmsg, finfo.Name, finfo.Size, b.General)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data, _, err := b.mc6.Client.DownloadFile(id, true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
helper.HandleDownloadData(b.Log, rmsg, finfo.Name, rmsg.Text, url, &data, b.General)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bmattermost) handleMatter() {
|
func (b *Bmattermost) handleMatter() {
|
||||||
messages := make(chan *config.Message)
|
messages := make(chan *config.Message)
|
||||||
if b.GetString("WebhookBindAddress") != "" {
|
if b.GetString("WebhookBindAddress") != "" {
|
||||||
@@ -126,12 +87,6 @@ func (b *Bmattermost) handleMatter() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bmattermost) handleMatterClient(messages chan *config.Message) {
|
func (b *Bmattermost) handleMatterClient(messages chan *config.Message) {
|
||||||
if b.mc6 != nil {
|
|
||||||
b.Log.Debug("starting matterclient6")
|
|
||||||
b.handleMatterClient6(messages)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for message := range b.mc.MessageChan {
|
for message := range b.mc.MessageChan {
|
||||||
b.Log.Debugf("%#v", message.Raw.Data)
|
b.Log.Debugf("%#v", message.Raw.Data)
|
||||||
|
|
||||||
@@ -140,14 +95,9 @@ func (b *Bmattermost) handleMatterClient(messages chan *config.Message) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
channelName := b.getChannelName(message.Post.ChannelId)
|
|
||||||
if channelName == "" {
|
|
||||||
channelName = message.Channel
|
|
||||||
}
|
|
||||||
|
|
||||||
// only download avatars if we have a place to upload them (configured mediaserver)
|
// only download avatars if we have a place to upload them (configured mediaserver)
|
||||||
if b.General.MediaServerUpload != "" || b.General.MediaDownloadPath != "" {
|
if b.General.MediaServerUpload != "" || b.General.MediaDownloadPath != "" {
|
||||||
b.handleDownloadAvatar(message.UserID, channelName)
|
b.handleDownloadAvatar(message.UserID, message.Channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Log.Debugf("== Receiving event %#v", message)
|
b.Log.Debugf("== Receiving event %#v", message)
|
||||||
@@ -155,10 +105,10 @@ func (b *Bmattermost) handleMatterClient(messages chan *config.Message) {
|
|||||||
rmsg := &config.Message{
|
rmsg := &config.Message{
|
||||||
Username: message.Username,
|
Username: message.Username,
|
||||||
UserID: message.UserID,
|
UserID: message.UserID,
|
||||||
Channel: channelName,
|
Channel: message.Channel,
|
||||||
Text: message.Text,
|
Text: message.Text,
|
||||||
ID: message.Post.Id,
|
ID: message.Post.Id,
|
||||||
ParentID: message.Post.RootId, // ParentID is obsolete with mattermost
|
ParentID: message.Post.ParentId,
|
||||||
Extra: make(map[string][]interface{}),
|
Extra: make(map[string][]interface{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,72 +132,8 @@ func (b *Bmattermost) handleMatterClient(messages chan *config.Message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Use nickname instead of username if defined
|
// Use nickname instead of username if defined
|
||||||
if !b.GetBool("useusername") {
|
if nick := b.mc.GetNickName(rmsg.UserID); nick != "" {
|
||||||
if nick := b.mc.GetNickName(rmsg.UserID); nick != "" {
|
rmsg.Username = nick
|
||||||
rmsg.Username = nick
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
messages <- rmsg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// nolint:cyclop
|
|
||||||
func (b *Bmattermost) handleMatterClient6(messages chan *config.Message) {
|
|
||||||
for message := range b.mc6.MessageChan {
|
|
||||||
b.Log.Debugf("%#v %#v", message.Raw.GetData(), message.Raw.EventType())
|
|
||||||
|
|
||||||
if b.skipMessage6(message) {
|
|
||||||
b.Log.Debugf("Skipped message: %#v", message)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
channelName := b.getChannelName(message.Post.ChannelId)
|
|
||||||
if channelName == "" {
|
|
||||||
channelName = message.Channel
|
|
||||||
}
|
|
||||||
|
|
||||||
// only download avatars if we have a place to upload them (configured mediaserver)
|
|
||||||
if b.General.MediaServerUpload != "" || b.General.MediaDownloadPath != "" {
|
|
||||||
b.handleDownloadAvatar(message.UserID, channelName)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Log.Debugf("== Receiving event %#v", message)
|
|
||||||
|
|
||||||
rmsg := &config.Message{
|
|
||||||
Username: message.Username,
|
|
||||||
UserID: message.UserID,
|
|
||||||
Channel: channelName,
|
|
||||||
Text: message.Text,
|
|
||||||
ID: message.Post.Id,
|
|
||||||
ParentID: message.Post.RootId, // ParentID is obsolete with mattermost
|
|
||||||
Extra: make(map[string][]interface{}),
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle mattermost post properties (override username and attachments)
|
|
||||||
b.handleProps6(rmsg, message)
|
|
||||||
|
|
||||||
// create a text for bridges that don't support native editing
|
|
||||||
if message.Raw.EventType() == model6.WebsocketEventPostEdited && !b.GetBool("EditDisable") {
|
|
||||||
rmsg.Text = message.Text + b.GetString("EditSuffix")
|
|
||||||
}
|
|
||||||
|
|
||||||
if message.Raw.EventType() == model6.WebsocketEventPostDeleted {
|
|
||||||
rmsg.Event = config.EventMsgDelete
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, id := range message.Post.FileIds {
|
|
||||||
err := b.handleDownloadFile(rmsg, id)
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("download failed: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use nickname instead of username if defined
|
|
||||||
if !b.GetBool("useusername") {
|
|
||||||
if nick := b.mc6.GetNickName(rmsg.UserID); nick != "" {
|
|
||||||
rmsg.Username = nick
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
messages <- rmsg
|
messages <- rmsg
|
||||||
@@ -258,7 +144,6 @@ func (b *Bmattermost) handleMatterHook(messages chan *config.Message) {
|
|||||||
for {
|
for {
|
||||||
message := b.mh.Receive()
|
message := b.mh.Receive()
|
||||||
b.Log.Debugf("Receiving from matterhook %#v", message)
|
b.Log.Debugf("Receiving from matterhook %#v", message)
|
||||||
|
|
||||||
messages <- &config.Message{
|
messages <- &config.Message{
|
||||||
UserID: message.UserID,
|
UserID: message.UserID,
|
||||||
Username: message.UserName,
|
Username: message.UserName,
|
||||||
@@ -270,13 +155,9 @@ func (b *Bmattermost) handleMatterHook(messages chan *config.Message) {
|
|||||||
|
|
||||||
// handleUploadFile handles native upload of files
|
// handleUploadFile handles native upload of files
|
||||||
func (b *Bmattermost) handleUploadFile(msg *config.Message) (string, error) {
|
func (b *Bmattermost) handleUploadFile(msg *config.Message) (string, error) {
|
||||||
if b.mc6 != nil {
|
|
||||||
return b.handleUploadFile6(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
var res, id string
|
var res, id string
|
||||||
channelID := b.getChannelID(msg.Channel)
|
channelID := b.mc.GetChannelId(msg.Channel, b.TeamID)
|
||||||
for _, f := range msg.Extra["file"] {
|
for _, f := range msg.Extra["file"] {
|
||||||
fi := f.(config.FileInfo)
|
fi := f.(config.FileInfo)
|
||||||
id, err = b.mc.UploadFile(*fi.Data, channelID, fi.Name)
|
id, err = b.mc.UploadFile(*fi.Data, channelID, fi.Name)
|
||||||
@@ -292,26 +173,6 @@ func (b *Bmattermost) handleUploadFile(msg *config.Message) (string, error) {
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint:forcetypeassert,wrapcheck
|
|
||||||
func (b *Bmattermost) handleUploadFile6(msg *config.Message) (string, error) {
|
|
||||||
var err error
|
|
||||||
var res, id string
|
|
||||||
channelID := b.getChannelID(msg.Channel)
|
|
||||||
for _, f := range msg.Extra["file"] {
|
|
||||||
fi := f.(config.FileInfo)
|
|
||||||
id, err = b.mc6.UploadFile(*fi.Data, channelID, fi.Name)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
msg.Text = fi.Comment
|
|
||||||
if b.GetBool("PrefixMessagesWithNick") {
|
|
||||||
msg.Text = msg.Username + msg.Text
|
|
||||||
}
|
|
||||||
res, err = b.mc6.PostMessageWithFiles(channelID, msg.Text, msg.ParentID, []string{id})
|
|
||||||
}
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bmattermost) handleProps(rmsg *config.Message, message *matterclient.Message) {
|
func (b *Bmattermost) handleProps(rmsg *config.Message, message *matterclient.Message) {
|
||||||
props := message.Post.Props
|
props := message.Post.Props
|
||||||
if props == nil {
|
if props == nil {
|
||||||
@@ -336,31 +197,3 @@ func (b *Bmattermost) handleProps(rmsg *config.Message, message *matterclient.Me
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint:forcetypeassert
|
|
||||||
func (b *Bmattermost) handleProps6(rmsg *config.Message, message *matterclient6.Message) {
|
|
||||||
props := message.Post.Props
|
|
||||||
if props == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, ok := props["override_username"].(string); ok {
|
|
||||||
rmsg.Username = props["override_username"].(string)
|
|
||||||
}
|
|
||||||
if _, ok := props["attachments"].([]interface{}); ok {
|
|
||||||
rmsg.Extra["attachments"] = props["attachments"].([]interface{})
|
|
||||||
if rmsg.Text != "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, attachment := range rmsg.Extra["attachments"] {
|
|
||||||
attach := attachment.(map[string]interface{})
|
|
||||||
if attach["text"].(string) != "" {
|
|
||||||
rmsg.Text += attach["text"].(string)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if attach["fallback"].(string) != "" {
|
|
||||||
rmsg.Text += attach["fallback"].(string)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
package bmattermost
|
package bmattermost
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
"github.com/42wim/matterbridge/matterclient"
|
"github.com/42wim/matterbridge/matterclient"
|
||||||
"github.com/42wim/matterbridge/matterhook"
|
"github.com/42wim/matterbridge/matterhook"
|
||||||
matterclient6 "github.com/matterbridge/matterclient"
|
|
||||||
"github.com/mattermost/mattermost-server/v5/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
model6 "github.com/mattermost/mattermost-server/v6/model"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *Bmattermost) doConnectWebhookBind() error {
|
func (b *Bmattermost) doConnectWebhookBind() error {
|
||||||
@@ -18,47 +15,25 @@ func (b *Bmattermost) doConnectWebhookBind() error {
|
|||||||
case b.GetString("WebhookURL") != "":
|
case b.GetString("WebhookURL") != "":
|
||||||
b.Log.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)")
|
b.Log.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)")
|
||||||
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
||||||
matterhook.Config{
|
matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
||||||
InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
BindAddress: b.GetString("WebhookBindAddress")})
|
||||||
BindAddress: b.GetString("WebhookBindAddress"),
|
|
||||||
})
|
|
||||||
case b.GetString("Token") != "":
|
case b.GetString("Token") != "":
|
||||||
b.Log.Info("Connecting using token (sending)")
|
b.Log.Info("Connecting using token (sending)")
|
||||||
b.Log.Infof("Using mattermost v6 methods: %t", b.v6)
|
err := b.apiLogin()
|
||||||
|
if err != nil {
|
||||||
if b.v6 {
|
return err
|
||||||
err := b.apiLogin6()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err := b.apiLogin()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case b.GetString("Login") != "":
|
case b.GetString("Login") != "":
|
||||||
b.Log.Info("Connecting using login/password (sending)")
|
b.Log.Info("Connecting using login/password (sending)")
|
||||||
b.Log.Infof("Using mattermost v6 methods: %t", b.v6)
|
err := b.apiLogin()
|
||||||
|
if err != nil {
|
||||||
if b.v6 {
|
return err
|
||||||
err := b.apiLogin6()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err := b.apiLogin()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
b.Log.Info("Connecting using webhookbindaddress (receiving)")
|
b.Log.Info("Connecting using webhookbindaddress (receiving)")
|
||||||
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
||||||
matterhook.Config{
|
matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
||||||
InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
BindAddress: b.GetString("WebhookBindAddress")})
|
||||||
BindAddress: b.GetString("WebhookBindAddress"),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -66,39 +41,19 @@ func (b *Bmattermost) doConnectWebhookBind() error {
|
|||||||
func (b *Bmattermost) doConnectWebhookURL() error {
|
func (b *Bmattermost) doConnectWebhookURL() error {
|
||||||
b.Log.Info("Connecting using webhookurl (sending)")
|
b.Log.Info("Connecting using webhookurl (sending)")
|
||||||
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
||||||
matterhook.Config{
|
matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
||||||
InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
DisableServer: true})
|
||||||
DisableServer: true,
|
|
||||||
})
|
|
||||||
if b.GetString("Token") != "" {
|
if b.GetString("Token") != "" {
|
||||||
b.Log.Info("Connecting using token (receiving)")
|
b.Log.Info("Connecting using token (receiving)")
|
||||||
b.Log.Infof("Using mattermost v6 methods: %t", b.v6)
|
err := b.apiLogin()
|
||||||
|
if err != nil {
|
||||||
if b.v6 {
|
return err
|
||||||
err := b.apiLogin6()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err := b.apiLogin()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if b.GetString("Login") != "" {
|
} else if b.GetString("Login") != "" {
|
||||||
b.Log.Info("Connecting using login/password (receiving)")
|
b.Log.Info("Connecting using login/password (receiving)")
|
||||||
b.Log.Infof("Using mattermost v6 methods: %t", b.v6)
|
err := b.apiLogin()
|
||||||
|
if err != nil {
|
||||||
if b.v6 {
|
return err
|
||||||
err := b.apiLogin6()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err := b.apiLogin()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -129,31 +84,6 @@ func (b *Bmattermost) apiLogin() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint:wrapcheck
|
|
||||||
func (b *Bmattermost) apiLogin6() error {
|
|
||||||
password := b.GetString("Password")
|
|
||||||
if b.GetString("Token") != "" {
|
|
||||||
password = "token=" + b.GetString("Token")
|
|
||||||
}
|
|
||||||
|
|
||||||
b.mc6 = matterclient6.New(b.GetString("Login"), password, b.GetString("Team"), b.GetString("Server"), "")
|
|
||||||
if b.GetBool("debug") {
|
|
||||||
b.mc6.SetLogLevel("debug")
|
|
||||||
}
|
|
||||||
b.mc6.SkipTLSVerify = b.GetBool("SkipTLSVerify")
|
|
||||||
b.mc6.SkipVersionCheck = b.GetBool("SkipVersionCheck")
|
|
||||||
b.mc6.NoTLS = b.GetBool("NoTLS")
|
|
||||||
b.Log.Infof("Connecting %s (team: %s) on %s", b.GetString("Login"), b.GetString("Team"), b.GetString("Server"))
|
|
||||||
|
|
||||||
if err := b.mc6.Login(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Log.Info("Connection succeeded")
|
|
||||||
b.TeamID = b.mc6.GetTeamID()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// replaceAction replace the message with the correct action (/me) code
|
// replaceAction replace the message with the correct action (/me) code
|
||||||
func (b *Bmattermost) replaceAction(text string) (string, bool) {
|
func (b *Bmattermost) replaceAction(text string) (string, bool) {
|
||||||
if strings.HasPrefix(text, "*") && strings.HasSuffix(text, "*") {
|
if strings.HasPrefix(text, "*") && strings.HasSuffix(text, "*") {
|
||||||
@@ -241,17 +171,11 @@ func (b *Bmattermost) skipMessage(message *matterclient.Message) bool {
|
|||||||
if b.GetBool("nosendjoinpart") {
|
if b.GetBool("nosendjoinpart") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
channelName := b.getChannelName(message.Post.ChannelId)
|
|
||||||
if channelName == "" {
|
|
||||||
channelName = message.Channel
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Log.Debugf("Sending JOIN_LEAVE event from %s to gateway", b.Account)
|
b.Log.Debugf("Sending JOIN_LEAVE event from %s to gateway", b.Account)
|
||||||
b.Remote <- config.Message{
|
b.Remote <- config.Message{
|
||||||
Username: "system",
|
Username: "system",
|
||||||
Text: message.Text,
|
Text: message.Text,
|
||||||
Channel: channelName,
|
Channel: message.Channel,
|
||||||
Account: b.Account,
|
Account: b.Account,
|
||||||
Event: config.EventJoinLeave,
|
Event: config.EventJoinLeave,
|
||||||
}
|
}
|
||||||
@@ -299,119 +223,3 @@ func (b *Bmattermost) skipMessage(message *matterclient.Message) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// skipMessages returns true if this message should not be handled
|
|
||||||
// nolint:gocyclo,cyclop
|
|
||||||
func (b *Bmattermost) skipMessage6(message *matterclient6.Message) bool {
|
|
||||||
// Handle join/leave
|
|
||||||
if message.Type == "system_join_leave" ||
|
|
||||||
message.Type == "system_join_channel" ||
|
|
||||||
message.Type == "system_leave_channel" {
|
|
||||||
if b.GetBool("nosendjoinpart") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
channelName := b.getChannelName(message.Post.ChannelId)
|
|
||||||
if channelName == "" {
|
|
||||||
channelName = message.Channel
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Log.Debugf("Sending JOIN_LEAVE event from %s to gateway", b.Account)
|
|
||||||
b.Remote <- config.Message{
|
|
||||||
Username: "system",
|
|
||||||
Text: message.Text,
|
|
||||||
Channel: channelName,
|
|
||||||
Account: b.Account,
|
|
||||||
Event: config.EventJoinLeave,
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle edited messages
|
|
||||||
if (message.Raw.EventType() == model6.WebsocketEventPostEdited) && b.GetBool("EditDisable") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore non-post messages
|
|
||||||
if message.Post == nil {
|
|
||||||
b.Log.Debugf("ignoring nil message.Post: %#v", message)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore messages sent from matterbridge
|
|
||||||
if message.Post.Props != nil {
|
|
||||||
if _, ok := message.Post.Props["matterbridge_"+b.uuid].(bool); ok {
|
|
||||||
b.Log.Debug("sent by matterbridge, ignoring")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore messages sent from a user logged in as the bot
|
|
||||||
if b.mc6.User.Username == message.Username {
|
|
||||||
b.Log.Debug("message from same user as bot, ignoring")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the message has reactions don't repost it (for now, until we can correlate reaction with message)
|
|
||||||
if message.Post.HasReactions {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore messages from other teams than ours
|
|
||||||
if message.Raw.GetData()["team_id"].(string) != b.TeamID {
|
|
||||||
b.Log.Debug("message from other team, ignoring")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// only handle posted, edited or deleted events
|
|
||||||
if !(message.Raw.EventType() == "posted" || message.Raw.EventType() == model6.WebsocketEventPostEdited ||
|
|
||||||
message.Raw.EventType() == model6.WebsocketEventPostDeleted) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bmattermost) getVersion() string {
|
|
||||||
proto := "https"
|
|
||||||
|
|
||||||
if b.GetBool("notls") {
|
|
||||||
proto = "http"
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := http.Get(proto + "://" + b.GetString("server"))
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Error("failed getting version")
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
return resp.Header.Get("X-Version-Id")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bmattermost) getChannelID(name string) string {
|
|
||||||
idcheck := strings.Split(name, "ID:")
|
|
||||||
if len(idcheck) > 1 {
|
|
||||||
return idcheck[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.mc6 != nil {
|
|
||||||
return b.mc6.GetChannelID(name, b.TeamID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.mc.GetChannelId(name, b.TeamID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bmattermost) 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,43 +3,29 @@ package bmattermost
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/42wim/matterbridge/bridge"
|
"github.com/42wim/matterbridge/bridge"
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
"github.com/42wim/matterbridge/matterclient"
|
"github.com/42wim/matterbridge/matterclient"
|
||||||
"github.com/42wim/matterbridge/matterhook"
|
"github.com/42wim/matterbridge/matterhook"
|
||||||
matterclient6 "github.com/matterbridge/matterclient"
|
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Bmattermost struct {
|
type Bmattermost struct {
|
||||||
mh *matterhook.Client
|
mh *matterhook.Client
|
||||||
mc *matterclient.MMClient
|
mc *matterclient.MMClient
|
||||||
mc6 *matterclient6.Client
|
|
||||||
v6 bool
|
|
||||||
uuid string
|
uuid string
|
||||||
TeamID string
|
TeamID string
|
||||||
*bridge.Config
|
*bridge.Config
|
||||||
avatarMap map[string]string
|
avatarMap map[string]string
|
||||||
channelsMutex sync.RWMutex
|
|
||||||
channelInfoMap map[string]*config.ChannelInfo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mattermostPlugin = "mattermost.plugin"
|
const mattermostPlugin = "mattermost.plugin"
|
||||||
|
|
||||||
func New(cfg *bridge.Config) bridge.Bridger {
|
func New(cfg *bridge.Config) bridge.Bridger {
|
||||||
b := &Bmattermost{
|
b := &Bmattermost{Config: cfg, avatarMap: make(map[string]string)}
|
||||||
Config: cfg,
|
|
||||||
avatarMap: make(map[string]string),
|
|
||||||
channelInfoMap: make(map[string]*config.ChannelInfo),
|
|
||||||
}
|
|
||||||
|
|
||||||
b.v6 = b.GetBool("v6")
|
|
||||||
b.uuid = xid.New().String()
|
b.uuid = xid.New().String()
|
||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,13 +37,6 @@ func (b *Bmattermost) Connect() error {
|
|||||||
if b.Account == mattermostPlugin {
|
if b.Account == mattermostPlugin {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(b.getVersion(), "6.") {
|
|
||||||
if !b.v6 {
|
|
||||||
b.v6 = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.GetString("WebhookBindAddress") != "" {
|
if b.GetString("WebhookBindAddress") != "" {
|
||||||
if err := b.doConnectWebhookBind(); err != nil {
|
if err := b.doConnectWebhookBind(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -74,34 +53,16 @@ func (b *Bmattermost) Connect() error {
|
|||||||
return nil
|
return nil
|
||||||
case b.GetString("Token") != "":
|
case b.GetString("Token") != "":
|
||||||
b.Log.Info("Connecting using token (sending and receiving)")
|
b.Log.Info("Connecting using token (sending and receiving)")
|
||||||
b.Log.Infof("Using mattermost v6 methods: %t", b.v6)
|
err := b.apiLogin()
|
||||||
|
if err != nil {
|
||||||
if b.v6 {
|
return err
|
||||||
err := b.apiLogin6()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err := b.apiLogin()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
go b.handleMatter()
|
go b.handleMatter()
|
||||||
case b.GetString("Login") != "":
|
case b.GetString("Login") != "":
|
||||||
b.Log.Info("Connecting using login/password (sending and receiving)")
|
b.Log.Info("Connecting using login/password (sending and receiving)")
|
||||||
b.Log.Infof("Using mattermost v6 methods: %t", b.v6)
|
err := b.apiLogin()
|
||||||
|
if err != nil {
|
||||||
if b.v6 {
|
return err
|
||||||
err := b.apiLogin6()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err := b.apiLogin()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
go b.handleMatter()
|
go b.handleMatter()
|
||||||
}
|
}
|
||||||
@@ -120,25 +81,14 @@ func (b *Bmattermost) JoinChannel(channel config.ChannelInfo) error {
|
|||||||
if b.Account == mattermostPlugin {
|
if b.Account == mattermostPlugin {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
b.channelsMutex.Lock()
|
|
||||||
b.channelInfoMap[channel.ID] = &channel
|
|
||||||
b.channelsMutex.Unlock()
|
|
||||||
|
|
||||||
// we can only join channels using the API
|
// we can only join channels using the API
|
||||||
if b.GetString("WebhookURL") == "" && b.GetString("WebhookBindAddress") == "" {
|
if b.GetString("WebhookURL") == "" && b.GetString("WebhookBindAddress") == "" {
|
||||||
id := b.getChannelID(channel.Name)
|
id := b.mc.GetChannelId(channel.Name, b.TeamID)
|
||||||
if id == "" {
|
if id == "" {
|
||||||
return fmt.Errorf("Could not find channel ID for channel %s", channel.Name)
|
return fmt.Errorf("Could not find channel ID for channel %s", channel.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.mc6 != nil {
|
|
||||||
return b.mc6.JoinChannel(id) // nolint:wrapcheck
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.mc.JoinChannel(id)
|
return b.mc.JoinChannel(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,51 +118,20 @@ func (b *Bmattermost) Send(msg config.Message) (string, error) {
|
|||||||
if msg.ID == "" {
|
if msg.ID == "" {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
if b.mc6 != nil {
|
|
||||||
return msg.ID, b.mc6.DeleteMessage(msg.ID) // nolint:wrapcheck
|
|
||||||
}
|
|
||||||
|
|
||||||
return msg.ID, b.mc.DeleteMessage(msg.ID)
|
return msg.ID, b.mc.DeleteMessage(msg.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle prefix hint for unthreaded messages.
|
// Handle prefix hint for unthreaded messages.
|
||||||
if msg.ParentNotFound() {
|
if msg.ParentID == "msg-parent-not-found" {
|
||||||
msg.ParentID = ""
|
msg.ParentID = ""
|
||||||
msg.Text = fmt.Sprintf("[thread]: %s", msg.Text)
|
msg.Text = fmt.Sprintf("[thread]: %s", msg.Text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// we only can reply to the root of the thread, not to a specific ID (like discord for example does)
|
|
||||||
if msg.ParentID != "" {
|
|
||||||
if b.mc6 != nil {
|
|
||||||
post, _, err := b.mc6.Client.GetPost(msg.ParentID, "")
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("getting post %s failed: %s", msg.ParentID, err)
|
|
||||||
}
|
|
||||||
if post.RootId != "" {
|
|
||||||
msg.ParentID = post.RootId
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
post, res := b.mc.Client.GetPost(msg.ParentID, "")
|
|
||||||
if res.Error != nil {
|
|
||||||
b.Log.Errorf("getting post %s failed: %s", msg.ParentID, res.Error.DetailedError)
|
|
||||||
}
|
|
||||||
if post.RootId != "" {
|
|
||||||
msg.ParentID = post.RootId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload a file if it exists
|
// Upload a file if it exists
|
||||||
if msg.Extra != nil {
|
if msg.Extra != nil {
|
||||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||||
if b.mc6 != nil {
|
if _, err := b.mc.PostMessage(b.mc.GetChannelId(rmsg.Channel, b.TeamID), rmsg.Username+rmsg.Text, msg.ParentID); err != nil {
|
||||||
if _, err := b.mc6.PostMessage(b.getChannelID(rmsg.Channel), rmsg.Username+rmsg.Text, msg.ParentID); err != nil {
|
b.Log.Errorf("PostMessage failed: %s", err)
|
||||||
b.Log.Errorf("PostMessage failed: %s", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if _, err := b.mc.PostMessage(b.getChannelID(rmsg.Channel), rmsg.Username+rmsg.Text, msg.ParentID); err != nil {
|
|
||||||
b.Log.Errorf("PostMessage failed: %s", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(msg.Extra["file"]) > 0 {
|
if len(msg.Extra["file"]) > 0 {
|
||||||
@@ -227,17 +146,9 @@ func (b *Bmattermost) Send(msg config.Message) (string, error) {
|
|||||||
|
|
||||||
// Edit message if we have an ID
|
// Edit message if we have an ID
|
||||||
if msg.ID != "" {
|
if msg.ID != "" {
|
||||||
if b.mc6 != nil {
|
|
||||||
return b.mc6.EditMessage(msg.ID, msg.Text) // nolint:wrapcheck
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.mc.EditMessage(msg.ID, msg.Text)
|
return b.mc.EditMessage(msg.ID, msg.Text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post normal message
|
// Post normal message
|
||||||
if b.mc6 != nil {
|
return b.mc.PostMessage(b.mc.GetChannelId(msg.Channel, b.TeamID), msg.Text, msg.ParentID)
|
||||||
return b.mc6.PostMessage(b.getChannelID(msg.Channel), msg.Text, msg.ParentID) // nolint:wrapcheck
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.mc.PostMessage(b.getChannelID(msg.Channel), msg.Text, msg.ParentID)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,8 @@ import (
|
|||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var defaultScopes = []string{"openid", "profile", "offline_access", "Group.Read.All", "Group.ReadWrite.All"}
|
||||||
defaultScopes = []string{"openid", "profile", "offline_access", "Group.Read.All", "Group.ReadWrite.All"}
|
var attachRE = regexp.MustCompile(`<attachment id=.*?attachment>`)
|
||||||
attachRE = regexp.MustCompile(`<attachment id=.*?attachment>`)
|
|
||||||
)
|
|
||||||
|
|
||||||
type Bmsteams struct {
|
type Bmsteams struct {
|
||||||
gc *msgraph.GraphServiceRequestBuilder
|
gc *msgraph.GraphServiceRequestBuilder
|
||||||
@@ -52,7 +50,7 @@ func (b *Bmsteams) Connect() error {
|
|||||||
b.Log.Errorf("Couldn't save sessionfile in %s: %s", tokenCachePath, err)
|
b.Log.Errorf("Couldn't save sessionfile in %s: %s", tokenCachePath, err)
|
||||||
}
|
}
|
||||||
// make file readable only for matterbridge user
|
// make file readable only for matterbridge user
|
||||||
err = os.Chmod(tokenCachePath, 0o600)
|
err = os.Chmod(tokenCachePath, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Errorf("Couldn't change permissions for %s: %s", tokenCachePath, err)
|
b.Log.Errorf("Couldn't change permissions for %s: %s", tokenCachePath, err)
|
||||||
}
|
}
|
||||||
@@ -88,16 +86,13 @@ func (b *Bmsteams) JoinChannel(channel config.ChannelInfo) error {
|
|||||||
|
|
||||||
func (b *Bmsteams) Send(msg config.Message) (string, error) {
|
func (b *Bmsteams) Send(msg config.Message) (string, error) {
|
||||||
b.Log.Debugf("=> Receiving %#v", msg)
|
b.Log.Debugf("=> Receiving %#v", msg)
|
||||||
if msg.ParentValid() {
|
if msg.ParentID != "" && msg.ParentID != "msg-parent-not-found" {
|
||||||
return b.sendReply(msg)
|
return b.sendReply(msg)
|
||||||
}
|
}
|
||||||
|
if msg.ParentID == "msg-parent-not-found" {
|
||||||
// Handle prefix hint for unthreaded messages.
|
|
||||||
if msg.ParentNotFound() {
|
|
||||||
msg.ParentID = ""
|
msg.ParentID = ""
|
||||||
msg.Text = fmt.Sprintf("[thread]: %s", msg.Text)
|
msg.Text = fmt.Sprintf("[thread]: %s", msg.Text)
|
||||||
}
|
}
|
||||||
|
|
||||||
ct := b.gc.Teams().ID(b.GetString("TeamID")).Channels().ID(msg.Channel).Messages().Request()
|
ct := b.gc.Teams().ID(b.GetString("TeamID")).Channels().ID(msg.Channel).Messages().Request()
|
||||||
text := msg.Username + msg.Text
|
text := msg.Username + msg.Text
|
||||||
content := &msgraph.ItemBody{Content: &text}
|
content := &msgraph.ItemBody{Content: &text}
|
||||||
@@ -170,7 +165,7 @@ func (b *Bmsteams) poll(channelName string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// skip non-user message for now.
|
// skip non-user message for now.
|
||||||
if msg.From == nil || msg.From.User == nil {
|
if msg.From.User == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
package bmumble
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"layeh.com/gumble/gumble"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This is a dummy implementation of a Gumble audio codec which claims
|
|
||||||
// to implement Opus, but does not actually do anything. This serves
|
|
||||||
// as a workaround until https://github.com/layeh/gumble/pull/61 is
|
|
||||||
// merged.
|
|
||||||
// See https://github.com/42wim/matterbridge/issues/1750 for details.
|
|
||||||
|
|
||||||
const (
|
|
||||||
audioCodecIDOpus = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
func registerNullCodecAsOpus() {
|
|
||||||
codec := &NullCodec{
|
|
||||||
encoder: &NullAudioEncoder{},
|
|
||||||
decoder: &NullAudioDecoder{},
|
|
||||||
}
|
|
||||||
gumble.RegisterAudioCodec(audioCodecIDOpus, codec)
|
|
||||||
}
|
|
||||||
|
|
||||||
type NullCodec struct {
|
|
||||||
encoder *NullAudioEncoder
|
|
||||||
decoder *NullAudioDecoder
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *NullCodec) ID() int {
|
|
||||||
return audioCodecIDOpus
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *NullCodec) NewEncoder() gumble.AudioEncoder {
|
|
||||||
e := &NullAudioEncoder{}
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *NullCodec) NewDecoder() gumble.AudioDecoder {
|
|
||||||
d := &NullAudioDecoder{}
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
type NullAudioEncoder struct{}
|
|
||||||
|
|
||||||
func (e *NullAudioEncoder) ID() int {
|
|
||||||
return audioCodecIDOpus
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *NullAudioEncoder) Encode(pcm []int16, mframeSize, maxDataBytes int) ([]byte, error) {
|
|
||||||
return nil, fmt.Errorf("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *NullAudioEncoder) Reset() {
|
|
||||||
}
|
|
||||||
|
|
||||||
type NullAudioDecoder struct{}
|
|
||||||
|
|
||||||
func (d *NullAudioDecoder) ID() int {
|
|
||||||
return audioCodecIDOpus
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *NullAudioDecoder) Decode(data []byte, frameSize int) ([]int16, error) {
|
|
||||||
return nil, fmt.Errorf("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *NullAudioDecoder) Reset() {
|
|
||||||
}
|
|
||||||
@@ -19,12 +19,6 @@ func (b *Bmumble) handleTextMessage(event *gumble.TextMessageEvent) {
|
|||||||
if event.TextMessage.Sender != nil {
|
if event.TextMessage.Sender != nil {
|
||||||
sender = event.TextMessage.Sender.Name
|
sender = event.TextMessage.Sender.Name
|
||||||
}
|
}
|
||||||
// If the text message is received before receiving a ServerSync
|
|
||||||
// and UserState, Client.Self or Self.Channel are nil
|
|
||||||
if event.Client.Self == nil || event.Client.Self.Channel == nil {
|
|
||||||
b.Log.Warn("Connection bootstrap not finished, discarding text message")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Convert Mumble HTML messages to markdown
|
// Convert Mumble HTML messages to markdown
|
||||||
parts, err := b.convertHTMLtoMarkdown(event.TextMessage.Message)
|
parts, err := b.convertHTMLtoMarkdown(event.TextMessage.Message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"layeh.com/gumble/gumble"
|
"layeh.com/gumble/gumble"
|
||||||
@@ -185,7 +184,6 @@ func (b *Bmumble) doConnect() error {
|
|||||||
gumbleConfig.Password = password
|
gumbleConfig.Password = password
|
||||||
}
|
}
|
||||||
|
|
||||||
registerNullCodecAsOpus()
|
|
||||||
client, err := gumble.DialWithDialer(new(net.Dialer), b.GetString("Server"), gumbleConfig, &b.tlsConfig)
|
client, err := gumble.DialWithDialer(new(net.Dialer), b.GetString("Server"), gumbleConfig, &b.tlsConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -250,14 +248,12 @@ func (b *Bmumble) processMessage(msg *config.Message) {
|
|||||||
// If there is a maximum message length, split and truncate the lines
|
// If there is a maximum message length, split and truncate the lines
|
||||||
var msgLines []string
|
var msgLines []string
|
||||||
if maxLength := b.serverConfig.MaximumMessageLength; maxLength != nil {
|
if maxLength := b.serverConfig.MaximumMessageLength; maxLength != nil {
|
||||||
msgLines = helper.GetSubLines(msg.Text, *maxLength-len(msg.Username), b.GetString("MessageClipped"))
|
msgLines = helper.GetSubLines(msg.Text, *maxLength-len(msg.Username))
|
||||||
} else {
|
} else {
|
||||||
msgLines = helper.GetSubLines(msg.Text, 0, b.GetString("MessageClipped"))
|
msgLines = helper.GetSubLines(msg.Text, 0)
|
||||||
}
|
}
|
||||||
// Send the individual lines
|
// Send the individual lindes
|
||||||
for i := range msgLines {
|
for i := range msgLines {
|
||||||
// Remove unnecessary newline character, since either way we're sending it as individual lines
|
|
||||||
msgLines[i] = strings.TrimSuffix(msgLines[i], "\n")
|
|
||||||
b.client.Self.Channel.Send(msg.Username+msgLines[i], false)
|
b.client.Self.Channel.Send(msg.Username+msgLines[i], false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,33 +74,44 @@ func (b *Btalk) JoinChannel(channel config.ChannelInfo) error {
|
|||||||
}
|
}
|
||||||
b.rooms = append(b.rooms, newRoom)
|
b.rooms = append(b.rooms, newRoom)
|
||||||
|
|
||||||
|
// Config
|
||||||
|
guestSuffix := " (Guest)"
|
||||||
|
if b.IsKeySet("GuestSuffix") {
|
||||||
|
guestSuffix = b.GetString("GuestSuffix")
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for msg := range c {
|
for msg := range c {
|
||||||
msg := msg
|
msg := msg
|
||||||
|
// ignore messages that are one of the following
|
||||||
if msg.Error != nil {
|
// * not a message from a user
|
||||||
b.Log.Errorf("Fatal message poll error: %s\n", msg.Error)
|
// * from ourselves
|
||||||
|
if msg.MessageType != ocs.MessageComment || msg.ActorID == b.user.User {
|
||||||
return
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore messages that are from the bot user
|
// Handle Files
|
||||||
if msg.ActorID == b.user.User || msg.ActorType == "bridged" {
|
err = b.handleFiles(&remoteMessage, &msg)
|
||||||
continue
|
if err != nil {
|
||||||
}
|
b.Log.Errorf("Error handling file: %#v", msg)
|
||||||
|
|
||||||
// Handle deleting messages
|
|
||||||
if msg.MessageType == ocs.MessageSystem && msg.Parent != nil && msg.Parent.MessageType == ocs.MessageDelete {
|
|
||||||
b.handleDeletingMessage(&msg, &newRoom)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle sending messages
|
|
||||||
if msg.MessageType == ocs.MessageComment {
|
|
||||||
b.handleSendingMessage(&msg, &newRoom)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b.Log.Debugf("<= Message is %#v", remoteMessage)
|
||||||
|
b.Remote <- remoteMessage
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return nil
|
return nil
|
||||||
@@ -113,40 +124,16 @@ func (b *Btalk) Send(msg config.Message) (string, error) {
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Standard Message Send
|
// Talk currently only supports sending normal messages
|
||||||
if msg.Event == "" {
|
if msg.Event != "" {
|
||||||
// Handle sending files if they are included
|
return "", nil
|
||||||
err := b.handleSendingFile(&msg, r)
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("Could not send files in message to room %v from %v: %v", msg.Channel, msg.Username, err)
|
|
||||||
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
sentMessage, err := b.sendText(r, &msg, 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
|
|
||||||
}
|
}
|
||||||
|
sentMessage, err := r.room.SendMessage(msg.Username + msg.Text)
|
||||||
// Message Deletion
|
if err != nil {
|
||||||
if msg.Event == config.EventMsgDelete {
|
b.Log.Errorf("Could not send message to room %v from %v: %v", msg.Channel, msg.Username, err)
|
||||||
messageID, err := strconv.Atoi(msg.ID)
|
return "", nil
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
data, err := r.room.DeleteMessage(messageID)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return strconv.Itoa(data.ID), nil
|
|
||||||
}
|
}
|
||||||
|
return strconv.Itoa(sentMessage.ID), nil
|
||||||
// Message is not a type that is currently supported
|
|
||||||
return "", nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Btalk) getRoom(token string) *Broom {
|
func (b *Btalk) getRoom(token string) *Broom {
|
||||||
@@ -158,17 +145,6 @@ func (b *Btalk) getRoom(token string) *Broom {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Btalk) sendText(r *Broom, msg *config.Message, text string) (*ocs.TalkRoomMessageData, error) {
|
|
||||||
messageToSend := &room.Message{Message: msg.Username + text}
|
|
||||||
|
|
||||||
if b.GetBool("SeparateDisplayName") {
|
|
||||||
messageToSend.Message = text
|
|
||||||
messageToSend.ActorDisplayName = msg.Username
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.room.SendComplexMessage(messageToSend)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Btalk) handleFiles(mmsg *config.Message, message *ocs.TalkRoomMessageData) error {
|
func (b *Btalk) handleFiles(mmsg *config.Message, message *ocs.TalkRoomMessageData) error {
|
||||||
for _, parameter := range message.MessageParameters {
|
for _, parameter := range message.MessageParameters {
|
||||||
if parameter.Type == ocs.ROSTypeFile {
|
if parameter.Type == ocs.ROSTypeFile {
|
||||||
@@ -194,74 +170,6 @@ func (b *Btalk) handleFiles(mmsg *config.Message, message *ocs.TalkRoomMessageDa
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Btalk) handleSendingFile(msg *config.Message, r *Broom) error {
|
|
||||||
for _, f := range msg.Extra["file"] {
|
|
||||||
fi := f.(config.FileInfo)
|
|
||||||
if fi.URL == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
message := ""
|
|
||||||
if fi.Comment != "" {
|
|
||||||
message += fi.Comment + " "
|
|
||||||
}
|
|
||||||
message += fi.URL
|
|
||||||
_, err := b.sendText(r, msg, message)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Btalk) handleSendingMessage(msg *ocs.TalkRoomMessageData, r *Broom) {
|
|
||||||
remoteMessage := config.Message{
|
|
||||||
Text: formatRichObjectString(msg.Message, msg.MessageParameters),
|
|
||||||
Channel: r.room.Token,
|
|
||||||
Username: DisplayName(msg, b.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)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Log.Debugf("<= Message is %#v", remoteMessage)
|
|
||||||
b.Remote <- remoteMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Btalk) handleDeletingMessage(msg *ocs.TalkRoomMessageData, r *Broom) {
|
|
||||||
remoteMessage := config.Message{
|
|
||||||
Event: config.EventMsgDelete,
|
|
||||||
Text: config.EventMsgDelete,
|
|
||||||
Channel: r.room.Token,
|
|
||||||
ID: strconv.Itoa(msg.Parent.ID),
|
|
||||||
Account: b.Account,
|
|
||||||
}
|
|
||||||
b.Log.Debugf("<= Message being deleted is %#v", remoteMessage)
|
|
||||||
b.Remote <- remoteMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Btalk) guestSuffix() string {
|
|
||||||
guestSuffix := " (Guest)"
|
|
||||||
if b.IsKeySet("GuestSuffix") {
|
|
||||||
guestSuffix = b.GetString("GuestSuffix")
|
|
||||||
}
|
|
||||||
|
|
||||||
return guestSuffix
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spec: https://github.com/nextcloud/server/issues/1706#issue-182308785
|
// Spec: https://github.com/nextcloud/server/issues/1706#issue-182308785
|
||||||
func formatRichObjectString(message string, parameters map[string]ocs.RichObjectString) string {
|
func formatRichObjectString(message string, parameters map[string]ocs.RichObjectString) string {
|
||||||
for id, parameter := range parameters {
|
for id, parameter := range parameters {
|
||||||
@@ -282,7 +190,7 @@ func formatRichObjectString(message string, parameters map[string]ocs.RichObject
|
|||||||
return message
|
return message
|
||||||
}
|
}
|
||||||
|
|
||||||
func DisplayName(msg *ocs.TalkRoomMessageData, suffix string) string {
|
func DisplayName(msg ocs.TalkRoomMessageData, suffix string) string {
|
||||||
if msg.ActorType == ocs.ActorGuest {
|
if msg.ActorType == ocs.ActorGuest {
|
||||||
if msg.ActorDisplayName == "" {
|
if msg.ActorDisplayName == "" {
|
||||||
return "Guest"
|
return "Guest"
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
package brocketchat
|
package brocketchat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
|
||||||
"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
|
"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -61,7 +58,6 @@ func (b *Brocketchat) handleStatusEvent(ev models.Message, rmsg *config.Message)
|
|||||||
|
|
||||||
func (b *Brocketchat) handleRocketClient(messages chan *config.Message) {
|
func (b *Brocketchat) handleRocketClient(messages chan *config.Message) {
|
||||||
for message := range b.messageChan {
|
for message := range b.messageChan {
|
||||||
message := message
|
|
||||||
// skip messages with same ID, apparently messages get duplicated for an unknown reason
|
// skip messages with same ID, apparently messages get duplicated for an unknown reason
|
||||||
if _, ok := b.cache.Get(message.ID); ok {
|
if _, ok := b.cache.Get(message.ID); ok {
|
||||||
continue
|
continue
|
||||||
@@ -80,11 +76,8 @@ func (b *Brocketchat) handleRocketClient(messages chan *config.Message) {
|
|||||||
Account: b.Account,
|
Account: b.Account,
|
||||||
UserID: message.User.ID,
|
UserID: message.User.ID,
|
||||||
ID: message.ID,
|
ID: message.ID,
|
||||||
Extra: make(map[string][]interface{}),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
b.handleAttachments(&message, rmsg)
|
|
||||||
|
|
||||||
// handleStatusEvent returns false if the message should be dropped
|
// 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
|
// in that case it is probably some modification to the channel we do not want to relay
|
||||||
if b.handleStatusEvent(m, rmsg) {
|
if b.handleStatusEvent(m, rmsg) {
|
||||||
@@ -93,38 +86,6 @@ func (b *Brocketchat) handleRocketClient(messages chan *config.Message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Brocketchat) handleAttachments(message *models.Message, rmsg *config.Message) {
|
|
||||||
if rmsg.Text == "" {
|
|
||||||
for _, attachment := range message.Attachments {
|
|
||||||
if attachment.Title != "" {
|
|
||||||
rmsg.Text = attachment.Title + "\n"
|
|
||||||
}
|
|
||||||
if attachment.Title != "" && attachment.Text != "" {
|
|
||||||
rmsg.Text += "\n"
|
|
||||||
}
|
|
||||||
if attachment.Text != "" {
|
|
||||||
rmsg.Text += attachment.Text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range message.Attachments {
|
|
||||||
if err := b.handleDownloadFile(rmsg, &message.Attachments[i]); err != nil {
|
|
||||||
b.Log.Errorf("Could not download incoming file: %#v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Brocketchat) handleDownloadFile(rmsg *config.Message, file *models.Attachment) error {
|
|
||||||
downloadURL := b.GetString("server") + file.TitleLink
|
|
||||||
data, err := helper.DownloadFileAuthRocket(downloadURL, b.user.Token, b.user.ID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("download %s failed %#v", downloadURL, err)
|
|
||||||
}
|
|
||||||
helper.HandleDownloadData(b.Log, rmsg, file.Title, rmsg.Text, downloadURL, data, b.General)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Brocketchat) handleUploadFile(msg *config.Message) error {
|
func (b *Brocketchat) handleUploadFile(msg *config.Message) error {
|
||||||
for _, f := range msg.Extra["file"] {
|
for _, f := range msg.Extra["file"] {
|
||||||
fi := f.(config.FileInfo)
|
fi := f.(config.FileInfo)
|
||||||
|
|||||||
@@ -27,8 +27,7 @@ func (b *Bslack) handleSlack() {
|
|||||||
b.Log.Debug("Start listening for Slack messages")
|
b.Log.Debug("Start listening for Slack messages")
|
||||||
for message := range messages {
|
for message := range messages {
|
||||||
// don't do any action on deleted/typing messages
|
// don't do any action on deleted/typing messages
|
||||||
if message.Event != config.EventUserTyping && message.Event != config.EventMsgDelete &&
|
if message.Event != config.EventUserTyping && message.Event != config.EventMsgDelete {
|
||||||
message.Event != config.EventFileDelete {
|
|
||||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", message.Username, b.Account)
|
b.Log.Debugf("<= Sending message from %s on %s to gateway", message.Username, b.Account)
|
||||||
// cleanup the message
|
// cleanup the message
|
||||||
message.Text = b.replaceMention(message.Text)
|
message.Text = b.replaceMention(message.Text)
|
||||||
@@ -77,13 +76,6 @@ func (b *Bslack) handleSlackClient(messages chan *config.Message) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
messages <- rmsg
|
messages <- rmsg
|
||||||
case *slack.FileDeletedEvent:
|
|
||||||
rmsg, err := b.handleFileDeletedEvent(ev)
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("%#v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
messages <- rmsg
|
|
||||||
case *slack.OutgoingErrorEvent:
|
case *slack.OutgoingErrorEvent:
|
||||||
b.Log.Debugf("%#v", ev.Error())
|
b.Log.Debugf("%#v", ev.Error())
|
||||||
case *slack.ChannelJoinedEvent:
|
case *slack.ChannelJoinedEvent:
|
||||||
@@ -103,8 +95,6 @@ func (b *Bslack) handleSlackClient(messages chan *config.Message) {
|
|||||||
b.users.populateUser(ev.User)
|
b.users.populateUser(ev.User)
|
||||||
case *slack.HelloEvent, *slack.LatencyReport, *slack.ConnectingEvent:
|
case *slack.HelloEvent, *slack.LatencyReport, *slack.ConnectingEvent:
|
||||||
continue
|
continue
|
||||||
case *slack.UserChangeEvent:
|
|
||||||
b.users.invalidateUser(ev.User.ID)
|
|
||||||
default:
|
default:
|
||||||
b.Log.Debugf("Unhandled incoming event: %T", ev)
|
b.Log.Debugf("Unhandled incoming event: %T", ev)
|
||||||
}
|
}
|
||||||
@@ -230,26 +220,6 @@ func (b *Bslack) handleMessageEvent(ev *slack.MessageEvent) (*config.Message, er
|
|||||||
return rmsg, nil
|
return rmsg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bslack) handleFileDeletedEvent(ev *slack.FileDeletedEvent) (*config.Message, error) {
|
|
||||||
if rawChannel, ok := b.cache.Get(cfileDownloadChannel + ev.FileID); ok {
|
|
||||||
channel, err := b.channels.getChannelByID(rawChannel.(string))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &config.Message{
|
|
||||||
Event: config.EventFileDelete,
|
|
||||||
Text: config.EventFileDelete,
|
|
||||||
Channel: channel.Name,
|
|
||||||
Account: b.Account,
|
|
||||||
ID: ev.FileID,
|
|
||||||
Protocol: b.Protocol,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("channel ID for file ID %s not found", ev.FileID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bslack) handleStatusEvent(ev *slack.MessageEvent, rmsg *config.Message) bool {
|
func (b *Bslack) handleStatusEvent(ev *slack.MessageEvent, rmsg *config.Message) bool {
|
||||||
switch ev.SubType {
|
switch ev.SubType {
|
||||||
case sChannelJoined, sMemberJoined:
|
case sChannelJoined, sMemberJoined:
|
||||||
@@ -309,8 +279,6 @@ func (b *Bslack) handleAttachments(ev *slack.MessageEvent, rmsg *config.Message)
|
|||||||
|
|
||||||
// If we have files attached, download them (in memory) and put a pointer to it in msg.Extra.
|
// If we have files attached, download them (in memory) and put a pointer to it in msg.Extra.
|
||||||
for i := range ev.Files {
|
for i := range ev.Files {
|
||||||
// keep reference in cache on which channel we added this file
|
|
||||||
b.cache.Add(cfileDownloadChannel+ev.Files[i].ID, ev.Channel)
|
|
||||||
if err := b.handleDownloadFile(rmsg, &ev.Files[i], false); err != nil {
|
if err := b.handleDownloadFile(rmsg, &ev.Files[i], false); err != nil {
|
||||||
b.Log.Errorf("Could not download incoming file: %#v", err)
|
b.Log.Errorf("Could not download incoming file: %#v", err)
|
||||||
}
|
}
|
||||||
@@ -360,7 +328,7 @@ func (b *Bslack) handleDownloadFile(rmsg *config.Message, file *slack.File, retr
|
|||||||
// that the comment is not duplicated.
|
// that the comment is not duplicated.
|
||||||
comment := rmsg.Text
|
comment := rmsg.Text
|
||||||
rmsg.Text = ""
|
rmsg.Text = ""
|
||||||
helper.HandleDownloadData2(b.Log, rmsg, file.Name, file.ID, comment, file.URLPrivateDownload, data, b.General)
|
helper.HandleDownloadData(b.Log, rmsg, file.Name, comment, file.URLPrivateDownload, data, b.General)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,25 +36,24 @@ type Bslack struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
sHello = "hello"
|
sHello = "hello"
|
||||||
sChannelJoin = "channel_join"
|
sChannelJoin = "channel_join"
|
||||||
sChannelLeave = "channel_leave"
|
sChannelLeave = "channel_leave"
|
||||||
sChannelJoined = "channel_joined"
|
sChannelJoined = "channel_joined"
|
||||||
sMemberJoined = "member_joined_channel"
|
sMemberJoined = "member_joined_channel"
|
||||||
sMessageChanged = "message_changed"
|
sMessageChanged = "message_changed"
|
||||||
sMessageDeleted = "message_deleted"
|
sMessageDeleted = "message_deleted"
|
||||||
sSlackAttachment = "slack_attachment"
|
sSlackAttachment = "slack_attachment"
|
||||||
sPinnedItem = "pinned_item"
|
sPinnedItem = "pinned_item"
|
||||||
sUnpinnedItem = "unpinned_item"
|
sUnpinnedItem = "unpinned_item"
|
||||||
sChannelTopic = "channel_topic"
|
sChannelTopic = "channel_topic"
|
||||||
sChannelPurpose = "channel_purpose"
|
sChannelPurpose = "channel_purpose"
|
||||||
sFileComment = "file_comment"
|
sFileComment = "file_comment"
|
||||||
sMeMessage = "me_message"
|
sMeMessage = "me_message"
|
||||||
sUserTyping = "user_typing"
|
sUserTyping = "user_typing"
|
||||||
sLatencyReport = "latency_report"
|
sLatencyReport = "latency_report"
|
||||||
sSystemUser = "system"
|
sSystemUser = "system"
|
||||||
sSlackBotUser = "slackbot"
|
sSlackBotUser = "slackbot"
|
||||||
cfileDownloadChannel = "file_download_channel"
|
|
||||||
|
|
||||||
tokenConfig = "Token"
|
tokenConfig = "Token"
|
||||||
incomingWebhookConfig = "WebhookBindAddress"
|
incomingWebhookConfig = "WebhookBindAddress"
|
||||||
@@ -157,7 +156,7 @@ func (b *Bslack) JoinChannel(channel config.ChannelInfo) error {
|
|||||||
|
|
||||||
// try to join a channel when in legacy
|
// try to join a channel when in legacy
|
||||||
if b.legacy {
|
if b.legacy {
|
||||||
_, _, _, err := b.sc.JoinConversation(channel.Name)
|
_, err := b.sc.JoinChannel(channel.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err.Error() {
|
switch err.Error() {
|
||||||
case "name_taken", "restricted_action":
|
case "name_taken", "restricted_action":
|
||||||
@@ -196,7 +195,7 @@ func (b *Bslack) Send(msg config.Message) (string, error) {
|
|||||||
b.Log.Debugf("=> Receiving %#v", msg)
|
b.Log.Debugf("=> Receiving %#v", msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.Text = helper.ClipMessage(msg.Text, messageLength, b.GetString("MessageClipped"))
|
msg.Text = helper.ClipMessage(msg.Text, messageLength)
|
||||||
msg.Text = b.replaceCodeFence(msg.Text)
|
msg.Text = b.replaceCodeFence(msg.Text)
|
||||||
|
|
||||||
// Make a action /me of the message
|
// Make a action /me of the message
|
||||||
@@ -300,7 +299,7 @@ func (b *Bslack) sendRTM(msg config.Message) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle prefix hint for unthreaded messages.
|
// Handle prefix hint for unthreaded messages.
|
||||||
if msg.ParentNotFound() {
|
if msg.ParentID == "msg-parent-not-found" {
|
||||||
msg.ParentID = ""
|
msg.ParentID = ""
|
||||||
msg.Text = fmt.Sprintf("[thread]: %s", msg.Text)
|
msg.Text = fmt.Sprintf("[thread]: %s", msg.Text)
|
||||||
}
|
}
|
||||||
@@ -460,7 +459,7 @@ func (b *Bslack) uploadFile(msg *config.Message, channelID string) {
|
|||||||
b.cache.Add("filename"+fi.Name, ts)
|
b.cache.Add("filename"+fi.Name, ts)
|
||||||
initialComment := fmt.Sprintf("File from %s", msg.Username)
|
initialComment := fmt.Sprintf("File from %s", msg.Username)
|
||||||
if fi.Comment != "" {
|
if fi.Comment != "" {
|
||||||
initialComment += fmt.Sprintf(" with comment: %s", fi.Comment)
|
initialComment += fmt.Sprintf("with comment: %s", fi.Comment)
|
||||||
}
|
}
|
||||||
res, err := b.sc.UploadFile(slack.FileUploadParameters{
|
res, err := b.sc.UploadFile(slack.FileUploadParameters{
|
||||||
Reader: bytes.NewReader(*fi.Data),
|
Reader: bytes.NewReader(*fi.Data),
|
||||||
|
|||||||
@@ -113,12 +113,6 @@ func (b *users) populateUser(userID string) {
|
|||||||
b.users[userID] = user
|
b.users[userID] = user
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *users) invalidateUser(userID string) {
|
|
||||||
b.usersMutex.Lock()
|
|
||||||
defer b.usersMutex.Unlock()
|
|
||||||
delete(b.users, userID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *users) populateUsers(wait bool) {
|
func (b *users) populateUsers(wait bool) {
|
||||||
b.refreshMutex.Lock()
|
b.refreshMutex.Lock()
|
||||||
if !wait && (time.Now().Before(b.earliestRefresh) || b.refreshInProgress) {
|
if !wait && (time.Now().Before(b.earliestRefresh) || b.refreshInProgress) {
|
||||||
@@ -289,9 +283,8 @@ func (b *channels) populateChannels(wait bool) {
|
|||||||
// We only retrieve public and private channels, not IMs
|
// We only retrieve public and private channels, not IMs
|
||||||
// and MPIMs as those do not have a channel name.
|
// and MPIMs as those do not have a channel name.
|
||||||
queryParams := &slack.GetConversationsParameters{
|
queryParams := &slack.GetConversationsParameters{
|
||||||
ExcludeArchived: true,
|
ExcludeArchived: "true",
|
||||||
Types: []string{"public_channel,private_channel"},
|
Types: []string{"public_channel,private_channel"},
|
||||||
Limit: 1000,
|
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
channels, nextCursor, err := b.sc.GetConversations(queryParams)
|
channels, nextCursor, err := b.sc.GetConversations(queryParams)
|
||||||
|
|||||||
@@ -1,36 +1,22 @@
|
|||||||
package btelegram
|
package btelegram
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"html"
|
"html"
|
||||||
"path/filepath"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf16"
|
"unicode/utf16"
|
||||||
|
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
"github.com/davecgh/go-spew/spew"
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
|
||||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *Btelegram) handleUpdate(rmsg *config.Message, message, posted, edited *tgbotapi.Message) *tgbotapi.Message {
|
func (b *Btelegram) handleUpdate(rmsg *config.Message, message, posted, edited *tgbotapi.Message) *tgbotapi.Message {
|
||||||
// handle channels
|
// handle channels
|
||||||
if posted != nil {
|
if posted != nil {
|
||||||
if posted.Text == "/chatId" {
|
message = posted
|
||||||
chatID := strconv.FormatInt(posted.Chat.ID, 10)
|
rmsg.Text = message.Text
|
||||||
|
|
||||||
_, err := b.Send(config.Message{
|
|
||||||
Channel: chatID,
|
|
||||||
Text: fmt.Sprintf("ID of this chat: %s", chatID),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Warnf("Unable to send chatID to %s", chatID)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
message = posted
|
|
||||||
rmsg.Text = message.Text
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// edited channel message
|
// edited channel message
|
||||||
@@ -57,11 +43,6 @@ func (b *Btelegram) handleForwarded(rmsg *config.Message, message *tgbotapi.Mess
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if message.ForwardFromChat != nil && message.ForwardFrom == nil {
|
|
||||||
rmsg.Text = "Forwarded from " + message.ForwardFromChat.Title + ": " + rmsg.Text
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if message.ForwardFrom == nil {
|
if message.ForwardFrom == nil {
|
||||||
rmsg.Text = "Forwarded from " + unknownUser + ": " + rmsg.Text
|
rmsg.Text = "Forwarded from " + unknownUser + ": " + rmsg.Text
|
||||||
return
|
return
|
||||||
@@ -113,7 +94,7 @@ func (b *Btelegram) handleQuoting(rmsg *config.Message, message *tgbotapi.Messag
|
|||||||
// handleUsername handles the correct setting of the username
|
// handleUsername handles the correct setting of the username
|
||||||
func (b *Btelegram) handleUsername(rmsg *config.Message, message *tgbotapi.Message) {
|
func (b *Btelegram) handleUsername(rmsg *config.Message, message *tgbotapi.Message) {
|
||||||
if message.From != nil {
|
if message.From != nil {
|
||||||
rmsg.UserID = strconv.FormatInt(message.From.ID, 10)
|
rmsg.UserID = strconv.Itoa(message.From.ID)
|
||||||
if b.GetBool("UseFirstName") {
|
if b.GetBool("UseFirstName") {
|
||||||
rmsg.Username = message.From.FirstName
|
rmsg.Username = message.From.FirstName
|
||||||
}
|
}
|
||||||
@@ -129,25 +110,6 @@ func (b *Btelegram) handleUsername(rmsg *config.Message, message *tgbotapi.Messa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if message.SenderChat != nil { //nolint:nestif
|
|
||||||
rmsg.UserID = strconv.FormatInt(message.SenderChat.ID, 10)
|
|
||||||
if b.GetBool("UseFirstName") {
|
|
||||||
rmsg.Username = message.SenderChat.FirstName
|
|
||||||
}
|
|
||||||
|
|
||||||
if rmsg.Username == "" || rmsg.Username == "Channel_Bot" {
|
|
||||||
rmsg.Username = message.SenderChat.UserName
|
|
||||||
|
|
||||||
if rmsg.Username == "" || rmsg.Username == "Channel_Bot" {
|
|
||||||
rmsg.Username = message.SenderChat.FirstName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// only download avatars if we have a place to upload them (configured mediaserver)
|
|
||||||
if b.General.MediaServerUpload != "" || (b.General.MediaServerDownload != "" && b.General.MediaDownloadPath != "") {
|
|
||||||
b.handleDownloadAvatar(message.SenderChat.ID, rmsg.Channel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we really didn't find a username, set it to unknown
|
// if we really didn't find a username, set it to unknown
|
||||||
if rmsg.Username == "" {
|
if rmsg.Username == "" {
|
||||||
rmsg.Username = unknownUser
|
rmsg.Username = unknownUser
|
||||||
@@ -164,10 +126,6 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.GetInt("debuglevel") == 1 {
|
|
||||||
spew.Dump(update.Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
var message *tgbotapi.Message
|
var message *tgbotapi.Message
|
||||||
|
|
||||||
rmsg := config.Message{Account: b.Account, Extra: make(map[string][]interface{})}
|
rmsg := config.Message{Account: b.Account, Extra: make(map[string][]interface{})}
|
||||||
@@ -187,9 +145,6 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
|
|||||||
rmsg.ID = strconv.Itoa(message.MessageID)
|
rmsg.ID = strconv.Itoa(message.MessageID)
|
||||||
rmsg.Channel = strconv.FormatInt(message.Chat.ID, 10)
|
rmsg.Channel = strconv.FormatInt(message.Chat.ID, 10)
|
||||||
|
|
||||||
// handle entities (adding URLs)
|
|
||||||
b.handleEntities(&rmsg, message)
|
|
||||||
|
|
||||||
// handle username
|
// handle username
|
||||||
b.handleUsername(&rmsg, message)
|
b.handleUsername(&rmsg, message)
|
||||||
|
|
||||||
@@ -205,12 +160,14 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
|
|||||||
// quote the previous message
|
// quote the previous message
|
||||||
b.handleQuoting(&rmsg, message)
|
b.handleQuoting(&rmsg, message)
|
||||||
|
|
||||||
|
// handle entities (adding URLs)
|
||||||
|
b.handleEntities(&rmsg, message)
|
||||||
|
|
||||||
if rmsg.Text != "" || len(rmsg.Extra) > 0 {
|
if rmsg.Text != "" || len(rmsg.Extra) > 0 {
|
||||||
// Comment the next line out due to avoid removing empty lines in Telegram
|
rmsg.Text = helper.RemoveEmptyNewLines(rmsg.Text)
|
||||||
// rmsg.Text = helper.RemoveEmptyNewLines(rmsg.Text)
|
|
||||||
// channels don't have (always?) user information. see #410
|
// channels don't have (always?) user information. see #410
|
||||||
if message.From != nil {
|
if message.From != nil {
|
||||||
rmsg.Avatar = helper.GetAvatar(b.avatarMap, strconv.FormatInt(message.From.ID, 10), b.General)
|
rmsg.Avatar = helper.GetAvatar(b.avatarMap, strconv.Itoa(message.From.ID), b.General)
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account)
|
b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account)
|
||||||
@@ -223,52 +180,58 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
|
|||||||
// handleDownloadAvatar downloads the avatar of userid from channel
|
// handleDownloadAvatar downloads the avatar of userid from channel
|
||||||
// sends a EVENT_AVATAR_DOWNLOAD message to the gateway if successful.
|
// sends a EVENT_AVATAR_DOWNLOAD message to the gateway if successful.
|
||||||
// logs an error message if it fails
|
// logs an error message if it fails
|
||||||
func (b *Btelegram) handleDownloadAvatar(userid int64, channel string) {
|
func (b *Btelegram) handleDownloadAvatar(userid int, channel string) {
|
||||||
rmsg := config.Message{
|
rmsg := config.Message{Username: "system",
|
||||||
Username: "system",
|
Text: "avatar",
|
||||||
Text: "avatar",
|
Channel: channel,
|
||||||
Channel: channel,
|
Account: b.Account,
|
||||||
Account: b.Account,
|
UserID: strconv.Itoa(userid),
|
||||||
UserID: strconv.FormatInt(userid, 10),
|
Event: config.EventAvatarDownload,
|
||||||
Event: config.EventAvatarDownload,
|
Extra: make(map[string][]interface{})}
|
||||||
Extra: make(map[string][]interface{}),
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := b.avatarMap[strconv.FormatInt(userid, 10)]; ok {
|
if _, ok := b.avatarMap[strconv.Itoa(userid)]; !ok {
|
||||||
return
|
photos, err := b.c.GetUserProfilePhotos(tgbotapi.UserProfilePhotosConfig{UserID: userid, Limit: 1})
|
||||||
}
|
|
||||||
|
|
||||||
photos, err := b.c.GetUserProfilePhotos(tgbotapi.UserProfilePhotosConfig{UserID: userid, Limit: 1})
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("Userprofile download failed for %#v %s", userid, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(photos.Photos) > 0 {
|
|
||||||
photo := photos.Photos[0][0]
|
|
||||||
url := b.getFileDirectURL(photo.FileID)
|
|
||||||
name := strconv.FormatInt(userid, 10) + ".png"
|
|
||||||
b.Log.Debugf("trying to download %#v fileid %#v with size %#v", name, photo.FileID, photo.FileSize)
|
|
||||||
|
|
||||||
err := helper.HandleDownloadSize(b.Log, &rmsg, name, int64(photo.FileSize), b.General)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Error(err)
|
b.Log.Errorf("Userprofile download failed for %#v %s", userid, err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
data, err := helper.DownloadFile(url)
|
|
||||||
if err != nil {
|
if len(photos.Photos) > 0 {
|
||||||
b.Log.Errorf("download %s failed %#v", url, err)
|
photo := photos.Photos[0][0]
|
||||||
return
|
url := b.getFileDirectURL(photo.FileID)
|
||||||
|
name := strconv.Itoa(userid) + ".png"
|
||||||
|
b.Log.Debugf("trying to download %#v fileid %#v with size %#v", name, photo.FileID, photo.FileSize)
|
||||||
|
|
||||||
|
err := helper.HandleDownloadSize(b.Log, &rmsg, name, int64(photo.FileSize), b.General)
|
||||||
|
if err != nil {
|
||||||
|
b.Log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, err := helper.DownloadFile(url)
|
||||||
|
if err != nil {
|
||||||
|
b.Log.Errorf("download %s failed %#v", url, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.HandleDownloadData(b.Log, &rmsg, name, rmsg.Text, "", data, b.General)
|
||||||
|
b.Remote <- rmsg
|
||||||
}
|
}
|
||||||
helper.HandleDownloadData(b.Log, &rmsg, name, rmsg.Text, "", data, b.General)
|
|
||||||
b.Remote <- rmsg
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Btelegram) maybeConvertTgs(name *string, data *[]byte) {
|
func (b *Btelegram) maybeConvertTgs(name *string, data *[]byte) {
|
||||||
format := b.GetString("MediaConvertTgs")
|
var format string
|
||||||
if helper.SupportsFormat(format) {
|
switch b.GetString("MediaConvertTgs") {
|
||||||
b.Log.Debugf("Format supported by %s, converting %v", helper.LottieBackend(), name)
|
case FormatWebp:
|
||||||
} else {
|
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
|
// Otherwise, no conversion was requested. Trying to run the usual webp
|
||||||
// converter would fail, because '.tgs.webp' is actually a gzipped JSON
|
// converter would fail, because '.tgs.webp' is actually a gzipped JSON
|
||||||
// file, and has nothing to do with WebP.
|
// file, and has nothing to do with WebP.
|
||||||
@@ -317,7 +280,7 @@ func (b *Btelegram) handleDownload(rmsg *config.Message, message *tgbotapi.Messa
|
|||||||
name = message.Document.FileName
|
name = message.Document.FileName
|
||||||
text = " " + message.Document.FileName + " : " + url
|
text = " " + message.Document.FileName + " : " + url
|
||||||
case message.Photo != nil:
|
case message.Photo != nil:
|
||||||
photos := message.Photo
|
photos := *message.Photo
|
||||||
size = photos[len(photos)-1].FileSize
|
size = photos[len(photos)-1].FileSize
|
||||||
text, name, url = b.getDownloadInfo(photos[len(photos)-1].FileID, "", true)
|
text, name, url = b.getDownloadInfo(photos[len(photos)-1].FileID, "", true)
|
||||||
}
|
}
|
||||||
@@ -348,11 +311,6 @@ func (b *Btelegram) handleDownload(rmsg *config.Message, message *tgbotapi.Messa
|
|||||||
b.maybeConvertWebp(&name, data)
|
b.maybeConvertWebp(&name, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// rename .oga to .ogg https://github.com/42wim/matterbridge/issues/906#issuecomment-741793512
|
|
||||||
if strings.HasSuffix(name, ".oga") && message.Audio != nil {
|
|
||||||
name = strings.Replace(name, ".oga", ".ogg", 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
helper.HandleDownloadData(b.Log, rmsg, name, message.Caption, "", data, b.General)
|
helper.HandleDownloadData(b.Log, rmsg, name, message.Caption, "", data, b.General)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -376,15 +334,11 @@ func (b *Btelegram) handleDelete(msg *config.Message, chatid int64) (string, err
|
|||||||
if msg.ID == "" {
|
if msg.ID == "" {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
msgid, err := strconv.Atoi(msg.ID)
|
msgid, err := strconv.Atoi(msg.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
_, err = b.c.DeleteMessage(tgbotapi.DeleteMessageConfig{ChatID: chatid, MessageID: msgid})
|
||||||
cfg := tgbotapi.NewDeleteMessage(chatid, msgid)
|
|
||||||
_, err = b.c.Send(cfg)
|
|
||||||
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,32 +384,21 @@ func (b *Btelegram) handleUploadFile(msg *config.Message, chatid int64) string {
|
|||||||
Name: fi.Name,
|
Name: fi.Name,
|
||||||
Bytes: *fi.Data,
|
Bytes: *fi.Data,
|
||||||
}
|
}
|
||||||
switch filepath.Ext(fi.Name) {
|
re := regexp.MustCompile(".(jpg|png)$")
|
||||||
case ".jpg", ".jpe", ".png":
|
if re.MatchString(fi.Name) {
|
||||||
pc := tgbotapi.NewPhoto(chatid, file)
|
c = tgbotapi.NewPhotoUpload(chatid, file)
|
||||||
pc.Caption, pc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
|
} else {
|
||||||
c = pc
|
c = tgbotapi.NewDocumentUpload(chatid, file)
|
||||||
case ".mp4", ".m4v":
|
|
||||||
vc := tgbotapi.NewVideo(chatid, file)
|
|
||||||
vc.Caption, vc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
|
|
||||||
c = vc
|
|
||||||
case ".mp3", ".oga":
|
|
||||||
ac := tgbotapi.NewAudio(chatid, file)
|
|
||||||
ac.Caption, ac.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
|
|
||||||
c = ac
|
|
||||||
case ".ogg":
|
|
||||||
voc := tgbotapi.NewVoice(chatid, file)
|
|
||||||
voc.Caption, voc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
|
|
||||||
c = voc
|
|
||||||
default:
|
|
||||||
dc := tgbotapi.NewDocument(chatid, file)
|
|
||||||
dc.Caption, dc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
|
|
||||||
c = dc
|
|
||||||
}
|
}
|
||||||
_, err := b.c.Send(c)
|
_, err := b.c.Send(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Errorf("file upload failed: %#v", err)
|
b.Log.Errorf("file upload failed: %#v", err)
|
||||||
}
|
}
|
||||||
|
if fi.Comment != "" {
|
||||||
|
if _, err := b.sendMessage(chatid, msg.Username, fi.Comment); err != nil {
|
||||||
|
b.Log.Errorf("posting file comment %s failed: %s", fi.Comment, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -465,7 +408,7 @@ func (b *Btelegram) handleQuote(message, quoteNick, quoteMessage string) string
|
|||||||
if format == "" {
|
if format == "" {
|
||||||
format = "{MESSAGE} (re @{QUOTENICK}: {QUOTEMESSAGE})"
|
format = "{MESSAGE} (re @{QUOTENICK}: {QUOTEMESSAGE})"
|
||||||
}
|
}
|
||||||
quoteMessagelength := len([]rune(quoteMessage))
|
quoteMessagelength := len(quoteMessage)
|
||||||
if b.GetInt("QuoteLengthLimit") != 0 && quoteMessagelength >= b.GetInt("QuoteLengthLimit") {
|
if b.GetInt("QuoteLengthLimit") != 0 && quoteMessagelength >= b.GetInt("QuoteLengthLimit") {
|
||||||
runes := []rune(quoteMessage)
|
runes := []rune(quoteMessage)
|
||||||
quoteMessage = string(runes[0:b.GetInt("QuoteLengthLimit")])
|
quoteMessage = string(runes[0:b.GetInt("QuoteLengthLimit")])
|
||||||
@@ -484,56 +427,21 @@ func (b *Btelegram) handleEntities(rmsg *config.Message, message *tgbotapi.Messa
|
|||||||
if message.Entities == nil {
|
if message.Entities == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
indexMovedBy := 0
|
|
||||||
|
|
||||||
// for now only do URL replacements
|
// for now only do URL replacements
|
||||||
for _, e := range message.Entities {
|
for _, e := range *message.Entities {
|
||||||
|
|
||||||
asRunes := utf16.Encode([]rune(rmsg.Text))
|
|
||||||
|
|
||||||
if e.Type == "text_link" {
|
if e.Type == "text_link" {
|
||||||
offset := e.Offset + indexMovedBy
|
|
||||||
url, err := e.ParseURL()
|
url, err := e.ParseURL()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Errorf("entity text_link url parse failed: %s", err)
|
b.Log.Errorf("entity text_link url parse failed: %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
utfEncodedString := utf16.Encode([]rune(rmsg.Text))
|
utfEncodedString := utf16.Encode([]rune(rmsg.Text))
|
||||||
if offset+e.Length > len(utfEncodedString) {
|
if e.Offset+e.Length > len(utfEncodedString) {
|
||||||
b.Log.Errorf("entity length is too long %d > %d", offset+e.Length, len(utfEncodedString))
|
b.Log.Errorf("entity length is too long %d > %d", e.Offset+e.Length, len(utfEncodedString))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
rmsg.Text = string(utf16.Decode(asRunes[:offset+e.Length])) + " (" + url.String() + ")" + string(utf16.Decode(asRunes[offset+e.Length:]))
|
link := utf16.Decode(utfEncodedString[e.Offset : e.Offset+e.Length])
|
||||||
indexMovedBy += len(url.String()) + 3
|
rmsg.Text = strings.Replace(rmsg.Text, string(link), url.String(), 1)
|
||||||
}
|
|
||||||
|
|
||||||
if e.Type == "code" {
|
|
||||||
offset := e.Offset + indexMovedBy
|
|
||||||
rmsg.Text = string(utf16.Decode(asRunes[:offset])) + "`" + string(utf16.Decode(asRunes[offset:offset+e.Length])) + "`" + string(utf16.Decode(asRunes[offset+e.Length:]))
|
|
||||||
indexMovedBy += 2
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.Type == "pre" {
|
|
||||||
offset := e.Offset + indexMovedBy
|
|
||||||
rmsg.Text = string(utf16.Decode(asRunes[:offset])) + "```\n" + string(utf16.Decode(asRunes[offset:offset+e.Length])) + "```\n" + string(utf16.Decode(asRunes[offset+e.Length:]))
|
|
||||||
indexMovedBy += 8
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.Type == "bold" {
|
|
||||||
offset := e.Offset + indexMovedBy
|
|
||||||
rmsg.Text = string(utf16.Decode(asRunes[:offset])) + "*" + string(utf16.Decode(asRunes[offset:offset+e.Length])) + "*" + string(utf16.Decode(asRunes[offset+e.Length:]))
|
|
||||||
indexMovedBy += 2
|
|
||||||
}
|
|
||||||
if e.Type == "italic" {
|
|
||||||
offset := e.Offset + indexMovedBy
|
|
||||||
rmsg.Text = string(utf16.Decode(asRunes[:offset])) + "_" + string(utf16.Decode(asRunes[offset:offset+e.Length])) + "_" + string(utf16.Decode(asRunes[offset+e.Length:]))
|
|
||||||
indexMovedBy += 2
|
|
||||||
}
|
|
||||||
if e.Type == "strike" {
|
|
||||||
offset := e.Offset + indexMovedBy
|
|
||||||
rmsg.Text = string(utf16.Decode(asRunes[:offset])) + "~" + string(utf16.Decode(asRunes[offset:offset+e.Length])) + "~" + string(utf16.Decode(asRunes[offset+e.Length:]))
|
|
||||||
indexMovedBy += 2
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/42wim/matterbridge/bridge"
|
"github.com/42wim/matterbridge/bridge"
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -17,6 +17,8 @@ const (
|
|||||||
HTMLFormat = "HTML"
|
HTMLFormat = "HTML"
|
||||||
HTMLNick = "htmlnick"
|
HTMLNick = "htmlnick"
|
||||||
MarkdownV2 = "MarkdownV2"
|
MarkdownV2 = "MarkdownV2"
|
||||||
|
FormatPng = "png"
|
||||||
|
FormatWebp = "webp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Btelegram struct {
|
type Btelegram struct {
|
||||||
@@ -30,10 +32,10 @@ func New(cfg *bridge.Config) bridge.Bridger {
|
|||||||
if tgsConvertFormat != "" {
|
if tgsConvertFormat != "" {
|
||||||
err := helper.CanConvertTgsToX()
|
err := helper.CanConvertTgsToX()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Telegram bridge configured to convert .tgs files to '%s', but %s does not appear to work:\n%#v", tgsConvertFormat, helper.LottieBackend(), err)
|
log.Fatalf("Telegram bridge configured to convert .tgs files to '%s', but lottie does not appear to work:\n%#v", tgsConvertFormat, err)
|
||||||
}
|
}
|
||||||
if !helper.SupportsFormat(tgsConvertFormat) {
|
if tgsConvertFormat != FormatPng && tgsConvertFormat != FormatWebp {
|
||||||
log.Fatalf("Telegram bridge configured to convert .tgs files to '%s', but %s doesn't support it.", tgsConvertFormat, helper.LottieBackend())
|
log.Fatalf("Telegram bridge configured to convert .tgs files to '%s', but only '%s' and '%s' are supported.", FormatPng, FormatWebp, tgsConvertFormat)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &Btelegram{Config: cfg, avatarMap: make(map[string]string)}
|
return &Btelegram{Config: cfg, avatarMap: make(map[string]string)}
|
||||||
@@ -49,7 +51,11 @@ func (b *Btelegram) Connect() error {
|
|||||||
}
|
}
|
||||||
u := tgbotapi.NewUpdate(0)
|
u := tgbotapi.NewUpdate(0)
|
||||||
u.Timeout = 60
|
u.Timeout = 60
|
||||||
updates := b.c.GetUpdatesChan(u)
|
updates, err := b.c.GetUpdatesChan(u)
|
||||||
|
if err != nil {
|
||||||
|
b.Log.Debugf("%#v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
b.Log.Info("Connection succeeded")
|
b.Log.Info("Connection succeeded")
|
||||||
go b.handleRecv(updates)
|
go b.handleRecv(updates)
|
||||||
return nil
|
return nil
|
||||||
@@ -63,28 +69,6 @@ func (b *Btelegram) JoinChannel(channel config.ChannelInfo) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TGGetParseMode(b *Btelegram, username string, text string) (textout string, parsemode string) {
|
|
||||||
textout = username + text
|
|
||||||
if b.GetString("MessageFormat") == HTMLFormat {
|
|
||||||
b.Log.Debug("Using mode HTML")
|
|
||||||
parsemode = tgbotapi.ModeHTML
|
|
||||||
}
|
|
||||||
if b.GetString("MessageFormat") == "Markdown" {
|
|
||||||
b.Log.Debug("Using mode markdown")
|
|
||||||
parsemode = tgbotapi.ModeMarkdown
|
|
||||||
}
|
|
||||||
if b.GetString("MessageFormat") == MarkdownV2 {
|
|
||||||
b.Log.Debug("Using mode MarkdownV2")
|
|
||||||
parsemode = MarkdownV2
|
|
||||||
}
|
|
||||||
if strings.ToLower(b.GetString("MessageFormat")) == HTMLNick {
|
|
||||||
b.Log.Debug("Using mode HTML - nick only")
|
|
||||||
textout = username + html.EscapeString(text)
|
|
||||||
parsemode = tgbotapi.ModeHTML
|
|
||||||
}
|
|
||||||
return textout, parsemode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Btelegram) Send(msg config.Message) (string, error) {
|
func (b *Btelegram) Send(msg config.Message) (string, error) {
|
||||||
b.Log.Debugf("=> Receiving %#v", msg)
|
b.Log.Debugf("=> Receiving %#v", msg)
|
||||||
|
|
||||||
@@ -147,7 +131,24 @@ func (b *Btelegram) getFileDirectURL(id string) string {
|
|||||||
|
|
||||||
func (b *Btelegram) sendMessage(chatid int64, username, text string) (string, error) {
|
func (b *Btelegram) sendMessage(chatid int64, username, text string) (string, error) {
|
||||||
m := tgbotapi.NewMessage(chatid, "")
|
m := tgbotapi.NewMessage(chatid, "")
|
||||||
m.Text, m.ParseMode = TGGetParseMode(b, username, text)
|
m.Text = username + text
|
||||||
|
if b.GetString("MessageFormat") == HTMLFormat {
|
||||||
|
b.Log.Debug("Using mode HTML")
|
||||||
|
m.ParseMode = tgbotapi.ModeHTML
|
||||||
|
}
|
||||||
|
if b.GetString("MessageFormat") == "Markdown" {
|
||||||
|
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")
|
m.DisableWebPagePreview = b.GetBool("DisableWebPagePreview")
|
||||||
|
|
||||||
|
|||||||
332
bridge/vk/vk.go
@@ -1,332 +0,0 @@
|
|||||||
package bvk
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/42wim/matterbridge/bridge"
|
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
|
||||||
|
|
||||||
"github.com/SevereCloud/vksdk/v2/api"
|
|
||||||
"github.com/SevereCloud/vksdk/v2/events"
|
|
||||||
longpoll "github.com/SevereCloud/vksdk/v2/longpoll-bot"
|
|
||||||
"github.com/SevereCloud/vksdk/v2/object"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
audioMessage = "audio_message"
|
|
||||||
document = "doc"
|
|
||||||
photo = "photo"
|
|
||||||
video = "video"
|
|
||||||
graffiti = "graffiti"
|
|
||||||
sticker = "sticker"
|
|
||||||
wall = "wall"
|
|
||||||
)
|
|
||||||
|
|
||||||
type user struct {
|
|
||||||
lastname, firstname, avatar string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Bvk struct {
|
|
||||||
c *api.VK
|
|
||||||
lp *longpoll.LongPoll
|
|
||||||
usernamesMap map[int]user // cache of user names and avatar URLs
|
|
||||||
*bridge.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(cfg *bridge.Config) bridge.Bridger {
|
|
||||||
return &Bvk{usernamesMap: make(map[int]user), Config: cfg}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bvk) Connect() error {
|
|
||||||
b.Log.Info("Connecting")
|
|
||||||
b.c = api.NewVK(b.GetString("Token"))
|
|
||||||
|
|
||||||
var err error
|
|
||||||
b.lp, err = longpoll.NewLongPollCommunity(b.c)
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Debugf("%#v", err)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
b.lp.MessageNew(func(ctx context.Context, obj events.MessageNewObject) {
|
|
||||||
b.handleMessage(obj.Message, false)
|
|
||||||
})
|
|
||||||
|
|
||||||
b.Log.Info("Connection succeeded")
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
err := b.lp.Run()
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Fatal("Enable longpoll in group management")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bvk) Disconnect() error {
|
|
||||||
b.lp.Shutdown()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bvk) JoinChannel(channel config.ChannelInfo) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bvk) Send(msg config.Message) (string, error) {
|
|
||||||
b.Log.Debugf("=> Receiving %#v", msg)
|
|
||||||
|
|
||||||
peerID, err := strconv.Atoi(msg.Channel)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
params := api.Params{}
|
|
||||||
|
|
||||||
text := msg.Username + msg.Text
|
|
||||||
|
|
||||||
if msg.Extra != nil {
|
|
||||||
if len(msg.Extra["file"]) > 0 {
|
|
||||||
// generate attachments string
|
|
||||||
attachment, urls := b.uploadFiles(msg.Extra, peerID)
|
|
||||||
params["attachment"] = attachment
|
|
||||||
text += urls
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
params["message"] = text
|
|
||||||
|
|
||||||
if msg.ID == "" {
|
|
||||||
// New message
|
|
||||||
params["random_id"] = time.Now().Unix()
|
|
||||||
params["peer_ids"] = msg.Channel
|
|
||||||
|
|
||||||
res, e := b.c.MessagesSendPeerIDs(params)
|
|
||||||
if e != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return strconv.Itoa(res[0].ConversationMessageID), nil
|
|
||||||
}
|
|
||||||
// Edit message
|
|
||||||
messageID, err := strconv.ParseInt(msg.ID, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
params["peer_id"] = peerID
|
|
||||||
params["conversation_message_id"] = messageID
|
|
||||||
|
|
||||||
_, err = b.c.MessagesEdit(params)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return msg.ID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bvk) getUser(id int) user {
|
|
||||||
u, found := b.usernamesMap[id]
|
|
||||||
if !found {
|
|
||||||
b.Log.Debug("Fetching username for ", id)
|
|
||||||
|
|
||||||
if id >= 0 {
|
|
||||||
result, _ := b.c.UsersGet(api.Params{
|
|
||||||
"user_ids": id,
|
|
||||||
"fields": "photo_200",
|
|
||||||
})
|
|
||||||
|
|
||||||
resUser := result[0]
|
|
||||||
u = user{lastname: resUser.LastName, firstname: resUser.FirstName, avatar: resUser.Photo200}
|
|
||||||
b.usernamesMap[id] = u
|
|
||||||
} else {
|
|
||||||
result, _ := b.c.GroupsGetByID(api.Params{
|
|
||||||
"group_id": id * -1,
|
|
||||||
})
|
|
||||||
|
|
||||||
resGroup := result[0]
|
|
||||||
u = user{lastname: resGroup.Name, avatar: resGroup.Photo200}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bvk) handleMessage(msg object.MessagesMessage, isFwd bool) {
|
|
||||||
b.Log.Debug("ChatID: ", msg.PeerID)
|
|
||||||
// fetch user info
|
|
||||||
u := b.getUser(msg.FromID)
|
|
||||||
|
|
||||||
rmsg := config.Message{
|
|
||||||
Text: msg.Text,
|
|
||||||
Username: u.firstname + " " + u.lastname,
|
|
||||||
Avatar: u.avatar,
|
|
||||||
Channel: strconv.Itoa(msg.PeerID),
|
|
||||||
Account: b.Account,
|
|
||||||
UserID: strconv.Itoa(msg.FromID),
|
|
||||||
ID: strconv.Itoa(msg.ConversationMessageID),
|
|
||||||
Extra: make(map[string][]interface{}),
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg.ReplyMessage != nil {
|
|
||||||
ur := b.getUser(msg.ReplyMessage.FromID)
|
|
||||||
rmsg.Text = "Re: " + ur.firstname + " " + ur.lastname + "\n" + rmsg.Text
|
|
||||||
}
|
|
||||||
|
|
||||||
if isFwd {
|
|
||||||
rmsg.Username = "Fwd: " + rmsg.Username
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(msg.Attachments) > 0 {
|
|
||||||
urls, text := b.getFiles(msg.Attachments)
|
|
||||||
|
|
||||||
if text != "" {
|
|
||||||
rmsg.Text += "\n" + text
|
|
||||||
}
|
|
||||||
|
|
||||||
// download
|
|
||||||
b.downloadFiles(&rmsg, urls)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(msg.FwdMessages) > 0 {
|
|
||||||
rmsg.Text += strconv.Itoa(len(msg.FwdMessages)) + " forwarded messages"
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Remote <- rmsg
|
|
||||||
|
|
||||||
if len(msg.FwdMessages) > 0 {
|
|
||||||
// recursive processing of forwarded messages
|
|
||||||
for _, m := range msg.FwdMessages {
|
|
||||||
m.PeerID = msg.PeerID
|
|
||||||
b.handleMessage(m, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bvk) uploadFiles(extra map[string][]interface{}, peerID int) (string, string) {
|
|
||||||
var attachments []string
|
|
||||||
text := ""
|
|
||||||
|
|
||||||
for _, f := range extra["file"] {
|
|
||||||
fi := f.(config.FileInfo)
|
|
||||||
|
|
||||||
if fi.Comment != "" {
|
|
||||||
text += fi.Comment + "\n"
|
|
||||||
}
|
|
||||||
a, err := b.uploadFile(fi, peerID)
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Error("File upload error ", fi.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
attachments = append(attachments, a)
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(attachments, ","), text
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bvk) uploadFile(file config.FileInfo, peerID int) (string, error) {
|
|
||||||
r := bytes.NewReader(*file.Data)
|
|
||||||
|
|
||||||
photoRE := regexp.MustCompile(".(jpg|jpe|png)$")
|
|
||||||
if photoRE.MatchString(file.Name) {
|
|
||||||
p, err := b.c.UploadMessagesPhoto(peerID, r)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return photo + strconv.Itoa(p[0].OwnerID) + "_" + strconv.Itoa(p[0].ID), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var doctype string
|
|
||||||
if strings.Contains(file.Name, ".ogg") {
|
|
||||||
doctype = audioMessage
|
|
||||||
} else {
|
|
||||||
doctype = document
|
|
||||||
}
|
|
||||||
|
|
||||||
doc, err := b.c.UploadMessagesDoc(peerID, doctype, file.Name, "", r)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch doc.Type {
|
|
||||||
case audioMessage:
|
|
||||||
return document + strconv.Itoa(doc.AudioMessage.OwnerID) + "_" + strconv.Itoa(doc.AudioMessage.ID), nil
|
|
||||||
case document:
|
|
||||||
return document + strconv.Itoa(doc.Doc.OwnerID) + "_" + strconv.Itoa(doc.Doc.ID), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bvk) getFiles(attachments []object.MessagesMessageAttachment) ([]string, string) {
|
|
||||||
var urls []string
|
|
||||||
var text []string
|
|
||||||
|
|
||||||
for _, a := range attachments {
|
|
||||||
switch a.Type {
|
|
||||||
case photo:
|
|
||||||
var resolution float64 = 0
|
|
||||||
url := a.Photo.Sizes[0].URL
|
|
||||||
for _, size := range a.Photo.Sizes {
|
|
||||||
r := size.Height * size.Width
|
|
||||||
if resolution < r {
|
|
||||||
resolution = r
|
|
||||||
url = size.URL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
urls = append(urls, url)
|
|
||||||
|
|
||||||
case document:
|
|
||||||
urls = append(urls, a.Doc.URL)
|
|
||||||
|
|
||||||
case graffiti:
|
|
||||||
urls = append(urls, a.Graffiti.URL)
|
|
||||||
|
|
||||||
case audioMessage:
|
|
||||||
urls = append(urls, a.AudioMessage.DocsDocPreviewAudioMessage.LinkOgg)
|
|
||||||
|
|
||||||
case sticker:
|
|
||||||
var resolution float64 = 0
|
|
||||||
url := a.Sticker.Images[0].URL
|
|
||||||
for _, size := range a.Sticker.Images {
|
|
||||||
r := size.Height * size.Width
|
|
||||||
if resolution < r {
|
|
||||||
resolution = r
|
|
||||||
url = size.URL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
urls = append(urls, url+".png")
|
|
||||||
case video:
|
|
||||||
text = append(text, "https://vk.com/video"+strconv.Itoa(a.Video.OwnerID)+"_"+strconv.Itoa(a.Video.ID))
|
|
||||||
|
|
||||||
case wall:
|
|
||||||
text = append(text, "https://vk.com/wall"+strconv.Itoa(a.Wall.FromID)+"_"+strconv.Itoa(a.Wall.ID))
|
|
||||||
|
|
||||||
default:
|
|
||||||
text = append(text, "This attachment is not supported ("+a.Type+")")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return urls, strings.Join(text, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bvk) downloadFiles(rmsg *config.Message, urls []string) {
|
|
||||||
for _, url := range urls {
|
|
||||||
data, err := helper.DownloadFile(url)
|
|
||||||
if err == nil {
|
|
||||||
urlPart := strings.Split(url, "/")
|
|
||||||
name := strings.Split(urlPart[len(urlPart)-1], "?")[0]
|
|
||||||
helper.HandleDownloadData(b.Log, rmsg, name, "", url, data, b.General)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -24,8 +24,7 @@ Check:
|
|||||||
func (b *Bwhatsapp) HandleError(err error) {
|
func (b *Bwhatsapp) HandleError(err error) {
|
||||||
// ignore received invalid data errors. https://github.com/42wim/matterbridge/issues/843
|
// ignore received invalid data errors. https://github.com/42wim/matterbridge/issues/843
|
||||||
// ignore tag 174 errors. https://github.com/42wim/matterbridge/issues/1094
|
// ignore tag 174 errors. https://github.com/42wim/matterbridge/issues/1094
|
||||||
if strings.Contains(err.Error(), "error processing data: received invalid data") ||
|
if strings.Contains(err.Error(), "error processing data: received invalid data") || strings.Contains(err.Error(), "invalid string with tag 174") {
|
||||||
strings.Contains(err.Error(), "invalid string with tag 174") {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,22 +47,16 @@ func (b *Bwhatsapp) reconnect(err error) {
|
|||||||
Max: 5 * time.Minute,
|
Max: 5 * time.Minute,
|
||||||
Jitter: true,
|
Jitter: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
d := bf.Duration()
|
d := bf.Duration()
|
||||||
|
|
||||||
b.Log.Errorf("Connection failed, underlying error: %v", err)
|
b.Log.Errorf("Connection failed, underlying error: %v", err)
|
||||||
b.Log.Infof("Waiting %s...", d)
|
b.Log.Infof("Waiting %s...", d)
|
||||||
|
|
||||||
time.Sleep(d)
|
time.Sleep(d)
|
||||||
|
|
||||||
b.Log.Info("Reconnecting...")
|
b.Log.Info("Reconnecting...")
|
||||||
|
|
||||||
err := b.conn.Restore()
|
err := b.conn.Restore()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
bf.Reset()
|
bf.Reset()
|
||||||
b.startedAt = uint64(time.Now().Unix())
|
b.startedAt = uint64(time.Now().Unix())
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,7 +64,7 @@ func (b *Bwhatsapp) reconnect(err error) {
|
|||||||
|
|
||||||
// HandleTextMessage sent from WhatsApp, relay it to the brige
|
// HandleTextMessage sent from WhatsApp, relay it to the brige
|
||||||
func (b *Bwhatsapp) HandleTextMessage(message whatsapp.TextMessage) {
|
func (b *Bwhatsapp) HandleTextMessage(message whatsapp.TextMessage) {
|
||||||
if message.Info.FromMe {
|
if message.Info.FromMe { // || !strings.Contains(strings.ToLower(message.Text), "@echo") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// whatsapp sends last messages to show context , cut them
|
// whatsapp sends last messages to show context , cut them
|
||||||
@@ -79,10 +72,12 @@ func (b *Bwhatsapp) HandleTextMessage(message whatsapp.TextMessage) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
messageTime := time.Unix(int64(message.Info.Timestamp), 0) // TODO check how behaves between timezones
|
||||||
groupJID := message.Info.RemoteJid
|
groupJID := message.Info.RemoteJid
|
||||||
senderJID := message.Info.SenderJid
|
|
||||||
|
|
||||||
|
senderJID := message.Info.SenderJid
|
||||||
if len(senderJID) == 0 {
|
if len(senderJID) == 0 {
|
||||||
|
// TODO workaround till https://github.com/Rhymen/go-whatsapp/issues/86 resolved
|
||||||
if message.Info.Source != nil && message.Info.Source.Participant != nil {
|
if message.Info.Source != nil && message.Info.Source.Participant != nil {
|
||||||
senderJID = *message.Info.Source.Participant
|
senderJID = *message.Info.Source.Participant
|
||||||
}
|
}
|
||||||
@@ -106,275 +101,110 @@ func (b *Bwhatsapp) HandleTextMessage(message whatsapp.TextMessage) {
|
|||||||
if mention == "" {
|
if mention == "" {
|
||||||
mention = "someone"
|
mention = "someone"
|
||||||
}
|
}
|
||||||
|
|
||||||
message.Text = strings.Replace(message.Text, "@"+numberAndSuffix[0], "@"+mention, 1)
|
message.Text = strings.Replace(message.Text, "@"+numberAndSuffix[0], "@"+mention, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account)
|
||||||
rmsg := config.Message{
|
rmsg := config.Message{
|
||||||
UserID: senderJID,
|
UserID: senderJID,
|
||||||
Username: senderName,
|
Username: senderName,
|
||||||
Text: message.Text,
|
Text: message.Text,
|
||||||
Channel: groupJID,
|
Timestamp: messageTime,
|
||||||
Account: b.Account,
|
Channel: groupJID,
|
||||||
Protocol: b.Protocol,
|
Account: b.Account,
|
||||||
Extra: make(map[string][]interface{}),
|
Protocol: b.Protocol,
|
||||||
|
Extra: make(map[string][]interface{}),
|
||||||
// ParentID: TODO, // TODO handle thread replies // map from Info.QuotedMessageID string
|
// ParentID: TODO, // TODO handle thread replies // map from Info.QuotedMessageID string
|
||||||
ID: message.Info.Id,
|
// Event string `json:"event"`
|
||||||
}
|
// Gateway string // will be added during message processing
|
||||||
|
ID: message.Info.Id}
|
||||||
|
|
||||||
if avatarURL, exists := b.userAvatars[senderJID]; exists {
|
if avatarURL, exists := b.userAvatars[senderJID]; exists {
|
||||||
rmsg.Avatar = avatarURL
|
rmsg.Avatar = avatarURL
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account)
|
|
||||||
b.Log.Debugf("<= Message is %#v", rmsg)
|
b.Log.Debugf("<= Message is %#v", rmsg)
|
||||||
|
|
||||||
b.Remote <- rmsg
|
b.Remote <- rmsg
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleImageMessage sent from WhatsApp, relay it to the brige
|
// HandleImageMessage sent from WhatsApp, relay it to the brige
|
||||||
func (b *Bwhatsapp) HandleImageMessage(message whatsapp.ImageMessage) {
|
func (b *Bwhatsapp) HandleImageMessage(message whatsapp.ImageMessage) {
|
||||||
if message.Info.FromMe || message.Info.Timestamp < b.startedAt {
|
if message.Info.FromMe { // || !strings.Contains(strings.ToLower(message.Text), "@echo") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
senderJID := message.Info.SenderJid
|
// whatsapp sends last messages to show context , cut them
|
||||||
if len(message.Info.SenderJid) == 0 && message.Info.Source != nil && message.Info.Source.Participant != nil {
|
if message.Info.Timestamp < b.startedAt {
|
||||||
senderJID = *message.Info.Source.Participant
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
senderName := b.getSenderName(message.Info.SenderJid)
|
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 == "" {
|
if senderName == "" {
|
||||||
senderName = "Someone" // don't expose telephone number
|
senderName = "Someone" // don't expose telephone number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account)
|
||||||
rmsg := config.Message{
|
rmsg := config.Message{
|
||||||
UserID: senderJID,
|
UserID: senderJID,
|
||||||
Username: senderName,
|
Username: senderName,
|
||||||
Channel: message.Info.RemoteJid,
|
Timestamp: messageTime,
|
||||||
Account: b.Account,
|
Channel: groupJID,
|
||||||
Protocol: b.Protocol,
|
Account: b.Account,
|
||||||
Extra: make(map[string][]interface{}),
|
Protocol: b.Protocol,
|
||||||
ID: message.Info.Id,
|
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 {
|
if avatarURL, exists := b.userAvatars[senderJID]; exists {
|
||||||
rmsg.Avatar = avatarURL
|
rmsg.Avatar = avatarURL
|
||||||
}
|
}
|
||||||
|
|
||||||
fileExt, err := mime.ExtensionsByType(message.Type)
|
// Download and unencrypt content
|
||||||
|
data, err := message.Download()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Errorf("Mimetype detection error: %s", err)
|
b.Log.Errorf("%v", err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// rename .jfif to .jpg https://github.com/42wim/matterbridge/issues/1292
|
// Get file extension by mimetype
|
||||||
if fileExt[0] == ".jfif" {
|
fileExt, err := mime.ExtensionsByType(message.Type)
|
||||||
fileExt[0] = ".jpg"
|
if err != nil {
|
||||||
}
|
b.Log.Errorf("%v", err)
|
||||||
|
return
|
||||||
// rename .jpe to .jpg https://github.com/42wim/matterbridge/issues/1463
|
|
||||||
if fileExt[0] == ".jpe" {
|
|
||||||
fileExt[0] = ".jpg"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filename := fmt.Sprintf("%v%v", message.Info.Id, fileExt[0])
|
filename := fmt.Sprintf("%v%v", message.Info.Id, fileExt[0])
|
||||||
|
|
||||||
b.Log.Debugf("Trying to download %s with type %s", filename, message.Type)
|
b.Log.Debugf("<= Image downloaded and unencrypted")
|
||||||
|
|
||||||
data, err := message.Download()
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("Download image failed: %s", err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move file to bridge storage
|
// Move file to bridge storage
|
||||||
helper.HandleDownloadData(b.Log, &rmsg, filename, message.Caption, "", &data, b.General)
|
helper.HandleDownloadData(b.Log, &rmsg, filename, message.Caption, "", &data, b.General)
|
||||||
|
|
||||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account)
|
b.Log.Debugf("<= Image Message is %#v", rmsg)
|
||||||
b.Log.Debugf("<= Message is %#v", rmsg)
|
|
||||||
|
|
||||||
b.Remote <- rmsg
|
b.Remote <- rmsg
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleVideoMessage downloads video messages
|
//func (b *Bwhatsapp) HandleVideoMessage(message whatsapp.VideoMessage) {
|
||||||
func (b *Bwhatsapp) HandleVideoMessage(message whatsapp.VideoMessage) {
|
// fmt.Println(message) // TODO implement
|
||||||
if message.Info.FromMe || message.Info.Timestamp < b.startedAt {
|
//}
|
||||||
return
|
//
|
||||||
}
|
//func (b *Bwhatsapp) HandleJsonMessage(message string) {
|
||||||
|
// fmt.Println(message) // TODO implement
|
||||||
senderJID := message.Info.SenderJid
|
//}
|
||||||
if len(message.Info.SenderJid) == 0 && message.Info.Source != nil && message.Info.Source.Participant != nil {
|
// TODO HandleRawMessage
|
||||||
senderJID = *message.Info.Source.Participant
|
// TODO HandleAudioMessage
|
||||||
}
|
|
||||||
|
|
||||||
senderName := b.getSenderName(message.Info.SenderJid)
|
|
||||||
if senderName == "" {
|
|
||||||
senderName = "Someone" // don't expose telephone number
|
|
||||||
}
|
|
||||||
|
|
||||||
rmsg := config.Message{
|
|
||||||
UserID: senderJID,
|
|
||||||
Username: senderName,
|
|
||||||
Channel: message.Info.RemoteJid,
|
|
||||||
Account: b.Account,
|
|
||||||
Protocol: b.Protocol,
|
|
||||||
Extra: make(map[string][]interface{}),
|
|
||||||
ID: message.Info.Id,
|
|
||||||
}
|
|
||||||
|
|
||||||
if avatarURL, exists := b.userAvatars[senderJID]; exists {
|
|
||||||
rmsg.Avatar = avatarURL
|
|
||||||
}
|
|
||||||
|
|
||||||
fileExt, err := mime.ExtensionsByType(message.Type)
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("Mimetype detection error: %s", err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(fileExt) == 0 {
|
|
||||||
fileExt = append(fileExt, ".mp4")
|
|
||||||
}
|
|
||||||
|
|
||||||
filename := fmt.Sprintf("%v%v", message.Info.Id, fileExt[0])
|
|
||||||
|
|
||||||
b.Log.Debugf("Trying to download %s with size %#v and type %s", filename, message.Length, message.Type)
|
|
||||||
|
|
||||||
data, err := message.Download()
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("Download video failed: %s", err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move file to bridge storage
|
|
||||||
helper.HandleDownloadData(b.Log, &rmsg, filename, message.Caption, "", &data, b.General)
|
|
||||||
|
|
||||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account)
|
|
||||||
b.Log.Debugf("<= Message is %#v", rmsg)
|
|
||||||
|
|
||||||
b.Remote <- rmsg
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleAudioMessage downloads audio messages
|
|
||||||
func (b *Bwhatsapp) HandleAudioMessage(message whatsapp.AudioMessage) {
|
|
||||||
if message.Info.FromMe || message.Info.Timestamp < b.startedAt {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
senderJID := message.Info.SenderJid
|
|
||||||
if len(message.Info.SenderJid) == 0 && message.Info.Source != nil && message.Info.Source.Participant != nil {
|
|
||||||
senderJID = *message.Info.Source.Participant
|
|
||||||
}
|
|
||||||
|
|
||||||
senderName := b.getSenderName(message.Info.SenderJid)
|
|
||||||
if senderName == "" {
|
|
||||||
senderName = "Someone" // don't expose telephone number
|
|
||||||
}
|
|
||||||
|
|
||||||
rmsg := config.Message{
|
|
||||||
UserID: senderJID,
|
|
||||||
Username: senderName,
|
|
||||||
Channel: message.Info.RemoteJid,
|
|
||||||
Account: b.Account,
|
|
||||||
Protocol: b.Protocol,
|
|
||||||
Extra: make(map[string][]interface{}),
|
|
||||||
ID: message.Info.Id,
|
|
||||||
}
|
|
||||||
|
|
||||||
if avatarURL, exists := b.userAvatars[senderJID]; exists {
|
|
||||||
rmsg.Avatar = avatarURL
|
|
||||||
}
|
|
||||||
|
|
||||||
fileExt, err := mime.ExtensionsByType(message.Type)
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("Mimetype detection error: %s", err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(fileExt) == 0 {
|
|
||||||
fileExt = append(fileExt, ".ogg")
|
|
||||||
}
|
|
||||||
|
|
||||||
filename := fmt.Sprintf("%v%v", message.Info.Id, fileExt[0])
|
|
||||||
|
|
||||||
b.Log.Debugf("Trying to download %s with size %#v and type %s", filename, message.Length, message.Type)
|
|
||||||
|
|
||||||
data, err := message.Download()
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("Download audio failed: %s", err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move file to bridge storage
|
|
||||||
helper.HandleDownloadData(b.Log, &rmsg, filename, "audio message", "", &data, b.General)
|
|
||||||
|
|
||||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account)
|
|
||||||
b.Log.Debugf("<= Message is %#v", rmsg)
|
|
||||||
|
|
||||||
b.Remote <- rmsg
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleDocumentMessage downloads documents
|
|
||||||
func (b *Bwhatsapp) HandleDocumentMessage(message whatsapp.DocumentMessage) {
|
|
||||||
if message.Info.FromMe || message.Info.Timestamp < b.startedAt {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
senderJID := message.Info.SenderJid
|
|
||||||
if len(message.Info.SenderJid) == 0 && message.Info.Source != nil && message.Info.Source.Participant != nil {
|
|
||||||
senderJID = *message.Info.Source.Participant
|
|
||||||
}
|
|
||||||
|
|
||||||
senderName := b.getSenderName(message.Info.SenderJid)
|
|
||||||
if senderName == "" {
|
|
||||||
senderName = "Someone" // don't expose telephone number
|
|
||||||
}
|
|
||||||
|
|
||||||
rmsg := config.Message{
|
|
||||||
UserID: senderJID,
|
|
||||||
Username: senderName,
|
|
||||||
Channel: message.Info.RemoteJid,
|
|
||||||
Account: b.Account,
|
|
||||||
Protocol: b.Protocol,
|
|
||||||
Extra: make(map[string][]interface{}),
|
|
||||||
ID: message.Info.Id,
|
|
||||||
}
|
|
||||||
|
|
||||||
if avatarURL, exists := b.userAvatars[senderJID]; exists {
|
|
||||||
rmsg.Avatar = avatarURL
|
|
||||||
}
|
|
||||||
|
|
||||||
fileExt, err := mime.ExtensionsByType(message.Type)
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("Mimetype detection error: %s", err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
filename := fmt.Sprintf("%v", message.FileName)
|
|
||||||
|
|
||||||
b.Log.Debugf("Trying to download %s with extension %s and type %s", filename, fileExt, message.Type)
|
|
||||||
|
|
||||||
data, err := message.Download()
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("Download document message failed: %s", err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move file to bridge storage
|
|
||||||
helper.HandleDownloadData(b.Log, &rmsg, filename, "document", "", &data, b.General)
|
|
||||||
|
|
||||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account)
|
|
||||||
b.Log.Debugf("<= Message is %#v", rmsg)
|
|
||||||
|
|
||||||
b.Remote <- rmsg
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,24 +6,22 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
qrcodeTerminal "github.com/Baozisoftware/qrcode-terminal-go"
|
qrcodeTerminal "github.com/Baozisoftware/qrcode-terminal-go"
|
||||||
"github.com/Rhymen/go-whatsapp"
|
"github.com/Rhymen/go-whatsapp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProfilePicInfo struct {
|
type ProfilePicInfo struct {
|
||||||
URL string `json:"eurl"`
|
URL string `json:"eurl"`
|
||||||
Tag string `json:"tag"`
|
Tag string `json:"tag"`
|
||||||
Status int16 `json:"status"`
|
|
||||||
|
Status int16 `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func qrFromTerminal(invert bool) chan string {
|
func qrFromTerminal(invert bool) chan string {
|
||||||
qr := make(chan string)
|
qr := make(chan string)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
terminal := qrcodeTerminal.New()
|
terminal := qrcodeTerminal.New()
|
||||||
|
|
||||||
if invert {
|
if invert {
|
||||||
terminal = qrcodeTerminal.New2(qrcodeTerminal.ConsoleColors.BrightWhite, qrcodeTerminal.ConsoleColors.BrightBlack, qrcodeTerminal.QRCodeRecoveryLevels.Medium)
|
terminal = qrcodeTerminal.New2(qrcodeTerminal.ConsoleColors.BrightWhite, qrcodeTerminal.ConsoleColors.BrightBlack, qrcodeTerminal.QRCodeRecoveryLevels.Medium)
|
||||||
}
|
}
|
||||||
@@ -46,12 +44,13 @@ func (b *Bwhatsapp) readSession() (whatsapp.Session, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return session, err
|
return session, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
decoder := gob.NewDecoder(file)
|
decoder := gob.NewDecoder(file)
|
||||||
|
err = decoder.Decode(&session)
|
||||||
return session, decoder.Decode(&session)
|
if err != nil {
|
||||||
|
return session, err
|
||||||
|
}
|
||||||
|
return session, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bwhatsapp) writeSession(session whatsapp.Session) error {
|
func (b *Bwhatsapp) writeSession(session whatsapp.Session) error {
|
||||||
@@ -66,31 +65,11 @@ func (b *Bwhatsapp) writeSession(session whatsapp.Session) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
encoder := gob.NewEncoder(file)
|
encoder := gob.NewEncoder(file)
|
||||||
|
err = encoder.Encode(session)
|
||||||
|
|
||||||
return encoder.Encode(session)
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bwhatsapp) restoreSession() (*whatsapp.Session, error) {
|
|
||||||
session, err := b.readSession()
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Warn(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Log.Debugln("Restoring WhatsApp session..")
|
|
||||||
|
|
||||||
session, err = b.conn.RestoreWithSession(session)
|
|
||||||
if err != nil {
|
|
||||||
// restore session connection timed out (I couldn't get over it without logging in again)
|
|
||||||
return nil, errors.New("failed to restore session: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Log.Debugln("Session restored successfully!")
|
|
||||||
|
|
||||||
return &session, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bwhatsapp) getSenderName(senderJid string) string {
|
func (b *Bwhatsapp) getSenderName(senderJid string) string {
|
||||||
@@ -135,7 +114,6 @@ func (b *Bwhatsapp) getSenderNotify(senderJid string) string {
|
|||||||
if sender, exists := b.users[senderJid]; exists {
|
if sender, exists := b.users[senderJid]; exists {
|
||||||
return sender.Notify
|
return sender.Notify
|
||||||
}
|
}
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,20 +122,11 @@ func (b *Bwhatsapp) GetProfilePicThumb(jid string) (*ProfilePicInfo, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get avatar: %v", err)
|
return nil, fmt.Errorf("failed to get avatar: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
content := <-data
|
content := <-data
|
||||||
info := &ProfilePicInfo{}
|
info := &ProfilePicInfo{}
|
||||||
|
|
||||||
err = json.Unmarshal([]byte(content), info)
|
err = json.Unmarshal([]byte(content), info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return info, fmt.Errorf("failed to unmarshal avatar info: %v", err)
|
return info, fmt.Errorf("failed to unmarshal avatar info: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return info, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isGroupJid(identifier string) bool {
|
|
||||||
return strings.HasSuffix(identifier, "@g.us") ||
|
|
||||||
strings.HasSuffix(identifier, "@temp") ||
|
|
||||||
strings.HasSuffix(identifier, "@broadcast")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const (
|
|||||||
type Bwhatsapp struct {
|
type Bwhatsapp struct {
|
||||||
*bridge.Config
|
*bridge.Config
|
||||||
|
|
||||||
|
// https://github.com/Rhymen/go-whatsapp/blob/c31092027237441cffba1b9cb148eadf7c83c3d2/session.go#L18-L21
|
||||||
session *whatsapp.Session
|
session *whatsapp.Session
|
||||||
conn *whatsapp.Conn
|
conn *whatsapp.Conn
|
||||||
startedAt uint64
|
startedAt uint64
|
||||||
@@ -39,7 +40,6 @@ type Bwhatsapp struct {
|
|||||||
// New Create a new WhatsApp bridge. This will be called for each [whatsapp.<server>] entry you have in the config file
|
// New Create a new WhatsApp bridge. This will be called for each [whatsapp.<server>] entry you have in the config file
|
||||||
func New(cfg *bridge.Config) bridge.Bridger {
|
func New(cfg *bridge.Config) bridge.Bridger {
|
||||||
number := cfg.GetString(cfgNumber)
|
number := cfg.GetString(cfgNumber)
|
||||||
|
|
||||||
if number == "" {
|
if number == "" {
|
||||||
cfg.Log.Fatalf("Missing configuration for WhatsApp bridge: Number")
|
cfg.Log.Fatalf("Missing configuration for WhatsApp bridge: Number")
|
||||||
}
|
}
|
||||||
@@ -50,17 +50,21 @@ func New(cfg *bridge.Config) bridge.Bridger {
|
|||||||
users: make(map[string]whatsapp.Contact),
|
users: make(map[string]whatsapp.Contact),
|
||||||
userAvatars: make(map[string]string),
|
userAvatars: make(map[string]string),
|
||||||
}
|
}
|
||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect to WhatsApp. Required implementation of the Bridger interface
|
// Connect to WhatsApp. Required implementation of the Bridger interface
|
||||||
|
// https://github.com/42wim/matterbridge/blob/2cfd880cdb0df29771bf8f31df8d990ab897889d/bridge/bridge.go#L11-L16
|
||||||
func (b *Bwhatsapp) Connect() error {
|
func (b *Bwhatsapp) Connect() error {
|
||||||
|
b.RLock() // TODO do we need locking for Whatsapp?
|
||||||
|
defer b.RUnlock()
|
||||||
|
|
||||||
number := b.GetString(cfgNumber)
|
number := b.GetString(cfgNumber)
|
||||||
if number == "" {
|
if number == "" {
|
||||||
return errors.New("whatsapp's telephone number need to be configured")
|
return errors.New("WhatsApp's telephone Number need to be configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/Rhymen/go-whatsapp#creating-a-connection
|
||||||
b.Log.Debugln("Connecting to WhatsApp..")
|
b.Log.Debugln("Connecting to WhatsApp..")
|
||||||
conn, err := whatsapp.NewConn(20 * time.Second)
|
conn, err := whatsapp.NewConn(20 * time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -73,18 +77,35 @@ func (b *Bwhatsapp) Connect() error {
|
|||||||
b.Log.Debugln("WhatsApp connection successful")
|
b.Log.Debugln("WhatsApp connection successful")
|
||||||
|
|
||||||
// load existing session in order to keep it between restarts
|
// load existing session in order to keep it between restarts
|
||||||
b.session, err = b.restoreSession()
|
if b.session == nil {
|
||||||
if err != nil {
|
var session whatsapp.Session
|
||||||
b.Log.Warn(err.Error())
|
session, err = b.readSession()
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
b.Log.Debugln("Restoring WhatsApp session..")
|
||||||
|
|
||||||
|
// https://github.com/Rhymen/go-whatsapp#restore
|
||||||
|
session, err = b.conn.RestoreWithSession(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)
|
||||||
|
return errors.New("failed to restore session: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
b.session = &session
|
||||||
|
b.Log.Debugln("Session restored successfully!")
|
||||||
|
} else {
|
||||||
|
b.Log.Warn(err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// login to a new session
|
// login to a new session
|
||||||
if b.session == nil {
|
if b.session == nil {
|
||||||
if err = b.Login(); err != nil {
|
err = b.Login()
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
b.startedAt = uint64(time.Now().Unix())
|
b.startedAt = uint64(time.Now().Unix())
|
||||||
|
|
||||||
_, err = b.conn.Contacts()
|
_, err = b.conn.Contacts()
|
||||||
@@ -95,7 +116,6 @@ func (b *Bwhatsapp) Connect() error {
|
|||||||
// see https://github.com/Rhymen/go-whatsapp/issues/137#issuecomment-480316013
|
// see https://github.com/Rhymen/go-whatsapp/issues/137#issuecomment-480316013
|
||||||
for len(b.conn.Store.Contacts) == 0 {
|
for len(b.conn.Store.Contacts) == 0 {
|
||||||
b.conn.Contacts() // nolint:errcheck
|
b.conn.Contacts() // nolint:errcheck
|
||||||
|
|
||||||
<-time.After(1 * time.Second)
|
<-time.After(1 * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,13 +135,12 @@ func (b *Bwhatsapp) Connect() error {
|
|||||||
info, err := b.GetProfilePicThumb(jid)
|
info, err := b.GetProfilePicThumb(jid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Warnf("Could not get profile photo of %s: %v", jid, err)
|
b.Log.Warnf("Could not get profile photo of %s: %v", jid, err)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
b.Lock()
|
// TODO any race conditions here?
|
||||||
b.userAvatars[jid] = info.URL
|
b.userAvatars[jid] = info.URL
|
||||||
b.Unlock()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Log.Debug("Finished getting avatars..")
|
b.Log.Debug("Finished getting avatars..")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -138,10 +157,8 @@ func (b *Bwhatsapp) Login() error {
|
|||||||
session, err := b.conn.Login(qrChan)
|
session, err := b.conn.Login(qrChan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Warnln("Failed to log in:", err)
|
b.Log.Warnln("Failed to log in:", err)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
b.session = &session
|
b.session = &session
|
||||||
|
|
||||||
b.Log.Infof("Logged into session: %#v", session)
|
b.Log.Infof("Logged into session: %#v", session)
|
||||||
@@ -152,17 +169,29 @@ func (b *Bwhatsapp) Login() error {
|
|||||||
fmt.Fprintf(os.Stderr, "error saving session: %v\n", err)
|
fmt.Fprintf(os.Stderr, "error saving session: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO change connection strings to configured ones longClientName:"github.com/rhymen/go-whatsapp", shortClientName:"go-whatsapp"}" prefix=whatsapp
|
||||||
|
// TODO get also a nice logo
|
||||||
|
|
||||||
|
// TODO notification about unplugged and dead battery
|
||||||
|
// conn.Info: Wid, Pushname, Connected, Battery, Plugged
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disconnect is called while reconnecting to the bridge
|
// Disconnect is called while reconnecting to the bridge
|
||||||
|
// TODO 42wim Documentation would be helpful on when reconnects happen and what should be done in this function
|
||||||
// Required implementation of the Bridger interface
|
// Required implementation of the Bridger interface
|
||||||
|
// https://github.com/42wim/matterbridge/blob/2cfd880cdb0df29771bf8f31df8d990ab897889d/bridge/bridge.go#L11-L16
|
||||||
func (b *Bwhatsapp) Disconnect() error {
|
func (b *Bwhatsapp) Disconnect() error {
|
||||||
// We could Logout, but that would close the session completely and would require a new QR code scan
|
// We could Logout, but that would close the session completely and would require a new QR code scan
|
||||||
// https://github.com/Rhymen/go-whatsapp/blob/c31092027237441cffba1b9cb148eadf7c83c3d2/session.go#L377-L381
|
// https://github.com/Rhymen/go-whatsapp/blob/c31092027237441cffba1b9cb148eadf7c83c3d2/session.go#L377-L381
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isGroupJid(identifier string) bool {
|
||||||
|
return strings.HasSuffix(identifier, "@g.us") || strings.HasSuffix(identifier, "@temp") || strings.HasSuffix(identifier, "@broadcast")
|
||||||
|
}
|
||||||
|
|
||||||
// JoinChannel Join a WhatsApp group specified in gateway config as channel='number-id@g.us' or channel='Channel name'
|
// JoinChannel Join a WhatsApp group specified in gateway config as channel='number-id@g.us' or channel='Channel name'
|
||||||
// Required implementation of the Bridger interface
|
// Required implementation of the Bridger interface
|
||||||
// https://github.com/42wim/matterbridge/blob/2cfd880cdb0df29771bf8f31df8d990ab897889d/bridge/bridge.go#L11-L16
|
// https://github.com/42wim/matterbridge/blob/2cfd880cdb0df29771bf8f31df8d990ab897889d/bridge/bridge.go#L11-L16
|
||||||
@@ -181,33 +210,39 @@ func (b *Bwhatsapp) JoinChannel(channel config.ChannelInfo) error {
|
|||||||
if _, exists := b.conn.Store.Contacts[channel.Name]; !exists {
|
if _, exists := b.conn.Store.Contacts[channel.Name]; !exists {
|
||||||
return fmt.Errorf("account doesn't belong to group with jid %s", channel.Name)
|
return fmt.Errorf("account doesn't belong to group with jid %s", channel.Name)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
return nil
|
// channel.Name specifies group name that might change, warn about it
|
||||||
}
|
var jids []string
|
||||||
|
|
||||||
// channel.Name specifies group name that might change, warn about it
|
|
||||||
var jids []string
|
|
||||||
for id, contact := range b.conn.Store.Contacts {
|
|
||||||
if isGroupJid(id) && contact.Name == channel.Name {
|
|
||||||
jids = append(jids, id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch len(jids) {
|
|
||||||
case 0:
|
|
||||||
// didn't match any group - print out possibilites
|
|
||||||
for id, contact := range b.conn.Store.Contacts {
|
for id, contact := range b.conn.Store.Contacts {
|
||||||
if isGroupJid(id) {
|
if isGroupJid(id) && contact.Name == channel.Name {
|
||||||
b.Log.Infof("%s %s", contact.Jid, contact.Name)
|
jids = append(jids, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("please specify group's JID from the list above instead of the name '%s'", channel.Name)
|
switch len(jids) {
|
||||||
case 1:
|
case 0:
|
||||||
return fmt.Errorf("group name might change. Please configure gateway with channel=\"%v\" instead of channel=\"%v\"", jids[0], channel.Name)
|
// didn't match any group - print out possibilites
|
||||||
default:
|
// TODO sort
|
||||||
return fmt.Errorf("there is more than one group with name '%s'. Please specify one of JIDs as channel name: %v", channel.Name, jids)
|
// copy b;
|
||||||
|
//sort.Slice(people, func(i, j int) bool {
|
||||||
|
// return people[i].Age > people[j].Age
|
||||||
|
//})
|
||||||
|
for id, contact := range b.conn.Store.Contacts {
|
||||||
|
if isGroupJid(id) {
|
||||||
|
b.Log.Infof("%s %s", contact.Jid, contact.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("please specify group's JID from the list above instead of the name '%s'", channel.Name)
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
return fmt.Errorf("group name might change. Please configure gateway with channel=\"%v\" instead of channel=\"%v\"", jids[0], channel.Name)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("there is more than one group with name '%s'. Please specify one of JIDs as channel name: %v", channel.Name, jids)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post a document message from the bridge to WhatsApp
|
// Post a document message from the bridge to WhatsApp
|
||||||
@@ -281,23 +316,22 @@ func (b *Bwhatsapp) Send(msg config.Message) (string, error) {
|
|||||||
if msg.ID == "" {
|
if msg.ID == "" {
|
||||||
// No message ID in case action is executed on a message sent before the bridge was started
|
// No message ID in case action is executed on a message sent before the bridge was started
|
||||||
// and then the bridge cache doesn't have this message ID mapped
|
// and then the bridge cache doesn't have this message ID mapped
|
||||||
|
|
||||||
|
// TODO 42wim Doesn't the app get clogged with a ton of IDs after some time of running?
|
||||||
|
// WhatsApp allows to set any ID so in that case we could use external IDs and don't do mapping
|
||||||
|
// but external IDs are not set
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
// TODO delete message on WhatsApp https://github.com/Rhymen/go-whatsapp/issues/100
|
||||||
_, err := b.conn.RevokeMessage(msg.Channel, msg.ID, true)
|
return "", nil
|
||||||
|
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Edit message
|
// Edit message
|
||||||
if msg.ID != "" {
|
if msg.ID != "" {
|
||||||
b.Log.Debugf("updating message with id %s", msg.ID)
|
b.Log.Debugf("updating message with id %s", msg.ID)
|
||||||
|
|
||||||
if b.GetString("editsuffix") != "" {
|
msg.Text += " (edited)"
|
||||||
msg.Text += b.GetString("EditSuffix")
|
// TODO handle edit as a message reply with updated text
|
||||||
} else {
|
|
||||||
msg.Text += " (edited)"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle Upload a file
|
// Handle Upload a file
|
||||||
@@ -327,7 +361,16 @@ func (b *Bwhatsapp) Send(msg config.Message) (string, error) {
|
|||||||
|
|
||||||
b.Log.Debugf("=> Sending %#v", msg)
|
b.Log.Debugf("=> Sending %#v", msg)
|
||||||
|
|
||||||
return b.conn.Send(message)
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO do we want that? to allow login with QR code from a bridged channel? https://github.com/tulir/mautrix-whatsapp/blob/513eb18e2d59bada0dd515ee1abaaf38a3bfe3d5/commands.go#L76
|
// TODO do we want that? to allow login with QR code from a bridged channel? https://github.com/tulir/mautrix-whatsapp/blob/513eb18e2d59bada0dd515ee1abaaf38a3bfe3d5/commands.go#L76
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
package bxmpp
|
package bxmpp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -90,21 +86,14 @@ func (b *Bxmpp) Send(msg config.Message) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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).
|
||||||
var err error
|
|
||||||
if msg.Extra != nil {
|
if msg.Extra != nil {
|
||||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||||
b.Log.Debugf("=> Sending attachement message %#v", rmsg)
|
b.Log.Debugf("=> Sending attachement message %#v", rmsg)
|
||||||
if b.GetString("WebhookURL") != "" {
|
if _, err := b.xc.Send(xmpp.Chat{
|
||||||
err = b.postSlackCompatibleWebhook(msg)
|
Type: "groupchat",
|
||||||
} else {
|
Remote: rmsg.Channel + "@" + b.GetString("Muc"),
|
||||||
_, err = b.xc.Send(xmpp.Chat{
|
Text: rmsg.Username + rmsg.Text,
|
||||||
Type: "groupchat",
|
}); err != nil {
|
||||||
Remote: rmsg.Channel + "@" + b.GetString("Muc"),
|
|
||||||
Text: rmsg.Username + rmsg.Text,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
b.Log.WithError(err).Error("Unable to send message with share URL.")
|
b.Log.WithError(err).Error("Unable to send message with share URL.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,23 +102,13 @@ func (b *Bxmpp) Send(msg config.Message) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.GetString("WebhookURL") != "" {
|
|
||||||
b.Log.Debugf("Sending message using Webhook")
|
|
||||||
err := b.postSlackCompatibleWebhook(msg)
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("Failed to send message using webhook: %s", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Post normal message.
|
|
||||||
var msgReplaceID string
|
var msgReplaceID string
|
||||||
msgID := xid.New().String()
|
msgID := xid.New().String()
|
||||||
if msg.ID != "" {
|
if msg.ID != "" {
|
||||||
|
msgID = msg.ID
|
||||||
msgReplaceID = msg.ID
|
msgReplaceID = msg.ID
|
||||||
}
|
}
|
||||||
|
// Post normal message.
|
||||||
b.Log.Debugf("=> Sending message %#v", msg)
|
b.Log.Debugf("=> Sending message %#v", msg)
|
||||||
if _, err := b.xc.Send(xmpp.Chat{
|
if _, err := b.xc.Send(xmpp.Chat{
|
||||||
Type: "groupchat",
|
Type: "groupchat",
|
||||||
@@ -143,46 +122,12 @@ func (b *Bxmpp) Send(msg config.Message) (string, error) {
|
|||||||
return msgID, nil
|
return msgID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bxmpp) postSlackCompatibleWebhook(msg config.Message) error {
|
|
||||||
type XMPPWebhook struct {
|
|
||||||
Username string `json:"username"`
|
|
||||||
Text string `json:"text"`
|
|
||||||
}
|
|
||||||
webhookBody, err := json.Marshal(XMPPWebhook{
|
|
||||||
Username: msg.Username,
|
|
||||||
Text: msg.Text,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("Failed to marshal webhook: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := http.Post(b.GetString("WebhookURL")+"/"+url.QueryEscape(msg.Channel), "application/json", bytes.NewReader(webhookBody))
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("Failed to POST webhook: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bxmpp) createXMPP() error {
|
func (b *Bxmpp) createXMPP() error {
|
||||||
var serverName string
|
if !strings.Contains(b.GetString("Jid"), "@") {
|
||||||
switch {
|
return fmt.Errorf("the Jid %s doesn't contain an @", b.GetString("Jid"))
|
||||||
case !b.GetBool("Anonymous"):
|
|
||||||
if !strings.Contains(b.GetString("Jid"), "@") {
|
|
||||||
return fmt.Errorf("the Jid %s doesn't contain an @", b.GetString("Jid"))
|
|
||||||
}
|
|
||||||
serverName = strings.Split(b.GetString("Jid"), "@")[1]
|
|
||||||
case !strings.Contains(b.GetString("Server"), ":"):
|
|
||||||
serverName = strings.Split(b.GetString("Server"), ":")[0]
|
|
||||||
default:
|
|
||||||
serverName = b.GetString("Server")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tc := &tls.Config{
|
tc := &tls.Config{
|
||||||
ServerName: serverName,
|
ServerName: strings.Split(b.GetString("Jid"), "@")[1],
|
||||||
InsecureSkipVerify: b.GetBool("SkipTLSVerify"), // nolint: gosec
|
InsecureSkipVerify: b.GetBool("SkipTLSVerify"), // nolint: gosec
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,13 +228,7 @@ func (b *Bxmpp) handleXMPP() error {
|
|||||||
for {
|
for {
|
||||||
m, err := b.xc.Recv()
|
m, err := b.xc.Recv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// An error together with AvatarData is non-fatal
|
return err
|
||||||
switch m.(type) {
|
|
||||||
case xmpp.AvatarData:
|
|
||||||
continue
|
|
||||||
default:
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := m.(type) {
|
switch v := m.(type) {
|
||||||
@@ -400,7 +339,7 @@ func (b *Bxmpp) handleUploadFile(msg *config.Message) error {
|
|||||||
|
|
||||||
func (b *Bxmpp) parseNick(remote string) string {
|
func (b *Bxmpp) parseNick(remote string) string {
|
||||||
s := strings.Split(remote, "@")
|
s := strings.Split(remote, "@")
|
||||||
if len(s) > 1 {
|
if len(s) > 0 {
|
||||||
s = strings.Split(s[1], "/")
|
s = strings.Split(s[1], "/")
|
||||||
if len(s) == 2 {
|
if len(s) == 2 {
|
||||||
return s[1] // nick
|
return s[1] // nick
|
||||||
@@ -439,11 +378,6 @@ func (b *Bxmpp) skipMessage(message xmpp.Chat) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore messages posted by our webhook
|
|
||||||
if b.GetString("WebhookURL") != "" && strings.Contains(message.ID, "webhookbot") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip delayed messages
|
// skip delayed messages
|
||||||
return !message.Stamp.IsZero() && time.Since(message.Stamp).Minutes() > 5
|
return !message.Stamp.IsZero() && time.Since(message.Stamp).Minutes() > 5
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package bzulip
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -12,7 +11,6 @@ import (
|
|||||||
"github.com/42wim/matterbridge/bridge"
|
"github.com/42wim/matterbridge/bridge"
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
"github.com/42wim/matterbridge/version"
|
|
||||||
gzb "github.com/matterbridge/gozulipbot"
|
gzb "github.com/matterbridge/gozulipbot"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,7 +27,7 @@ func New(cfg *bridge.Config) bridge.Bridger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bzulip) Connect() error {
|
func (b *Bzulip) Connect() error {
|
||||||
bot := gzb.Bot{APIKey: b.GetString("token"), APIURL: b.GetString("server") + "/api/v1/", Email: b.GetString("login"), UserAgent: fmt.Sprintf("matterbridge/%s", version.Release)}
|
bot := gzb.Bot{APIKey: b.GetString("token"), APIURL: b.GetString("server") + "/api/v1/", Email: b.GetString("login")}
|
||||||
bot.Init()
|
bot.Init()
|
||||||
q, err := bot.RegisterAll()
|
q, err := bot.RegisterAll()
|
||||||
b.q = q
|
b.q = q
|
||||||
@@ -127,7 +125,6 @@ func (b *Bzulip) handleQueue() error {
|
|||||||
b.Log.Debug("heartbeat received.")
|
b.Log.Debug("heartbeat received.")
|
||||||
default:
|
default:
|
||||||
b.Log.Debugf("receiving error: %#v", err)
|
b.Log.Debugf("receiving error: %#v", err)
|
||||||
time.Sleep(time.Second * 10)
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
|
|||||||
1429
changelog.md
@@ -1,15 +0,0 @@
|
|||||||
fmt := import("fmt")
|
|
||||||
os := import("os")
|
|
||||||
times := import("times")
|
|
||||||
|
|
||||||
if msgText != "" && msgUsername != "system" {
|
|
||||||
os.chdir("/var/www/matterbridge")
|
|
||||||
file := os.open_file("inmessage.log", os.o_append|os.o_wronly|os.o_create, 0644)
|
|
||||||
file.write_string(fmt.sprintf(
|
|
||||||
"[%s] <%s> %s\n",
|
|
||||||
times.time_format(times.now(), times.format_rfc1123),
|
|
||||||
msgUsername,
|
|
||||||
msgText
|
|
||||||
))
|
|
||||||
file.close()
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#!/sbin/openrc-run
|
|
||||||
# Copyright 2021-2022 Gentoo Authors
|
|
||||||
# Distributed under the terms of the GNU General Public License v2
|
|
||||||
|
|
||||||
command=/usr/bin/matterbridge
|
|
||||||
command_args="-conf ${MATTERBRIDGE_CONF:-/etc/matterbridge/bridge.toml} ${MATTERBRIDGE_ARGS}"
|
|
||||||
command_user="matterbridge:matterbridge"
|
|
||||||
pidfile="/run/${RC_SVCNAME}.pid"
|
|
||||||
command_background=1
|
|
||||||
output_log="/var/log/${RC_SVCNAME}.log"
|
|
||||||
error_log="${output_log}"
|
|
||||||
|
|
||||||
depend() {
|
|
||||||
need net
|
|
||||||
}
|
|
||||||
|
|
||||||
start_pre() {
|
|
||||||
checkpath -f "${output_log}" -o "${command_user}" || return 1
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
FROM alpine:edge as certs
|
FROM alpine:edge as certs
|
||||||
RUN apk --update add ca-certificates
|
RUN apk --update add ca-certificates
|
||||||
ARG VERSION=1.22.3
|
|
||||||
ADD https://github.com/42wim/matterbridge/releases/download/v${VERSION}/matterbridge-${VERSION}-linux-arm64 /bin/matterbridge
|
|
||||||
RUN chmod +x /bin/matterbridge
|
|
||||||
|
|
||||||
FROM scratch
|
FROM scratch
|
||||||
|
ARG VERSION=1.12.3
|
||||||
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||||
COPY --from=certs /bin/matterbridge /bin/matterbridge
|
ADD https://github.com/42wim/matterbridge/releases/download/v${VERSION}/matterbridge-linux-arm /bin/matterbridge
|
||||||
|
RUN chmod +x /bin/matterbridge
|
||||||
ENTRYPOINT ["/bin/matterbridge"]
|
ENTRYPOINT ["/bin/matterbridge"]
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
//go:build !noharmony
|
|
||||||
// +build !noharmony
|
|
||||||
|
|
||||||
package bridgemap
|
|
||||||
|
|
||||||
import (
|
|
||||||
bharmony "github.com/42wim/matterbridge/bridge/harmony"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
FullMap["harmony"] = bharmony.New
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
// +build !novk
|
|
||||||
|
|
||||||
package bridgemap
|
|
||||||
|
|
||||||
import (
|
|
||||||
bvk "github.com/42wim/matterbridge/bridge/vk"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
FullMap["vk"] = bvk.New
|
|
||||||
}
|
|
||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
"github.com/d5/tengo/v2"
|
"github.com/d5/tengo/v2"
|
||||||
"github.com/d5/tengo/v2/stdlib"
|
"github.com/d5/tengo/v2/stdlib"
|
||||||
lru "github.com/hashicorp/golang-lru"
|
lru "github.com/hashicorp/golang-lru"
|
||||||
"github.com/kyokomi/emoji/v2"
|
"github.com/matterbridge/emoji"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ func New(rootLogger *logrus.Logger, cfg *config.Gateway, r *Router) *Gateway {
|
|||||||
func (gw *Gateway) FindCanonicalMsgID(protocol string, mID string) string {
|
func (gw *Gateway) FindCanonicalMsgID(protocol string, mID string) string {
|
||||||
ID := protocol + " " + mID
|
ID := protocol + " " + mID
|
||||||
if gw.Messages.Contains(ID) {
|
if gw.Messages.Contains(ID) {
|
||||||
return ID
|
return mID
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not keyed, iterate through cache for downstream, and infer upstream.
|
// If not keyed, iterate through cache for downstream, and infer upstream.
|
||||||
@@ -75,7 +75,7 @@ func (gw *Gateway) FindCanonicalMsgID(protocol string, mID string) string {
|
|||||||
ids := v.([]*BrMsgID)
|
ids := v.([]*BrMsgID)
|
||||||
for _, downstreamMsgObj := range ids {
|
for _, downstreamMsgObj := range ids {
|
||||||
if ID == downstreamMsgObj.ID {
|
if ID == downstreamMsgObj.ID {
|
||||||
return mid.(string)
|
return strings.Replace(mid.(string), protocol+" ", "", 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,7 +127,7 @@ func (gw *Gateway) AddConfig(cfg *config.Gateway) error {
|
|||||||
gw.logger.Errorf("mapChannels() failed: %s", err)
|
gw.logger.Errorf("mapChannels() failed: %s", err)
|
||||||
}
|
}
|
||||||
for _, br := range append(gw.MyConfig.In, append(gw.MyConfig.InOut, gw.MyConfig.Out...)...) {
|
for _, br := range append(gw.MyConfig.In, append(gw.MyConfig.InOut, gw.MyConfig.Out...)...) {
|
||||||
br := br // scopelint
|
br := br //scopelint
|
||||||
err := gw.AddBridge(&br)
|
err := gw.AddBridge(&br)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -337,21 +337,20 @@ func (gw *Gateway) modifyUsername(msg *config.Message, dest *bridge.Bridge) stri
|
|||||||
}
|
}
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
nick = strings.ReplaceAll(nick, "{NOPINGNICK}", msg.Username[:i]+"\u200b"+msg.Username[i:])
|
nick = strings.Replace(nick, "{NOPINGNICK}", msg.Username[:i]+""+msg.Username[i:], -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
nick = strings.ReplaceAll(nick, "{BRIDGE}", br.Name)
|
nick = strings.Replace(nick, "{BRIDGE}", br.Name, -1)
|
||||||
nick = strings.ReplaceAll(nick, "{PROTOCOL}", br.Protocol)
|
nick = strings.Replace(nick, "{PROTOCOL}", br.Protocol, -1)
|
||||||
nick = strings.ReplaceAll(nick, "{GATEWAY}", gw.Name)
|
nick = strings.Replace(nick, "{GATEWAY}", gw.Name, -1)
|
||||||
nick = strings.ReplaceAll(nick, "{LABEL}", br.GetString("Label"))
|
nick = strings.Replace(nick, "{LABEL}", br.GetString("Label"), -1)
|
||||||
nick = strings.ReplaceAll(nick, "{NICK}", msg.Username)
|
nick = strings.Replace(nick, "{NICK}", msg.Username, -1)
|
||||||
nick = strings.ReplaceAll(nick, "{USERID}", msg.UserID)
|
nick = strings.Replace(nick, "{CHANNEL}", msg.Channel, -1)
|
||||||
nick = strings.ReplaceAll(nick, "{CHANNEL}", msg.Channel)
|
|
||||||
tengoNick, err := gw.modifyUsernameTengo(msg, br)
|
tengoNick, err := gw.modifyUsernameTengo(msg, br)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
gw.logger.Errorf("modifyUsernameTengo error: %s", err)
|
gw.logger.Errorf("modifyUsernameTengo error: %s", err)
|
||||||
}
|
}
|
||||||
nick = strings.ReplaceAll(nick, "{TENGO}", tengoNick)
|
nick = strings.Replace(nick, "{TENGO}", tengoNick, -1) //nolint:gocritic
|
||||||
return nick
|
return nick
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,7 +385,6 @@ func (gw *Gateway) modifyMessage(msg *config.Message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// replace :emoji: to unicode
|
// replace :emoji: to unicode
|
||||||
emoji.ReplacePadding = ""
|
|
||||||
msg.Text = emoji.Sprint(msg.Text)
|
msg.Text = emoji.Sprint(msg.Text)
|
||||||
|
|
||||||
br := gw.Bridges[msg.Account]
|
br := gw.Bridges[msg.Account]
|
||||||
@@ -447,25 +445,22 @@ func (gw *Gateway) SendMessage(
|
|||||||
msg.Avatar = gw.modifyAvatar(rmsg, dest)
|
msg.Avatar = gw.modifyAvatar(rmsg, dest)
|
||||||
msg.Username = gw.modifyUsername(rmsg, dest)
|
msg.Username = gw.modifyUsername(rmsg, dest)
|
||||||
|
|
||||||
// exclude file delete event as the msg ID here is the native file ID that needs to be deleted
|
msg.ID = gw.getDestMsgID(rmsg.Protocol+" "+rmsg.ID, dest, channel)
|
||||||
if msg.Event != config.EventFileDelete {
|
|
||||||
msg.ID = gw.getDestMsgID(rmsg.Protocol+" "+rmsg.ID, dest, channel)
|
|
||||||
}
|
|
||||||
|
|
||||||
// for api we need originchannel as channel
|
// for api we need originchannel as channel
|
||||||
if dest.Protocol == apiProtocol {
|
if dest.Protocol == apiProtocol {
|
||||||
msg.Channel = rmsg.Channel
|
msg.Channel = rmsg.Channel
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.ParentID = gw.getDestMsgID(canonicalParentMsgID, dest, channel)
|
msg.ParentID = gw.getDestMsgID(rmsg.Protocol+" "+canonicalParentMsgID, dest, channel)
|
||||||
if msg.ParentID == "" {
|
if msg.ParentID == "" {
|
||||||
msg.ParentID = strings.Replace(canonicalParentMsgID, dest.Protocol+" ", "", 1)
|
msg.ParentID = canonicalParentMsgID
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the parentID is still empty and we have a parentID set in the original message
|
// if the parentID is still empty and we have a parentID set in the original message
|
||||||
// this means that we didn't find it in the cache so set it to a "msg-parent-not-found" constant
|
// this means that we didn't find it in the cache so set it "msg-parent-not-found"
|
||||||
if msg.ParentID == "" && rmsg.ParentID != "" {
|
if msg.ParentID == "" && rmsg.ParentID != "" {
|
||||||
msg.ParentID = config.ParentIDNotFound
|
msg.ParentID = "msg-parent-not-found"
|
||||||
}
|
}
|
||||||
|
|
||||||
drop, err := gw.modifyOutMessageTengo(rmsg, &msg, dest)
|
drop, err := gw.modifyOutMessageTengo(rmsg, &msg, dest)
|
||||||
@@ -500,7 +495,7 @@ func (gw *Gateway) SendMessage(
|
|||||||
if mID != "" {
|
if mID != "" {
|
||||||
gw.logger.Debugf("mID %s: %s", dest.Account, mID)
|
gw.logger.Debugf("mID %s: %s", dest.Account, mID)
|
||||||
return mID, nil
|
return mID, nil
|
||||||
// brMsgIDs = append(brMsgIDs, &BrMsgID{dest, dest.Protocol + " " + mID, channel.ID})
|
//brMsgIDs = append(brMsgIDs, &BrMsgID{dest, dest.Protocol + " " + mID, channel.ID})
|
||||||
}
|
}
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
@@ -554,7 +549,6 @@ func modifyInMessageTengo(filename string, msg *config.Message) error {
|
|||||||
s.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...))
|
s.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...))
|
||||||
_ = s.Add("msgText", msg.Text)
|
_ = s.Add("msgText", msg.Text)
|
||||||
_ = s.Add("msgUsername", msg.Username)
|
_ = s.Add("msgUsername", msg.Username)
|
||||||
_ = s.Add("msgUserID", msg.UserID)
|
|
||||||
_ = s.Add("msgAccount", msg.Account)
|
_ = s.Add("msgAccount", msg.Account)
|
||||||
_ = s.Add("msgChannel", msg.Channel)
|
_ = s.Add("msgChannel", msg.Channel)
|
||||||
c, err := s.Compile()
|
c, err := s.Compile()
|
||||||
@@ -583,7 +577,6 @@ func (gw *Gateway) modifyUsernameTengo(msg *config.Message, br *bridge.Bridge) (
|
|||||||
_ = s.Add("result", "")
|
_ = s.Add("result", "")
|
||||||
_ = s.Add("msgText", msg.Text)
|
_ = s.Add("msgText", msg.Text)
|
||||||
_ = s.Add("msgUsername", msg.Username)
|
_ = s.Add("msgUsername", msg.Username)
|
||||||
_ = s.Add("msgUserID", msg.UserID)
|
|
||||||
_ = s.Add("nick", msg.Username)
|
_ = s.Add("nick", msg.Username)
|
||||||
_ = s.Add("msgAccount", msg.Account)
|
_ = s.Add("msgAccount", msg.Account)
|
||||||
_ = s.Add("msgChannel", msg.Channel)
|
_ = s.Add("msgChannel", msg.Channel)
|
||||||
@@ -638,7 +631,6 @@ func (gw *Gateway) modifyOutMessageTengo(origmsg *config.Message, msg *config.Me
|
|||||||
_ = s.Add("outEvent", msg.Event)
|
_ = s.Add("outEvent", msg.Event)
|
||||||
_ = s.Add("msgText", msg.Text)
|
_ = s.Add("msgText", msg.Text)
|
||||||
_ = s.Add("msgUsername", msg.Username)
|
_ = s.Add("msgUsername", msg.Username)
|
||||||
_ = s.Add("msgUserID", msg.UserID)
|
|
||||||
_ = s.Add("msgDrop", drop)
|
_ = s.Add("msgDrop", drop)
|
||||||
c, err := s.Compile()
|
c, err := s.Compile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -110,9 +110,7 @@ func (r *Router) disableBridge(br *bridge.Bridge, err error) bool {
|
|||||||
if r.BridgeValues().General.IgnoreFailureOnStart {
|
if r.BridgeValues().General.IgnoreFailureOnStart {
|
||||||
r.logger.Error(err)
|
r.logger.Error(err)
|
||||||
// setting this bridge empty
|
// setting this bridge empty
|
||||||
*br = bridge.Bridge{
|
*br = bridge.Bridge{}
|
||||||
Log: br.Log,
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -162,7 +160,7 @@ func (r *Router) handleReceive() {
|
|||||||
// For some bridges we always add/update the message ID.
|
// For some bridges we always add/update the message ID.
|
||||||
// This is necessary as msgIDs will change if a bridge returns
|
// This is necessary as msgIDs will change if a bridge returns
|
||||||
// a different ID in response to edits.
|
// a different ID in response to edits.
|
||||||
if !exists {
|
if !exists || msg.Protocol == "discord" {
|
||||||
gw.Messages.Add(msg.Protocol+" "+msg.ID, msgIDs)
|
gw.Messages.Add(msg.Protocol+" "+msg.ID, msgIDs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
156
go.mod
@@ -3,138 +3,56 @@ module github.com/42wim/matterbridge
|
|||||||
require (
|
require (
|
||||||
github.com/42wim/go-gitter v0.0.0-20170828205020-017310c2d557
|
github.com/42wim/go-gitter v0.0.0-20170828205020-017310c2d557
|
||||||
github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f
|
github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f
|
||||||
github.com/Benau/tgsconverter v0.0.0-20210809170556-99f4a4f6337f
|
github.com/Jeffail/gabs v1.1.1 // indirect
|
||||||
github.com/Philipp15b/go-steam v1.0.1-0.20200727090957-6ae9b3c0a560
|
github.com/Philipp15b/go-steam v1.0.1-0.20200727090957-6ae9b3c0a560
|
||||||
github.com/Rhymen/go-whatsapp v0.1.2-0.20211102134409-31a2e740845c
|
github.com/Rhymen/go-whatsapp v0.1.2-0.20201122130733-6e5488ac98df
|
||||||
github.com/SevereCloud/vksdk/v2 v2.13.1
|
github.com/d5/tengo/v2 v2.6.2
|
||||||
github.com/bwmarrin/discordgo v0.24.0
|
|
||||||
github.com/d5/tengo/v2 v2.10.1
|
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/fsnotify/fsnotify v1.5.1
|
github.com/fsnotify/fsnotify v1.4.9
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
github.com/go-telegram-bot-api/telegram-bot-api v1.0.1-0.20200524105306-7434b0456e81
|
||||||
github.com/gomarkdown/markdown v0.0.0-20220310201231-552c6011c0b8
|
github.com/gomarkdown/markdown v0.0.0-20201113031856-722100d81a8e
|
||||||
github.com/google/gops v0.3.22
|
github.com/google/gops v0.3.13
|
||||||
|
github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4 // indirect
|
||||||
github.com/gorilla/schema v1.2.0
|
github.com/gorilla/schema v1.2.0
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.4.2
|
||||||
github.com/harmony-development/shibshib v0.0.0-20220101224523-c98059d09cfa
|
|
||||||
github.com/hashicorp/golang-lru v0.5.4
|
github.com/hashicorp/golang-lru v0.5.4
|
||||||
github.com/jpillora/backoff v1.0.0
|
github.com/jpillora/backoff v1.0.0
|
||||||
github.com/keybase/go-keybase-chat-bot v0.0.0-20211201215354-ee4b23828b55
|
github.com/keybase/go-keybase-chat-bot v0.0.0-20200505163032-5cacf52379da
|
||||||
github.com/kyokomi/emoji/v2 v2.2.9
|
github.com/labstack/echo/v4 v4.1.17
|
||||||
github.com/labstack/echo/v4 v4.7.0
|
github.com/lrstanley/girc v0.0.0-20190801035559-4fc93959e1a7
|
||||||
github.com/lrstanley/girc v0.0.0-20211023233735-147f0ff77566
|
github.com/matrix-org/gomatrix v0.0.0-20200827122206-7dd5e2a05bcd
|
||||||
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16
|
github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20200411204219-d5c18ce75048
|
||||||
github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20211016222428-79310a412696
|
github.com/matterbridge/discordgo v0.22.1
|
||||||
github.com/matterbridge/go-xmpp v0.0.0-20211030125215-791a06c5f1be
|
github.com/matterbridge/emoji v2.1.1-0.20191117213217-af507f6b02db+incompatible
|
||||||
github.com/matterbridge/gozulipbot v0.0.0-20211023205727-a19d6c1f3b75
|
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/matterbridge/logrus-prefixed-formatter v0.5.3-0.20200523233437-d971309a77ba
|
||||||
github.com/matterbridge/matterclient v0.0.0-20211107234719-faca3cd42315
|
github.com/mattermost/mattermost-server/v5 v5.29.0
|
||||||
github.com/mattermost/mattermost-server/v5 v5.39.3
|
github.com/mattn/godown v0.0.0-20201027140031-2c7783b24de7
|
||||||
github.com/mattermost/mattermost-server/v6 v6.4.2
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||||
github.com/mattn/godown v0.0.1
|
github.com/missdeer/golib v1.0.4
|
||||||
|
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474 // indirect
|
||||||
|
github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff // indirect
|
||||||
github.com/nelsonken/gomf v0.0.0-20180504123937-a9dd2f9deae9
|
github.com/nelsonken/gomf v0.0.0-20180504123937-a9dd2f9deae9
|
||||||
github.com/paulrosania/go-charset v0.0.0-20190326053356-55c9d7a5834c
|
github.com/paulrosania/go-charset v0.0.0-20190326053356-55c9d7a5834c
|
||||||
github.com/rs/xid v1.3.0
|
github.com/rs/xid v1.2.1
|
||||||
github.com/russross/blackfriday v1.6.0
|
github.com/russross/blackfriday v1.5.2
|
||||||
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca
|
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca
|
||||||
github.com/shazow/ssh-chat v1.10.1
|
github.com/shazow/ssh-chat v1.10.1
|
||||||
github.com/sirupsen/logrus v1.8.1
|
github.com/sirupsen/logrus v1.7.0
|
||||||
github.com/slack-go/slack v0.10.2
|
github.com/slack-go/slack v0.7.2
|
||||||
github.com/spf13/viper v1.10.1
|
github.com/spf13/viper v1.7.1
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.6.1
|
||||||
github.com/vincent-petithory/dataurl v1.0.0
|
github.com/vincent-petithory/dataurl v0.0.0-20191104211930-d1553a71de50
|
||||||
github.com/writeas/go-strip-markdown v2.0.1+incompatible
|
github.com/writeas/go-strip-markdown v2.0.1+incompatible
|
||||||
|
github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect
|
||||||
github.com/yaegashi/msgraph.go v0.1.4
|
github.com/yaegashi/msgraph.go v0.1.4
|
||||||
github.com/zfjagann/golang-ring v0.0.0-20210116075443-7c86fdb43134
|
github.com/zfjagann/golang-ring v0.0.0-20190304061218-d34796e0a6c2
|
||||||
golang.org/x/image v0.0.0-20220302094943-723b81ca9867
|
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5
|
||||||
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a
|
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58
|
||||||
golang.org/x/text v0.3.7
|
gomod.garykim.dev/nc-talk v0.1.5
|
||||||
gomod.garykim.dev/nc-talk v0.3.0
|
|
||||||
gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376
|
gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376
|
||||||
layeh.com/gumble v0.0.0-20200818122324-146f9205029b
|
layeh.com/gumble v0.0.0-20200818122324-146f9205029b
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
go 1.15
|
||||||
github.com/Benau/go_rlottie v0.0.0-20210807002906-98c1b2421989 // indirect
|
|
||||||
github.com/Jeffail/gabs v1.4.0 // indirect
|
|
||||||
github.com/apex/log v1.9.0 // indirect
|
|
||||||
github.com/av-elier/go-decimal-to-rational v0.0.0-20191127152832-89e6aad02ecf // indirect
|
|
||||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
|
||||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
|
||||||
github.com/dyatlov/go-opengraph v0.0.0-20210112100619-dae8665a5b09 // indirect
|
|
||||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.3 // indirect
|
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
|
||||||
github.com/gopackage/ddp v0.0.3 // indirect
|
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
|
||||||
github.com/kettek/apng v0.0.0-20191108220231-414630eed80f // indirect
|
|
||||||
github.com/klauspost/compress v1.14.2 // indirect
|
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
|
|
||||||
github.com/labstack/gommon v0.3.1 // indirect
|
|
||||||
github.com/magiconair/properties v1.8.5 // indirect
|
|
||||||
github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 // indirect
|
|
||||||
github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d // indirect
|
|
||||||
github.com/mattermost/logr v1.0.13 // indirect
|
|
||||||
github.com/mattermost/logr/v2 v2.0.15 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
|
||||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
|
||||||
github.com/minio/md5-simd v1.1.2 // indirect
|
|
||||||
github.com/minio/minio-go/v7 v7.0.16 // indirect
|
|
||||||
github.com/minio/sha256-simd v1.0.0 // indirect
|
|
||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
|
||||||
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
|
||||||
github.com/monaco-io/request v1.0.5 // indirect
|
|
||||||
github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d // indirect
|
|
||||||
github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff // indirect
|
|
||||||
github.com/pborman/uuid v1.2.1 // indirect
|
|
||||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
|
||||||
github.com/philhofer/fwd v1.1.1 // indirect
|
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
github.com/rickb777/date v1.12.4 // indirect
|
|
||||||
github.com/rickb777/plural v1.2.0 // indirect
|
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
|
||||||
github.com/shazow/rateio v0.0.0-20200113175441-4461efc8bdc4 // indirect
|
|
||||||
github.com/sizeofint/webpanimation v0.0.0-20210809145948-1d2b32119882 // indirect
|
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
|
|
||||||
github.com/spf13/afero v1.6.0 // indirect
|
|
||||||
github.com/spf13/cast v1.4.1 // indirect
|
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
|
||||||
github.com/subosito/gotenv v1.2.0 // indirect
|
|
||||||
github.com/tinylib/msgp v1.1.6 // indirect
|
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
|
||||||
github.com/valyala/fasttemplate v1.2.1 // indirect
|
|
||||||
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
|
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
|
||||||
github.com/wiggin77/cfg v1.0.2 // indirect
|
|
||||||
github.com/wiggin77/merror v1.0.3 // indirect
|
|
||||||
github.com/wiggin77/srslog v1.0.1 // indirect
|
|
||||||
go.uber.org/atomic v1.9.0 // indirect
|
|
||||||
go.uber.org/multierr v1.7.0 // indirect
|
|
||||||
go.uber.org/zap v1.17.0 // indirect
|
|
||||||
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 // indirect
|
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
|
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
|
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
|
||||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect
|
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
|
||||||
google.golang.org/protobuf v1.27.1 // indirect
|
|
||||||
gopkg.in/ini.v1 v1.66.2 // indirect
|
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
|
||||||
)
|
|
||||||
|
|
||||||
replace github.com/matrix-org/gomatrix => github.com/matterbridge/gomatrix v0.0.0-20220205235239-607eb9ee6419
|
|
||||||
|
|
||||||
go 1.17
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Message for rocketchat outgoing webhook.
|
// Message for rocketchat outgoing webhook.
|
||||||
@@ -69,6 +68,7 @@ func (c *Client) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
msg := Message{}
|
msg := Message{}
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
log.Println(string(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
@@ -89,11 +89,7 @@ func (c *Client) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
msg.ChannelName = "#" + msg.ChannelName
|
msg.ChannelName = "#" + msg.ChannelName
|
||||||
if c.Token != "" {
|
if c.Token != "" {
|
||||||
if msg.Token != c.Token {
|
if msg.Token != c.Token {
|
||||||
if regexp.MustCompile(`[^a-zA-Z0-9]+`).MatchString(msg.Token) {
|
log.Println("invalid token " + msg.Token + " from " + r.RemoteAddr)
|
||||||
log.Println("invalid token " + msg.Token + " from " + r.RemoteAddr)
|
|
||||||
} else {
|
|
||||||
log.Println("invalid token from " + r.RemoteAddr)
|
|
||||||
}
|
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
img/slack-setup-add-scopes.png
Normal file
|
After Width: | Height: | Size: 270 KiB |
BIN
img/slack-setup-app-page.png
Normal file
|
After Width: | Height: | Size: 170 KiB |
BIN
img/slack-setup-create-app.png
Normal file
|
After Width: | Height: | Size: 282 KiB |
BIN
img/slack-setup-create-bot.png
Normal file
|
After Width: | Height: | Size: 204 KiB |
BIN
img/slack-setup-finished.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
img/slack-setup-install-app.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
img/slack-setup-invite-bot.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
@@ -10,13 +10,15 @@ import (
|
|||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/gateway"
|
"github.com/42wim/matterbridge/gateway"
|
||||||
"github.com/42wim/matterbridge/gateway/bridgemap"
|
"github.com/42wim/matterbridge/gateway/bridgemap"
|
||||||
"github.com/42wim/matterbridge/version"
|
|
||||||
"github.com/google/gops/agent"
|
"github.com/google/gops/agent"
|
||||||
prefixed "github.com/matterbridge/logrus-prefixed-formatter"
|
prefixed "github.com/matterbridge/logrus-prefixed-formatter"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
version = "1.20.1-dev"
|
||||||
|
githash string
|
||||||
|
|
||||||
flagConfig = flag.String("conf", "matterbridge.toml", "config file")
|
flagConfig = flag.String("conf", "matterbridge.toml", "config file")
|
||||||
flagDebug = flag.Bool("debug", false, "enable debug")
|
flagDebug = flag.Bool("debug", false, "enable debug")
|
||||||
flagVersion = flag.Bool("version", false, "show version")
|
flagVersion = flag.Bool("version", false, "show version")
|
||||||
@@ -26,7 +28,7 @@ var (
|
|||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if *flagVersion {
|
if *flagVersion {
|
||||||
fmt.Printf("version: %s %s\n", version.Release, version.GitHash)
|
fmt.Printf("version: %s %s\n", version, githash)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,8 +43,8 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Printf("Running version %s %s", version.Release, version.GitHash)
|
logger.Printf("Running version %s %s", version, githash)
|
||||||
if strings.Contains(version.Release, "-dev") {
|
if strings.Contains(version, "-dev") {
|
||||||
logger.Println("WARNING: THIS IS A DEVELOPMENT VERSION. Things may break.")
|
logger.Println("WARNING: THIS IS A DEVELOPMENT VERSION. Things may break.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,12 +9,12 @@
|
|||||||
[irc]
|
[irc]
|
||||||
|
|
||||||
#You can configure multiple servers "[irc.name]" or "[irc.name2]"
|
#You can configure multiple servers "[irc.name]" or "[irc.name2]"
|
||||||
#In this example we use [irc.libera]
|
#In this example we use [irc.freenode]
|
||||||
#REQUIRED
|
#REQUIRED
|
||||||
[irc.libera]
|
[irc.freenode]
|
||||||
#irc server to connect to.
|
#irc server to connect to.
|
||||||
#REQUIRED
|
#REQUIRED
|
||||||
Server="irc.libera.chat:6667"
|
Server="irc.freenode.net:6667"
|
||||||
|
|
||||||
#Password for irc server (if necessary)
|
#Password for irc server (if necessary)
|
||||||
#OPTIONAL (default "")
|
#OPTIONAL (default "")
|
||||||
@@ -24,14 +24,7 @@ Password=""
|
|||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
UseTLS=false
|
UseTLS=false
|
||||||
|
|
||||||
#Use client certificate - see CertFP https://libera.chat/guides/certfp.html
|
#Enable SASL (PLAIN) authentication. (freenode requires this from eg AWS hosts)
|
||||||
#Specify filename which contains private key and cert
|
|
||||||
#OPTIONAL (default "")
|
|
||||||
#
|
|
||||||
#TLSClientCertificate="cert.pem"
|
|
||||||
TLSClientCertificate=""
|
|
||||||
|
|
||||||
#Enable SASL (PLAIN) authentication. (libera requires this from eg AWS hosts)
|
|
||||||
#It uses NickServNick and NickServPassword as login and password
|
#It uses NickServNick and NickServPassword as login and password
|
||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
UseSASL=false
|
UseSASL=false
|
||||||
@@ -41,11 +34,6 @@ UseSASL=false
|
|||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
SkipTLSVerify=true
|
SkipTLSVerify=true
|
||||||
|
|
||||||
#Local address to use for server connection
|
|
||||||
#Note that Server and Bind must resolve to addresses of the same family.
|
|
||||||
#OPTIONAL (default "")
|
|
||||||
Bind=""
|
|
||||||
|
|
||||||
#If you know your charset, you can specify it manually.
|
#If you know your charset, you can specify it manually.
|
||||||
#Otherwise it tries to detect this automatically. Select one below
|
#Otherwise it tries to detect this automatically. Select one below
|
||||||
# "iso-8859-2:1987", "iso-8859-9:1989", "866", "latin9", "iso-8859-10:1992", "iso-ir-109", "hebrew",
|
# "iso-8859-2:1987", "iso-8859-9:1989", "866", "latin9", "iso-8859-10:1992", "iso-ir-109", "hebrew",
|
||||||
@@ -67,15 +55,7 @@ Charset=""
|
|||||||
#REQUIRED
|
#REQUIRED
|
||||||
Nick="matterbot"
|
Nick="matterbot"
|
||||||
|
|
||||||
#Real name/gecos displayed in e.g. /WHOIS and /WHO
|
#If you registered your bot with a service like Nickserv on freenode.
|
||||||
#OPTIONAL (defaults to the nick)
|
|
||||||
RealName="Matterbridge instance on IRC"
|
|
||||||
|
|
||||||
#IRC username/ident preceding the hostname in hostmasks and /WHOIS
|
|
||||||
#OPTIONAL (defaults to the nick)
|
|
||||||
UserName="bridge"
|
|
||||||
|
|
||||||
#If you registered your bot with a service like Nickserv on libera.
|
|
||||||
#Also being used when UseSASL=true
|
#Also being used when UseSASL=true
|
||||||
#
|
#
|
||||||
#Note: if you want do to quakenet auth, set NickServNick="Q@CServe.quakenet.org"
|
#Note: if you want do to quakenet auth, set NickServNick="Q@CServe.quakenet.org"
|
||||||
@@ -96,24 +76,20 @@ MessageDelay=1300
|
|||||||
|
|
||||||
#Maximum amount of messages to hold in queue. If queue is full
|
#Maximum amount of messages to hold in queue. If queue is full
|
||||||
#messages will be dropped.
|
#messages will be dropped.
|
||||||
#<clipped message> will be add to the message that fills the queue.
|
#<message clipped> will be add to the message that fills the queue.
|
||||||
#OPTIONAL (default 30)
|
#OPTIONAL (default 30)
|
||||||
MessageQueue=30
|
MessageQueue=30
|
||||||
|
|
||||||
#Maximum length of message sent to irc server. If it exceeds
|
#Maximum length of message sent to irc server. If it exceeds
|
||||||
#<clipped message> will be add to the message.
|
#<message clipped> will be add to the message.
|
||||||
#OPTIONAL (default 400)
|
#OPTIONAL (default 400)
|
||||||
MessageLength=400
|
MessageLength=400
|
||||||
|
|
||||||
#Split messages on MessageLength instead of showing the <clipped message>
|
#Split messages on MessageLength instead of showing the <message clipped>
|
||||||
#WARNING: this could lead to flooding
|
#WARNING: this could lead to flooding
|
||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
MessageSplit=false
|
MessageSplit=false
|
||||||
|
|
||||||
#Message to show when a message is too big
|
|
||||||
#Default "<clipped message>"
|
|
||||||
MessageClipped="<clipped message>"
|
|
||||||
|
|
||||||
#Delay in seconds to rejoin a channel when kicked
|
#Delay in seconds to rejoin a channel when kicked
|
||||||
#OPTIONAL (default 0)
|
#OPTIONAL (default 0)
|
||||||
RejoinDelay=0
|
RejoinDelay=0
|
||||||
@@ -217,19 +193,6 @@ ShowTopicChange=false
|
|||||||
#OPTIONAL (default 0)
|
#OPTIONAL (default 0)
|
||||||
JoinDelay=0
|
JoinDelay=0
|
||||||
|
|
||||||
#Use the optional RELAYMSG extension for username spoofing on IRC.
|
|
||||||
#This requires an IRCd that supports the draft/relaymsg specification: currently this includes
|
|
||||||
#Oragono 2.4.0+ and InspIRCd 3 with the m_relaymsg contrib module.
|
|
||||||
#See https://github.com/42wim/matterbridge/issues/667#issuecomment-634214165 for more details.
|
|
||||||
#Spoofed nicks will use the configured RemoteNickFormat, replacing reserved IRC characters
|
|
||||||
#(!+%@&#$:'"?*,.) with a hyphen (-).
|
|
||||||
#On most configurations, the RemoteNickFormat must include a separator character such as "/".
|
|
||||||
#You should make sure that the settings here match your IRCd.
|
|
||||||
#This option overrides ColorNicks.
|
|
||||||
#OPTIONAL (default false)
|
|
||||||
UseRelayMsg=false
|
|
||||||
#RemoteNickFormat="{NICK}/{PROTOCOL}"
|
|
||||||
|
|
||||||
###################################################################
|
###################################################################
|
||||||
#XMPP section
|
#XMPP section
|
||||||
###################################################################
|
###################################################################
|
||||||
@@ -243,16 +206,12 @@ UseRelayMsg=false
|
|||||||
#REQUIRED
|
#REQUIRED
|
||||||
Server="jabber.example.com:5222"
|
Server="jabber.example.com:5222"
|
||||||
|
|
||||||
#Use anonymous MUC login
|
|
||||||
#OPTIONAL (default false)
|
|
||||||
Anonymous=false
|
|
||||||
|
|
||||||
#Jid
|
#Jid
|
||||||
#REQUIRED if Anonymous=false
|
#REQUIRED
|
||||||
Jid="user@example.com"
|
Jid="user@example.com"
|
||||||
|
|
||||||
#Password
|
#Password
|
||||||
#REQUIRED if Anonymous=false
|
#REQUIRED
|
||||||
Password="yourpass"
|
Password="yourpass"
|
||||||
|
|
||||||
#MUC
|
#MUC
|
||||||
@@ -338,11 +297,6 @@ StripNick=false
|
|||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
ShowTopicChange=false
|
ShowTopicChange=false
|
||||||
|
|
||||||
#Enable sending messages using a webhook instead of regular MUC messages.
|
|
||||||
#Only works with a prosody server using mod_slack_webhook. Does not support editing.
|
|
||||||
#OPTIONAL (default "")
|
|
||||||
WebhookURL="https://yourdomain/prosody/msg/someid"
|
|
||||||
|
|
||||||
###################################################################
|
###################################################################
|
||||||
#mattermost section
|
#mattermost section
|
||||||
###################################################################
|
###################################################################
|
||||||
@@ -408,10 +362,6 @@ SkipTLSVerify=true
|
|||||||
## RELOADABLE SETTINGS
|
## RELOADABLE SETTINGS
|
||||||
## Settings below can be reloaded by editing the file
|
## Settings below can be reloaded by editing the file
|
||||||
|
|
||||||
# UseUserName shows the username instead of the server nickname
|
|
||||||
# OPTIONAL (default false)
|
|
||||||
UseUserName=false
|
|
||||||
|
|
||||||
#how to format the list of IRC nicks when displayed in mattermost.
|
#how to format the list of IRC nicks when displayed in mattermost.
|
||||||
#Possible options are "table" and "plain"
|
#Possible options are "table" and "plain"
|
||||||
#OPTIONAL (default plain)
|
#OPTIONAL (default plain)
|
||||||
@@ -858,10 +808,6 @@ PreserveThreading=false
|
|||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
ShowUserTyping=false
|
ShowUserTyping=false
|
||||||
|
|
||||||
#Message to show when a message is too big
|
|
||||||
#Default "<clipped message>"
|
|
||||||
MessageClipped="<clipped message>"
|
|
||||||
|
|
||||||
###################################################################
|
###################################################################
|
||||||
#discord section
|
#discord section
|
||||||
###################################################################
|
###################################################################
|
||||||
@@ -884,14 +830,6 @@ Server="yourservername"
|
|||||||
## All settings below can be reloaded by editing the file.
|
## All settings below can be reloaded by editing the file.
|
||||||
## They are also all optional.
|
## They are also all optional.
|
||||||
|
|
||||||
# AllowMention controls which mentions are allowed. If not specified, all mentions are allowed.
|
|
||||||
# Note that even when a mention is not allowed, it will still be displayed nicely and be clickable. It just prevents the ping/notification.
|
|
||||||
#
|
|
||||||
# "everyone" allows @everyone and @here mentions
|
|
||||||
# "roles" allows @role mentions
|
|
||||||
# "users" allows @user mentions
|
|
||||||
AllowMention=["everyone", "roles", "users"]
|
|
||||||
|
|
||||||
# ShowEmbeds shows the title, description and URL of embedded messages (sent by other bots)
|
# ShowEmbeds shows the title, description and URL of embedded messages (sent by other bots)
|
||||||
ShowEmbeds=false
|
ShowEmbeds=false
|
||||||
|
|
||||||
@@ -908,11 +846,10 @@ UseUserName=false
|
|||||||
# UseDiscriminator appends the `#xxxx` discriminator when used with UseUserName
|
# UseDiscriminator appends the `#xxxx` discriminator when used with UseUserName
|
||||||
UseDiscriminator=false
|
UseDiscriminator=false
|
||||||
|
|
||||||
# AutoWebhooks automatically configures message sending in the style of puppets.
|
# WebhookURL sends messages in the style of puppets.
|
||||||
# This is an easier alternative to manually configuring "WebhookURL" for each gateway,
|
# 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
|
||||||
# as turning this on will automatically load or create webhooks for each channel.
|
# Example: "https://discordapp.com/api/webhooks/1234/abcd_xyzw"
|
||||||
# This feature requires the "Manage Webhooks" permission (either globally or as per-channel).
|
WebhookURL=""
|
||||||
AutoWebhooks=false
|
|
||||||
|
|
||||||
# EditDisable disables sending of edits to other bridges
|
# EditDisable disables sending of edits to other bridges
|
||||||
EditDisable=false
|
EditDisable=false
|
||||||
@@ -997,10 +934,6 @@ ShowTopicChange=false
|
|||||||
# Supported from the following bridges: slack
|
# Supported from the following bridges: slack
|
||||||
SyncTopic=false
|
SyncTopic=false
|
||||||
|
|
||||||
#Message to show when a message is too big
|
|
||||||
#Default "<clipped message>"
|
|
||||||
MessageClipped="<clipped message>"
|
|
||||||
|
|
||||||
###################################################################
|
###################################################################
|
||||||
#telegram section
|
#telegram section
|
||||||
###################################################################
|
###################################################################
|
||||||
@@ -1059,13 +992,6 @@ QuoteFormat="{MESSAGE} (re @{QUOTENICK}: {QUOTEMESSAGE})"
|
|||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
MediaConvertWebPToPNG=false
|
MediaConvertWebPToPNG=false
|
||||||
|
|
||||||
#Convert Tgs (Telegram animated sticker) images to PNG before upload.
|
|
||||||
#This is useful when your bridge also contains platforms that do not support animated WebP files, like Discord.
|
|
||||||
#This requires the external dependency `lottie`, which can be installed like this:
|
|
||||||
#`pip install lottie cairosvg`
|
|
||||||
#https://github.com/42wim/matterbridge/issues/874
|
|
||||||
#MediaConvertTgs="png"
|
|
||||||
|
|
||||||
#Disable sending of edits to other bridges
|
#Disable sending of edits to other bridges
|
||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
EditDisable=false
|
EditDisable=false
|
||||||
@@ -1285,16 +1211,12 @@ ShowTopicChange=false
|
|||||||
#REQUIRED
|
#REQUIRED
|
||||||
Server="https://matrix.org"
|
Server="https://matrix.org"
|
||||||
|
|
||||||
#Authentication for your bot.
|
#login/pass of your bot.
|
||||||
#You can use either login/password OR mxid/token. The latter will be preferred if found.
|
|
||||||
#Use a dedicated user for this and not your own!
|
#Use a dedicated user for this and not your own!
|
||||||
#Messages sent from this user will not be relayed to avoid loops.
|
#Messages sent from this user will not be relayed to avoid loops.
|
||||||
#REQUIRED
|
#REQUIRED
|
||||||
Login="yourlogin"
|
Login="yourlogin"
|
||||||
Password="yourpass"
|
Password="yourpass"
|
||||||
#OR
|
|
||||||
MxID="@yourlogin:domain.tld"
|
|
||||||
Token="tokenforthebotuser"
|
|
||||||
|
|
||||||
#Whether to send the homeserver suffix. eg ":matrix.org" in @username:matrix.org
|
#Whether to send the homeserver suffix. eg ":matrix.org" in @username:matrix.org
|
||||||
#to other bridges, or only send "username".(true only sends username)
|
#to other bridges, or only send "username".(true only sends username)
|
||||||
@@ -1312,6 +1234,13 @@ HTMLDisable=false
|
|||||||
# UseUserName shows the username instead of the server nickname
|
# UseUserName shows the username instead of the server nickname
|
||||||
UseUserName=false
|
UseUserName=false
|
||||||
|
|
||||||
|
#Whether to prefix messages from other bridges to matrix with the sender's nick.
|
||||||
|
#Useful if username overrides for incoming webhooks isn't enabled on the
|
||||||
|
#matrix server. If you set PrefixMessagesWithNick to true, each message
|
||||||
|
#from bridge to matrix will by default be prefixed by the RemoteNickFormat setting. i
|
||||||
|
#OPTIONAL (default false)
|
||||||
|
PrefixMessagesWithNick=false
|
||||||
|
|
||||||
#Nicks you want to ignore.
|
#Nicks you want to ignore.
|
||||||
#Regular expressions supported
|
#Regular expressions supported
|
||||||
#Messages from those users will not be sent to other bridges.
|
#Messages from those users will not be sent to other bridges.
|
||||||
@@ -1468,7 +1397,9 @@ StripNick=false
|
|||||||
ShowTopicChange=false
|
ShowTopicChange=false
|
||||||
|
|
||||||
###################################################################
|
###################################################################
|
||||||
|
#
|
||||||
# NCTalk (Nextcloud Talk)
|
# NCTalk (Nextcloud Talk)
|
||||||
|
#
|
||||||
###################################################################
|
###################################################################
|
||||||
|
|
||||||
[nctalk.bridge]
|
[nctalk.bridge]
|
||||||
@@ -1490,11 +1421,10 @@ Password = "talkuserpass"
|
|||||||
# Suffix for Guest Users
|
# Suffix for Guest Users
|
||||||
GuestSuffix = " (Guest)"
|
GuestSuffix = " (Guest)"
|
||||||
|
|
||||||
# Separate display name (Note: needs to be configured from Nextcloud Talk to work)
|
|
||||||
SeparateDisplayName=false
|
|
||||||
|
|
||||||
###################################################################
|
###################################################################
|
||||||
|
#
|
||||||
# Mumble
|
# Mumble
|
||||||
|
#
|
||||||
###################################################################
|
###################################################################
|
||||||
|
|
||||||
[mumble.bridge]
|
[mumble.bridge]
|
||||||
@@ -1537,21 +1467,10 @@ TLSCACertificate=mumble-ca.crt
|
|||||||
# OPTIONAL (default false)
|
# OPTIONAL (default false)
|
||||||
SkipTLSVerify=false
|
SkipTLSVerify=false
|
||||||
|
|
||||||
#Message to show when a message is too big
|
|
||||||
#Default "<clipped message>"
|
|
||||||
MessageClipped="<clipped message>"
|
|
||||||
|
|
||||||
###################################################################
|
|
||||||
#VK
|
|
||||||
###################################################################
|
###################################################################
|
||||||
#
|
#
|
||||||
[vk.myvk]
|
|
||||||
#Group access token
|
|
||||||
#See https://vk.com/dev/bots_docs
|
|
||||||
Token="Yourtokenhere"
|
|
||||||
|
|
||||||
###################################################################
|
|
||||||
# WhatsApp
|
# WhatsApp
|
||||||
|
#
|
||||||
###################################################################
|
###################################################################
|
||||||
|
|
||||||
[whatsapp.bridge]
|
[whatsapp.bridge]
|
||||||
@@ -1578,7 +1497,9 @@ Label="Organization"
|
|||||||
|
|
||||||
|
|
||||||
###################################################################
|
###################################################################
|
||||||
|
#
|
||||||
# zulip
|
# zulip
|
||||||
|
#
|
||||||
###################################################################
|
###################################################################
|
||||||
|
|
||||||
[zulip]
|
[zulip]
|
||||||
@@ -1667,18 +1588,6 @@ StripNick=false
|
|||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
ShowTopicChange=false
|
ShowTopicChange=false
|
||||||
|
|
||||||
###################################################################
|
|
||||||
# Harmony
|
|
||||||
###################################################################
|
|
||||||
|
|
||||||
[harmony.chat_harmonyapp_io]
|
|
||||||
Homeserver = "https://chat.harmonyapp.io:2289"
|
|
||||||
Token = "your token goes here"
|
|
||||||
UserID = "user id of the bot account"
|
|
||||||
Community = "community id that channels will be located in"
|
|
||||||
UseUserName = true
|
|
||||||
RemoteNickFormat = "{NICK}"
|
|
||||||
|
|
||||||
###################################################################
|
###################################################################
|
||||||
#API
|
#API
|
||||||
###################################################################
|
###################################################################
|
||||||
@@ -1723,9 +1632,7 @@ RemoteNickFormat="{NICK}"
|
|||||||
## Settings below can be reloaded by editing the file
|
## Settings below can be reloaded by editing the file
|
||||||
|
|
||||||
#RemoteNickFormat defines how remote users appear on this bridge
|
#RemoteNickFormat defines how remote users appear on this bridge
|
||||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick.
|
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||||
#The string "{NOPINGNICK}" (case sensitive) will be replaced by the actual nick / username, but with a ZWSP inside the nick, so the irc user with the same nick won't get pinged.
|
|
||||||
#The string "{USERID}" (case sensitive) will be replaced by the user ID.
|
|
||||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||||
#The string "{LABEL}" (case sensitive) will be replaced by label= field of the sending bridge
|
#The string "{LABEL}" (case sensitive) will be replaced by label= field of the sending bridge
|
||||||
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
||||||
@@ -1751,7 +1658,7 @@ StripNick=false
|
|||||||
#The MediaServerDownload will be used so that bridges without native uploading support:
|
#The MediaServerDownload will be used so that bridges without native uploading support:
|
||||||
#gitter, irc and xmpp will be shown links to the files on MediaServerDownload
|
#gitter, irc and xmpp will be shown links to the files on MediaServerDownload
|
||||||
#
|
#
|
||||||
#More information https://github.com/42wim/matterbridge/wiki/Mediaserver-setup-%28advanced%29
|
#More information https://github.com/42wim/matterbridge/wiki/Mediaserver-setup-%5Badvanced%5D
|
||||||
#OPTIONAL (default empty)
|
#OPTIONAL (default empty)
|
||||||
MediaServerUpload="https://user:pass@yourserver.com/upload"
|
MediaServerUpload="https://user:pass@yourserver.com/upload"
|
||||||
#OPTIONAL (default empty)
|
#OPTIONAL (default empty)
|
||||||
@@ -1800,7 +1707,7 @@ LogFile="/var/log/matterbridge.log"
|
|||||||
#This script will receive every incoming message and can be used to modify the Username and the Text of that message.
|
#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:
|
#The script will have the following global variables:
|
||||||
#to modify: msgUsername and msgText
|
#to modify: msgUsername and msgText
|
||||||
#to read: msgUserID, msgChannel, msgAccount
|
#to read: msgChannel and msgAccount
|
||||||
#
|
#
|
||||||
#The script is reloaded on every message, so you can modify the script on the fly.
|
#The script is reloaded on every message, so you can modify the script on the fly.
|
||||||
#
|
#
|
||||||
@@ -1824,7 +1731,6 @@ InMessage="example.tengo"
|
|||||||
#read-only:
|
#read-only:
|
||||||
#inAccount, inProtocol, inChannel, inGateway, inEvent
|
#inAccount, inProtocol, inChannel, inGateway, inEvent
|
||||||
#outAccount, outProtocol, outChannel, outGateway, outEvent
|
#outAccount, outProtocol, outChannel, outGateway, outEvent
|
||||||
#msgUserID
|
|
||||||
#
|
#
|
||||||
#read-write:
|
#read-write:
|
||||||
#msgText, msgUsername, msgDrop
|
#msgText, msgUsername, msgDrop
|
||||||
@@ -1842,7 +1748,7 @@ OutMessage="example.tengo"
|
|||||||
#RemoteNickFormat allows you to specify the location of a tengo (https://github.com/d5/tengo/) script.
|
#RemoteNickFormat allows you to specify the location of a tengo (https://github.com/d5/tengo/) script.
|
||||||
#The script will have the following global variables:
|
#The script will have the following global variables:
|
||||||
#to modify: result
|
#to modify: result
|
||||||
#to read: channel, bridge, gateway, protocol, nick, msgUserID
|
#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 result will be set in {TENGO} in the RemoteNickFormat key of every bridge where {TENGO} is specified
|
||||||
#
|
#
|
||||||
@@ -1880,7 +1786,7 @@ enable=true
|
|||||||
|
|
||||||
# account specified above
|
# account specified above
|
||||||
# REQUIRED
|
# REQUIRED
|
||||||
account="irc.libera"
|
account="irc.freenode"
|
||||||
|
|
||||||
# The channel key in each gateway is mapped to a similar group chat ID on the chat platform
|
# 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
|
# To find the group chat ID for different platforms, refer to the table below
|
||||||
@@ -1897,8 +1803,7 @@ enable=true
|
|||||||
# -------------------------------------------------------------------------------------------------------------------------------------
|
# -------------------------------------------------------------------------------------------------------------------------------------
|
||||||
# irc | channel | #general | The # symbol is required and should be lowercase!
|
# irc | channel | #general | The # symbol is required and should be lowercase!
|
||||||
# -------------------------------------------------------------------------------------------------------------------------------------
|
# -------------------------------------------------------------------------------------------------------------------------------------
|
||||||
# | channel | general | This is the channel name as seen in the URL, not the display name
|
# mattermost | channel | general | This is the channel name as seen in the URL, not the display name
|
||||||
# mattermost | channel id | ID:oc4wifyuojgw5f3nsuweesmz8w | This is the channel ID (only use if you know what you're doing)
|
|
||||||
# -------------------------------------------------------------------------------------------------------------------------------------
|
# -------------------------------------------------------------------------------------------------------------------------------------
|
||||||
# matrix | #channel:server | #yourchannel:matrix.org | Encrypted rooms are not supported in matrix
|
# matrix | #channel:server | #yourchannel:matrix.org | Encrypted rooms are not supported in matrix
|
||||||
# -------------------------------------------------------------------------------------------------------------------------------------
|
# -------------------------------------------------------------------------------------------------------------------------------------
|
||||||
@@ -1909,7 +1814,7 @@ enable=true
|
|||||||
# rocketchat | channel | #channel | # is required for private channels too
|
# rocketchat | channel | #channel | # is required for private channels too
|
||||||
# -------------------------------------------------------------------------------------------------------------------------------------
|
# -------------------------------------------------------------------------------------------------------------------------------------
|
||||||
# slack | channel name | general | Do not include the # symbol
|
# slack | channel name | general | Do not include the # symbol
|
||||||
# | channel id | ID:C123456 | The underlying ID of a channel. This doesn't work with webhooks.
|
# | 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
|
# steam | chatid | example needed | The number in the URL when you click "enter chat room" in the browser
|
||||||
# -------------------------------------------------------------------------------------------------------------------------------------
|
# -------------------------------------------------------------------------------------------------------------------------------------
|
||||||
@@ -1917,14 +1822,12 @@ enable=true
|
|||||||
# -------------------------------------------------------------------------------------------------------------------------------------
|
# -------------------------------------------------------------------------------------------------------------------------------------
|
||||||
# telegram | chatid | -123456789 | A large negative number. see https://www.linkedin.com/pulse/telegram-bots-beginners-marco-frau
|
# telegram | chatid | -123456789 | A large negative number. see https://www.linkedin.com/pulse/telegram-bots-beginners-marco-frau
|
||||||
# -------------------------------------------------------------------------------------------------------------------------------------
|
# -------------------------------------------------------------------------------------------------------------------------------------
|
||||||
# vk | peerid | 2000000002 | A number that starts form 2000000000. Use --debug and send any message in chat to get PeerID in the logs
|
|
||||||
# -------------------------------------------------------------------------------------------------------------------------------------
|
|
||||||
# whatsapp | group JID | 48111222333-123455678999@g.us | A unique group JID. If you specify an empty string, bridge will list all the possibilities
|
# 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.
|
# | "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
|
# xmpp | channel | general | The room name
|
||||||
# -------------------------------------------------------------------------------------------------------------------------------------
|
# -------------------------------------------------------------------------------------------------------------------------------------
|
||||||
# zulip | stream/topic:topic | general/topic:food | Do not use the # when specifying a topic
|
# zulip | stream/topic:topic | general/off-topic:food | Do not use the # when specifying a topic
|
||||||
# -------------------------------------------------------------------------------------------------------------------------------------
|
# -------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -1939,7 +1842,7 @@ enable=true
|
|||||||
|
|
||||||
#[[gateway.out]] specifies the account and channels we will sent messages to.
|
#[[gateway.out]] specifies the account and channels we will sent messages to.
|
||||||
[[gateway.out]]
|
[[gateway.out]]
|
||||||
account="irc.libera"
|
account="irc.freenode"
|
||||||
channel="#testing"
|
channel="#testing"
|
||||||
|
|
||||||
#OPTIONAL - only used for IRC and XMPP protocols at the moment
|
#OPTIONAL - only used for IRC and XMPP protocols at the moment
|
||||||
@@ -1958,25 +1861,18 @@ enable=true
|
|||||||
#OPTIONAL - your irc / xmpp channel key
|
#OPTIONAL - your irc / xmpp channel key
|
||||||
key="yourkey"
|
key="yourkey"
|
||||||
|
|
||||||
# Discord specific gateway options
|
|
||||||
[[gateway.inout]]
|
[[gateway.inout]]
|
||||||
account="discord.game"
|
account="discord.game"
|
||||||
channel="mygreatgame"
|
channel="mygreatgame"
|
||||||
|
|
||||||
|
#OPTIONAL - webhookurl only works for discord (it needs a different URL for each cahnnel)
|
||||||
[gateway.inout.options]
|
[gateway.inout.options]
|
||||||
# WebhookURL sends messages in the style of "puppets". You must configure a webhook URL for each channel you want to bridge.
|
webhookurl="https://discordapp.com/api/webhooks/123456789123456789/C9WPqExYWONPDZabcdef-def1434FGFjstasJX9pYht73y"
|
||||||
# If you have more than one channel and don't wnat to configure each channel manually, see the "AutoWebhooks" option in the gateway config.
|
|
||||||
# Example: "https://discord.com/api/webhooks/1234/abcd_xyzw"
|
|
||||||
WebhookURL=""
|
|
||||||
|
|
||||||
[[gateway.inout]]
|
[[gateway.inout]]
|
||||||
account="zulip.streamchat"
|
account="zulip.streamchat"
|
||||||
channel="general/topic:mytopic"
|
channel="general/topic:mytopic"
|
||||||
|
|
||||||
[[gateway.inout]]
|
|
||||||
account="harmony.chat_harmonyapp_io"
|
|
||||||
channel="channel id goes here"
|
|
||||||
|
|
||||||
#API example
|
#API example
|
||||||
#[[gateway.inout]]
|
#[[gateway.inout]]
|
||||||
#account="api.local"
|
#account="api.local"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
func (m *MMClient) parseActionPost(rmsg *Message) {
|
func (m *MMClient) parseActionPost(rmsg *Message) {
|
||||||
// add post to cache, if it already exists don't relay this again.
|
// add post to cache, if it already exists don't relay this again.
|
||||||
// this should fix reposts
|
// this should fix reposts
|
||||||
if ok, _ := m.lruCache.ContainsOrAdd(digestString(rmsg.Raw.Data["post"].(string)), true); ok && rmsg.Raw.Event != model.WEBSOCKET_EVENT_POST_DELETED {
|
if ok, _ := m.lruCache.ContainsOrAdd(digestString(rmsg.Raw.Data["post"].(string)), true); ok {
|
||||||
m.logger.Debugf("message %#v in cache, not processing again", rmsg.Raw.Data["post"].(string))
|
m.logger.Debugf("message %#v in cache, not processing again", rmsg.Raw.Data["post"].(string))
|
||||||
rmsg.Text = ""
|
rmsg.Text = ""
|
||||||
return
|
return
|
||||||
@@ -111,7 +111,7 @@ func (m *MMClient) GetFileLinks(filenames []string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *MMClient) GetPosts(channelId string, limit int) *model.PostList { //nolint:golint
|
func (m *MMClient) GetPosts(channelId string, limit int) *model.PostList { //nolint:golint
|
||||||
res, resp := m.Client.GetPostsForChannel(channelId, 0, limit, "", true)
|
res, resp := m.Client.GetPostsForChannel(channelId, 0, limit, "")
|
||||||
if resp.Error != nil {
|
if resp.Error != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -119,7 +119,7 @@ func (m *MMClient) GetPosts(channelId string, limit int) *model.PostList { //nol
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *MMClient) GetPostsSince(channelId string, time int64) *model.PostList { //nolint:golint
|
func (m *MMClient) GetPostsSince(channelId string, time int64) *model.PostList { //nolint:golint
|
||||||
res, resp := m.Client.GetPostsSince(channelId, time, true)
|
res, resp := m.Client.GetPostsSince(channelId, time)
|
||||||
if resp.Error != nil {
|
if resp.Error != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
FROM alpine AS builder
|
FROM alpine AS builder
|
||||||
|
|
||||||
COPY . /go/src/matterbridge
|
COPY . /go/src/github.com/42wim/matterbridge
|
||||||
RUN apk add \
|
RUN apk add \
|
||||||
go \
|
go \
|
||||||
git \
|
git \
|
||||||
&& cd /go/src/matterbridge \
|
gcc \
|
||||||
&& CGO_ENABLED=0 go build -mod vendor -ldflags "-X github.com/42wim/matterbridge/version.GitHash=$(git log --pretty=format:'%h' -n 1)" -o /bin/matterbridge
|
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
|
FROM alpine
|
||||||
RUN apk --no-cache add \
|
RUN apk --no-cache add \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
cairo \
|
cairo \
|
||||||
libjpeg-turbo \
|
libjpeg-turbo \
|
||||||
libwebp-dev \
|
|
||||||
mailcap \
|
mailcap \
|
||||||
py3-webencodings \
|
py3-webencodings \
|
||||||
python3 \
|
python3 \
|
||||||
|
|||||||
24
vendor/github.com/Benau/go_rlottie/LICENSE
generated
vendored
@@ -1,24 +0,0 @@
|
|||||||
The MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2021, (see AUTHORS)
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person
|
|
||||||
obtaining a copy of this software and associated documentation
|
|
||||||
files (the "Software"), to deal in the Software without
|
|
||||||
restriction, including without limitation the rights to use,
|
|
||||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the
|
|
||||||
Software is furnished to do so, subject to the following
|
|
||||||
conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
||||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
||||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
||||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
||||||
OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
1
vendor/github.com/Benau/go_rlottie/README.md
generated
vendored
@@ -1 +0,0 @@
|
|||||||
Go binding for https://github.com/Samsung/rlottie, example at https://github.com/Benau/tgsconverter
|
|
||||||
284
vendor/github.com/Benau/go_rlottie/binding_c_lottieanimation_capi.cpp
generated
vendored
@@ -1,284 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved.
|
|
||||||
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "rlottie.h"
|
|
||||||
#include "rlottie_capi.h"
|
|
||||||
#include "vector_vdebug.h"
|
|
||||||
|
|
||||||
using namespace rlottie;
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
|
|
||||||
struct Lottie_Animation_S
|
|
||||||
{
|
|
||||||
std::unique_ptr<Animation> mAnimation;
|
|
||||||
std::future<Surface> mRenderTask;
|
|
||||||
uint32_t *mBufferRef;
|
|
||||||
LOTMarkerList *mMarkerList;
|
|
||||||
};
|
|
||||||
|
|
||||||
RLOTTIE_API Lottie_Animation_S *lottie_animation_from_file(const char *path)
|
|
||||||
{
|
|
||||||
if (auto animation = Animation::loadFromFile(path) ) {
|
|
||||||
Lottie_Animation_S *handle = new Lottie_Animation_S();
|
|
||||||
handle->mAnimation = std::move(animation);
|
|
||||||
return handle;
|
|
||||||
} else {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RLOTTIE_API Lottie_Animation_S *lottie_animation_from_data(const char *data, const char *key, const char *resourcePath)
|
|
||||||
{
|
|
||||||
if (auto animation = Animation::loadFromData(data, key, resourcePath) ) {
|
|
||||||
Lottie_Animation_S *handle = new Lottie_Animation_S();
|
|
||||||
handle->mAnimation = std::move(animation);
|
|
||||||
return handle;
|
|
||||||
} else {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RLOTTIE_API void lottie_animation_destroy(Lottie_Animation_S *animation)
|
|
||||||
{
|
|
||||||
if (animation) {
|
|
||||||
if (animation->mMarkerList) {
|
|
||||||
for(size_t i = 0; i < animation->mMarkerList->size; i++) {
|
|
||||||
if (animation->mMarkerList->ptr[i].name) free(animation->mMarkerList->ptr[i].name);
|
|
||||||
}
|
|
||||||
delete[] animation->mMarkerList->ptr;
|
|
||||||
delete animation->mMarkerList;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (animation->mRenderTask.valid()) {
|
|
||||||
animation->mRenderTask.get();
|
|
||||||
}
|
|
||||||
animation->mAnimation = nullptr;
|
|
||||||
delete animation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RLOTTIE_API void lottie_animation_get_size(const Lottie_Animation_S *animation, size_t *width, size_t *height)
|
|
||||||
{
|
|
||||||
if (!animation) return;
|
|
||||||
|
|
||||||
animation->mAnimation->size(*width, *height);
|
|
||||||
}
|
|
||||||
|
|
||||||
RLOTTIE_API double lottie_animation_get_duration(const Lottie_Animation_S *animation)
|
|
||||||
{
|
|
||||||
if (!animation) return 0;
|
|
||||||
|
|
||||||
return animation->mAnimation->duration();
|
|
||||||
}
|
|
||||||
|
|
||||||
RLOTTIE_API size_t lottie_animation_get_totalframe(const Lottie_Animation_S *animation)
|
|
||||||
{
|
|
||||||
if (!animation) return 0;
|
|
||||||
|
|
||||||
return animation->mAnimation->totalFrame();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
RLOTTIE_API double lottie_animation_get_framerate(const Lottie_Animation_S *animation)
|
|
||||||
{
|
|
||||||
if (!animation) return 0;
|
|
||||||
|
|
||||||
return animation->mAnimation->frameRate();
|
|
||||||
}
|
|
||||||
|
|
||||||
RLOTTIE_API const LOTLayerNode * lottie_animation_render_tree(Lottie_Animation_S *animation, size_t frame_num, size_t width, size_t height)
|
|
||||||
{
|
|
||||||
if (!animation) return nullptr;
|
|
||||||
|
|
||||||
return animation->mAnimation->renderTree(frame_num, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
RLOTTIE_API size_t
|
|
||||||
lottie_animation_get_frame_at_pos(const Lottie_Animation_S *animation, float pos)
|
|
||||||
{
|
|
||||||
if (!animation) return 0;
|
|
||||||
|
|
||||||
return animation->mAnimation->frameAtPos(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
RLOTTIE_API void
|
|
||||||
lottie_animation_render(Lottie_Animation_S *animation,
|
|
||||||
size_t frame_number,
|
|
||||||
uint32_t *buffer,
|
|
||||||
size_t width,
|
|
||||||
size_t height,
|
|
||||||
size_t bytes_per_line)
|
|
||||||
{
|
|
||||||
if (!animation) return;
|
|
||||||
|
|
||||||
rlottie::Surface surface(buffer, width, height, bytes_per_line);
|
|
||||||
animation->mAnimation->renderSync(frame_number, surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
RLOTTIE_API void
|
|
||||||
lottie_animation_render_async(Lottie_Animation_S *animation,
|
|
||||||
size_t frame_number,
|
|
||||||
uint32_t *buffer,
|
|
||||||
size_t width,
|
|
||||||
size_t height,
|
|
||||||
size_t bytes_per_line)
|
|
||||||
{
|
|
||||||
if (!animation) return;
|
|
||||||
|
|
||||||
rlottie::Surface surface(buffer, width, height, bytes_per_line);
|
|
||||||
animation->mRenderTask = animation->mAnimation->render(frame_number, surface);
|
|
||||||
animation->mBufferRef = buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
RLOTTIE_API uint32_t *
|
|
||||||
lottie_animation_render_flush(Lottie_Animation_S *animation)
|
|
||||||
{
|
|
||||||
if (!animation) return nullptr;
|
|
||||||
|
|
||||||
if (animation->mRenderTask.valid()) {
|
|
||||||
animation->mRenderTask.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
return animation->mBufferRef;
|
|
||||||
}
|
|
||||||
|
|
||||||
RLOTTIE_API void
|
|
||||||
lottie_animation_property_override(Lottie_Animation_S *animation,
|
|
||||||
const Lottie_Animation_Property type,
|
|
||||||
const char *keypath,
|
|
||||||
...)
|
|
||||||
{
|
|
||||||
va_list prop;
|
|
||||||
va_start(prop, keypath);
|
|
||||||
const int arg_count = [type](){
|
|
||||||
switch (type) {
|
|
||||||
case LOTTIE_ANIMATION_PROPERTY_FILLCOLOR:
|
|
||||||
case LOTTIE_ANIMATION_PROPERTY_STROKECOLOR:
|
|
||||||
return 3;
|
|
||||||
case LOTTIE_ANIMATION_PROPERTY_FILLOPACITY:
|
|
||||||
case LOTTIE_ANIMATION_PROPERTY_STROKEOPACITY:
|
|
||||||
case LOTTIE_ANIMATION_PROPERTY_STROKEWIDTH:
|
|
||||||
case LOTTIE_ANIMATION_PROPERTY_TR_ROTATION:
|
|
||||||
return 1;
|
|
||||||
case LOTTIE_ANIMATION_PROPERTY_TR_POSITION:
|
|
||||||
case LOTTIE_ANIMATION_PROPERTY_TR_SCALE:
|
|
||||||
return 2;
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
double v[3] = {0};
|
|
||||||
for (int i = 0; i < arg_count ; i++) {
|
|
||||||
v[i] = va_arg(prop, double);
|
|
||||||
}
|
|
||||||
va_end(prop);
|
|
||||||
|
|
||||||
switch(type) {
|
|
||||||
case LOTTIE_ANIMATION_PROPERTY_FILLCOLOR: {
|
|
||||||
double r = v[0];
|
|
||||||
double g = v[1];
|
|
||||||
double b = v[2];
|
|
||||||
if (r > 1 || r < 0 || g > 1 || g < 0 || b > 1 || b < 0) break;
|
|
||||||
animation->mAnimation->setValue<rlottie::Property::FillColor>(keypath, rlottie::Color(r, g, b));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case LOTTIE_ANIMATION_PROPERTY_FILLOPACITY: {
|
|
||||||
double opacity = v[0];
|
|
||||||
if (opacity > 100 || opacity < 0) break;
|
|
||||||
animation->mAnimation->setValue<rlottie::Property::FillOpacity>(keypath, (float)opacity);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case LOTTIE_ANIMATION_PROPERTY_STROKECOLOR: {
|
|
||||||
double r = v[0];
|
|
||||||
double g = v[1];
|
|
||||||
double b = v[2];
|
|
||||||
if (r > 1 || r < 0 || g > 1 || g < 0 || b > 1 || b < 0) break;
|
|
||||||
animation->mAnimation->setValue<rlottie::Property::StrokeColor>(keypath, rlottie::Color(r, g, b));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case LOTTIE_ANIMATION_PROPERTY_STROKEOPACITY: {
|
|
||||||
double opacity = v[0];
|
|
||||||
if (opacity > 100 || opacity < 0) break;
|
|
||||||
animation->mAnimation->setValue<rlottie::Property::StrokeOpacity>(keypath, (float)opacity);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case LOTTIE_ANIMATION_PROPERTY_STROKEWIDTH: {
|
|
||||||
double width = v[0];
|
|
||||||
if (width < 0) break;
|
|
||||||
animation->mAnimation->setValue<rlottie::Property::StrokeWidth>(keypath, (float)width);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case LOTTIE_ANIMATION_PROPERTY_TR_POSITION: {
|
|
||||||
double x = v[0];
|
|
||||||
double y = v[1];
|
|
||||||
animation->mAnimation->setValue<rlottie::Property::TrPosition>(keypath, rlottie::Point((float)x, (float)y));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case LOTTIE_ANIMATION_PROPERTY_TR_SCALE: {
|
|
||||||
double w = v[0];
|
|
||||||
double h = v[1];
|
|
||||||
animation->mAnimation->setValue<rlottie::Property::TrScale>(keypath, rlottie::Size((float)w, (float)h));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case LOTTIE_ANIMATION_PROPERTY_TR_ROTATION: {
|
|
||||||
double r = v[0];
|
|
||||||
animation->mAnimation->setValue<rlottie::Property::TrRotation>(keypath, (float)r);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case LOTTIE_ANIMATION_PROPERTY_TR_ANCHOR:
|
|
||||||
case LOTTIE_ANIMATION_PROPERTY_TR_OPACITY:
|
|
||||||
//@TODO handle propery update.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RLOTTIE_API const LOTMarkerList*
|
|
||||||
lottie_animation_get_markerlist(Lottie_Animation_S *animation)
|
|
||||||
{
|
|
||||||
if (!animation) return nullptr;
|
|
||||||
|
|
||||||
auto markers = animation->mAnimation->markers();
|
|
||||||
if (markers.size() == 0) return nullptr;
|
|
||||||
if (animation->mMarkerList) return (const LOTMarkerList*)animation->mMarkerList;
|
|
||||||
|
|
||||||
animation->mMarkerList = new LOTMarkerList();
|
|
||||||
animation->mMarkerList->size = markers.size();
|
|
||||||
animation->mMarkerList->ptr = new LOTMarker[markers.size()]();
|
|
||||||
|
|
||||||
for(size_t i = 0; i < markers.size(); i++) {
|
|
||||||
animation->mMarkerList->ptr[i].name = strdup(std::get<0>(markers[i]).c_str());
|
|
||||||
animation->mMarkerList->ptr[i].startframe= std::get<1>(markers[i]);
|
|
||||||
animation->mMarkerList->ptr[i].endframe= std::get<2>(markers[i]);
|
|
||||||
}
|
|
||||||
return (const LOTMarkerList*)animation->mMarkerList;
|
|
||||||
}
|
|
||||||
|
|
||||||
RLOTTIE_API void
|
|
||||||
lottie_configure_model_cache_size(size_t cacheSize)
|
|
||||||
{
|
|
||||||
rlottie::configureModelCacheSize(cacheSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
10
vendor/github.com/Benau/go_rlottie/config.h
generated
vendored
@@ -1,10 +0,0 @@
|
|||||||
#ifndef GO_RLOTTIE_HPP
|
|
||||||
#define GO_RLOTTIE_HPP
|
|
||||||
#ifndef __APPLE__
|
|
||||||
#ifdef __ARM_NEON__
|
|
||||||
#define USE_ARM_NEON
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
#define LOTTIE_THREAD_SUPPORT
|
|
||||||
#define LOTTIE_CACHE_SUPPORT
|
|
||||||
#endif
|
|
||||||
122
vendor/github.com/Benau/go_rlottie/generate_from_rlottie.py
generated
vendored
@@ -1,122 +0,0 @@
|
|||||||
#!/usr/bin/python3
|
|
||||||
# ./generate_from_rlottie.py /path/to/clean/rlottie/src/ /path/to/clean/rlottie/inc/
|
|
||||||
import glob
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
FILE_KEYS = {}
|
|
||||||
|
|
||||||
def get_closest_local_header(header):
|
|
||||||
for full_path, local in FILE_KEYS.items():
|
|
||||||
if os.path.basename(full_path) == header:
|
|
||||||
return local
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def fix_headers(code_text):
|
|
||||||
out = ''
|
|
||||||
has_neon = False
|
|
||||||
for line in code_text:
|
|
||||||
# Special fixes
|
|
||||||
if line == '#include <vpoint.h>':
|
|
||||||
line = '#include "vpoint.h"'
|
|
||||||
if line == '#include <vsharedptr.h>':
|
|
||||||
line = '#include "vsharedptr.h"'
|
|
||||||
if line == '#include <vglobal.h>':
|
|
||||||
line = '#include "vglobal.h"'
|
|
||||||
if line == '#include <vrect.h>':
|
|
||||||
line = '#include "vrect.h"'
|
|
||||||
# ARM on apple fixes
|
|
||||||
if '__ARM_NEON__' in line:
|
|
||||||
has_neon = True
|
|
||||||
line = line.replace('__ARM_NEON__', 'USE_ARM_NEON')
|
|
||||||
header_file = re.match('#include\s+["]([^"]+)["].*', line)
|
|
||||||
# regex to search for <, > too
|
|
||||||
#header_file = re.match('#include\s+[<"]([^>"]+)[>"].*', line)
|
|
||||||
if header_file:
|
|
||||||
header = header_file.groups()[0]
|
|
||||||
abs_header = os.path.abspath(header)
|
|
||||||
header_exists = os.path.exists(abs_header)
|
|
||||||
if header_exists and abs_header in FILE_KEYS:
|
|
||||||
out += '#include "' + FILE_KEYS[abs_header] + '"\n'
|
|
||||||
else:
|
|
||||||
local = get_closest_local_header(header)
|
|
||||||
if local != '':
|
|
||||||
out += '#include "' + local + '"\n'
|
|
||||||
else:
|
|
||||||
out += line + '\n'
|
|
||||||
else:
|
|
||||||
out += line + '\n'
|
|
||||||
if has_neon:
|
|
||||||
out = '#include "config.h"\n' + out
|
|
||||||
return out
|
|
||||||
|
|
||||||
if len(sys.argv) < 2:
|
|
||||||
print('usage: ./generate_from_rlottie.py /path/to/clean/rlottie/src/ /path/to/clean/rlottie/inc/')
|
|
||||||
os._exit(1)
|
|
||||||
|
|
||||||
code = ['.c', '.s', '.S', '.sx', 'cc', 'cpp', 'cpp' ]
|
|
||||||
header = ['.h', '.hh', '.hpp', '.hxx' ]
|
|
||||||
|
|
||||||
# Remove old files
|
|
||||||
files = os.listdir('.')
|
|
||||||
for file in files:
|
|
||||||
if file.endswith(tuple(code)) or file.endswith(tuple(header)):
|
|
||||||
os.remove(os.path.join('.', file))
|
|
||||||
|
|
||||||
paths = []
|
|
||||||
it = iter(sys.argv)
|
|
||||||
next(it, None)
|
|
||||||
for argv in it:
|
|
||||||
paths.append(argv)
|
|
||||||
|
|
||||||
for path in paths:
|
|
||||||
for file in glob.iglob(path + '/**', recursive=True):
|
|
||||||
# Ignore msvc config.h and wasm file
|
|
||||||
if file.endswith('config.h') or 'wasm' in file:
|
|
||||||
continue
|
|
||||||
if file.endswith(tuple(code)) or file.endswith(tuple(header)):
|
|
||||||
key = os.path.abspath(file)
|
|
||||||
val = file.replace(path, '').replace('/', '_')
|
|
||||||
FILE_KEYS[key] = val
|
|
||||||
|
|
||||||
header_check = []
|
|
||||||
for full_path, local in FILE_KEYS.items():
|
|
||||||
header_file = os.path.basename(full_path)
|
|
||||||
if header_file.endswith(tuple(code)):
|
|
||||||
continue
|
|
||||||
if not header_file in header_check:
|
|
||||||
header_check.append(header_file)
|
|
||||||
else:
|
|
||||||
print('WARNING: ' + header_file + ' has multiple reference in subdirectories')
|
|
||||||
|
|
||||||
cur_dir = os.path.abspath('.')
|
|
||||||
for full_path, local in FILE_KEYS.items():
|
|
||||||
os.chdir(os.path.dirname(full_path))
|
|
||||||
with open(full_path) as code:
|
|
||||||
code_text = code.read().splitlines()
|
|
||||||
code.close()
|
|
||||||
fixed = fix_headers(code_text)
|
|
||||||
os.chdir(cur_dir)
|
|
||||||
local_file = open(local, "w")
|
|
||||||
local_file.write(fixed)
|
|
||||||
local_file.close()
|
|
||||||
|
|
||||||
# Write config.h
|
|
||||||
config = '#ifndef GO_RLOTTIE_HPP\n#define GO_RLOTTIE_HPP\n'
|
|
||||||
# ARM on apple won't compile
|
|
||||||
config += '#ifndef __APPLE__\n#ifdef __ARM_NEON__\n#define USE_ARM_NEON\n#endif\n#endif\n'
|
|
||||||
config += '#define LOTTIE_THREAD_SUPPORT\n#define LOTTIE_CACHE_SUPPORT\n'
|
|
||||||
config += '#endif\n'
|
|
||||||
config_file = open('config.h', "w")
|
|
||||||
config_file.write(config)
|
|
||||||
config_file.close()
|
|
||||||
|
|
||||||
# Fix vector_pixman_pixman-arm-neon-asm.S
|
|
||||||
with open('vector_pixman_pixman-arm-neon-asm.S') as code:
|
|
||||||
assembly = code.read()
|
|
||||||
code.close()
|
|
||||||
assembly = '#include "config.h"\n#ifdef USE_ARM_NEON\n' + assembly + '#endif\n'
|
|
||||||
fixed_assembly = open('vector_pixman_pixman-arm-neon-asm.S', "w")
|
|
||||||
fixed_assembly.write(assembly)
|
|
||||||
fixed_assembly.close()
|
|
||||||
56
vendor/github.com/Benau/go_rlottie/go_rlottie.go
generated
vendored
@@ -1,56 +0,0 @@
|
|||||||
package go_rlottie
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo !windows LDFLAGS: -lm
|
|
||||||
#cgo windows CFLAGS: -DRLOTTIE_BUILD=0
|
|
||||||
#cgo windows CXXFLAGS: -DRLOTTIE_BUILD=0
|
|
||||||
#cgo CXXFLAGS: -std=c++14 -fno-exceptions -fno-asynchronous-unwind-tables -fno-rtti -Wall -fvisibility=hidden -Wnon-virtual-dtor -Woverloaded-virtual -Wno-unused-parameter
|
|
||||||
#include "rlottie_capi.h"
|
|
||||||
void lottie_configure_model_cache_size(size_t cacheSize);
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
import "unsafe"
|
|
||||||
|
|
||||||
type Lottie_Animation *C.Lottie_Animation
|
|
||||||
|
|
||||||
func LottieConfigureModelCacheSize(size uint) {
|
|
||||||
C.lottie_configure_model_cache_size(C.size_t(size))
|
|
||||||
}
|
|
||||||
|
|
||||||
func LottieAnimationFromData(data string, key string, resource_path string) Lottie_Animation {
|
|
||||||
var animation Lottie_Animation
|
|
||||||
animation = C.lottie_animation_from_data(C.CString(data), C.CString(key), C.CString(resource_path))
|
|
||||||
return animation
|
|
||||||
}
|
|
||||||
|
|
||||||
func LottieAnimationDestroy(animation Lottie_Animation) {
|
|
||||||
C.lottie_animation_destroy(animation)
|
|
||||||
}
|
|
||||||
|
|
||||||
func LottieAnimationGetSize(animation Lottie_Animation) (uint, uint) {
|
|
||||||
var width C.size_t
|
|
||||||
var height C.size_t
|
|
||||||
C.lottie_animation_get_size(animation, &width, &height)
|
|
||||||
return uint(width), uint(height)
|
|
||||||
}
|
|
||||||
|
|
||||||
func LottieAnimationGetTotalframe(animation Lottie_Animation) uint {
|
|
||||||
return uint(C.lottie_animation_get_totalframe(animation))
|
|
||||||
}
|
|
||||||
|
|
||||||
func LottieAnimationGetFramerate(animation Lottie_Animation) float64 {
|
|
||||||
return float64(C.lottie_animation_get_framerate(animation))
|
|
||||||
}
|
|
||||||
|
|
||||||
func LottieAnimationGetFrameAtPos(animation Lottie_Animation, pos float32) uint {
|
|
||||||
return uint(C.lottie_animation_get_frame_at_pos(animation, C.float(pos)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func LottieAnimationGetDuration(animation Lottie_Animation) float64 {
|
|
||||||
return float64(C.lottie_animation_get_duration(animation))
|
|
||||||
}
|
|
||||||
|
|
||||||
func LottieAnimationRender(animation Lottie_Animation, frame_num uint, buffer []byte, width uint, height uint, bytes_per_line uint) {
|
|
||||||
var ptr *C.uint32_t = (*C.uint32_t)(unsafe.Pointer(&buffer[0]));
|
|
||||||
C.lottie_animation_render(animation, C.size_t(frame_num), ptr, C.size_t(width), C.size_t(height), C.size_t(bytes_per_line))
|
|
||||||
}
|
|
||||||
457
vendor/github.com/Benau/go_rlottie/lottie_lottieanimation.cpp
generated
vendored
@@ -1,457 +0,0 @@
|
|||||||
#include "config.h"
|
|
||||||
/*
|
|
||||||
* Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved.
|
|
||||||
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
#include "config.h"
|
|
||||||
#include "lottie_lottieitem.h"
|
|
||||||
#include "lottie_lottiemodel.h"
|
|
||||||
#include "rlottie.h"
|
|
||||||
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
using namespace rlottie;
|
|
||||||
using namespace rlottie::internal;
|
|
||||||
|
|
||||||
RLOTTIE_API void rlottie::configureModelCacheSize(size_t cacheSize)
|
|
||||||
{
|
|
||||||
internal::model::configureModelCacheSize(cacheSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RenderTask {
|
|
||||||
RenderTask() { receiver = sender.get_future(); }
|
|
||||||
std::promise<Surface> sender;
|
|
||||||
std::future<Surface> receiver;
|
|
||||||
AnimationImpl * playerImpl{nullptr};
|
|
||||||
size_t frameNo{0};
|
|
||||||
Surface surface;
|
|
||||||
bool keepAspectRatio{true};
|
|
||||||
};
|
|
||||||
using SharedRenderTask = std::shared_ptr<RenderTask>;
|
|
||||||
|
|
||||||
class AnimationImpl {
|
|
||||||
public:
|
|
||||||
void init(std::shared_ptr<model::Composition> composition);
|
|
||||||
bool update(size_t frameNo, const VSize &size, bool keepAspectRatio);
|
|
||||||
VSize size() const { return mModel->size(); }
|
|
||||||
double duration() const { return mModel->duration(); }
|
|
||||||
double frameRate() const { return mModel->frameRate(); }
|
|
||||||
size_t totalFrame() const { return mModel->totalFrame(); }
|
|
||||||
size_t frameAtPos(double pos) const { return mModel->frameAtPos(pos); }
|
|
||||||
Surface render(size_t frameNo, const Surface &surface,
|
|
||||||
bool keepAspectRatio);
|
|
||||||
std::future<Surface> renderAsync(size_t frameNo, Surface &&surface,
|
|
||||||
bool keepAspectRatio);
|
|
||||||
const LOTLayerNode * renderTree(size_t frameNo, const VSize &size);
|
|
||||||
|
|
||||||
const LayerInfoList &layerInfoList() const
|
|
||||||
{
|
|
||||||
if (mLayerList.empty()) {
|
|
||||||
mLayerList = mModel->layerInfoList();
|
|
||||||
}
|
|
||||||
return mLayerList;
|
|
||||||
}
|
|
||||||
const MarkerList &markers() const { return mModel->markers(); }
|
|
||||||
void setValue(const std::string &keypath, LOTVariant &&value);
|
|
||||||
void removeFilter(const std::string &keypath, Property prop);
|
|
||||||
|
|
||||||
private:
|
|
||||||
mutable LayerInfoList mLayerList;
|
|
||||||
model::Composition * mModel;
|
|
||||||
SharedRenderTask mTask;
|
|
||||||
std::atomic<bool> mRenderInProgress;
|
|
||||||
std::unique_ptr<renderer::Composition> mRenderer{nullptr};
|
|
||||||
};
|
|
||||||
|
|
||||||
void AnimationImpl::setValue(const std::string &keypath, LOTVariant &&value)
|
|
||||||
{
|
|
||||||
if (keypath.empty()) return;
|
|
||||||
mRenderer->setValue(keypath, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
const LOTLayerNode *AnimationImpl::renderTree(size_t frameNo, const VSize &size)
|
|
||||||
{
|
|
||||||
if (update(frameNo, size, true)) {
|
|
||||||
mRenderer->buildRenderTree();
|
|
||||||
}
|
|
||||||
return mRenderer->renderTree();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AnimationImpl::update(size_t frameNo, const VSize &size,
|
|
||||||
bool keepAspectRatio)
|
|
||||||
{
|
|
||||||
frameNo += mModel->startFrame();
|
|
||||||
|
|
||||||
if (frameNo > mModel->endFrame()) frameNo = mModel->endFrame();
|
|
||||||
|
|
||||||
if (frameNo < mModel->startFrame()) frameNo = mModel->startFrame();
|
|
||||||
|
|
||||||
return mRenderer->update(int(frameNo), size, keepAspectRatio);
|
|
||||||
}
|
|
||||||
|
|
||||||
Surface AnimationImpl::render(size_t frameNo, const Surface &surface,
|
|
||||||
bool keepAspectRatio)
|
|
||||||
{
|
|
||||||
bool renderInProgress = mRenderInProgress.load();
|
|
||||||
if (renderInProgress) {
|
|
||||||
vCritical << "Already Rendering Scheduled for this Animation";
|
|
||||||
return surface;
|
|
||||||
}
|
|
||||||
|
|
||||||
mRenderInProgress.store(true);
|
|
||||||
update(
|
|
||||||
frameNo,
|
|
||||||
VSize(int(surface.drawRegionWidth()), int(surface.drawRegionHeight())),
|
|
||||||
keepAspectRatio);
|
|
||||||
mRenderer->render(surface);
|
|
||||||
mRenderInProgress.store(false);
|
|
||||||
|
|
||||||
return surface;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AnimationImpl::init(std::shared_ptr<model::Composition> composition)
|
|
||||||
{
|
|
||||||
mModel = composition.get();
|
|
||||||
mRenderer = std::make_unique<renderer::Composition>(composition);
|
|
||||||
mRenderInProgress = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef LOTTIE_THREAD_SUPPORT
|
|
||||||
|
|
||||||
#include <thread>
|
|
||||||
#include "vector_vtaskqueue.h"
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Implement a task stealing schduler to perform render task
|
|
||||||
* As each player draws into its own buffer we can delegate this
|
|
||||||
* task to a slave thread. The scheduler creates a threadpool depending
|
|
||||||
* on the number of cores available in the system and does a simple fair
|
|
||||||
* scheduling by assigning the task in a round-robin fashion. Each thread
|
|
||||||
* in the threadpool has its own queue. once it finishes all the task on its
|
|
||||||
* own queue it goes through rest of the queue and looks for task if it founds
|
|
||||||
* one it steals the task from it and executes. if it couldn't find one then it
|
|
||||||
* just waits for new task on its own queue.
|
|
||||||
*/
|
|
||||||
class RenderTaskScheduler {
|
|
||||||
const unsigned _count{std::thread::hardware_concurrency()};
|
|
||||||
std::vector<std::thread> _threads;
|
|
||||||
std::vector<TaskQueue<SharedRenderTask>> _q{_count};
|
|
||||||
std::atomic<unsigned> _index{0};
|
|
||||||
|
|
||||||
void run(unsigned i)
|
|
||||||
{
|
|
||||||
while (true) {
|
|
||||||
bool success = false;
|
|
||||||
SharedRenderTask task;
|
|
||||||
for (unsigned n = 0; n != _count * 2; ++n) {
|
|
||||||
if (_q[(i + n) % _count].try_pop(task)) {
|
|
||||||
success = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!success && !_q[i].pop(task)) break;
|
|
||||||
|
|
||||||
auto result = task->playerImpl->render(task->frameNo, task->surface,
|
|
||||||
task->keepAspectRatio);
|
|
||||||
task->sender.set_value(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderTaskScheduler()
|
|
||||||
{
|
|
||||||
for (unsigned n = 0; n != _count; ++n) {
|
|
||||||
_threads.emplace_back([&, n] { run(n); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
static RenderTaskScheduler &instance()
|
|
||||||
{
|
|
||||||
static RenderTaskScheduler singleton;
|
|
||||||
return singleton;
|
|
||||||
}
|
|
||||||
|
|
||||||
~RenderTaskScheduler()
|
|
||||||
{
|
|
||||||
for (auto &e : _q) e.done();
|
|
||||||
|
|
||||||
for (auto &e : _threads) e.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::future<Surface> process(SharedRenderTask task)
|
|
||||||
{
|
|
||||||
auto receiver = std::move(task->receiver);
|
|
||||||
auto i = _index++;
|
|
||||||
|
|
||||||
for (unsigned n = 0; n != _count; ++n) {
|
|
||||||
if (_q[(i + n) % _count].try_push(std::move(task))) return receiver;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_count > 0) {
|
|
||||||
_q[i % _count].push(std::move(task));
|
|
||||||
}
|
|
||||||
|
|
||||||
return receiver;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#else
|
|
||||||
class RenderTaskScheduler {
|
|
||||||
public:
|
|
||||||
static RenderTaskScheduler &instance()
|
|
||||||
{
|
|
||||||
static RenderTaskScheduler singleton;
|
|
||||||
return singleton;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::future<Surface> process(SharedRenderTask task)
|
|
||||||
{
|
|
||||||
auto result = task->playerImpl->render(task->frameNo, task->surface,
|
|
||||||
task->keepAspectRatio);
|
|
||||||
task->sender.set_value(result);
|
|
||||||
return std::move(task->receiver);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
std::future<Surface> AnimationImpl::renderAsync(size_t frameNo,
|
|
||||||
Surface &&surface,
|
|
||||||
bool keepAspectRatio)
|
|
||||||
{
|
|
||||||
if (!mTask) {
|
|
||||||
mTask = std::make_shared<RenderTask>();
|
|
||||||
} else {
|
|
||||||
mTask->sender = std::promise<Surface>();
|
|
||||||
mTask->receiver = mTask->sender.get_future();
|
|
||||||
}
|
|
||||||
mTask->playerImpl = this;
|
|
||||||
mTask->frameNo = frameNo;
|
|
||||||
mTask->surface = std::move(surface);
|
|
||||||
mTask->keepAspectRatio = keepAspectRatio;
|
|
||||||
|
|
||||||
return RenderTaskScheduler::instance().process(mTask);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \breif Brief abput the Api.
|
|
||||||
* Description about the setFilePath Api
|
|
||||||
* @param path add the details
|
|
||||||
*/
|
|
||||||
std::unique_ptr<Animation> Animation::loadFromData(
|
|
||||||
std::string jsonData, const std::string &key,
|
|
||||||
const std::string &resourcePath, bool cachePolicy)
|
|
||||||
{
|
|
||||||
if (jsonData.empty()) {
|
|
||||||
vWarning << "jason data is empty";
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto composition = model::loadFromData(std::move(jsonData), key,
|
|
||||||
resourcePath, cachePolicy);
|
|
||||||
if (composition) {
|
|
||||||
auto animation = std::unique_ptr<Animation>(new Animation);
|
|
||||||
animation->d->init(std::move(composition));
|
|
||||||
return animation;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Animation> Animation::loadFromData(std::string jsonData,
|
|
||||||
std::string resourcePath,
|
|
||||||
ColorFilter filter)
|
|
||||||
{
|
|
||||||
if (jsonData.empty()) {
|
|
||||||
vWarning << "jason data is empty";
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto composition = model::loadFromData(
|
|
||||||
std::move(jsonData), std::move(resourcePath), std::move(filter));
|
|
||||||
if (composition) {
|
|
||||||
auto animation = std::unique_ptr<Animation>(new Animation);
|
|
||||||
animation->d->init(std::move(composition));
|
|
||||||
return animation;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Animation> Animation::loadFromFile(const std::string &path,
|
|
||||||
bool cachePolicy)
|
|
||||||
{
|
|
||||||
if (path.empty()) {
|
|
||||||
vWarning << "File path is empty";
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto composition = model::loadFromFile(path, cachePolicy);
|
|
||||||
if (composition) {
|
|
||||||
auto animation = std::unique_ptr<Animation>(new Animation);
|
|
||||||
animation->d->init(std::move(composition));
|
|
||||||
return animation;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Animation::size(size_t &width, size_t &height) const
|
|
||||||
{
|
|
||||||
VSize sz = d->size();
|
|
||||||
|
|
||||||
width = sz.width();
|
|
||||||
height = sz.height();
|
|
||||||
}
|
|
||||||
|
|
||||||
double Animation::duration() const
|
|
||||||
{
|
|
||||||
return d->duration();
|
|
||||||
}
|
|
||||||
|
|
||||||
double Animation::frameRate() const
|
|
||||||
{
|
|
||||||
return d->frameRate();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t Animation::totalFrame() const
|
|
||||||
{
|
|
||||||
return d->totalFrame();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t Animation::frameAtPos(double pos)
|
|
||||||
{
|
|
||||||
return d->frameAtPos(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
const LOTLayerNode *Animation::renderTree(size_t frameNo, size_t width,
|
|
||||||
size_t height) const
|
|
||||||
{
|
|
||||||
return d->renderTree(frameNo, VSize(int(width), int(height)));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::future<Surface> Animation::render(size_t frameNo, Surface surface,
|
|
||||||
bool keepAspectRatio)
|
|
||||||
{
|
|
||||||
return d->renderAsync(frameNo, std::move(surface), keepAspectRatio);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Animation::renderSync(size_t frameNo, Surface surface,
|
|
||||||
bool keepAspectRatio)
|
|
||||||
{
|
|
||||||
d->render(frameNo, surface, keepAspectRatio);
|
|
||||||
}
|
|
||||||
|
|
||||||
const LayerInfoList &Animation::layers() const
|
|
||||||
{
|
|
||||||
return d->layerInfoList();
|
|
||||||
}
|
|
||||||
|
|
||||||
const MarkerList &Animation::markers() const
|
|
||||||
{
|
|
||||||
return d->markers();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Animation::setValue(Color_Type, Property prop, const std::string &keypath,
|
|
||||||
Color value)
|
|
||||||
{
|
|
||||||
d->setValue(keypath,
|
|
||||||
LOTVariant(prop, [value](const FrameInfo &) { return value; }));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Animation::setValue(Float_Type, Property prop, const std::string &keypath,
|
|
||||||
float value)
|
|
||||||
{
|
|
||||||
d->setValue(keypath,
|
|
||||||
LOTVariant(prop, [value](const FrameInfo &) { return value; }));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Animation::setValue(Size_Type, Property prop, const std::string &keypath,
|
|
||||||
Size value)
|
|
||||||
{
|
|
||||||
d->setValue(keypath,
|
|
||||||
LOTVariant(prop, [value](const FrameInfo &) { return value; }));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Animation::setValue(Point_Type, Property prop, const std::string &keypath,
|
|
||||||
Point value)
|
|
||||||
{
|
|
||||||
d->setValue(keypath,
|
|
||||||
LOTVariant(prop, [value](const FrameInfo &) { return value; }));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Animation::setValue(Color_Type, Property prop, const std::string &keypath,
|
|
||||||
std::function<Color(const FrameInfo &)> &&value)
|
|
||||||
{
|
|
||||||
d->setValue(keypath, LOTVariant(prop, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Animation::setValue(Float_Type, Property prop, const std::string &keypath,
|
|
||||||
std::function<float(const FrameInfo &)> &&value)
|
|
||||||
{
|
|
||||||
d->setValue(keypath, LOTVariant(prop, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Animation::setValue(Size_Type, Property prop, const std::string &keypath,
|
|
||||||
std::function<Size(const FrameInfo &)> &&value)
|
|
||||||
{
|
|
||||||
d->setValue(keypath, LOTVariant(prop, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Animation::setValue(Point_Type, Property prop, const std::string &keypath,
|
|
||||||
std::function<Point(const FrameInfo &)> &&value)
|
|
||||||
{
|
|
||||||
d->setValue(keypath, LOTVariant(prop, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
Animation::~Animation() = default;
|
|
||||||
Animation::Animation() : d(std::make_unique<AnimationImpl>()) {}
|
|
||||||
|
|
||||||
Surface::Surface(uint32_t *buffer, size_t width, size_t height,
|
|
||||||
size_t bytesPerLine)
|
|
||||||
: mBuffer(buffer),
|
|
||||||
mWidth(width),
|
|
||||||
mHeight(height),
|
|
||||||
mBytesPerLine(bytesPerLine)
|
|
||||||
{
|
|
||||||
mDrawArea.w = mWidth;
|
|
||||||
mDrawArea.h = mHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Surface::setDrawRegion(size_t x, size_t y, size_t width, size_t height)
|
|
||||||
{
|
|
||||||
if ((x + width > mWidth) || (y + height > mHeight)) return;
|
|
||||||
|
|
||||||
mDrawArea.x = x;
|
|
||||||
mDrawArea.y = y;
|
|
||||||
mDrawArea.w = width;
|
|
||||||
mDrawArea.h = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef LOTTIE_LOGGING_SUPPORT
|
|
||||||
void initLogging()
|
|
||||||
{
|
|
||||||
#if defined(USE_ARM_NEON)
|
|
||||||
set_log_level(LogLevel::OFF);
|
|
||||||
#else
|
|
||||||
initialize(GuaranteedLogger(), "/tmp/", "rlottie", 1);
|
|
||||||
set_log_level(LogLevel::INFO);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
V_CONSTRUCTOR_FUNCTION(initLogging)
|
|
||||||
#endif
|
|
||||||
435
vendor/github.com/Benau/go_rlottie/lottie_lottiefiltermodel.h
generated
vendored
@@ -1,435 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved.
|
|
||||||
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef LOTTIEFILTERMODEL_H
|
|
||||||
#define LOTTIEFILTERMODEL_H
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <bitset>
|
|
||||||
#include <cassert>
|
|
||||||
#include "lottie_lottiemodel.h"
|
|
||||||
#include "rlottie.h"
|
|
||||||
|
|
||||||
using namespace rlottie::internal;
|
|
||||||
// Naive way to implement std::variant
|
|
||||||
// refactor it when we move to c++17
|
|
||||||
// users should make sure proper combination
|
|
||||||
// of id and value are passed while creating the object.
|
|
||||||
class LOTVariant {
|
|
||||||
public:
|
|
||||||
using ValueFunc = std::function<float(const rlottie::FrameInfo&)>;
|
|
||||||
using ColorFunc = std::function<rlottie::Color(const rlottie::FrameInfo&)>;
|
|
||||||
using PointFunc = std::function<rlottie::Point(const rlottie::FrameInfo&)>;
|
|
||||||
using SizeFunc = std::function<rlottie::Size(const rlottie::FrameInfo&)>;
|
|
||||||
|
|
||||||
LOTVariant(rlottie::Property prop, const ValueFunc& v)
|
|
||||||
: mPropery(prop), mTag(Value)
|
|
||||||
{
|
|
||||||
construct(impl.valueFunc, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOTVariant(rlottie::Property prop, ValueFunc&& v)
|
|
||||||
: mPropery(prop), mTag(Value)
|
|
||||||
{
|
|
||||||
moveConstruct(impl.valueFunc, std::move(v));
|
|
||||||
}
|
|
||||||
|
|
||||||
LOTVariant(rlottie::Property prop, const ColorFunc& v)
|
|
||||||
: mPropery(prop), mTag(Color)
|
|
||||||
{
|
|
||||||
construct(impl.colorFunc, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOTVariant(rlottie::Property prop, ColorFunc&& v)
|
|
||||||
: mPropery(prop), mTag(Color)
|
|
||||||
{
|
|
||||||
moveConstruct(impl.colorFunc, std::move(v));
|
|
||||||
}
|
|
||||||
|
|
||||||
LOTVariant(rlottie::Property prop, const PointFunc& v)
|
|
||||||
: mPropery(prop), mTag(Point)
|
|
||||||
{
|
|
||||||
construct(impl.pointFunc, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOTVariant(rlottie::Property prop, PointFunc&& v)
|
|
||||||
: mPropery(prop), mTag(Point)
|
|
||||||
{
|
|
||||||
moveConstruct(impl.pointFunc, std::move(v));
|
|
||||||
}
|
|
||||||
|
|
||||||
LOTVariant(rlottie::Property prop, const SizeFunc& v)
|
|
||||||
: mPropery(prop), mTag(Size)
|
|
||||||
{
|
|
||||||
construct(impl.sizeFunc, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOTVariant(rlottie::Property prop, SizeFunc&& v)
|
|
||||||
: mPropery(prop), mTag(Size)
|
|
||||||
{
|
|
||||||
moveConstruct(impl.sizeFunc, std::move(v));
|
|
||||||
}
|
|
||||||
|
|
||||||
rlottie::Property property() const { return mPropery; }
|
|
||||||
|
|
||||||
const ColorFunc& color() const
|
|
||||||
{
|
|
||||||
assert(mTag == Color);
|
|
||||||
return impl.colorFunc;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ValueFunc& value() const
|
|
||||||
{
|
|
||||||
assert(mTag == Value);
|
|
||||||
return impl.valueFunc;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PointFunc& point() const
|
|
||||||
{
|
|
||||||
assert(mTag == Point);
|
|
||||||
return impl.pointFunc;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SizeFunc& size() const
|
|
||||||
{
|
|
||||||
assert(mTag == Size);
|
|
||||||
return impl.sizeFunc;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOTVariant() = default;
|
|
||||||
~LOTVariant() noexcept { Destroy(); }
|
|
||||||
LOTVariant(const LOTVariant& other) { Copy(other); }
|
|
||||||
LOTVariant(LOTVariant&& other) noexcept { Move(std::move(other)); }
|
|
||||||
LOTVariant& operator=(LOTVariant&& other)
|
|
||||||
{
|
|
||||||
Destroy();
|
|
||||||
Move(std::move(other));
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LOTVariant& operator=(const LOTVariant& other)
|
|
||||||
{
|
|
||||||
Destroy();
|
|
||||||
Copy(other);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
template <typename T>
|
|
||||||
void construct(T& member, const T& val)
|
|
||||||
{
|
|
||||||
new (&member) T(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
void moveConstruct(T& member, T&& val)
|
|
||||||
{
|
|
||||||
new (&member) T(std::move(val));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Move(LOTVariant&& other)
|
|
||||||
{
|
|
||||||
switch (other.mTag) {
|
|
||||||
case Type::Value:
|
|
||||||
moveConstruct(impl.valueFunc, std::move(other.impl.valueFunc));
|
|
||||||
break;
|
|
||||||
case Type::Color:
|
|
||||||
moveConstruct(impl.colorFunc, std::move(other.impl.colorFunc));
|
|
||||||
break;
|
|
||||||
case Type::Point:
|
|
||||||
moveConstruct(impl.pointFunc, std::move(other.impl.pointFunc));
|
|
||||||
break;
|
|
||||||
case Type::Size:
|
|
||||||
moveConstruct(impl.sizeFunc, std::move(other.impl.sizeFunc));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
mTag = other.mTag;
|
|
||||||
mPropery = other.mPropery;
|
|
||||||
other.mTag = MonoState;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Copy(const LOTVariant& other)
|
|
||||||
{
|
|
||||||
switch (other.mTag) {
|
|
||||||
case Type::Value:
|
|
||||||
construct(impl.valueFunc, other.impl.valueFunc);
|
|
||||||
break;
|
|
||||||
case Type::Color:
|
|
||||||
construct(impl.colorFunc, other.impl.colorFunc);
|
|
||||||
break;
|
|
||||||
case Type::Point:
|
|
||||||
construct(impl.pointFunc, other.impl.pointFunc);
|
|
||||||
break;
|
|
||||||
case Type::Size:
|
|
||||||
construct(impl.sizeFunc, other.impl.sizeFunc);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
mTag = other.mTag;
|
|
||||||
mPropery = other.mPropery;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Destroy()
|
|
||||||
{
|
|
||||||
switch (mTag) {
|
|
||||||
case MonoState: {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Value: {
|
|
||||||
impl.valueFunc.~ValueFunc();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Color: {
|
|
||||||
impl.colorFunc.~ColorFunc();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Point: {
|
|
||||||
impl.pointFunc.~PointFunc();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Size: {
|
|
||||||
impl.sizeFunc.~SizeFunc();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Type { MonoState, Value, Color, Point, Size };
|
|
||||||
rlottie::Property mPropery;
|
|
||||||
Type mTag{MonoState};
|
|
||||||
union details {
|
|
||||||
ColorFunc colorFunc;
|
|
||||||
ValueFunc valueFunc;
|
|
||||||
PointFunc pointFunc;
|
|
||||||
SizeFunc sizeFunc;
|
|
||||||
details() {}
|
|
||||||
~details() noexcept {}
|
|
||||||
} impl;
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace rlottie {
|
|
||||||
|
|
||||||
namespace internal {
|
|
||||||
|
|
||||||
namespace model {
|
|
||||||
|
|
||||||
class FilterData {
|
|
||||||
public:
|
|
||||||
void addValue(LOTVariant& value)
|
|
||||||
{
|
|
||||||
uint index = static_cast<uint>(value.property());
|
|
||||||
if (mBitset.test(index)) {
|
|
||||||
std::replace_if(mFilters.begin(), mFilters.end(),
|
|
||||||
[&value](const LOTVariant& e) {
|
|
||||||
return e.property() == value.property();
|
|
||||||
},
|
|
||||||
value);
|
|
||||||
} else {
|
|
||||||
mBitset.set(index);
|
|
||||||
mFilters.push_back(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void removeValue(LOTVariant& value)
|
|
||||||
{
|
|
||||||
uint index = static_cast<uint>(value.property());
|
|
||||||
if (mBitset.test(index)) {
|
|
||||||
mBitset.reset(index);
|
|
||||||
mFilters.erase(std::remove_if(mFilters.begin(), mFilters.end(),
|
|
||||||
[&value](const LOTVariant& e) {
|
|
||||||
return e.property() ==
|
|
||||||
value.property();
|
|
||||||
}),
|
|
||||||
mFilters.end());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bool hasFilter(rlottie::Property prop) const
|
|
||||||
{
|
|
||||||
return mBitset.test(static_cast<uint>(prop));
|
|
||||||
}
|
|
||||||
model::Color color(rlottie::Property prop, int frame) const
|
|
||||||
{
|
|
||||||
rlottie::FrameInfo info(frame);
|
|
||||||
rlottie::Color col = data(prop).color()(info);
|
|
||||||
return model::Color(col.r(), col.g(), col.b());
|
|
||||||
}
|
|
||||||
VPointF point(rlottie::Property prop, int frame) const
|
|
||||||
{
|
|
||||||
rlottie::FrameInfo info(frame);
|
|
||||||
rlottie::Point pt = data(prop).point()(info);
|
|
||||||
return VPointF(pt.x(), pt.y());
|
|
||||||
}
|
|
||||||
VSize scale(rlottie::Property prop, int frame) const
|
|
||||||
{
|
|
||||||
rlottie::FrameInfo info(frame);
|
|
||||||
rlottie::Size sz = data(prop).size()(info);
|
|
||||||
return VSize(sz.w(), sz.h());
|
|
||||||
}
|
|
||||||
float opacity(rlottie::Property prop, int frame) const
|
|
||||||
{
|
|
||||||
rlottie::FrameInfo info(frame);
|
|
||||||
float val = data(prop).value()(info);
|
|
||||||
return val / 100;
|
|
||||||
}
|
|
||||||
float value(rlottie::Property prop, int frame) const
|
|
||||||
{
|
|
||||||
rlottie::FrameInfo info(frame);
|
|
||||||
return data(prop).value()(info);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
const LOTVariant& data(rlottie::Property prop) const
|
|
||||||
{
|
|
||||||
auto result = std::find_if(
|
|
||||||
mFilters.begin(), mFilters.end(),
|
|
||||||
[prop](const LOTVariant& e) { return e.property() == prop; });
|
|
||||||
return *result;
|
|
||||||
}
|
|
||||||
std::bitset<32> mBitset{0};
|
|
||||||
std::vector<LOTVariant> mFilters;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
struct FilterBase
|
|
||||||
{
|
|
||||||
FilterBase(T *model): model_(model){}
|
|
||||||
|
|
||||||
const char* name() const { return model_->name(); }
|
|
||||||
|
|
||||||
FilterData* filter() {
|
|
||||||
if (!filterData_) filterData_ = std::make_unique<FilterData>();
|
|
||||||
return filterData_.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
const FilterData * filter() const { return filterData_.get(); }
|
|
||||||
const T* model() const { return model_;}
|
|
||||||
|
|
||||||
bool hasFilter(rlottie::Property prop) const {
|
|
||||||
return filterData_ ? filterData_->hasFilter(prop)
|
|
||||||
: false;
|
|
||||||
}
|
|
||||||
|
|
||||||
T* model_{nullptr};
|
|
||||||
std::unique_ptr<FilterData> filterData_{nullptr};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
class Filter : public FilterBase<T> {
|
|
||||||
public:
|
|
||||||
Filter(T* model): FilterBase<T>(model){}
|
|
||||||
model::Color color(int frame) const
|
|
||||||
{
|
|
||||||
if (this->hasFilter(rlottie::Property::StrokeColor)) {
|
|
||||||
return this->filter()->color(rlottie::Property::StrokeColor, frame);
|
|
||||||
}
|
|
||||||
return this->model()->color(frame);
|
|
||||||
}
|
|
||||||
float opacity(int frame) const
|
|
||||||
{
|
|
||||||
if (this->hasFilter(rlottie::Property::StrokeOpacity)) {
|
|
||||||
return this->filter()->opacity(rlottie::Property::StrokeOpacity, frame);
|
|
||||||
}
|
|
||||||
return this->model()->opacity(frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
float strokeWidth(int frame) const
|
|
||||||
{
|
|
||||||
if (this->hasFilter(rlottie::Property::StrokeWidth)) {
|
|
||||||
return this->filter()->value(rlottie::Property::StrokeWidth, frame);
|
|
||||||
}
|
|
||||||
return this->model()->strokeWidth(frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
float miterLimit() const { return this->model()->miterLimit(); }
|
|
||||||
CapStyle capStyle() const { return this->model()->capStyle(); }
|
|
||||||
JoinStyle joinStyle() const { return this->model()->joinStyle(); }
|
|
||||||
bool hasDashInfo() const { return this->model()->hasDashInfo(); }
|
|
||||||
void getDashInfo(int frameNo, std::vector<float>& result) const
|
|
||||||
{
|
|
||||||
return this->model()->getDashInfo(frameNo, result);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
template <>
|
|
||||||
class Filter<model::Fill>: public FilterBase<model::Fill>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Filter(model::Fill* model) : FilterBase<model::Fill>(model) {}
|
|
||||||
|
|
||||||
model::Color color(int frame) const
|
|
||||||
{
|
|
||||||
if (this->hasFilter(rlottie::Property::FillColor)) {
|
|
||||||
return this->filter()->color(rlottie::Property::FillColor, frame);
|
|
||||||
}
|
|
||||||
return this->model()->color(frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
float opacity(int frame) const
|
|
||||||
{
|
|
||||||
if (this->hasFilter(rlottie::Property::FillOpacity)) {
|
|
||||||
return this->filter()->opacity(rlottie::Property::FillOpacity, frame);
|
|
||||||
}
|
|
||||||
return this->model()->opacity(frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
FillRule fillRule() const { return this->model()->fillRule(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
template <>
|
|
||||||
class Filter<model::Group> : public FilterBase<model::Group>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Filter(model::Group* model = nullptr) : FilterBase<model::Group>(model) {}
|
|
||||||
|
|
||||||
bool hasModel() const { return this->model() ? true : false; }
|
|
||||||
|
|
||||||
model::Transform* transform() const { return this->model() ? this->model()->mTransform : nullptr; }
|
|
||||||
VMatrix matrix(int frame) const
|
|
||||||
{
|
|
||||||
VMatrix mS, mR, mT;
|
|
||||||
if (this->hasFilter(rlottie::Property::TrScale)) {
|
|
||||||
VSize s = this->filter()->scale(rlottie::Property::TrScale, frame);
|
|
||||||
mS.scale(s.width() / 100.0, s.height() / 100.0);
|
|
||||||
}
|
|
||||||
if (this->hasFilter(rlottie::Property::TrRotation)) {
|
|
||||||
mR.rotate(this->filter()->value(rlottie::Property::TrRotation, frame));
|
|
||||||
}
|
|
||||||
if (this->hasFilter(rlottie::Property::TrPosition)) {
|
|
||||||
mT.translate(this->filter()->point(rlottie::Property::TrPosition, frame));
|
|
||||||
}
|
|
||||||
|
|
||||||
return this->model()->mTransform->matrix(frame) * mS * mR * mT;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace model
|
|
||||||
|
|
||||||
} // namespace internal
|
|
||||||
|
|
||||||
} // namespace rlottie
|
|
||||||
|
|
||||||
#endif // LOTTIEFILTERMODEL_H
|
|
||||||
1491
vendor/github.com/Benau/go_rlottie/lottie_lottieitem.cpp
generated
vendored
626
vendor/github.com/Benau/go_rlottie/lottie_lottieitem.h
generated
vendored
@@ -1,626 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved.
|
|
||||||
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef LOTTIEITEM_H
|
|
||||||
#define LOTTIEITEM_H
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
#include "lottie_lottiekeypath.h"
|
|
||||||
#include "lottie_lottiefiltermodel.h"
|
|
||||||
#include "rlottie.h"
|
|
||||||
#include "rlottiecommon.h"
|
|
||||||
#include "vector_varenaalloc.h"
|
|
||||||
#include "vector_vdrawable.h"
|
|
||||||
#include "vector_vmatrix.h"
|
|
||||||
#include "vector_vpainter.h"
|
|
||||||
#include "vector_vpath.h"
|
|
||||||
#include "vector_vpathmesure.h"
|
|
||||||
#include "vector_vpoint.h"
|
|
||||||
|
|
||||||
V_USE_NAMESPACE
|
|
||||||
|
|
||||||
namespace rlottie {
|
|
||||||
|
|
||||||
namespace internal {
|
|
||||||
|
|
||||||
template <class T>
|
|
||||||
class VSpan {
|
|
||||||
public:
|
|
||||||
using reference = T &;
|
|
||||||
using pointer = T *;
|
|
||||||
using const_pointer = T const *;
|
|
||||||
using const_reference = T const &;
|
|
||||||
using index_type = size_t;
|
|
||||||
|
|
||||||
using iterator = pointer;
|
|
||||||
using const_iterator = const_pointer;
|
|
||||||
|
|
||||||
VSpan() = default;
|
|
||||||
VSpan(pointer data, index_type size) : _data(data), _size(size) {}
|
|
||||||
|
|
||||||
constexpr pointer data() const noexcept { return _data; }
|
|
||||||
constexpr index_type size() const noexcept { return _size; }
|
|
||||||
constexpr bool empty() const noexcept { return size() == 0; }
|
|
||||||
constexpr iterator begin() const noexcept { return data(); }
|
|
||||||
constexpr iterator end() const noexcept { return data() + size(); }
|
|
||||||
constexpr const_iterator cbegin() const noexcept { return data(); }
|
|
||||||
constexpr const_iterator cend() const noexcept { return data() + size(); }
|
|
||||||
constexpr reference operator[](index_type idx) const
|
|
||||||
{
|
|
||||||
return *(data() + idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
pointer _data{nullptr};
|
|
||||||
index_type _size{0};
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace renderer {
|
|
||||||
|
|
||||||
using DrawableList = VSpan<VDrawable *>;
|
|
||||||
|
|
||||||
enum class DirtyFlagBit : uchar {
|
|
||||||
None = 0x00,
|
|
||||||
Matrix = 0x01,
|
|
||||||
Alpha = 0x02,
|
|
||||||
All = (Matrix | Alpha)
|
|
||||||
};
|
|
||||||
typedef vFlag<DirtyFlagBit> DirtyFlag;
|
|
||||||
|
|
||||||
class SurfaceCache {
|
|
||||||
public:
|
|
||||||
SurfaceCache() { mCache.reserve(10); }
|
|
||||||
|
|
||||||
VBitmap make_surface(
|
|
||||||
size_t width, size_t height,
|
|
||||||
VBitmap::Format format = VBitmap::Format::ARGB32_Premultiplied)
|
|
||||||
{
|
|
||||||
if (mCache.empty()) return {width, height, format};
|
|
||||||
|
|
||||||
auto surface = mCache.back();
|
|
||||||
surface.reset(width, height, format);
|
|
||||||
|
|
||||||
mCache.pop_back();
|
|
||||||
return surface;
|
|
||||||
}
|
|
||||||
|
|
||||||
void release_surface(VBitmap &surface) { mCache.push_back(surface); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<VBitmap> mCache;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Drawable final : public VDrawable {
|
|
||||||
public:
|
|
||||||
void sync();
|
|
||||||
|
|
||||||
public:
|
|
||||||
std::unique_ptr<LOTNode> mCNode{nullptr};
|
|
||||||
|
|
||||||
~Drawable() noexcept
|
|
||||||
{
|
|
||||||
if (mCNode && mCNode->mGradient.stopPtr)
|
|
||||||
free(mCNode->mGradient.stopPtr);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CApiData {
|
|
||||||
CApiData();
|
|
||||||
LOTLayerNode mLayer;
|
|
||||||
std::vector<LOTMask> mMasks;
|
|
||||||
std::vector<LOTLayerNode *> mLayers;
|
|
||||||
std::vector<LOTNode *> mCNodeList;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Clipper {
|
|
||||||
public:
|
|
||||||
explicit Clipper(VSize size) : mSize(size) {}
|
|
||||||
void update(const VMatrix &matrix);
|
|
||||||
void preprocess(const VRect &clip);
|
|
||||||
VRle rle(const VRle &mask);
|
|
||||||
|
|
||||||
public:
|
|
||||||
VSize mSize;
|
|
||||||
VPath mPath;
|
|
||||||
VRle mMaskedRle;
|
|
||||||
VRasterizer mRasterizer;
|
|
||||||
bool mRasterRequest{false};
|
|
||||||
};
|
|
||||||
|
|
||||||
class Mask {
|
|
||||||
public:
|
|
||||||
explicit Mask(model::Mask *data) : mData(data) {}
|
|
||||||
void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha,
|
|
||||||
const DirtyFlag &flag);
|
|
||||||
model::Mask::Mode maskMode() const { return mData->mMode; }
|
|
||||||
VRle rle();
|
|
||||||
void preprocess(const VRect &clip);
|
|
||||||
bool inverted() const { return mData->mInv; }
|
|
||||||
public:
|
|
||||||
model::Mask *mData{nullptr};
|
|
||||||
VPath mLocalPath;
|
|
||||||
VPath mFinalPath;
|
|
||||||
VRasterizer mRasterizer;
|
|
||||||
float mCombinedAlpha{0};
|
|
||||||
bool mRasterRequest{false};
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Handels mask property of a layer item
|
|
||||||
*/
|
|
||||||
class LayerMask {
|
|
||||||
public:
|
|
||||||
explicit LayerMask(model::Layer *layerData);
|
|
||||||
void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha,
|
|
||||||
const DirtyFlag &flag);
|
|
||||||
bool isStatic() const { return mStatic; }
|
|
||||||
VRle maskRle(const VRect &clipRect);
|
|
||||||
void preprocess(const VRect &clip);
|
|
||||||
|
|
||||||
public:
|
|
||||||
std::vector<Mask> mMasks;
|
|
||||||
VRle mRle;
|
|
||||||
bool mStatic{true};
|
|
||||||
bool mDirty{true};
|
|
||||||
};
|
|
||||||
|
|
||||||
class Layer;
|
|
||||||
|
|
||||||
class Composition {
|
|
||||||
public:
|
|
||||||
explicit Composition(std::shared_ptr<model::Composition> composition);
|
|
||||||
bool update(int frameNo, const VSize &size, bool keepAspectRatio);
|
|
||||||
VSize size() const { return mViewSize; }
|
|
||||||
void buildRenderTree();
|
|
||||||
const LOTLayerNode *renderTree() const;
|
|
||||||
bool render(const rlottie::Surface &surface);
|
|
||||||
void setValue(const std::string &keypath, LOTVariant &value);
|
|
||||||
|
|
||||||
private:
|
|
||||||
SurfaceCache mSurfaceCache;
|
|
||||||
VBitmap mSurface;
|
|
||||||
VMatrix mScaleMatrix;
|
|
||||||
VSize mViewSize;
|
|
||||||
std::shared_ptr<model::Composition> mModel;
|
|
||||||
Layer * mRootLayer{nullptr};
|
|
||||||
VArenaAlloc mAllocator{2048};
|
|
||||||
int mCurFrameNo;
|
|
||||||
bool mKeepAspectRatio{true};
|
|
||||||
};
|
|
||||||
|
|
||||||
class Layer {
|
|
||||||
public:
|
|
||||||
virtual ~Layer() = default;
|
|
||||||
Layer &operator=(Layer &&) noexcept = delete;
|
|
||||||
Layer(model::Layer *layerData);
|
|
||||||
int id() const { return mLayerData->id(); }
|
|
||||||
int parentId() const { return mLayerData->parentId(); }
|
|
||||||
void setParentLayer(Layer *parent) { mParentLayer = parent; }
|
|
||||||
void setComplexContent(bool value) { mComplexContent = value; }
|
|
||||||
bool complexContent() const { return mComplexContent; }
|
|
||||||
virtual void update(int frameNo, const VMatrix &parentMatrix,
|
|
||||||
float parentAlpha);
|
|
||||||
VMatrix matrix(int frameNo) const;
|
|
||||||
void preprocess(const VRect &clip);
|
|
||||||
virtual DrawableList renderList() { return {}; }
|
|
||||||
virtual void render(VPainter *painter, const VRle &mask,
|
|
||||||
const VRle &matteRle, SurfaceCache &cache);
|
|
||||||
bool hasMatte()
|
|
||||||
{
|
|
||||||
if (mLayerData->mMatteType == model::MatteType::None) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
model::MatteType matteType() const { return mLayerData->mMatteType; }
|
|
||||||
bool visible() const;
|
|
||||||
virtual void buildLayerNode();
|
|
||||||
LOTLayerNode & clayer() { return mCApiData->mLayer; }
|
|
||||||
std::vector<LOTLayerNode *> &clayers() { return mCApiData->mLayers; }
|
|
||||||
std::vector<LOTMask> & cmasks() { return mCApiData->mMasks; }
|
|
||||||
std::vector<LOTNode *> & cnodes() { return mCApiData->mCNodeList; }
|
|
||||||
const char * name() const { return mLayerData->name(); }
|
|
||||||
virtual bool resolveKeyPath(LOTKeyPath &keyPath, uint depth,
|
|
||||||
LOTVariant &value);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void preprocessStage(const VRect &clip) = 0;
|
|
||||||
virtual void updateContent() = 0;
|
|
||||||
inline VMatrix combinedMatrix() const { return mCombinedMatrix; }
|
|
||||||
inline int frameNo() const { return mFrameNo; }
|
|
||||||
inline float combinedAlpha() const { return mCombinedAlpha; }
|
|
||||||
inline bool isStatic() const { return mLayerData->isStatic(); }
|
|
||||||
float opacity(int frameNo) const { return mLayerData->opacity(frameNo); }
|
|
||||||
inline DirtyFlag flag() const { return mDirtyFlag; }
|
|
||||||
bool skipRendering() const
|
|
||||||
{
|
|
||||||
return (!visible() || vIsZero(combinedAlpha()));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::unique_ptr<LayerMask> mLayerMask;
|
|
||||||
model::Layer * mLayerData{nullptr};
|
|
||||||
Layer * mParentLayer{nullptr};
|
|
||||||
VMatrix mCombinedMatrix;
|
|
||||||
float mCombinedAlpha{0.0};
|
|
||||||
int mFrameNo{-1};
|
|
||||||
DirtyFlag mDirtyFlag{DirtyFlagBit::All};
|
|
||||||
bool mComplexContent{false};
|
|
||||||
std::unique_ptr<CApiData> mCApiData;
|
|
||||||
};
|
|
||||||
|
|
||||||
class CompLayer final : public Layer {
|
|
||||||
public:
|
|
||||||
explicit CompLayer(model::Layer *layerData, VArenaAlloc *allocator);
|
|
||||||
|
|
||||||
void render(VPainter *painter, const VRle &mask, const VRle &matteRle,
|
|
||||||
SurfaceCache &cache) final;
|
|
||||||
void buildLayerNode() final;
|
|
||||||
bool resolveKeyPath(LOTKeyPath &keyPath, uint depth,
|
|
||||||
LOTVariant &value) override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void preprocessStage(const VRect &clip) final;
|
|
||||||
void updateContent() final;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void renderHelper(VPainter *painter, const VRle &mask, const VRle &matteRle,
|
|
||||||
SurfaceCache &cache);
|
|
||||||
void renderMatteLayer(VPainter *painter, const VRle &inheritMask,
|
|
||||||
const VRle &matteRle, Layer *layer, Layer *src,
|
|
||||||
SurfaceCache &cache);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<Layer *> mLayers;
|
|
||||||
std::unique_ptr<Clipper> mClipper;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SolidLayer final : public Layer {
|
|
||||||
public:
|
|
||||||
explicit SolidLayer(model::Layer *layerData);
|
|
||||||
void buildLayerNode() final;
|
|
||||||
DrawableList renderList() final;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void preprocessStage(const VRect &clip) final;
|
|
||||||
void updateContent() final;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Drawable mRenderNode;
|
|
||||||
VPath mPath;
|
|
||||||
VDrawable *mDrawableList{nullptr}; // to work with the Span api
|
|
||||||
};
|
|
||||||
|
|
||||||
class Group;
|
|
||||||
|
|
||||||
class ShapeLayer final : public Layer {
|
|
||||||
public:
|
|
||||||
explicit ShapeLayer(model::Layer *layerData, VArenaAlloc *allocator);
|
|
||||||
DrawableList renderList() final;
|
|
||||||
void buildLayerNode() final;
|
|
||||||
bool resolveKeyPath(LOTKeyPath &keyPath, uint depth,
|
|
||||||
LOTVariant &value) override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void preprocessStage(const VRect &clip) final;
|
|
||||||
void updateContent() final;
|
|
||||||
std::vector<VDrawable *> mDrawableList;
|
|
||||||
Group * mRoot{nullptr};
|
|
||||||
};
|
|
||||||
|
|
||||||
class NullLayer final : public Layer {
|
|
||||||
public:
|
|
||||||
explicit NullLayer(model::Layer *layerData);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void preprocessStage(const VRect &) final {}
|
|
||||||
void updateContent() final;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ImageLayer final : public Layer {
|
|
||||||
public:
|
|
||||||
explicit ImageLayer(model::Layer *layerData);
|
|
||||||
void buildLayerNode() final;
|
|
||||||
DrawableList renderList() final;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void preprocessStage(const VRect &clip) final;
|
|
||||||
void updateContent() final;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Drawable mRenderNode;
|
|
||||||
VTexture mTexture;
|
|
||||||
VPath mPath;
|
|
||||||
VDrawable *mDrawableList{nullptr}; // to work with the Span api
|
|
||||||
};
|
|
||||||
|
|
||||||
class Object {
|
|
||||||
public:
|
|
||||||
enum class Type : uchar { Unknown, Group, Shape, Paint, Trim };
|
|
||||||
virtual ~Object() = default;
|
|
||||||
Object & operator=(Object &&) noexcept = delete;
|
|
||||||
virtual void update(int frameNo, const VMatrix &parentMatrix,
|
|
||||||
float parentAlpha, const DirtyFlag &flag) = 0;
|
|
||||||
virtual void renderList(std::vector<VDrawable *> &) {}
|
|
||||||
virtual bool resolveKeyPath(LOTKeyPath &, uint, LOTVariant &)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
virtual Object::Type type() const { return Object::Type::Unknown; }
|
|
||||||
};
|
|
||||||
|
|
||||||
class Shape;
|
|
||||||
class Group : public Object {
|
|
||||||
public:
|
|
||||||
Group() = default;
|
|
||||||
explicit Group(model::Group *data, VArenaAlloc *allocator);
|
|
||||||
void addChildren(model::Group *data, VArenaAlloc *allocator);
|
|
||||||
void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha,
|
|
||||||
const DirtyFlag &flag) override;
|
|
||||||
void applyTrim();
|
|
||||||
void processTrimItems(std::vector<Shape *> &list);
|
|
||||||
void processPaintItems(std::vector<Shape *> &list);
|
|
||||||
void renderList(std::vector<VDrawable *> &list) override;
|
|
||||||
Object::Type type() const final { return Object::Type::Group; }
|
|
||||||
const VMatrix &matrix() const { return mMatrix; }
|
|
||||||
const char * name() const
|
|
||||||
{
|
|
||||||
static const char *TAG = "__";
|
|
||||||
return mModel.hasModel() ? mModel.name() : TAG;
|
|
||||||
}
|
|
||||||
bool resolveKeyPath(LOTKeyPath &keyPath, uint depth,
|
|
||||||
LOTVariant &value) override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::vector<Object *> mContents;
|
|
||||||
VMatrix mMatrix;
|
|
||||||
|
|
||||||
private:
|
|
||||||
model::Filter<model::Group> mModel;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Shape : public Object {
|
|
||||||
public:
|
|
||||||
Shape(bool staticPath) : mStaticPath(staticPath) {}
|
|
||||||
void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha,
|
|
||||||
const DirtyFlag &flag) final;
|
|
||||||
Object::Type type() const final { return Object::Type::Shape; }
|
|
||||||
bool dirty() const { return mDirtyPath; }
|
|
||||||
const VPath &localPath() const { return mTemp; }
|
|
||||||
void finalPath(VPath &result);
|
|
||||||
void updatePath(const VPath &path)
|
|
||||||
{
|
|
||||||
mTemp = path;
|
|
||||||
mDirtyPath = true;
|
|
||||||
}
|
|
||||||
bool staticPath() const { return mStaticPath; }
|
|
||||||
void setParent(Group *parent) { mParent = parent; }
|
|
||||||
Group *parent() const { return mParent; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void updatePath(VPath &path, int frameNo) = 0;
|
|
||||||
virtual bool hasChanged(int prevFrame, int curFrame) = 0;
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool hasChanged(int frameNo)
|
|
||||||
{
|
|
||||||
int prevFrame = mFrameNo;
|
|
||||||
mFrameNo = frameNo;
|
|
||||||
if (prevFrame == -1) return true;
|
|
||||||
if (mStaticPath || (prevFrame == frameNo)) return false;
|
|
||||||
return hasChanged(prevFrame, frameNo);
|
|
||||||
}
|
|
||||||
Group *mParent{nullptr};
|
|
||||||
VPath mLocalPath;
|
|
||||||
VPath mTemp;
|
|
||||||
int mFrameNo{-1};
|
|
||||||
bool mDirtyPath{true};
|
|
||||||
bool mStaticPath;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Rect final : public Shape {
|
|
||||||
public:
|
|
||||||
explicit Rect(model::Rect *data);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void updatePath(VPath &path, int frameNo) final;
|
|
||||||
model::Rect *mData{nullptr};
|
|
||||||
|
|
||||||
bool hasChanged(int prevFrame, int curFrame) final
|
|
||||||
{
|
|
||||||
return (mData->mPos.changed(prevFrame, curFrame) ||
|
|
||||||
mData->mSize.changed(prevFrame, curFrame) ||
|
|
||||||
mData->roundnessChanged(prevFrame, curFrame));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class Ellipse final : public Shape {
|
|
||||||
public:
|
|
||||||
explicit Ellipse(model::Ellipse *data);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void updatePath(VPath &path, int frameNo) final;
|
|
||||||
model::Ellipse *mData{nullptr};
|
|
||||||
bool hasChanged(int prevFrame, int curFrame) final
|
|
||||||
{
|
|
||||||
return (mData->mPos.changed(prevFrame, curFrame) ||
|
|
||||||
mData->mSize.changed(prevFrame, curFrame));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class Path final : public Shape {
|
|
||||||
public:
|
|
||||||
explicit Path(model::Path *data);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void updatePath(VPath &path, int frameNo) final;
|
|
||||||
model::Path *mData{nullptr};
|
|
||||||
bool hasChanged(int prevFrame, int curFrame) final
|
|
||||||
{
|
|
||||||
return mData->mShape.changed(prevFrame, curFrame);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class Polystar final : public Shape {
|
|
||||||
public:
|
|
||||||
explicit Polystar(model::Polystar *data);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void updatePath(VPath &path, int frameNo) final;
|
|
||||||
model::Polystar *mData{nullptr};
|
|
||||||
|
|
||||||
bool hasChanged(int prevFrame, int curFrame) final
|
|
||||||
{
|
|
||||||
return (mData->mPos.changed(prevFrame, curFrame) ||
|
|
||||||
mData->mPointCount.changed(prevFrame, curFrame) ||
|
|
||||||
mData->mInnerRadius.changed(prevFrame, curFrame) ||
|
|
||||||
mData->mOuterRadius.changed(prevFrame, curFrame) ||
|
|
||||||
mData->mInnerRoundness.changed(prevFrame, curFrame) ||
|
|
||||||
mData->mOuterRoundness.changed(prevFrame, curFrame) ||
|
|
||||||
mData->mRotation.changed(prevFrame, curFrame));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class Paint : public Object {
|
|
||||||
public:
|
|
||||||
Paint(bool staticContent);
|
|
||||||
void addPathItems(std::vector<Shape *> &list, size_t startOffset);
|
|
||||||
void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha,
|
|
||||||
const DirtyFlag &flag) override;
|
|
||||||
void renderList(std::vector<VDrawable *> &list) final;
|
|
||||||
Object::Type type() const final { return Object::Type::Paint; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual bool updateContent(int frameNo, const VMatrix &matrix,
|
|
||||||
float alpha) = 0;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void updateRenderNode();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::vector<Shape *> mPathItems;
|
|
||||||
Drawable mDrawable;
|
|
||||||
VPath mPath;
|
|
||||||
DirtyFlag mFlag;
|
|
||||||
bool mStaticContent;
|
|
||||||
bool mRenderNodeUpdate{true};
|
|
||||||
bool mContentToRender{true};
|
|
||||||
};
|
|
||||||
|
|
||||||
class Fill final : public Paint {
|
|
||||||
public:
|
|
||||||
explicit Fill(model::Fill *data);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool updateContent(int frameNo, const VMatrix &matrix, float alpha) final;
|
|
||||||
bool resolveKeyPath(LOTKeyPath &keyPath, uint depth,
|
|
||||||
LOTVariant &value) final;
|
|
||||||
|
|
||||||
private:
|
|
||||||
model::Filter<model::Fill> mModel;
|
|
||||||
};
|
|
||||||
|
|
||||||
class GradientFill final : public Paint {
|
|
||||||
public:
|
|
||||||
explicit GradientFill(model::GradientFill *data);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool updateContent(int frameNo, const VMatrix &matrix, float alpha) final;
|
|
||||||
|
|
||||||
private:
|
|
||||||
model::GradientFill * mData{nullptr};
|
|
||||||
std::unique_ptr<VGradient> mGradient;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Stroke : public Paint {
|
|
||||||
public:
|
|
||||||
explicit Stroke(model::Stroke *data);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool updateContent(int frameNo, const VMatrix &matrix, float alpha) final;
|
|
||||||
bool resolveKeyPath(LOTKeyPath &keyPath, uint depth,
|
|
||||||
LOTVariant &value) final;
|
|
||||||
|
|
||||||
private:
|
|
||||||
model::Filter<model::Stroke> mModel;
|
|
||||||
};
|
|
||||||
|
|
||||||
class GradientStroke final : public Paint {
|
|
||||||
public:
|
|
||||||
explicit GradientStroke(model::GradientStroke *data);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool updateContent(int frameNo, const VMatrix &matrix, float alpha) final;
|
|
||||||
|
|
||||||
private:
|
|
||||||
model::GradientStroke * mData{nullptr};
|
|
||||||
std::unique_ptr<VGradient> mGradient;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Trim final : public Object {
|
|
||||||
public:
|
|
||||||
explicit Trim(model::Trim *data) : mData(data) {}
|
|
||||||
void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha,
|
|
||||||
const DirtyFlag &flag) final;
|
|
||||||
Object::Type type() const final { return Object::Type::Trim; }
|
|
||||||
void update();
|
|
||||||
void addPathItems(std::vector<Shape *> &list, size_t startOffset);
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool pathDirty() const
|
|
||||||
{
|
|
||||||
for (auto &i : mPathItems) {
|
|
||||||
if (i->dirty()) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
struct Cache {
|
|
||||||
int mFrameNo{-1};
|
|
||||||
model::Trim::Segment mSegment{};
|
|
||||||
};
|
|
||||||
Cache mCache;
|
|
||||||
std::vector<Shape *> mPathItems;
|
|
||||||
model::Trim * mData{nullptr};
|
|
||||||
VPathMesure mPathMesure;
|
|
||||||
bool mDirty{true};
|
|
||||||
};
|
|
||||||
|
|
||||||
class Repeater final : public Group {
|
|
||||||
public:
|
|
||||||
explicit Repeater(model::Repeater *data, VArenaAlloc *allocator);
|
|
||||||
void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha,
|
|
||||||
const DirtyFlag &flag) final;
|
|
||||||
void renderList(std::vector<VDrawable *> &list) final;
|
|
||||||
|
|
||||||
private:
|
|
||||||
model::Repeater *mRepeaterData{nullptr};
|
|
||||||
bool mHidden{false};
|
|
||||||
int mCopies{0};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace renderer
|
|
||||||
|
|
||||||
} // namespace internal
|
|
||||||
|
|
||||||
} // namespace rlottie
|
|
||||||
|
|
||||||
#endif // LOTTIEITEM_H
|
|
||||||
339
vendor/github.com/Benau/go_rlottie/lottie_lottieitem_capi.cpp
generated
vendored
@@ -1,339 +0,0 @@
|
|||||||
/*
|
|
||||||
* Implements LottieItem functions needed
|
|
||||||
* to support renderTree() api.
|
|
||||||
* Moving all those implementation to its own
|
|
||||||
* file make clear separation as well easy of
|
|
||||||
* maintenance.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "lottie_lottieitem.h"
|
|
||||||
#include "vector_vdasher.h"
|
|
||||||
|
|
||||||
using namespace rlottie::internal;
|
|
||||||
|
|
||||||
renderer::CApiData::CApiData()
|
|
||||||
{
|
|
||||||
mLayer.mMaskList.ptr = nullptr;
|
|
||||||
mLayer.mMaskList.size = 0;
|
|
||||||
mLayer.mLayerList.ptr = nullptr;
|
|
||||||
mLayer.mLayerList.size = 0;
|
|
||||||
mLayer.mNodeList.ptr = nullptr;
|
|
||||||
mLayer.mNodeList.size = 0;
|
|
||||||
mLayer.mMatte = MatteNone;
|
|
||||||
mLayer.mVisible = 0;
|
|
||||||
mLayer.mAlpha = 255;
|
|
||||||
mLayer.mClipPath.ptPtr = nullptr;
|
|
||||||
mLayer.mClipPath.elmPtr = nullptr;
|
|
||||||
mLayer.mClipPath.ptCount = 0;
|
|
||||||
mLayer.mClipPath.elmCount = 0;
|
|
||||||
mLayer.keypath = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void renderer::Composition::buildRenderTree()
|
|
||||||
{
|
|
||||||
mRootLayer->buildLayerNode();
|
|
||||||
}
|
|
||||||
|
|
||||||
const LOTLayerNode *renderer::Composition::renderTree() const
|
|
||||||
{
|
|
||||||
return &mRootLayer->clayer();
|
|
||||||
}
|
|
||||||
|
|
||||||
void renderer::CompLayer::buildLayerNode()
|
|
||||||
{
|
|
||||||
renderer::Layer::buildLayerNode();
|
|
||||||
if (mClipper) {
|
|
||||||
const auto &elm = mClipper->mPath.elements();
|
|
||||||
const auto &pts = mClipper->mPath.points();
|
|
||||||
auto ptPtr = reinterpret_cast<const float *>(pts.data());
|
|
||||||
auto elmPtr = reinterpret_cast<const char *>(elm.data());
|
|
||||||
clayer().mClipPath.ptPtr = ptPtr;
|
|
||||||
clayer().mClipPath.elmPtr = elmPtr;
|
|
||||||
clayer().mClipPath.ptCount = 2 * pts.size();
|
|
||||||
clayer().mClipPath.elmCount = elm.size();
|
|
||||||
}
|
|
||||||
if (mLayers.size() != clayers().size()) {
|
|
||||||
for (const auto &layer : mLayers) {
|
|
||||||
layer->buildLayerNode();
|
|
||||||
clayers().push_back(&layer->clayer());
|
|
||||||
}
|
|
||||||
clayer().mLayerList.ptr = clayers().data();
|
|
||||||
clayer().mLayerList.size = clayers().size();
|
|
||||||
} else {
|
|
||||||
for (const auto &layer : mLayers) {
|
|
||||||
layer->buildLayerNode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void renderer::ShapeLayer::buildLayerNode()
|
|
||||||
{
|
|
||||||
renderer::Layer::buildLayerNode();
|
|
||||||
|
|
||||||
auto renderlist = renderList();
|
|
||||||
|
|
||||||
cnodes().clear();
|
|
||||||
for (auto &i : renderlist) {
|
|
||||||
auto lotDrawable = static_cast<renderer::Drawable *>(i);
|
|
||||||
lotDrawable->sync();
|
|
||||||
cnodes().push_back(lotDrawable->mCNode.get());
|
|
||||||
}
|
|
||||||
clayer().mNodeList.ptr = cnodes().data();
|
|
||||||
clayer().mNodeList.size = cnodes().size();
|
|
||||||
}
|
|
||||||
|
|
||||||
void renderer::Layer::buildLayerNode()
|
|
||||||
{
|
|
||||||
if (!mCApiData) {
|
|
||||||
mCApiData = std::make_unique<renderer::CApiData>();
|
|
||||||
clayer().keypath = name();
|
|
||||||
}
|
|
||||||
if (complexContent()) clayer().mAlpha = uchar(combinedAlpha() * 255.f);
|
|
||||||
clayer().mVisible = visible();
|
|
||||||
// update matte
|
|
||||||
if (hasMatte()) {
|
|
||||||
switch (mLayerData->mMatteType) {
|
|
||||||
case model::MatteType::Alpha:
|
|
||||||
clayer().mMatte = MatteAlpha;
|
|
||||||
break;
|
|
||||||
case model::MatteType::AlphaInv:
|
|
||||||
clayer().mMatte = MatteAlphaInv;
|
|
||||||
break;
|
|
||||||
case model::MatteType::Luma:
|
|
||||||
clayer().mMatte = MatteLuma;
|
|
||||||
break;
|
|
||||||
case model::MatteType::LumaInv:
|
|
||||||
clayer().mMatte = MatteLumaInv;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
clayer().mMatte = MatteNone;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mLayerMask) {
|
|
||||||
cmasks().clear();
|
|
||||||
cmasks().resize(mLayerMask->mMasks.size());
|
|
||||||
size_t i = 0;
|
|
||||||
for (const auto &mask : mLayerMask->mMasks) {
|
|
||||||
auto & cNode = cmasks()[i++];
|
|
||||||
const auto &elm = mask.mFinalPath.elements();
|
|
||||||
const auto &pts = mask.mFinalPath.points();
|
|
||||||
auto ptPtr = reinterpret_cast<const float *>(pts.data());
|
|
||||||
auto elmPtr = reinterpret_cast<const char *>(elm.data());
|
|
||||||
cNode.mPath.ptPtr = ptPtr;
|
|
||||||
cNode.mPath.ptCount = 2 * pts.size();
|
|
||||||
cNode.mPath.elmPtr = elmPtr;
|
|
||||||
cNode.mPath.elmCount = elm.size();
|
|
||||||
cNode.mAlpha = uchar(mask.mCombinedAlpha * 255.0f);
|
|
||||||
switch (mask.maskMode()) {
|
|
||||||
case model::Mask::Mode::Add:
|
|
||||||
cNode.mMode = MaskAdd;
|
|
||||||
break;
|
|
||||||
case model::Mask::Mode::Substarct:
|
|
||||||
cNode.mMode = MaskSubstract;
|
|
||||||
break;
|
|
||||||
case model::Mask::Mode::Intersect:
|
|
||||||
cNode.mMode = MaskIntersect;
|
|
||||||
break;
|
|
||||||
case model::Mask::Mode::Difference:
|
|
||||||
cNode.mMode = MaskDifference;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
cNode.mMode = MaskAdd;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
clayer().mMaskList.ptr = cmasks().data();
|
|
||||||
clayer().mMaskList.size = cmasks().size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void renderer::SolidLayer::buildLayerNode()
|
|
||||||
{
|
|
||||||
renderer::Layer::buildLayerNode();
|
|
||||||
|
|
||||||
auto renderlist = renderList();
|
|
||||||
|
|
||||||
cnodes().clear();
|
|
||||||
for (auto &i : renderlist) {
|
|
||||||
auto lotDrawable = static_cast<renderer::Drawable *>(i);
|
|
||||||
lotDrawable->sync();
|
|
||||||
cnodes().push_back(lotDrawable->mCNode.get());
|
|
||||||
}
|
|
||||||
clayer().mNodeList.ptr = cnodes().data();
|
|
||||||
clayer().mNodeList.size = cnodes().size();
|
|
||||||
}
|
|
||||||
|
|
||||||
void renderer::ImageLayer::buildLayerNode()
|
|
||||||
{
|
|
||||||
renderer::Layer::buildLayerNode();
|
|
||||||
|
|
||||||
auto renderlist = renderList();
|
|
||||||
|
|
||||||
cnodes().clear();
|
|
||||||
for (auto &i : renderlist) {
|
|
||||||
auto lotDrawable = static_cast<renderer::Drawable *>(i);
|
|
||||||
lotDrawable->sync();
|
|
||||||
|
|
||||||
lotDrawable->mCNode->mImageInfo.data =
|
|
||||||
lotDrawable->mBrush.mTexture->mBitmap.data();
|
|
||||||
lotDrawable->mCNode->mImageInfo.width =
|
|
||||||
int(lotDrawable->mBrush.mTexture->mBitmap.width());
|
|
||||||
lotDrawable->mCNode->mImageInfo.height =
|
|
||||||
int(lotDrawable->mBrush.mTexture->mBitmap.height());
|
|
||||||
|
|
||||||
lotDrawable->mCNode->mImageInfo.mMatrix.m11 = combinedMatrix().m_11();
|
|
||||||
lotDrawable->mCNode->mImageInfo.mMatrix.m12 = combinedMatrix().m_12();
|
|
||||||
lotDrawable->mCNode->mImageInfo.mMatrix.m13 = combinedMatrix().m_13();
|
|
||||||
|
|
||||||
lotDrawable->mCNode->mImageInfo.mMatrix.m21 = combinedMatrix().m_21();
|
|
||||||
lotDrawable->mCNode->mImageInfo.mMatrix.m22 = combinedMatrix().m_22();
|
|
||||||
lotDrawable->mCNode->mImageInfo.mMatrix.m23 = combinedMatrix().m_23();
|
|
||||||
|
|
||||||
lotDrawable->mCNode->mImageInfo.mMatrix.m31 = combinedMatrix().m_tx();
|
|
||||||
lotDrawable->mCNode->mImageInfo.mMatrix.m32 = combinedMatrix().m_ty();
|
|
||||||
lotDrawable->mCNode->mImageInfo.mMatrix.m33 = combinedMatrix().m_33();
|
|
||||||
|
|
||||||
// Alpha calculation already combined.
|
|
||||||
lotDrawable->mCNode->mImageInfo.mAlpha =
|
|
||||||
uchar(lotDrawable->mBrush.mTexture->mAlpha);
|
|
||||||
|
|
||||||
cnodes().push_back(lotDrawable->mCNode.get());
|
|
||||||
}
|
|
||||||
clayer().mNodeList.ptr = cnodes().data();
|
|
||||||
clayer().mNodeList.size = cnodes().size();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void updateGStops(LOTNode *n, const VGradient *grad)
|
|
||||||
{
|
|
||||||
if (grad->mStops.size() != n->mGradient.stopCount) {
|
|
||||||
if (n->mGradient.stopCount) free(n->mGradient.stopPtr);
|
|
||||||
n->mGradient.stopCount = grad->mStops.size();
|
|
||||||
n->mGradient.stopPtr = (LOTGradientStop *)malloc(
|
|
||||||
n->mGradient.stopCount * sizeof(LOTGradientStop));
|
|
||||||
}
|
|
||||||
|
|
||||||
LOTGradientStop *ptr = n->mGradient.stopPtr;
|
|
||||||
for (const auto &i : grad->mStops) {
|
|
||||||
ptr->pos = i.first;
|
|
||||||
ptr->a = uchar(i.second.alpha() * grad->alpha());
|
|
||||||
ptr->r = i.second.red();
|
|
||||||
ptr->g = i.second.green();
|
|
||||||
ptr->b = i.second.blue();
|
|
||||||
ptr++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void renderer::Drawable::sync()
|
|
||||||
{
|
|
||||||
if (!mCNode) {
|
|
||||||
mCNode = std::make_unique<LOTNode>();
|
|
||||||
mCNode->mGradient.stopPtr = nullptr;
|
|
||||||
mCNode->mGradient.stopCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
mCNode->mFlag = ChangeFlagNone;
|
|
||||||
if (mFlag & DirtyState::None) return;
|
|
||||||
|
|
||||||
if (mFlag & DirtyState::Path) {
|
|
||||||
applyDashOp();
|
|
||||||
const std::vector<VPath::Element> &elm = mPath.elements();
|
|
||||||
const std::vector<VPointF> & pts = mPath.points();
|
|
||||||
const float *ptPtr = reinterpret_cast<const float *>(pts.data());
|
|
||||||
const char * elmPtr = reinterpret_cast<const char *>(elm.data());
|
|
||||||
mCNode->mPath.elmPtr = elmPtr;
|
|
||||||
mCNode->mPath.elmCount = elm.size();
|
|
||||||
mCNode->mPath.ptPtr = ptPtr;
|
|
||||||
mCNode->mPath.ptCount = 2 * pts.size();
|
|
||||||
mCNode->mFlag |= ChangeFlagPath;
|
|
||||||
mCNode->keypath = name();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mStrokeInfo) {
|
|
||||||
mCNode->mStroke.width = mStrokeInfo->width;
|
|
||||||
mCNode->mStroke.miterLimit = mStrokeInfo->miterLimit;
|
|
||||||
mCNode->mStroke.enable = 1;
|
|
||||||
|
|
||||||
switch (mStrokeInfo->cap) {
|
|
||||||
case CapStyle::Flat:
|
|
||||||
mCNode->mStroke.cap = LOTCapStyle::CapFlat;
|
|
||||||
break;
|
|
||||||
case CapStyle::Square:
|
|
||||||
mCNode->mStroke.cap = LOTCapStyle::CapSquare;
|
|
||||||
break;
|
|
||||||
case CapStyle::Round:
|
|
||||||
mCNode->mStroke.cap = LOTCapStyle::CapRound;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (mStrokeInfo->join) {
|
|
||||||
case JoinStyle::Miter:
|
|
||||||
mCNode->mStroke.join = LOTJoinStyle::JoinMiter;
|
|
||||||
break;
|
|
||||||
case JoinStyle::Bevel:
|
|
||||||
mCNode->mStroke.join = LOTJoinStyle::JoinBevel;
|
|
||||||
break;
|
|
||||||
case JoinStyle::Round:
|
|
||||||
mCNode->mStroke.join = LOTJoinStyle::JoinRound;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
mCNode->mStroke.join = LOTJoinStyle::JoinMiter;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mCNode->mStroke.enable = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (mFillRule) {
|
|
||||||
case FillRule::EvenOdd:
|
|
||||||
mCNode->mFillRule = LOTFillRule::FillEvenOdd;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
mCNode->mFillRule = LOTFillRule::FillWinding;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (mBrush.type()) {
|
|
||||||
case VBrush::Type::Solid:
|
|
||||||
mCNode->mBrushType = LOTBrushType::BrushSolid;
|
|
||||||
mCNode->mColor.r = mBrush.mColor.r;
|
|
||||||
mCNode->mColor.g = mBrush.mColor.g;
|
|
||||||
mCNode->mColor.b = mBrush.mColor.b;
|
|
||||||
mCNode->mColor.a = mBrush.mColor.a;
|
|
||||||
break;
|
|
||||||
case VBrush::Type::LinearGradient: {
|
|
||||||
mCNode->mBrushType = LOTBrushType::BrushGradient;
|
|
||||||
mCNode->mGradient.type = LOTGradientType::GradientLinear;
|
|
||||||
VPointF s = mBrush.mGradient->mMatrix.map(
|
|
||||||
{mBrush.mGradient->linear.x1, mBrush.mGradient->linear.y1});
|
|
||||||
VPointF e = mBrush.mGradient->mMatrix.map(
|
|
||||||
{mBrush.mGradient->linear.x2, mBrush.mGradient->linear.y2});
|
|
||||||
mCNode->mGradient.start.x = s.x();
|
|
||||||
mCNode->mGradient.start.y = s.y();
|
|
||||||
mCNode->mGradient.end.x = e.x();
|
|
||||||
mCNode->mGradient.end.y = e.y();
|
|
||||||
updateGStops(mCNode.get(), mBrush.mGradient);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case VBrush::Type::RadialGradient: {
|
|
||||||
mCNode->mBrushType = LOTBrushType::BrushGradient;
|
|
||||||
mCNode->mGradient.type = LOTGradientType::GradientRadial;
|
|
||||||
VPointF c = mBrush.mGradient->mMatrix.map(
|
|
||||||
{mBrush.mGradient->radial.cx, mBrush.mGradient->radial.cy});
|
|
||||||
VPointF f = mBrush.mGradient->mMatrix.map(
|
|
||||||
{mBrush.mGradient->radial.fx, mBrush.mGradient->radial.fy});
|
|
||||||
mCNode->mGradient.center.x = c.x();
|
|
||||||
mCNode->mGradient.center.y = c.y();
|
|
||||||
mCNode->mGradient.focal.x = f.x();
|
|
||||||
mCNode->mGradient.focal.y = f.y();
|
|
||||||
|
|
||||||
float scale = mBrush.mGradient->mMatrix.scale();
|
|
||||||
mCNode->mGradient.cradius = mBrush.mGradient->radial.cradius * scale;
|
|
||||||
mCNode->mGradient.fradius = mBrush.mGradient->radial.fradius * scale;
|
|
||||||
updateGStops(mCNode.get(), mBrush.mGradient);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
86
vendor/github.com/Benau/go_rlottie/lottie_lottiekeypath.cpp
generated
vendored
@@ -1,86 +0,0 @@
|
|||||||
#include "lottie_lottiekeypath.h"
|
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
LOTKeyPath::LOTKeyPath(const std::string &keyPath)
|
|
||||||
{
|
|
||||||
std::stringstream ss(keyPath);
|
|
||||||
std::string item;
|
|
||||||
|
|
||||||
while (getline(ss, item, '.')) {
|
|
||||||
mKeys.push_back(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LOTKeyPath::matches(const std::string &key, uint depth)
|
|
||||||
{
|
|
||||||
if (skip(key)) {
|
|
||||||
// This is an object we programatically create.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (depth > size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if ((mKeys[depth] == key) || (mKeys[depth] == "*") ||
|
|
||||||
(mKeys[depth] == "**")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint LOTKeyPath::nextDepth(const std::string key, uint depth)
|
|
||||||
{
|
|
||||||
if (skip(key)) {
|
|
||||||
// If it's a container then we added programatically and it isn't a part
|
|
||||||
// of the keypath.
|
|
||||||
return depth;
|
|
||||||
}
|
|
||||||
if (mKeys[depth] != "**") {
|
|
||||||
// If it's not a globstar then it is part of the keypath.
|
|
||||||
return depth + 1;
|
|
||||||
}
|
|
||||||
if (depth == size()) {
|
|
||||||
// The last key is a globstar.
|
|
||||||
return depth;
|
|
||||||
}
|
|
||||||
if (mKeys[depth + 1] == key) {
|
|
||||||
// We are a globstar and the next key is our current key so consume
|
|
||||||
// both.
|
|
||||||
return depth + 2;
|
|
||||||
}
|
|
||||||
return depth;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LOTKeyPath::fullyResolvesTo(const std::string key, uint depth)
|
|
||||||
{
|
|
||||||
if (depth > mKeys.size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isLastDepth = (depth == size());
|
|
||||||
|
|
||||||
if (!isGlobstar(depth)) {
|
|
||||||
bool matches = (mKeys[depth] == key) || isGlob(depth);
|
|
||||||
return (isLastDepth || (depth == size() - 1 && endsWithGlobstar())) &&
|
|
||||||
matches;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isGlobstarButNextKeyMatches = !isLastDepth && mKeys[depth + 1] == key;
|
|
||||||
if (isGlobstarButNextKeyMatches) {
|
|
||||||
return depth == size() - 1 ||
|
|
||||||
(depth == size() - 2 && endsWithGlobstar());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isLastDepth) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (depth + 1 < size()) {
|
|
||||||
// We are a globstar but there is more than 1 key after the globstar we
|
|
||||||
// we can't fully match.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Return whether the next key (which we now know is the last one) is the
|
|
||||||
// same as the current key.
|
|
||||||
return mKeys[depth + 1] == key;
|
|
||||||
}
|
|
||||||
53
vendor/github.com/Benau/go_rlottie/lottie_lottiekeypath.h
generated
vendored
@@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved.
|
|
||||||
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef LOTTIEKEYPATH_H
|
|
||||||
#define LOTTIEKEYPATH_H
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
#include "vector_vglobal.h"
|
|
||||||
|
|
||||||
class LOTKeyPath {
|
|
||||||
public:
|
|
||||||
LOTKeyPath(const std::string &keyPath);
|
|
||||||
bool matches(const std::string &key, uint depth);
|
|
||||||
uint nextDepth(const std::string key, uint depth);
|
|
||||||
bool fullyResolvesTo(const std::string key, uint depth);
|
|
||||||
|
|
||||||
bool propagate(const std::string key, uint depth)
|
|
||||||
{
|
|
||||||
return skip(key) ? true : (depth < size()) || (mKeys[depth] == "**");
|
|
||||||
}
|
|
||||||
bool skip(const std::string &key) const { return key == "__"; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool isGlobstar(uint depth) const { return mKeys[depth] == "**"; }
|
|
||||||
bool isGlob(uint depth) const { return mKeys[depth] == "*"; }
|
|
||||||
bool endsWithGlobstar() const { return mKeys.back() == "**"; }
|
|
||||||
size_t size() const { return mKeys.size() - 1; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<std::string> mKeys;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // LOTTIEKEYPATH_H
|
|
||||||
169
vendor/github.com/Benau/go_rlottie/lottie_lottieloader.cpp
generated
vendored
@@ -1,169 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved.
|
|
||||||
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
#include <fstream>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
#include "lottie_lottiemodel.h"
|
|
||||||
|
|
||||||
using namespace rlottie::internal;
|
|
||||||
|
|
||||||
#ifdef LOTTIE_CACHE_SUPPORT
|
|
||||||
|
|
||||||
#include <mutex>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
class ModelCache {
|
|
||||||
public:
|
|
||||||
static ModelCache &instance()
|
|
||||||
{
|
|
||||||
static ModelCache singleton;
|
|
||||||
return singleton;
|
|
||||||
}
|
|
||||||
std::shared_ptr<model::Composition> find(const std::string &key)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> guard(mMutex);
|
|
||||||
|
|
||||||
if (!mcacheSize) return nullptr;
|
|
||||||
|
|
||||||
auto search = mHash.find(key);
|
|
||||||
|
|
||||||
return (search != mHash.end()) ? search->second : nullptr;
|
|
||||||
}
|
|
||||||
void add(const std::string &key, std::shared_ptr<model::Composition> value)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> guard(mMutex);
|
|
||||||
|
|
||||||
if (!mcacheSize) return;
|
|
||||||
|
|
||||||
//@TODO just remove the 1st element
|
|
||||||
// not the best of LRU logic
|
|
||||||
if (mcacheSize == mHash.size()) mHash.erase(mHash.cbegin());
|
|
||||||
|
|
||||||
mHash[key] = std::move(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void configureCacheSize(size_t cacheSize)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> guard(mMutex);
|
|
||||||
mcacheSize = cacheSize;
|
|
||||||
|
|
||||||
if (!mcacheSize) mHash.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
ModelCache() = default;
|
|
||||||
|
|
||||||
std::unordered_map<std::string, std::shared_ptr<model::Composition>> mHash;
|
|
||||||
std::mutex mMutex;
|
|
||||||
size_t mcacheSize{10};
|
|
||||||
};
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
class ModelCache {
|
|
||||||
public:
|
|
||||||
static ModelCache &instance()
|
|
||||||
{
|
|
||||||
static ModelCache singleton;
|
|
||||||
return singleton;
|
|
||||||
}
|
|
||||||
std::shared_ptr<model::Composition> find(const std::string &)
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
void add(const std::string &, std::shared_ptr<model::Composition>) {}
|
|
||||||
void configureCacheSize(size_t) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static std::string dirname(const std::string &path)
|
|
||||||
{
|
|
||||||
const char *ptr = strrchr(path.c_str(), '/');
|
|
||||||
#ifdef _WIN32
|
|
||||||
if (ptr) ptr = strrchr(ptr + 1, '\\');
|
|
||||||
#endif
|
|
||||||
int len = int(ptr + 1 - path.c_str()); // +1 to include '/'
|
|
||||||
return std::string(path, 0, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
void model::configureModelCacheSize(size_t cacheSize)
|
|
||||||
{
|
|
||||||
ModelCache::instance().configureCacheSize(cacheSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<model::Composition> model::loadFromFile(const std::string &path,
|
|
||||||
bool cachePolicy)
|
|
||||||
{
|
|
||||||
if (cachePolicy) {
|
|
||||||
auto obj = ModelCache::instance().find(path);
|
|
||||||
if (obj) return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ifstream f;
|
|
||||||
f.open(path);
|
|
||||||
|
|
||||||
if (!f.is_open()) {
|
|
||||||
vCritical << "failed to open file = " << path.c_str();
|
|
||||||
return {};
|
|
||||||
} else {
|
|
||||||
std::string content;
|
|
||||||
|
|
||||||
std::getline(f, content, '\0');
|
|
||||||
f.close();
|
|
||||||
|
|
||||||
if (content.empty()) return {};
|
|
||||||
|
|
||||||
auto obj = internal::model::parse(const_cast<char *>(content.c_str()),
|
|
||||||
dirname(path));
|
|
||||||
|
|
||||||
if (obj && cachePolicy) ModelCache::instance().add(path, obj);
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<model::Composition> model::loadFromData(
|
|
||||||
std::string jsonData, const std::string &key, std::string resourcePath,
|
|
||||||
bool cachePolicy)
|
|
||||||
{
|
|
||||||
if (cachePolicy) {
|
|
||||||
auto obj = ModelCache::instance().find(key);
|
|
||||||
if (obj) return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto obj = internal::model::parse(const_cast<char *>(jsonData.c_str()),
|
|
||||||
std::move(resourcePath));
|
|
||||||
|
|
||||||
if (obj && cachePolicy) ModelCache::instance().add(key, obj);
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<model::Composition> model::loadFromData(
|
|
||||||
std::string jsonData, std::string resourcePath, model::ColorFilter filter)
|
|
||||||
{
|
|
||||||
return internal::model::parse(const_cast<char *>(jsonData.c_str()),
|
|
||||||
std::move(resourcePath), std::move(filter));
|
|
||||||
}
|
|
||||||
390
vendor/github.com/Benau/go_rlottie/lottie_lottiemodel.cpp
generated
vendored
@@ -1,390 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved.
|
|
||||||
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "lottie_lottiemodel.h"
|
|
||||||
#include <cassert>
|
|
||||||
#include <iterator>
|
|
||||||
#include <stack>
|
|
||||||
#include "vector_vimageloader.h"
|
|
||||||
#include "vector_vline.h"
|
|
||||||
|
|
||||||
using namespace rlottie::internal;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We process the iterator objects in the children list
|
|
||||||
* by iterating from back to front. when we find a repeater object
|
|
||||||
* we remove the objects from satrt till repeater object and then place
|
|
||||||
* under a new shape group object which we add it as children to the repeater
|
|
||||||
* object.
|
|
||||||
* Then we visit the childrens of the newly created shape group object to
|
|
||||||
* process the remaining repeater object(when children list contains more than
|
|
||||||
* one repeater).
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class LottieRepeaterProcesser {
|
|
||||||
public:
|
|
||||||
void visitChildren(model::Group *obj)
|
|
||||||
{
|
|
||||||
for (auto i = obj->mChildren.rbegin(); i != obj->mChildren.rend();
|
|
||||||
++i) {
|
|
||||||
auto child = (*i);
|
|
||||||
if (child->type() == model::Object::Type::Repeater) {
|
|
||||||
model::Repeater *repeater =
|
|
||||||
static_cast<model::Repeater *>(child);
|
|
||||||
// check if this repeater is already processed
|
|
||||||
// can happen if the layer is an asset and referenced by
|
|
||||||
// multiple layer.
|
|
||||||
if (repeater->processed()) continue;
|
|
||||||
|
|
||||||
repeater->markProcessed();
|
|
||||||
|
|
||||||
auto content = repeater->content();
|
|
||||||
// 1. increment the reverse iterator to point to the
|
|
||||||
// object before the repeater
|
|
||||||
++i;
|
|
||||||
// 2. move all the children till repater to the group
|
|
||||||
std::move(obj->mChildren.begin(), i.base(),
|
|
||||||
back_inserter(content->mChildren));
|
|
||||||
// 3. erase the objects from the original children list
|
|
||||||
obj->mChildren.erase(obj->mChildren.begin(), i.base());
|
|
||||||
|
|
||||||
// 5. visit newly created group to process remaining repeater
|
|
||||||
// object.
|
|
||||||
visitChildren(content);
|
|
||||||
// 6. exit the loop as the current iterators are invalid
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
visit(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void visit(model::Object *obj)
|
|
||||||
{
|
|
||||||
switch (obj->type()) {
|
|
||||||
case model::Object::Type::Group:
|
|
||||||
case model::Object::Type::Layer: {
|
|
||||||
visitChildren(static_cast<model::Group *>(obj));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class LottieUpdateStatVisitor {
|
|
||||||
model::Composition::Stats *stat;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit LottieUpdateStatVisitor(model::Composition::Stats *s) : stat(s) {}
|
|
||||||
void visitChildren(model::Group *obj)
|
|
||||||
{
|
|
||||||
for (const auto &child : obj->mChildren) {
|
|
||||||
if (child) visit(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void visitLayer(model::Layer *layer)
|
|
||||||
{
|
|
||||||
switch (layer->mLayerType) {
|
|
||||||
case model::Layer::Type::Precomp:
|
|
||||||
stat->precompLayerCount++;
|
|
||||||
break;
|
|
||||||
case model::Layer::Type::Null:
|
|
||||||
stat->nullLayerCount++;
|
|
||||||
break;
|
|
||||||
case model::Layer::Type::Shape:
|
|
||||||
stat->shapeLayerCount++;
|
|
||||||
break;
|
|
||||||
case model::Layer::Type::Solid:
|
|
||||||
stat->solidLayerCount++;
|
|
||||||
break;
|
|
||||||
case model::Layer::Type::Image:
|
|
||||||
stat->imageLayerCount++;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
visitChildren(layer);
|
|
||||||
}
|
|
||||||
void visit(model::Object *obj)
|
|
||||||
{
|
|
||||||
switch (obj->type()) {
|
|
||||||
case model::Object::Type::Layer: {
|
|
||||||
visitLayer(static_cast<model::Layer *>(obj));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case model::Object::Type::Repeater: {
|
|
||||||
visitChildren(static_cast<model::Repeater *>(obj)->content());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case model::Object::Type::Group: {
|
|
||||||
visitChildren(static_cast<model::Group *>(obj));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void model::Composition::processRepeaterObjects()
|
|
||||||
{
|
|
||||||
LottieRepeaterProcesser visitor;
|
|
||||||
visitor.visit(mRootLayer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void model::Composition::updateStats()
|
|
||||||
{
|
|
||||||
LottieUpdateStatVisitor visitor(&mStats);
|
|
||||||
visitor.visit(mRootLayer);
|
|
||||||
}
|
|
||||||
|
|
||||||
VMatrix model::Repeater::Transform::matrix(int frameNo, float multiplier) const
|
|
||||||
{
|
|
||||||
VPointF scale = mScale.value(frameNo) / 100.f;
|
|
||||||
scale.setX(std::pow(scale.x(), multiplier));
|
|
||||||
scale.setY(std::pow(scale.y(), multiplier));
|
|
||||||
VMatrix m;
|
|
||||||
m.translate(mPosition.value(frameNo) * multiplier)
|
|
||||||
.translate(mAnchor.value(frameNo))
|
|
||||||
.scale(scale)
|
|
||||||
.rotate(mRotation.value(frameNo) * multiplier)
|
|
||||||
.translate(-mAnchor.value(frameNo));
|
|
||||||
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
VMatrix model::Transform::Data::matrix(int frameNo, bool autoOrient) const
|
|
||||||
{
|
|
||||||
VMatrix m;
|
|
||||||
VPointF position;
|
|
||||||
if (mExtra && mExtra->mSeparate) {
|
|
||||||
position.setX(mExtra->mSeparateX.value(frameNo));
|
|
||||||
position.setY(mExtra->mSeparateY.value(frameNo));
|
|
||||||
} else {
|
|
||||||
position = mPosition.value(frameNo);
|
|
||||||
}
|
|
||||||
|
|
||||||
float angle = autoOrient ? mPosition.angle(frameNo) : 0;
|
|
||||||
if (mExtra && mExtra->m3DData) {
|
|
||||||
m.translate(position)
|
|
||||||
.rotate(mExtra->m3DRz.value(frameNo) + angle)
|
|
||||||
.rotate(mExtra->m3DRy.value(frameNo), VMatrix::Axis::Y)
|
|
||||||
.rotate(mExtra->m3DRx.value(frameNo), VMatrix::Axis::X)
|
|
||||||
.scale(mScale.value(frameNo) / 100.f)
|
|
||||||
.translate(-mAnchor.value(frameNo));
|
|
||||||
} else {
|
|
||||||
m.translate(position)
|
|
||||||
.rotate(mRotation.value(frameNo) + angle)
|
|
||||||
.scale(mScale.value(frameNo) / 100.f)
|
|
||||||
.translate(-mAnchor.value(frameNo));
|
|
||||||
}
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
void model::Dash::getDashInfo(int frameNo, std::vector<float> &result) const
|
|
||||||
{
|
|
||||||
result.clear();
|
|
||||||
|
|
||||||
if (mData.size() <= 1) return;
|
|
||||||
|
|
||||||
if (result.capacity() < mData.size()) result.reserve(mData.size() + 1);
|
|
||||||
|
|
||||||
for (const auto &elm : mData) result.push_back(elm.value(frameNo));
|
|
||||||
|
|
||||||
// if the size is even then we are missing last
|
|
||||||
// gap information which is same as the last dash value
|
|
||||||
// copy it from the last dash value.
|
|
||||||
// NOTE: last value is the offset and last-1 is the last dash value.
|
|
||||||
auto size = result.size();
|
|
||||||
if ((size % 2) == 0) {
|
|
||||||
// copy offset value to end.
|
|
||||||
result.push_back(result.back());
|
|
||||||
// copy dash value to gap.
|
|
||||||
result[size - 1] = result[size - 2];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Both the color stops and opacity stops are in the same array.
|
|
||||||
* There are {@link #colorPoints} colors sequentially as:
|
|
||||||
* [
|
|
||||||
* ...,
|
|
||||||
* position,
|
|
||||||
* red,
|
|
||||||
* green,
|
|
||||||
* blue,
|
|
||||||
* ...
|
|
||||||
* ]
|
|
||||||
*
|
|
||||||
* The remainder of the array is the opacity stops sequentially as:
|
|
||||||
* [
|
|
||||||
* ...,
|
|
||||||
* position,
|
|
||||||
* opacity,
|
|
||||||
* ...
|
|
||||||
* ]
|
|
||||||
*/
|
|
||||||
void model::Gradient::populate(VGradientStops &stops, int frameNo)
|
|
||||||
{
|
|
||||||
model::Gradient::Data gradData = mGradient.value(frameNo);
|
|
||||||
auto size = gradData.mGradient.size();
|
|
||||||
float * ptr = gradData.mGradient.data();
|
|
||||||
int colorPoints = mColorPoints;
|
|
||||||
if (colorPoints == -1) { // for legacy bodymovin (ref: lottie-android)
|
|
||||||
colorPoints = int(size / 4);
|
|
||||||
}
|
|
||||||
auto opacityArraySize = size - colorPoints * 4;
|
|
||||||
float *opacityPtr = ptr + (colorPoints * 4);
|
|
||||||
stops.clear();
|
|
||||||
size_t j = 0;
|
|
||||||
for (int i = 0; i < colorPoints; i++) {
|
|
||||||
float colorStop = ptr[0];
|
|
||||||
model::Color color = model::Color(ptr[1], ptr[2], ptr[3]);
|
|
||||||
if (opacityArraySize) {
|
|
||||||
if (j == opacityArraySize) {
|
|
||||||
// already reached the end
|
|
||||||
float stop1 = opacityPtr[j - 4];
|
|
||||||
float op1 = opacityPtr[j - 3];
|
|
||||||
float stop2 = opacityPtr[j - 2];
|
|
||||||
float op2 = opacityPtr[j - 1];
|
|
||||||
if (colorStop > stop2) {
|
|
||||||
stops.push_back(
|
|
||||||
std::make_pair(colorStop, color.toColor(op2)));
|
|
||||||
} else {
|
|
||||||
float progress = (colorStop - stop1) / (stop2 - stop1);
|
|
||||||
float opacity = op1 + progress * (op2 - op1);
|
|
||||||
stops.push_back(
|
|
||||||
std::make_pair(colorStop, color.toColor(opacity)));
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (; j < opacityArraySize; j += 2) {
|
|
||||||
float opacityStop = opacityPtr[j];
|
|
||||||
if (opacityStop < colorStop) {
|
|
||||||
// add a color using opacity stop
|
|
||||||
stops.push_back(std::make_pair(
|
|
||||||
opacityStop, color.toColor(opacityPtr[j + 1])));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// add a color using color stop
|
|
||||||
if (j == 0) {
|
|
||||||
stops.push_back(std::make_pair(
|
|
||||||
colorStop, color.toColor(opacityPtr[j + 1])));
|
|
||||||
} else {
|
|
||||||
float progress = (colorStop - opacityPtr[j - 2]) /
|
|
||||||
(opacityPtr[j] - opacityPtr[j - 2]);
|
|
||||||
float opacity =
|
|
||||||
opacityPtr[j - 1] +
|
|
||||||
progress * (opacityPtr[j + 1] - opacityPtr[j - 1]);
|
|
||||||
stops.push_back(
|
|
||||||
std::make_pair(colorStop, color.toColor(opacity)));
|
|
||||||
}
|
|
||||||
j += 2;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
stops.push_back(std::make_pair(colorStop, color.toColor()));
|
|
||||||
}
|
|
||||||
ptr += 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void model::Gradient::update(std::unique_ptr<VGradient> &grad, int frameNo)
|
|
||||||
{
|
|
||||||
bool init = false;
|
|
||||||
if (!grad) {
|
|
||||||
if (mGradientType == 1)
|
|
||||||
grad = std::make_unique<VGradient>(VGradient::Type::Linear);
|
|
||||||
else
|
|
||||||
grad = std::make_unique<VGradient>(VGradient::Type::Radial);
|
|
||||||
grad->mSpread = VGradient::Spread::Pad;
|
|
||||||
init = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mGradient.isStatic() || init) {
|
|
||||||
populate(grad->mStops, frameNo);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mGradientType == 1) { // linear gradient
|
|
||||||
VPointF start = mStartPoint.value(frameNo);
|
|
||||||
VPointF end = mEndPoint.value(frameNo);
|
|
||||||
grad->linear.x1 = start.x();
|
|
||||||
grad->linear.y1 = start.y();
|
|
||||||
grad->linear.x2 = end.x();
|
|
||||||
grad->linear.y2 = end.y();
|
|
||||||
} else { // radial gradient
|
|
||||||
VPointF start = mStartPoint.value(frameNo);
|
|
||||||
VPointF end = mEndPoint.value(frameNo);
|
|
||||||
grad->radial.cx = start.x();
|
|
||||||
grad->radial.cy = start.y();
|
|
||||||
grad->radial.cradius =
|
|
||||||
VLine::length(start.x(), start.y(), end.x(), end.y());
|
|
||||||
/*
|
|
||||||
* Focal point is the point lives in highlight length distance from
|
|
||||||
* center along the line (start, end) and rotated by highlight angle.
|
|
||||||
* below calculation first finds the quadrant(angle) on which the point
|
|
||||||
* lives by applying inverse slope formula then adds the rotation angle
|
|
||||||
* to find the final angle. then point is retrived using circle equation
|
|
||||||
* of center, angle and distance.
|
|
||||||
*/
|
|
||||||
float progress = mHighlightLength.value(frameNo) / 100.0f;
|
|
||||||
if (vCompare(progress, 1.0f)) progress = 0.99f;
|
|
||||||
float startAngle = VLine(start, end).angle();
|
|
||||||
float highlightAngle = mHighlightAngle.value(frameNo);
|
|
||||||
static constexpr float K_PI = 3.1415926f;
|
|
||||||
float angle = (startAngle + highlightAngle) * (K_PI / 180.0f);
|
|
||||||
grad->radial.fx =
|
|
||||||
grad->radial.cx + std::cos(angle) * progress * grad->radial.cradius;
|
|
||||||
grad->radial.fy =
|
|
||||||
grad->radial.cy + std::sin(angle) * progress * grad->radial.cradius;
|
|
||||||
// Lottie dosen't have any focal radius concept.
|
|
||||||
grad->radial.fradius = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void model::Asset::loadImageData(std::string data)
|
|
||||||
{
|
|
||||||
if (!data.empty())
|
|
||||||
mBitmap = VImageLoader::instance().load(data.c_str(), data.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
void model::Asset::loadImagePath(std::string path)
|
|
||||||
{
|
|
||||||
if (!path.empty()) mBitmap = VImageLoader::instance().load(path.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<LayerInfo> model::Composition::layerInfoList() const
|
|
||||||
{
|
|
||||||
if (!mRootLayer || mRootLayer->mChildren.empty()) return {};
|
|
||||||
|
|
||||||
std::vector<LayerInfo> result;
|
|
||||||
|
|
||||||
result.reserve(mRootLayer->mChildren.size());
|
|
||||||
|
|
||||||
for (auto it : mRootLayer->mChildren) {
|
|
||||||
auto layer = static_cast<model::Layer *>(it);
|
|
||||||
result.emplace_back(layer->name(), layer->mInFrame, layer->mOutFrame);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
1148
vendor/github.com/Benau/go_rlottie/lottie_lottiemodel.h
generated
vendored
2390
vendor/github.com/Benau/go_rlottie/lottie_lottieparser.cpp
generated
vendored
284
vendor/github.com/Benau/go_rlottie/lottie_rapidjson_allocators.h
generated
vendored
@@ -1,284 +0,0 @@
|
|||||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
|
||||||
//
|
|
||||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
|
||||||
// in compliance with the License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://opensource.org/licenses/MIT
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software distributed
|
|
||||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations under the License.
|
|
||||||
|
|
||||||
#ifndef RAPIDJSON_ALLOCATORS_H_
|
|
||||||
#define RAPIDJSON_ALLOCATORS_H_
|
|
||||||
|
|
||||||
#include "lottie_rapidjson_rapidjson.h"
|
|
||||||
|
|
||||||
RAPIDJSON_NAMESPACE_BEGIN
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Allocator
|
|
||||||
|
|
||||||
/*! \class rapidjson::Allocator
|
|
||||||
\brief Concept for allocating, resizing and freeing memory block.
|
|
||||||
|
|
||||||
Note that Malloc() and Realloc() are non-static but Free() is static.
|
|
||||||
|
|
||||||
So if an allocator need to support Free(), it needs to put its pointer in
|
|
||||||
the header of memory block.
|
|
||||||
|
|
||||||
\code
|
|
||||||
concept Allocator {
|
|
||||||
static const bool kNeedFree; //!< Whether this allocator needs to call Free().
|
|
||||||
|
|
||||||
// Allocate a memory block.
|
|
||||||
// \param size of the memory block in bytes.
|
|
||||||
// \returns pointer to the memory block.
|
|
||||||
void* Malloc(size_t size);
|
|
||||||
|
|
||||||
// Resize a memory block.
|
|
||||||
// \param originalPtr The pointer to current memory block. Null pointer is permitted.
|
|
||||||
// \param originalSize The current size in bytes. (Design issue: since some allocator may not book-keep this, explicitly pass to it can save memory.)
|
|
||||||
// \param newSize the new size in bytes.
|
|
||||||
void* Realloc(void* originalPtr, size_t originalSize, size_t newSize);
|
|
||||||
|
|
||||||
// Free a memory block.
|
|
||||||
// \param pointer to the memory block. Null pointer is permitted.
|
|
||||||
static void Free(void *ptr);
|
|
||||||
};
|
|
||||||
\endcode
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/*! \def RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY
|
|
||||||
\ingroup RAPIDJSON_CONFIG
|
|
||||||
\brief User-defined kDefaultChunkCapacity definition.
|
|
||||||
|
|
||||||
User can define this as any \c size that is a power of 2.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY
|
|
||||||
#define RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY (64 * 1024)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
// CrtAllocator
|
|
||||||
|
|
||||||
//! C-runtime library allocator.
|
|
||||||
/*! This class is just wrapper for standard C library memory routines.
|
|
||||||
\note implements Allocator concept
|
|
||||||
*/
|
|
||||||
class CrtAllocator {
|
|
||||||
public:
|
|
||||||
static const bool kNeedFree = true;
|
|
||||||
void* Malloc(size_t size) {
|
|
||||||
if (size) // behavior of malloc(0) is implementation defined.
|
|
||||||
return RAPIDJSON_MALLOC(size);
|
|
||||||
else
|
|
||||||
return NULL; // standardize to returning NULL.
|
|
||||||
}
|
|
||||||
void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) {
|
|
||||||
(void)originalSize;
|
|
||||||
if (newSize == 0) {
|
|
||||||
RAPIDJSON_FREE(originalPtr);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return RAPIDJSON_REALLOC(originalPtr, newSize);
|
|
||||||
}
|
|
||||||
static void Free(void *ptr) { RAPIDJSON_FREE(ptr); }
|
|
||||||
};
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
// MemoryPoolAllocator
|
|
||||||
|
|
||||||
//! Default memory allocator used by the parser and DOM.
|
|
||||||
/*! This allocator allocate memory blocks from pre-allocated memory chunks.
|
|
||||||
|
|
||||||
It does not free memory blocks. And Realloc() only allocate new memory.
|
|
||||||
|
|
||||||
The memory chunks are allocated by BaseAllocator, which is CrtAllocator by default.
|
|
||||||
|
|
||||||
User may also supply a buffer as the first chunk.
|
|
||||||
|
|
||||||
If the user-buffer is full then additional chunks are allocated by BaseAllocator.
|
|
||||||
|
|
||||||
The user-buffer is not deallocated by this allocator.
|
|
||||||
|
|
||||||
\tparam BaseAllocator the allocator type for allocating memory chunks. Default is CrtAllocator.
|
|
||||||
\note implements Allocator concept
|
|
||||||
*/
|
|
||||||
template <typename BaseAllocator = CrtAllocator>
|
|
||||||
class MemoryPoolAllocator {
|
|
||||||
public:
|
|
||||||
static const bool kNeedFree = false; //!< Tell users that no need to call Free() with this allocator. (concept Allocator)
|
|
||||||
|
|
||||||
//! Constructor with chunkSize.
|
|
||||||
/*! \param chunkSize The size of memory chunk. The default is kDefaultChunkSize.
|
|
||||||
\param baseAllocator The allocator for allocating memory chunks.
|
|
||||||
*/
|
|
||||||
MemoryPoolAllocator(size_t chunkSize = kDefaultChunkCapacity, BaseAllocator* baseAllocator = 0) :
|
|
||||||
chunkHead_(0), chunk_capacity_(chunkSize), userBuffer_(0), baseAllocator_(baseAllocator), ownBaseAllocator_(0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Constructor with user-supplied buffer.
|
|
||||||
/*! The user buffer will be used firstly. When it is full, memory pool allocates new chunk with chunk size.
|
|
||||||
|
|
||||||
The user buffer will not be deallocated when this allocator is destructed.
|
|
||||||
|
|
||||||
\param buffer User supplied buffer.
|
|
||||||
\param size Size of the buffer in bytes. It must at least larger than sizeof(ChunkHeader).
|
|
||||||
\param chunkSize The size of memory chunk. The default is kDefaultChunkSize.
|
|
||||||
\param baseAllocator The allocator for allocating memory chunks.
|
|
||||||
*/
|
|
||||||
MemoryPoolAllocator(void *buffer, size_t size, size_t chunkSize = kDefaultChunkCapacity, BaseAllocator* baseAllocator = 0) :
|
|
||||||
chunkHead_(0), chunk_capacity_(chunkSize), userBuffer_(buffer), baseAllocator_(baseAllocator), ownBaseAllocator_(0)
|
|
||||||
{
|
|
||||||
RAPIDJSON_ASSERT(buffer != 0);
|
|
||||||
RAPIDJSON_ASSERT(size > sizeof(ChunkHeader));
|
|
||||||
chunkHead_ = reinterpret_cast<ChunkHeader*>(buffer);
|
|
||||||
chunkHead_->capacity = size - sizeof(ChunkHeader);
|
|
||||||
chunkHead_->size = 0;
|
|
||||||
chunkHead_->next = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Destructor.
|
|
||||||
/*! This deallocates all memory chunks, excluding the user-supplied buffer.
|
|
||||||
*/
|
|
||||||
~MemoryPoolAllocator() {
|
|
||||||
Clear();
|
|
||||||
RAPIDJSON_DELETE(ownBaseAllocator_);
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Deallocates all memory chunks, excluding the user-supplied buffer.
|
|
||||||
void Clear() {
|
|
||||||
while (chunkHead_ && chunkHead_ != userBuffer_) {
|
|
||||||
ChunkHeader* next = chunkHead_->next;
|
|
||||||
baseAllocator_->Free(chunkHead_);
|
|
||||||
chunkHead_ = next;
|
|
||||||
}
|
|
||||||
if (chunkHead_ && chunkHead_ == userBuffer_)
|
|
||||||
chunkHead_->size = 0; // Clear user buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Computes the total capacity of allocated memory chunks.
|
|
||||||
/*! \return total capacity in bytes.
|
|
||||||
*/
|
|
||||||
size_t Capacity() const {
|
|
||||||
size_t capacity = 0;
|
|
||||||
for (ChunkHeader* c = chunkHead_; c != 0; c = c->next)
|
|
||||||
capacity += c->capacity;
|
|
||||||
return capacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Computes the memory blocks allocated.
|
|
||||||
/*! \return total used bytes.
|
|
||||||
*/
|
|
||||||
size_t Size() const {
|
|
||||||
size_t size = 0;
|
|
||||||
for (ChunkHeader* c = chunkHead_; c != 0; c = c->next)
|
|
||||||
size += c->size;
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Allocates a memory block. (concept Allocator)
|
|
||||||
void* Malloc(size_t size) {
|
|
||||||
if (!size)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
size = RAPIDJSON_ALIGN(size);
|
|
||||||
if (chunkHead_ == 0 || chunkHead_->size + size > chunkHead_->capacity)
|
|
||||||
if (!AddChunk(chunk_capacity_ > size ? chunk_capacity_ : size))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
void *buffer = reinterpret_cast<char *>(chunkHead_) + RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + chunkHead_->size;
|
|
||||||
chunkHead_->size += size;
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Resizes a memory block (concept Allocator)
|
|
||||||
void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) {
|
|
||||||
if (originalPtr == 0)
|
|
||||||
return Malloc(newSize);
|
|
||||||
|
|
||||||
if (newSize == 0)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
originalSize = RAPIDJSON_ALIGN(originalSize);
|
|
||||||
newSize = RAPIDJSON_ALIGN(newSize);
|
|
||||||
|
|
||||||
// Do not shrink if new size is smaller than original
|
|
||||||
if (originalSize >= newSize)
|
|
||||||
return originalPtr;
|
|
||||||
|
|
||||||
// Simply expand it if it is the last allocation and there is sufficient space
|
|
||||||
if (originalPtr == reinterpret_cast<char *>(chunkHead_) + RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + chunkHead_->size - originalSize) {
|
|
||||||
size_t increment = static_cast<size_t>(newSize - originalSize);
|
|
||||||
if (chunkHead_->size + increment <= chunkHead_->capacity) {
|
|
||||||
chunkHead_->size += increment;
|
|
||||||
return originalPtr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Realloc process: allocate and copy memory, do not free original buffer.
|
|
||||||
if (void* newBuffer = Malloc(newSize)) {
|
|
||||||
if (originalSize)
|
|
||||||
std::memcpy(newBuffer, originalPtr, originalSize);
|
|
||||||
return newBuffer;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Frees a memory block (concept Allocator)
|
|
||||||
static void Free(void *ptr) { (void)ptr; } // Do nothing
|
|
||||||
|
|
||||||
private:
|
|
||||||
//! Copy constructor is not permitted.
|
|
||||||
MemoryPoolAllocator(const MemoryPoolAllocator& rhs) /* = delete */;
|
|
||||||
//! Copy assignment operator is not permitted.
|
|
||||||
MemoryPoolAllocator& operator=(const MemoryPoolAllocator& rhs) /* = delete */;
|
|
||||||
|
|
||||||
//! Creates a new chunk.
|
|
||||||
/*! \param capacity Capacity of the chunk in bytes.
|
|
||||||
\return true if success.
|
|
||||||
*/
|
|
||||||
bool AddChunk(size_t capacity) {
|
|
||||||
if (!baseAllocator_)
|
|
||||||
ownBaseAllocator_ = baseAllocator_ = RAPIDJSON_NEW(BaseAllocator)();
|
|
||||||
if (ChunkHeader* chunk = reinterpret_cast<ChunkHeader*>(baseAllocator_->Malloc(RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + capacity))) {
|
|
||||||
chunk->capacity = capacity;
|
|
||||||
chunk->size = 0;
|
|
||||||
chunk->next = chunkHead_;
|
|
||||||
chunkHead_ = chunk;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const int kDefaultChunkCapacity = RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY; //!< Default chunk capacity.
|
|
||||||
|
|
||||||
//! Chunk header for perpending to each chunk.
|
|
||||||
/*! Chunks are stored as a singly linked list.
|
|
||||||
*/
|
|
||||||
struct ChunkHeader {
|
|
||||||
size_t capacity; //!< Capacity of the chunk in bytes (excluding the header itself).
|
|
||||||
size_t size; //!< Current size of allocated memory in bytes.
|
|
||||||
ChunkHeader *next; //!< Next chunk in the linked list.
|
|
||||||
};
|
|
||||||
|
|
||||||
ChunkHeader *chunkHead_; //!< Head of the chunk linked-list. Only the head chunk serves allocation.
|
|
||||||
size_t chunk_capacity_; //!< The minimum capacity of chunk when they are allocated.
|
|
||||||
void *userBuffer_; //!< User supplied buffer.
|
|
||||||
BaseAllocator* baseAllocator_; //!< base allocator for allocating memory chunks.
|
|
||||||
BaseAllocator* ownBaseAllocator_; //!< base allocator created by this object.
|
|
||||||
};
|
|
||||||
|
|
||||||
RAPIDJSON_NAMESPACE_END
|
|
||||||
|
|
||||||
#endif // RAPIDJSON_ENCODINGS_H_
|
|
||||||
78
vendor/github.com/Benau/go_rlottie/lottie_rapidjson_cursorstreamwrapper.h
generated
vendored
@@ -1,78 +0,0 @@
|
|||||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
|
||||||
//
|
|
||||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
|
||||||
// in compliance with the License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://opensource.org/licenses/MIT
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software distributed
|
|
||||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations under the License.
|
|
||||||
|
|
||||||
#ifndef RAPIDJSON_CURSORSTREAMWRAPPER_H_
|
|
||||||
#define RAPIDJSON_CURSORSTREAMWRAPPER_H_
|
|
||||||
|
|
||||||
#include "lottie_rapidjson_stream.h"
|
|
||||||
|
|
||||||
#if defined(__GNUC__)
|
|
||||||
RAPIDJSON_DIAG_PUSH
|
|
||||||
RAPIDJSON_DIAG_OFF(effc++)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(_MSC_VER) && _MSC_VER <= 1800
|
|
||||||
RAPIDJSON_DIAG_PUSH
|
|
||||||
RAPIDJSON_DIAG_OFF(4702) // unreachable code
|
|
||||||
RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated
|
|
||||||
#endif
|
|
||||||
|
|
||||||
RAPIDJSON_NAMESPACE_BEGIN
|
|
||||||
|
|
||||||
|
|
||||||
//! Cursor stream wrapper for counting line and column number if error exists.
|
|
||||||
/*!
|
|
||||||
\tparam InputStream Any stream that implements Stream Concept
|
|
||||||
*/
|
|
||||||
template <typename InputStream, typename Encoding = UTF8<> >
|
|
||||||
class CursorStreamWrapper : public GenericStreamWrapper<InputStream, Encoding> {
|
|
||||||
public:
|
|
||||||
typedef typename Encoding::Ch Ch;
|
|
||||||
|
|
||||||
CursorStreamWrapper(InputStream& is):
|
|
||||||
GenericStreamWrapper<InputStream, Encoding>(is), line_(1), col_(0) {}
|
|
||||||
|
|
||||||
// counting line and column number
|
|
||||||
Ch Take() {
|
|
||||||
Ch ch = this->is_.Take();
|
|
||||||
if(ch == '\n') {
|
|
||||||
line_ ++;
|
|
||||||
col_ = 0;
|
|
||||||
} else {
|
|
||||||
col_ ++;
|
|
||||||
}
|
|
||||||
return ch;
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Get the error line number, if error exists.
|
|
||||||
size_t GetLine() const { return line_; }
|
|
||||||
//! Get the error column number, if error exists.
|
|
||||||
size_t GetColumn() const { return col_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
size_t line_; //!< Current Line
|
|
||||||
size_t col_; //!< Current Column
|
|
||||||
};
|
|
||||||
|
|
||||||
#if defined(_MSC_VER) && _MSC_VER <= 1800
|
|
||||||
RAPIDJSON_DIAG_POP
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(__GNUC__)
|
|
||||||
RAPIDJSON_DIAG_POP
|
|
||||||
#endif
|
|
||||||
|
|
||||||
RAPIDJSON_NAMESPACE_END
|
|
||||||
|
|
||||||
#endif // RAPIDJSON_CURSORSTREAMWRAPPER_H_
|
|
||||||
2732
vendor/github.com/Benau/go_rlottie/lottie_rapidjson_document.h
generated
vendored
299
vendor/github.com/Benau/go_rlottie/lottie_rapidjson_encodedstream.h
generated
vendored
@@ -1,299 +0,0 @@
|
|||||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
|
||||||
//
|
|
||||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
|
||||||
// in compliance with the License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://opensource.org/licenses/MIT
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software distributed
|
|
||||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations under the License.
|
|
||||||
|
|
||||||
#ifndef RAPIDJSON_ENCODEDSTREAM_H_
|
|
||||||
#define RAPIDJSON_ENCODEDSTREAM_H_
|
|
||||||
|
|
||||||
#include "lottie_rapidjson_stream.h"
|
|
||||||
#include "lottie_rapidjson_memorystream.h"
|
|
||||||
|
|
||||||
#ifdef __GNUC__
|
|
||||||
RAPIDJSON_DIAG_PUSH
|
|
||||||
RAPIDJSON_DIAG_OFF(effc++)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __clang__
|
|
||||||
RAPIDJSON_DIAG_PUSH
|
|
||||||
RAPIDJSON_DIAG_OFF(padded)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
RAPIDJSON_NAMESPACE_BEGIN
|
|
||||||
|
|
||||||
//! Input byte stream wrapper with a statically bound encoding.
|
|
||||||
/*!
|
|
||||||
\tparam Encoding The interpretation of encoding of the stream. Either UTF8, UTF16LE, UTF16BE, UTF32LE, UTF32BE.
|
|
||||||
\tparam InputByteStream Type of input byte stream. For example, FileReadStream.
|
|
||||||
*/
|
|
||||||
template <typename Encoding, typename InputByteStream>
|
|
||||||
class EncodedInputStream {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
|
||||||
public:
|
|
||||||
typedef typename Encoding::Ch Ch;
|
|
||||||
|
|
||||||
EncodedInputStream(InputByteStream& is) : is_(is) {
|
|
||||||
current_ = Encoding::TakeBOM(is_);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ch Peek() const { return current_; }
|
|
||||||
Ch Take() { Ch c = current_; current_ = Encoding::Take(is_); return c; }
|
|
||||||
size_t Tell() const { return is_.Tell(); }
|
|
||||||
|
|
||||||
// Not implemented
|
|
||||||
void Put(Ch) { RAPIDJSON_ASSERT(false); }
|
|
||||||
void Flush() { RAPIDJSON_ASSERT(false); }
|
|
||||||
Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; }
|
|
||||||
size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
EncodedInputStream(const EncodedInputStream&);
|
|
||||||
EncodedInputStream& operator=(const EncodedInputStream&);
|
|
||||||
|
|
||||||
InputByteStream& is_;
|
|
||||||
Ch current_;
|
|
||||||
};
|
|
||||||
|
|
||||||
//! Specialized for UTF8 MemoryStream.
|
|
||||||
template <>
|
|
||||||
class EncodedInputStream<UTF8<>, MemoryStream> {
|
|
||||||
public:
|
|
||||||
typedef UTF8<>::Ch Ch;
|
|
||||||
|
|
||||||
EncodedInputStream(MemoryStream& is) : is_(is) {
|
|
||||||
if (static_cast<unsigned char>(is_.Peek()) == 0xEFu) is_.Take();
|
|
||||||
if (static_cast<unsigned char>(is_.Peek()) == 0xBBu) is_.Take();
|
|
||||||
if (static_cast<unsigned char>(is_.Peek()) == 0xBFu) is_.Take();
|
|
||||||
}
|
|
||||||
Ch Peek() const { return is_.Peek(); }
|
|
||||||
Ch Take() { return is_.Take(); }
|
|
||||||
size_t Tell() const { return is_.Tell(); }
|
|
||||||
|
|
||||||
// Not implemented
|
|
||||||
void Put(Ch) {}
|
|
||||||
void Flush() {}
|
|
||||||
Ch* PutBegin() { return 0; }
|
|
||||||
size_t PutEnd(Ch*) { return 0; }
|
|
||||||
|
|
||||||
MemoryStream& is_;
|
|
||||||
|
|
||||||
private:
|
|
||||||
EncodedInputStream(const EncodedInputStream&);
|
|
||||||
EncodedInputStream& operator=(const EncodedInputStream&);
|
|
||||||
};
|
|
||||||
|
|
||||||
//! Output byte stream wrapper with statically bound encoding.
|
|
||||||
/*!
|
|
||||||
\tparam Encoding The interpretation of encoding of the stream. Either UTF8, UTF16LE, UTF16BE, UTF32LE, UTF32BE.
|
|
||||||
\tparam OutputByteStream Type of input byte stream. For example, FileWriteStream.
|
|
||||||
*/
|
|
||||||
template <typename Encoding, typename OutputByteStream>
|
|
||||||
class EncodedOutputStream {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
|
||||||
public:
|
|
||||||
typedef typename Encoding::Ch Ch;
|
|
||||||
|
|
||||||
EncodedOutputStream(OutputByteStream& os, bool putBOM = true) : os_(os) {
|
|
||||||
if (putBOM)
|
|
||||||
Encoding::PutBOM(os_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Put(Ch c) { Encoding::Put(os_, c); }
|
|
||||||
void Flush() { os_.Flush(); }
|
|
||||||
|
|
||||||
// Not implemented
|
|
||||||
Ch Peek() const { RAPIDJSON_ASSERT(false); return 0;}
|
|
||||||
Ch Take() { RAPIDJSON_ASSERT(false); return 0;}
|
|
||||||
size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; }
|
|
||||||
Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; }
|
|
||||||
size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
EncodedOutputStream(const EncodedOutputStream&);
|
|
||||||
EncodedOutputStream& operator=(const EncodedOutputStream&);
|
|
||||||
|
|
||||||
OutputByteStream& os_;
|
|
||||||
};
|
|
||||||
|
|
||||||
#define RAPIDJSON_ENCODINGS_FUNC(x) UTF8<Ch>::x, UTF16LE<Ch>::x, UTF16BE<Ch>::x, UTF32LE<Ch>::x, UTF32BE<Ch>::x
|
|
||||||
|
|
||||||
//! Input stream wrapper with dynamically bound encoding and automatic encoding detection.
|
|
||||||
/*!
|
|
||||||
\tparam CharType Type of character for reading.
|
|
||||||
\tparam InputByteStream type of input byte stream to be wrapped.
|
|
||||||
*/
|
|
||||||
template <typename CharType, typename InputByteStream>
|
|
||||||
class AutoUTFInputStream {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
|
||||||
public:
|
|
||||||
typedef CharType Ch;
|
|
||||||
|
|
||||||
//! Constructor.
|
|
||||||
/*!
|
|
||||||
\param is input stream to be wrapped.
|
|
||||||
\param type UTF encoding type if it is not detected from the stream.
|
|
||||||
*/
|
|
||||||
AutoUTFInputStream(InputByteStream& is, UTFType type = kUTF8) : is_(&is), type_(type), hasBOM_(false) {
|
|
||||||
RAPIDJSON_ASSERT(type >= kUTF8 && type <= kUTF32BE);
|
|
||||||
DetectType();
|
|
||||||
static const TakeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Take) };
|
|
||||||
takeFunc_ = f[type_];
|
|
||||||
current_ = takeFunc_(*is_);
|
|
||||||
}
|
|
||||||
|
|
||||||
UTFType GetType() const { return type_; }
|
|
||||||
bool HasBOM() const { return hasBOM_; }
|
|
||||||
|
|
||||||
Ch Peek() const { return current_; }
|
|
||||||
Ch Take() { Ch c = current_; current_ = takeFunc_(*is_); return c; }
|
|
||||||
size_t Tell() const { return is_->Tell(); }
|
|
||||||
|
|
||||||
// Not implemented
|
|
||||||
void Put(Ch) { RAPIDJSON_ASSERT(false); }
|
|
||||||
void Flush() { RAPIDJSON_ASSERT(false); }
|
|
||||||
Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; }
|
|
||||||
size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
AutoUTFInputStream(const AutoUTFInputStream&);
|
|
||||||
AutoUTFInputStream& operator=(const AutoUTFInputStream&);
|
|
||||||
|
|
||||||
// Detect encoding type with BOM or RFC 4627
|
|
||||||
void DetectType() {
|
|
||||||
// BOM (Byte Order Mark):
|
|
||||||
// 00 00 FE FF UTF-32BE
|
|
||||||
// FF FE 00 00 UTF-32LE
|
|
||||||
// FE FF UTF-16BE
|
|
||||||
// FF FE UTF-16LE
|
|
||||||
// EF BB BF UTF-8
|
|
||||||
|
|
||||||
const unsigned char* c = reinterpret_cast<const unsigned char *>(is_->Peek4());
|
|
||||||
if (!c)
|
|
||||||
return;
|
|
||||||
|
|
||||||
unsigned bom = static_cast<unsigned>(c[0] | (c[1] << 8) | (c[2] << 16) | (c[3] << 24));
|
|
||||||
hasBOM_ = false;
|
|
||||||
if (bom == 0xFFFE0000) { type_ = kUTF32BE; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); is_->Take(); }
|
|
||||||
else if (bom == 0x0000FEFF) { type_ = kUTF32LE; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); is_->Take(); }
|
|
||||||
else if ((bom & 0xFFFF) == 0xFFFE) { type_ = kUTF16BE; hasBOM_ = true; is_->Take(); is_->Take(); }
|
|
||||||
else if ((bom & 0xFFFF) == 0xFEFF) { type_ = kUTF16LE; hasBOM_ = true; is_->Take(); is_->Take(); }
|
|
||||||
else if ((bom & 0xFFFFFF) == 0xBFBBEF) { type_ = kUTF8; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); }
|
|
||||||
|
|
||||||
// RFC 4627: Section 3
|
|
||||||
// "Since the first two characters of a JSON text will always be ASCII
|
|
||||||
// characters [RFC0020], it is possible to determine whether an octet
|
|
||||||
// stream is UTF-8, UTF-16 (BE or LE), or UTF-32 (BE or LE) by looking
|
|
||||||
// at the pattern of nulls in the first four octets."
|
|
||||||
// 00 00 00 xx UTF-32BE
|
|
||||||
// 00 xx 00 xx UTF-16BE
|
|
||||||
// xx 00 00 00 UTF-32LE
|
|
||||||
// xx 00 xx 00 UTF-16LE
|
|
||||||
// xx xx xx xx UTF-8
|
|
||||||
|
|
||||||
if (!hasBOM_) {
|
|
||||||
int pattern = (c[0] ? 1 : 0) | (c[1] ? 2 : 0) | (c[2] ? 4 : 0) | (c[3] ? 8 : 0);
|
|
||||||
switch (pattern) {
|
|
||||||
case 0x08: type_ = kUTF32BE; break;
|
|
||||||
case 0x0A: type_ = kUTF16BE; break;
|
|
||||||
case 0x01: type_ = kUTF32LE; break;
|
|
||||||
case 0x05: type_ = kUTF16LE; break;
|
|
||||||
case 0x0F: type_ = kUTF8; break;
|
|
||||||
default: break; // Use type defined by user.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Runtime check whether the size of character type is sufficient. It only perform checks with assertion.
|
|
||||||
if (type_ == kUTF16LE || type_ == kUTF16BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 2);
|
|
||||||
if (type_ == kUTF32LE || type_ == kUTF32BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef Ch (*TakeFunc)(InputByteStream& is);
|
|
||||||
InputByteStream* is_;
|
|
||||||
UTFType type_;
|
|
||||||
Ch current_;
|
|
||||||
TakeFunc takeFunc_;
|
|
||||||
bool hasBOM_;
|
|
||||||
};
|
|
||||||
|
|
||||||
//! Output stream wrapper with dynamically bound encoding and automatic encoding detection.
|
|
||||||
/*!
|
|
||||||
\tparam CharType Type of character for writing.
|
|
||||||
\tparam OutputByteStream type of output byte stream to be wrapped.
|
|
||||||
*/
|
|
||||||
template <typename CharType, typename OutputByteStream>
|
|
||||||
class AutoUTFOutputStream {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
|
||||||
public:
|
|
||||||
typedef CharType Ch;
|
|
||||||
|
|
||||||
//! Constructor.
|
|
||||||
/*!
|
|
||||||
\param os output stream to be wrapped.
|
|
||||||
\param type UTF encoding type.
|
|
||||||
\param putBOM Whether to write BOM at the beginning of the stream.
|
|
||||||
*/
|
|
||||||
AutoUTFOutputStream(OutputByteStream& os, UTFType type, bool putBOM) : os_(&os), type_(type) {
|
|
||||||
RAPIDJSON_ASSERT(type >= kUTF8 && type <= kUTF32BE);
|
|
||||||
|
|
||||||
// Runtime check whether the size of character type is sufficient. It only perform checks with assertion.
|
|
||||||
if (type_ == kUTF16LE || type_ == kUTF16BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 2);
|
|
||||||
if (type_ == kUTF32LE || type_ == kUTF32BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 4);
|
|
||||||
|
|
||||||
static const PutFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Put) };
|
|
||||||
putFunc_ = f[type_];
|
|
||||||
|
|
||||||
if (putBOM)
|
|
||||||
PutBOM();
|
|
||||||
}
|
|
||||||
|
|
||||||
UTFType GetType() const { return type_; }
|
|
||||||
|
|
||||||
void Put(Ch c) { putFunc_(*os_, c); }
|
|
||||||
void Flush() { os_->Flush(); }
|
|
||||||
|
|
||||||
// Not implemented
|
|
||||||
Ch Peek() const { RAPIDJSON_ASSERT(false); return 0;}
|
|
||||||
Ch Take() { RAPIDJSON_ASSERT(false); return 0;}
|
|
||||||
size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; }
|
|
||||||
Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; }
|
|
||||||
size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
AutoUTFOutputStream(const AutoUTFOutputStream&);
|
|
||||||
AutoUTFOutputStream& operator=(const AutoUTFOutputStream&);
|
|
||||||
|
|
||||||
void PutBOM() {
|
|
||||||
typedef void (*PutBOMFunc)(OutputByteStream&);
|
|
||||||
static const PutBOMFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(PutBOM) };
|
|
||||||
f[type_](*os_);
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef void (*PutFunc)(OutputByteStream&, Ch);
|
|
||||||
|
|
||||||
OutputByteStream* os_;
|
|
||||||
UTFType type_;
|
|
||||||
PutFunc putFunc_;
|
|
||||||
};
|
|
||||||
|
|
||||||
#undef RAPIDJSON_ENCODINGS_FUNC
|
|
||||||
|
|
||||||
RAPIDJSON_NAMESPACE_END
|
|
||||||
|
|
||||||
#ifdef __clang__
|
|
||||||
RAPIDJSON_DIAG_POP
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __GNUC__
|
|
||||||
RAPIDJSON_DIAG_POP
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // RAPIDJSON_FILESTREAM_H_
|
|
||||||
716
vendor/github.com/Benau/go_rlottie/lottie_rapidjson_encodings.h
generated
vendored
@@ -1,716 +0,0 @@
|
|||||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
|
||||||
//
|
|
||||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
|
||||||
// in compliance with the License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://opensource.org/licenses/MIT
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software distributed
|
|
||||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations under the License.
|
|
||||||
|
|
||||||
#ifndef RAPIDJSON_ENCODINGS_H_
|
|
||||||
#define RAPIDJSON_ENCODINGS_H_
|
|
||||||
|
|
||||||
#include "lottie_rapidjson_rapidjson.h"
|
|
||||||
|
|
||||||
#if defined(_MSC_VER) && !defined(__clang__)
|
|
||||||
RAPIDJSON_DIAG_PUSH
|
|
||||||
RAPIDJSON_DIAG_OFF(4244) // conversion from 'type1' to 'type2', possible loss of data
|
|
||||||
RAPIDJSON_DIAG_OFF(4702) // unreachable code
|
|
||||||
#elif defined(__GNUC__)
|
|
||||||
RAPIDJSON_DIAG_PUSH
|
|
||||||
RAPIDJSON_DIAG_OFF(effc++)
|
|
||||||
RAPIDJSON_DIAG_OFF(overflow)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
RAPIDJSON_NAMESPACE_BEGIN
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Encoding
|
|
||||||
|
|
||||||
/*! \class rapidjson::Encoding
|
|
||||||
\brief Concept for encoding of Unicode characters.
|
|
||||||
|
|
||||||
\code
|
|
||||||
concept Encoding {
|
|
||||||
typename Ch; //! Type of character. A "character" is actually a code unit in unicode's definition.
|
|
||||||
|
|
||||||
enum { supportUnicode = 1 }; // or 0 if not supporting unicode
|
|
||||||
|
|
||||||
//! \brief Encode a Unicode codepoint to an output stream.
|
|
||||||
//! \param os Output stream.
|
|
||||||
//! \param codepoint An unicode codepoint, ranging from 0x0 to 0x10FFFF inclusively.
|
|
||||||
template<typename OutputStream>
|
|
||||||
static void Encode(OutputStream& os, unsigned codepoint);
|
|
||||||
|
|
||||||
//! \brief Decode a Unicode codepoint from an input stream.
|
|
||||||
//! \param is Input stream.
|
|
||||||
//! \param codepoint Output of the unicode codepoint.
|
|
||||||
//! \return true if a valid codepoint can be decoded from the stream.
|
|
||||||
template <typename InputStream>
|
|
||||||
static bool Decode(InputStream& is, unsigned* codepoint);
|
|
||||||
|
|
||||||
//! \brief Validate one Unicode codepoint from an encoded stream.
|
|
||||||
//! \param is Input stream to obtain codepoint.
|
|
||||||
//! \param os Output for copying one codepoint.
|
|
||||||
//! \return true if it is valid.
|
|
||||||
//! \note This function just validating and copying the codepoint without actually decode it.
|
|
||||||
template <typename InputStream, typename OutputStream>
|
|
||||||
static bool Validate(InputStream& is, OutputStream& os);
|
|
||||||
|
|
||||||
// The following functions are deal with byte streams.
|
|
||||||
|
|
||||||
//! Take a character from input byte stream, skip BOM if exist.
|
|
||||||
template <typename InputByteStream>
|
|
||||||
static CharType TakeBOM(InputByteStream& is);
|
|
||||||
|
|
||||||
//! Take a character from input byte stream.
|
|
||||||
template <typename InputByteStream>
|
|
||||||
static Ch Take(InputByteStream& is);
|
|
||||||
|
|
||||||
//! Put BOM to output byte stream.
|
|
||||||
template <typename OutputByteStream>
|
|
||||||
static void PutBOM(OutputByteStream& os);
|
|
||||||
|
|
||||||
//! Put a character to output byte stream.
|
|
||||||
template <typename OutputByteStream>
|
|
||||||
static void Put(OutputByteStream& os, Ch c);
|
|
||||||
};
|
|
||||||
\endcode
|
|
||||||
*/
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
// UTF8
|
|
||||||
|
|
||||||
//! UTF-8 encoding.
|
|
||||||
/*! http://en.wikipedia.org/wiki/UTF-8
|
|
||||||
http://tools.ietf.org/html/rfc3629
|
|
||||||
\tparam CharType Code unit for storing 8-bit UTF-8 data. Default is char.
|
|
||||||
\note implements Encoding concept
|
|
||||||
*/
|
|
||||||
template<typename CharType = char>
|
|
||||||
struct UTF8 {
|
|
||||||
typedef CharType Ch;
|
|
||||||
|
|
||||||
enum { supportUnicode = 1 };
|
|
||||||
|
|
||||||
template<typename OutputStream>
|
|
||||||
static void Encode(OutputStream& os, unsigned codepoint) {
|
|
||||||
if (codepoint <= 0x7F)
|
|
||||||
os.Put(static_cast<Ch>(codepoint & 0xFF));
|
|
||||||
else if (codepoint <= 0x7FF) {
|
|
||||||
os.Put(static_cast<Ch>(0xC0 | ((codepoint >> 6) & 0xFF)));
|
|
||||||
os.Put(static_cast<Ch>(0x80 | ((codepoint & 0x3F))));
|
|
||||||
}
|
|
||||||
else if (codepoint <= 0xFFFF) {
|
|
||||||
os.Put(static_cast<Ch>(0xE0 | ((codepoint >> 12) & 0xFF)));
|
|
||||||
os.Put(static_cast<Ch>(0x80 | ((codepoint >> 6) & 0x3F)));
|
|
||||||
os.Put(static_cast<Ch>(0x80 | (codepoint & 0x3F)));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
RAPIDJSON_ASSERT(codepoint <= 0x10FFFF);
|
|
||||||
os.Put(static_cast<Ch>(0xF0 | ((codepoint >> 18) & 0xFF)));
|
|
||||||
os.Put(static_cast<Ch>(0x80 | ((codepoint >> 12) & 0x3F)));
|
|
||||||
os.Put(static_cast<Ch>(0x80 | ((codepoint >> 6) & 0x3F)));
|
|
||||||
os.Put(static_cast<Ch>(0x80 | (codepoint & 0x3F)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename OutputStream>
|
|
||||||
static void EncodeUnsafe(OutputStream& os, unsigned codepoint) {
|
|
||||||
if (codepoint <= 0x7F)
|
|
||||||
PutUnsafe(os, static_cast<Ch>(codepoint & 0xFF));
|
|
||||||
else if (codepoint <= 0x7FF) {
|
|
||||||
PutUnsafe(os, static_cast<Ch>(0xC0 | ((codepoint >> 6) & 0xFF)));
|
|
||||||
PutUnsafe(os, static_cast<Ch>(0x80 | ((codepoint & 0x3F))));
|
|
||||||
}
|
|
||||||
else if (codepoint <= 0xFFFF) {
|
|
||||||
PutUnsafe(os, static_cast<Ch>(0xE0 | ((codepoint >> 12) & 0xFF)));
|
|
||||||
PutUnsafe(os, static_cast<Ch>(0x80 | ((codepoint >> 6) & 0x3F)));
|
|
||||||
PutUnsafe(os, static_cast<Ch>(0x80 | (codepoint & 0x3F)));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
RAPIDJSON_ASSERT(codepoint <= 0x10FFFF);
|
|
||||||
PutUnsafe(os, static_cast<Ch>(0xF0 | ((codepoint >> 18) & 0xFF)));
|
|
||||||
PutUnsafe(os, static_cast<Ch>(0x80 | ((codepoint >> 12) & 0x3F)));
|
|
||||||
PutUnsafe(os, static_cast<Ch>(0x80 | ((codepoint >> 6) & 0x3F)));
|
|
||||||
PutUnsafe(os, static_cast<Ch>(0x80 | (codepoint & 0x3F)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename InputStream>
|
|
||||||
static bool Decode(InputStream& is, unsigned* codepoint) {
|
|
||||||
#define RAPIDJSON_COPY() c = is.Take(); *codepoint = (*codepoint << 6) | (static_cast<unsigned char>(c) & 0x3Fu)
|
|
||||||
#define RAPIDJSON_TRANS(mask) result &= ((GetRange(static_cast<unsigned char>(c)) & mask) != 0)
|
|
||||||
#define RAPIDJSON_TAIL() RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x70)
|
|
||||||
typename InputStream::Ch c = is.Take();
|
|
||||||
if (!(c & 0x80)) {
|
|
||||||
*codepoint = static_cast<unsigned char>(c);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned char type = GetRange(static_cast<unsigned char>(c));
|
|
||||||
if (type >= 32) {
|
|
||||||
*codepoint = 0;
|
|
||||||
} else {
|
|
||||||
*codepoint = (0xFFu >> type) & static_cast<unsigned char>(c);
|
|
||||||
}
|
|
||||||
bool result = true;
|
|
||||||
switch (type) {
|
|
||||||
case 2: RAPIDJSON_TAIL(); return result;
|
|
||||||
case 3: RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result;
|
|
||||||
case 4: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x50); RAPIDJSON_TAIL(); return result;
|
|
||||||
case 5: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x10); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result;
|
|
||||||
case 6: RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result;
|
|
||||||
case 10: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x20); RAPIDJSON_TAIL(); return result;
|
|
||||||
case 11: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x60); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result;
|
|
||||||
default: return false;
|
|
||||||
}
|
|
||||||
#undef RAPIDJSON_COPY
|
|
||||||
#undef RAPIDJSON_TRANS
|
|
||||||
#undef RAPIDJSON_TAIL
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename InputStream, typename OutputStream>
|
|
||||||
static bool Validate(InputStream& is, OutputStream& os) {
|
|
||||||
#define RAPIDJSON_COPY() os.Put(c = is.Take())
|
|
||||||
#define RAPIDJSON_TRANS(mask) result &= ((GetRange(static_cast<unsigned char>(c)) & mask) != 0)
|
|
||||||
#define RAPIDJSON_TAIL() RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x70)
|
|
||||||
Ch c;
|
|
||||||
RAPIDJSON_COPY();
|
|
||||||
if (!(c & 0x80))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
bool result = true;
|
|
||||||
switch (GetRange(static_cast<unsigned char>(c))) {
|
|
||||||
case 2: RAPIDJSON_TAIL(); return result;
|
|
||||||
case 3: RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result;
|
|
||||||
case 4: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x50); RAPIDJSON_TAIL(); return result;
|
|
||||||
case 5: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x10); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result;
|
|
||||||
case 6: RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result;
|
|
||||||
case 10: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x20); RAPIDJSON_TAIL(); return result;
|
|
||||||
case 11: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x60); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result;
|
|
||||||
default: return false;
|
|
||||||
}
|
|
||||||
#undef RAPIDJSON_COPY
|
|
||||||
#undef RAPIDJSON_TRANS
|
|
||||||
#undef RAPIDJSON_TAIL
|
|
||||||
}
|
|
||||||
|
|
||||||
static unsigned char GetRange(unsigned char c) {
|
|
||||||
// Referring to DFA of http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
|
||||||
// With new mapping 1 -> 0x10, 7 -> 0x20, 9 -> 0x40, such that AND operation can test multiple types.
|
|
||||||
static const unsigned char type[] = {
|
|
||||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
||||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
||||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
||||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
||||||
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
|
|
||||||
0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,
|
|
||||||
0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
|
|
||||||
0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
|
|
||||||
8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
|
|
||||||
10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8,
|
|
||||||
};
|
|
||||||
return type[c];
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename InputByteStream>
|
|
||||||
static CharType TakeBOM(InputByteStream& is) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
|
||||||
typename InputByteStream::Ch c = Take(is);
|
|
||||||
if (static_cast<unsigned char>(c) != 0xEFu) return c;
|
|
||||||
c = is.Take();
|
|
||||||
if (static_cast<unsigned char>(c) != 0xBBu) return c;
|
|
||||||
c = is.Take();
|
|
||||||
if (static_cast<unsigned char>(c) != 0xBFu) return c;
|
|
||||||
c = is.Take();
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename InputByteStream>
|
|
||||||
static Ch Take(InputByteStream& is) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
|
||||||
return static_cast<Ch>(is.Take());
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename OutputByteStream>
|
|
||||||
static void PutBOM(OutputByteStream& os) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>(0xEFu));
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>(0xBBu));
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>(0xBFu));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename OutputByteStream>
|
|
||||||
static void Put(OutputByteStream& os, Ch c) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>(c));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
// UTF16
|
|
||||||
|
|
||||||
//! UTF-16 encoding.
|
|
||||||
/*! http://en.wikipedia.org/wiki/UTF-16
|
|
||||||
http://tools.ietf.org/html/rfc2781
|
|
||||||
\tparam CharType Type for storing 16-bit UTF-16 data. Default is wchar_t. C++11 may use char16_t instead.
|
|
||||||
\note implements Encoding concept
|
|
||||||
|
|
||||||
\note For in-memory access, no need to concern endianness. The code units and code points are represented by CPU's endianness.
|
|
||||||
For streaming, use UTF16LE and UTF16BE, which handle endianness.
|
|
||||||
*/
|
|
||||||
template<typename CharType = wchar_t>
|
|
||||||
struct UTF16 {
|
|
||||||
typedef CharType Ch;
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(Ch) >= 2);
|
|
||||||
|
|
||||||
enum { supportUnicode = 1 };
|
|
||||||
|
|
||||||
template<typename OutputStream>
|
|
||||||
static void Encode(OutputStream& os, unsigned codepoint) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2);
|
|
||||||
if (codepoint <= 0xFFFF) {
|
|
||||||
RAPIDJSON_ASSERT(codepoint < 0xD800 || codepoint > 0xDFFF); // Code point itself cannot be surrogate pair
|
|
||||||
os.Put(static_cast<typename OutputStream::Ch>(codepoint));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
RAPIDJSON_ASSERT(codepoint <= 0x10FFFF);
|
|
||||||
unsigned v = codepoint - 0x10000;
|
|
||||||
os.Put(static_cast<typename OutputStream::Ch>((v >> 10) | 0xD800));
|
|
||||||
os.Put(static_cast<typename OutputStream::Ch>((v & 0x3FF) | 0xDC00));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
template<typename OutputStream>
|
|
||||||
static void EncodeUnsafe(OutputStream& os, unsigned codepoint) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2);
|
|
||||||
if (codepoint <= 0xFFFF) {
|
|
||||||
RAPIDJSON_ASSERT(codepoint < 0xD800 || codepoint > 0xDFFF); // Code point itself cannot be surrogate pair
|
|
||||||
PutUnsafe(os, static_cast<typename OutputStream::Ch>(codepoint));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
RAPIDJSON_ASSERT(codepoint <= 0x10FFFF);
|
|
||||||
unsigned v = codepoint - 0x10000;
|
|
||||||
PutUnsafe(os, static_cast<typename OutputStream::Ch>((v >> 10) | 0xD800));
|
|
||||||
PutUnsafe(os, static_cast<typename OutputStream::Ch>((v & 0x3FF) | 0xDC00));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename InputStream>
|
|
||||||
static bool Decode(InputStream& is, unsigned* codepoint) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 2);
|
|
||||||
typename InputStream::Ch c = is.Take();
|
|
||||||
if (c < 0xD800 || c > 0xDFFF) {
|
|
||||||
*codepoint = static_cast<unsigned>(c);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (c <= 0xDBFF) {
|
|
||||||
*codepoint = (static_cast<unsigned>(c) & 0x3FF) << 10;
|
|
||||||
c = is.Take();
|
|
||||||
*codepoint |= (static_cast<unsigned>(c) & 0x3FF);
|
|
||||||
*codepoint += 0x10000;
|
|
||||||
return c >= 0xDC00 && c <= 0xDFFF;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename InputStream, typename OutputStream>
|
|
||||||
static bool Validate(InputStream& is, OutputStream& os) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 2);
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2);
|
|
||||||
typename InputStream::Ch c;
|
|
||||||
os.Put(static_cast<typename OutputStream::Ch>(c = is.Take()));
|
|
||||||
if (c < 0xD800 || c > 0xDFFF)
|
|
||||||
return true;
|
|
||||||
else if (c <= 0xDBFF) {
|
|
||||||
os.Put(c = is.Take());
|
|
||||||
return c >= 0xDC00 && c <= 0xDFFF;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//! UTF-16 little endian encoding.
|
|
||||||
template<typename CharType = wchar_t>
|
|
||||||
struct UTF16LE : UTF16<CharType> {
|
|
||||||
template <typename InputByteStream>
|
|
||||||
static CharType TakeBOM(InputByteStream& is) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
|
||||||
CharType c = Take(is);
|
|
||||||
return static_cast<uint16_t>(c) == 0xFEFFu ? Take(is) : c;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename InputByteStream>
|
|
||||||
static CharType Take(InputByteStream& is) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
|
||||||
unsigned c = static_cast<uint8_t>(is.Take());
|
|
||||||
c |= static_cast<unsigned>(static_cast<uint8_t>(is.Take())) << 8;
|
|
||||||
return static_cast<CharType>(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename OutputByteStream>
|
|
||||||
static void PutBOM(OutputByteStream& os) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>(0xFFu));
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>(0xFEu));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename OutputByteStream>
|
|
||||||
static void Put(OutputByteStream& os, CharType c) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>(static_cast<unsigned>(c) & 0xFFu));
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>((static_cast<unsigned>(c) >> 8) & 0xFFu));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//! UTF-16 big endian encoding.
|
|
||||||
template<typename CharType = wchar_t>
|
|
||||||
struct UTF16BE : UTF16<CharType> {
|
|
||||||
template <typename InputByteStream>
|
|
||||||
static CharType TakeBOM(InputByteStream& is) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
|
||||||
CharType c = Take(is);
|
|
||||||
return static_cast<uint16_t>(c) == 0xFEFFu ? Take(is) : c;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename InputByteStream>
|
|
||||||
static CharType Take(InputByteStream& is) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
|
||||||
unsigned c = static_cast<unsigned>(static_cast<uint8_t>(is.Take())) << 8;
|
|
||||||
c |= static_cast<unsigned>(static_cast<uint8_t>(is.Take()));
|
|
||||||
return static_cast<CharType>(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename OutputByteStream>
|
|
||||||
static void PutBOM(OutputByteStream& os) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>(0xFEu));
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>(0xFFu));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename OutputByteStream>
|
|
||||||
static void Put(OutputByteStream& os, CharType c) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>((static_cast<unsigned>(c) >> 8) & 0xFFu));
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>(static_cast<unsigned>(c) & 0xFFu));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
// UTF32
|
|
||||||
|
|
||||||
//! UTF-32 encoding.
|
|
||||||
/*! http://en.wikipedia.org/wiki/UTF-32
|
|
||||||
\tparam CharType Type for storing 32-bit UTF-32 data. Default is unsigned. C++11 may use char32_t instead.
|
|
||||||
\note implements Encoding concept
|
|
||||||
|
|
||||||
\note For in-memory access, no need to concern endianness. The code units and code points are represented by CPU's endianness.
|
|
||||||
For streaming, use UTF32LE and UTF32BE, which handle endianness.
|
|
||||||
*/
|
|
||||||
template<typename CharType = unsigned>
|
|
||||||
struct UTF32 {
|
|
||||||
typedef CharType Ch;
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(Ch) >= 4);
|
|
||||||
|
|
||||||
enum { supportUnicode = 1 };
|
|
||||||
|
|
||||||
template<typename OutputStream>
|
|
||||||
static void Encode(OutputStream& os, unsigned codepoint) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 4);
|
|
||||||
RAPIDJSON_ASSERT(codepoint <= 0x10FFFF);
|
|
||||||
os.Put(codepoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename OutputStream>
|
|
||||||
static void EncodeUnsafe(OutputStream& os, unsigned codepoint) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 4);
|
|
||||||
RAPIDJSON_ASSERT(codepoint <= 0x10FFFF);
|
|
||||||
PutUnsafe(os, codepoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename InputStream>
|
|
||||||
static bool Decode(InputStream& is, unsigned* codepoint) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 4);
|
|
||||||
Ch c = is.Take();
|
|
||||||
*codepoint = c;
|
|
||||||
return c <= 0x10FFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename InputStream, typename OutputStream>
|
|
||||||
static bool Validate(InputStream& is, OutputStream& os) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 4);
|
|
||||||
Ch c;
|
|
||||||
os.Put(c = is.Take());
|
|
||||||
return c <= 0x10FFFF;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//! UTF-32 little endian enocoding.
|
|
||||||
template<typename CharType = unsigned>
|
|
||||||
struct UTF32LE : UTF32<CharType> {
|
|
||||||
template <typename InputByteStream>
|
|
||||||
static CharType TakeBOM(InputByteStream& is) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
|
||||||
CharType c = Take(is);
|
|
||||||
return static_cast<uint32_t>(c) == 0x0000FEFFu ? Take(is) : c;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename InputByteStream>
|
|
||||||
static CharType Take(InputByteStream& is) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
|
||||||
unsigned c = static_cast<uint8_t>(is.Take());
|
|
||||||
c |= static_cast<unsigned>(static_cast<uint8_t>(is.Take())) << 8;
|
|
||||||
c |= static_cast<unsigned>(static_cast<uint8_t>(is.Take())) << 16;
|
|
||||||
c |= static_cast<unsigned>(static_cast<uint8_t>(is.Take())) << 24;
|
|
||||||
return static_cast<CharType>(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename OutputByteStream>
|
|
||||||
static void PutBOM(OutputByteStream& os) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>(0xFFu));
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>(0xFEu));
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>(0x00u));
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>(0x00u));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename OutputByteStream>
|
|
||||||
static void Put(OutputByteStream& os, CharType c) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>(c & 0xFFu));
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>((c >> 8) & 0xFFu));
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>((c >> 16) & 0xFFu));
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>((c >> 24) & 0xFFu));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//! UTF-32 big endian encoding.
|
|
||||||
template<typename CharType = unsigned>
|
|
||||||
struct UTF32BE : UTF32<CharType> {
|
|
||||||
template <typename InputByteStream>
|
|
||||||
static CharType TakeBOM(InputByteStream& is) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
|
||||||
CharType c = Take(is);
|
|
||||||
return static_cast<uint32_t>(c) == 0x0000FEFFu ? Take(is) : c;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename InputByteStream>
|
|
||||||
static CharType Take(InputByteStream& is) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
|
||||||
unsigned c = static_cast<unsigned>(static_cast<uint8_t>(is.Take())) << 24;
|
|
||||||
c |= static_cast<unsigned>(static_cast<uint8_t>(is.Take())) << 16;
|
|
||||||
c |= static_cast<unsigned>(static_cast<uint8_t>(is.Take())) << 8;
|
|
||||||
c |= static_cast<unsigned>(static_cast<uint8_t>(is.Take()));
|
|
||||||
return static_cast<CharType>(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename OutputByteStream>
|
|
||||||
static void PutBOM(OutputByteStream& os) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>(0x00u));
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>(0x00u));
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>(0xFEu));
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>(0xFFu));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename OutputByteStream>
|
|
||||||
static void Put(OutputByteStream& os, CharType c) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>((c >> 24) & 0xFFu));
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>((c >> 16) & 0xFFu));
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>((c >> 8) & 0xFFu));
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>(c & 0xFFu));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
// ASCII
|
|
||||||
|
|
||||||
//! ASCII encoding.
|
|
||||||
/*! http://en.wikipedia.org/wiki/ASCII
|
|
||||||
\tparam CharType Code unit for storing 7-bit ASCII data. Default is char.
|
|
||||||
\note implements Encoding concept
|
|
||||||
*/
|
|
||||||
template<typename CharType = char>
|
|
||||||
struct ASCII {
|
|
||||||
typedef CharType Ch;
|
|
||||||
|
|
||||||
enum { supportUnicode = 0 };
|
|
||||||
|
|
||||||
template<typename OutputStream>
|
|
||||||
static void Encode(OutputStream& os, unsigned codepoint) {
|
|
||||||
RAPIDJSON_ASSERT(codepoint <= 0x7F);
|
|
||||||
os.Put(static_cast<Ch>(codepoint & 0xFF));
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename OutputStream>
|
|
||||||
static void EncodeUnsafe(OutputStream& os, unsigned codepoint) {
|
|
||||||
RAPIDJSON_ASSERT(codepoint <= 0x7F);
|
|
||||||
PutUnsafe(os, static_cast<Ch>(codepoint & 0xFF));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename InputStream>
|
|
||||||
static bool Decode(InputStream& is, unsigned* codepoint) {
|
|
||||||
uint8_t c = static_cast<uint8_t>(is.Take());
|
|
||||||
*codepoint = c;
|
|
||||||
return c <= 0X7F;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename InputStream, typename OutputStream>
|
|
||||||
static bool Validate(InputStream& is, OutputStream& os) {
|
|
||||||
uint8_t c = static_cast<uint8_t>(is.Take());
|
|
||||||
os.Put(static_cast<typename OutputStream::Ch>(c));
|
|
||||||
return c <= 0x7F;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename InputByteStream>
|
|
||||||
static CharType TakeBOM(InputByteStream& is) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
|
||||||
uint8_t c = static_cast<uint8_t>(Take(is));
|
|
||||||
return static_cast<Ch>(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename InputByteStream>
|
|
||||||
static Ch Take(InputByteStream& is) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
|
||||||
return static_cast<Ch>(is.Take());
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename OutputByteStream>
|
|
||||||
static void PutBOM(OutputByteStream& os) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
|
||||||
(void)os;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename OutputByteStream>
|
|
||||||
static void Put(OutputByteStream& os, Ch c) {
|
|
||||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
|
||||||
os.Put(static_cast<typename OutputByteStream::Ch>(c));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
// AutoUTF
|
|
||||||
|
|
||||||
//! Runtime-specified UTF encoding type of a stream.
|
|
||||||
enum UTFType {
|
|
||||||
kUTF8 = 0, //!< UTF-8.
|
|
||||||
kUTF16LE = 1, //!< UTF-16 little endian.
|
|
||||||
kUTF16BE = 2, //!< UTF-16 big endian.
|
|
||||||
kUTF32LE = 3, //!< UTF-32 little endian.
|
|
||||||
kUTF32BE = 4 //!< UTF-32 big endian.
|
|
||||||
};
|
|
||||||
|
|
||||||
//! Dynamically select encoding according to stream's runtime-specified UTF encoding type.
|
|
||||||
/*! \note This class can be used with AutoUTFInputtStream and AutoUTFOutputStream, which provides GetType().
|
|
||||||
*/
|
|
||||||
template<typename CharType>
|
|
||||||
struct AutoUTF {
|
|
||||||
typedef CharType Ch;
|
|
||||||
|
|
||||||
enum { supportUnicode = 1 };
|
|
||||||
|
|
||||||
#define RAPIDJSON_ENCODINGS_FUNC(x) UTF8<Ch>::x, UTF16LE<Ch>::x, UTF16BE<Ch>::x, UTF32LE<Ch>::x, UTF32BE<Ch>::x
|
|
||||||
|
|
||||||
template<typename OutputStream>
|
|
||||||
static RAPIDJSON_FORCEINLINE void Encode(OutputStream& os, unsigned codepoint) {
|
|
||||||
typedef void (*EncodeFunc)(OutputStream&, unsigned);
|
|
||||||
static const EncodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Encode) };
|
|
||||||
(*f[os.GetType()])(os, codepoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename OutputStream>
|
|
||||||
static RAPIDJSON_FORCEINLINE void EncodeUnsafe(OutputStream& os, unsigned codepoint) {
|
|
||||||
typedef void (*EncodeFunc)(OutputStream&, unsigned);
|
|
||||||
static const EncodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(EncodeUnsafe) };
|
|
||||||
(*f[os.GetType()])(os, codepoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename InputStream>
|
|
||||||
static RAPIDJSON_FORCEINLINE bool Decode(InputStream& is, unsigned* codepoint) {
|
|
||||||
typedef bool (*DecodeFunc)(InputStream&, unsigned*);
|
|
||||||
static const DecodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Decode) };
|
|
||||||
return (*f[is.GetType()])(is, codepoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename InputStream, typename OutputStream>
|
|
||||||
static RAPIDJSON_FORCEINLINE bool Validate(InputStream& is, OutputStream& os) {
|
|
||||||
typedef bool (*ValidateFunc)(InputStream&, OutputStream&);
|
|
||||||
static const ValidateFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Validate) };
|
|
||||||
return (*f[is.GetType()])(is, os);
|
|
||||||
}
|
|
||||||
|
|
||||||
#undef RAPIDJSON_ENCODINGS_FUNC
|
|
||||||
};
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Transcoder
|
|
||||||
|
|
||||||
//! Encoding conversion.
|
|
||||||
template<typename SourceEncoding, typename TargetEncoding>
|
|
||||||
struct Transcoder {
|
|
||||||
//! Take one Unicode codepoint from source encoding, convert it to target encoding and put it to the output stream.
|
|
||||||
template<typename InputStream, typename OutputStream>
|
|
||||||
static RAPIDJSON_FORCEINLINE bool Transcode(InputStream& is, OutputStream& os) {
|
|
||||||
unsigned codepoint;
|
|
||||||
if (!SourceEncoding::Decode(is, &codepoint))
|
|
||||||
return false;
|
|
||||||
TargetEncoding::Encode(os, codepoint);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename InputStream, typename OutputStream>
|
|
||||||
static RAPIDJSON_FORCEINLINE bool TranscodeUnsafe(InputStream& is, OutputStream& os) {
|
|
||||||
unsigned codepoint;
|
|
||||||
if (!SourceEncoding::Decode(is, &codepoint))
|
|
||||||
return false;
|
|
||||||
TargetEncoding::EncodeUnsafe(os, codepoint);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Validate one Unicode codepoint from an encoded stream.
|
|
||||||
template<typename InputStream, typename OutputStream>
|
|
||||||
static RAPIDJSON_FORCEINLINE bool Validate(InputStream& is, OutputStream& os) {
|
|
||||||
return Transcode(is, os); // Since source/target encoding is different, must transcode.
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Forward declaration.
|
|
||||||
template<typename Stream>
|
|
||||||
inline void PutUnsafe(Stream& stream, typename Stream::Ch c);
|
|
||||||
|
|
||||||
//! Specialization of Transcoder with same source and target encoding.
|
|
||||||
template<typename Encoding>
|
|
||||||
struct Transcoder<Encoding, Encoding> {
|
|
||||||
template<typename InputStream, typename OutputStream>
|
|
||||||
static RAPIDJSON_FORCEINLINE bool Transcode(InputStream& is, OutputStream& os) {
|
|
||||||
os.Put(is.Take()); // Just copy one code unit. This semantic is different from primary template class.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename InputStream, typename OutputStream>
|
|
||||||
static RAPIDJSON_FORCEINLINE bool TranscodeUnsafe(InputStream& is, OutputStream& os) {
|
|
||||||
PutUnsafe(os, is.Take()); // Just copy one code unit. This semantic is different from primary template class.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename InputStream, typename OutputStream>
|
|
||||||
static RAPIDJSON_FORCEINLINE bool Validate(InputStream& is, OutputStream& os) {
|
|
||||||
return Encoding::Validate(is, os); // source/target encoding are the same
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
RAPIDJSON_NAMESPACE_END
|
|
||||||
|
|
||||||
#if defined(__GNUC__) || (defined(_MSC_VER) && !defined(__clang__))
|
|
||||||
RAPIDJSON_DIAG_POP
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // RAPIDJSON_ENCODINGS_H_
|
|
||||||
74
vendor/github.com/Benau/go_rlottie/lottie_rapidjson_error_en.h
generated
vendored
@@ -1,74 +0,0 @@
|
|||||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
|
||||||
//
|
|
||||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
|
||||||
// in compliance with the License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://opensource.org/licenses/MIT
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software distributed
|
|
||||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations under the License.
|
|
||||||
|
|
||||||
#ifndef RAPIDJSON_ERROR_EN_H_
|
|
||||||
#define RAPIDJSON_ERROR_EN_H_
|
|
||||||
|
|
||||||
#include "lottie_rapidjson_error_error.h"
|
|
||||||
|
|
||||||
#ifdef __clang__
|
|
||||||
RAPIDJSON_DIAG_PUSH
|
|
||||||
RAPIDJSON_DIAG_OFF(switch-enum)
|
|
||||||
RAPIDJSON_DIAG_OFF(covered-switch-default)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
RAPIDJSON_NAMESPACE_BEGIN
|
|
||||||
|
|
||||||
//! Maps error code of parsing into error message.
|
|
||||||
/*!
|
|
||||||
\ingroup RAPIDJSON_ERRORS
|
|
||||||
\param parseErrorCode Error code obtained in parsing.
|
|
||||||
\return the error message.
|
|
||||||
\note User can make a copy of this function for localization.
|
|
||||||
Using switch-case is safer for future modification of error codes.
|
|
||||||
*/
|
|
||||||
inline const RAPIDJSON_ERROR_CHARTYPE* GetParseError_En(ParseErrorCode parseErrorCode) {
|
|
||||||
switch (parseErrorCode) {
|
|
||||||
case kParseErrorNone: return RAPIDJSON_ERROR_STRING("No error.");
|
|
||||||
|
|
||||||
case kParseErrorDocumentEmpty: return RAPIDJSON_ERROR_STRING("The document is empty.");
|
|
||||||
case kParseErrorDocumentRootNotSingular: return RAPIDJSON_ERROR_STRING("The document root must not be followed by other values.");
|
|
||||||
|
|
||||||
case kParseErrorValueInvalid: return RAPIDJSON_ERROR_STRING("Invalid value.");
|
|
||||||
|
|
||||||
case kParseErrorObjectMissName: return RAPIDJSON_ERROR_STRING("Missing a name for object member.");
|
|
||||||
case kParseErrorObjectMissColon: return RAPIDJSON_ERROR_STRING("Missing a colon after a name of object member.");
|
|
||||||
case kParseErrorObjectMissCommaOrCurlyBracket: return RAPIDJSON_ERROR_STRING("Missing a comma or '}' after an object member.");
|
|
||||||
|
|
||||||
case kParseErrorArrayMissCommaOrSquareBracket: return RAPIDJSON_ERROR_STRING("Missing a comma or ']' after an array element.");
|
|
||||||
|
|
||||||
case kParseErrorStringUnicodeEscapeInvalidHex: return RAPIDJSON_ERROR_STRING("Incorrect hex digit after \\u escape in string.");
|
|
||||||
case kParseErrorStringUnicodeSurrogateInvalid: return RAPIDJSON_ERROR_STRING("The surrogate pair in string is invalid.");
|
|
||||||
case kParseErrorStringEscapeInvalid: return RAPIDJSON_ERROR_STRING("Invalid escape character in string.");
|
|
||||||
case kParseErrorStringMissQuotationMark: return RAPIDJSON_ERROR_STRING("Missing a closing quotation mark in string.");
|
|
||||||
case kParseErrorStringInvalidEncoding: return RAPIDJSON_ERROR_STRING("Invalid encoding in string.");
|
|
||||||
|
|
||||||
case kParseErrorNumberTooBig: return RAPIDJSON_ERROR_STRING("Number too big to be stored in double.");
|
|
||||||
case kParseErrorNumberMissFraction: return RAPIDJSON_ERROR_STRING("Miss fraction part in number.");
|
|
||||||
case kParseErrorNumberMissExponent: return RAPIDJSON_ERROR_STRING("Miss exponent in number.");
|
|
||||||
|
|
||||||
case kParseErrorTermination: return RAPIDJSON_ERROR_STRING("Terminate parsing due to Handler error.");
|
|
||||||
case kParseErrorUnspecificSyntaxError: return RAPIDJSON_ERROR_STRING("Unspecific syntax error.");
|
|
||||||
|
|
||||||
default: return RAPIDJSON_ERROR_STRING("Unknown error.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RAPIDJSON_NAMESPACE_END
|
|
||||||
|
|
||||||
#ifdef __clang__
|
|
||||||
RAPIDJSON_DIAG_POP
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // RAPIDJSON_ERROR_EN_H_
|
|
||||||
161
vendor/github.com/Benau/go_rlottie/lottie_rapidjson_error_error.h
generated
vendored
@@ -1,161 +0,0 @@
|
|||||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
|
||||||
//
|
|
||||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
|
||||||
// in compliance with the License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://opensource.org/licenses/MIT
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software distributed
|
|
||||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations under the License.
|
|
||||||
|
|
||||||
#ifndef RAPIDJSON_ERROR_ERROR_H_
|
|
||||||
#define RAPIDJSON_ERROR_ERROR_H_
|
|
||||||
|
|
||||||
#include "lottie_rapidjson_rapidjson.h"
|
|
||||||
|
|
||||||
#ifdef __clang__
|
|
||||||
RAPIDJSON_DIAG_PUSH
|
|
||||||
RAPIDJSON_DIAG_OFF(padded)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*! \file error.h */
|
|
||||||
|
|
||||||
/*! \defgroup RAPIDJSON_ERRORS RapidJSON error handling */
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
// RAPIDJSON_ERROR_CHARTYPE
|
|
||||||
|
|
||||||
//! Character type of error messages.
|
|
||||||
/*! \ingroup RAPIDJSON_ERRORS
|
|
||||||
The default character type is \c char.
|
|
||||||
On Windows, user can define this macro as \c TCHAR for supporting both
|
|
||||||
unicode/non-unicode settings.
|
|
||||||
*/
|
|
||||||
#ifndef RAPIDJSON_ERROR_CHARTYPE
|
|
||||||
#define RAPIDJSON_ERROR_CHARTYPE char
|
|
||||||
#endif
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
// RAPIDJSON_ERROR_STRING
|
|
||||||
|
|
||||||
//! Macro for converting string literial to \ref RAPIDJSON_ERROR_CHARTYPE[].
|
|
||||||
/*! \ingroup RAPIDJSON_ERRORS
|
|
||||||
By default this conversion macro does nothing.
|
|
||||||
On Windows, user can define this macro as \c _T(x) for supporting both
|
|
||||||
unicode/non-unicode settings.
|
|
||||||
*/
|
|
||||||
#ifndef RAPIDJSON_ERROR_STRING
|
|
||||||
#define RAPIDJSON_ERROR_STRING(x) x
|
|
||||||
#endif
|
|
||||||
|
|
||||||
RAPIDJSON_NAMESPACE_BEGIN
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
// ParseErrorCode
|
|
||||||
|
|
||||||
//! Error code of parsing.
|
|
||||||
/*! \ingroup RAPIDJSON_ERRORS
|
|
||||||
\see GenericReader::Parse, GenericReader::GetParseErrorCode
|
|
||||||
*/
|
|
||||||
enum ParseErrorCode {
|
|
||||||
kParseErrorNone = 0, //!< No error.
|
|
||||||
|
|
||||||
kParseErrorDocumentEmpty, //!< The document is empty.
|
|
||||||
kParseErrorDocumentRootNotSingular, //!< The document root must not follow by other values.
|
|
||||||
|
|
||||||
kParseErrorValueInvalid, //!< Invalid value.
|
|
||||||
|
|
||||||
kParseErrorObjectMissName, //!< Missing a name for object member.
|
|
||||||
kParseErrorObjectMissColon, //!< Missing a colon after a name of object member.
|
|
||||||
kParseErrorObjectMissCommaOrCurlyBracket, //!< Missing a comma or '}' after an object member.
|
|
||||||
|
|
||||||
kParseErrorArrayMissCommaOrSquareBracket, //!< Missing a comma or ']' after an array element.
|
|
||||||
|
|
||||||
kParseErrorStringUnicodeEscapeInvalidHex, //!< Incorrect hex digit after \\u escape in string.
|
|
||||||
kParseErrorStringUnicodeSurrogateInvalid, //!< The surrogate pair in string is invalid.
|
|
||||||
kParseErrorStringEscapeInvalid, //!< Invalid escape character in string.
|
|
||||||
kParseErrorStringMissQuotationMark, //!< Missing a closing quotation mark in string.
|
|
||||||
kParseErrorStringInvalidEncoding, //!< Invalid encoding in string.
|
|
||||||
|
|
||||||
kParseErrorNumberTooBig, //!< Number too big to be stored in double.
|
|
||||||
kParseErrorNumberMissFraction, //!< Miss fraction part in number.
|
|
||||||
kParseErrorNumberMissExponent, //!< Miss exponent in number.
|
|
||||||
|
|
||||||
kParseErrorTermination, //!< Parsing was terminated.
|
|
||||||
kParseErrorUnspecificSyntaxError //!< Unspecific syntax error.
|
|
||||||
};
|
|
||||||
|
|
||||||
//! Result of parsing (wraps ParseErrorCode)
|
|
||||||
/*!
|
|
||||||
\ingroup RAPIDJSON_ERRORS
|
|
||||||
\code
|
|
||||||
Document doc;
|
|
||||||
ParseResult ok = doc.Parse("[42]");
|
|
||||||
if (!ok) {
|
|
||||||
fprintf(stderr, "JSON parse error: %s (%u)",
|
|
||||||
GetParseError_En(ok.Code()), ok.Offset());
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
\endcode
|
|
||||||
\see GenericReader::Parse, GenericDocument::Parse
|
|
||||||
*/
|
|
||||||
struct ParseResult {
|
|
||||||
//!! Unspecified boolean type
|
|
||||||
typedef bool (ParseResult::*BooleanType)() const;
|
|
||||||
public:
|
|
||||||
//! Default constructor, no error.
|
|
||||||
ParseResult() : code_(kParseErrorNone), offset_(0) {}
|
|
||||||
//! Constructor to set an error.
|
|
||||||
ParseResult(ParseErrorCode code, size_t offset) : code_(code), offset_(offset) {}
|
|
||||||
|
|
||||||
//! Get the error code.
|
|
||||||
ParseErrorCode Code() const { return code_; }
|
|
||||||
//! Get the error offset, if \ref IsError(), 0 otherwise.
|
|
||||||
size_t Offset() const { return offset_; }
|
|
||||||
|
|
||||||
//! Explicit conversion to \c bool, returns \c true, iff !\ref IsError().
|
|
||||||
operator BooleanType() const { return !IsError() ? &ParseResult::IsError : NULL; }
|
|
||||||
//! Whether the result is an error.
|
|
||||||
bool IsError() const { return code_ != kParseErrorNone; }
|
|
||||||
|
|
||||||
bool operator==(const ParseResult& that) const { return code_ == that.code_; }
|
|
||||||
bool operator==(ParseErrorCode code) const { return code_ == code; }
|
|
||||||
friend bool operator==(ParseErrorCode code, const ParseResult & err) { return code == err.code_; }
|
|
||||||
|
|
||||||
bool operator!=(const ParseResult& that) const { return !(*this == that); }
|
|
||||||
bool operator!=(ParseErrorCode code) const { return !(*this == code); }
|
|
||||||
friend bool operator!=(ParseErrorCode code, const ParseResult & err) { return err != code; }
|
|
||||||
|
|
||||||
//! Reset error code.
|
|
||||||
void Clear() { Set(kParseErrorNone); }
|
|
||||||
//! Update error code and offset.
|
|
||||||
void Set(ParseErrorCode code, size_t offset = 0) { code_ = code; offset_ = offset; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
ParseErrorCode code_;
|
|
||||||
size_t offset_;
|
|
||||||
};
|
|
||||||
|
|
||||||
//! Function pointer type of GetParseError().
|
|
||||||
/*! \ingroup RAPIDJSON_ERRORS
|
|
||||||
|
|
||||||
This is the prototype for \c GetParseError_X(), where \c X is a locale.
|
|
||||||
User can dynamically change locale in runtime, e.g.:
|
|
||||||
\code
|
|
||||||
GetParseErrorFunc GetParseError = GetParseError_En; // or whatever
|
|
||||||
const RAPIDJSON_ERROR_CHARTYPE* s = GetParseError(document.GetParseErrorCode());
|
|
||||||
\endcode
|
|
||||||
*/
|
|
||||||
typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetParseErrorFunc)(ParseErrorCode);
|
|
||||||
|
|
||||||
RAPIDJSON_NAMESPACE_END
|
|
||||||
|
|
||||||
#ifdef __clang__
|
|
||||||
RAPIDJSON_DIAG_POP
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // RAPIDJSON_ERROR_ERROR_H_
|
|
||||||
99
vendor/github.com/Benau/go_rlottie/lottie_rapidjson_filereadstream.h
generated
vendored
@@ -1,99 +0,0 @@
|
|||||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
|
||||||
//
|
|
||||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
|
||||||
// in compliance with the License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://opensource.org/licenses/MIT
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software distributed
|
|
||||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations under the License.
|
|
||||||
|
|
||||||
#ifndef RAPIDJSON_FILEREADSTREAM_H_
|
|
||||||
#define RAPIDJSON_FILEREADSTREAM_H_
|
|
||||||
|
|
||||||
#include "lottie_rapidjson_stream.h"
|
|
||||||
#include <cstdio>
|
|
||||||
|
|
||||||
#ifdef __clang__
|
|
||||||
RAPIDJSON_DIAG_PUSH
|
|
||||||
RAPIDJSON_DIAG_OFF(padded)
|
|
||||||
RAPIDJSON_DIAG_OFF(unreachable-code)
|
|
||||||
RAPIDJSON_DIAG_OFF(missing-noreturn)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
RAPIDJSON_NAMESPACE_BEGIN
|
|
||||||
|
|
||||||
//! File byte stream for input using fread().
|
|
||||||
/*!
|
|
||||||
\note implements Stream concept
|
|
||||||
*/
|
|
||||||
class FileReadStream {
|
|
||||||
public:
|
|
||||||
typedef char Ch; //!< Character type (byte).
|
|
||||||
|
|
||||||
//! Constructor.
|
|
||||||
/*!
|
|
||||||
\param fp File pointer opened for read.
|
|
||||||
\param buffer user-supplied buffer.
|
|
||||||
\param bufferSize size of buffer in bytes. Must >=4 bytes.
|
|
||||||
*/
|
|
||||||
FileReadStream(std::FILE* fp, char* buffer, size_t bufferSize) : fp_(fp), buffer_(buffer), bufferSize_(bufferSize), bufferLast_(0), current_(buffer_), readCount_(0), count_(0), eof_(false) {
|
|
||||||
RAPIDJSON_ASSERT(fp_ != 0);
|
|
||||||
RAPIDJSON_ASSERT(bufferSize >= 4);
|
|
||||||
Read();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ch Peek() const { return *current_; }
|
|
||||||
Ch Take() { Ch c = *current_; Read(); return c; }
|
|
||||||
size_t Tell() const { return count_ + static_cast<size_t>(current_ - buffer_); }
|
|
||||||
|
|
||||||
// Not implemented
|
|
||||||
void Put(Ch) { RAPIDJSON_ASSERT(false); }
|
|
||||||
void Flush() { RAPIDJSON_ASSERT(false); }
|
|
||||||
Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; }
|
|
||||||
size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; }
|
|
||||||
|
|
||||||
// For encoding detection only.
|
|
||||||
const Ch* Peek4() const {
|
|
||||||
return (current_ + 4 - !eof_ <= bufferLast_) ? current_ : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void Read() {
|
|
||||||
if (current_ < bufferLast_)
|
|
||||||
++current_;
|
|
||||||
else if (!eof_) {
|
|
||||||
count_ += readCount_;
|
|
||||||
readCount_ = std::fread(buffer_, 1, bufferSize_, fp_);
|
|
||||||
bufferLast_ = buffer_ + readCount_ - 1;
|
|
||||||
current_ = buffer_;
|
|
||||||
|
|
||||||
if (readCount_ < bufferSize_) {
|
|
||||||
buffer_[readCount_] = '\0';
|
|
||||||
++bufferLast_;
|
|
||||||
eof_ = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::FILE* fp_;
|
|
||||||
Ch *buffer_;
|
|
||||||
size_t bufferSize_;
|
|
||||||
Ch *bufferLast_;
|
|
||||||
Ch *current_;
|
|
||||||
size_t readCount_;
|
|
||||||
size_t count_; //!< Number of characters read
|
|
||||||
bool eof_;
|
|
||||||
};
|
|
||||||
|
|
||||||
RAPIDJSON_NAMESPACE_END
|
|
||||||
|
|
||||||
#ifdef __clang__
|
|
||||||
RAPIDJSON_DIAG_POP
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // RAPIDJSON_FILESTREAM_H_
|
|
||||||
104
vendor/github.com/Benau/go_rlottie/lottie_rapidjson_filewritestream.h
generated
vendored
@@ -1,104 +0,0 @@
|
|||||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
|
||||||
//
|
|
||||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
|
||||||
// in compliance with the License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://opensource.org/licenses/MIT
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software distributed
|
|
||||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations under the License.
|
|
||||||
|
|
||||||
#ifndef RAPIDJSON_FILEWRITESTREAM_H_
|
|
||||||
#define RAPIDJSON_FILEWRITESTREAM_H_
|
|
||||||
|
|
||||||
#include "lottie_rapidjson_stream.h"
|
|
||||||
#include <cstdio>
|
|
||||||
|
|
||||||
#ifdef __clang__
|
|
||||||
RAPIDJSON_DIAG_PUSH
|
|
||||||
RAPIDJSON_DIAG_OFF(unreachable-code)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
RAPIDJSON_NAMESPACE_BEGIN
|
|
||||||
|
|
||||||
//! Wrapper of C file stream for output using fwrite().
|
|
||||||
/*!
|
|
||||||
\note implements Stream concept
|
|
||||||
*/
|
|
||||||
class FileWriteStream {
|
|
||||||
public:
|
|
||||||
typedef char Ch; //!< Character type. Only support char.
|
|
||||||
|
|
||||||
FileWriteStream(std::FILE* fp, char* buffer, size_t bufferSize) : fp_(fp), buffer_(buffer), bufferEnd_(buffer + bufferSize), current_(buffer_) {
|
|
||||||
RAPIDJSON_ASSERT(fp_ != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Put(char c) {
|
|
||||||
if (current_ >= bufferEnd_)
|
|
||||||
Flush();
|
|
||||||
|
|
||||||
*current_++ = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PutN(char c, size_t n) {
|
|
||||||
size_t avail = static_cast<size_t>(bufferEnd_ - current_);
|
|
||||||
while (n > avail) {
|
|
||||||
std::memset(current_, c, avail);
|
|
||||||
current_ += avail;
|
|
||||||
Flush();
|
|
||||||
n -= avail;
|
|
||||||
avail = static_cast<size_t>(bufferEnd_ - current_);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (n > 0) {
|
|
||||||
std::memset(current_, c, n);
|
|
||||||
current_ += n;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Flush() {
|
|
||||||
if (current_ != buffer_) {
|
|
||||||
size_t result = std::fwrite(buffer_, 1, static_cast<size_t>(current_ - buffer_), fp_);
|
|
||||||
if (result < static_cast<size_t>(current_ - buffer_)) {
|
|
||||||
// failure deliberately ignored at this time
|
|
||||||
// added to avoid warn_unused_result build errors
|
|
||||||
}
|
|
||||||
current_ = buffer_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not implemented
|
|
||||||
char Peek() const { RAPIDJSON_ASSERT(false); return 0; }
|
|
||||||
char Take() { RAPIDJSON_ASSERT(false); return 0; }
|
|
||||||
size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; }
|
|
||||||
char* PutBegin() { RAPIDJSON_ASSERT(false); return 0; }
|
|
||||||
size_t PutEnd(char*) { RAPIDJSON_ASSERT(false); return 0; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Prohibit copy constructor & assignment operator.
|
|
||||||
FileWriteStream(const FileWriteStream&);
|
|
||||||
FileWriteStream& operator=(const FileWriteStream&);
|
|
||||||
|
|
||||||
std::FILE* fp_;
|
|
||||||
char *buffer_;
|
|
||||||
char *bufferEnd_;
|
|
||||||
char *current_;
|
|
||||||
};
|
|
||||||
|
|
||||||
//! Implement specialized version of PutN() with memset() for better performance.
|
|
||||||
template<>
|
|
||||||
inline void PutN(FileWriteStream& stream, char c, size_t n) {
|
|
||||||
stream.PutN(c, n);
|
|
||||||
}
|
|
||||||
|
|
||||||
RAPIDJSON_NAMESPACE_END
|
|
||||||
|
|
||||||
#ifdef __clang__
|
|
||||||
RAPIDJSON_DIAG_POP
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // RAPIDJSON_FILESTREAM_H_
|
|
||||||
151
vendor/github.com/Benau/go_rlottie/lottie_rapidjson_fwd.h
generated
vendored
@@ -1,151 +0,0 @@
|
|||||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
|
||||||
//
|
|
||||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
|
||||||
// in compliance with the License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://opensource.org/licenses/MIT
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software distributed
|
|
||||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations under the License.
|
|
||||||
|
|
||||||
#ifndef RAPIDJSON_FWD_H_
|
|
||||||
#define RAPIDJSON_FWD_H_
|
|
||||||
|
|
||||||
#include "lottie_rapidjson_rapidjson.h"
|
|
||||||
|
|
||||||
RAPIDJSON_NAMESPACE_BEGIN
|
|
||||||
|
|
||||||
// encodings.h
|
|
||||||
|
|
||||||
template<typename CharType> struct UTF8;
|
|
||||||
template<typename CharType> struct UTF16;
|
|
||||||
template<typename CharType> struct UTF16BE;
|
|
||||||
template<typename CharType> struct UTF16LE;
|
|
||||||
template<typename CharType> struct UTF32;
|
|
||||||
template<typename CharType> struct UTF32BE;
|
|
||||||
template<typename CharType> struct UTF32LE;
|
|
||||||
template<typename CharType> struct ASCII;
|
|
||||||
template<typename CharType> struct AutoUTF;
|
|
||||||
|
|
||||||
template<typename SourceEncoding, typename TargetEncoding>
|
|
||||||
struct Transcoder;
|
|
||||||
|
|
||||||
// allocators.h
|
|
||||||
|
|
||||||
class CrtAllocator;
|
|
||||||
|
|
||||||
template <typename BaseAllocator>
|
|
||||||
class MemoryPoolAllocator;
|
|
||||||
|
|
||||||
// stream.h
|
|
||||||
|
|
||||||
template <typename Encoding>
|
|
||||||
struct GenericStringStream;
|
|
||||||
|
|
||||||
typedef GenericStringStream<UTF8<char> > StringStream;
|
|
||||||
|
|
||||||
template <typename Encoding>
|
|
||||||
struct GenericInsituStringStream;
|
|
||||||
|
|
||||||
typedef GenericInsituStringStream<UTF8<char> > InsituStringStream;
|
|
||||||
|
|
||||||
// stringbuffer.h
|
|
||||||
|
|
||||||
template <typename Encoding, typename Allocator>
|
|
||||||
class GenericStringBuffer;
|
|
||||||
|
|
||||||
typedef GenericStringBuffer<UTF8<char>, CrtAllocator> StringBuffer;
|
|
||||||
|
|
||||||
// filereadstream.h
|
|
||||||
|
|
||||||
class FileReadStream;
|
|
||||||
|
|
||||||
// filewritestream.h
|
|
||||||
|
|
||||||
class FileWriteStream;
|
|
||||||
|
|
||||||
// memorybuffer.h
|
|
||||||
|
|
||||||
template <typename Allocator>
|
|
||||||
struct GenericMemoryBuffer;
|
|
||||||
|
|
||||||
typedef GenericMemoryBuffer<CrtAllocator> MemoryBuffer;
|
|
||||||
|
|
||||||
// memorystream.h
|
|
||||||
|
|
||||||
struct MemoryStream;
|
|
||||||
|
|
||||||
// reader.h
|
|
||||||
|
|
||||||
template<typename Encoding, typename Derived>
|
|
||||||
struct BaseReaderHandler;
|
|
||||||
|
|
||||||
template <typename SourceEncoding, typename TargetEncoding, typename StackAllocator>
|
|
||||||
class GenericReader;
|
|
||||||
|
|
||||||
typedef GenericReader<UTF8<char>, UTF8<char>, CrtAllocator> Reader;
|
|
||||||
|
|
||||||
// writer.h
|
|
||||||
|
|
||||||
template<typename OutputStream, typename SourceEncoding, typename TargetEncoding, typename StackAllocator, unsigned writeFlags>
|
|
||||||
class Writer;
|
|
||||||
|
|
||||||
// prettywriter.h
|
|
||||||
|
|
||||||
template<typename OutputStream, typename SourceEncoding, typename TargetEncoding, typename StackAllocator, unsigned writeFlags>
|
|
||||||
class PrettyWriter;
|
|
||||||
|
|
||||||
// document.h
|
|
||||||
|
|
||||||
template <typename Encoding, typename Allocator>
|
|
||||||
class GenericMember;
|
|
||||||
|
|
||||||
template <bool Const, typename Encoding, typename Allocator>
|
|
||||||
class GenericMemberIterator;
|
|
||||||
|
|
||||||
template<typename CharType>
|
|
||||||
struct GenericStringRef;
|
|
||||||
|
|
||||||
template <typename Encoding, typename Allocator>
|
|
||||||
class GenericValue;
|
|
||||||
|
|
||||||
typedef GenericValue<UTF8<char>, MemoryPoolAllocator<CrtAllocator> > Value;
|
|
||||||
|
|
||||||
template <typename Encoding, typename Allocator, typename StackAllocator>
|
|
||||||
class GenericDocument;
|
|
||||||
|
|
||||||
typedef GenericDocument<UTF8<char>, MemoryPoolAllocator<CrtAllocator>, CrtAllocator> Document;
|
|
||||||
|
|
||||||
// pointer.h
|
|
||||||
|
|
||||||
template <typename ValueType, typename Allocator>
|
|
||||||
class GenericPointer;
|
|
||||||
|
|
||||||
typedef GenericPointer<Value, CrtAllocator> Pointer;
|
|
||||||
|
|
||||||
// schema.h
|
|
||||||
|
|
||||||
template <typename SchemaDocumentType>
|
|
||||||
class IGenericRemoteSchemaDocumentProvider;
|
|
||||||
|
|
||||||
template <typename ValueT, typename Allocator>
|
|
||||||
class GenericSchemaDocument;
|
|
||||||
|
|
||||||
typedef GenericSchemaDocument<Value, CrtAllocator> SchemaDocument;
|
|
||||||
typedef IGenericRemoteSchemaDocumentProvider<SchemaDocument> IRemoteSchemaDocumentProvider;
|
|
||||||
|
|
||||||
template <
|
|
||||||
typename SchemaDocumentType,
|
|
||||||
typename OutputHandler,
|
|
||||||
typename StateAllocator>
|
|
||||||
class GenericSchemaValidator;
|
|
||||||
|
|
||||||
typedef GenericSchemaValidator<SchemaDocument, BaseReaderHandler<UTF8<char>, void>, CrtAllocator> SchemaValidator;
|
|
||||||
|
|
||||||
RAPIDJSON_NAMESPACE_END
|
|
||||||
|
|
||||||
#endif // RAPIDJSON_RAPIDJSONFWD_H_
|
|
||||||
290
vendor/github.com/Benau/go_rlottie/lottie_rapidjson_internal_biginteger.h
generated
vendored
@@ -1,290 +0,0 @@
|
|||||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
|
||||||
//
|
|
||||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
|
||||||
// in compliance with the License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://opensource.org/licenses/MIT
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software distributed
|
|
||||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations under the License.
|
|
||||||
|
|
||||||
#ifndef RAPIDJSON_BIGINTEGER_H_
|
|
||||||
#define RAPIDJSON_BIGINTEGER_H_
|
|
||||||
|
|
||||||
#include "lottie_rapidjson_rapidjson.h"
|
|
||||||
|
|
||||||
#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) && defined(_M_AMD64)
|
|
||||||
#include <intrin.h> // for _umul128
|
|
||||||
#pragma intrinsic(_umul128)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
RAPIDJSON_NAMESPACE_BEGIN
|
|
||||||
namespace internal {
|
|
||||||
|
|
||||||
class BigInteger {
|
|
||||||
public:
|
|
||||||
typedef uint64_t Type;
|
|
||||||
|
|
||||||
BigInteger(const BigInteger& rhs) : count_(rhs.count_) {
|
|
||||||
std::memcpy(digits_, rhs.digits_, count_ * sizeof(Type));
|
|
||||||
}
|
|
||||||
|
|
||||||
explicit BigInteger(uint64_t u) : count_(1) {
|
|
||||||
digits_[0] = u;
|
|
||||||
}
|
|
||||||
|
|
||||||
BigInteger(const char* decimals, size_t length) : count_(1) {
|
|
||||||
RAPIDJSON_ASSERT(length > 0);
|
|
||||||
digits_[0] = 0;
|
|
||||||
size_t i = 0;
|
|
||||||
const size_t kMaxDigitPerIteration = 19; // 2^64 = 18446744073709551616 > 10^19
|
|
||||||
while (length >= kMaxDigitPerIteration) {
|
|
||||||
AppendDecimal64(decimals + i, decimals + i + kMaxDigitPerIteration);
|
|
||||||
length -= kMaxDigitPerIteration;
|
|
||||||
i += kMaxDigitPerIteration;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (length > 0)
|
|
||||||
AppendDecimal64(decimals + i, decimals + i + length);
|
|
||||||
}
|
|
||||||
|
|
||||||
BigInteger& operator=(const BigInteger &rhs)
|
|
||||||
{
|
|
||||||
if (this != &rhs) {
|
|
||||||
count_ = rhs.count_;
|
|
||||||
std::memcpy(digits_, rhs.digits_, count_ * sizeof(Type));
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
BigInteger& operator=(uint64_t u) {
|
|
||||||
digits_[0] = u;
|
|
||||||
count_ = 1;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
BigInteger& operator+=(uint64_t u) {
|
|
||||||
Type backup = digits_[0];
|
|
||||||
digits_[0] += u;
|
|
||||||
for (size_t i = 0; i < count_ - 1; i++) {
|
|
||||||
if (digits_[i] >= backup)
|
|
||||||
return *this; // no carry
|
|
||||||
backup = digits_[i + 1];
|
|
||||||
digits_[i + 1] += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Last carry
|
|
||||||
if (digits_[count_ - 1] < backup)
|
|
||||||
PushBack(1);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
BigInteger& operator*=(uint64_t u) {
|
|
||||||
if (u == 0) return *this = 0;
|
|
||||||
if (u == 1) return *this;
|
|
||||||
if (*this == 1) return *this = u;
|
|
||||||
|
|
||||||
uint64_t k = 0;
|
|
||||||
for (size_t i = 0; i < count_; i++) {
|
|
||||||
uint64_t hi;
|
|
||||||
digits_[i] = MulAdd64(digits_[i], u, k, &hi);
|
|
||||||
k = hi;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (k > 0)
|
|
||||||
PushBack(k);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
BigInteger& operator*=(uint32_t u) {
|
|
||||||
if (u == 0) return *this = 0;
|
|
||||||
if (u == 1) return *this;
|
|
||||||
if (*this == 1) return *this = u;
|
|
||||||
|
|
||||||
uint64_t k = 0;
|
|
||||||
for (size_t i = 0; i < count_; i++) {
|
|
||||||
const uint64_t c = digits_[i] >> 32;
|
|
||||||
const uint64_t d = digits_[i] & 0xFFFFFFFF;
|
|
||||||
const uint64_t uc = u * c;
|
|
||||||
const uint64_t ud = u * d;
|
|
||||||
const uint64_t p0 = ud + k;
|
|
||||||
const uint64_t p1 = uc + (p0 >> 32);
|
|
||||||
digits_[i] = (p0 & 0xFFFFFFFF) | (p1 << 32);
|
|
||||||
k = p1 >> 32;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (k > 0)
|
|
||||||
PushBack(k);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
BigInteger& operator<<=(size_t shift) {
|
|
||||||
if (IsZero() || shift == 0) return *this;
|
|
||||||
|
|
||||||
size_t offset = shift / kTypeBit;
|
|
||||||
size_t interShift = shift % kTypeBit;
|
|
||||||
RAPIDJSON_ASSERT(count_ + offset <= kCapacity);
|
|
||||||
|
|
||||||
if (interShift == 0) {
|
|
||||||
std::memmove(digits_ + offset, digits_, count_ * sizeof(Type));
|
|
||||||
count_ += offset;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
digits_[count_] = 0;
|
|
||||||
for (size_t i = count_; i > 0; i--)
|
|
||||||
digits_[i + offset] = (digits_[i] << interShift) | (digits_[i - 1] >> (kTypeBit - interShift));
|
|
||||||
digits_[offset] = digits_[0] << interShift;
|
|
||||||
count_ += offset;
|
|
||||||
if (digits_[count_])
|
|
||||||
count_++;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::memset(digits_, 0, offset * sizeof(Type));
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator==(const BigInteger& rhs) const {
|
|
||||||
return count_ == rhs.count_ && std::memcmp(digits_, rhs.digits_, count_ * sizeof(Type)) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator==(const Type rhs) const {
|
|
||||||
return count_ == 1 && digits_[0] == rhs;
|
|
||||||
}
|
|
||||||
|
|
||||||
BigInteger& MultiplyPow5(unsigned exp) {
|
|
||||||
static const uint32_t kPow5[12] = {
|
|
||||||
5,
|
|
||||||
5 * 5,
|
|
||||||
5 * 5 * 5,
|
|
||||||
5 * 5 * 5 * 5,
|
|
||||||
5 * 5 * 5 * 5 * 5,
|
|
||||||
5 * 5 * 5 * 5 * 5 * 5,
|
|
||||||
5 * 5 * 5 * 5 * 5 * 5 * 5,
|
|
||||||
5 * 5 * 5 * 5 * 5 * 5 * 5 * 5,
|
|
||||||
5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5,
|
|
||||||
5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5,
|
|
||||||
5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5,
|
|
||||||
5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5
|
|
||||||
};
|
|
||||||
if (exp == 0) return *this;
|
|
||||||
for (; exp >= 27; exp -= 27) *this *= RAPIDJSON_UINT64_C2(0X6765C793, 0XFA10079D); // 5^27
|
|
||||||
for (; exp >= 13; exp -= 13) *this *= static_cast<uint32_t>(1220703125u); // 5^13
|
|
||||||
if (exp > 0) *this *= kPow5[exp - 1];
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute absolute difference of this and rhs.
|
|
||||||
// Assume this != rhs
|
|
||||||
bool Difference(const BigInteger& rhs, BigInteger* out) const {
|
|
||||||
int cmp = Compare(rhs);
|
|
||||||
RAPIDJSON_ASSERT(cmp != 0);
|
|
||||||
const BigInteger *a, *b; // Makes a > b
|
|
||||||
bool ret;
|
|
||||||
if (cmp < 0) { a = &rhs; b = this; ret = true; }
|
|
||||||
else { a = this; b = &rhs; ret = false; }
|
|
||||||
|
|
||||||
Type borrow = 0;
|
|
||||||
for (size_t i = 0; i < a->count_; i++) {
|
|
||||||
Type d = a->digits_[i] - borrow;
|
|
||||||
if (i < b->count_)
|
|
||||||
d -= b->digits_[i];
|
|
||||||
borrow = (d > a->digits_[i]) ? 1 : 0;
|
|
||||||
out->digits_[i] = d;
|
|
||||||
if (d != 0)
|
|
||||||
out->count_ = i + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Compare(const BigInteger& rhs) const {
|
|
||||||
if (count_ != rhs.count_)
|
|
||||||
return count_ < rhs.count_ ? -1 : 1;
|
|
||||||
|
|
||||||
for (size_t i = count_; i-- > 0;)
|
|
||||||
if (digits_[i] != rhs.digits_[i])
|
|
||||||
return digits_[i] < rhs.digits_[i] ? -1 : 1;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t GetCount() const { return count_; }
|
|
||||||
Type GetDigit(size_t index) const { RAPIDJSON_ASSERT(index < count_); return digits_[index]; }
|
|
||||||
bool IsZero() const { return count_ == 1 && digits_[0] == 0; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
void AppendDecimal64(const char* begin, const char* end) {
|
|
||||||
uint64_t u = ParseUint64(begin, end);
|
|
||||||
if (IsZero())
|
|
||||||
*this = u;
|
|
||||||
else {
|
|
||||||
unsigned exp = static_cast<unsigned>(end - begin);
|
|
||||||
(MultiplyPow5(exp) <<= exp) += u; // *this = *this * 10^exp + u
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PushBack(Type digit) {
|
|
||||||
RAPIDJSON_ASSERT(count_ < kCapacity);
|
|
||||||
digits_[count_++] = digit;
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint64_t ParseUint64(const char* begin, const char* end) {
|
|
||||||
uint64_t r = 0;
|
|
||||||
for (const char* p = begin; p != end; ++p) {
|
|
||||||
RAPIDJSON_ASSERT(*p >= '0' && *p <= '9');
|
|
||||||
r = r * 10u + static_cast<unsigned>(*p - '0');
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assume a * b + k < 2^128
|
|
||||||
static uint64_t MulAdd64(uint64_t a, uint64_t b, uint64_t k, uint64_t* outHigh) {
|
|
||||||
#if defined(_MSC_VER) && defined(_M_AMD64)
|
|
||||||
uint64_t low = _umul128(a, b, outHigh) + k;
|
|
||||||
if (low < k)
|
|
||||||
(*outHigh)++;
|
|
||||||
return low;
|
|
||||||
#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__)
|
|
||||||
__extension__ typedef unsigned __int128 uint128;
|
|
||||||
uint128 p = static_cast<uint128>(a) * static_cast<uint128>(b);
|
|
||||||
p += k;
|
|
||||||
*outHigh = static_cast<uint64_t>(p >> 64);
|
|
||||||
return static_cast<uint64_t>(p);
|
|
||||||
#else
|
|
||||||
const uint64_t a0 = a & 0xFFFFFFFF, a1 = a >> 32, b0 = b & 0xFFFFFFFF, b1 = b >> 32;
|
|
||||||
uint64_t x0 = a0 * b0, x1 = a0 * b1, x2 = a1 * b0, x3 = a1 * b1;
|
|
||||||
x1 += (x0 >> 32); // can't give carry
|
|
||||||
x1 += x2;
|
|
||||||
if (x1 < x2)
|
|
||||||
x3 += (static_cast<uint64_t>(1) << 32);
|
|
||||||
uint64_t lo = (x1 << 32) + (x0 & 0xFFFFFFFF);
|
|
||||||
uint64_t hi = x3 + (x1 >> 32);
|
|
||||||
|
|
||||||
lo += k;
|
|
||||||
if (lo < k)
|
|
||||||
hi++;
|
|
||||||
*outHigh = hi;
|
|
||||||
return lo;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static const size_t kBitCount = 3328; // 64bit * 54 > 10^1000
|
|
||||||
static const size_t kCapacity = kBitCount / sizeof(Type);
|
|
||||||
static const size_t kTypeBit = sizeof(Type) * 8;
|
|
||||||
|
|
||||||
Type digits_[kCapacity];
|
|
||||||
size_t count_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace internal
|
|
||||||
RAPIDJSON_NAMESPACE_END
|
|
||||||
|
|
||||||
#endif // RAPIDJSON_BIGINTEGER_H_
|
|
||||||
71
vendor/github.com/Benau/go_rlottie/lottie_rapidjson_internal_clzll.h
generated
vendored
@@ -1,71 +0,0 @@
|
|||||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
|
||||||
//
|
|
||||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
|
||||||
// in compliance with the License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://opensource.org/licenses/MIT
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software distributed
|
|
||||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations under the License.
|
|
||||||
|
|
||||||
#ifndef RAPIDJSON_CLZLL_H_
|
|
||||||
#define RAPIDJSON_CLZLL_H_
|
|
||||||
|
|
||||||
#include "lottie_rapidjson_rapidjson.h"
|
|
||||||
|
|
||||||
#if defined(_MSC_VER) && !defined(UNDER_CE)
|
|
||||||
#include <intrin.h>
|
|
||||||
#if defined(_WIN64)
|
|
||||||
#pragma intrinsic(_BitScanReverse64)
|
|
||||||
#else
|
|
||||||
#pragma intrinsic(_BitScanReverse)
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
RAPIDJSON_NAMESPACE_BEGIN
|
|
||||||
namespace internal {
|
|
||||||
|
|
||||||
inline uint32_t clzll(uint64_t x) {
|
|
||||||
// Passing 0 to __builtin_clzll is UB in GCC and results in an
|
|
||||||
// infinite loop in the software implementation.
|
|
||||||
RAPIDJSON_ASSERT(x != 0);
|
|
||||||
|
|
||||||
#if defined(_MSC_VER) && !defined(UNDER_CE)
|
|
||||||
unsigned long r = 0;
|
|
||||||
#if defined(_WIN64)
|
|
||||||
_BitScanReverse64(&r, x);
|
|
||||||
#else
|
|
||||||
// Scan the high 32 bits.
|
|
||||||
if (_BitScanReverse(&r, static_cast<uint32_t>(x >> 32)))
|
|
||||||
return 63 - (r + 32);
|
|
||||||
|
|
||||||
// Scan the low 32 bits.
|
|
||||||
_BitScanReverse(&r, static_cast<uint32_t>(x & 0xFFFFFFFF));
|
|
||||||
#endif // _WIN64
|
|
||||||
|
|
||||||
return 63 - r;
|
|
||||||
#elif (defined(__GNUC__) && __GNUC__ >= 4) || RAPIDJSON_HAS_BUILTIN(__builtin_clzll)
|
|
||||||
// __builtin_clzll wrapper
|
|
||||||
return static_cast<uint32_t>(__builtin_clzll(x));
|
|
||||||
#else
|
|
||||||
// naive version
|
|
||||||
uint32_t r = 0;
|
|
||||||
while (!(x & (static_cast<uint64_t>(1) << 63))) {
|
|
||||||
x <<= 1;
|
|
||||||
++r;
|
|
||||||
}
|
|
||||||
|
|
||||||
return r;
|
|
||||||
#endif // _MSC_VER
|
|
||||||
}
|
|
||||||
|
|
||||||
#define RAPIDJSON_CLZLL RAPIDJSON_NAMESPACE::internal::clzll
|
|
||||||
|
|
||||||
} // namespace internal
|
|
||||||
RAPIDJSON_NAMESPACE_END
|
|
||||||
|
|
||||||
#endif // RAPIDJSON_CLZLL_H_
|
|
||||||
257
vendor/github.com/Benau/go_rlottie/lottie_rapidjson_internal_diyfp.h
generated
vendored
@@ -1,257 +0,0 @@
|
|||||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
|
||||||
//
|
|
||||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
|
||||||
// in compliance with the License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://opensource.org/licenses/MIT
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software distributed
|
|
||||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations under the License.
|
|
||||||
|
|
||||||
// This is a C++ header-only implementation of Grisu2 algorithm from the publication:
|
|
||||||
// Loitsch, Florian. "Printing floating-point numbers quickly and accurately with
|
|
||||||
// integers." ACM Sigplan Notices 45.6 (2010): 233-243.
|
|
||||||
|
|
||||||
#ifndef RAPIDJSON_DIYFP_H_
|
|
||||||
#define RAPIDJSON_DIYFP_H_
|
|
||||||
|
|
||||||
#include "lottie_rapidjson_rapidjson.h"
|
|
||||||
#include "lottie_rapidjson_internal_clzll.h"
|
|
||||||
#include <limits>
|
|
||||||
|
|
||||||
#if defined(_MSC_VER) && defined(_M_AMD64) && !defined(__INTEL_COMPILER)
|
|
||||||
#include <intrin.h>
|
|
||||||
#pragma intrinsic(_umul128)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
RAPIDJSON_NAMESPACE_BEGIN
|
|
||||||
namespace internal {
|
|
||||||
|
|
||||||
#ifdef __GNUC__
|
|
||||||
RAPIDJSON_DIAG_PUSH
|
|
||||||
RAPIDJSON_DIAG_OFF(effc++)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __clang__
|
|
||||||
RAPIDJSON_DIAG_PUSH
|
|
||||||
RAPIDJSON_DIAG_OFF(padded)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct DiyFp {
|
|
||||||
DiyFp() : f(), e() {}
|
|
||||||
|
|
||||||
DiyFp(uint64_t fp, int exp) : f(fp), e(exp) {}
|
|
||||||
|
|
||||||
explicit DiyFp(double d) {
|
|
||||||
union {
|
|
||||||
double d;
|
|
||||||
uint64_t u64;
|
|
||||||
} u = { d };
|
|
||||||
|
|
||||||
int biased_e = static_cast<int>((u.u64 & kDpExponentMask) >> kDpSignificandSize);
|
|
||||||
uint64_t significand = (u.u64 & kDpSignificandMask);
|
|
||||||
if (biased_e != 0) {
|
|
||||||
f = significand + kDpHiddenBit;
|
|
||||||
e = biased_e - kDpExponentBias;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
f = significand;
|
|
||||||
e = kDpMinExponent + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DiyFp operator-(const DiyFp& rhs) const {
|
|
||||||
return DiyFp(f - rhs.f, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
DiyFp operator*(const DiyFp& rhs) const {
|
|
||||||
#if defined(_MSC_VER) && defined(_M_AMD64)
|
|
||||||
uint64_t h;
|
|
||||||
uint64_t l = _umul128(f, rhs.f, &h);
|
|
||||||
if (l & (uint64_t(1) << 63)) // rounding
|
|
||||||
h++;
|
|
||||||
return DiyFp(h, e + rhs.e + 64);
|
|
||||||
#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__)
|
|
||||||
__extension__ typedef unsigned __int128 uint128;
|
|
||||||
uint128 p = static_cast<uint128>(f) * static_cast<uint128>(rhs.f);
|
|
||||||
uint64_t h = static_cast<uint64_t>(p >> 64);
|
|
||||||
uint64_t l = static_cast<uint64_t>(p);
|
|
||||||
if (l & (uint64_t(1) << 63)) // rounding
|
|
||||||
h++;
|
|
||||||
return DiyFp(h, e + rhs.e + 64);
|
|
||||||
#else
|
|
||||||
const uint64_t M32 = 0xFFFFFFFF;
|
|
||||||
const uint64_t a = f >> 32;
|
|
||||||
const uint64_t b = f & M32;
|
|
||||||
const uint64_t c = rhs.f >> 32;
|
|
||||||
const uint64_t d = rhs.f & M32;
|
|
||||||
const uint64_t ac = a * c;
|
|
||||||
const uint64_t bc = b * c;
|
|
||||||
const uint64_t ad = a * d;
|
|
||||||
const uint64_t bd = b * d;
|
|
||||||
uint64_t tmp = (bd >> 32) + (ad & M32) + (bc & M32);
|
|
||||||
tmp += 1U << 31; /// mult_round
|
|
||||||
return DiyFp(ac + (ad >> 32) + (bc >> 32) + (tmp >> 32), e + rhs.e + 64);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
DiyFp Normalize() const {
|
|
||||||
int s = static_cast<int>(clzll(f));
|
|
||||||
return DiyFp(f << s, e - s);
|
|
||||||
}
|
|
||||||
|
|
||||||
DiyFp NormalizeBoundary() const {
|
|
||||||
DiyFp res = *this;
|
|
||||||
while (!(res.f & (kDpHiddenBit << 1))) {
|
|
||||||
res.f <<= 1;
|
|
||||||
res.e--;
|
|
||||||
}
|
|
||||||
res.f <<= (kDiySignificandSize - kDpSignificandSize - 2);
|
|
||||||
res.e = res.e - (kDiySignificandSize - kDpSignificandSize - 2);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NormalizedBoundaries(DiyFp* minus, DiyFp* plus) const {
|
|
||||||
DiyFp pl = DiyFp((f << 1) + 1, e - 1).NormalizeBoundary();
|
|
||||||
DiyFp mi = (f == kDpHiddenBit) ? DiyFp((f << 2) - 1, e - 2) : DiyFp((f << 1) - 1, e - 1);
|
|
||||||
mi.f <<= mi.e - pl.e;
|
|
||||||
mi.e = pl.e;
|
|
||||||
*plus = pl;
|
|
||||||
*minus = mi;
|
|
||||||
}
|
|
||||||
|
|
||||||
double ToDouble() const {
|
|
||||||
union {
|
|
||||||
double d;
|
|
||||||
uint64_t u64;
|
|
||||||
}u;
|
|
||||||
RAPIDJSON_ASSERT(f <= kDpHiddenBit + kDpSignificandMask);
|
|
||||||
if (e < kDpDenormalExponent) {
|
|
||||||
// Underflow.
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
if (e >= kDpMaxExponent) {
|
|
||||||
// Overflow.
|
|
||||||
return std::numeric_limits<double>::infinity();
|
|
||||||
}
|
|
||||||
const uint64_t be = (e == kDpDenormalExponent && (f & kDpHiddenBit) == 0) ? 0 :
|
|
||||||
static_cast<uint64_t>(e + kDpExponentBias);
|
|
||||||
u.u64 = (f & kDpSignificandMask) | (be << kDpSignificandSize);
|
|
||||||
return u.d;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const int kDiySignificandSize = 64;
|
|
||||||
static const int kDpSignificandSize = 52;
|
|
||||||
static const int kDpExponentBias = 0x3FF + kDpSignificandSize;
|
|
||||||
static const int kDpMaxExponent = 0x7FF - kDpExponentBias;
|
|
||||||
static const int kDpMinExponent = -kDpExponentBias;
|
|
||||||
static const int kDpDenormalExponent = -kDpExponentBias + 1;
|
|
||||||
static const uint64_t kDpExponentMask = RAPIDJSON_UINT64_C2(0x7FF00000, 0x00000000);
|
|
||||||
static const uint64_t kDpSignificandMask = RAPIDJSON_UINT64_C2(0x000FFFFF, 0xFFFFFFFF);
|
|
||||||
static const uint64_t kDpHiddenBit = RAPIDJSON_UINT64_C2(0x00100000, 0x00000000);
|
|
||||||
|
|
||||||
uint64_t f;
|
|
||||||
int e;
|
|
||||||
};
|
|
||||||
|
|
||||||
inline DiyFp GetCachedPowerByIndex(size_t index) {
|
|
||||||
// 10^-348, 10^-340, ..., 10^340
|
|
||||||
static const uint64_t kCachedPowers_F[] = {
|
|
||||||
RAPIDJSON_UINT64_C2(0xfa8fd5a0, 0x081c0288), RAPIDJSON_UINT64_C2(0xbaaee17f, 0xa23ebf76),
|
|
||||||
RAPIDJSON_UINT64_C2(0x8b16fb20, 0x3055ac76), RAPIDJSON_UINT64_C2(0xcf42894a, 0x5dce35ea),
|
|
||||||
RAPIDJSON_UINT64_C2(0x9a6bb0aa, 0x55653b2d), RAPIDJSON_UINT64_C2(0xe61acf03, 0x3d1a45df),
|
|
||||||
RAPIDJSON_UINT64_C2(0xab70fe17, 0xc79ac6ca), RAPIDJSON_UINT64_C2(0xff77b1fc, 0xbebcdc4f),
|
|
||||||
RAPIDJSON_UINT64_C2(0xbe5691ef, 0x416bd60c), RAPIDJSON_UINT64_C2(0x8dd01fad, 0x907ffc3c),
|
|
||||||
RAPIDJSON_UINT64_C2(0xd3515c28, 0x31559a83), RAPIDJSON_UINT64_C2(0x9d71ac8f, 0xada6c9b5),
|
|
||||||
RAPIDJSON_UINT64_C2(0xea9c2277, 0x23ee8bcb), RAPIDJSON_UINT64_C2(0xaecc4991, 0x4078536d),
|
|
||||||
RAPIDJSON_UINT64_C2(0x823c1279, 0x5db6ce57), RAPIDJSON_UINT64_C2(0xc2109436, 0x4dfb5637),
|
|
||||||
RAPIDJSON_UINT64_C2(0x9096ea6f, 0x3848984f), RAPIDJSON_UINT64_C2(0xd77485cb, 0x25823ac7),
|
|
||||||
RAPIDJSON_UINT64_C2(0xa086cfcd, 0x97bf97f4), RAPIDJSON_UINT64_C2(0xef340a98, 0x172aace5),
|
|
||||||
RAPIDJSON_UINT64_C2(0xb23867fb, 0x2a35b28e), RAPIDJSON_UINT64_C2(0x84c8d4df, 0xd2c63f3b),
|
|
||||||
RAPIDJSON_UINT64_C2(0xc5dd4427, 0x1ad3cdba), RAPIDJSON_UINT64_C2(0x936b9fce, 0xbb25c996),
|
|
||||||
RAPIDJSON_UINT64_C2(0xdbac6c24, 0x7d62a584), RAPIDJSON_UINT64_C2(0xa3ab6658, 0x0d5fdaf6),
|
|
||||||
RAPIDJSON_UINT64_C2(0xf3e2f893, 0xdec3f126), RAPIDJSON_UINT64_C2(0xb5b5ada8, 0xaaff80b8),
|
|
||||||
RAPIDJSON_UINT64_C2(0x87625f05, 0x6c7c4a8b), RAPIDJSON_UINT64_C2(0xc9bcff60, 0x34c13053),
|
|
||||||
RAPIDJSON_UINT64_C2(0x964e858c, 0x91ba2655), RAPIDJSON_UINT64_C2(0xdff97724, 0x70297ebd),
|
|
||||||
RAPIDJSON_UINT64_C2(0xa6dfbd9f, 0xb8e5b88f), RAPIDJSON_UINT64_C2(0xf8a95fcf, 0x88747d94),
|
|
||||||
RAPIDJSON_UINT64_C2(0xb9447093, 0x8fa89bcf), RAPIDJSON_UINT64_C2(0x8a08f0f8, 0xbf0f156b),
|
|
||||||
RAPIDJSON_UINT64_C2(0xcdb02555, 0x653131b6), RAPIDJSON_UINT64_C2(0x993fe2c6, 0xd07b7fac),
|
|
||||||
RAPIDJSON_UINT64_C2(0xe45c10c4, 0x2a2b3b06), RAPIDJSON_UINT64_C2(0xaa242499, 0x697392d3),
|
|
||||||
RAPIDJSON_UINT64_C2(0xfd87b5f2, 0x8300ca0e), RAPIDJSON_UINT64_C2(0xbce50864, 0x92111aeb),
|
|
||||||
RAPIDJSON_UINT64_C2(0x8cbccc09, 0x6f5088cc), RAPIDJSON_UINT64_C2(0xd1b71758, 0xe219652c),
|
|
||||||
RAPIDJSON_UINT64_C2(0x9c400000, 0x00000000), RAPIDJSON_UINT64_C2(0xe8d4a510, 0x00000000),
|
|
||||||
RAPIDJSON_UINT64_C2(0xad78ebc5, 0xac620000), RAPIDJSON_UINT64_C2(0x813f3978, 0xf8940984),
|
|
||||||
RAPIDJSON_UINT64_C2(0xc097ce7b, 0xc90715b3), RAPIDJSON_UINT64_C2(0x8f7e32ce, 0x7bea5c70),
|
|
||||||
RAPIDJSON_UINT64_C2(0xd5d238a4, 0xabe98068), RAPIDJSON_UINT64_C2(0x9f4f2726, 0x179a2245),
|
|
||||||
RAPIDJSON_UINT64_C2(0xed63a231, 0xd4c4fb27), RAPIDJSON_UINT64_C2(0xb0de6538, 0x8cc8ada8),
|
|
||||||
RAPIDJSON_UINT64_C2(0x83c7088e, 0x1aab65db), RAPIDJSON_UINT64_C2(0xc45d1df9, 0x42711d9a),
|
|
||||||
RAPIDJSON_UINT64_C2(0x924d692c, 0xa61be758), RAPIDJSON_UINT64_C2(0xda01ee64, 0x1a708dea),
|
|
||||||
RAPIDJSON_UINT64_C2(0xa26da399, 0x9aef774a), RAPIDJSON_UINT64_C2(0xf209787b, 0xb47d6b85),
|
|
||||||
RAPIDJSON_UINT64_C2(0xb454e4a1, 0x79dd1877), RAPIDJSON_UINT64_C2(0x865b8692, 0x5b9bc5c2),
|
|
||||||
RAPIDJSON_UINT64_C2(0xc83553c5, 0xc8965d3d), RAPIDJSON_UINT64_C2(0x952ab45c, 0xfa97a0b3),
|
|
||||||
RAPIDJSON_UINT64_C2(0xde469fbd, 0x99a05fe3), RAPIDJSON_UINT64_C2(0xa59bc234, 0xdb398c25),
|
|
||||||
RAPIDJSON_UINT64_C2(0xf6c69a72, 0xa3989f5c), RAPIDJSON_UINT64_C2(0xb7dcbf53, 0x54e9bece),
|
|
||||||
RAPIDJSON_UINT64_C2(0x88fcf317, 0xf22241e2), RAPIDJSON_UINT64_C2(0xcc20ce9b, 0xd35c78a5),
|
|
||||||
RAPIDJSON_UINT64_C2(0x98165af3, 0x7b2153df), RAPIDJSON_UINT64_C2(0xe2a0b5dc, 0x971f303a),
|
|
||||||
RAPIDJSON_UINT64_C2(0xa8d9d153, 0x5ce3b396), RAPIDJSON_UINT64_C2(0xfb9b7cd9, 0xa4a7443c),
|
|
||||||
RAPIDJSON_UINT64_C2(0xbb764c4c, 0xa7a44410), RAPIDJSON_UINT64_C2(0x8bab8eef, 0xb6409c1a),
|
|
||||||
RAPIDJSON_UINT64_C2(0xd01fef10, 0xa657842c), RAPIDJSON_UINT64_C2(0x9b10a4e5, 0xe9913129),
|
|
||||||
RAPIDJSON_UINT64_C2(0xe7109bfb, 0xa19c0c9d), RAPIDJSON_UINT64_C2(0xac2820d9, 0x623bf429),
|
|
||||||
RAPIDJSON_UINT64_C2(0x80444b5e, 0x7aa7cf85), RAPIDJSON_UINT64_C2(0xbf21e440, 0x03acdd2d),
|
|
||||||
RAPIDJSON_UINT64_C2(0x8e679c2f, 0x5e44ff8f), RAPIDJSON_UINT64_C2(0xd433179d, 0x9c8cb841),
|
|
||||||
RAPIDJSON_UINT64_C2(0x9e19db92, 0xb4e31ba9), RAPIDJSON_UINT64_C2(0xeb96bf6e, 0xbadf77d9),
|
|
||||||
RAPIDJSON_UINT64_C2(0xaf87023b, 0x9bf0ee6b)
|
|
||||||
};
|
|
||||||
static const int16_t kCachedPowers_E[] = {
|
|
||||||
-1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980,
|
|
||||||
-954, -927, -901, -874, -847, -821, -794, -768, -741, -715,
|
|
||||||
-688, -661, -635, -608, -582, -555, -529, -502, -475, -449,
|
|
||||||
-422, -396, -369, -343, -316, -289, -263, -236, -210, -183,
|
|
||||||
-157, -130, -103, -77, -50, -24, 3, 30, 56, 83,
|
|
||||||
109, 136, 162, 189, 216, 242, 269, 295, 322, 348,
|
|
||||||
375, 402, 428, 455, 481, 508, 534, 561, 588, 614,
|
|
||||||
641, 667, 694, 720, 747, 774, 800, 827, 853, 880,
|
|
||||||
907, 933, 960, 986, 1013, 1039, 1066
|
|
||||||
};
|
|
||||||
RAPIDJSON_ASSERT(index < 87);
|
|
||||||
return DiyFp(kCachedPowers_F[index], kCachedPowers_E[index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline DiyFp GetCachedPower(int e, int* K) {
|
|
||||||
|
|
||||||
//int k = static_cast<int>(ceil((-61 - e) * 0.30102999566398114)) + 374;
|
|
||||||
double dk = (-61 - e) * 0.30102999566398114 + 347; // dk must be positive, so can do ceiling in positive
|
|
||||||
int k = static_cast<int>(dk);
|
|
||||||
if (dk - k > 0.0)
|
|
||||||
k++;
|
|
||||||
|
|
||||||
unsigned index = static_cast<unsigned>((k >> 3) + 1);
|
|
||||||
*K = -(-348 + static_cast<int>(index << 3)); // decimal exponent no need lookup table
|
|
||||||
|
|
||||||
return GetCachedPowerByIndex(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline DiyFp GetCachedPower10(int exp, int *outExp) {
|
|
||||||
RAPIDJSON_ASSERT(exp >= -348);
|
|
||||||
unsigned index = static_cast<unsigned>(exp + 348) / 8u;
|
|
||||||
*outExp = -348 + static_cast<int>(index) * 8;
|
|
||||||
return GetCachedPowerByIndex(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef __GNUC__
|
|
||||||
RAPIDJSON_DIAG_POP
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __clang__
|
|
||||||
RAPIDJSON_DIAG_POP
|
|
||||||
RAPIDJSON_DIAG_OFF(padded)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace internal
|
|
||||||
RAPIDJSON_NAMESPACE_END
|
|
||||||
|
|
||||||
#endif // RAPIDJSON_DIYFP_H_
|
|
||||||
245
vendor/github.com/Benau/go_rlottie/lottie_rapidjson_internal_dtoa.h
generated
vendored
@@ -1,245 +0,0 @@
|
|||||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
|
||||||
//
|
|
||||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
|
||||||
// in compliance with the License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://opensource.org/licenses/MIT
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software distributed
|
|
||||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations under the License.
|
|
||||||
|
|
||||||
// This is a C++ header-only implementation of Grisu2 algorithm from the publication:
|
|
||||||
// Loitsch, Florian. "Printing floating-point numbers quickly and accurately with
|
|
||||||
// integers." ACM Sigplan Notices 45.6 (2010): 233-243.
|
|
||||||
|
|
||||||
#ifndef RAPIDJSON_DTOA_
|
|
||||||
#define RAPIDJSON_DTOA_
|
|
||||||
|
|
||||||
#include "lottie_rapidjson_internal_itoa.h"
|
|
||||||
#include "lottie_rapidjson_internal_diyfp.h"
|
|
||||||
#include "lottie_rapidjson_internal_ieee754.h"
|
|
||||||
|
|
||||||
RAPIDJSON_NAMESPACE_BEGIN
|
|
||||||
namespace internal {
|
|
||||||
|
|
||||||
#ifdef __GNUC__
|
|
||||||
RAPIDJSON_DIAG_PUSH
|
|
||||||
RAPIDJSON_DIAG_OFF(effc++)
|
|
||||||
RAPIDJSON_DIAG_OFF(array-bounds) // some gcc versions generate wrong warnings https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59124
|
|
||||||
#endif
|
|
||||||
|
|
||||||
inline void GrisuRound(char* buffer, int len, uint64_t delta, uint64_t rest, uint64_t ten_kappa, uint64_t wp_w) {
|
|
||||||
while (rest < wp_w && delta - rest >= ten_kappa &&
|
|
||||||
(rest + ten_kappa < wp_w || /// closer
|
|
||||||
wp_w - rest > rest + ten_kappa - wp_w)) {
|
|
||||||
buffer[len - 1]--;
|
|
||||||
rest += ten_kappa;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline int CountDecimalDigit32(uint32_t n) {
|
|
||||||
// Simple pure C++ implementation was faster than __builtin_clz version in this situation.
|
|
||||||
if (n < 10) return 1;
|
|
||||||
if (n < 100) return 2;
|
|
||||||
if (n < 1000) return 3;
|
|
||||||
if (n < 10000) return 4;
|
|
||||||
if (n < 100000) return 5;
|
|
||||||
if (n < 1000000) return 6;
|
|
||||||
if (n < 10000000) return 7;
|
|
||||||
if (n < 100000000) return 8;
|
|
||||||
// Will not reach 10 digits in DigitGen()
|
|
||||||
//if (n < 1000000000) return 9;
|
|
||||||
//return 10;
|
|
||||||
return 9;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void DigitGen(const DiyFp& W, const DiyFp& Mp, uint64_t delta, char* buffer, int* len, int* K) {
|
|
||||||
static const uint32_t kPow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
|
|
||||||
const DiyFp one(uint64_t(1) << -Mp.e, Mp.e);
|
|
||||||
const DiyFp wp_w = Mp - W;
|
|
||||||
uint32_t p1 = static_cast<uint32_t>(Mp.f >> -one.e);
|
|
||||||
uint64_t p2 = Mp.f & (one.f - 1);
|
|
||||||
int kappa = CountDecimalDigit32(p1); // kappa in [0, 9]
|
|
||||||
*len = 0;
|
|
||||||
|
|
||||||
while (kappa > 0) {
|
|
||||||
uint32_t d = 0;
|
|
||||||
switch (kappa) {
|
|
||||||
case 9: d = p1 / 100000000; p1 %= 100000000; break;
|
|
||||||
case 8: d = p1 / 10000000; p1 %= 10000000; break;
|
|
||||||
case 7: d = p1 / 1000000; p1 %= 1000000; break;
|
|
||||||
case 6: d = p1 / 100000; p1 %= 100000; break;
|
|
||||||
case 5: d = p1 / 10000; p1 %= 10000; break;
|
|
||||||
case 4: d = p1 / 1000; p1 %= 1000; break;
|
|
||||||
case 3: d = p1 / 100; p1 %= 100; break;
|
|
||||||
case 2: d = p1 / 10; p1 %= 10; break;
|
|
||||||
case 1: d = p1; p1 = 0; break;
|
|
||||||
default:;
|
|
||||||
}
|
|
||||||
if (d || *len)
|
|
||||||
buffer[(*len)++] = static_cast<char>('0' + static_cast<char>(d));
|
|
||||||
kappa--;
|
|
||||||
uint64_t tmp = (static_cast<uint64_t>(p1) << -one.e) + p2;
|
|
||||||
if (tmp <= delta) {
|
|
||||||
*K += kappa;
|
|
||||||
GrisuRound(buffer, *len, delta, tmp, static_cast<uint64_t>(kPow10[kappa]) << -one.e, wp_w.f);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// kappa = 0
|
|
||||||
for (;;) {
|
|
||||||
p2 *= 10;
|
|
||||||
delta *= 10;
|
|
||||||
char d = static_cast<char>(p2 >> -one.e);
|
|
||||||
if (d || *len)
|
|
||||||
buffer[(*len)++] = static_cast<char>('0' + d);
|
|
||||||
p2 &= one.f - 1;
|
|
||||||
kappa--;
|
|
||||||
if (p2 < delta) {
|
|
||||||
*K += kappa;
|
|
||||||
int index = -kappa;
|
|
||||||
GrisuRound(buffer, *len, delta, p2, one.f, wp_w.f * (index < 9 ? kPow10[index] : 0));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void Grisu2(double value, char* buffer, int* length, int* K) {
|
|
||||||
const DiyFp v(value);
|
|
||||||
DiyFp w_m, w_p;
|
|
||||||
v.NormalizedBoundaries(&w_m, &w_p);
|
|
||||||
|
|
||||||
const DiyFp c_mk = GetCachedPower(w_p.e, K);
|
|
||||||
const DiyFp W = v.Normalize() * c_mk;
|
|
||||||
DiyFp Wp = w_p * c_mk;
|
|
||||||
DiyFp Wm = w_m * c_mk;
|
|
||||||
Wm.f++;
|
|
||||||
Wp.f--;
|
|
||||||
DigitGen(W, Wp, Wp.f - Wm.f, buffer, length, K);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline char* WriteExponent(int K, char* buffer) {
|
|
||||||
if (K < 0) {
|
|
||||||
*buffer++ = '-';
|
|
||||||
K = -K;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (K >= 100) {
|
|
||||||
*buffer++ = static_cast<char>('0' + static_cast<char>(K / 100));
|
|
||||||
K %= 100;
|
|
||||||
const char* d = GetDigitsLut() + K * 2;
|
|
||||||
*buffer++ = d[0];
|
|
||||||
*buffer++ = d[1];
|
|
||||||
}
|
|
||||||
else if (K >= 10) {
|
|
||||||
const char* d = GetDigitsLut() + K * 2;
|
|
||||||
*buffer++ = d[0];
|
|
||||||
*buffer++ = d[1];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
*buffer++ = static_cast<char>('0' + static_cast<char>(K));
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline char* Prettify(char* buffer, int length, int k, int maxDecimalPlaces) {
|
|
||||||
const int kk = length + k; // 10^(kk-1) <= v < 10^kk
|
|
||||||
|
|
||||||
if (0 <= k && kk <= 21) {
|
|
||||||
// 1234e7 -> 12340000000
|
|
||||||
for (int i = length; i < kk; i++)
|
|
||||||
buffer[i] = '0';
|
|
||||||
buffer[kk] = '.';
|
|
||||||
buffer[kk + 1] = '0';
|
|
||||||
return &buffer[kk + 2];
|
|
||||||
}
|
|
||||||
else if (0 < kk && kk <= 21) {
|
|
||||||
// 1234e-2 -> 12.34
|
|
||||||
std::memmove(&buffer[kk + 1], &buffer[kk], static_cast<size_t>(length - kk));
|
|
||||||
buffer[kk] = '.';
|
|
||||||
if (0 > k + maxDecimalPlaces) {
|
|
||||||
// When maxDecimalPlaces = 2, 1.2345 -> 1.23, 1.102 -> 1.1
|
|
||||||
// Remove extra trailing zeros (at least one) after truncation.
|
|
||||||
for (int i = kk + maxDecimalPlaces; i > kk + 1; i--)
|
|
||||||
if (buffer[i] != '0')
|
|
||||||
return &buffer[i + 1];
|
|
||||||
return &buffer[kk + 2]; // Reserve one zero
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return &buffer[length + 1];
|
|
||||||
}
|
|
||||||
else if (-6 < kk && kk <= 0) {
|
|
||||||
// 1234e-6 -> 0.001234
|
|
||||||
const int offset = 2 - kk;
|
|
||||||
std::memmove(&buffer[offset], &buffer[0], static_cast<size_t>(length));
|
|
||||||
buffer[0] = '0';
|
|
||||||
buffer[1] = '.';
|
|
||||||
for (int i = 2; i < offset; i++)
|
|
||||||
buffer[i] = '0';
|
|
||||||
if (length - kk > maxDecimalPlaces) {
|
|
||||||
// When maxDecimalPlaces = 2, 0.123 -> 0.12, 0.102 -> 0.1
|
|
||||||
// Remove extra trailing zeros (at least one) after truncation.
|
|
||||||
for (int i = maxDecimalPlaces + 1; i > 2; i--)
|
|
||||||
if (buffer[i] != '0')
|
|
||||||
return &buffer[i + 1];
|
|
||||||
return &buffer[3]; // Reserve one zero
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return &buffer[length + offset];
|
|
||||||
}
|
|
||||||
else if (kk < -maxDecimalPlaces) {
|
|
||||||
// Truncate to zero
|
|
||||||
buffer[0] = '0';
|
|
||||||
buffer[1] = '.';
|
|
||||||
buffer[2] = '0';
|
|
||||||
return &buffer[3];
|
|
||||||
}
|
|
||||||
else if (length == 1) {
|
|
||||||
// 1e30
|
|
||||||
buffer[1] = 'e';
|
|
||||||
return WriteExponent(kk - 1, &buffer[2]);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// 1234e30 -> 1.234e33
|
|
||||||
std::memmove(&buffer[2], &buffer[1], static_cast<size_t>(length - 1));
|
|
||||||
buffer[1] = '.';
|
|
||||||
buffer[length + 1] = 'e';
|
|
||||||
return WriteExponent(kk - 1, &buffer[0 + length + 2]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline char* dtoa(double value, char* buffer, int maxDecimalPlaces = 324) {
|
|
||||||
RAPIDJSON_ASSERT(maxDecimalPlaces >= 1);
|
|
||||||
Double d(value);
|
|
||||||
if (d.IsZero()) {
|
|
||||||
if (d.Sign())
|
|
||||||
*buffer++ = '-'; // -0.0, Issue #289
|
|
||||||
buffer[0] = '0';
|
|
||||||
buffer[1] = '.';
|
|
||||||
buffer[2] = '0';
|
|
||||||
return &buffer[3];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (value < 0) {
|
|
||||||
*buffer++ = '-';
|
|
||||||
value = -value;
|
|
||||||
}
|
|
||||||
int length, K;
|
|
||||||
Grisu2(value, buffer, &length, &K);
|
|
||||||
return Prettify(buffer, length, K, maxDecimalPlaces);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef __GNUC__
|
|
||||||
RAPIDJSON_DIAG_POP
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace internal
|
|
||||||
RAPIDJSON_NAMESPACE_END
|
|
||||||
|
|
||||||
#endif // RAPIDJSON_DTOA_
|
|
||||||
78
vendor/github.com/Benau/go_rlottie/lottie_rapidjson_internal_ieee754.h
generated
vendored
@@ -1,78 +0,0 @@
|
|||||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
|
||||||
//
|
|
||||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
|
||||||
// in compliance with the License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://opensource.org/licenses/MIT
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software distributed
|
|
||||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations under the License.
|
|
||||||
|
|
||||||
#ifndef RAPIDJSON_IEEE754_
|
|
||||||
#define RAPIDJSON_IEEE754_
|
|
||||||
|
|
||||||
#include "lottie_rapidjson_rapidjson.h"
|
|
||||||
|
|
||||||
RAPIDJSON_NAMESPACE_BEGIN
|
|
||||||
namespace internal {
|
|
||||||
|
|
||||||
class Double {
|
|
||||||
public:
|
|
||||||
Double() {}
|
|
||||||
Double(double d) : d_(d) {}
|
|
||||||
Double(uint64_t u) : u_(u) {}
|
|
||||||
|
|
||||||
double Value() const { return d_; }
|
|
||||||
uint64_t Uint64Value() const { return u_; }
|
|
||||||
|
|
||||||
double NextPositiveDouble() const {
|
|
||||||
RAPIDJSON_ASSERT(!Sign());
|
|
||||||
return Double(u_ + 1).Value();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Sign() const { return (u_ & kSignMask) != 0; }
|
|
||||||
uint64_t Significand() const { return u_ & kSignificandMask; }
|
|
||||||
int Exponent() const { return static_cast<int>(((u_ & kExponentMask) >> kSignificandSize) - kExponentBias); }
|
|
||||||
|
|
||||||
bool IsNan() const { return (u_ & kExponentMask) == kExponentMask && Significand() != 0; }
|
|
||||||
bool IsInf() const { return (u_ & kExponentMask) == kExponentMask && Significand() == 0; }
|
|
||||||
bool IsNanOrInf() const { return (u_ & kExponentMask) == kExponentMask; }
|
|
||||||
bool IsNormal() const { return (u_ & kExponentMask) != 0 || Significand() == 0; }
|
|
||||||
bool IsZero() const { return (u_ & (kExponentMask | kSignificandMask)) == 0; }
|
|
||||||
|
|
||||||
uint64_t IntegerSignificand() const { return IsNormal() ? Significand() | kHiddenBit : Significand(); }
|
|
||||||
int IntegerExponent() const { return (IsNormal() ? Exponent() : kDenormalExponent) - kSignificandSize; }
|
|
||||||
uint64_t ToBias() const { return (u_ & kSignMask) ? ~u_ + 1 : u_ | kSignMask; }
|
|
||||||
|
|
||||||
static int EffectiveSignificandSize(int order) {
|
|
||||||
if (order >= -1021)
|
|
||||||
return 53;
|
|
||||||
else if (order <= -1074)
|
|
||||||
return 0;
|
|
||||||
else
|
|
||||||
return order + 1074;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
static const int kSignificandSize = 52;
|
|
||||||
static const int kExponentBias = 0x3FF;
|
|
||||||
static const int kDenormalExponent = 1 - kExponentBias;
|
|
||||||
static const uint64_t kSignMask = RAPIDJSON_UINT64_C2(0x80000000, 0x00000000);
|
|
||||||
static const uint64_t kExponentMask = RAPIDJSON_UINT64_C2(0x7FF00000, 0x00000000);
|
|
||||||
static const uint64_t kSignificandMask = RAPIDJSON_UINT64_C2(0x000FFFFF, 0xFFFFFFFF);
|
|
||||||
static const uint64_t kHiddenBit = RAPIDJSON_UINT64_C2(0x00100000, 0x00000000);
|
|
||||||
|
|
||||||
union {
|
|
||||||
double d_;
|
|
||||||
uint64_t u_;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace internal
|
|
||||||
RAPIDJSON_NAMESPACE_END
|
|
||||||
|
|
||||||
#endif // RAPIDJSON_IEEE754_
|
|
||||||
308
vendor/github.com/Benau/go_rlottie/lottie_rapidjson_internal_itoa.h
generated
vendored
@@ -1,308 +0,0 @@
|
|||||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
|
||||||
//
|
|
||||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
|
||||||
// in compliance with the License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://opensource.org/licenses/MIT
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software distributed
|
|
||||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations under the License.
|
|
||||||
|
|
||||||
#ifndef RAPIDJSON_ITOA_
|
|
||||||
#define RAPIDJSON_ITOA_
|
|
||||||
|
|
||||||
#include "lottie_rapidjson_rapidjson.h"
|
|
||||||
|
|
||||||
RAPIDJSON_NAMESPACE_BEGIN
|
|
||||||
namespace internal {
|
|
||||||
|
|
||||||
inline const char* GetDigitsLut() {
|
|
||||||
static const char cDigitsLut[200] = {
|
|
||||||
'0','0','0','1','0','2','0','3','0','4','0','5','0','6','0','7','0','8','0','9',
|
|
||||||
'1','0','1','1','1','2','1','3','1','4','1','5','1','6','1','7','1','8','1','9',
|
|
||||||
'2','0','2','1','2','2','2','3','2','4','2','5','2','6','2','7','2','8','2','9',
|
|
||||||
'3','0','3','1','3','2','3','3','3','4','3','5','3','6','3','7','3','8','3','9',
|
|
||||||
'4','0','4','1','4','2','4','3','4','4','4','5','4','6','4','7','4','8','4','9',
|
|
||||||
'5','0','5','1','5','2','5','3','5','4','5','5','5','6','5','7','5','8','5','9',
|
|
||||||
'6','0','6','1','6','2','6','3','6','4','6','5','6','6','6','7','6','8','6','9',
|
|
||||||
'7','0','7','1','7','2','7','3','7','4','7','5','7','6','7','7','7','8','7','9',
|
|
||||||
'8','0','8','1','8','2','8','3','8','4','8','5','8','6','8','7','8','8','8','9',
|
|
||||||
'9','0','9','1','9','2','9','3','9','4','9','5','9','6','9','7','9','8','9','9'
|
|
||||||
};
|
|
||||||
return cDigitsLut;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline char* u32toa(uint32_t value, char* buffer) {
|
|
||||||
RAPIDJSON_ASSERT(buffer != 0);
|
|
||||||
|
|
||||||
const char* cDigitsLut = GetDigitsLut();
|
|
||||||
|
|
||||||
if (value < 10000) {
|
|
||||||
const uint32_t d1 = (value / 100) << 1;
|
|
||||||
const uint32_t d2 = (value % 100) << 1;
|
|
||||||
|
|
||||||
if (value >= 1000)
|
|
||||||
*buffer++ = cDigitsLut[d1];
|
|
||||||
if (value >= 100)
|
|
||||||
*buffer++ = cDigitsLut[d1 + 1];
|
|
||||||
if (value >= 10)
|
|
||||||
*buffer++ = cDigitsLut[d2];
|
|
||||||
*buffer++ = cDigitsLut[d2 + 1];
|
|
||||||
}
|
|
||||||
else if (value < 100000000) {
|
|
||||||
// value = bbbbcccc
|
|
||||||
const uint32_t b = value / 10000;
|
|
||||||
const uint32_t c = value % 10000;
|
|
||||||
|
|
||||||
const uint32_t d1 = (b / 100) << 1;
|
|
||||||
const uint32_t d2 = (b % 100) << 1;
|
|
||||||
|
|
||||||
const uint32_t d3 = (c / 100) << 1;
|
|
||||||
const uint32_t d4 = (c % 100) << 1;
|
|
||||||
|
|
||||||
if (value >= 10000000)
|
|
||||||
*buffer++ = cDigitsLut[d1];
|
|
||||||
if (value >= 1000000)
|
|
||||||
*buffer++ = cDigitsLut[d1 + 1];
|
|
||||||
if (value >= 100000)
|
|
||||||
*buffer++ = cDigitsLut[d2];
|
|
||||||
*buffer++ = cDigitsLut[d2 + 1];
|
|
||||||
|
|
||||||
*buffer++ = cDigitsLut[d3];
|
|
||||||
*buffer++ = cDigitsLut[d3 + 1];
|
|
||||||
*buffer++ = cDigitsLut[d4];
|
|
||||||
*buffer++ = cDigitsLut[d4 + 1];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// value = aabbbbcccc in decimal
|
|
||||||
|
|
||||||
const uint32_t a = value / 100000000; // 1 to 42
|
|
||||||
value %= 100000000;
|
|
||||||
|
|
||||||
if (a >= 10) {
|
|
||||||
const unsigned i = a << 1;
|
|
||||||
*buffer++ = cDigitsLut[i];
|
|
||||||
*buffer++ = cDigitsLut[i + 1];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
*buffer++ = static_cast<char>('0' + static_cast<char>(a));
|
|
||||||
|
|
||||||
const uint32_t b = value / 10000; // 0 to 9999
|
|
||||||
const uint32_t c = value % 10000; // 0 to 9999
|
|
||||||
|
|
||||||
const uint32_t d1 = (b / 100) << 1;
|
|
||||||
const uint32_t d2 = (b % 100) << 1;
|
|
||||||
|
|
||||||
const uint32_t d3 = (c / 100) << 1;
|
|
||||||
const uint32_t d4 = (c % 100) << 1;
|
|
||||||
|
|
||||||
*buffer++ = cDigitsLut[d1];
|
|
||||||
*buffer++ = cDigitsLut[d1 + 1];
|
|
||||||
*buffer++ = cDigitsLut[d2];
|
|
||||||
*buffer++ = cDigitsLut[d2 + 1];
|
|
||||||
*buffer++ = cDigitsLut[d3];
|
|
||||||
*buffer++ = cDigitsLut[d3 + 1];
|
|
||||||
*buffer++ = cDigitsLut[d4];
|
|
||||||
*buffer++ = cDigitsLut[d4 + 1];
|
|
||||||
}
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline char* i32toa(int32_t value, char* buffer) {
|
|
||||||
RAPIDJSON_ASSERT(buffer != 0);
|
|
||||||
uint32_t u = static_cast<uint32_t>(value);
|
|
||||||
if (value < 0) {
|
|
||||||
*buffer++ = '-';
|
|
||||||
u = ~u + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return u32toa(u, buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline char* u64toa(uint64_t value, char* buffer) {
|
|
||||||
RAPIDJSON_ASSERT(buffer != 0);
|
|
||||||
const char* cDigitsLut = GetDigitsLut();
|
|
||||||
const uint64_t kTen8 = 100000000;
|
|
||||||
const uint64_t kTen9 = kTen8 * 10;
|
|
||||||
const uint64_t kTen10 = kTen8 * 100;
|
|
||||||
const uint64_t kTen11 = kTen8 * 1000;
|
|
||||||
const uint64_t kTen12 = kTen8 * 10000;
|
|
||||||
const uint64_t kTen13 = kTen8 * 100000;
|
|
||||||
const uint64_t kTen14 = kTen8 * 1000000;
|
|
||||||
const uint64_t kTen15 = kTen8 * 10000000;
|
|
||||||
const uint64_t kTen16 = kTen8 * kTen8;
|
|
||||||
|
|
||||||
if (value < kTen8) {
|
|
||||||
uint32_t v = static_cast<uint32_t>(value);
|
|
||||||
if (v < 10000) {
|
|
||||||
const uint32_t d1 = (v / 100) << 1;
|
|
||||||
const uint32_t d2 = (v % 100) << 1;
|
|
||||||
|
|
||||||
if (v >= 1000)
|
|
||||||
*buffer++ = cDigitsLut[d1];
|
|
||||||
if (v >= 100)
|
|
||||||
*buffer++ = cDigitsLut[d1 + 1];
|
|
||||||
if (v >= 10)
|
|
||||||
*buffer++ = cDigitsLut[d2];
|
|
||||||
*buffer++ = cDigitsLut[d2 + 1];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// value = bbbbcccc
|
|
||||||
const uint32_t b = v / 10000;
|
|
||||||
const uint32_t c = v % 10000;
|
|
||||||
|
|
||||||
const uint32_t d1 = (b / 100) << 1;
|
|
||||||
const uint32_t d2 = (b % 100) << 1;
|
|
||||||
|
|
||||||
const uint32_t d3 = (c / 100) << 1;
|
|
||||||
const uint32_t d4 = (c % 100) << 1;
|
|
||||||
|
|
||||||
if (value >= 10000000)
|
|
||||||
*buffer++ = cDigitsLut[d1];
|
|
||||||
if (value >= 1000000)
|
|
||||||
*buffer++ = cDigitsLut[d1 + 1];
|
|
||||||
if (value >= 100000)
|
|
||||||
*buffer++ = cDigitsLut[d2];
|
|
||||||
*buffer++ = cDigitsLut[d2 + 1];
|
|
||||||
|
|
||||||
*buffer++ = cDigitsLut[d3];
|
|
||||||
*buffer++ = cDigitsLut[d3 + 1];
|
|
||||||
*buffer++ = cDigitsLut[d4];
|
|
||||||
*buffer++ = cDigitsLut[d4 + 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (value < kTen16) {
|
|
||||||
const uint32_t v0 = static_cast<uint32_t>(value / kTen8);
|
|
||||||
const uint32_t v1 = static_cast<uint32_t>(value % kTen8);
|
|
||||||
|
|
||||||
const uint32_t b0 = v0 / 10000;
|
|
||||||
const uint32_t c0 = v0 % 10000;
|
|
||||||
|
|
||||||
const uint32_t d1 = (b0 / 100) << 1;
|
|
||||||
const uint32_t d2 = (b0 % 100) << 1;
|
|
||||||
|
|
||||||
const uint32_t d3 = (c0 / 100) << 1;
|
|
||||||
const uint32_t d4 = (c0 % 100) << 1;
|
|
||||||
|
|
||||||
const uint32_t b1 = v1 / 10000;
|
|
||||||
const uint32_t c1 = v1 % 10000;
|
|
||||||
|
|
||||||
const uint32_t d5 = (b1 / 100) << 1;
|
|
||||||
const uint32_t d6 = (b1 % 100) << 1;
|
|
||||||
|
|
||||||
const uint32_t d7 = (c1 / 100) << 1;
|
|
||||||
const uint32_t d8 = (c1 % 100) << 1;
|
|
||||||
|
|
||||||
if (value >= kTen15)
|
|
||||||
*buffer++ = cDigitsLut[d1];
|
|
||||||
if (value >= kTen14)
|
|
||||||
*buffer++ = cDigitsLut[d1 + 1];
|
|
||||||
if (value >= kTen13)
|
|
||||||
*buffer++ = cDigitsLut[d2];
|
|
||||||
if (value >= kTen12)
|
|
||||||
*buffer++ = cDigitsLut[d2 + 1];
|
|
||||||
if (value >= kTen11)
|
|
||||||
*buffer++ = cDigitsLut[d3];
|
|
||||||
if (value >= kTen10)
|
|
||||||
*buffer++ = cDigitsLut[d3 + 1];
|
|
||||||
if (value >= kTen9)
|
|
||||||
*buffer++ = cDigitsLut[d4];
|
|
||||||
|
|
||||||
*buffer++ = cDigitsLut[d4 + 1];
|
|
||||||
*buffer++ = cDigitsLut[d5];
|
|
||||||
*buffer++ = cDigitsLut[d5 + 1];
|
|
||||||
*buffer++ = cDigitsLut[d6];
|
|
||||||
*buffer++ = cDigitsLut[d6 + 1];
|
|
||||||
*buffer++ = cDigitsLut[d7];
|
|
||||||
*buffer++ = cDigitsLut[d7 + 1];
|
|
||||||
*buffer++ = cDigitsLut[d8];
|
|
||||||
*buffer++ = cDigitsLut[d8 + 1];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const uint32_t a = static_cast<uint32_t>(value / kTen16); // 1 to 1844
|
|
||||||
value %= kTen16;
|
|
||||||
|
|
||||||
if (a < 10)
|
|
||||||
*buffer++ = static_cast<char>('0' + static_cast<char>(a));
|
|
||||||
else if (a < 100) {
|
|
||||||
const uint32_t i = a << 1;
|
|
||||||
*buffer++ = cDigitsLut[i];
|
|
||||||
*buffer++ = cDigitsLut[i + 1];
|
|
||||||
}
|
|
||||||
else if (a < 1000) {
|
|
||||||
*buffer++ = static_cast<char>('0' + static_cast<char>(a / 100));
|
|
||||||
|
|
||||||
const uint32_t i = (a % 100) << 1;
|
|
||||||
*buffer++ = cDigitsLut[i];
|
|
||||||
*buffer++ = cDigitsLut[i + 1];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const uint32_t i = (a / 100) << 1;
|
|
||||||
const uint32_t j = (a % 100) << 1;
|
|
||||||
*buffer++ = cDigitsLut[i];
|
|
||||||
*buffer++ = cDigitsLut[i + 1];
|
|
||||||
*buffer++ = cDigitsLut[j];
|
|
||||||
*buffer++ = cDigitsLut[j + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint32_t v0 = static_cast<uint32_t>(value / kTen8);
|
|
||||||
const uint32_t v1 = static_cast<uint32_t>(value % kTen8);
|
|
||||||
|
|
||||||
const uint32_t b0 = v0 / 10000;
|
|
||||||
const uint32_t c0 = v0 % 10000;
|
|
||||||
|
|
||||||
const uint32_t d1 = (b0 / 100) << 1;
|
|
||||||
const uint32_t d2 = (b0 % 100) << 1;
|
|
||||||
|
|
||||||
const uint32_t d3 = (c0 / 100) << 1;
|
|
||||||
const uint32_t d4 = (c0 % 100) << 1;
|
|
||||||
|
|
||||||
const uint32_t b1 = v1 / 10000;
|
|
||||||
const uint32_t c1 = v1 % 10000;
|
|
||||||
|
|
||||||
const uint32_t d5 = (b1 / 100) << 1;
|
|
||||||
const uint32_t d6 = (b1 % 100) << 1;
|
|
||||||
|
|
||||||
const uint32_t d7 = (c1 / 100) << 1;
|
|
||||||
const uint32_t d8 = (c1 % 100) << 1;
|
|
||||||
|
|
||||||
*buffer++ = cDigitsLut[d1];
|
|
||||||
*buffer++ = cDigitsLut[d1 + 1];
|
|
||||||
*buffer++ = cDigitsLut[d2];
|
|
||||||
*buffer++ = cDigitsLut[d2 + 1];
|
|
||||||
*buffer++ = cDigitsLut[d3];
|
|
||||||
*buffer++ = cDigitsLut[d3 + 1];
|
|
||||||
*buffer++ = cDigitsLut[d4];
|
|
||||||
*buffer++ = cDigitsLut[d4 + 1];
|
|
||||||
*buffer++ = cDigitsLut[d5];
|
|
||||||
*buffer++ = cDigitsLut[d5 + 1];
|
|
||||||
*buffer++ = cDigitsLut[d6];
|
|
||||||
*buffer++ = cDigitsLut[d6 + 1];
|
|
||||||
*buffer++ = cDigitsLut[d7];
|
|
||||||
*buffer++ = cDigitsLut[d7 + 1];
|
|
||||||
*buffer++ = cDigitsLut[d8];
|
|
||||||
*buffer++ = cDigitsLut[d8 + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline char* i64toa(int64_t value, char* buffer) {
|
|
||||||
RAPIDJSON_ASSERT(buffer != 0);
|
|
||||||
uint64_t u = static_cast<uint64_t>(value);
|
|
||||||
if (value < 0) {
|
|
||||||
*buffer++ = '-';
|
|
||||||
u = ~u + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return u64toa(u, buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace internal
|
|
||||||
RAPIDJSON_NAMESPACE_END
|
|
||||||
|
|
||||||
#endif // RAPIDJSON_ITOA_
|
|
||||||