Compare commits
176 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
051e6e76e9 | ||
|
|
1e55dd47f2 | ||
|
|
700b95546b | ||
|
|
2fa96ec0ed | ||
|
|
81e6f75aa4 | ||
|
|
888c8b9a84 | ||
|
|
e775a8a22e | ||
|
|
99fbd9cae6 | ||
|
|
67adad3e08 | ||
|
|
2fca3c7563 | ||
|
|
c3573f1a46 | ||
|
|
ee932a9f8e | ||
|
|
ce18c948e6 | ||
|
|
7bc93c5506 | ||
|
|
d7cad3b404 | ||
|
|
7740a362c9 | ||
|
|
281ef53e7d | ||
|
|
f044b948e2 | ||
|
|
32474a5f4d | ||
|
|
26596acf80 | ||
|
|
e63870a631 | ||
|
|
ce782ff6fb | ||
|
|
c6716e030c | ||
|
|
4ab72acec6 | ||
|
|
30aae8e257 | ||
|
|
d7b7ff7bb4 | ||
|
|
6fe0cff342 | ||
|
|
5f75f9886d | ||
|
|
5d9604cd15 | ||
|
|
cc36ebf1c9 | ||
|
|
e6adecfd81 | ||
|
|
5c8f224e3b | ||
|
|
952221d3b9 | ||
|
|
496d5b4ec7 | ||
|
|
2623a412c4 | ||
|
|
d64eed49bc | ||
|
|
fffa29c2f3 | ||
|
|
4da1444ffc | ||
|
|
21c4e56d16 | ||
|
|
5356b3856a | ||
|
|
320c996a21 | ||
|
|
69c74be7bb | ||
|
|
aefa70891c | ||
|
|
1b9877fda4 | ||
|
|
0205a67309 | ||
|
|
e3cafeaf92 | ||
|
|
e7b193788a | ||
|
|
17da95b094 | ||
|
|
c5e49eec96 | ||
|
|
24bc0f127b | ||
|
|
f0f801402d | ||
|
|
663850a2b8 | ||
|
|
c51753cab1 | ||
|
|
b3be2e208c | ||
|
|
c30e90ff3f | ||
|
|
e4c0ca0f48 | ||
|
|
9c203327c0 | ||
|
|
ccb5b1d075 | ||
|
|
0dbbd0414c | ||
|
|
e7b3ebf98a | ||
|
|
5bc18fb780 | ||
|
|
df30366072 | ||
|
|
65c7ac80b5 | ||
|
|
dd3fb32ec7 | ||
|
|
2a3f475ff5 | ||
|
|
7288f71201 | ||
|
|
9c43eff753 | ||
|
|
c8d7fdeedc | ||
|
|
c211152e23 | ||
|
|
ab75d5097e | ||
|
|
c3644c8d3b | ||
|
|
6438a3dba3 | ||
|
|
4b226a6a63 | ||
|
|
4801850013 | ||
|
|
6a7412bf2b | ||
|
|
5a1fd7dadd | ||
|
|
ac06a26809 | ||
|
|
61d56f26f8 | ||
|
|
6aa05b3981 | ||
|
|
aad60c882e | ||
|
|
fecca57507 | ||
|
|
2bcad846c0 | ||
|
|
15ad0165fc | ||
|
|
2e8ab11978 | ||
|
|
9a8ce9b17e | ||
|
|
16ab4c6fed | ||
|
|
e3ee0df7ba | ||
|
|
8f7ab280e2 | ||
|
|
dbedc99421 | ||
|
|
6cb359cb80 | ||
|
|
ae2ad824a9 | ||
|
|
02e3d7852b | ||
|
|
3893a035be | ||
|
|
658bdd9faa | ||
|
|
e1eebcd4e0 | ||
|
|
062b831e88 | ||
|
|
b275efaeff | ||
|
|
80d3033456 | ||
|
|
bd0516f09a | ||
|
|
df4d76e466 | ||
|
|
dcbd7f8cad | ||
|
|
73ec02ab9d | ||
|
|
d1f8347071 | ||
|
|
8601eedada | ||
|
|
9afd33cdfc | ||
|
|
5e1be8e558 | ||
|
|
835dd2635a | ||
|
|
f65b18c2f6 | ||
|
|
b0e7b84f40 | ||
|
|
1635db93c7 | ||
|
|
c4fe462d11 | ||
|
|
b1f403165d | ||
|
|
46e4317b77 | ||
|
|
e3ffbcadd8 | ||
|
|
b7d73077e5 | ||
|
|
77f61ee20a | ||
|
|
8967f02fc9 | ||
|
|
831ff6d0a9 | ||
|
|
2199174def | ||
|
|
55f41ddaab | ||
|
|
21305d93bf | ||
|
|
4478d5d904 | ||
|
|
cc6253a6b8 | ||
|
|
85f66853bc | ||
|
|
7464fd149c | ||
|
|
86f1a8019c | ||
|
|
b98d56dcf6 | ||
|
|
a3a8a5769d | ||
|
|
4dd8bae5c9 | ||
|
|
7ae45c42e7 | ||
|
|
7551b4e7a3 | ||
|
|
61bab22dde | ||
|
|
6dcc23ebb6 | ||
|
|
b06a574cc5 | ||
|
|
b56f80b1b8 | ||
|
|
20f6c05ec5 | ||
|
|
57fce93af7 | ||
|
|
110b6a1431 | ||
|
|
53cafa9f3d | ||
|
|
d4195deb3a | ||
|
|
400ecfb79c | ||
|
|
86151da271 | ||
|
|
44f3e2557d | ||
|
|
1f365c716e | ||
|
|
9efcc41ab2 | ||
|
|
13bbeeaceb | ||
|
|
da4dcec14d | ||
|
|
761c0b79c5 | ||
|
|
d93ab0496f | ||
|
|
66b6f9749d | ||
|
|
17c2d1f26a | ||
|
|
a79e632cdc | ||
|
|
f36498421b | ||
|
|
e45bbe4571 | ||
|
|
fb5a84212c | ||
|
|
dedc1c45a1 | ||
|
|
6a12f9ff84 | ||
|
|
641ed1873b | ||
|
|
1d50da4b1c | ||
|
|
c7897cca5d | ||
|
|
4091b6f6b4 | ||
|
|
766f35554e | ||
|
|
c86137449e | ||
|
|
efec01a92f | ||
|
|
4fcad8e04b | ||
|
|
4b4b2d790e | ||
|
|
ec6ae343dd | ||
|
|
b9fb361959 | ||
|
|
a189298ab0 | ||
|
|
714a2ad730 | ||
|
|
fa8b96dfa1 | ||
|
|
01955a0df8 | ||
|
|
ac4aee39e3 | ||
|
|
a0bca42a7a | ||
|
|
af543dcd05 | ||
|
|
af77109a47 |
16
.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: v1.29
|
version: latest
|
||||||
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.15.x, 1.16.x]
|
go-version: [1.17.x]
|
||||||
platform: [ubuntu-latest]
|
platform: [ubuntu-latest]
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
steps:
|
steps:
|
||||||
@@ -35,23 +35,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)
|
||||||
GOOS=linux GOARCH=amd64 go build -ldflags "-s -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o output/lin/matterbridge-$VERSION-linux-amd64
|
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=windows GOARCH=amd64 go build -ldflags "-s -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o output/win/matterbridge-$VERSION-windows-amd64.exe
|
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=darwin GOARCH=amd64 go build -ldflags "-s -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o output/mac/matterbridge-$VERSION-darwin-amd64
|
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
|
||||||
- name: Upload linux 64-bit
|
- name: Upload linux 64-bit
|
||||||
if: startsWith(matrix.go-version,'1.16')
|
if: startsWith(matrix.go-version,'1.17')
|
||||||
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.16')
|
if: startsWith(matrix.go-version,'1.17')
|
||||||
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.16')
|
if: startsWith(matrix.go-version,'1.17')
|
||||||
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
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
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,3 +4,6 @@
|
|||||||
|
|
||||||
# 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: 2m
|
deadline: 5m
|
||||||
|
|
||||||
# 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
|
||||||
@@ -182,7 +182,28 @@ 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,8 +18,11 @@ builds:
|
|||||||
- arm
|
- arm
|
||||||
- arm64
|
- arm64
|
||||||
- 386
|
- 386
|
||||||
|
goarm:
|
||||||
|
- 6
|
||||||
|
- 7
|
||||||
ldflags:
|
ldflags:
|
||||||
- -s -w -X main.githash={{.ShortCommit}}
|
- -s -w -X github.com/42wim/matterbridge/version.GitHash={{.ShortCommit}}
|
||||||
|
|
||||||
archives:
|
archives:
|
||||||
-
|
-
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ FROM alpine AS builder
|
|||||||
COPY . /go/src/matterbridge
|
COPY . /go/src/matterbridge
|
||||||
RUN apk --no-cache add go git \
|
RUN apk --no-cache add go git \
|
||||||
&& cd /go/src/matterbridge \
|
&& cd /go/src/matterbridge \
|
||||||
&& go build -mod vendor -ldflags "-X main.githash=$(git log --pretty=format:'%h' -n 1)" -o /bin/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
|
||||||
|
|
||||||
FROM alpine
|
FROM alpine
|
||||||
RUN apk --no-cache add ca-certificates mailcap
|
RUN apk --no-cache add ca-certificates mailcap
|
||||||
|
|||||||
14
Dockerfile_whatsappmulti
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
FROM alpine AS builder
|
||||||
|
|
||||||
|
COPY . /go/src/matterbridge
|
||||||
|
RUN apk --no-cache add go git \
|
||||||
|
&& cd /go/src/matterbridge \
|
||||||
|
&& CGO_ENABLED=0 go build -tags whatsappmulti -mod vendor -ldflags "-X github.com/42wim/matterbridge/version.GitHash=$(git log --pretty=format:'%h' -n 1)" -o /bin/matterbridge
|
||||||
|
|
||||||
|
FROM alpine
|
||||||
|
RUN apk --no-cache add ca-certificates mailcap
|
||||||
|
COPY --from=builder /bin/matterbridge /bin/matterbridge
|
||||||
|
RUN mkdir /etc/matterbridge \
|
||||||
|
&& touch /etc/matterbridge/matterbridge.toml \
|
||||||
|
&& ln -sf /matterbridge.toml /etc/matterbridge/matterbridge.toml
|
||||||
|
ENTRYPOINT ["/bin/matterbridge", "-conf", "/etc/matterbridge/matterbridge.toml"]
|
||||||
100
README.md
@@ -58,20 +58,22 @@ And more...
|
|||||||
- [Binaries](#binaries)
|
- [Binaries](#binaries)
|
||||||
- [Packages](#packages)
|
- [Packages](#packages)
|
||||||
- [Building](#building)
|
- [Building](#building)
|
||||||
|
- [Building with whatsapp (beta) multidevice support](#building-with-whatsapp-beta-multidevice-support)
|
||||||
- [Configuration](#configuration)
|
- [Configuration](#configuration)
|
||||||
- [Basic configuration](#basic-configuration)
|
- [Basic configuration](#basic-configuration)
|
||||||
- [Settings](#settings)
|
- [Settings](#settings)
|
||||||
- [Advanced configuration](#advanced-configuration)
|
- [Advanced configuration](#advanced-configuration)
|
||||||
- [Examples](#examples)
|
- [Examples](#examples)
|
||||||
- [Bridge mattermost (off-topic) - irc (#testing)](#bridge-mattermost-off-topic---irc-testing)
|
- [Bridge mattermost (off-topic) - irc (#testing)](#bridge-mattermost-off-topic---irc-testing)
|
||||||
- [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)
|
||||||
- [Changelog](#changelog)
|
- [Systemd](#systemd)
|
||||||
- [FAQ](#faq)
|
- [Changelog](#changelog)
|
||||||
- [Related projects](#related-projects)
|
- [FAQ](#faq)
|
||||||
- [Articles / Tutorials](#articles--tutorials)
|
- [Related projects](#related-projects)
|
||||||
- [Thanks](#thanks)
|
- [Articles / Tutorials](#articles--tutorials)
|
||||||
|
- [Thanks](#thanks)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@@ -88,6 +90,7 @@ And more...
|
|||||||
|
|
||||||
- [Discord](https://discordapp.com)
|
- [Discord](https://discordapp.com)
|
||||||
- [Gitter](https://gitter.im)
|
- [Gitter](https://gitter.im)
|
||||||
|
- [Harmony](https://harmonyapp.io)
|
||||||
- [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)
|
||||||
@@ -104,6 +107,8 @@ And more...
|
|||||||
- [Twitch](https://twitch.tv)
|
- [Twitch](https://twitch.tv)
|
||||||
- [VK](https://vk.com/)
|
- [VK](https://vk.com/)
|
||||||
- [WhatsApp](https://www.whatsapp.com/)
|
- [WhatsApp](https://www.whatsapp.com/)
|
||||||
|
- Whatsapp legacy is natively supported
|
||||||
|
- Whatsapp multidevice beta is natively supported but you need to build yourself, see [here](#building-with-whatsapp-beta-multidevice-support)
|
||||||
- [XMPP](https://xmpp.org)
|
- [XMPP](https://xmpp.org)
|
||||||
- [Zulip](https://zulipchat.com)
|
- [Zulip](https://zulipchat.com)
|
||||||
|
|
||||||
@@ -119,6 +124,8 @@ And more...
|
|||||||
- [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)
|
- [Vintage Story](https://github.com/NikkyAI/vs-matterbridge)
|
||||||
|
- [Ultima Online Emulator](https://github.com/kuoushi/ServUO-Matterbridge)
|
||||||
|
- [Teamspeak](https://github.com/Archeb/ts-matterbridge)
|
||||||
|
|
||||||
### API
|
### API
|
||||||
|
|
||||||
@@ -137,6 +144,8 @@ Used by the projects below. Feel free to make a PR to add your project to this l
|
|||||||
- [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)
|
- [Vintage Story](https://github.com/NikkyAI/vs-matterbridge)
|
||||||
|
- [ServUO-matterbridge](https://github.com/kuoushi/ServUO-Matterbridge) (A matterbridge connector for ServUO servers)
|
||||||
|
- [ts-matterbridge](https://github.com/Archeb/ts-matterbridge) (Integrate teamspeak chat with matterbridge)
|
||||||
|
|
||||||
## Chat with us
|
## Chat with us
|
||||||
|
|
||||||
@@ -163,10 +172,10 @@ See <https://github.com/42wim/matterbridge/wiki>
|
|||||||
|
|
||||||
### Binaries
|
### Binaries
|
||||||
|
|
||||||
- Latest stable release [v1.22.1](https://github.com/42wim/matterbridge/releases/latest)
|
- Latest stable release [v1.25.1](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). 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.24.1-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.
|
||||||
|
|
||||||
### Packages
|
### Packages
|
||||||
|
|
||||||
@@ -179,10 +188,55 @@ To install or upgrade just download the latest [binary](https://github.com/42wim
|
|||||||
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.13+ is required. Make sure you have [Go](https://golang.org/doc/install) properly installed.
|
Go 1.17+ is required. Make sure you have [Go](https://golang.org/doc/install) properly installed.
|
||||||
|
|
||||||
|
Building the binary with **all** the bridges enabled needs about 3GB RAM to compile.
|
||||||
|
You can reduce this memory requirement to 0,5GB RAM by adding the `nomsteams` tag if you don't need/use the Microsoft Teams bridge.
|
||||||
|
|
||||||
|
Matterbridge can be build without gcc/c-compiler: If you're running on windows first run `set CGO_ENABLED=0` on other platforms you prepend `CGO_ENABLED=0` to the `go build` command. (eg `CGO_ENABLED=0 go install github.com/42wim/matterbridge`)
|
||||||
|
|
||||||
|
To install the latest stable run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go get github.com/42wim/matterbridge
|
go install github.com/42wim/matterbridge
|
||||||
|
```
|
||||||
|
|
||||||
|
To install the latest dev run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install github.com/42wim/matterbridge@master
|
||||||
|
```
|
||||||
|
|
||||||
|
To install the latest stable run without msteams or zulip bridge:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install -tags nomsteams,nozulip github.com/42wim/matterbridge
|
||||||
|
```
|
||||||
|
|
||||||
|
You should now have matterbridge binary in the ~/go/bin directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ls ~/go/bin/
|
||||||
|
matterbridge
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building with whatsapp (beta) multidevice support
|
||||||
|
|
||||||
|
Because the library we use for Whatsapp multidevice support includes a GPL3 library we can not provide you binaries.
|
||||||
|
(as this would require the Matterbridge to change it license to GPL)
|
||||||
|
|
||||||
|
Matterbridge can be build without gcc/c-compiler: If you're running on windows first run `set CGO_ENABLED=0` on other platforms you prepend `CGO_ENABLED=0` to the `go build` command. (eg `CGO_ENABLED=0 go install github.com/42wim/matterbridge`)
|
||||||
|
|
||||||
|
So this means you have to build it yourself using the instructions below:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install -tags whatsappmulti github.com/42wim/matterbridge@master
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're low on memory and don't need msteams:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install -tags nomsteams,whatsappmulti github.com/42wim/matterbridge@master
|
||||||
```
|
```
|
||||||
|
|
||||||
You should now have matterbridge binary in the ~/go/bin directory:
|
You should now have matterbridge binary in the ~/go/bin directory:
|
||||||
@@ -212,8 +266,8 @@ All possible [settings](https://github.com/42wim/matterbridge/wiki/Settings) for
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[irc]
|
[irc]
|
||||||
[irc.freenode]
|
[irc.libera]
|
||||||
Server="irc.freenode.net:6667"
|
Server="irc.libera.chat:6667"
|
||||||
Nick="yourbotname"
|
Nick="yourbotname"
|
||||||
|
|
||||||
[mattermost]
|
[mattermost]
|
||||||
@@ -229,7 +283,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.freenode"
|
account="irc.libera"
|
||||||
channel="#testing"
|
channel="#testing"
|
||||||
|
|
||||||
[[gateway.inout]]
|
[[gateway.inout]]
|
||||||
@@ -286,6 +340,10 @@ 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)
|
||||||
@@ -310,6 +368,8 @@ See [FAQ](https://github.com/42wim/matterbridge/wiki/FAQ)
|
|||||||
- [nextcloud talk](https://github.com/nextcloud/talk_matterbridge) (Integrates matterbridge in Nextcloud Talk)
|
- [nextcloud talk](https://github.com/nextcloud/talk_matterbridge) (Integrates matterbridge in Nextcloud Talk)
|
||||||
- [mattercraft](https://github.com/raws/mattercraft) (Minecraft bridge)
|
- [mattercraft](https://github.com/raws/mattercraft) (Minecraft bridge)
|
||||||
- [vs-matterbridge](https://github.com/NikkyAI/vs-matterbridge) (Vintage Story bridge)
|
- [vs-matterbridge](https://github.com/NikkyAI/vs-matterbridge) (Vintage Story bridge)
|
||||||
|
- [ServUO-matterbridge](https://github.com/kuoushi/ServUO-Matterbridge) (A matterbridge connector for ServUO servers)
|
||||||
|
- [ts-matterbridge](https://github.com/Archeb/ts-matterbridge) (Integrate teamspeak chat with matterbridge)
|
||||||
|
|
||||||
## Articles / Tutorials
|
## Articles / Tutorials
|
||||||
|
|
||||||
@@ -323,6 +383,7 @@ See [FAQ](https://github.com/42wim/matterbridge/wiki/FAQ)
|
|||||||
- <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://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)
|
- Youtube: [whatsapp - telegram bridging](https://www.youtube.com/watch?v=W-VXISoKtNc)
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
@@ -342,6 +403,7 @@ Matterbridge wouldn't exist without these libraries:
|
|||||||
- gops - <https://github.com/google/gops>
|
- gops - <https://github.com/google/gops>
|
||||||
- gozulipbot - <https://github.com/ifo/gozulipbot>
|
- gozulipbot - <https://github.com/ifo/gozulipbot>
|
||||||
- gumble - <https://github.com/layeh/gumble>
|
- gumble - <https://github.com/layeh/gumble>
|
||||||
|
- harmony - <https://github.com/harmony-development/shibshib>
|
||||||
- irc - <https://github.com/lrstanley/girc>
|
- irc - <https://github.com/lrstanley/girc>
|
||||||
- keybase - <https://github.com/keybase/go-keybase-chat-bot>
|
- keybase - <https://github.com/keybase/go-keybase-chat-bot>
|
||||||
- matrix - <https://github.com/matrix-org/gomatrix>
|
- matrix - <https://github.com/matrix-org/gomatrix>
|
||||||
@@ -349,6 +411,7 @@ Matterbridge wouldn't exist without these libraries:
|
|||||||
- msgraph.go - <https://github.com/yaegashi/msgraph.go>
|
- msgraph.go - <https://github.com/yaegashi/msgraph.go>
|
||||||
- mumble - <https://github.com/layeh/gumble>
|
- mumble - <https://github.com/layeh/gumble>
|
||||||
- nctalk - <https://github.com/gary-kim/go-nc-talk>
|
- nctalk - <https://github.com/gary-kim/go-nc-talk>
|
||||||
|
- rocketchat - <https://github.com/RocketChat/Rocket.Chat.Go.SDK>
|
||||||
- slack - <https://github.com/nlopes/slack>
|
- slack - <https://github.com/nlopes/slack>
|
||||||
- sshchat - <https://github.com/shazow/ssh-chat>
|
- sshchat - <https://github.com/shazow/ssh-chat>
|
||||||
- steam - <https://github.com/Philipp15b/go-steam>
|
- steam - <https://github.com/Philipp15b/go-steam>
|
||||||
@@ -356,6 +419,7 @@ Matterbridge wouldn't exist without these libraries:
|
|||||||
- tengo - <https://github.com/d5/tengo>
|
- tengo - <https://github.com/d5/tengo>
|
||||||
- vk - <https://github.com/SevereCloud/vksdk>
|
- vk - <https://github.com/SevereCloud/vksdk>
|
||||||
- whatsapp - <https://github.com/Rhymen/go-whatsapp>
|
- whatsapp - <https://github.com/Rhymen/go-whatsapp>
|
||||||
|
- whatsapp - <https://github.com/tulir/whatsmeow>
|
||||||
- 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>
|
||||||
|
|
||||||
@@ -363,7 +427,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://webchat.freenode.net/?channels=matterbridgechat
|
[mb-irc]: https://web.libera.chat/#matterbridge
|
||||||
[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,6 +23,7 @@ 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"
|
||||||
@@ -56,13 +57,14 @@ func (m Message) ParentValid() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@@ -85,27 +87,28 @@ type ChannelMember struct {
|
|||||||
type ChannelMembers []ChannelMember
|
type ChannelMembers []ChannelMember
|
||||||
|
|
||||||
type Protocol struct {
|
type Protocol struct {
|
||||||
AuthCode string // steam
|
AllowMention []string // discord
|
||||||
BindAddress string // mattermost, slack // DEPRECATED
|
AuthCode string // steam
|
||||||
Buffer int // api
|
BindAddress string // mattermost, slack // DEPRECATED
|
||||||
Charset string // irc
|
Buffer int // api
|
||||||
ClientID string // msteams
|
Charset string // irc
|
||||||
ColorNicks bool // only irc for now
|
ClientID string // msteams
|
||||||
Debug bool // general
|
ColorNicks bool // only irc for now
|
||||||
DebugLevel int // only for irc now
|
Debug bool // general
|
||||||
DisableWebPagePreview bool // telegram
|
DebugLevel int // only for irc now
|
||||||
EditSuffix string // mattermost, slack, discord, telegram, gitter
|
DisableWebPagePreview bool // telegram
|
||||||
EditDisable bool // mattermost, slack, discord, telegram, gitter
|
EditSuffix string // mattermost, slack, discord, telegram, gitter
|
||||||
HTMLDisable bool // matrix
|
EditDisable bool // mattermost, slack, discord, telegram, gitter
|
||||||
IconURL string // mattermost, slack
|
HTMLDisable bool // matrix
|
||||||
IgnoreFailureOnStart bool // general
|
IconURL string // mattermost, slack
|
||||||
IgnoreNicks string // all protocols
|
IgnoreFailureOnStart bool // general
|
||||||
IgnoreMessages string // all protocols
|
IgnoreNicks string // all protocols
|
||||||
Jid string // xmpp
|
IgnoreMessages string // all protocols
|
||||||
JoinDelay string // all protocols
|
Jid string // xmpp
|
||||||
Label string // all protocols
|
JoinDelay string // all protocols
|
||||||
Login string // mattermost, matrix
|
Label string // all protocols
|
||||||
LogFile string // general
|
Login string // mattermost, matrix
|
||||||
|
LogFile string // general
|
||||||
MediaDownloadBlackList []string
|
MediaDownloadBlackList []string
|
||||||
MediaDownloadPath string // Basically MediaServerUpload, but instead of uploading it, just write it to a file on the same server.
|
MediaDownloadPath string // Basically MediaServerUpload, but instead of uploading it, just write it to a file on the same server.
|
||||||
MediaDownloadSize int // all protocols
|
MediaDownloadSize int // all protocols
|
||||||
@@ -119,6 +122,7 @@ 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
|
||||||
@@ -136,12 +140,13 @@ 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
|
Server string // IRC,mattermost,XMPP,discord,matrix
|
||||||
SessionFile string // msteams,whatsapp
|
SessionFile string // msteams,whatsapp
|
||||||
ShowJoinPart bool // all protocols
|
ShowJoinPart bool // all protocols
|
||||||
ShowTopicChange bool // slack
|
ShowTopicChange bool // slack
|
||||||
@@ -156,7 +161,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
|
Token string // gitter, slack, discord, api, matrix
|
||||||
Topic string // zulip
|
Topic string // zulip
|
||||||
URL string // mattermost, slack // DEPRECATED
|
URL string // mattermost, slack // DEPRECATED
|
||||||
UseAPI bool // mattermost, slack
|
UseAPI bool // mattermost, slack
|
||||||
@@ -165,8 +170,9 @@ 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
|
UseUserName bool // discord, matrix, mattermost
|
||||||
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
|
||||||
|
|||||||
@@ -10,10 +10,14 @@ import (
|
|||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/discord/transmitter"
|
"github.com/42wim/matterbridge/bridge/discord/transmitter"
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
"github.com/matterbridge/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
|
lru "github.com/hashicorp/golang-lru"
|
||||||
)
|
)
|
||||||
|
|
||||||
const MessageLength = 1950
|
const (
|
||||||
|
MessageLength = 1950
|
||||||
|
cFileUpload = "file_upload"
|
||||||
|
)
|
||||||
|
|
||||||
type Bdiscord struct {
|
type Bdiscord struct {
|
||||||
*bridge.Config
|
*bridge.Config
|
||||||
@@ -35,10 +39,20 @@ type Bdiscord struct {
|
|||||||
// Webhook specific logic
|
// Webhook specific logic
|
||||||
useAutoWebhooks bool
|
useAutoWebhooks bool
|
||||||
transmitter *transmitter.Transmitter
|
transmitter *transmitter.Transmitter
|
||||||
|
cache *lru.Cache
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(cfg *bridge.Config) bridge.Bridger {
|
func New(cfg *bridge.Config) bridge.Bridger {
|
||||||
b := &Bdiscord{Config: cfg}
|
newCache, err := lru.New(5000)
|
||||||
|
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)
|
||||||
@@ -75,6 +89,9 @@ 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
|
// Add privileged intent for guild member tracking. This is needed to track nicks
|
||||||
// for display names and @mention translation
|
// for display names and @mention translation
|
||||||
b.c.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsAllWithoutPrivileged |
|
b.c.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsAllWithoutPrivileged |
|
||||||
@@ -153,7 +170,7 @@ func (b *Bdiscord) Connect() error {
|
|||||||
return fmt.Errorf("use of removed WebhookURL setting")
|
return fmt.Errorf("use of removed WebhookURL setting")
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.GetInt("debuglevel") > 0 {
|
if b.GetInt("debuglevel") == 2 {
|
||||||
b.Log.Debug("enabling even more discord debug")
|
b.Log.Debug("enabling even more discord debug")
|
||||||
b.c.Debug = true
|
b.c.Debug = true
|
||||||
}
|
}
|
||||||
@@ -255,7 +272,6 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
|
|||||||
// Handle prefix hint for unthreaded messages.
|
// Handle prefix hint for unthreaded messages.
|
||||||
if msg.ParentNotFound() {
|
if msg.ParentNotFound() {
|
||||||
msg.ParentID = ""
|
msg.ParentID = ""
|
||||||
msg.Text = fmt.Sprintf("[thread]: %s", msg.Text)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use webhook to send the message
|
// Use webhook to send the message
|
||||||
@@ -280,10 +296,25 @@ 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)
|
rmsg.Text = helper.ClipMessage(rmsg.Text, MessageLength, b.GetString("MessageClipped"))
|
||||||
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)
|
||||||
}
|
}
|
||||||
@@ -294,7 +325,7 @@ func (b *Bdiscord) handleEventBotUser(msg *config.Message, channelID string) (st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.Text = helper.ClipMessage(msg.Text, MessageLength)
|
msg.Text = helper.ClipMessage(msg.Text, MessageLength, b.GetString("MessageClipped"))
|
||||||
msg.Text = b.replaceUserMentions(msg.Text)
|
msg.Text = b.replaceUserMentions(msg.Text)
|
||||||
|
|
||||||
// Edit message
|
// Edit message
|
||||||
@@ -304,7 +335,8 @@ func (b *Bdiscord) handleEventBotUser(msg *config.Message, channelID string) (st
|
|||||||
}
|
}
|
||||||
|
|
||||||
m := discordgo.MessageSend{
|
m := discordgo.MessageSend{
|
||||||
Content: msg.Username + msg.Text,
|
Content: msg.Username + msg.Text,
|
||||||
|
AllowedMentions: b.getAllowedMentions(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg.ParentValid() {
|
if msg.ParentValid() {
|
||||||
@@ -326,7 +358,6 @@ func (b *Bdiscord) handleEventBotUser(msg *config.Message, channelID string) (st
|
|||||||
|
|
||||||
// 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{
|
||||||
@@ -335,13 +366,19 @@ 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(),
|
||||||
}
|
}
|
||||||
_, err = b.c.ChannelMessageSendComplex(channelID, &m)
|
res, 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ package bdiscord
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/matterbridge/discordgo"
|
"github.com/bwmarrin/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
|
||||||
@@ -31,6 +32,10 @@ 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
|
||||||
@@ -51,7 +56,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 != "" {
|
if m.Message.EditedTimestamp != nil {
|
||||||
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{
|
||||||
@@ -82,8 +87,9 @@ 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 {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package bdiscord
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/matterbridge/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,33 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/matterbridge/discordgo"
|
"github.com/bwmarrin/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()
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/matterbridge/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package transmitter
|
package transmitter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/matterbridge/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
// isDiscordPermissionError returns false for nil, and true if a Discord RESTError with code discordgo.ErrorCodeMissionPermissions
|
// isDiscordPermissionError returns false for nil, and true if a Discord RESTError with code discordgo.ErrorCodeMissionPermissions
|
||||||
|
|||||||
@@ -5,7 +5,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/matterbridge/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
// shouldMessageUseWebhooks checks if have a channel specific webhook, if we're not using auto webhooks
|
// shouldMessageUseWebhooks checks if have a channel specific webhook, if we're not using auto webhooks
|
||||||
@@ -63,9 +63,10 @@ func (b *Bdiscord) webhookSend(msg *config.Message, channelID string) (*discordg
|
|||||||
res, err = b.transmitter.Send(
|
res, err = b.transmitter.Send(
|
||||||
channelID,
|
channelID,
|
||||||
&discordgo.WebhookParams{
|
&discordgo.WebhookParams{
|
||||||
Content: msg.Text,
|
Content: msg.Text,
|
||||||
Username: msg.Username,
|
Username: msg.Username,
|
||||||
AvatarURL: msg.Avatar,
|
AvatarURL: msg.Avatar,
|
||||||
|
AllowedMentions: b.getAllowedMentions(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -81,17 +82,16 @@ func (b *Bdiscord) webhookSend(msg *config.Message, channelID string) (*discordg
|
|||||||
ContentType: "",
|
ContentType: "",
|
||||||
Reader: bytes.NewReader(*fi.Data),
|
Reader: bytes.NewReader(*fi.Data),
|
||||||
}
|
}
|
||||||
content := ""
|
content := fi.Comment
|
||||||
if msg.Text == "" {
|
|
||||||
content = fi.Comment
|
|
||||||
}
|
|
||||||
_, e2 := b.transmitter.Send(
|
_, e2 := b.transmitter.Send(
|
||||||
channelID,
|
channelID,
|
||||||
&discordgo.WebhookParams{
|
&discordgo.WebhookParams{
|
||||||
Username: msg.Username,
|
Username: msg.Username,
|
||||||
AvatarURL: msg.Avatar,
|
AvatarURL: msg.Avatar,
|
||||||
File: &file,
|
Files: []*discordgo.File{&file},
|
||||||
Content: content,
|
Content: content,
|
||||||
|
AllowedMentions: b.getAllowedMentions(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if e2 != nil {
|
if e2 != nil {
|
||||||
@@ -114,7 +114,7 @@ func (b *Bdiscord) handleEventWebhook(msg *config.Message, channelID string) (st
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.Text = helper.ClipMessage(msg.Text, MessageLength)
|
msg.Text = helper.ClipMessage(msg.Text, MessageLength, b.GetString("MessageClipped"))
|
||||||
msg.Text = b.replaceUserMentions(msg.Text)
|
msg.Text = b.replaceUserMentions(msg.Text)
|
||||||
// discord username must be [0..32] max
|
// discord username must be [0..32] max
|
||||||
if len(msg.Username) > 32 {
|
if len(msg.Username) > 32 {
|
||||||
@@ -124,8 +124,9 @@ func (b *Bdiscord) handleEventWebhook(msg *config.Message, channelID string) (st
|
|||||||
if msg.ID != "" {
|
if msg.ID != "" {
|
||||||
b.Log.Debugf("Editing webhook message")
|
b.Log.Debugf("Editing webhook message")
|
||||||
err := b.transmitter.Edit(channelID, msg.ID, &discordgo.WebhookParams{
|
err := b.transmitter.Edit(channelID, msg.ID, &discordgo.WebhookParams{
|
||||||
Content: msg.Text,
|
Content: msg.Text,
|
||||||
Username: msg.Username,
|
Username: msg.Username,
|
||||||
|
AllowedMentions: b.getAllowedMentions(),
|
||||||
})
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return msg.ID, nil
|
return msg.ID, nil
|
||||||
|
|||||||
252
bridge/harmony/harmony.go
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
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,10 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image/png"
|
"image/png"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -82,8 +79,10 @@ 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) []string {
|
func GetSubLines(message string, maxLineLength int, clippingMessage string) []string {
|
||||||
const clippingMessage = " <clipped message>"
|
if clippingMessage == "" {
|
||||||
|
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") {
|
||||||
@@ -169,17 +168,23 @@ 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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,8 +198,11 @@ 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) string {
|
func ClipMessage(text string, length int, clippingMessage string) string {
|
||||||
const clippingMessage = " <clipped message>"
|
if clippingMessage == "" {
|
||||||
|
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 {
|
||||||
@@ -234,66 +242,3 @@ 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:
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,98 +10,96 @@ import (
|
|||||||
|
|
||||||
const testLineLength = 64
|
const testLineLength = 64
|
||||||
|
|
||||||
var (
|
var lineSplittingTestCases = map[string]struct {
|
||||||
lineSplittingTestCases = map[string]struct {
|
input string
|
||||||
input string
|
splitOutput []string
|
||||||
splitOutput []string
|
nonSplitOutput []string
|
||||||
nonSplitOutput []string
|
}{
|
||||||
}{
|
"Short single-line message": {
|
||||||
"Short single-line message": {
|
input: "short",
|
||||||
input: "short",
|
splitOutput: []string{"short"},
|
||||||
splitOutput: []string{"short"},
|
nonSplitOutput: []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.",
|
||||||
},
|
},
|
||||||
"Long single-line message": {
|
nonSplitOutput: []string{"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."},
|
||||||
input: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
},
|
||||||
splitOutput: []string{
|
"Short multi-line message": {
|
||||||
"Lorem ipsum dolor sit amet, consectetur adipis <clipped message>",
|
input: "I\ncan't\nget\nno\nsatisfaction!",
|
||||||
"cing elit, sed do eiusmod tempor incididunt ut <clipped message>",
|
splitOutput: []string{
|
||||||
" labore et dolore magna aliqua.",
|
"I",
|
||||||
},
|
"can't",
|
||||||
nonSplitOutput: []string{"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."},
|
"get",
|
||||||
|
"no",
|
||||||
|
"satisfaction!",
|
||||||
},
|
},
|
||||||
"Short multi-line message": {
|
nonSplitOutput: []string{
|
||||||
input: "I\ncan't\nget\nno\nsatisfaction!",
|
"I",
|
||||||
splitOutput: []string{
|
"can't",
|
||||||
"I",
|
"get",
|
||||||
"can't",
|
"no",
|
||||||
"get",
|
"satisfaction!",
|
||||||
"no",
|
|
||||||
"satisfaction!",
|
|
||||||
},
|
|
||||||
nonSplitOutput: []string{
|
|
||||||
"I",
|
|
||||||
"can't",
|
|
||||||
"get",
|
|
||||||
"no",
|
|
||||||
"satisfaction!",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"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" +
|
"Long multi-line message": {
|
||||||
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n" +
|
input: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n" +
|
||||||
"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n" +
|
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n" +
|
||||||
"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
|
"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n" +
|
||||||
splitOutput: []string{
|
"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
|
||||||
"Lorem ipsum dolor sit amet, consectetur adipis <clipped message>",
|
splitOutput: []string{
|
||||||
"cing elit, sed do eiusmod tempor incididunt ut <clipped message>",
|
"Lorem ipsum dolor sit amet, consectetur adipis <clipped message>",
|
||||||
" labore et dolore magna aliqua.",
|
"cing elit, sed do eiusmod tempor incididunt ut <clipped message>",
|
||||||
"Ut enim ad minim veniam, quis nostrud exercita <clipped message>",
|
" labore et dolore magna aliqua.",
|
||||||
"tion ullamco laboris nisi ut aliquip ex ea com <clipped message>",
|
"Ut enim ad minim veniam, quis nostrud exercita <clipped message>",
|
||||||
"modo consequat.",
|
"tion ullamco laboris nisi ut aliquip ex ea com <clipped message>",
|
||||||
"Duis aute irure dolor in reprehenderit in volu <clipped message>",
|
"modo consequat.",
|
||||||
"ptate velit esse cillum dolore eu fugiat nulla <clipped message>",
|
"Duis aute irure dolor in reprehenderit in volu <clipped message>",
|
||||||
" pariatur.",
|
"ptate velit esse cillum dolore eu fugiat nulla <clipped message>",
|
||||||
"Excepteur sint occaecat cupidatat non proident <clipped message>",
|
" pariatur.",
|
||||||
", sunt in culpa qui officia deserunt mollit an <clipped message>",
|
"Excepteur sint occaecat cupidatat non proident <clipped message>",
|
||||||
"im id est laborum.",
|
", sunt in culpa qui officia deserunt mollit an <clipped message>",
|
||||||
},
|
"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.",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"Message ending with new-line.": {
|
nonSplitOutput: []string{
|
||||||
input: "Newline ending\n",
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
||||||
splitOutput: []string{"Newline ending"},
|
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
|
||||||
nonSplitOutput: []string{"Newline ending"},
|
"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.",
|
||||||
},
|
},
|
||||||
"Long message containing UTF-8 multi-byte runes": {
|
},
|
||||||
input: "不布人個我此而及單石業喜資富下我河下日沒一我臺空達的常景便物沒為……子大我別名解成?生賣的全直黑,我自我結毛分洲了世當,是政福那是東;斯說",
|
"Message ending with new-line.": {
|
||||||
splitOutput: []string{
|
input: "Newline ending\n",
|
||||||
"不布人個我此而及單石業喜資富下 <clipped message>",
|
splitOutput: []string{"Newline ending"},
|
||||||
"我河下日沒一我臺空達的常景便物 <clipped message>",
|
nonSplitOutput: []string{"Newline ending"},
|
||||||
"沒為……子大我別名解成?生賣的 <clipped message>",
|
},
|
||||||
"全直黑,我自我結毛分洲了世當, <clipped message>",
|
"Long message containing UTF-8 multi-byte runes": {
|
||||||
"是政福那是東;斯說",
|
input: "不布人個我此而及單石業喜資富下我河下日沒一我臺空達的常景便物沒為……子大我別名解成?生賣的全直黑,我自我結毛分洲了世當,是政福那是東;斯說",
|
||||||
},
|
splitOutput: []string{
|
||||||
nonSplitOutput: []string{"不布人個我此而及單石業喜資富下我河下日沒一我臺空達的常景便物沒為……子大我別名解成?生賣的全直黑,我自我結毛分洲了世當,是政福那是東;斯說"},
|
"不布人個我此而及單石業喜資富下 <clipped message>",
|
||||||
|
"我河下日沒一我臺空達的常景便物 <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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,16 +108,19 @@ 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()
|
||||||
}
|
}
|
||||||
|
|||||||
36
bridge/helper/libtgsconverter.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
//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"
|
||||||
|
}
|
||||||
89
bridge/helper/lottie_convert.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
// +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"
|
||||||
|
}
|
||||||
32
bridge/irc/charset.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
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,7 +11,6 @@ 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"
|
||||||
|
|
||||||
@@ -24,12 +23,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 = ic.ConvertString("utf-8", b.GetString("Charset"), msg.Text)
|
msg.Text = toUTF8(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 from utf-8 conversion failed: %s", err)
|
b.Log.Errorf("charset to utf-8 conversion failed: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprint(w, msg.Text)
|
fmt.Fprint(w, msg.Text)
|
||||||
@@ -227,7 +226,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 = ic.ConvertString("utf-8", b.GetString("Charset"), rmsg.Text)
|
rmsg.Text = toUTF8(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,6 +2,7 @@ package birc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/crc32"
|
"hash/crc32"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@@ -72,6 +73,10 @@ 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"))
|
||||||
|
|
||||||
@@ -167,9 +172,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)
|
msgLines = helper.GetSubLines(msg.Text, b.MessageLength, b.GetString("MessageClipped"))
|
||||||
} else {
|
} else {
|
||||||
msgLines = helper.GetSubLines(msg.Text, 0)
|
msgLines = helper.GetSubLines(msg.Text, 0, b.GetString("MessageClipped"))
|
||||||
}
|
}
|
||||||
for i := range msgLines {
|
for i := range msgLines {
|
||||||
if len(b.Local) >= b.MessageQueue {
|
if len(b.Local) >= b.MessageQueue {
|
||||||
@@ -271,8 +276,11 @@ 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"
|
||||||
@@ -280,6 +288,10 @@ 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 {
|
||||||
@@ -293,15 +305,21 @@ 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: b.GetString("Nick"),
|
Name: realName,
|
||||||
SSL: b.GetBool("UseTLS"),
|
SSL: b.GetBool("UseTLS"),
|
||||||
TLSConfig: &tls.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), ServerName: server}, //nolint:gosec
|
Bind: b.GetString("Bind"),
|
||||||
|
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,
|
||||||
@@ -316,12 +334,16 @@ 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{Username: b.Nick, Text: b.formatnicks(b.names[channel][0:maxNamesPerPost]),
|
b.Remote <- config.Message{
|
||||||
Channel: channel, Account: b.Account}
|
Username: b.Nick, Text: b.formatnicks(b.names[channel][0:maxNamesPerPost]),
|
||||||
|
Channel: channel, Account: b.Account,
|
||||||
|
}
|
||||||
b.names[channel] = b.names[channel][maxNamesPerPost:]
|
b.names[channel] = b.names[channel][maxNamesPerPost:]
|
||||||
}
|
}
|
||||||
b.Remote <- config.Message{Username: b.Nick, Text: b.formatnicks(b.names[channel]),
|
b.Remote <- config.Message{
|
||||||
Channel: channel, Account: b.Account}
|
Username: b.Nick, Text: b.formatnicks(b.names[channel]),
|
||||||
|
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)
|
||||||
@@ -340,8 +362,10 @@ 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.Name == b.Nick {
|
if event.Source != nil {
|
||||||
return true
|
if event.Source.Name == b.Nick {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// don't forward messages we sent via RELAYMSG
|
// don't forward messages we sent via RELAYMSG
|
||||||
if relayedNick, ok := event.Tags.Get("draft/relaymsg"); ok && relayedNick == b.Nick {
|
if relayedNick, ok := event.Tags.Get("draft/relaymsg"); ok && relayedNick == b.Nick {
|
||||||
@@ -369,3 +393,23 @@ 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
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
matrix "github.com/matrix-org/gomatrix"
|
matrix "github.com/matterbridge/gomatrix"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newMatrixUsername(username string) *matrixUsername {
|
func newMatrixUsername(username string) *matrixUsername {
|
||||||
|
|||||||
@@ -12,7 +12,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"
|
||||||
matrix "github.com/matrix-org/gomatrix"
|
matrix "github.com/matterbridge/gomatrix"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -48,8 +48,10 @@ 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.
|
||||||
@@ -65,6 +67,19 @@ 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)
|
||||||
@@ -75,22 +90,33 @@ 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"))
|
||||||
b.mc, err = matrix.NewClient(b.GetString("Server"), "", "")
|
if b.GetString("MxID") != "" && b.GetString("Token") != "" {
|
||||||
if err != nil {
|
b.mc, err = matrix.NewClient(
|
||||||
return err
|
b.GetString("Server"), b.GetString("MxID"), b.GetString("Token"),
|
||||||
|
)
|
||||||
|
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
|
||||||
}
|
}
|
||||||
@@ -127,7 +153,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 + msg.Text,
|
FormattedBody: username.formatted + helper.ParseMarkdown(msg.Text),
|
||||||
|
Format: "org.matrix.custom.html",
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.GetBool("HTMLDisable") {
|
||||||
|
m.Format = ""
|
||||||
|
m.FormattedBody = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
msgID := ""
|
msgID := ""
|
||||||
@@ -190,20 +222,29 @@ 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{TextMessage: matrix.TextMessage{
|
rmsg := EditedMessage{
|
||||||
Body: username.plain + msg.Text,
|
TextMessage: matrix.TextMessage{
|
||||||
MsgType: "m.text",
|
Body: username.plain + msg.Text,
|
||||||
}}
|
MsgType: "m.text",
|
||||||
if b.GetBool("HTMLDisable") {
|
Format: "org.matrix.custom.html",
|
||||||
rmsg.TextMessage.FormattedBody = username.formatted + "* " + msg.Text
|
FormattedBody: username.formatted + helper.ParseMarkdown(msg.Text),
|
||||||
} else {
|
},
|
||||||
rmsg.Format = "org.matrix.custom.html"
|
|
||||||
rmsg.TextMessage.FormattedBody = username.formatted + "* " + helper.ParseMarkdown(msg.Text)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rmsg.NewContent = SubTextMessage{
|
rmsg.NewContent = SubTextMessage{
|
||||||
Body: rmsg.TextMessage.Body,
|
Body: rmsg.TextMessage.Body,
|
||||||
MsgType: "m.text",
|
FormattedBody: rmsg.TextMessage.FormattedBody,
|
||||||
|
Format: rmsg.TextMessage.Format,
|
||||||
|
MsgType: "m.text",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if b.GetBool("HTMLDisable") {
|
||||||
|
rmsg.TextMessage.Format = ""
|
||||||
|
rmsg.TextMessage.FormattedBody = ""
|
||||||
|
rmsg.NewContent.Format = ""
|
||||||
|
rmsg.NewContent.FormattedBody = ""
|
||||||
|
}
|
||||||
|
|
||||||
rmsg.RelatedTo = MessageRelation{
|
rmsg.RelatedTo = MessageRelation{
|
||||||
EventID: msg.ID,
|
EventID: msg.ID,
|
||||||
Type: "m.replace",
|
Type: "m.replace",
|
||||||
@@ -227,6 +268,50 @@ 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",
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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 (
|
var (
|
||||||
@@ -290,6 +375,9 @@ 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)
|
||||||
}
|
}
|
||||||
@@ -327,6 +415,38 @@ 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
|
||||||
|
|
||||||
|
if !b.GetBool("keepquotedreply") {
|
||||||
|
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" {
|
||||||
@@ -357,13 +477,6 @@ 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("(.*?):.*")
|
||||||
@@ -379,6 +492,13 @@ 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
|
||||||
@@ -389,6 +509,11 @@ 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)
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import (
|
|||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
"github.com/42wim/matterbridge/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
|
||||||
@@ -21,12 +23,26 @@ 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 {
|
||||||
data, resp := b.mc.Client.GetProfileImage(userid, "")
|
var (
|
||||||
if resp.Error != nil {
|
data []byte
|
||||||
b.Log.Errorf("ProfileImage download failed for %#v %s", userid, resp.Error)
|
err error
|
||||||
return
|
resp *model.Response
|
||||||
|
)
|
||||||
|
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
|
||||||
@@ -38,6 +54,10 @@ 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 {
|
||||||
@@ -55,6 +75,25 @@ 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") != "" {
|
||||||
@@ -87,6 +126,12 @@ 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)
|
||||||
|
|
||||||
@@ -95,9 +140,14 @@ 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, message.Channel)
|
b.handleDownloadAvatar(message.UserID, channelName)
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Log.Debugf("== Receiving event %#v", message)
|
b.Log.Debugf("== Receiving event %#v", message)
|
||||||
@@ -105,7 +155,7 @@ 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: message.Channel,
|
Channel: channelName,
|
||||||
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.RootId, // ParentID is obsolete with mattermost
|
||||||
@@ -132,8 +182,72 @@ func (b *Bmattermost) handleMatterClient(messages chan *config.Message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Use nickname instead of username if defined
|
// Use nickname instead of username if defined
|
||||||
if nick := b.mc.GetNickName(rmsg.UserID); nick != "" {
|
if !b.GetBool("useusername") {
|
||||||
rmsg.Username = nick
|
if nick := b.mc.GetNickName(rmsg.UserID); 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
|
||||||
@@ -144,6 +258,7 @@ 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,
|
||||||
@@ -155,9 +270,13 @@ 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.mc.GetChannelId(msg.Channel, b.TeamID)
|
channelID := b.getChannelID(msg.Channel)
|
||||||
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)
|
||||||
@@ -173,6 +292,26 @@ 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 {
|
||||||
@@ -197,3 +336,31 @@ 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,13 +1,16 @@
|
|||||||
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 {
|
||||||
@@ -15,25 +18,47 @@ 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{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
matterhook.Config{
|
||||||
BindAddress: b.GetString("WebhookBindAddress")})
|
InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
||||||
|
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)")
|
||||||
err := b.apiLogin()
|
b.Log.Infof("Using mattermost v6 methods: %t", b.v6)
|
||||||
if err != nil {
|
|
||||||
return err
|
if b.v6 {
|
||||||
|
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)")
|
||||||
err := b.apiLogin()
|
b.Log.Infof("Using mattermost v6 methods: %t", b.v6)
|
||||||
if err != nil {
|
|
||||||
return err
|
if b.v6 {
|
||||||
|
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{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
matterhook.Config{
|
||||||
BindAddress: b.GetString("WebhookBindAddress")})
|
InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
||||||
|
BindAddress: b.GetString("WebhookBindAddress"),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -41,19 +66,39 @@ 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{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
matterhook.Config{
|
||||||
DisableServer: true})
|
InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
||||||
|
DisableServer: true,
|
||||||
|
})
|
||||||
if b.GetString("Token") != "" {
|
if b.GetString("Token") != "" {
|
||||||
b.Log.Info("Connecting using token (receiving)")
|
b.Log.Info("Connecting using token (receiving)")
|
||||||
err := b.apiLogin()
|
b.Log.Infof("Using mattermost v6 methods: %t", b.v6)
|
||||||
if err != nil {
|
|
||||||
return err
|
if b.v6 {
|
||||||
|
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)")
|
||||||
err := b.apiLogin()
|
b.Log.Infof("Using mattermost v6 methods: %t", b.v6)
|
||||||
if err != nil {
|
|
||||||
return err
|
if b.v6 {
|
||||||
|
err := b.apiLogin6()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := b.apiLogin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -84,6 +129,31 @@ 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, "*") {
|
||||||
@@ -113,6 +183,7 @@ func (b *Bmattermost) sendWebhook(msg config.Message) (string, error) {
|
|||||||
if b.GetBool("PrefixMessagesWithNick") {
|
if b.GetBool("PrefixMessagesWithNick") {
|
||||||
msg.Text = msg.Username + msg.Text
|
msg.Text = msg.Username + msg.Text
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg.Extra != nil {
|
if msg.Extra != nil {
|
||||||
// this sends a message only if we received a config.EVENT_FILE_FAILURE_SIZE
|
// this sends a message only if we received a config.EVENT_FILE_FAILURE_SIZE
|
||||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||||
@@ -136,7 +207,7 @@ func (b *Bmattermost) sendWebhook(msg config.Message) (string, error) {
|
|||||||
for _, f := range msg.Extra["file"] {
|
for _, f := range msg.Extra["file"] {
|
||||||
fi := f.(config.FileInfo)
|
fi := f.(config.FileInfo)
|
||||||
if fi.URL != "" {
|
if fi.URL != "" {
|
||||||
msg.Text += fi.URL
|
msg.Text += " " + fi.URL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,11 +242,17 @@ 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: message.Channel,
|
Channel: channelName,
|
||||||
Account: b.Account,
|
Account: b.Account,
|
||||||
Event: config.EventJoinLeave,
|
Event: config.EventJoinLeave,
|
||||||
}
|
}
|
||||||
@@ -223,3 +300,119 @@ 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,29 +3,43 @@ 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{Config: cfg, avatarMap: make(map[string]string)}
|
b := &Bmattermost{
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,6 +51,13 @@ 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
|
||||||
@@ -53,16 +74,34 @@ 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)")
|
||||||
err := b.apiLogin()
|
b.Log.Infof("Using mattermost v6 methods: %t", b.v6)
|
||||||
if err != nil {
|
|
||||||
return err
|
if b.v6 {
|
||||||
|
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)")
|
||||||
err := b.apiLogin()
|
b.Log.Infof("Using mattermost v6 methods: %t", b.v6)
|
||||||
if err != nil {
|
|
||||||
return err
|
if b.v6 {
|
||||||
|
err := b.apiLogin6()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := b.apiLogin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
go b.handleMatter()
|
go b.handleMatter()
|
||||||
}
|
}
|
||||||
@@ -81,14 +120,25 @@ 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.mc.GetChannelId(channel.Name, b.TeamID)
|
id := b.getChannelID(channel.Name)
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,6 +168,10 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,18 +183,36 @@ func (b *Bmattermost) Send(msg config.Message) (string, error) {
|
|||||||
|
|
||||||
// we only can reply to the root of the thread, not to a specific ID (like discord for example does)
|
// we only can reply to the root of the thread, not to a specific ID (like discord for example does)
|
||||||
if msg.ParentID != "" {
|
if msg.ParentID != "" {
|
||||||
post, res := b.mc.Client.GetPost(msg.ParentID, "")
|
if b.mc6 != nil {
|
||||||
if res.Error != nil {
|
post, _, err := b.mc6.Client.GetPost(msg.ParentID, "")
|
||||||
b.Log.Errorf("getting post %s failed: %s", msg.ParentID, res.Error.DetailedError)
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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 _, err := b.mc.PostMessage(b.mc.GetChannelId(rmsg.Channel, b.TeamID), rmsg.Username+rmsg.Text, msg.ParentID); err != nil {
|
if b.mc6 != nil {
|
||||||
b.Log.Errorf("PostMessage failed: %s", err)
|
if _, err := b.mc6.PostMessage(b.getChannelID(rmsg.Channel), rmsg.Username+rmsg.Text, msg.ParentID); err != nil {
|
||||||
|
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 {
|
||||||
@@ -155,9 +227,17 @@ 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
|
||||||
return b.mc.PostMessage(b.mc.GetChannelId(msg.Channel, b.TeamID), msg.Text, msg.ParentID)
|
if b.mc6 != nil {
|
||||||
|
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,8 +19,10 @@ import (
|
|||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultScopes = []string{"openid", "profile", "offline_access", "Group.Read.All", "Group.ReadWrite.All"}
|
var (
|
||||||
var attachRE = regexp.MustCompile(`<attachment id=.*?attachment>`)
|
defaultScopes = []string{"openid", "profile", "offline_access", "Group.Read.All", "Group.ReadWrite.All"}
|
||||||
|
attachRE = regexp.MustCompile(`<attachment id=.*?attachment>`)
|
||||||
|
)
|
||||||
|
|
||||||
type Bmsteams struct {
|
type Bmsteams struct {
|
||||||
gc *msgraph.GraphServiceRequestBuilder
|
gc *msgraph.GraphServiceRequestBuilder
|
||||||
@@ -50,7 +52,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, 0600)
|
err = os.Chmod(tokenCachePath, 0o600)
|
||||||
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)
|
||||||
}
|
}
|
||||||
@@ -168,7 +170,7 @@ func (b *Bmsteams) poll(channelName string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// skip non-user message for now.
|
// skip non-user message for now.
|
||||||
if msg.From.User == nil {
|
if msg.From == nil || msg.From.User == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
70
bridge/mumble/codec.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
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() {
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"layeh.com/gumble/gumble"
|
"layeh.com/gumble/gumble"
|
||||||
@@ -184,6 +185,7 @@ 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
|
||||||
@@ -248,12 +250,14 @@ 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))
|
msgLines = helper.GetSubLines(msg.Text, *maxLength-len(msg.Username), b.GetString("MessageClipped"))
|
||||||
} else {
|
} else {
|
||||||
msgLines = helper.GetSubLines(msg.Text, 0)
|
msgLines = helper.GetSubLines(msg.Text, 0, b.GetString("MessageClipped"))
|
||||||
}
|
}
|
||||||
// Send the individual lindes
|
// Send the individual lines
|
||||||
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,12 +74,6 @@ 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
|
||||||
@@ -90,35 +84,23 @@ func (b *Btalk) JoinChannel(channel config.ChannelInfo) error {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore messages that are one of the following
|
// Ignore messages that are from the bot user
|
||||||
// * not a message from a user
|
if msg.ActorID == b.user.User || msg.ActorType == "bridged" {
|
||||||
// * from ourselves
|
continue
|
||||||
if msg.MessageType != ocs.MessageComment || msg.ActorID == b.user.User {
|
}
|
||||||
continue
|
|
||||||
}
|
// Handle deleting messages
|
||||||
remoteMessage := config.Message{
|
if msg.MessageType == ocs.MessageSystem && msg.Parent != nil && msg.Parent.MessageType == ocs.MessageDelete {
|
||||||
Text: formatRichObjectString(msg.Message, msg.MessageParameters),
|
b.handleDeletingMessage(&msg, &newRoom)
|
||||||
Channel: newRoom.room.Token,
|
continue
|
||||||
Username: DisplayName(msg, guestSuffix),
|
}
|
||||||
UserID: msg.ActorID,
|
|
||||||
Account: b.Account,
|
// Handle sending messages
|
||||||
}
|
if msg.MessageType == ocs.MessageComment {
|
||||||
// It is possible for the ID to not be set on older versions of Talk so we only set it if
|
b.handleSendingMessage(&msg, &newRoom)
|
||||||
// the ID is not blank
|
|
||||||
if msg.ID != 0 {
|
|
||||||
remoteMessage.ID = strconv.Itoa(msg.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle Files
|
|
||||||
err = b.handleFiles(&remoteMessage, &msg)
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("Error handling file: %#v", msg)
|
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Log.Debugf("<= Message is %#v", remoteMessage)
|
|
||||||
b.Remote <- remoteMessage
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return nil
|
return nil
|
||||||
@@ -131,16 +113,40 @@ func (b *Btalk) Send(msg config.Message) (string, error) {
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Talk currently only supports sending normal messages
|
// Standard Message Send
|
||||||
if msg.Event != "" {
|
if msg.Event == "" {
|
||||||
return "", nil
|
// Handle sending files if they are included
|
||||||
|
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)
|
|
||||||
if err != nil {
|
// Message Deletion
|
||||||
b.Log.Errorf("Could not send message to room %v from %v: %v", msg.Channel, msg.Username, err)
|
if msg.Event == config.EventMsgDelete {
|
||||||
return "", nil
|
messageID, err := strconv.Atoi(msg.ID)
|
||||||
|
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 {
|
||||||
@@ -152,6 +158,17 @@ 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 {
|
||||||
@@ -177,6 +194,74 @@ 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 {
|
||||||
@@ -197,7 +282,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"
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ 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)
|
||||||
@@ -76,6 +77,13 @@ 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:
|
||||||
@@ -95,6 +103,8 @@ 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)
|
||||||
}
|
}
|
||||||
@@ -220,6 +230,26 @@ 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:
|
||||||
@@ -252,6 +282,13 @@ func (b *Bslack) handleStatusEvent(ev *slack.MessageEvent, rmsg *config.Message)
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getMessageTitle(attach *slack.Attachment) string {
|
||||||
|
if attach.TitleLink != "" {
|
||||||
|
return fmt.Sprintf("[%s](%s)\n", attach.Title, attach.TitleLink)
|
||||||
|
}
|
||||||
|
return attach.Title
|
||||||
|
}
|
||||||
|
|
||||||
func (b *Bslack) handleAttachments(ev *slack.MessageEvent, rmsg *config.Message) {
|
func (b *Bslack) handleAttachments(ev *slack.MessageEvent, rmsg *config.Message) {
|
||||||
// File comments are set by the system (because there is no username given).
|
// File comments are set by the system (because there is no username given).
|
||||||
if ev.SubType == sFileComment {
|
if ev.SubType == sFileComment {
|
||||||
@@ -260,12 +297,15 @@ func (b *Bslack) handleAttachments(ev *slack.MessageEvent, rmsg *config.Message)
|
|||||||
|
|
||||||
// See if we have some text in the attachments.
|
// See if we have some text in the attachments.
|
||||||
if rmsg.Text == "" {
|
if rmsg.Text == "" {
|
||||||
for _, attach := range ev.Attachments {
|
for i, attach := range ev.Attachments {
|
||||||
if attach.Text != "" {
|
if attach.Text != "" {
|
||||||
if attach.Title != "" {
|
if attach.Title != "" {
|
||||||
rmsg.Text = attach.Title + "\n"
|
rmsg.Text = getMessageTitle(&ev.Attachments[i])
|
||||||
}
|
}
|
||||||
rmsg.Text += attach.Text
|
rmsg.Text += attach.Text
|
||||||
|
if attach.Footer != "" {
|
||||||
|
rmsg.Text += "\n\n" + attach.Footer
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
rmsg.Text = attach.Fallback
|
rmsg.Text = attach.Fallback
|
||||||
}
|
}
|
||||||
@@ -279,6 +319,8 @@ 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)
|
||||||
}
|
}
|
||||||
@@ -328,7 +370,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.HandleDownloadData(b.Log, rmsg, file.Name, comment, file.URLPrivateDownload, data, b.General)
|
helper.HandleDownloadData2(b.Log, rmsg, file.Name, file.ID, comment, file.URLPrivateDownload, data, b.General)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -87,6 +87,9 @@ func (b *Bslack) populateMessageWithUserInfo(ev *slack.MessageEvent, rmsg *confi
|
|||||||
if user.Profile.DisplayName != "" {
|
if user.Profile.DisplayName != "" {
|
||||||
rmsg.Username = user.Profile.DisplayName
|
rmsg.Username = user.Profile.DisplayName
|
||||||
}
|
}
|
||||||
|
if b.GetBool("UseFullName") && user.Profile.RealName != "" {
|
||||||
|
rmsg.Username = user.Profile.RealName
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +127,7 @@ var (
|
|||||||
mentionRE = regexp.MustCompile(`<@([a-zA-Z0-9]+)>`)
|
mentionRE = regexp.MustCompile(`<@([a-zA-Z0-9]+)>`)
|
||||||
channelRE = regexp.MustCompile(`<#[a-zA-Z0-9]+\|(.+?)>`)
|
channelRE = regexp.MustCompile(`<#[a-zA-Z0-9]+\|(.+?)>`)
|
||||||
variableRE = regexp.MustCompile(`<!((?:subteam\^)?[a-zA-Z0-9]+)(?:\|@?(.+?))?>`)
|
variableRE = regexp.MustCompile(`<!((?:subteam\^)?[a-zA-Z0-9]+)(?:\|@?(.+?))?>`)
|
||||||
urlRE = regexp.MustCompile(`<(.*?)(\|.*?)?>`)
|
urlRE = regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`)
|
||||||
codeFenceRE = regexp.MustCompile(`(?m)^` + "```" + `\w+$`)
|
codeFenceRE = regexp.MustCompile(`(?m)^` + "```" + `\w+$`)
|
||||||
topicOrPurposeRE = regexp.MustCompile(`(?s)(@.+) (cleared|set)(?: the)? channel (topic|purpose)(?:: (.*))?`)
|
topicOrPurposeRE = regexp.MustCompile(`(?s)(@.+) (cleared|set)(?: the)? channel (topic|purpose)(?:: (.*))?`)
|
||||||
)
|
)
|
||||||
@@ -178,14 +181,7 @@ func (b *Bslack) replaceVariable(text string) string {
|
|||||||
|
|
||||||
// @see https://api.slack.com/docs/message-formatting#linking_to_urls
|
// @see https://api.slack.com/docs/message-formatting#linking_to_urls
|
||||||
func (b *Bslack) replaceURL(text string) string {
|
func (b *Bslack) replaceURL(text string) string {
|
||||||
for _, r := range urlRE.FindAllStringSubmatch(text, -1) {
|
return urlRE.ReplaceAllString(text, "[${2}](${1})")
|
||||||
if len(strings.TrimSpace(r[2])) == 1 { // A display text separator was found, but the text was blank
|
|
||||||
text = strings.Replace(text, r[0], "", 1)
|
|
||||||
} else {
|
|
||||||
text = strings.Replace(text, r[0], r[1], 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return text
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bslack) replaceb0rkedMarkDown(text string) string {
|
func (b *Bslack) replaceb0rkedMarkDown(text string) string {
|
||||||
|
|||||||
@@ -36,24 +36,25 @@ 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"
|
||||||
@@ -156,7 +157,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.JoinChannel(channel.Name)
|
_, _, _, err := b.sc.JoinConversation(channel.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err.Error() {
|
switch err.Error() {
|
||||||
case "name_taken", "restricted_action":
|
case "name_taken", "restricted_action":
|
||||||
@@ -195,7 +196,7 @@ func (b *Bslack) Send(msg config.Message) (string, error) {
|
|||||||
b.Log.Debugf("=> Receiving %#v", msg)
|
b.Log.Debugf("=> Receiving %#v", msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.Text = helper.ClipMessage(msg.Text, messageLength)
|
msg.Text = helper.ClipMessage(msg.Text, messageLength, b.GetString("MessageClipped"))
|
||||||
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
|
||||||
@@ -320,7 +321,7 @@ func (b *Bslack) sendRTM(msg config.Message) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Upload a file if it exists.
|
// Upload a file if it exists.
|
||||||
if msg.Extra != nil {
|
if len(msg.Extra) > 0 {
|
||||||
extraMsgs := helper.HandleExtra(&msg, b.General)
|
extraMsgs := helper.HandleExtra(&msg, b.General)
|
||||||
for i := range extraMsgs {
|
for i := range extraMsgs {
|
||||||
rmsg := &extraMsgs[i]
|
rmsg := &extraMsgs[i]
|
||||||
@@ -331,7 +332,7 @@ func (b *Bslack) sendRTM(msg config.Message) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Upload files if necessary (from Slack, Telegram or Mattermost).
|
// Upload files if necessary (from Slack, Telegram or Mattermost).
|
||||||
b.uploadFile(&msg, channelInfo.ID)
|
return b.uploadFile(&msg, channelInfo.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post message.
|
// Post message.
|
||||||
@@ -442,7 +443,8 @@ func (b *Bslack) postMessage(msg *config.Message, channelInfo *slack.Channel) (s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// uploadFile handles native upload of files
|
// uploadFile handles native upload of files
|
||||||
func (b *Bslack) uploadFile(msg *config.Message, channelID string) {
|
func (b *Bslack) uploadFile(msg *config.Message, channelID string) (string, error) {
|
||||||
|
var messageID string
|
||||||
for _, f := range msg.Extra["file"] {
|
for _, f := range msg.Extra["file"] {
|
||||||
fi, ok := f.(config.FileInfo)
|
fi, ok := f.(config.FileInfo)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -459,7 +461,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),
|
||||||
@@ -470,13 +472,22 @@ func (b *Bslack) uploadFile(msg *config.Message, channelID string) {
|
|||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Errorf("uploadfile %#v", err)
|
b.Log.Errorf("uploadfile %#v", err)
|
||||||
return
|
return "", err
|
||||||
}
|
}
|
||||||
if res.ID != "" {
|
if res.ID != "" {
|
||||||
b.Log.Debugf("Adding file ID %s to cache with timestamp %s", res.ID, ts.String())
|
b.Log.Debugf("Adding file ID %s to cache with timestamp %s", res.ID, ts.String())
|
||||||
b.cache.Add("file"+res.ID, ts)
|
b.cache.Add("file"+res.ID, ts)
|
||||||
|
|
||||||
|
// search for message id by uploaded file in private/public channels, get thread timestamp from uploaded file
|
||||||
|
if v, ok := res.Shares.Private[channelID]; ok && len(v) > 0 {
|
||||||
|
messageID = v[0].Ts
|
||||||
|
}
|
||||||
|
if v, ok := res.Shares.Public[channelID]; ok && len(v) > 0 {
|
||||||
|
messageID = v[0].Ts
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return messageID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bslack) prepareMessageOptions(msg *config.Message) []slack.MsgOption {
|
func (b *Bslack) prepareMessageOptions(msg *config.Message) []slack.MsgOption {
|
||||||
|
|||||||
@@ -113,6 +113,12 @@ 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) {
|
||||||
@@ -283,8 +289,9 @@ 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,6 +1,7 @@
|
|||||||
package btelegram
|
package btelegram
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -9,14 +10,27 @@ 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"
|
||||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
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 {
|
||||||
message = posted
|
if posted.Text == "/chatId" {
|
||||||
rmsg.Text = message.Text
|
chatID := strconv.FormatInt(posted.Chat.ID, 10)
|
||||||
|
|
||||||
|
_, 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
|
||||||
@@ -43,6 +57,11 @@ 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
|
||||||
@@ -52,6 +71,9 @@ func (b *Btelegram) handleForwarded(rmsg *config.Message, message *tgbotapi.Mess
|
|||||||
if b.GetBool("UseFirstName") {
|
if b.GetBool("UseFirstName") {
|
||||||
usernameForward = message.ForwardFrom.FirstName
|
usernameForward = message.ForwardFrom.FirstName
|
||||||
}
|
}
|
||||||
|
if b.GetBool("UseFullName") {
|
||||||
|
usernameForward = message.ForwardFrom.FirstName + " " + message.ForwardFrom.LastName
|
||||||
|
}
|
||||||
|
|
||||||
if usernameForward == "" {
|
if usernameForward == "" {
|
||||||
usernameForward = message.ForwardFrom.UserName
|
usernameForward = message.ForwardFrom.UserName
|
||||||
@@ -75,6 +97,9 @@ func (b *Btelegram) handleQuoting(rmsg *config.Message, message *tgbotapi.Messag
|
|||||||
if b.GetBool("UseFirstName") {
|
if b.GetBool("UseFirstName") {
|
||||||
usernameReply = message.ReplyToMessage.From.FirstName
|
usernameReply = message.ReplyToMessage.From.FirstName
|
||||||
}
|
}
|
||||||
|
if b.GetBool("UseFullName") {
|
||||||
|
usernameReply = message.ReplyToMessage.From.FirstName + " " + message.ReplyToMessage.From.LastName
|
||||||
|
}
|
||||||
if usernameReply == "" {
|
if usernameReply == "" {
|
||||||
usernameReply = message.ReplyToMessage.From.UserName
|
usernameReply = message.ReplyToMessage.From.UserName
|
||||||
if usernameReply == "" {
|
if usernameReply == "" {
|
||||||
@@ -86,7 +111,11 @@ func (b *Btelegram) handleQuoting(rmsg *config.Message, message *tgbotapi.Messag
|
|||||||
usernameReply = unknownUser
|
usernameReply = unknownUser
|
||||||
}
|
}
|
||||||
if !b.GetBool("QuoteDisable") {
|
if !b.GetBool("QuoteDisable") {
|
||||||
rmsg.Text = b.handleQuote(rmsg.Text, usernameReply, message.ReplyToMessage.Text)
|
quote := message.ReplyToMessage.Text
|
||||||
|
if quote == "" {
|
||||||
|
quote = message.ReplyToMessage.Caption
|
||||||
|
}
|
||||||
|
rmsg.Text = b.handleQuote(rmsg.Text, usernameReply, quote)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,10 +123,13 @@ 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.Itoa(message.From.ID)
|
rmsg.UserID = strconv.FormatInt(message.From.ID, 10)
|
||||||
if b.GetBool("UseFirstName") {
|
if b.GetBool("UseFirstName") {
|
||||||
rmsg.Username = message.From.FirstName
|
rmsg.Username = message.From.FirstName
|
||||||
}
|
}
|
||||||
|
if b.GetBool("UseFullName") {
|
||||||
|
rmsg.Username = message.From.FirstName + " " + message.From.LastName
|
||||||
|
}
|
||||||
if rmsg.Username == "" {
|
if rmsg.Username == "" {
|
||||||
rmsg.Username = message.From.UserName
|
rmsg.Username = message.From.UserName
|
||||||
if rmsg.Username == "" {
|
if rmsg.Username == "" {
|
||||||
@@ -110,6 +142,28 @@ 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 b.GetBool("UseFullName") {
|
||||||
|
rmsg.Username = message.SenderChat.FirstName + " " + message.SenderChat.LastName
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
@@ -126,6 +180,10 @@ 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{})}
|
||||||
@@ -145,6 +203,14 @@ 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)
|
||||||
|
|
||||||
|
// preserve threading from telegram reply
|
||||||
|
if message.ReplyToMessage != nil {
|
||||||
|
rmsg.ParentID = strconv.Itoa(message.ReplyToMessage.MessageID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle entities (adding URLs)
|
||||||
|
b.handleEntities(&rmsg, message)
|
||||||
|
|
||||||
// handle username
|
// handle username
|
||||||
b.handleUsername(&rmsg, message)
|
b.handleUsername(&rmsg, message)
|
||||||
|
|
||||||
@@ -160,14 +226,12 @@ 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 {
|
||||||
rmsg.Text = helper.RemoveEmptyNewLines(rmsg.Text)
|
// Comment the next line out due to avoid removing empty lines in Telegram
|
||||||
|
// 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.Itoa(message.From.ID), b.General)
|
rmsg.Avatar = helper.GetAvatar(b.avatarMap, strconv.FormatInt(message.From.ID, 10), 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)
|
||||||
@@ -180,60 +244,52 @@ 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 int, channel string) {
|
func (b *Btelegram) handleDownloadAvatar(userid int64, 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.Itoa(userid)]; !ok {
|
if _, ok := b.avatarMap[strconv.FormatInt(userid, 10)]; ok {
|
||||||
photos, err := b.c.GetUserProfilePhotos(tgbotapi.UserProfilePhotosConfig{UserID: userid, Limit: 1})
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Errorf("Userprofile download failed for %#v %s", userid, err)
|
b.Log.Error(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
data, err := helper.DownloadFile(url)
|
||||||
if len(photos.Photos) > 0 {
|
if err != nil {
|
||||||
photo := photos.Photos[0][0]
|
b.Log.Errorf("download %s failed %#v", url, err)
|
||||||
url := b.getFileDirectURL(photo.FileID)
|
return
|
||||||
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) {
|
||||||
var format string
|
format := b.GetString("MediaConvertTgs")
|
||||||
switch b.GetString("MediaConvertTgs") {
|
if helper.SupportsFormat(format) {
|
||||||
case FormatWebp:
|
b.Log.Debugf("Format supported by %s, converting %v", helper.LottieBackend(), name)
|
||||||
b.Log.Debugf("Tgs to WebP conversion enabled, converting %v", name)
|
} else {
|
||||||
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.
|
||||||
@@ -282,7 +338,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)
|
||||||
}
|
}
|
||||||
@@ -329,7 +385,7 @@ func (b *Btelegram) getDownloadInfo(id string, suffix string, urlpart bool) (str
|
|||||||
urlPart := strings.Split(url, "/")
|
urlPart := strings.Split(url, "/")
|
||||||
name = urlPart[len(urlPart)-1]
|
name = urlPart[len(urlPart)-1]
|
||||||
}
|
}
|
||||||
if suffix != "" && !strings.HasSuffix(name, suffix) {
|
if suffix != "" && !strings.HasSuffix(name, suffix) && !strings.HasSuffix(name, ".webm") {
|
||||||
name += suffix
|
name += suffix
|
||||||
}
|
}
|
||||||
text := " " + url
|
text := " " + url
|
||||||
@@ -341,11 +397,15 @@ 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.Request(cfg)
|
||||||
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,8 +443,8 @@ func (b *Btelegram) handleEdit(msg *config.Message, chatid int64) (string, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handleUploadFile handles native upload of files
|
// handleUploadFile handles native upload of files
|
||||||
func (b *Btelegram) handleUploadFile(msg *config.Message, chatid int64) string {
|
func (b *Btelegram) handleUploadFile(msg *config.Message, chatid int64, parentID int) (string, error) {
|
||||||
var c tgbotapi.Chattable
|
var media []interface{}
|
||||||
for _, f := range msg.Extra["file"] {
|
for _, f := range msg.Extra["file"] {
|
||||||
fi := f.(config.FileInfo)
|
fi := f.(config.FileInfo)
|
||||||
file := tgbotapi.FileBytes{
|
file := tgbotapi.FileBytes{
|
||||||
@@ -393,32 +453,42 @@ func (b *Btelegram) handleUploadFile(msg *config.Message, chatid int64) string {
|
|||||||
}
|
}
|
||||||
switch filepath.Ext(fi.Name) {
|
switch filepath.Ext(fi.Name) {
|
||||||
case ".jpg", ".jpe", ".png":
|
case ".jpg", ".jpe", ".png":
|
||||||
pc := tgbotapi.NewPhotoUpload(chatid, file)
|
pc := tgbotapi.NewInputMediaPhoto(file)
|
||||||
pc.Caption, pc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
|
if fi.Comment != "" {
|
||||||
c = pc
|
pc.Caption, pc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
|
||||||
|
}
|
||||||
|
media = append(media, pc)
|
||||||
case ".mp4", ".m4v":
|
case ".mp4", ".m4v":
|
||||||
vc := tgbotapi.NewVideoUpload(chatid, file)
|
vc := tgbotapi.NewInputMediaVideo(file)
|
||||||
vc.Caption, vc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
|
if fi.Comment != "" {
|
||||||
c = vc
|
vc.Caption, vc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
|
||||||
|
}
|
||||||
|
media = append(media, vc)
|
||||||
case ".mp3", ".oga":
|
case ".mp3", ".oga":
|
||||||
ac := tgbotapi.NewAudioUpload(chatid, file)
|
ac := tgbotapi.NewInputMediaAudio(file)
|
||||||
ac.Caption, ac.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
|
if fi.Comment != "" {
|
||||||
c = ac
|
ac.Caption, ac.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
|
||||||
|
}
|
||||||
|
media = append(media, ac)
|
||||||
case ".ogg":
|
case ".ogg":
|
||||||
voc := tgbotapi.NewVoiceUpload(chatid, file)
|
voc := tgbotapi.NewVoice(chatid, file)
|
||||||
voc.Caption, voc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
|
voc.Caption, voc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
|
||||||
c = voc
|
voc.ReplyToMessageID = parentID
|
||||||
|
res, err := b.c.Send(voc)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return strconv.Itoa(res.MessageID), nil
|
||||||
default:
|
default:
|
||||||
dc := tgbotapi.NewDocumentUpload(chatid, file)
|
dc := tgbotapi.NewInputMediaDocument(file)
|
||||||
dc.Caption, dc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
|
if fi.Comment != "" {
|
||||||
c = dc
|
dc.Caption, dc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
|
||||||
}
|
}
|
||||||
_, err := b.c.Send(c)
|
media = append(media, dc)
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("file upload failed: %#v", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
|
||||||
|
return b.sendMediaFiles(msg, chatid, parentID, media)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Btelegram) handleQuote(message, quoteNick, quoteMessage string) string {
|
func (b *Btelegram) handleQuote(message, quoteNick, quoteMessage string) string {
|
||||||
@@ -445,21 +515,61 @@ func (b *Btelegram) handleEntities(rmsg *config.Message, message *tgbotapi.Messa
|
|||||||
if message.Entities == nil {
|
if message.Entities == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// for now only do URL replacements
|
|
||||||
for _, e := range *message.Entities {
|
indexMovedBy := 0
|
||||||
|
prevLinkOffset := -1
|
||||||
|
|
||||||
|
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 e.Offset+e.Length > len(utfEncodedString) {
|
if offset+e.Length > len(utfEncodedString) {
|
||||||
b.Log.Errorf("entity length is too long %d > %d", e.Offset+e.Length, len(utfEncodedString))
|
b.Log.Errorf("entity length is too long %d > %d", offset+e.Length, len(utfEncodedString))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
link := utf16.Decode(utfEncodedString[e.Offset : e.Offset+e.Length])
|
rmsg.Text = string(utf16.Decode(asRunes[:offset+e.Length])) + " (" + url.String() + ")" + string(utf16.Decode(asRunes[offset+e.Length:]))
|
||||||
rmsg.Text = strings.Replace(rmsg.Text, string(link), url.String(), 1)
|
indexMovedBy += len(url.String()) + 3
|
||||||
|
prevLinkOffset = e.Offset
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Offset == prevLinkOffset {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package btelegram
|
package btelegram
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"log"
|
"log"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -9,7 +10,7 @@ import (
|
|||||||
"github.com/42wim/matterbridge/bridge"
|
"github.com/42wim/matterbridge/bridge"
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -17,8 +18,6 @@ const (
|
|||||||
HTMLFormat = "HTML"
|
HTMLFormat = "HTML"
|
||||||
HTMLNick = "htmlnick"
|
HTMLNick = "htmlnick"
|
||||||
MarkdownV2 = "MarkdownV2"
|
MarkdownV2 = "MarkdownV2"
|
||||||
FormatPng = "png"
|
|
||||||
FormatWebp = "webp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Btelegram struct {
|
type Btelegram struct {
|
||||||
@@ -32,10 +31,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 lottie does not appear to work:\n%#v", tgsConvertFormat, err)
|
log.Fatalf("Telegram bridge configured to convert .tgs files to '%s', but %s does not appear to work:\n%#v", tgsConvertFormat, helper.LottieBackend(), err)
|
||||||
}
|
}
|
||||||
if tgsConvertFormat != FormatPng && tgsConvertFormat != FormatWebp {
|
if !helper.SupportsFormat(tgsConvertFormat) {
|
||||||
log.Fatalf("Telegram bridge configured to convert .tgs files to '%s', but only '%s' and '%s' are supported.", FormatPng, FormatWebp, tgsConvertFormat)
|
log.Fatalf("Telegram bridge configured to convert .tgs files to '%s', but %s doesn't support it.", tgsConvertFormat, helper.LottieBackend())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &Btelegram{Config: cfg, avatarMap: make(map[string]string)}
|
return &Btelegram{Config: cfg, avatarMap: make(map[string]string)}
|
||||||
@@ -51,11 +50,7 @@ func (b *Btelegram) Connect() error {
|
|||||||
}
|
}
|
||||||
u := tgbotapi.NewUpdate(0)
|
u := tgbotapi.NewUpdate(0)
|
||||||
u.Timeout = 60
|
u.Timeout = 60
|
||||||
updates, err := b.c.GetUpdatesChan(u)
|
updates := 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
|
||||||
@@ -114,16 +109,27 @@ func (b *Btelegram) Send(msg config.Message) (string, error) {
|
|||||||
return b.handleDelete(&msg, chatid)
|
return b.handleDelete(&msg, chatid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle prefix hint for unthreaded messages.
|
||||||
|
if msg.ParentNotFound() {
|
||||||
|
msg.ParentID = ""
|
||||||
|
msg.Text = fmt.Sprintf("[reply]: %s", msg.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
var parentID int
|
||||||
|
if msg.ParentID != "" {
|
||||||
|
parentID, _ = b.intParentID(msg.ParentID)
|
||||||
|
}
|
||||||
|
|
||||||
// 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 _, msgErr := b.sendMessage(chatid, rmsg.Username, rmsg.Text); msgErr != nil {
|
if _, msgErr := b.sendMessage(chatid, rmsg.Username, rmsg.Text, parentID); msgErr != nil {
|
||||||
b.Log.Errorf("sendMessage failed: %s", msgErr)
|
b.Log.Errorf("sendMessage failed: %s", msgErr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// check if we have files to upload (from slack, telegram or mattermost)
|
// check if we have files to upload (from slack, telegram or mattermost)
|
||||||
if len(msg.Extra["file"]) > 0 {
|
if len(msg.Extra["file"]) > 0 {
|
||||||
b.handleUploadFile(&msg, chatid)
|
return b.handleUploadFile(&msg, chatid, parentID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +143,7 @@ func (b *Btelegram) Send(msg config.Message) (string, error) {
|
|||||||
// Ignore empty text field needs for prevent double messages from whatsapp to telegram
|
// Ignore empty text field needs for prevent double messages from whatsapp to telegram
|
||||||
// when sending media with text caption
|
// when sending media with text caption
|
||||||
if msg.Text != "" {
|
if msg.Text != "" {
|
||||||
return b.sendMessage(chatid, msg.Username, msg.Text)
|
return b.sendMessage(chatid, msg.Username, msg.Text, parentID)
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", nil
|
return "", nil
|
||||||
@@ -151,10 +157,10 @@ func (b *Btelegram) getFileDirectURL(id string) string {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Btelegram) sendMessage(chatid int64, username, text string) (string, error) {
|
func (b *Btelegram) sendMessage(chatid int64, username, text string, parentID int) (string, error) {
|
||||||
m := tgbotapi.NewMessage(chatid, "")
|
m := tgbotapi.NewMessage(chatid, "")
|
||||||
m.Text, m.ParseMode = TGGetParseMode(b, username, text)
|
m.Text, m.ParseMode = TGGetParseMode(b, username, text)
|
||||||
|
m.ReplyToMessageID = parentID
|
||||||
m.DisableWebPagePreview = b.GetBool("DisableWebPagePreview")
|
m.DisableWebPagePreview = b.GetBool("DisableWebPagePreview")
|
||||||
|
|
||||||
res, err := b.c.Send(m)
|
res, err := b.c.Send(m)
|
||||||
@@ -164,6 +170,29 @@ func (b *Btelegram) sendMessage(chatid int64, username, text string) (string, er
|
|||||||
return strconv.Itoa(res.MessageID), nil
|
return strconv.Itoa(res.MessageID), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sendMediaFiles native upload media files via media group
|
||||||
|
func (b *Btelegram) sendMediaFiles(msg *config.Message, chatid int64, parentID int, media []interface{}) (string, error) {
|
||||||
|
if len(media) == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
mg := tgbotapi.MediaGroupConfig{ChatID: chatid, ChannelUsername: msg.Username, Media: media, ReplyToMessageID: parentID}
|
||||||
|
messages, err := b.c.SendMediaGroup(mg)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// return first message id
|
||||||
|
return strconv.Itoa(messages[0].MessageID), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// intParentID return integer parent id for telegram message
|
||||||
|
func (b *Btelegram) intParentID(parentID string) (int, error) {
|
||||||
|
pid, err := strconv.Atoi(parentID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return pid, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (b *Btelegram) cacheAvatar(msg *config.Message) (string, error) {
|
func (b *Btelegram) cacheAvatar(msg *config.Message) (string, error) {
|
||||||
fi := msg.Extra["file"][0].(config.FileInfo)
|
fi := msg.Extra["file"][0].(config.FileInfo)
|
||||||
/* if we have a sha we have successfully uploaded the file to the media server,
|
/* if we have a sha we have successfully uploaded the file to the media server,
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ type user struct {
|
|||||||
|
|
||||||
type Bvk struct {
|
type Bvk struct {
|
||||||
c *api.VK
|
c *api.VK
|
||||||
|
lp *longpoll.LongPoll
|
||||||
usernamesMap map[int]user // cache of user names and avatar URLs
|
usernamesMap map[int]user // cache of user names and avatar URLs
|
||||||
*bridge.Config
|
*bridge.Config
|
||||||
}
|
}
|
||||||
@@ -45,23 +46,25 @@ func New(cfg *bridge.Config) bridge.Bridger {
|
|||||||
func (b *Bvk) Connect() error {
|
func (b *Bvk) Connect() error {
|
||||||
b.Log.Info("Connecting")
|
b.Log.Info("Connecting")
|
||||||
b.c = api.NewVK(b.GetString("Token"))
|
b.c = api.NewVK(b.GetString("Token"))
|
||||||
lp, err := longpoll.NewLongPoll(b.c, b.GetInt("GroupID"))
|
|
||||||
|
var err error
|
||||||
|
b.lp, err = longpoll.NewLongPollCommunity(b.c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Debugf("%#v", err)
|
b.Log.Debugf("%#v", err)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
lp.MessageNew(func(ctx context.Context, obj events.MessageNewObject) {
|
b.lp.MessageNew(func(ctx context.Context, obj events.MessageNewObject) {
|
||||||
b.handleMessage(obj.Message, false)
|
b.handleMessage(obj.Message, false)
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Log.Info("Connection succeeded")
|
b.Log.Info("Connection succeeded")
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
err := lp.Run()
|
err := b.lp.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Fatal("Enable longpoll in group management")
|
b.Log.WithError(err).Fatal("Enable longpoll in group management")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -69,6 +72,8 @@ func (b *Bvk) Connect() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bvk) Disconnect() error {
|
func (b *Bvk) Disconnect() error {
|
||||||
|
b.lp.Shutdown()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +223,7 @@ func (b *Bvk) uploadFiles(extra map[string][]interface{}, peerID int) (string, s
|
|||||||
}
|
}
|
||||||
a, err := b.uploadFile(fi, peerID)
|
a, err := b.uploadFile(fi, peerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Error("File upload error ", fi.Name)
|
b.Log.WithError(err).Error("File upload error ", fi.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
attachments = append(attachments, a)
|
attachments = append(attachments, a)
|
||||||
@@ -232,7 +237,8 @@ func (b *Bvk) uploadFile(file config.FileInfo, peerID int) (string, error) {
|
|||||||
|
|
||||||
photoRE := regexp.MustCompile(".(jpg|jpe|png)$")
|
photoRE := regexp.MustCompile(".(jpg|jpe|png)$")
|
||||||
if photoRE.MatchString(file.Name) {
|
if photoRE.MatchString(file.Name) {
|
||||||
p, err := b.c.UploadMessagesPhoto(peerID, r)
|
// BUG(VK): for community chat peerID=0
|
||||||
|
p, err := b.c.UploadMessagesPhoto(0, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// nolint:goconst
|
||||||
package bwhatsapp
|
package bwhatsapp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -134,6 +135,7 @@ func (b *Bwhatsapp) HandleTextMessage(message whatsapp.TextMessage) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleImageMessage sent from WhatsApp, relay it to the brige
|
// HandleImageMessage sent from WhatsApp, relay it to the brige
|
||||||
|
// nolint:funlen
|
||||||
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 || message.Info.Timestamp < b.startedAt {
|
||||||
return
|
return
|
||||||
@@ -175,6 +177,11 @@ func (b *Bwhatsapp) HandleImageMessage(message whatsapp.ImageMessage) {
|
|||||||
fileExt[0] = ".jpg"
|
fileExt[0] = ".jpg"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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("Trying to download %s with type %s", filename, message.Type)
|
||||||
@@ -232,6 +239,10 @@ func (b *Bwhatsapp) HandleVideoMessage(message whatsapp.VideoMessage) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(fileExt) == 0 {
|
||||||
|
fileExt = append(fileExt, ".mp4")
|
||||||
|
}
|
||||||
|
|
||||||
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 size %#v and type %s", filename, message.Length, message.Type)
|
b.Log.Debugf("Trying to download %s with size %#v and type %s", filename, message.Length, message.Type)
|
||||||
@@ -312,3 +323,60 @@ func (b *Bwhatsapp) HandleAudioMessage(message whatsapp.AudioMessage) {
|
|||||||
|
|
||||||
b.Remote <- 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
|
||||||
|
}
|
||||||
|
|||||||
@@ -111,8 +111,7 @@ func (b *Bwhatsapp) getSenderName(senderJid string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// try to reload this contact
|
// try to reload this contact
|
||||||
_, err := b.conn.Contacts()
|
if _, err := b.conn.Contacts(); err != nil {
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("error on update of contacts: %v", err)
|
b.Log.Errorf("error on update of contacts: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,11 @@ type Bwhatsapp struct {
|
|||||||
func New(cfg *bridge.Config) bridge.Bridger {
|
func New(cfg *bridge.Config) bridge.Bridger {
|
||||||
number := cfg.GetString(cfgNumber)
|
number := cfg.GetString(cfgNumber)
|
||||||
|
|
||||||
|
cfg.Log.Warn("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
||||||
|
cfg.Log.Warn("This bridge is deprecated and not supported anymore. Use the new multidevice whatsapp bridge")
|
||||||
|
cfg.Log.Warn("See https://github.com/42wim/matterbridge#building-with-whatsapp-beta-multidevice-support for more info")
|
||||||
|
cfg.Log.Warn("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
||||||
|
|
||||||
if number == "" {
|
if number == "" {
|
||||||
cfg.Log.Fatalf("Missing configuration for WhatsApp bridge: Number")
|
cfg.Log.Fatalf("Missing configuration for WhatsApp bridge: Number")
|
||||||
}
|
}
|
||||||
@@ -293,7 +298,11 @@ func (b *Bwhatsapp) Send(msg config.Message) (string, error) {
|
|||||||
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)
|
||||||
|
|
||||||
msg.Text += " (edited)"
|
if b.GetString("editsuffix") != "" {
|
||||||
|
msg.Text += b.GetString("EditSuffix")
|
||||||
|
} else {
|
||||||
|
msg.Text += " (edited)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle Upload a file
|
// Handle Upload a file
|
||||||
|
|||||||
344
bridge/whatsappmulti/handlers.go
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
// +build whatsappmulti
|
||||||
|
|
||||||
|
package bwhatsapp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"mime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
|
|
||||||
|
"go.mau.fi/whatsmeow/binary/proto"
|
||||||
|
"go.mau.fi/whatsmeow/types"
|
||||||
|
"go.mau.fi/whatsmeow/types/events"
|
||||||
|
)
|
||||||
|
|
||||||
|
// nolint:gocritic
|
||||||
|
func (b *Bwhatsapp) eventHandler(evt interface{}) {
|
||||||
|
switch e := evt.(type) {
|
||||||
|
case *events.Message:
|
||||||
|
b.handleMessage(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bwhatsapp) handleMessage(message *events.Message) {
|
||||||
|
msg := message.Message
|
||||||
|
switch {
|
||||||
|
case msg == nil, message.Info.IsFromMe, message.Info.Timestamp.Before(b.startedAt):
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Log.Infof("Receiving message %#v", msg)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case msg.Conversation != nil || msg.ExtendedTextMessage != nil:
|
||||||
|
b.handleTextMessage(message.Info, msg)
|
||||||
|
case msg.VideoMessage != nil:
|
||||||
|
b.handleVideoMessage(message)
|
||||||
|
case msg.AudioMessage != nil:
|
||||||
|
b.handleAudioMessage(message)
|
||||||
|
case msg.DocumentMessage != nil:
|
||||||
|
b.handleDocumentMessage(message)
|
||||||
|
case msg.ImageMessage != nil:
|
||||||
|
b.handleImageMessage(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint:funlen
|
||||||
|
func (b *Bwhatsapp) handleTextMessage(messageInfo types.MessageInfo, msg *proto.Message) {
|
||||||
|
senderJID := messageInfo.Sender
|
||||||
|
channel := messageInfo.Chat
|
||||||
|
|
||||||
|
senderName := b.getSenderName(messageInfo.Sender)
|
||||||
|
if senderName == "" {
|
||||||
|
senderName = "Someone" // don't expose telephone number
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.GetExtendedTextMessage() == nil && msg.GetConversation() == "" {
|
||||||
|
b.Log.Debugf("message without text content? %#v", msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var text string
|
||||||
|
|
||||||
|
// nolint:nestif
|
||||||
|
if msg.GetExtendedTextMessage() == nil {
|
||||||
|
text = msg.GetConversation()
|
||||||
|
} else {
|
||||||
|
text = msg.GetExtendedTextMessage().GetText()
|
||||||
|
ci := msg.GetExtendedTextMessage().GetContextInfo()
|
||||||
|
|
||||||
|
if senderJID == (types.JID{}) && ci.Participant != nil {
|
||||||
|
senderJID = types.NewJID(ci.GetParticipant(), types.DefaultUserServer)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ci.MentionedJid != nil {
|
||||||
|
// handle user mentions
|
||||||
|
for _, mentionedJID := range ci.MentionedJid {
|
||||||
|
numberAndSuffix := strings.SplitN(mentionedJID, "@", 2)
|
||||||
|
|
||||||
|
// mentions comes as telephone numbers and we don't want to expose it to other bridges
|
||||||
|
// replace it with something more meaninful to others
|
||||||
|
mention := b.getSenderNotify(types.NewJID(numberAndSuffix[0], types.DefaultUserServer))
|
||||||
|
if mention == "" {
|
||||||
|
mention = "someone"
|
||||||
|
}
|
||||||
|
|
||||||
|
text = strings.Replace(text, "@"+numberAndSuffix[0], "@"+mention, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rmsg := config.Message{
|
||||||
|
UserID: senderJID.String(),
|
||||||
|
Username: senderName,
|
||||||
|
Text: text,
|
||||||
|
Channel: channel.String(),
|
||||||
|
Account: b.Account,
|
||||||
|
Protocol: b.Protocol,
|
||||||
|
Extra: make(map[string][]interface{}),
|
||||||
|
// ParentID: TODO, // TODO handle thread replies // map from Info.QuotedMessageID string
|
||||||
|
ID: messageInfo.ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
if avatarURL, exists := b.userAvatars[senderJID.String()]; exists {
|
||||||
|
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.Remote <- rmsg
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleImageMessage sent from WhatsApp, relay it to the brige
|
||||||
|
func (b *Bwhatsapp) handleImageMessage(msg *events.Message) {
|
||||||
|
imsg := msg.Message.GetImageMessage()
|
||||||
|
|
||||||
|
senderJID := msg.Info.Sender
|
||||||
|
senderName := b.getSenderName(senderJID)
|
||||||
|
ci := imsg.GetContextInfo()
|
||||||
|
|
||||||
|
if senderJID == (types.JID{}) && ci.Participant != nil {
|
||||||
|
senderJID = types.NewJID(ci.GetParticipant(), types.DefaultUserServer)
|
||||||
|
}
|
||||||
|
|
||||||
|
rmsg := config.Message{
|
||||||
|
UserID: senderJID.String(),
|
||||||
|
Username: senderName,
|
||||||
|
Channel: msg.Info.Chat.String(),
|
||||||
|
Account: b.Account,
|
||||||
|
Protocol: b.Protocol,
|
||||||
|
Extra: make(map[string][]interface{}),
|
||||||
|
ID: msg.Info.ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
if avatarURL, exists := b.userAvatars[senderJID.String()]; exists {
|
||||||
|
rmsg.Avatar = avatarURL
|
||||||
|
}
|
||||||
|
|
||||||
|
fileExt, err := mime.ExtensionsByType(imsg.GetMimetype())
|
||||||
|
if err != nil {
|
||||||
|
b.Log.Errorf("Mimetype detection error: %s", err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// rename .jfif to .jpg https://github.com/42wim/matterbridge/issues/1292
|
||||||
|
if fileExt[0] == ".jfif" {
|
||||||
|
fileExt[0] = ".jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
// rename .jpe to .jpg https://github.com/42wim/matterbridge/issues/1463
|
||||||
|
if fileExt[0] == ".jpe" {
|
||||||
|
fileExt[0] = ".jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := fmt.Sprintf("%v%v", msg.Info.ID, fileExt[0])
|
||||||
|
|
||||||
|
b.Log.Debugf("Trying to download %s with type %s", filename, imsg.GetMimetype())
|
||||||
|
|
||||||
|
data, err := b.wc.Download(imsg)
|
||||||
|
if err != nil {
|
||||||
|
b.Log.Errorf("Download image failed: %s", err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move file to bridge storage
|
||||||
|
helper.HandleDownloadData(b.Log, &rmsg, filename, imsg.GetCaption(), "", &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
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleVideoMessage downloads video messages
|
||||||
|
func (b *Bwhatsapp) handleVideoMessage(msg *events.Message) {
|
||||||
|
imsg := msg.Message.GetVideoMessage()
|
||||||
|
|
||||||
|
senderJID := msg.Info.Sender
|
||||||
|
senderName := b.getSenderName(senderJID)
|
||||||
|
ci := imsg.GetContextInfo()
|
||||||
|
|
||||||
|
if senderJID == (types.JID{}) && ci.Participant != nil {
|
||||||
|
senderJID = types.NewJID(ci.GetParticipant(), types.DefaultUserServer)
|
||||||
|
}
|
||||||
|
|
||||||
|
rmsg := config.Message{
|
||||||
|
UserID: senderJID.String(),
|
||||||
|
Username: senderName,
|
||||||
|
Channel: msg.Info.Chat.String(),
|
||||||
|
Account: b.Account,
|
||||||
|
Protocol: b.Protocol,
|
||||||
|
Extra: make(map[string][]interface{}),
|
||||||
|
ID: msg.Info.ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
if avatarURL, exists := b.userAvatars[senderJID.String()]; exists {
|
||||||
|
rmsg.Avatar = avatarURL
|
||||||
|
}
|
||||||
|
|
||||||
|
fileExt, err := mime.ExtensionsByType(imsg.GetMimetype())
|
||||||
|
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", msg.Info.ID, fileExt[0])
|
||||||
|
|
||||||
|
b.Log.Debugf("Trying to download %s with size %#v and type %s", filename, imsg.GetFileLength(), imsg.GetMimetype())
|
||||||
|
|
||||||
|
data, err := b.wc.Download(imsg)
|
||||||
|
if err != nil {
|
||||||
|
b.Log.Errorf("Download video failed: %s", err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move file to bridge storage
|
||||||
|
helper.HandleDownloadData(b.Log, &rmsg, filename, imsg.GetCaption(), "", &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(msg *events.Message) {
|
||||||
|
imsg := msg.Message.GetAudioMessage()
|
||||||
|
|
||||||
|
senderJID := msg.Info.Sender
|
||||||
|
senderName := b.getSenderName(senderJID)
|
||||||
|
ci := imsg.GetContextInfo()
|
||||||
|
|
||||||
|
if senderJID == (types.JID{}) && ci.Participant != nil {
|
||||||
|
senderJID = types.NewJID(ci.GetParticipant(), types.DefaultUserServer)
|
||||||
|
}
|
||||||
|
|
||||||
|
rmsg := config.Message{
|
||||||
|
UserID: senderJID.String(),
|
||||||
|
Username: senderName,
|
||||||
|
Channel: msg.Info.Chat.String(),
|
||||||
|
Account: b.Account,
|
||||||
|
Protocol: b.Protocol,
|
||||||
|
Extra: make(map[string][]interface{}),
|
||||||
|
ID: msg.Info.ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
if avatarURL, exists := b.userAvatars[senderJID.String()]; exists {
|
||||||
|
rmsg.Avatar = avatarURL
|
||||||
|
}
|
||||||
|
|
||||||
|
fileExt, err := mime.ExtensionsByType(imsg.GetMimetype())
|
||||||
|
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", msg.Info.ID, fileExt[0])
|
||||||
|
|
||||||
|
b.Log.Debugf("Trying to download %s with size %#v and type %s", filename, imsg.GetFileLength(), imsg.GetMimetype())
|
||||||
|
|
||||||
|
data, err := b.wc.Download(imsg)
|
||||||
|
if err != nil {
|
||||||
|
b.Log.Errorf("Download video 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(msg *events.Message) {
|
||||||
|
imsg := msg.Message.GetDocumentMessage()
|
||||||
|
|
||||||
|
senderJID := msg.Info.Sender
|
||||||
|
senderName := b.getSenderName(senderJID)
|
||||||
|
ci := imsg.GetContextInfo()
|
||||||
|
|
||||||
|
if senderJID == (types.JID{}) && ci.Participant != nil {
|
||||||
|
senderJID = types.NewJID(ci.GetParticipant(), types.DefaultUserServer)
|
||||||
|
}
|
||||||
|
|
||||||
|
rmsg := config.Message{
|
||||||
|
UserID: senderJID.String(),
|
||||||
|
Username: senderName,
|
||||||
|
Channel: msg.Info.Chat.String(),
|
||||||
|
Account: b.Account,
|
||||||
|
Protocol: b.Protocol,
|
||||||
|
Extra: make(map[string][]interface{}),
|
||||||
|
ID: msg.Info.ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
if avatarURL, exists := b.userAvatars[senderJID.String()]; exists {
|
||||||
|
rmsg.Avatar = avatarURL
|
||||||
|
}
|
||||||
|
|
||||||
|
fileExt, err := mime.ExtensionsByType(imsg.GetMimetype())
|
||||||
|
if err != nil {
|
||||||
|
b.Log.Errorf("Mimetype detection error: %s", err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := fmt.Sprintf("%v", imsg.GetFileName())
|
||||||
|
|
||||||
|
b.Log.Debugf("Trying to download %s with extension %s and type %s", filename, fileExt, imsg.GetMimetype())
|
||||||
|
|
||||||
|
data, err := b.wc.Download(imsg)
|
||||||
|
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
|
||||||
|
}
|
||||||
108
bridge/whatsappmulti/helpers.go
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
// +build whatsappmulti
|
||||||
|
|
||||||
|
package bwhatsapp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"go.mau.fi/whatsmeow/store"
|
||||||
|
"go.mau.fi/whatsmeow/store/sqlstore"
|
||||||
|
"go.mau.fi/whatsmeow/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProfilePicInfo struct {
|
||||||
|
URL string `json:"eurl"`
|
||||||
|
Tag string `json:"tag"`
|
||||||
|
Status int16 `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bwhatsapp) getSenderName(senderJid types.JID) string {
|
||||||
|
if sender, exists := b.contacts[senderJid]; exists {
|
||||||
|
if sender.FullName != "" {
|
||||||
|
return sender.FullName
|
||||||
|
}
|
||||||
|
// if user is not in phone contacts
|
||||||
|
// it is the most obvious scenario unless you sync your phone contacts with some remote updated source
|
||||||
|
// users can change it in their WhatsApp settings -> profile -> click on Avatar
|
||||||
|
if sender.PushName != "" {
|
||||||
|
return sender.PushName
|
||||||
|
}
|
||||||
|
|
||||||
|
if sender.FirstName != "" {
|
||||||
|
return sender.FirstName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to reload this contact
|
||||||
|
if _, err := b.wc.Store.Contacts.GetAllContacts(); err != nil {
|
||||||
|
b.Log.Errorf("error on update of contacts: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
allcontacts, err := b.wc.Store.Contacts.GetAllContacts()
|
||||||
|
if err != nil {
|
||||||
|
b.Log.Errorf("error on update of contacts: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(allcontacts) > 0 {
|
||||||
|
b.contacts = allcontacts
|
||||||
|
}
|
||||||
|
|
||||||
|
if sender, exists := b.contacts[senderJid]; exists {
|
||||||
|
if sender.FullName != "" {
|
||||||
|
return sender.FullName
|
||||||
|
}
|
||||||
|
// if user is not in phone contacts
|
||||||
|
// it is the most obvious scenario unless you sync your phone contacts with some remote updated source
|
||||||
|
// users can change it in their WhatsApp settings -> profile -> click on Avatar
|
||||||
|
if sender.PushName != "" {
|
||||||
|
return sender.PushName
|
||||||
|
}
|
||||||
|
|
||||||
|
if sender.FirstName != "" {
|
||||||
|
return sender.FirstName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Someone"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bwhatsapp) getSenderNotify(senderJid types.JID) string {
|
||||||
|
if sender, exists := b.contacts[senderJid]; exists {
|
||||||
|
return sender.PushName
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bwhatsapp) GetProfilePicThumb(jid string) (*types.ProfilePictureInfo, error) {
|
||||||
|
pjid, _ := types.ParseJID(jid)
|
||||||
|
info, err := b.wc.GetProfilePictureInfo(pjid, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get avatar: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isGroupJid(identifier string) bool {
|
||||||
|
return strings.HasSuffix(identifier, "@g.us") ||
|
||||||
|
strings.HasSuffix(identifier, "@temp") ||
|
||||||
|
strings.HasSuffix(identifier, "@broadcast")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bwhatsapp) getDevice() (*store.Device, error) {
|
||||||
|
device := &store.Device{}
|
||||||
|
|
||||||
|
storeContainer, err := sqlstore.New("sqlite", "file:"+b.Config.GetString("sessionfile")+".db?_foreign_keys=on&_pragma=busy_timeout=10000", nil)
|
||||||
|
if err != nil {
|
||||||
|
return device, fmt.Errorf("failed to connect to database: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
device, err = storeContainer.GetFirstDevice()
|
||||||
|
if err != nil {
|
||||||
|
return device, fmt.Errorf("failed to get device: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return device, nil
|
||||||
|
}
|
||||||
333
bridge/whatsappmulti/whatsapp.go
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
// +build whatsappmulti
|
||||||
|
|
||||||
|
package bwhatsapp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"mime"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/42wim/matterbridge/bridge"
|
||||||
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
|
"github.com/mdp/qrterminal"
|
||||||
|
|
||||||
|
"go.mau.fi/whatsmeow"
|
||||||
|
"go.mau.fi/whatsmeow/binary/proto"
|
||||||
|
"go.mau.fi/whatsmeow/types"
|
||||||
|
waLog "go.mau.fi/whatsmeow/util/log"
|
||||||
|
|
||||||
|
goproto "google.golang.org/protobuf/proto"
|
||||||
|
|
||||||
|
_ "modernc.org/sqlite" // needed for sqlite
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Account config parameters
|
||||||
|
cfgNumber = "Number"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bwhatsapp Bridge structure keeping all the information needed for relying
|
||||||
|
type Bwhatsapp struct {
|
||||||
|
*bridge.Config
|
||||||
|
|
||||||
|
startedAt time.Time
|
||||||
|
wc *whatsmeow.Client
|
||||||
|
contacts map[types.JID]types.ContactInfo
|
||||||
|
users map[string]types.ContactInfo
|
||||||
|
userAvatars map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
number := cfg.GetString(cfgNumber)
|
||||||
|
|
||||||
|
if number == "" {
|
||||||
|
cfg.Log.Fatalf("Missing configuration for WhatsApp bridge: Number")
|
||||||
|
}
|
||||||
|
|
||||||
|
b := &Bwhatsapp{
|
||||||
|
Config: cfg,
|
||||||
|
|
||||||
|
users: make(map[string]types.ContactInfo),
|
||||||
|
userAvatars: make(map[string]string),
|
||||||
|
}
|
||||||
|
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to WhatsApp. Required implementation of the Bridger interface
|
||||||
|
func (b *Bwhatsapp) Connect() error {
|
||||||
|
device, err := b.getDevice()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
number := b.GetString(cfgNumber)
|
||||||
|
if number == "" {
|
||||||
|
return errors.New("whatsapp's telephone number need to be configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Log.Debugln("Connecting to WhatsApp..")
|
||||||
|
|
||||||
|
b.wc = whatsmeow.NewClient(device, waLog.Stdout("Client", "INFO", true))
|
||||||
|
b.wc.AddEventHandler(b.eventHandler)
|
||||||
|
|
||||||
|
firstlogin := false
|
||||||
|
var qrChan <-chan whatsmeow.QRChannelItem
|
||||||
|
if b.wc.Store.ID == nil {
|
||||||
|
firstlogin = true
|
||||||
|
qrChan, err = b.wc.GetQRChannel(context.Background())
|
||||||
|
if err != nil && !errors.Is(err, whatsmeow.ErrQRStoreContainsID) {
|
||||||
|
return errors.New("failed to to get QR channel:" + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = b.wc.Connect()
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("failed to connect to WhatsApp: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.wc.Store.ID == nil {
|
||||||
|
for evt := range qrChan {
|
||||||
|
if evt.Event == "code" {
|
||||||
|
qrterminal.GenerateHalfBlock(evt.Code, qrterminal.L, os.Stdout)
|
||||||
|
} else {
|
||||||
|
b.Log.Infof("QR channel result: %s", evt.Event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// disconnect and reconnect on our first login/pairing
|
||||||
|
// for some reason the GetJoinedGroups in JoinChannel doesn't work on first login
|
||||||
|
if firstlogin {
|
||||||
|
b.wc.Disconnect()
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
err = b.wc.Connect()
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("failed to connect to WhatsApp: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Log.Infoln("WhatsApp connection successful")
|
||||||
|
|
||||||
|
b.contacts, err = b.wc.Store.Contacts.GetAllContacts()
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("failed to get contacts: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
b.startedAt = time.Now()
|
||||||
|
|
||||||
|
// map all the users
|
||||||
|
for id, contact := range b.contacts {
|
||||||
|
if !isGroupJid(id.String()) && id.String() != "status@broadcast" {
|
||||||
|
// it is user
|
||||||
|
b.users[id.String()] = contact
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get user avatar asynchronously
|
||||||
|
b.Log.Info("Getting user avatars..")
|
||||||
|
|
||||||
|
for jid := range b.users {
|
||||||
|
info, err := b.GetProfilePicThumb(jid)
|
||||||
|
if err != nil {
|
||||||
|
b.Log.Warnf("Could not get profile photo of %s: %v", jid, err)
|
||||||
|
} else {
|
||||||
|
b.Lock()
|
||||||
|
if info != nil {
|
||||||
|
b.userAvatars[jid] = info.URL
|
||||||
|
}
|
||||||
|
b.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Log.Info("Finished getting avatars..")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disconnect is called while reconnecting to the bridge
|
||||||
|
// Required implementation of the Bridger interface
|
||||||
|
func (b *Bwhatsapp) Disconnect() error {
|
||||||
|
b.wc.Disconnect()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// https://github.com/42wim/matterbridge/blob/2cfd880cdb0df29771bf8f31df8d990ab897889d/bridge/bridge.go#L11-L16
|
||||||
|
func (b *Bwhatsapp) JoinChannel(channel config.ChannelInfo) error {
|
||||||
|
byJid := isGroupJid(channel.Name)
|
||||||
|
|
||||||
|
groups, err := b.wc.GetJoinedGroups()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify if we are member of the given group
|
||||||
|
if byJid {
|
||||||
|
gJID, err := types.ParseJID(channel.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, group := range groups {
|
||||||
|
if group.JID == gJID {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foundGroups := []string{}
|
||||||
|
|
||||||
|
for _, group := range groups {
|
||||||
|
if group.Name == channel.Name {
|
||||||
|
foundGroups = append(foundGroups, group.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(foundGroups) {
|
||||||
|
case 0:
|
||||||
|
// didn't match any group - print out possibilites
|
||||||
|
for _, group := range groups {
|
||||||
|
b.Log.Infof("%s %s", group.JID, group.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\"", foundGroups[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, foundGroups)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post a document message from the bridge to WhatsApp
|
||||||
|
func (b *Bwhatsapp) PostDocumentMessage(msg config.Message, filetype string) (string, error) {
|
||||||
|
groupJID, _ := types.ParseJID(msg.Channel)
|
||||||
|
|
||||||
|
fi := msg.Extra["file"][0].(config.FileInfo)
|
||||||
|
|
||||||
|
resp, err := b.wc.Upload(context.Background(), *fi.Data, whatsmeow.MediaDocument)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post document message
|
||||||
|
var message proto.Message
|
||||||
|
|
||||||
|
message.DocumentMessage = &proto.DocumentMessage{
|
||||||
|
Title: &fi.Name,
|
||||||
|
FileName: &fi.Name,
|
||||||
|
Mimetype: &filetype,
|
||||||
|
MediaKey: resp.MediaKey,
|
||||||
|
FileEncSha256: resp.FileEncSHA256,
|
||||||
|
FileSha256: resp.FileSHA256,
|
||||||
|
FileLength: goproto.Uint64(resp.FileLength),
|
||||||
|
Url: &resp.URL,
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Log.Debugf("=> Sending %#v", msg)
|
||||||
|
|
||||||
|
ID := whatsmeow.GenerateMessageID()
|
||||||
|
_, err = b.wc.SendMessage(groupJID, ID, &message)
|
||||||
|
|
||||||
|
return ID, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post an image message from the bridge to WhatsApp
|
||||||
|
// Handle, for sure image/jpeg, image/png and image/gif MIME types
|
||||||
|
func (b *Bwhatsapp) PostImageMessage(msg config.Message, filetype string) (string, error) {
|
||||||
|
groupJID, _ := types.ParseJID(msg.Channel)
|
||||||
|
|
||||||
|
fi := msg.Extra["file"][0].(config.FileInfo)
|
||||||
|
|
||||||
|
caption := msg.Username + fi.Comment
|
||||||
|
|
||||||
|
resp, err := b.wc.Upload(context.Background(), *fi.Data, whatsmeow.MediaImage)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var message proto.Message
|
||||||
|
|
||||||
|
message.ImageMessage = &proto.ImageMessage{
|
||||||
|
Mimetype: &filetype,
|
||||||
|
Caption: &caption,
|
||||||
|
MediaKey: resp.MediaKey,
|
||||||
|
FileEncSha256: resp.FileEncSHA256,
|
||||||
|
FileSha256: resp.FileSHA256,
|
||||||
|
FileLength: goproto.Uint64(resp.FileLength),
|
||||||
|
Url: &resp.URL,
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Log.Debugf("=> Sending %#v", msg)
|
||||||
|
|
||||||
|
ID := whatsmeow.GenerateMessageID()
|
||||||
|
_, err = b.wc.SendMessage(groupJID, ID, &message)
|
||||||
|
|
||||||
|
return ID, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a message from the bridge to WhatsApp
|
||||||
|
func (b *Bwhatsapp) Send(msg config.Message) (string, error) {
|
||||||
|
groupJID, _ := types.ParseJID(msg.Channel)
|
||||||
|
|
||||||
|
b.Log.Debugf("=> Receiving %#v", msg)
|
||||||
|
|
||||||
|
// Delete message
|
||||||
|
if msg.Event == config.EventMsgDelete {
|
||||||
|
if msg.ID == "" {
|
||||||
|
// 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
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := b.wc.RevokeMessage(groupJID, msg.ID)
|
||||||
|
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edit message
|
||||||
|
if msg.ID != "" {
|
||||||
|
b.Log.Debugf("updating message with id %s", msg.ID)
|
||||||
|
|
||||||
|
if b.GetString("editsuffix") != "" {
|
||||||
|
msg.Text += b.GetString("EditSuffix")
|
||||||
|
} else {
|
||||||
|
msg.Text += " (edited)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Upload a file
|
||||||
|
if msg.Extra["file"] != nil {
|
||||||
|
fi := msg.Extra["file"][0].(config.FileInfo)
|
||||||
|
filetype := mime.TypeByExtension(filepath.Ext(fi.Name))
|
||||||
|
|
||||||
|
b.Log.Debugf("Extra file is %#v", filetype)
|
||||||
|
|
||||||
|
// TODO: add different types
|
||||||
|
// TODO: add webp conversion
|
||||||
|
switch filetype {
|
||||||
|
case "image/jpeg", "image/png", "image/gif":
|
||||||
|
return b.PostImageMessage(msg, filetype)
|
||||||
|
default:
|
||||||
|
return b.PostDocumentMessage(msg, filetype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
text := msg.Username + msg.Text
|
||||||
|
|
||||||
|
var message proto.Message
|
||||||
|
|
||||||
|
message.Conversation = &text
|
||||||
|
|
||||||
|
ID := whatsmeow.GenerateMessageID()
|
||||||
|
_, err := b.wc.SendMessage(groupJID, ID, &message)
|
||||||
|
|
||||||
|
return ID, err
|
||||||
|
}
|
||||||
@@ -128,7 +128,6 @@ func (b *Bxmpp) Send(msg config.Message) (string, error) {
|
|||||||
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
|
||||||
}
|
}
|
||||||
b.Log.Debugf("=> Sending message %#v", msg)
|
b.Log.Debugf("=> Sending message %#v", msg)
|
||||||
@@ -169,11 +168,21 @@ func (b *Bxmpp) postSlackCompatibleWebhook(msg config.Message) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bxmpp) createXMPP() error {
|
func (b *Bxmpp) createXMPP() error {
|
||||||
if !strings.Contains(b.GetString("Jid"), "@") {
|
var serverName string
|
||||||
return fmt.Errorf("the Jid %s doesn't contain an @", b.GetString("Jid"))
|
switch {
|
||||||
|
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: strings.Split(b.GetString("Jid"), "@")[1],
|
ServerName: serverName,
|
||||||
InsecureSkipVerify: b.GetBool("SkipTLSVerify"), // nolint: gosec
|
InsecureSkipVerify: b.GetBool("SkipTLSVerify"), // nolint: gosec
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,7 +283,13 @@ func (b *Bxmpp) handleXMPP() error {
|
|||||||
for {
|
for {
|
||||||
m, err := b.xc.Recv()
|
m, err := b.xc.Recv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
// An error together with AvatarData is non-fatal
|
||||||
|
switch m.(type) {
|
||||||
|
case xmpp.AvatarData:
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := m.(type) {
|
switch v := m.(type) {
|
||||||
@@ -385,7 +400,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) > 0 {
|
if len(s) > 1 {
|
||||||
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
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package bzulip
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -11,6 +12,7 @@ import (
|
|||||||
"github.com/42wim/matterbridge/bridge"
|
"github.com/42wim/matterbridge/bridge"
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
|
"github.com/42wim/matterbridge/version"
|
||||||
gzb "github.com/matterbridge/gozulipbot"
|
gzb "github.com/matterbridge/gozulipbot"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,7 +29,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")}
|
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.Init()
|
bot.Init()
|
||||||
q, err := bot.RegisterAll()
|
q, err := bot.RegisterAll()
|
||||||
b.q = q
|
b.q = q
|
||||||
@@ -125,6 +127,7 @@ 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
|
||||||
|
|||||||
193
changelog.md
@@ -1,3 +1,196 @@
|
|||||||
|
# v1.25.1
|
||||||
|
|
||||||
|
## Enhancements
|
||||||
|
|
||||||
|
- matrix: Add KeepQuotedReply option for matrix to fix regression (#1823)
|
||||||
|
- slack: Improve Slack attachments formatting (slack) (#1807)
|
||||||
|
|
||||||
|
## Bugfix
|
||||||
|
|
||||||
|
- general: Update dependencies (#1813,#1822,#1833)
|
||||||
|
- mattermost: Add space between filename and URL (mattermost). Fixes #1820
|
||||||
|
- matrix: Update matterbridge/gomatrix. Fixes #1772 (#1803)
|
||||||
|
- telegram: Do not modify .webm files (telegram). Fixes #17**88 (#1802)
|
||||||
|
- telegram: Do not apply any markup to URL entities (telegram) (#1808)
|
||||||
|
- telegram: Fix telegram message deletion request (#1818)
|
||||||
|
- vk: Fix UploadMessagesPhoto for vk community chat (vk) (#1812)
|
||||||
|
|
||||||
|
This release couldn't exist without the following contributors:
|
||||||
|
@bd808, @chugunov, @sas1024, @SevereCloud, @ValdikSS
|
||||||
|
|
||||||
|
# v1.25.0
|
||||||
|
|
||||||
|
## Breaking changes
|
||||||
|
|
||||||
|
- whatsapp: deprecated, the library <https://github.com/Rhymen/go-whatsapp> isn't maintained anymore.
|
||||||
|
We're switching to <https://github.com/tulir/whatsmeow> but as this uses a GPL3 licensed library we can't provide you with binaries.
|
||||||
|
You'll need to build it yourself. More information about this can be found here: <https://github.com/42wim/matterbridge#building-with-whatsapp-beta-multidevice-support>
|
||||||
|
|
||||||
|
## New features
|
||||||
|
|
||||||
|
- whatsappmulti: whatsapp multidevice support added - more info <https://github.com/42wim/matterbridge#building-with-whatsapp-beta-multidevice-support>
|
||||||
|
- general: Add Dockerfile_whatsappmulti for building with WhatsApp Multi-Device support (Whatsmeow) (#1774)
|
||||||
|
- telegram: Add UseFullName option (telegram) (#1777)
|
||||||
|
- slack: Use slack real name as user name (slack) (#1775)
|
||||||
|
|
||||||
|
## Enhancements
|
||||||
|
|
||||||
|
- general: Ignore sending file with comment, if comment contains IgnoreMessages value (#1783)
|
||||||
|
- general: Update dependencies (#1784)
|
||||||
|
- irc: Update lrstanley/girc dep (#1773)
|
||||||
|
- slack: Preserve threading for messages with files (slack) (#1781)
|
||||||
|
- telegram: Preserve threading from telegram replies (telegram) (#1776)
|
||||||
|
- telegram: Multiple media in one message (telegram) (#1779)
|
||||||
|
- whatsapp: Add whatsapp deprecation warning (#1792)
|
||||||
|
|
||||||
|
## Bugfix
|
||||||
|
|
||||||
|
- discord: Change discord non-native threading behaviour (discord) (#1791)
|
||||||
|
|
||||||
|
This release couldn't exist without the following contributors:
|
||||||
|
@sas1024, @tpxtron
|
||||||
|
|
||||||
|
# v1.24.1
|
||||||
|
|
||||||
|
## Enhancements
|
||||||
|
|
||||||
|
- discord: Switch to discordgo upstream again (#1759)
|
||||||
|
- general: Update dependencies and vendor (#1761)
|
||||||
|
- general: Create inmessage-logger.tengo (#1688) (#1747)
|
||||||
|
- general: Add OpenRC service file (#1746)
|
||||||
|
- irc: Refactor utf-8 conversion (irc) (#1767)
|
||||||
|
|
||||||
|
## Bugfixes
|
||||||
|
|
||||||
|
- irc: Fix panic in irc. Closes #1751 (#1760)
|
||||||
|
- mumble: Implement a workaround to signal Opus support (mumble) (#1764)
|
||||||
|
- telegram: Fix for complex-formatted Telegram text (#1765)
|
||||||
|
- telegram: Fix Telegram channel title in forwards (#1753)
|
||||||
|
- telegram: Fix Telegram Problem (unforwarded formatting and skipping of linebreaks) (#1749)
|
||||||
|
|
||||||
|
This release couldn't exist without the following contributors:
|
||||||
|
@s3lph, @ValdikSS, @reckel-jm, @CyberTailor
|
||||||
|
|
||||||
|
# v1.24.0
|
||||||
|
|
||||||
|
## New features
|
||||||
|
|
||||||
|
- harmony: new protocol added: Add support for Harmony (#1656)
|
||||||
|
- irc: Allow binding to IP on IRC (#1640)
|
||||||
|
- irc: Add support for client certificate (irc) (#1710)
|
||||||
|
- mattermost: Add UseUsername option (mattermost). Fixes #1665 (#1714)
|
||||||
|
- mattermost: Add support for using ID in channel config (mattermost) (#1715)
|
||||||
|
- matrix: Reply support for Matrix (#1664)
|
||||||
|
- telegram: Add Telegram Bot Command /chatId (telegram) (#1703)
|
||||||
|
|
||||||
|
## Enhancements
|
||||||
|
|
||||||
|
- general: Update dependencies/vendor (#1659)
|
||||||
|
- discord: Add more debug options for discord (#1712)
|
||||||
|
- docker: Use Alpine stable again in Dockerfile (#1643)
|
||||||
|
- mattermost: Log eventtype in debug (mattermost) (#1676)
|
||||||
|
- mattermost: Add more ignore debug messages (mattermost) (#1678)
|
||||||
|
- slack: Add support for deleting files from slack to discord. Fixes #1705 (#1709)
|
||||||
|
- telegram: Add support for code blocks in telegram (#1650)
|
||||||
|
- telegram: Update telegram-bot-api to v5 (#1660)
|
||||||
|
- telegram: Add comments to messages (telegram) (#1652)
|
||||||
|
- telegram: Add support for sender_chat (telegram) (#1677)
|
||||||
|
- vk: Remove GroupID (vk) (#1668)
|
||||||
|
|
||||||
|
## Bugfix
|
||||||
|
|
||||||
|
- mattermost: Use current parentID if rootId is not set (mattermost) (#1675)
|
||||||
|
- matrix: Make HTMLDisable work correct (matrix) (#1716)
|
||||||
|
- whatsapp: Make EditSuffix option actually work (whatsapp). Fixes #1510 (#1728)
|
||||||
|
|
||||||
|
This release couldn't exist without the following contributors:
|
||||||
|
@DavyJohnesev, @GoliathLabs, @pontaoski, @PeGaSuS-Coder, @dependabot[bot], @vpzomtrrfrt, @SevereCloud, @soloam, @YashRE42, @danwalmsley, @SuperSandro2000, @inzanity
|
||||||
|
|
||||||
|
# v1.23.2
|
||||||
|
|
||||||
|
If you're running whatsapp you should update.
|
||||||
|
|
||||||
|
## Bugfix
|
||||||
|
|
||||||
|
- whatsapp: Update go-whatsapp version (#1630)
|
||||||
|
|
||||||
|
This release couldn't exist without the following contributors:
|
||||||
|
@snikpic
|
||||||
|
|
||||||
|
# v1.23.1
|
||||||
|
|
||||||
|
If you're running mattermost 6 you should update.
|
||||||
|
|
||||||
|
## Bugfix
|
||||||
|
|
||||||
|
- mattermost: Do not check cache on deleted messages (mattermost). Fixes #1555 (#1624)
|
||||||
|
- mattermost: Fix crash on users updating info. Update matterclient dep. Fixes #1617
|
||||||
|
- matrix: Keep the logger on a disabled bridge. Fixes #1616 (#1621)
|
||||||
|
- msteams: Fix panic in msteams. Fixes #1588 (#1622)
|
||||||
|
- xmpp: Do not fail on no avatar data (xmpp) #1529 (#1627)
|
||||||
|
- xmpp: Use a new msgID when replacing messages (xmpp). Fixes #1584 (#1623)
|
||||||
|
- zulip: Add better error handling on Zulip (#1589)
|
||||||
|
|
||||||
|
This release couldn't exist without the following contributors:
|
||||||
|
@Polynomdivision, @minecraftchest1, @alexmv
|
||||||
|
|
||||||
|
# v1.23.0
|
||||||
|
|
||||||
|
## New features
|
||||||
|
|
||||||
|
- irc: Add UserName and RealName options for IRC (#1590)
|
||||||
|
- mattermost: Add support for mattermost v6
|
||||||
|
- nctalk: Add support for separate display name (nctalk) (#1506)
|
||||||
|
- xmpp: Add support for anonymous connection (xmpp) (#1548)
|
||||||
|
|
||||||
|
## Enhancements
|
||||||
|
|
||||||
|
- general: Update vendored libraries
|
||||||
|
- docker: Use github actions to build dockerhub/ghcr.io images
|
||||||
|
- docker: Update GH actions to multi arch (arm64) (#1614)
|
||||||
|
- telegram: Convert .tgs with go libraries (and cgo) (telegram) (#1569)
|
||||||
|
|
||||||
|
## Bugfix
|
||||||
|
|
||||||
|
- mumble: Remove newline character in bridge multiline messages (mumble) (#1572)
|
||||||
|
- slack: Add space before file upload comment (slack) (#1554)
|
||||||
|
- slack: Invalidate user in cache on user change event (#1604)
|
||||||
|
- xmpp: Fix XMPP parseNick function (#1547)
|
||||||
|
|
||||||
|
This release couldn't exist without the following contributors:
|
||||||
|
@powerjungle, @gary-kim, @KingPin, @Benau, @keenan-v1, @tytan652, @KidA001,@minecraftchest1, @irydacea
|
||||||
|
|
||||||
|
# v1.22.3
|
||||||
|
|
||||||
|
## Bugfixes
|
||||||
|
|
||||||
|
- whatsapp: Update Rhymen/go-whatsapp module to latest master (2b8a3e9b8aa2) (#1518)
|
||||||
|
|
||||||
|
This release couldn't exist without the following contributors:
|
||||||
|
@nathanaelhoun
|
||||||
|
|
||||||
|
# v1.22.2
|
||||||
|
|
||||||
|
## Enhancements
|
||||||
|
|
||||||
|
- general: Add a MessageClipped option to set your own clipped message. Closes #1359 (#1487)
|
||||||
|
- discord: Add AllowMention to restrict allowed mentions (#1462)
|
||||||
|
- matrix: Add MxId/Token login option for Matrix (#1438)
|
||||||
|
- nctalk: Support sending file URLs (nctalk) (#1489)
|
||||||
|
- nctalk: Add support for message deletion (nctalk) (#1492)
|
||||||
|
- whatsapp: Handle document messages (whatsapp) (#1475)
|
||||||
|
|
||||||
|
## Bugfixes
|
||||||
|
|
||||||
|
- general: Update vendored libs
|
||||||
|
- matrix: Fix content body issue for redactions (matrix) (#1496)
|
||||||
|
- telegram: Add libwebp-dev to tgs.Dockerfile fixes Telegram sticker to WebP rendering (#1476)
|
||||||
|
- whatsapp: Rename .jpe files to .jpg Fixes #1463 (whatsapp) (#1485)
|
||||||
|
- whatsapp: Fix crash on encountering VideoMessage (whatsapp) (#1483)
|
||||||
|
|
||||||
|
This release couldn't exist without the following contributors:
|
||||||
|
@AvinashReddy3108, @chrisbobbe, @jaywink, @Funatiker, @computeronix, @alexandregv, @gary-kim, @SuperSandro2000
|
||||||
|
|
||||||
# v1.22.1
|
# v1.22.1
|
||||||
|
|
||||||
## Enhancements
|
## Enhancements
|
||||||
|
|||||||
15
contrib/inmessage-logger.tengo
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
19
contrib/matterbridge.openrc
Executable file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/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,9 +1,10 @@
|
|||||||
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
|
||||||
ADD https://github.com/42wim/matterbridge/releases/download/v${VERSION}/matterbridge-linux-arm /bin/matterbridge
|
COPY --from=certs /bin/matterbridge /bin/matterbridge
|
||||||
RUN chmod +x /bin/matterbridge
|
|
||||||
ENTRYPOINT ["/bin/matterbridge"]
|
ENTRYPOINT ["/bin/matterbridge"]
|
||||||
|
|||||||
12
gateway/bridgemap/bharmony.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
//go:build !noharmony
|
||||||
|
// +build !noharmony
|
||||||
|
|
||||||
|
package bridgemap
|
||||||
|
|
||||||
|
import (
|
||||||
|
bharmony "github.com/42wim/matterbridge/bridge/harmony"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
FullMap["harmony"] = bharmony.New
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
// +build !nowhatsapp
|
// +build !nowhatsapp
|
||||||
|
// +build !whatsappmulti
|
||||||
|
|
||||||
package bridgemap
|
package bridgemap
|
||||||
|
|
||||||
|
|||||||
11
gateway/bridgemap/bwhatsappmulti.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// +build whatsappmulti
|
||||||
|
|
||||||
|
package bridgemap
|
||||||
|
|
||||||
|
import (
|
||||||
|
bwhatsapp "github.com/42wim/matterbridge/bridge/whatsappmulti"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
FullMap["whatsapp"] = bwhatsapp.New
|
||||||
|
}
|
||||||
@@ -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 mID
|
return ID
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 strings.Replace(mid.(string), protocol+" ", "", 1)
|
return mid.(string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,13 +299,30 @@ func (gw *Gateway) ignoreMessage(msg *config.Message) bool {
|
|||||||
|
|
||||||
igNicks := strings.Fields(gw.Bridges[msg.Account].GetString("IgnoreNicks"))
|
igNicks := strings.Fields(gw.Bridges[msg.Account].GetString("IgnoreNicks"))
|
||||||
igMessages := strings.Fields(gw.Bridges[msg.Account].GetString("IgnoreMessages"))
|
igMessages := strings.Fields(gw.Bridges[msg.Account].GetString("IgnoreMessages"))
|
||||||
if gw.ignoreTextEmpty(msg) || gw.ignoreText(msg.Username, igNicks) || gw.ignoreText(msg.Text, igMessages) {
|
if gw.ignoreTextEmpty(msg) || gw.ignoreText(msg.Username, igNicks) || gw.ignoreText(msg.Text, igMessages) || gw.ignoreFilesComment(msg.Extra, igMessages) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ignoreFilesComment returns true if we need to ignore a file with matched comment.
|
||||||
|
func (gw *Gateway) ignoreFilesComment(extra map[string][]interface{}, igMessages []string) bool {
|
||||||
|
if extra == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, f := range extra["file"] {
|
||||||
|
fi, ok := f.(config.FileInfo)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if gw.ignoreText(fi.Comment, igMessages) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (gw *Gateway) modifyUsername(msg *config.Message, dest *bridge.Bridge) string {
|
func (gw *Gateway) modifyUsername(msg *config.Message, dest *bridge.Bridge) string {
|
||||||
if dest.GetBool("StripNick") {
|
if dest.GetBool("StripNick") {
|
||||||
re := regexp.MustCompile("[^a-zA-Z0-9]+")
|
re := regexp.MustCompile("[^a-zA-Z0-9]+")
|
||||||
@@ -447,16 +464,19 @@ 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)
|
||||||
|
|
||||||
msg.ID = gw.getDestMsgID(rmsg.Protocol+" "+rmsg.ID, dest, channel)
|
// exclude file delete event as the msg ID here is the native file ID that needs to be deleted
|
||||||
|
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(rmsg.Protocol+" "+canonicalParentMsgID, dest, channel)
|
msg.ParentID = gw.getDestMsgID(canonicalParentMsgID, dest, channel)
|
||||||
if msg.ParentID == "" {
|
if msg.ParentID == "" {
|
||||||
msg.ParentID = canonicalParentMsgID
|
msg.ParentID = strings.Replace(canonicalParentMsgID, dest.Protocol+" ", "", 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
|||||||
@@ -110,7 +110,9 @@ 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
|
||||||
|
|||||||
178
go.mod
@@ -3,59 +3,161 @@ 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/Jeffail/gabs v1.4.0 // indirect
|
github.com/Benau/tgsconverter v0.0.0-20210809170556-99f4a4f6337f
|
||||||
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.20210126174449-3c094ebae0ce
|
github.com/Rhymen/go-whatsapp v0.1.2-0.20211102134409-31a2e740845c
|
||||||
github.com/SevereCloud/vksdk/v2 v2.9.0
|
github.com/SevereCloud/vksdk/v2 v2.14.0
|
||||||
github.com/d5/tengo/v2 v2.7.0
|
github.com/bwmarrin/discordgo v0.25.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.4.9
|
github.com/fsnotify/fsnotify v1.5.4
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api v1.0.1-0.20200524105306-7434b0456e81
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
||||||
github.com/gomarkdown/markdown v0.0.0-20210208175418-bda154fe17d8
|
github.com/gomarkdown/markdown v0.0.0-20220509074759-a57bf950ab8c
|
||||||
github.com/google/gops v0.3.17
|
github.com/google/gops v0.3.23
|
||||||
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.4.2
|
github.com/gorilla/websocket v1.5.0
|
||||||
|
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-20200505163032-5cacf52379da
|
github.com/keybase/go-keybase-chat-bot v0.0.0-20220322223021-75d497527469
|
||||||
github.com/kyokomi/emoji/v2 v2.2.8
|
github.com/kyokomi/emoji/v2 v2.2.9
|
||||||
github.com/labstack/echo/v4 v4.2.1
|
github.com/labstack/echo/v4 v4.7.2
|
||||||
github.com/lrstanley/girc v0.0.0-20190801035559-4fc93959e1a7
|
github.com/lrstanley/girc v0.0.0-20220507183218-96757fe3d2a2
|
||||||
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16
|
github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20211016222428-79310a412696
|
||||||
github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20210403163225-761e8622445d
|
github.com/matterbridge/go-xmpp v0.0.0-20211030125215-791a06c5f1be
|
||||||
github.com/matterbridge/discordgo v0.21.2-0.20210201201054-fb39a175b4f7
|
github.com/matterbridge/gomatrix v0.0.0-20220411225302-271e5088ea27
|
||||||
github.com/matterbridge/go-xmpp v0.0.0-20200418225040-c8a3a57b4050
|
github.com/matterbridge/gozulipbot v0.0.0-20211023205727-a19d6c1f3b75
|
||||||
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/mattermost/mattermost-server/v5 v5.30.1
|
github.com/matterbridge/matterclient v0.0.0-20220430213656-07aca2731bc9
|
||||||
|
github.com/mattermost/mattermost-server/v5 v5.39.3
|
||||||
|
github.com/mattermost/mattermost-server/v6 v6.6.1
|
||||||
github.com/mattn/godown v0.0.1
|
github.com/mattn/godown v0.0.1
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
github.com/mdp/qrterminal v1.0.1
|
||||||
github.com/missdeer/golib v1.0.4
|
github.com/nelsonken/gomf v0.0.0-20190423072027-c65cc0469e94
|
||||||
github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d // indirect
|
|
||||||
github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff // indirect
|
|
||||||
github.com/nelsonken/gomf v0.0.0-20180504123937-a9dd2f9deae9
|
|
||||||
github.com/paulrosania/go-charset v0.0.0-20190326053356-55c9d7a5834c
|
github.com/paulrosania/go-charset v0.0.0-20190326053356-55c9d7a5834c
|
||||||
github.com/rs/xid v1.3.0
|
github.com/rs/xid v1.4.0
|
||||||
github.com/russross/blackfriday v1.6.0
|
github.com/russross/blackfriday v1.6.0
|
||||||
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.8.1
|
||||||
github.com/slack-go/slack v0.8.2
|
github.com/slack-go/slack v0.10.3
|
||||||
github.com/spf13/afero v1.3.4 // indirect
|
github.com/spf13/viper v1.11.0
|
||||||
github.com/spf13/cast v1.3.1 // indirect
|
github.com/stretchr/testify v1.7.1
|
||||||
github.com/spf13/viper v1.7.1
|
github.com/vincent-petithory/dataurl v1.0.0
|
||||||
github.com/stretchr/testify v1.7.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-20220330170733-19bcea1b6289
|
||||||
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb
|
go.mau.fi/whatsmeow v0.0.0-20220504135614-f1f2a9d231fb
|
||||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602
|
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9
|
||||||
gomod.garykim.dev/nc-talk v0.1.7
|
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5
|
||||||
|
golang.org/x/text v0.3.7
|
||||||
|
gomod.garykim.dev/nc-talk v0.3.0
|
||||||
|
google.golang.org/protobuf v1.28.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
|
||||||
|
modernc.org/sqlite v1.17.2
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.15
|
require (
|
||||||
|
filippo.io/edwards25519 v1.0.0-rc.1 // indirect
|
||||||
|
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/graph-gophers/graphql-go v1.3.0 // 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/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
|
github.com/kettek/apng v0.0.0-20191108220231-414630eed80f // indirect
|
||||||
|
github.com/klauspost/compress v1.15.1 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.11 // indirect
|
||||||
|
github.com/labstack/gommon v0.3.1 // indirect
|
||||||
|
github.com/magiconair/properties v1.8.6 // 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.23 // 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/opentracing/opentracing-go v1.2.0 // indirect
|
||||||
|
github.com/pborman/uuid v1.2.1 // indirect
|
||||||
|
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.0-beta.8 // 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/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // 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-20190110000554-dc11ecdae0a9 // indirect
|
||||||
|
github.com/spf13/afero v1.8.2 // 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.mau.fi/libsignal v0.0.0-20220425070825-c40c839ee6a0 // 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-20220411220226-7b82a4e95df4 // indirect
|
||||||
|
golang.org/x/mod v0.5.1 // indirect
|
||||||
|
golang.org/x/net v0.0.0-20220412020605-290c469a71a5 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||||
|
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect
|
||||||
|
golang.org/x/tools v0.1.9 // indirect
|
||||||
|
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
|
||||||
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.66.4 // 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
|
||||||
|
lukechampine.com/uint128 v1.1.1 // indirect
|
||||||
|
modernc.org/cc/v3 v3.36.0 // indirect
|
||||||
|
modernc.org/ccgo/v3 v3.16.6 // indirect
|
||||||
|
modernc.org/libc v1.16.7 // indirect
|
||||||
|
modernc.org/mathutil v1.4.1 // indirect
|
||||||
|
modernc.org/memory v1.1.1 // indirect
|
||||||
|
modernc.org/opt v0.1.1 // indirect
|
||||||
|
modernc.org/strutil v1.1.1 // indirect
|
||||||
|
modernc.org/token v1.0.0 // indirect
|
||||||
|
rsc.io/qr v0.2.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
//replace github.com/matrix-org/gomatrix => github.com/matterbridge/gomatrix v0.0.0-20220205235239-607eb9ee6419
|
||||||
|
|
||||||
|
go 1.17
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Message for rocketchat outgoing webhook.
|
// Message for rocketchat outgoing webhook.
|
||||||
@@ -68,7 +69,6 @@ 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,7 +89,11 @@ 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 {
|
||||||
log.Println("invalid token " + msg.Token + " from " + r.RemoteAddr)
|
if regexp.MustCompile(`[^a-zA-Z0-9]+`).MatchString(msg.Token) {
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 270 KiB |
|
Before Width: | Height: | Size: 170 KiB |
|
Before Width: | Height: | Size: 282 KiB |
|
Before Width: | Height: | Size: 204 KiB |
|
Before Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 62 KiB |
@@ -10,15 +10,13 @@ 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.22.1"
|
|
||||||
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")
|
||||||
@@ -28,7 +26,7 @@ var (
|
|||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if *flagVersion {
|
if *flagVersion {
|
||||||
fmt.Printf("version: %s %s\n", version, githash)
|
fmt.Printf("version: %s %s\n", version.Release, version.GitHash)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,8 +41,8 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Printf("Running version %s %s", version, githash)
|
logger.Printf("Running version %s %s", version.Release, version.GitHash)
|
||||||
if strings.Contains(version, "-dev") {
|
if strings.Contains(version.Release, "-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.freenode]
|
#In this example we use [irc.libera]
|
||||||
#REQUIRED
|
#REQUIRED
|
||||||
[irc.freenode]
|
[irc.libera]
|
||||||
#irc server to connect to.
|
#irc server to connect to.
|
||||||
#REQUIRED
|
#REQUIRED
|
||||||
Server="irc.freenode.net:6667"
|
Server="irc.libera.chat:6667"
|
||||||
|
|
||||||
#Password for irc server (if necessary)
|
#Password for irc server (if necessary)
|
||||||
#OPTIONAL (default "")
|
#OPTIONAL (default "")
|
||||||
@@ -24,7 +24,14 @@ Password=""
|
|||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
UseTLS=false
|
UseTLS=false
|
||||||
|
|
||||||
#Enable SASL (PLAIN) authentication. (freenode requires this from eg AWS hosts)
|
#Use client certificate - see CertFP https://libera.chat/guides/certfp.html
|
||||||
|
#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
|
||||||
@@ -34,6 +41,11 @@ 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",
|
||||||
@@ -55,7 +67,15 @@ Charset=""
|
|||||||
#REQUIRED
|
#REQUIRED
|
||||||
Nick="matterbot"
|
Nick="matterbot"
|
||||||
|
|
||||||
#If you registered your bot with a service like Nickserv on freenode.
|
#Real name/gecos displayed in e.g. /WHOIS and /WHO
|
||||||
|
#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"
|
||||||
@@ -76,20 +96,24 @@ 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.
|
||||||
#<message clipped> will be add to the message that fills the queue.
|
#<clipped message> 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
|
||||||
#<message clipped> will be add to the message.
|
#<clipped message> will be add to the message.
|
||||||
#OPTIONAL (default 400)
|
#OPTIONAL (default 400)
|
||||||
MessageLength=400
|
MessageLength=400
|
||||||
|
|
||||||
#Split messages on MessageLength instead of showing the <message clipped>
|
#Split messages on MessageLength instead of showing the <clipped message>
|
||||||
#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
|
||||||
@@ -219,12 +243,16 @@ 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
|
#REQUIRED if Anonymous=false
|
||||||
Jid="user@example.com"
|
Jid="user@example.com"
|
||||||
|
|
||||||
#Password
|
#Password
|
||||||
#REQUIRED
|
#REQUIRED if Anonymous=false
|
||||||
Password="yourpass"
|
Password="yourpass"
|
||||||
|
|
||||||
#MUC
|
#MUC
|
||||||
@@ -380,6 +408,10 @@ 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)
|
||||||
@@ -826,6 +858,14 @@ 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>"
|
||||||
|
|
||||||
|
#If enabled use the slack "Real Name" as username.
|
||||||
|
#OPTIONAL (default false)
|
||||||
|
UseFullName=false
|
||||||
|
|
||||||
###################################################################
|
###################################################################
|
||||||
#discord section
|
#discord section
|
||||||
###################################################################
|
###################################################################
|
||||||
@@ -848,6 +888,14 @@ 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
|
||||||
|
|
||||||
@@ -953,6 +1001,10 @@ 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
|
||||||
###################################################################
|
###################################################################
|
||||||
@@ -988,6 +1040,12 @@ DisableWebPagePreview=false
|
|||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
UseFirstName=false
|
UseFirstName=false
|
||||||
|
|
||||||
|
#If enabled use the "Full Name" as username. If this is empty use the Username
|
||||||
|
#If disabled use the "Username" as username. If this is empty use the First Name and Last Name as Full Name
|
||||||
|
#If all names are empty, username will be "unknown"
|
||||||
|
#OPTIONAL (default false)
|
||||||
|
UseFullName=false
|
||||||
|
|
||||||
#WARNING! If enabled this will relay GIF/stickers/documents and other attachments as URLs
|
#WARNING! If enabled this will relay GIF/stickers/documents and other attachments as URLs
|
||||||
#Those URLs will contain your bot-token. This may not be what you want.
|
#Those URLs will contain your bot-token. This may not be what you want.
|
||||||
#For now there is no secure way to relay GIF/stickers/documents without seeing your token.
|
#For now there is no secure way to relay GIF/stickers/documents without seeing your token.
|
||||||
@@ -1011,6 +1069,13 @@ 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
|
||||||
@@ -1086,6 +1151,12 @@ StripNick=false
|
|||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
ShowTopicChange=false
|
ShowTopicChange=false
|
||||||
|
|
||||||
|
#Opportunistically preserve threaded replies between Telegram groups.
|
||||||
|
#This only works if the parent message is still in the cache.
|
||||||
|
#Cache is flushed between restarts.
|
||||||
|
#OPTIONAL (default false)
|
||||||
|
PreserveThreading=false
|
||||||
|
|
||||||
###################################################################
|
###################################################################
|
||||||
#rocketchat section
|
#rocketchat section
|
||||||
###################################################################
|
###################################################################
|
||||||
@@ -1230,12 +1301,16 @@ ShowTopicChange=false
|
|||||||
#REQUIRED
|
#REQUIRED
|
||||||
Server="https://matrix.org"
|
Server="https://matrix.org"
|
||||||
|
|
||||||
#login/pass of your bot.
|
#Authentication for 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)
|
||||||
@@ -1253,12 +1328,14 @@ 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.
|
# Matrix quotes replies and as of matterbridge 1.24.0 we strip those as this causes
|
||||||
#Useful if username overrides for incoming webhooks isn't enabled on the
|
# issues with bridges support threading and have PreserveThreading enabled.
|
||||||
#matrix server. If you set PrefixMessagesWithNick to true, each message
|
# But if you for example use mattermost or discord with webhooks you'll need to enable
|
||||||
#from bridge to matrix will by default be prefixed by the RemoteNickFormat setting. i
|
# this (and keep PreserveThreading disabled) if you want something that looks like a reply from matrix.
|
||||||
#OPTIONAL (default false)
|
# See issues:
|
||||||
PrefixMessagesWithNick=false
|
# - https://github.com/42wim/matterbridge/issues/1819
|
||||||
|
# - https://github.com/42wim/matterbridge/issues/1780
|
||||||
|
KeepQuotedReply=false
|
||||||
|
|
||||||
#Nicks you want to ignore.
|
#Nicks you want to ignore.
|
||||||
#Regular expressions supported
|
#Regular expressions supported
|
||||||
@@ -1416,9 +1493,7 @@ StripNick=false
|
|||||||
ShowTopicChange=false
|
ShowTopicChange=false
|
||||||
|
|
||||||
###################################################################
|
###################################################################
|
||||||
#
|
|
||||||
# NCTalk (Nextcloud Talk)
|
# NCTalk (Nextcloud Talk)
|
||||||
#
|
|
||||||
###################################################################
|
###################################################################
|
||||||
|
|
||||||
[nctalk.bridge]
|
[nctalk.bridge]
|
||||||
@@ -1440,10 +1515,11 @@ 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]
|
||||||
@@ -1486,22 +1562,21 @@ 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
|
||||||
###################################################################
|
###################################################################
|
||||||
|
#
|
||||||
[vk.myvk]
|
[vk.myvk]
|
||||||
#Group access token
|
#Group access token
|
||||||
#See https://vk.com/dev/bots_docs
|
#See https://vk.com/dev/bots_docs
|
||||||
Token="Yourtokenhere"
|
Token="Yourtokenhere"
|
||||||
|
|
||||||
#Group ID
|
|
||||||
#For example in URL https://vk.com/public168963511 group ID is 168963511
|
|
||||||
GroupID=123456789
|
|
||||||
|
|
||||||
###################################################################
|
###################################################################
|
||||||
#
|
|
||||||
# WhatsApp
|
# WhatsApp
|
||||||
#
|
|
||||||
###################################################################
|
###################################################################
|
||||||
|
|
||||||
[whatsapp.bridge]
|
[whatsapp.bridge]
|
||||||
@@ -1528,9 +1603,7 @@ Label="Organization"
|
|||||||
|
|
||||||
|
|
||||||
###################################################################
|
###################################################################
|
||||||
#
|
|
||||||
# zulip
|
# zulip
|
||||||
#
|
|
||||||
###################################################################
|
###################################################################
|
||||||
|
|
||||||
[zulip]
|
[zulip]
|
||||||
@@ -1619,6 +1692,18 @@ 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
|
||||||
###################################################################
|
###################################################################
|
||||||
@@ -1664,6 +1749,7 @@ RemoteNickFormat="{NICK}"
|
|||||||
|
|
||||||
#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.
|
||||||
|
#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 "{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
|
||||||
@@ -1690,7 +1776,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-%5Badvanced%5D
|
#More information https://github.com/42wim/matterbridge/wiki/Mediaserver-setup-%28advanced%29
|
||||||
#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)
|
||||||
@@ -1819,7 +1905,7 @@ enable=true
|
|||||||
|
|
||||||
# account specified above
|
# account specified above
|
||||||
# REQUIRED
|
# REQUIRED
|
||||||
account="irc.freenode"
|
account="irc.libera"
|
||||||
|
|
||||||
# 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
|
||||||
@@ -1836,7 +1922,8 @@ enable=true
|
|||||||
# -------------------------------------------------------------------------------------------------------------------------------------
|
# -------------------------------------------------------------------------------------------------------------------------------------
|
||||||
# irc | channel | #general | The # symbol is required and should be lowercase!
|
# irc | channel | #general | The # symbol is required and should be lowercase!
|
||||||
# -------------------------------------------------------------------------------------------------------------------------------------
|
# -------------------------------------------------------------------------------------------------------------------------------------
|
||||||
# mattermost | channel | general | This is the channel name as seen in the URL, not the display name
|
# | 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
|
||||||
# -------------------------------------------------------------------------------------------------------------------------------------
|
# -------------------------------------------------------------------------------------------------------------------------------------
|
||||||
@@ -1855,14 +1942,14 @@ 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
|
# 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/off-topic:food | Do not use the # when specifying a topic
|
# zulip | stream/topic:topic | general/topic:food | Do not use the # when specifying a topic
|
||||||
# -------------------------------------------------------------------------------------------------------------------------------------
|
# -------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -1877,7 +1964,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.freenode"
|
account="irc.libera"
|
||||||
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
|
||||||
@@ -1911,6 +1998,10 @@ enable=true
|
|||||||
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 {
|
if ok, _ := m.lruCache.ContainsOrAdd(digestString(rmsg.Raw.Data["post"].(string)), true); ok && rmsg.Raw.Event != model.WEBSOCKET_EVENT_POST_DELETED {
|
||||||
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, "")
|
res, resp := m.Client.GetPostsForChannel(channelId, 0, limit, "", true)
|
||||||
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)
|
res, resp := m.Client.GetPostsSince(channelId, time, true)
|
||||||
if resp.Error != nil {
|
if resp.Error != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
FROM alpine AS builder
|
FROM alpine AS builder
|
||||||
|
|
||||||
COPY . /go/src/github.com/42wim/matterbridge
|
COPY . /go/src/matterbridge
|
||||||
RUN apk add \
|
RUN apk add \
|
||||||
go \
|
go \
|
||||||
git \
|
git \
|
||||||
gcc \
|
&& cd /go/src/matterbridge \
|
||||||
musl-dev \
|
&& 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
|
||||||
&& 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 \
|
||||||
|
|||||||
27
vendor/filippo.io/edwards25519/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
14
vendor/filippo.io/edwards25519/README.md
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# filippo.io/edwards25519
|
||||||
|
|
||||||
|
```
|
||||||
|
import "filippo.io/edwards25519"
|
||||||
|
```
|
||||||
|
|
||||||
|
This library implements the edwards25519 elliptic curve, exposing the necessary APIs to build a wide array of higher-level primitives.
|
||||||
|
Read the docs at [pkg.go.dev/filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519).
|
||||||
|
|
||||||
|
The code is originally derived from Adam Langley's internal implementation in the Go standard library, and includes George Tankersley's [performance improvements](https://golang.org/cl/71950). It was then further developed by Henry de Valence for use in ristretto255.
|
||||||
|
|
||||||
|
Most users don't need this package, and should instead use `crypto/ed25519` for signatures, `golang.org/x/crypto/curve25519` for Diffie-Hellman, or `github.com/gtank/ristretto255` for prime order group logic. However, for anyone currently using a fork of `crypto/ed25519/internal/edwards25519` or `github.com/agl/edwards25519`, this package should be a safer, faster, and more powerful alternative.
|
||||||
|
|
||||||
|
Since this package is meant to curb proliferation of edwards25519 implementations in the Go ecosystem, it welcomes requests for new APIs or reviewable performance improvements.
|
||||||
20
vendor/filippo.io/edwards25519/doc.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright (c) 2021 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package edwards25519 implements group logic for the twisted Edwards curve
|
||||||
|
//
|
||||||
|
// -x^2 + y^2 = 1 + -(121665/121666)*x^2*y^2
|
||||||
|
//
|
||||||
|
// This is better known as the Edwards curve equivalent to Curve25519, and is
|
||||||
|
// the curve used by the Ed25519 signature scheme.
|
||||||
|
//
|
||||||
|
// Most users don't need this package, and should instead use crypto/ed25519 for
|
||||||
|
// signatures, golang.org/x/crypto/curve25519 for Diffie-Hellman, or
|
||||||
|
// github.com/gtank/ristretto255 for prime order group logic.
|
||||||
|
//
|
||||||
|
// However, developers who do need to interact with low-level edwards25519
|
||||||
|
// operations can use this package, which is an extended version of
|
||||||
|
// crypto/ed25519/internal/edwards25519 from the standard library repackaged as
|
||||||
|
// an importable module.
|
||||||
|
package edwards25519
|
||||||
428
vendor/filippo.io/edwards25519/edwards25519.go
generated
vendored
Normal file
@@ -0,0 +1,428 @@
|
|||||||
|
// Copyright (c) 2017 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package edwards25519
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"filippo.io/edwards25519/field"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Point types.
|
||||||
|
|
||||||
|
type projP1xP1 struct {
|
||||||
|
X, Y, Z, T field.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
type projP2 struct {
|
||||||
|
X, Y, Z field.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point represents a point on the edwards25519 curve.
|
||||||
|
//
|
||||||
|
// This type works similarly to math/big.Int, and all arguments and receivers
|
||||||
|
// are allowed to alias.
|
||||||
|
//
|
||||||
|
// The zero value is NOT valid, and it may be used only as a receiver.
|
||||||
|
type Point struct {
|
||||||
|
// The point is internally represented in extended coordinates (X, Y, Z, T)
|
||||||
|
// where x = X/Z, y = Y/Z, and xy = T/Z per https://eprint.iacr.org/2008/522.
|
||||||
|
x, y, z, t field.Element
|
||||||
|
|
||||||
|
// Make the type not comparable (i.e. used with == or as a map key), as
|
||||||
|
// equivalent points can be represented by different Go values.
|
||||||
|
_ incomparable
|
||||||
|
}
|
||||||
|
|
||||||
|
type incomparable [0]func()
|
||||||
|
|
||||||
|
func checkInitialized(points ...*Point) {
|
||||||
|
for _, p := range points {
|
||||||
|
if p.x == (field.Element{}) && p.y == (field.Element{}) {
|
||||||
|
panic("edwards25519: use of uninitialized Point")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type projCached struct {
|
||||||
|
YplusX, YminusX, Z, T2d field.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
type affineCached struct {
|
||||||
|
YplusX, YminusX, T2d field.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructors.
|
||||||
|
|
||||||
|
func (v *projP2) Zero() *projP2 {
|
||||||
|
v.X.Zero()
|
||||||
|
v.Y.One()
|
||||||
|
v.Z.One()
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// identity is the point at infinity.
|
||||||
|
var identity, _ = new(Point).SetBytes([]byte{
|
||||||
|
1, 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})
|
||||||
|
|
||||||
|
// NewIdentityPoint returns a new Point set to the identity.
|
||||||
|
func NewIdentityPoint() *Point {
|
||||||
|
return new(Point).Set(identity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// generator is the canonical curve basepoint. See TestGenerator for the
|
||||||
|
// correspondence of this encoding with the values in RFC 8032.
|
||||||
|
var generator, _ = new(Point).SetBytes([]byte{
|
||||||
|
0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
|
||||||
|
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
|
||||||
|
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
|
||||||
|
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66})
|
||||||
|
|
||||||
|
// NewGeneratorPoint returns a new Point set to the canonical generator.
|
||||||
|
func NewGeneratorPoint() *Point {
|
||||||
|
return new(Point).Set(generator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *projCached) Zero() *projCached {
|
||||||
|
v.YplusX.One()
|
||||||
|
v.YminusX.One()
|
||||||
|
v.Z.One()
|
||||||
|
v.T2d.Zero()
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *affineCached) Zero() *affineCached {
|
||||||
|
v.YplusX.One()
|
||||||
|
v.YminusX.One()
|
||||||
|
v.T2d.Zero()
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assignments.
|
||||||
|
|
||||||
|
// Set sets v = u, and returns v.
|
||||||
|
func (v *Point) Set(u *Point) *Point {
|
||||||
|
*v = *u
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encoding.
|
||||||
|
|
||||||
|
// Bytes returns the canonical 32-byte encoding of v, according to RFC 8032,
|
||||||
|
// Section 5.1.2.
|
||||||
|
func (v *Point) Bytes() []byte {
|
||||||
|
// This function is outlined to make the allocations inline in the caller
|
||||||
|
// rather than happen on the heap.
|
||||||
|
var buf [32]byte
|
||||||
|
return v.bytes(&buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Point) bytes(buf *[32]byte) []byte {
|
||||||
|
checkInitialized(v)
|
||||||
|
|
||||||
|
var zInv, x, y field.Element
|
||||||
|
zInv.Invert(&v.z) // zInv = 1 / Z
|
||||||
|
x.Multiply(&v.x, &zInv) // x = X / Z
|
||||||
|
y.Multiply(&v.y, &zInv) // y = Y / Z
|
||||||
|
|
||||||
|
out := copyFieldElement(buf, &y)
|
||||||
|
out[31] |= byte(x.IsNegative() << 7)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
var feOne = new(field.Element).One()
|
||||||
|
|
||||||
|
// SetBytes sets v = x, where x is a 32-byte encoding of v. If x does not
|
||||||
|
// represent a valid point on the curve, SetBytes returns nil and an error and
|
||||||
|
// the receiver is unchanged. Otherwise, SetBytes returns v.
|
||||||
|
//
|
||||||
|
// Note that SetBytes accepts all non-canonical encodings of valid points.
|
||||||
|
// That is, it follows decoding rules that match most implementations in
|
||||||
|
// the ecosystem rather than RFC 8032.
|
||||||
|
func (v *Point) SetBytes(x []byte) (*Point, error) {
|
||||||
|
// Specifically, the non-canonical encodings that are accepted are
|
||||||
|
// 1) the ones where the field element is not reduced (see the
|
||||||
|
// (*field.Element).SetBytes docs) and
|
||||||
|
// 2) the ones where the x-coordinate is zero and the sign bit is set.
|
||||||
|
//
|
||||||
|
// This is consistent with crypto/ed25519/internal/edwards25519. Read more
|
||||||
|
// at https://hdevalence.ca/blog/2020-10-04-its-25519am, specifically the
|
||||||
|
// "Canonical A, R" section.
|
||||||
|
|
||||||
|
y, err := new(field.Element).SetBytes(x)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("edwards25519: invalid point encoding length")
|
||||||
|
}
|
||||||
|
|
||||||
|
// -x² + y² = 1 + dx²y²
|
||||||
|
// x² + dx²y² = x²(dy² + 1) = y² - 1
|
||||||
|
// x² = (y² - 1) / (dy² + 1)
|
||||||
|
|
||||||
|
// u = y² - 1
|
||||||
|
y2 := new(field.Element).Square(y)
|
||||||
|
u := new(field.Element).Subtract(y2, feOne)
|
||||||
|
|
||||||
|
// v = dy² + 1
|
||||||
|
vv := new(field.Element).Multiply(y2, d)
|
||||||
|
vv = vv.Add(vv, feOne)
|
||||||
|
|
||||||
|
// x = +√(u/v)
|
||||||
|
xx, wasSquare := new(field.Element).SqrtRatio(u, vv)
|
||||||
|
if wasSquare == 0 {
|
||||||
|
return nil, errors.New("edwards25519: invalid point encoding")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select the negative square root if the sign bit is set.
|
||||||
|
xxNeg := new(field.Element).Negate(xx)
|
||||||
|
xx = xx.Select(xxNeg, xx, int(x[31]>>7))
|
||||||
|
|
||||||
|
v.x.Set(xx)
|
||||||
|
v.y.Set(y)
|
||||||
|
v.z.One()
|
||||||
|
v.t.Multiply(xx, y) // xy = T / Z
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFieldElement(buf *[32]byte, v *field.Element) []byte {
|
||||||
|
copy(buf[:], v.Bytes())
|
||||||
|
return buf[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conversions.
|
||||||
|
|
||||||
|
func (v *projP2) FromP1xP1(p *projP1xP1) *projP2 {
|
||||||
|
v.X.Multiply(&p.X, &p.T)
|
||||||
|
v.Y.Multiply(&p.Y, &p.Z)
|
||||||
|
v.Z.Multiply(&p.Z, &p.T)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *projP2) FromP3(p *Point) *projP2 {
|
||||||
|
v.X.Set(&p.x)
|
||||||
|
v.Y.Set(&p.y)
|
||||||
|
v.Z.Set(&p.z)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Point) fromP1xP1(p *projP1xP1) *Point {
|
||||||
|
v.x.Multiply(&p.X, &p.T)
|
||||||
|
v.y.Multiply(&p.Y, &p.Z)
|
||||||
|
v.z.Multiply(&p.Z, &p.T)
|
||||||
|
v.t.Multiply(&p.X, &p.Y)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Point) fromP2(p *projP2) *Point {
|
||||||
|
v.x.Multiply(&p.X, &p.Z)
|
||||||
|
v.y.Multiply(&p.Y, &p.Z)
|
||||||
|
v.z.Square(&p.Z)
|
||||||
|
v.t.Multiply(&p.X, &p.Y)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// d is a constant in the curve equation.
|
||||||
|
var d, _ = new(field.Element).SetBytes([]byte{
|
||||||
|
0xa3, 0x78, 0x59, 0x13, 0xca, 0x4d, 0xeb, 0x75,
|
||||||
|
0xab, 0xd8, 0x41, 0x41, 0x4d, 0x0a, 0x70, 0x00,
|
||||||
|
0x98, 0xe8, 0x79, 0x77, 0x79, 0x40, 0xc7, 0x8c,
|
||||||
|
0x73, 0xfe, 0x6f, 0x2b, 0xee, 0x6c, 0x03, 0x52})
|
||||||
|
var d2 = new(field.Element).Add(d, d)
|
||||||
|
|
||||||
|
func (v *projCached) FromP3(p *Point) *projCached {
|
||||||
|
v.YplusX.Add(&p.y, &p.x)
|
||||||
|
v.YminusX.Subtract(&p.y, &p.x)
|
||||||
|
v.Z.Set(&p.z)
|
||||||
|
v.T2d.Multiply(&p.t, d2)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *affineCached) FromP3(p *Point) *affineCached {
|
||||||
|
v.YplusX.Add(&p.y, &p.x)
|
||||||
|
v.YminusX.Subtract(&p.y, &p.x)
|
||||||
|
v.T2d.Multiply(&p.t, d2)
|
||||||
|
|
||||||
|
var invZ field.Element
|
||||||
|
invZ.Invert(&p.z)
|
||||||
|
v.YplusX.Multiply(&v.YplusX, &invZ)
|
||||||
|
v.YminusX.Multiply(&v.YminusX, &invZ)
|
||||||
|
v.T2d.Multiply(&v.T2d, &invZ)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// (Re)addition and subtraction.
|
||||||
|
|
||||||
|
// Add sets v = p + q, and returns v.
|
||||||
|
func (v *Point) Add(p, q *Point) *Point {
|
||||||
|
checkInitialized(p, q)
|
||||||
|
qCached := new(projCached).FromP3(q)
|
||||||
|
result := new(projP1xP1).Add(p, qCached)
|
||||||
|
return v.fromP1xP1(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subtract sets v = p - q, and returns v.
|
||||||
|
func (v *Point) Subtract(p, q *Point) *Point {
|
||||||
|
checkInitialized(p, q)
|
||||||
|
qCached := new(projCached).FromP3(q)
|
||||||
|
result := new(projP1xP1).Sub(p, qCached)
|
||||||
|
return v.fromP1xP1(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *projP1xP1) Add(p *Point, q *projCached) *projP1xP1 {
|
||||||
|
var YplusX, YminusX, PP, MM, TT2d, ZZ2 field.Element
|
||||||
|
|
||||||
|
YplusX.Add(&p.y, &p.x)
|
||||||
|
YminusX.Subtract(&p.y, &p.x)
|
||||||
|
|
||||||
|
PP.Multiply(&YplusX, &q.YplusX)
|
||||||
|
MM.Multiply(&YminusX, &q.YminusX)
|
||||||
|
TT2d.Multiply(&p.t, &q.T2d)
|
||||||
|
ZZ2.Multiply(&p.z, &q.Z)
|
||||||
|
|
||||||
|
ZZ2.Add(&ZZ2, &ZZ2)
|
||||||
|
|
||||||
|
v.X.Subtract(&PP, &MM)
|
||||||
|
v.Y.Add(&PP, &MM)
|
||||||
|
v.Z.Add(&ZZ2, &TT2d)
|
||||||
|
v.T.Subtract(&ZZ2, &TT2d)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *projP1xP1) Sub(p *Point, q *projCached) *projP1xP1 {
|
||||||
|
var YplusX, YminusX, PP, MM, TT2d, ZZ2 field.Element
|
||||||
|
|
||||||
|
YplusX.Add(&p.y, &p.x)
|
||||||
|
YminusX.Subtract(&p.y, &p.x)
|
||||||
|
|
||||||
|
PP.Multiply(&YplusX, &q.YminusX) // flipped sign
|
||||||
|
MM.Multiply(&YminusX, &q.YplusX) // flipped sign
|
||||||
|
TT2d.Multiply(&p.t, &q.T2d)
|
||||||
|
ZZ2.Multiply(&p.z, &q.Z)
|
||||||
|
|
||||||
|
ZZ2.Add(&ZZ2, &ZZ2)
|
||||||
|
|
||||||
|
v.X.Subtract(&PP, &MM)
|
||||||
|
v.Y.Add(&PP, &MM)
|
||||||
|
v.Z.Subtract(&ZZ2, &TT2d) // flipped sign
|
||||||
|
v.T.Add(&ZZ2, &TT2d) // flipped sign
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *projP1xP1) AddAffine(p *Point, q *affineCached) *projP1xP1 {
|
||||||
|
var YplusX, YminusX, PP, MM, TT2d, Z2 field.Element
|
||||||
|
|
||||||
|
YplusX.Add(&p.y, &p.x)
|
||||||
|
YminusX.Subtract(&p.y, &p.x)
|
||||||
|
|
||||||
|
PP.Multiply(&YplusX, &q.YplusX)
|
||||||
|
MM.Multiply(&YminusX, &q.YminusX)
|
||||||
|
TT2d.Multiply(&p.t, &q.T2d)
|
||||||
|
|
||||||
|
Z2.Add(&p.z, &p.z)
|
||||||
|
|
||||||
|
v.X.Subtract(&PP, &MM)
|
||||||
|
v.Y.Add(&PP, &MM)
|
||||||
|
v.Z.Add(&Z2, &TT2d)
|
||||||
|
v.T.Subtract(&Z2, &TT2d)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *projP1xP1) SubAffine(p *Point, q *affineCached) *projP1xP1 {
|
||||||
|
var YplusX, YminusX, PP, MM, TT2d, Z2 field.Element
|
||||||
|
|
||||||
|
YplusX.Add(&p.y, &p.x)
|
||||||
|
YminusX.Subtract(&p.y, &p.x)
|
||||||
|
|
||||||
|
PP.Multiply(&YplusX, &q.YminusX) // flipped sign
|
||||||
|
MM.Multiply(&YminusX, &q.YplusX) // flipped sign
|
||||||
|
TT2d.Multiply(&p.t, &q.T2d)
|
||||||
|
|
||||||
|
Z2.Add(&p.z, &p.z)
|
||||||
|
|
||||||
|
v.X.Subtract(&PP, &MM)
|
||||||
|
v.Y.Add(&PP, &MM)
|
||||||
|
v.Z.Subtract(&Z2, &TT2d) // flipped sign
|
||||||
|
v.T.Add(&Z2, &TT2d) // flipped sign
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Doubling.
|
||||||
|
|
||||||
|
func (v *projP1xP1) Double(p *projP2) *projP1xP1 {
|
||||||
|
var XX, YY, ZZ2, XplusYsq field.Element
|
||||||
|
|
||||||
|
XX.Square(&p.X)
|
||||||
|
YY.Square(&p.Y)
|
||||||
|
ZZ2.Square(&p.Z)
|
||||||
|
ZZ2.Add(&ZZ2, &ZZ2)
|
||||||
|
XplusYsq.Add(&p.X, &p.Y)
|
||||||
|
XplusYsq.Square(&XplusYsq)
|
||||||
|
|
||||||
|
v.Y.Add(&YY, &XX)
|
||||||
|
v.Z.Subtract(&YY, &XX)
|
||||||
|
|
||||||
|
v.X.Subtract(&XplusYsq, &v.Y)
|
||||||
|
v.T.Subtract(&ZZ2, &v.Z)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negation.
|
||||||
|
|
||||||
|
// Negate sets v = -p, and returns v.
|
||||||
|
func (v *Point) Negate(p *Point) *Point {
|
||||||
|
checkInitialized(p)
|
||||||
|
v.x.Negate(&p.x)
|
||||||
|
v.y.Set(&p.y)
|
||||||
|
v.z.Set(&p.z)
|
||||||
|
v.t.Negate(&p.t)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal returns 1 if v is equivalent to u, and 0 otherwise.
|
||||||
|
func (v *Point) Equal(u *Point) int {
|
||||||
|
checkInitialized(v, u)
|
||||||
|
|
||||||
|
var t1, t2, t3, t4 field.Element
|
||||||
|
t1.Multiply(&v.x, &u.z)
|
||||||
|
t2.Multiply(&u.x, &v.z)
|
||||||
|
t3.Multiply(&v.y, &u.z)
|
||||||
|
t4.Multiply(&u.y, &v.z)
|
||||||
|
|
||||||
|
return t1.Equal(&t2) & t3.Equal(&t4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constant-time operations
|
||||||
|
|
||||||
|
// Select sets v to a if cond == 1 and to b if cond == 0.
|
||||||
|
func (v *projCached) Select(a, b *projCached, cond int) *projCached {
|
||||||
|
v.YplusX.Select(&a.YplusX, &b.YplusX, cond)
|
||||||
|
v.YminusX.Select(&a.YminusX, &b.YminusX, cond)
|
||||||
|
v.Z.Select(&a.Z, &b.Z, cond)
|
||||||
|
v.T2d.Select(&a.T2d, &b.T2d, cond)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select sets v to a if cond == 1 and to b if cond == 0.
|
||||||
|
func (v *affineCached) Select(a, b *affineCached, cond int) *affineCached {
|
||||||
|
v.YplusX.Select(&a.YplusX, &b.YplusX, cond)
|
||||||
|
v.YminusX.Select(&a.YminusX, &b.YminusX, cond)
|
||||||
|
v.T2d.Select(&a.T2d, &b.T2d, cond)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// CondNeg negates v if cond == 1 and leaves it unchanged if cond == 0.
|
||||||
|
func (v *projCached) CondNeg(cond int) *projCached {
|
||||||
|
v.YplusX.Swap(&v.YminusX, cond)
|
||||||
|
v.T2d.Select(new(field.Element).Negate(&v.T2d), &v.T2d, cond)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// CondNeg negates v if cond == 1 and leaves it unchanged if cond == 0.
|
||||||
|
func (v *affineCached) CondNeg(cond int) *affineCached {
|
||||||
|
v.YplusX.Swap(&v.YminusX, cond)
|
||||||
|
v.T2d.Select(new(field.Element).Negate(&v.T2d), &v.T2d, cond)
|
||||||
|
return v
|
||||||
|
}
|
||||||
343
vendor/filippo.io/edwards25519/extra.go
generated
vendored
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
// Copyright (c) 2021 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package edwards25519
|
||||||
|
|
||||||
|
// This file contains additional functionality that is not included in the
|
||||||
|
// upstream crypto/ed25519/internal/edwards25519 package.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"filippo.io/edwards25519/field"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExtendedCoordinates returns v in extended coordinates (X:Y:Z:T) where
|
||||||
|
// x = X/Z, y = Y/Z, and xy = T/Z as in https://eprint.iacr.org/2008/522.
|
||||||
|
func (v *Point) ExtendedCoordinates() (X, Y, Z, T *field.Element) {
|
||||||
|
// This function is outlined to make the allocations inline in the caller
|
||||||
|
// rather than happen on the heap. Don't change the style without making
|
||||||
|
// sure it doesn't increase the inliner cost.
|
||||||
|
var e [4]field.Element
|
||||||
|
X, Y, Z, T = v.extendedCoordinates(&e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Point) extendedCoordinates(e *[4]field.Element) (X, Y, Z, T *field.Element) {
|
||||||
|
checkInitialized(v)
|
||||||
|
X = e[0].Set(&v.x)
|
||||||
|
Y = e[1].Set(&v.y)
|
||||||
|
Z = e[2].Set(&v.z)
|
||||||
|
T = e[3].Set(&v.t)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetExtendedCoordinates sets v = (X:Y:Z:T) in extended coordinates where
|
||||||
|
// x = X/Z, y = Y/Z, and xy = T/Z as in https://eprint.iacr.org/2008/522.
|
||||||
|
//
|
||||||
|
// If the coordinates are invalid or don't represent a valid point on the curve,
|
||||||
|
// SetExtendedCoordinates returns nil and an error and the receiver is
|
||||||
|
// unchanged. Otherwise, SetExtendedCoordinates returns v.
|
||||||
|
func (v *Point) SetExtendedCoordinates(X, Y, Z, T *field.Element) (*Point, error) {
|
||||||
|
if !isOnCurve(X, Y, Z, T) {
|
||||||
|
return nil, errors.New("edwards25519: invalid point coordinates")
|
||||||
|
}
|
||||||
|
v.x.Set(X)
|
||||||
|
v.y.Set(Y)
|
||||||
|
v.z.Set(Z)
|
||||||
|
v.t.Set(T)
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isOnCurve(X, Y, Z, T *field.Element) bool {
|
||||||
|
var lhs, rhs field.Element
|
||||||
|
XX := new(field.Element).Square(X)
|
||||||
|
YY := new(field.Element).Square(Y)
|
||||||
|
ZZ := new(field.Element).Square(Z)
|
||||||
|
TT := new(field.Element).Square(T)
|
||||||
|
// -x² + y² = 1 + dx²y²
|
||||||
|
// -(X/Z)² + (Y/Z)² = 1 + d(T/Z)²
|
||||||
|
// -X² + Y² = Z² + dT²
|
||||||
|
lhs.Subtract(YY, XX)
|
||||||
|
rhs.Multiply(d, TT).Add(&rhs, ZZ)
|
||||||
|
if lhs.Equal(&rhs) != 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// xy = T/Z
|
||||||
|
// XY/Z² = T/Z
|
||||||
|
// XY = TZ
|
||||||
|
lhs.Multiply(X, Y)
|
||||||
|
rhs.Multiply(T, Z)
|
||||||
|
return lhs.Equal(&rhs) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// BytesMontgomery converts v to a point on the birationally-equivalent
|
||||||
|
// Curve25519 Montgomery curve, and returns its canonical 32 bytes encoding
|
||||||
|
// according to RFC 7748.
|
||||||
|
//
|
||||||
|
// Note that BytesMontgomery only encodes the u-coordinate, so v and -v encode
|
||||||
|
// to the same value. If v is the identity point, BytesMontgomery returns 32
|
||||||
|
// zero bytes, analogously to the X25519 function.
|
||||||
|
func (v *Point) BytesMontgomery() []byte {
|
||||||
|
// This function is outlined to make the allocations inline in the caller
|
||||||
|
// rather than happen on the heap.
|
||||||
|
var buf [32]byte
|
||||||
|
return v.bytesMontgomery(&buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Point) bytesMontgomery(buf *[32]byte) []byte {
|
||||||
|
checkInitialized(v)
|
||||||
|
|
||||||
|
// RFC 7748, Section 4.1 provides the bilinear map to calculate the
|
||||||
|
// Montgomery u-coordinate
|
||||||
|
//
|
||||||
|
// u = (1 + y) / (1 - y)
|
||||||
|
//
|
||||||
|
// where y = Y / Z.
|
||||||
|
|
||||||
|
var y, recip, u field.Element
|
||||||
|
|
||||||
|
y.Multiply(&v.y, y.Invert(&v.z)) // y = Y / Z
|
||||||
|
recip.Invert(recip.Subtract(feOne, &y)) // r = 1/(1 - y)
|
||||||
|
u.Multiply(u.Add(feOne, &y), &recip) // u = (1 + y)*r
|
||||||
|
|
||||||
|
return copyFieldElement(buf, &u)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultByCofactor sets v = 8 * p, and returns v.
|
||||||
|
func (v *Point) MultByCofactor(p *Point) *Point {
|
||||||
|
checkInitialized(p)
|
||||||
|
result := projP1xP1{}
|
||||||
|
pp := (&projP2{}).FromP3(p)
|
||||||
|
result.Double(pp)
|
||||||
|
pp.FromP1xP1(&result)
|
||||||
|
result.Double(pp)
|
||||||
|
pp.FromP1xP1(&result)
|
||||||
|
result.Double(pp)
|
||||||
|
return v.fromP1xP1(&result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given k > 0, set s = s**(2*i).
|
||||||
|
func (s *Scalar) pow2k(k int) {
|
||||||
|
for i := 0; i < k; i++ {
|
||||||
|
s.Multiply(s, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invert sets s to the inverse of a nonzero scalar v, and returns s.
|
||||||
|
//
|
||||||
|
// If t is zero, Invert returns zero.
|
||||||
|
func (s *Scalar) Invert(t *Scalar) *Scalar {
|
||||||
|
// Uses a hardcoded sliding window of width 4.
|
||||||
|
var table [8]Scalar
|
||||||
|
var tt Scalar
|
||||||
|
tt.Multiply(t, t)
|
||||||
|
table[0] = *t
|
||||||
|
for i := 0; i < 7; i++ {
|
||||||
|
table[i+1].Multiply(&table[i], &tt)
|
||||||
|
}
|
||||||
|
// Now table = [t**1, t**3, t**7, t**11, t**13, t**15]
|
||||||
|
// so t**k = t[k/2] for odd k
|
||||||
|
|
||||||
|
// To compute the sliding window digits, use the following Sage script:
|
||||||
|
|
||||||
|
// sage: import itertools
|
||||||
|
// sage: def sliding_window(w,k):
|
||||||
|
// ....: digits = []
|
||||||
|
// ....: while k > 0:
|
||||||
|
// ....: if k % 2 == 1:
|
||||||
|
// ....: kmod = k % (2**w)
|
||||||
|
// ....: digits.append(kmod)
|
||||||
|
// ....: k = k - kmod
|
||||||
|
// ....: else:
|
||||||
|
// ....: digits.append(0)
|
||||||
|
// ....: k = k // 2
|
||||||
|
// ....: return digits
|
||||||
|
|
||||||
|
// Now we can compute s roughly as follows:
|
||||||
|
|
||||||
|
// sage: s = 1
|
||||||
|
// sage: for coeff in reversed(sliding_window(4,l-2)):
|
||||||
|
// ....: s = s*s
|
||||||
|
// ....: if coeff > 0 :
|
||||||
|
// ....: s = s*t**coeff
|
||||||
|
|
||||||
|
// This works on one bit at a time, with many runs of zeros.
|
||||||
|
// The digits can be collapsed into [(count, coeff)] as follows:
|
||||||
|
|
||||||
|
// sage: [(len(list(group)),d) for d,group in itertools.groupby(sliding_window(4,l-2))]
|
||||||
|
|
||||||
|
// Entries of the form (k, 0) turn into pow2k(k)
|
||||||
|
// Entries of the form (1, coeff) turn into a squaring and then a table lookup.
|
||||||
|
// We can fold the squaring into the previous pow2k(k) as pow2k(k+1).
|
||||||
|
|
||||||
|
*s = table[1/2]
|
||||||
|
s.pow2k(127 + 1)
|
||||||
|
s.Multiply(s, &table[1/2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.Multiply(s, &table[9/2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.Multiply(s, &table[11/2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.Multiply(s, &table[13/2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.Multiply(s, &table[15/2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.Multiply(s, &table[7/2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.Multiply(s, &table[15/2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.Multiply(s, &table[5/2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.Multiply(s, &table[1/2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.Multiply(s, &table[15/2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.Multiply(s, &table[15/2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.Multiply(s, &table[7/2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.Multiply(s, &table[3/2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.Multiply(s, &table[11/2])
|
||||||
|
s.pow2k(5 + 1)
|
||||||
|
s.Multiply(s, &table[11/2])
|
||||||
|
s.pow2k(9 + 1)
|
||||||
|
s.Multiply(s, &table[9/2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.Multiply(s, &table[3/2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.Multiply(s, &table[3/2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.Multiply(s, &table[3/2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.Multiply(s, &table[9/2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.Multiply(s, &table[7/2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.Multiply(s, &table[3/2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.Multiply(s, &table[13/2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.Multiply(s, &table[7/2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.Multiply(s, &table[9/2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.Multiply(s, &table[15/2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.Multiply(s, &table[11/2])
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiScalarMult sets v = sum(scalars[i] * points[i]), and returns v.
|
||||||
|
//
|
||||||
|
// Execution time depends only on the lengths of the two slices, which must match.
|
||||||
|
func (v *Point) MultiScalarMult(scalars []*Scalar, points []*Point) *Point {
|
||||||
|
if len(scalars) != len(points) {
|
||||||
|
panic("edwards25519: called MultiScalarMult with different size inputs")
|
||||||
|
}
|
||||||
|
checkInitialized(points...)
|
||||||
|
|
||||||
|
// Proceed as in the single-base case, but share doublings
|
||||||
|
// between each point in the multiscalar equation.
|
||||||
|
|
||||||
|
// Build lookup tables for each point
|
||||||
|
tables := make([]projLookupTable, len(points))
|
||||||
|
for i := range tables {
|
||||||
|
tables[i].FromP3(points[i])
|
||||||
|
}
|
||||||
|
// Compute signed radix-16 digits for each scalar
|
||||||
|
digits := make([][64]int8, len(scalars))
|
||||||
|
for i := range digits {
|
||||||
|
digits[i] = scalars[i].signedRadix16()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap first loop iteration to save computing 16*identity
|
||||||
|
multiple := &projCached{}
|
||||||
|
tmp1 := &projP1xP1{}
|
||||||
|
tmp2 := &projP2{}
|
||||||
|
// Lookup-and-add the appropriate multiple of each input point
|
||||||
|
for j := range tables {
|
||||||
|
tables[j].SelectInto(multiple, digits[j][63])
|
||||||
|
tmp1.Add(v, multiple) // tmp1 = v + x_(j,63)*Q in P1xP1 coords
|
||||||
|
v.fromP1xP1(tmp1) // update v
|
||||||
|
}
|
||||||
|
tmp2.FromP3(v) // set up tmp2 = v in P2 coords for next iteration
|
||||||
|
for i := 62; i >= 0; i-- {
|
||||||
|
tmp1.Double(tmp2) // tmp1 = 2*(prev) in P1xP1 coords
|
||||||
|
tmp2.FromP1xP1(tmp1) // tmp2 = 2*(prev) in P2 coords
|
||||||
|
tmp1.Double(tmp2) // tmp1 = 4*(prev) in P1xP1 coords
|
||||||
|
tmp2.FromP1xP1(tmp1) // tmp2 = 4*(prev) in P2 coords
|
||||||
|
tmp1.Double(tmp2) // tmp1 = 8*(prev) in P1xP1 coords
|
||||||
|
tmp2.FromP1xP1(tmp1) // tmp2 = 8*(prev) in P2 coords
|
||||||
|
tmp1.Double(tmp2) // tmp1 = 16*(prev) in P1xP1 coords
|
||||||
|
v.fromP1xP1(tmp1) // v = 16*(prev) in P3 coords
|
||||||
|
// Lookup-and-add the appropriate multiple of each input point
|
||||||
|
for j := range tables {
|
||||||
|
tables[j].SelectInto(multiple, digits[j][i])
|
||||||
|
tmp1.Add(v, multiple) // tmp1 = v + x_(j,i)*Q in P1xP1 coords
|
||||||
|
v.fromP1xP1(tmp1) // update v
|
||||||
|
}
|
||||||
|
tmp2.FromP3(v) // set up tmp2 = v in P2 coords for next iteration
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// VarTimeMultiScalarMult sets v = sum(scalars[i] * points[i]), and returns v.
|
||||||
|
//
|
||||||
|
// Execution time depends on the inputs.
|
||||||
|
func (v *Point) VarTimeMultiScalarMult(scalars []*Scalar, points []*Point) *Point {
|
||||||
|
if len(scalars) != len(points) {
|
||||||
|
panic("edwards25519: called VarTimeMultiScalarMult with different size inputs")
|
||||||
|
}
|
||||||
|
checkInitialized(points...)
|
||||||
|
|
||||||
|
// Generalize double-base NAF computation to arbitrary sizes.
|
||||||
|
// Here all the points are dynamic, so we only use the smaller
|
||||||
|
// tables.
|
||||||
|
|
||||||
|
// Build lookup tables for each point
|
||||||
|
tables := make([]nafLookupTable5, len(points))
|
||||||
|
for i := range tables {
|
||||||
|
tables[i].FromP3(points[i])
|
||||||
|
}
|
||||||
|
// Compute a NAF for each scalar
|
||||||
|
nafs := make([][256]int8, len(scalars))
|
||||||
|
for i := range nafs {
|
||||||
|
nafs[i] = scalars[i].nonAdjacentForm(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
multiple := &projCached{}
|
||||||
|
tmp1 := &projP1xP1{}
|
||||||
|
tmp2 := &projP2{}
|
||||||
|
tmp2.Zero()
|
||||||
|
|
||||||
|
// Move from high to low bits, doubling the accumulator
|
||||||
|
// at each iteration and checking whether there is a nonzero
|
||||||
|
// coefficient to look up a multiple of.
|
||||||
|
//
|
||||||
|
// Skip trying to find the first nonzero coefficent, because
|
||||||
|
// searching might be more work than a few extra doublings.
|
||||||
|
for i := 255; i >= 0; i-- {
|
||||||
|
tmp1.Double(tmp2)
|
||||||
|
|
||||||
|
for j := range nafs {
|
||||||
|
if nafs[j][i] > 0 {
|
||||||
|
v.fromP1xP1(tmp1)
|
||||||
|
tables[j].SelectInto(multiple, nafs[j][i])
|
||||||
|
tmp1.Add(v, multiple)
|
||||||
|
} else if nafs[j][i] < 0 {
|
||||||
|
v.fromP1xP1(tmp1)
|
||||||
|
tables[j].SelectInto(multiple, -nafs[j][i])
|
||||||
|
tmp1.Sub(v, multiple)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp2.FromP1xP1(tmp1)
|
||||||
|
}
|
||||||
|
|
||||||
|
v.fromP2(tmp2)
|
||||||
|
return v
|
||||||
|
}
|
||||||
419
vendor/filippo.io/edwards25519/field/fe.go
generated
vendored
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
// Copyright (c) 2017 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package field implements fast arithmetic modulo 2^255-19.
|
||||||
|
package field
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/subtle"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"math/bits"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Element represents an element of the field GF(2^255-19). Note that this
|
||||||
|
// is not a cryptographically secure group, and should only be used to interact
|
||||||
|
// with edwards25519.Point coordinates.
|
||||||
|
//
|
||||||
|
// This type works similarly to math/big.Int, and all arguments and receivers
|
||||||
|
// are allowed to alias.
|
||||||
|
//
|
||||||
|
// The zero value is a valid zero element.
|
||||||
|
type Element struct {
|
||||||
|
// An element t represents the integer
|
||||||
|
// t.l0 + t.l1*2^51 + t.l2*2^102 + t.l3*2^153 + t.l4*2^204
|
||||||
|
//
|
||||||
|
// Between operations, all limbs are expected to be lower than 2^52.
|
||||||
|
l0 uint64
|
||||||
|
l1 uint64
|
||||||
|
l2 uint64
|
||||||
|
l3 uint64
|
||||||
|
l4 uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
const maskLow51Bits uint64 = (1 << 51) - 1
|
||||||
|
|
||||||
|
var feZero = &Element{0, 0, 0, 0, 0}
|
||||||
|
|
||||||
|
// Zero sets v = 0, and returns v.
|
||||||
|
func (v *Element) Zero() *Element {
|
||||||
|
*v = *feZero
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
var feOne = &Element{1, 0, 0, 0, 0}
|
||||||
|
|
||||||
|
// One sets v = 1, and returns v.
|
||||||
|
func (v *Element) One() *Element {
|
||||||
|
*v = *feOne
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// reduce reduces v modulo 2^255 - 19 and returns it.
|
||||||
|
func (v *Element) reduce() *Element {
|
||||||
|
v.carryPropagate()
|
||||||
|
|
||||||
|
// After the light reduction we now have a field element representation
|
||||||
|
// v < 2^255 + 2^13 * 19, but need v < 2^255 - 19.
|
||||||
|
|
||||||
|
// If v >= 2^255 - 19, then v + 19 >= 2^255, which would overflow 2^255 - 1,
|
||||||
|
// generating a carry. That is, c will be 0 if v < 2^255 - 19, and 1 otherwise.
|
||||||
|
c := (v.l0 + 19) >> 51
|
||||||
|
c = (v.l1 + c) >> 51
|
||||||
|
c = (v.l2 + c) >> 51
|
||||||
|
c = (v.l3 + c) >> 51
|
||||||
|
c = (v.l4 + c) >> 51
|
||||||
|
|
||||||
|
// If v < 2^255 - 19 and c = 0, this will be a no-op. Otherwise, it's
|
||||||
|
// effectively applying the reduction identity to the carry.
|
||||||
|
v.l0 += 19 * c
|
||||||
|
|
||||||
|
v.l1 += v.l0 >> 51
|
||||||
|
v.l0 = v.l0 & maskLow51Bits
|
||||||
|
v.l2 += v.l1 >> 51
|
||||||
|
v.l1 = v.l1 & maskLow51Bits
|
||||||
|
v.l3 += v.l2 >> 51
|
||||||
|
v.l2 = v.l2 & maskLow51Bits
|
||||||
|
v.l4 += v.l3 >> 51
|
||||||
|
v.l3 = v.l3 & maskLow51Bits
|
||||||
|
// no additional carry
|
||||||
|
v.l4 = v.l4 & maskLow51Bits
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add sets v = a + b, and returns v.
|
||||||
|
func (v *Element) Add(a, b *Element) *Element {
|
||||||
|
v.l0 = a.l0 + b.l0
|
||||||
|
v.l1 = a.l1 + b.l1
|
||||||
|
v.l2 = a.l2 + b.l2
|
||||||
|
v.l3 = a.l3 + b.l3
|
||||||
|
v.l4 = a.l4 + b.l4
|
||||||
|
// Using the generic implementation here is actually faster than the
|
||||||
|
// assembly. Probably because the body of this function is so simple that
|
||||||
|
// the compiler can figure out better optimizations by inlining the carry
|
||||||
|
// propagation.
|
||||||
|
return v.carryPropagateGeneric()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subtract sets v = a - b, and returns v.
|
||||||
|
func (v *Element) Subtract(a, b *Element) *Element {
|
||||||
|
// We first add 2 * p, to guarantee the subtraction won't underflow, and
|
||||||
|
// then subtract b (which can be up to 2^255 + 2^13 * 19).
|
||||||
|
v.l0 = (a.l0 + 0xFFFFFFFFFFFDA) - b.l0
|
||||||
|
v.l1 = (a.l1 + 0xFFFFFFFFFFFFE) - b.l1
|
||||||
|
v.l2 = (a.l2 + 0xFFFFFFFFFFFFE) - b.l2
|
||||||
|
v.l3 = (a.l3 + 0xFFFFFFFFFFFFE) - b.l3
|
||||||
|
v.l4 = (a.l4 + 0xFFFFFFFFFFFFE) - b.l4
|
||||||
|
return v.carryPropagate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negate sets v = -a, and returns v.
|
||||||
|
func (v *Element) Negate(a *Element) *Element {
|
||||||
|
return v.Subtract(feZero, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invert sets v = 1/z mod p, and returns v.
|
||||||
|
//
|
||||||
|
// If z == 0, Invert returns v = 0.
|
||||||
|
func (v *Element) Invert(z *Element) *Element {
|
||||||
|
// Inversion is implemented as exponentiation with exponent p − 2. It uses the
|
||||||
|
// same sequence of 255 squarings and 11 multiplications as [Curve25519].
|
||||||
|
var z2, z9, z11, z2_5_0, z2_10_0, z2_20_0, z2_50_0, z2_100_0, t Element
|
||||||
|
|
||||||
|
z2.Square(z) // 2
|
||||||
|
t.Square(&z2) // 4
|
||||||
|
t.Square(&t) // 8
|
||||||
|
z9.Multiply(&t, z) // 9
|
||||||
|
z11.Multiply(&z9, &z2) // 11
|
||||||
|
t.Square(&z11) // 22
|
||||||
|
z2_5_0.Multiply(&t, &z9) // 31 = 2^5 - 2^0
|
||||||
|
|
||||||
|
t.Square(&z2_5_0) // 2^6 - 2^1
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
t.Square(&t) // 2^10 - 2^5
|
||||||
|
}
|
||||||
|
z2_10_0.Multiply(&t, &z2_5_0) // 2^10 - 2^0
|
||||||
|
|
||||||
|
t.Square(&z2_10_0) // 2^11 - 2^1
|
||||||
|
for i := 0; i < 9; i++ {
|
||||||
|
t.Square(&t) // 2^20 - 2^10
|
||||||
|
}
|
||||||
|
z2_20_0.Multiply(&t, &z2_10_0) // 2^20 - 2^0
|
||||||
|
|
||||||
|
t.Square(&z2_20_0) // 2^21 - 2^1
|
||||||
|
for i := 0; i < 19; i++ {
|
||||||
|
t.Square(&t) // 2^40 - 2^20
|
||||||
|
}
|
||||||
|
t.Multiply(&t, &z2_20_0) // 2^40 - 2^0
|
||||||
|
|
||||||
|
t.Square(&t) // 2^41 - 2^1
|
||||||
|
for i := 0; i < 9; i++ {
|
||||||
|
t.Square(&t) // 2^50 - 2^10
|
||||||
|
}
|
||||||
|
z2_50_0.Multiply(&t, &z2_10_0) // 2^50 - 2^0
|
||||||
|
|
||||||
|
t.Square(&z2_50_0) // 2^51 - 2^1
|
||||||
|
for i := 0; i < 49; i++ {
|
||||||
|
t.Square(&t) // 2^100 - 2^50
|
||||||
|
}
|
||||||
|
z2_100_0.Multiply(&t, &z2_50_0) // 2^100 - 2^0
|
||||||
|
|
||||||
|
t.Square(&z2_100_0) // 2^101 - 2^1
|
||||||
|
for i := 0; i < 99; i++ {
|
||||||
|
t.Square(&t) // 2^200 - 2^100
|
||||||
|
}
|
||||||
|
t.Multiply(&t, &z2_100_0) // 2^200 - 2^0
|
||||||
|
|
||||||
|
t.Square(&t) // 2^201 - 2^1
|
||||||
|
for i := 0; i < 49; i++ {
|
||||||
|
t.Square(&t) // 2^250 - 2^50
|
||||||
|
}
|
||||||
|
t.Multiply(&t, &z2_50_0) // 2^250 - 2^0
|
||||||
|
|
||||||
|
t.Square(&t) // 2^251 - 2^1
|
||||||
|
t.Square(&t) // 2^252 - 2^2
|
||||||
|
t.Square(&t) // 2^253 - 2^3
|
||||||
|
t.Square(&t) // 2^254 - 2^4
|
||||||
|
t.Square(&t) // 2^255 - 2^5
|
||||||
|
|
||||||
|
return v.Multiply(&t, &z11) // 2^255 - 21
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets v = a, and returns v.
|
||||||
|
func (v *Element) Set(a *Element) *Element {
|
||||||
|
*v = *a
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBytes sets v to x, where x is a 32-byte little-endian encoding. If x is
|
||||||
|
// not of the right length, SetUniformBytes returns nil and an error, and the
|
||||||
|
// receiver is unchanged.
|
||||||
|
//
|
||||||
|
// Consistent with RFC 7748, the most significant bit (the high bit of the
|
||||||
|
// last byte) is ignored, and non-canonical values (2^255-19 through 2^255-1)
|
||||||
|
// are accepted. Note that this is laxer than specified by RFC 8032.
|
||||||
|
func (v *Element) SetBytes(x []byte) (*Element, error) {
|
||||||
|
if len(x) != 32 {
|
||||||
|
return nil, errors.New("edwards25519: invalid field element input size")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bits 0:51 (bytes 0:8, bits 0:64, shift 0, mask 51).
|
||||||
|
v.l0 = binary.LittleEndian.Uint64(x[0:8])
|
||||||
|
v.l0 &= maskLow51Bits
|
||||||
|
// Bits 51:102 (bytes 6:14, bits 48:112, shift 3, mask 51).
|
||||||
|
v.l1 = binary.LittleEndian.Uint64(x[6:14]) >> 3
|
||||||
|
v.l1 &= maskLow51Bits
|
||||||
|
// Bits 102:153 (bytes 12:20, bits 96:160, shift 6, mask 51).
|
||||||
|
v.l2 = binary.LittleEndian.Uint64(x[12:20]) >> 6
|
||||||
|
v.l2 &= maskLow51Bits
|
||||||
|
// Bits 153:204 (bytes 19:27, bits 152:216, shift 1, mask 51).
|
||||||
|
v.l3 = binary.LittleEndian.Uint64(x[19:27]) >> 1
|
||||||
|
v.l3 &= maskLow51Bits
|
||||||
|
// Bits 204:251 (bytes 24:32, bits 192:256, shift 12, mask 51).
|
||||||
|
// Note: not bytes 25:33, shift 4, to avoid overread.
|
||||||
|
v.l4 = binary.LittleEndian.Uint64(x[24:32]) >> 12
|
||||||
|
v.l4 &= maskLow51Bits
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the canonical 32-byte little-endian encoding of v.
|
||||||
|
func (v *Element) Bytes() []byte {
|
||||||
|
// This function is outlined to make the allocations inline in the caller
|
||||||
|
// rather than happen on the heap.
|
||||||
|
var out [32]byte
|
||||||
|
return v.bytes(&out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Element) bytes(out *[32]byte) []byte {
|
||||||
|
t := *v
|
||||||
|
t.reduce()
|
||||||
|
|
||||||
|
var buf [8]byte
|
||||||
|
for i, l := range [5]uint64{t.l0, t.l1, t.l2, t.l3, t.l4} {
|
||||||
|
bitsOffset := i * 51
|
||||||
|
binary.LittleEndian.PutUint64(buf[:], l<<uint(bitsOffset%8))
|
||||||
|
for i, bb := range buf {
|
||||||
|
off := bitsOffset/8 + i
|
||||||
|
if off >= len(out) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
out[off] |= bb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal returns 1 if v and u are equal, and 0 otherwise.
|
||||||
|
func (v *Element) Equal(u *Element) int {
|
||||||
|
sa, sv := u.Bytes(), v.Bytes()
|
||||||
|
return subtle.ConstantTimeCompare(sa, sv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mask64Bits returns 0xffffffff if cond is 1, and 0 otherwise.
|
||||||
|
func mask64Bits(cond int) uint64 { return ^(uint64(cond) - 1) }
|
||||||
|
|
||||||
|
// Select sets v to a if cond == 1, and to b if cond == 0.
|
||||||
|
func (v *Element) Select(a, b *Element, cond int) *Element {
|
||||||
|
m := mask64Bits(cond)
|
||||||
|
v.l0 = (m & a.l0) | (^m & b.l0)
|
||||||
|
v.l1 = (m & a.l1) | (^m & b.l1)
|
||||||
|
v.l2 = (m & a.l2) | (^m & b.l2)
|
||||||
|
v.l3 = (m & a.l3) | (^m & b.l3)
|
||||||
|
v.l4 = (m & a.l4) | (^m & b.l4)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap swaps v and u if cond == 1 or leaves them unchanged if cond == 0, and returns v.
|
||||||
|
func (v *Element) Swap(u *Element, cond int) {
|
||||||
|
m := mask64Bits(cond)
|
||||||
|
t := m & (v.l0 ^ u.l0)
|
||||||
|
v.l0 ^= t
|
||||||
|
u.l0 ^= t
|
||||||
|
t = m & (v.l1 ^ u.l1)
|
||||||
|
v.l1 ^= t
|
||||||
|
u.l1 ^= t
|
||||||
|
t = m & (v.l2 ^ u.l2)
|
||||||
|
v.l2 ^= t
|
||||||
|
u.l2 ^= t
|
||||||
|
t = m & (v.l3 ^ u.l3)
|
||||||
|
v.l3 ^= t
|
||||||
|
u.l3 ^= t
|
||||||
|
t = m & (v.l4 ^ u.l4)
|
||||||
|
v.l4 ^= t
|
||||||
|
u.l4 ^= t
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNegative returns 1 if v is negative, and 0 otherwise.
|
||||||
|
func (v *Element) IsNegative() int {
|
||||||
|
return int(v.Bytes()[0] & 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Absolute sets v to |u|, and returns v.
|
||||||
|
func (v *Element) Absolute(u *Element) *Element {
|
||||||
|
return v.Select(new(Element).Negate(u), u, u.IsNegative())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiply sets v = x * y, and returns v.
|
||||||
|
func (v *Element) Multiply(x, y *Element) *Element {
|
||||||
|
feMul(v, x, y)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Square sets v = x * x, and returns v.
|
||||||
|
func (v *Element) Square(x *Element) *Element {
|
||||||
|
feSquare(v, x)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mult32 sets v = x * y, and returns v.
|
||||||
|
func (v *Element) Mult32(x *Element, y uint32) *Element {
|
||||||
|
x0lo, x0hi := mul51(x.l0, y)
|
||||||
|
x1lo, x1hi := mul51(x.l1, y)
|
||||||
|
x2lo, x2hi := mul51(x.l2, y)
|
||||||
|
x3lo, x3hi := mul51(x.l3, y)
|
||||||
|
x4lo, x4hi := mul51(x.l4, y)
|
||||||
|
v.l0 = x0lo + 19*x4hi // carried over per the reduction identity
|
||||||
|
v.l1 = x1lo + x0hi
|
||||||
|
v.l2 = x2lo + x1hi
|
||||||
|
v.l3 = x3lo + x2hi
|
||||||
|
v.l4 = x4lo + x3hi
|
||||||
|
// The hi portions are going to be only 32 bits, plus any previous excess,
|
||||||
|
// so we can skip the carry propagation.
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// mul51 returns lo + hi * 2⁵¹ = a * b.
|
||||||
|
func mul51(a uint64, b uint32) (lo uint64, hi uint64) {
|
||||||
|
mh, ml := bits.Mul64(a, uint64(b))
|
||||||
|
lo = ml & maskLow51Bits
|
||||||
|
hi = (mh << 13) | (ml >> 51)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pow22523 set v = x^((p-5)/8), and returns v. (p-5)/8 is 2^252-3.
|
||||||
|
func (v *Element) Pow22523(x *Element) *Element {
|
||||||
|
var t0, t1, t2 Element
|
||||||
|
|
||||||
|
t0.Square(x) // x^2
|
||||||
|
t1.Square(&t0) // x^4
|
||||||
|
t1.Square(&t1) // x^8
|
||||||
|
t1.Multiply(x, &t1) // x^9
|
||||||
|
t0.Multiply(&t0, &t1) // x^11
|
||||||
|
t0.Square(&t0) // x^22
|
||||||
|
t0.Multiply(&t1, &t0) // x^31
|
||||||
|
t1.Square(&t0) // x^62
|
||||||
|
for i := 1; i < 5; i++ { // x^992
|
||||||
|
t1.Square(&t1)
|
||||||
|
}
|
||||||
|
t0.Multiply(&t1, &t0) // x^1023 -> 1023 = 2^10 - 1
|
||||||
|
t1.Square(&t0) // 2^11 - 2
|
||||||
|
for i := 1; i < 10; i++ { // 2^20 - 2^10
|
||||||
|
t1.Square(&t1)
|
||||||
|
}
|
||||||
|
t1.Multiply(&t1, &t0) // 2^20 - 1
|
||||||
|
t2.Square(&t1) // 2^21 - 2
|
||||||
|
for i := 1; i < 20; i++ { // 2^40 - 2^20
|
||||||
|
t2.Square(&t2)
|
||||||
|
}
|
||||||
|
t1.Multiply(&t2, &t1) // 2^40 - 1
|
||||||
|
t1.Square(&t1) // 2^41 - 2
|
||||||
|
for i := 1; i < 10; i++ { // 2^50 - 2^10
|
||||||
|
t1.Square(&t1)
|
||||||
|
}
|
||||||
|
t0.Multiply(&t1, &t0) // 2^50 - 1
|
||||||
|
t1.Square(&t0) // 2^51 - 2
|
||||||
|
for i := 1; i < 50; i++ { // 2^100 - 2^50
|
||||||
|
t1.Square(&t1)
|
||||||
|
}
|
||||||
|
t1.Multiply(&t1, &t0) // 2^100 - 1
|
||||||
|
t2.Square(&t1) // 2^101 - 2
|
||||||
|
for i := 1; i < 100; i++ { // 2^200 - 2^100
|
||||||
|
t2.Square(&t2)
|
||||||
|
}
|
||||||
|
t1.Multiply(&t2, &t1) // 2^200 - 1
|
||||||
|
t1.Square(&t1) // 2^201 - 2
|
||||||
|
for i := 1; i < 50; i++ { // 2^250 - 2^50
|
||||||
|
t1.Square(&t1)
|
||||||
|
}
|
||||||
|
t0.Multiply(&t1, &t0) // 2^250 - 1
|
||||||
|
t0.Square(&t0) // 2^251 - 2
|
||||||
|
t0.Square(&t0) // 2^252 - 4
|
||||||
|
return v.Multiply(&t0, x) // 2^252 - 3 -> x^(2^252-3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sqrtM1 is 2^((p-1)/4), which squared is equal to -1 by Euler's Criterion.
|
||||||
|
var sqrtM1 = &Element{1718705420411056, 234908883556509,
|
||||||
|
2233514472574048, 2117202627021982, 765476049583133}
|
||||||
|
|
||||||
|
// SqrtRatio sets r to the non-negative square root of the ratio of u and v.
|
||||||
|
//
|
||||||
|
// If u/v is square, SqrtRatio returns r and 1. If u/v is not square, SqrtRatio
|
||||||
|
// sets r according to Section 4.3 of draft-irtf-cfrg-ristretto255-decaf448-00,
|
||||||
|
// and returns r and 0.
|
||||||
|
func (r *Element) SqrtRatio(u, v *Element) (rr *Element, wasSquare int) {
|
||||||
|
var a, b Element
|
||||||
|
|
||||||
|
// r = (u * v3) * (u * v7)^((p-5)/8)
|
||||||
|
v2 := a.Square(v)
|
||||||
|
uv3 := b.Multiply(u, b.Multiply(v2, v))
|
||||||
|
uv7 := a.Multiply(uv3, a.Square(v2))
|
||||||
|
r.Multiply(uv3, r.Pow22523(uv7))
|
||||||
|
|
||||||
|
check := a.Multiply(v, a.Square(r)) // check = v * r^2
|
||||||
|
|
||||||
|
uNeg := b.Negate(u)
|
||||||
|
correctSignSqrt := check.Equal(u)
|
||||||
|
flippedSignSqrt := check.Equal(uNeg)
|
||||||
|
flippedSignSqrtI := check.Equal(uNeg.Multiply(uNeg, sqrtM1))
|
||||||
|
|
||||||
|
rPrime := b.Multiply(r, sqrtM1) // r_prime = SQRT_M1 * r
|
||||||
|
// r = CT_SELECT(r_prime IF flipped_sign_sqrt | flipped_sign_sqrt_i ELSE r)
|
||||||
|
r.Select(rPrime, r, flippedSignSqrt|flippedSignSqrtI)
|
||||||
|
|
||||||
|
r.Absolute(r) // Choose the nonnegative square root.
|
||||||
|
return r, correctSignSqrt | flippedSignSqrt
|
||||||
|
}
|
||||||
13
vendor/filippo.io/edwards25519/field/fe_amd64.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// Code generated by command: go run fe_amd64_asm.go -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field. DO NOT EDIT.
|
||||||
|
|
||||||
|
// +build amd64,gc,!purego
|
||||||
|
|
||||||
|
package field
|
||||||
|
|
||||||
|
// feMul sets out = a * b. It works like feMulGeneric.
|
||||||
|
//go:noescape
|
||||||
|
func feMul(out *Element, a *Element, b *Element)
|
||||||
|
|
||||||
|
// feSquare sets out = a * a. It works like feSquareGeneric.
|
||||||
|
//go:noescape
|
||||||
|
func feSquare(out *Element, a *Element)
|
||||||
378
vendor/filippo.io/edwards25519/field/fe_amd64.s
generated
vendored
Normal file
@@ -0,0 +1,378 @@
|
|||||||
|
// Code generated by command: go run fe_amd64_asm.go -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field. DO NOT EDIT.
|
||||||
|
|
||||||
|
// +build amd64,gc,!purego
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// func feMul(out *Element, a *Element, b *Element)
|
||||||
|
TEXT ·feMul(SB), NOSPLIT, $0-24
|
||||||
|
MOVQ a+8(FP), CX
|
||||||
|
MOVQ b+16(FP), BX
|
||||||
|
|
||||||
|
// r0 = a0×b0
|
||||||
|
MOVQ (CX), AX
|
||||||
|
MULQ (BX)
|
||||||
|
MOVQ AX, DI
|
||||||
|
MOVQ DX, SI
|
||||||
|
|
||||||
|
// r0 += 19×a1×b4
|
||||||
|
MOVQ 8(CX), AX
|
||||||
|
IMUL3Q $0x13, AX, AX
|
||||||
|
MULQ 32(BX)
|
||||||
|
ADDQ AX, DI
|
||||||
|
ADCQ DX, SI
|
||||||
|
|
||||||
|
// r0 += 19×a2×b3
|
||||||
|
MOVQ 16(CX), AX
|
||||||
|
IMUL3Q $0x13, AX, AX
|
||||||
|
MULQ 24(BX)
|
||||||
|
ADDQ AX, DI
|
||||||
|
ADCQ DX, SI
|
||||||
|
|
||||||
|
// r0 += 19×a3×b2
|
||||||
|
MOVQ 24(CX), AX
|
||||||
|
IMUL3Q $0x13, AX, AX
|
||||||
|
MULQ 16(BX)
|
||||||
|
ADDQ AX, DI
|
||||||
|
ADCQ DX, SI
|
||||||
|
|
||||||
|
// r0 += 19×a4×b1
|
||||||
|
MOVQ 32(CX), AX
|
||||||
|
IMUL3Q $0x13, AX, AX
|
||||||
|
MULQ 8(BX)
|
||||||
|
ADDQ AX, DI
|
||||||
|
ADCQ DX, SI
|
||||||
|
|
||||||
|
// r1 = a0×b1
|
||||||
|
MOVQ (CX), AX
|
||||||
|
MULQ 8(BX)
|
||||||
|
MOVQ AX, R9
|
||||||
|
MOVQ DX, R8
|
||||||
|
|
||||||
|
// r1 += a1×b0
|
||||||
|
MOVQ 8(CX), AX
|
||||||
|
MULQ (BX)
|
||||||
|
ADDQ AX, R9
|
||||||
|
ADCQ DX, R8
|
||||||
|
|
||||||
|
// r1 += 19×a2×b4
|
||||||
|
MOVQ 16(CX), AX
|
||||||
|
IMUL3Q $0x13, AX, AX
|
||||||
|
MULQ 32(BX)
|
||||||
|
ADDQ AX, R9
|
||||||
|
ADCQ DX, R8
|
||||||
|
|
||||||
|
// r1 += 19×a3×b3
|
||||||
|
MOVQ 24(CX), AX
|
||||||
|
IMUL3Q $0x13, AX, AX
|
||||||
|
MULQ 24(BX)
|
||||||
|
ADDQ AX, R9
|
||||||
|
ADCQ DX, R8
|
||||||
|
|
||||||
|
// r1 += 19×a4×b2
|
||||||
|
MOVQ 32(CX), AX
|
||||||
|
IMUL3Q $0x13, AX, AX
|
||||||
|
MULQ 16(BX)
|
||||||
|
ADDQ AX, R9
|
||||||
|
ADCQ DX, R8
|
||||||
|
|
||||||
|
// r2 = a0×b2
|
||||||
|
MOVQ (CX), AX
|
||||||
|
MULQ 16(BX)
|
||||||
|
MOVQ AX, R11
|
||||||
|
MOVQ DX, R10
|
||||||
|
|
||||||
|
// r2 += a1×b1
|
||||||
|
MOVQ 8(CX), AX
|
||||||
|
MULQ 8(BX)
|
||||||
|
ADDQ AX, R11
|
||||||
|
ADCQ DX, R10
|
||||||
|
|
||||||
|
// r2 += a2×b0
|
||||||
|
MOVQ 16(CX), AX
|
||||||
|
MULQ (BX)
|
||||||
|
ADDQ AX, R11
|
||||||
|
ADCQ DX, R10
|
||||||
|
|
||||||
|
// r2 += 19×a3×b4
|
||||||
|
MOVQ 24(CX), AX
|
||||||
|
IMUL3Q $0x13, AX, AX
|
||||||
|
MULQ 32(BX)
|
||||||
|
ADDQ AX, R11
|
||||||
|
ADCQ DX, R10
|
||||||
|
|
||||||
|
// r2 += 19×a4×b3
|
||||||
|
MOVQ 32(CX), AX
|
||||||
|
IMUL3Q $0x13, AX, AX
|
||||||
|
MULQ 24(BX)
|
||||||
|
ADDQ AX, R11
|
||||||
|
ADCQ DX, R10
|
||||||
|
|
||||||
|
// r3 = a0×b3
|
||||||
|
MOVQ (CX), AX
|
||||||
|
MULQ 24(BX)
|
||||||
|
MOVQ AX, R13
|
||||||
|
MOVQ DX, R12
|
||||||
|
|
||||||
|
// r3 += a1×b2
|
||||||
|
MOVQ 8(CX), AX
|
||||||
|
MULQ 16(BX)
|
||||||
|
ADDQ AX, R13
|
||||||
|
ADCQ DX, R12
|
||||||
|
|
||||||
|
// r3 += a2×b1
|
||||||
|
MOVQ 16(CX), AX
|
||||||
|
MULQ 8(BX)
|
||||||
|
ADDQ AX, R13
|
||||||
|
ADCQ DX, R12
|
||||||
|
|
||||||
|
// r3 += a3×b0
|
||||||
|
MOVQ 24(CX), AX
|
||||||
|
MULQ (BX)
|
||||||
|
ADDQ AX, R13
|
||||||
|
ADCQ DX, R12
|
||||||
|
|
||||||
|
// r3 += 19×a4×b4
|
||||||
|
MOVQ 32(CX), AX
|
||||||
|
IMUL3Q $0x13, AX, AX
|
||||||
|
MULQ 32(BX)
|
||||||
|
ADDQ AX, R13
|
||||||
|
ADCQ DX, R12
|
||||||
|
|
||||||
|
// r4 = a0×b4
|
||||||
|
MOVQ (CX), AX
|
||||||
|
MULQ 32(BX)
|
||||||
|
MOVQ AX, R15
|
||||||
|
MOVQ DX, R14
|
||||||
|
|
||||||
|
// r4 += a1×b3
|
||||||
|
MOVQ 8(CX), AX
|
||||||
|
MULQ 24(BX)
|
||||||
|
ADDQ AX, R15
|
||||||
|
ADCQ DX, R14
|
||||||
|
|
||||||
|
// r4 += a2×b2
|
||||||
|
MOVQ 16(CX), AX
|
||||||
|
MULQ 16(BX)
|
||||||
|
ADDQ AX, R15
|
||||||
|
ADCQ DX, R14
|
||||||
|
|
||||||
|
// r4 += a3×b1
|
||||||
|
MOVQ 24(CX), AX
|
||||||
|
MULQ 8(BX)
|
||||||
|
ADDQ AX, R15
|
||||||
|
ADCQ DX, R14
|
||||||
|
|
||||||
|
// r4 += a4×b0
|
||||||
|
MOVQ 32(CX), AX
|
||||||
|
MULQ (BX)
|
||||||
|
ADDQ AX, R15
|
||||||
|
ADCQ DX, R14
|
||||||
|
|
||||||
|
// First reduction chain
|
||||||
|
MOVQ $0x0007ffffffffffff, AX
|
||||||
|
SHLQ $0x0d, DI, SI
|
||||||
|
SHLQ $0x0d, R9, R8
|
||||||
|
SHLQ $0x0d, R11, R10
|
||||||
|
SHLQ $0x0d, R13, R12
|
||||||
|
SHLQ $0x0d, R15, R14
|
||||||
|
ANDQ AX, DI
|
||||||
|
IMUL3Q $0x13, R14, R14
|
||||||
|
ADDQ R14, DI
|
||||||
|
ANDQ AX, R9
|
||||||
|
ADDQ SI, R9
|
||||||
|
ANDQ AX, R11
|
||||||
|
ADDQ R8, R11
|
||||||
|
ANDQ AX, R13
|
||||||
|
ADDQ R10, R13
|
||||||
|
ANDQ AX, R15
|
||||||
|
ADDQ R12, R15
|
||||||
|
|
||||||
|
// Second reduction chain (carryPropagate)
|
||||||
|
MOVQ DI, SI
|
||||||
|
SHRQ $0x33, SI
|
||||||
|
MOVQ R9, R8
|
||||||
|
SHRQ $0x33, R8
|
||||||
|
MOVQ R11, R10
|
||||||
|
SHRQ $0x33, R10
|
||||||
|
MOVQ R13, R12
|
||||||
|
SHRQ $0x33, R12
|
||||||
|
MOVQ R15, R14
|
||||||
|
SHRQ $0x33, R14
|
||||||
|
ANDQ AX, DI
|
||||||
|
IMUL3Q $0x13, R14, R14
|
||||||
|
ADDQ R14, DI
|
||||||
|
ANDQ AX, R9
|
||||||
|
ADDQ SI, R9
|
||||||
|
ANDQ AX, R11
|
||||||
|
ADDQ R8, R11
|
||||||
|
ANDQ AX, R13
|
||||||
|
ADDQ R10, R13
|
||||||
|
ANDQ AX, R15
|
||||||
|
ADDQ R12, R15
|
||||||
|
|
||||||
|
// Store output
|
||||||
|
MOVQ out+0(FP), AX
|
||||||
|
MOVQ DI, (AX)
|
||||||
|
MOVQ R9, 8(AX)
|
||||||
|
MOVQ R11, 16(AX)
|
||||||
|
MOVQ R13, 24(AX)
|
||||||
|
MOVQ R15, 32(AX)
|
||||||
|
RET
|
||||||
|
|
||||||
|
// func feSquare(out *Element, a *Element)
|
||||||
|
TEXT ·feSquare(SB), NOSPLIT, $0-16
|
||||||
|
MOVQ a+8(FP), CX
|
||||||
|
|
||||||
|
// r0 = l0×l0
|
||||||
|
MOVQ (CX), AX
|
||||||
|
MULQ (CX)
|
||||||
|
MOVQ AX, SI
|
||||||
|
MOVQ DX, BX
|
||||||
|
|
||||||
|
// r0 += 38×l1×l4
|
||||||
|
MOVQ 8(CX), AX
|
||||||
|
IMUL3Q $0x26, AX, AX
|
||||||
|
MULQ 32(CX)
|
||||||
|
ADDQ AX, SI
|
||||||
|
ADCQ DX, BX
|
||||||
|
|
||||||
|
// r0 += 38×l2×l3
|
||||||
|
MOVQ 16(CX), AX
|
||||||
|
IMUL3Q $0x26, AX, AX
|
||||||
|
MULQ 24(CX)
|
||||||
|
ADDQ AX, SI
|
||||||
|
ADCQ DX, BX
|
||||||
|
|
||||||
|
// r1 = 2×l0×l1
|
||||||
|
MOVQ (CX), AX
|
||||||
|
SHLQ $0x01, AX
|
||||||
|
MULQ 8(CX)
|
||||||
|
MOVQ AX, R8
|
||||||
|
MOVQ DX, DI
|
||||||
|
|
||||||
|
// r1 += 38×l2×l4
|
||||||
|
MOVQ 16(CX), AX
|
||||||
|
IMUL3Q $0x26, AX, AX
|
||||||
|
MULQ 32(CX)
|
||||||
|
ADDQ AX, R8
|
||||||
|
ADCQ DX, DI
|
||||||
|
|
||||||
|
// r1 += 19×l3×l3
|
||||||
|
MOVQ 24(CX), AX
|
||||||
|
IMUL3Q $0x13, AX, AX
|
||||||
|
MULQ 24(CX)
|
||||||
|
ADDQ AX, R8
|
||||||
|
ADCQ DX, DI
|
||||||
|
|
||||||
|
// r2 = 2×l0×l2
|
||||||
|
MOVQ (CX), AX
|
||||||
|
SHLQ $0x01, AX
|
||||||
|
MULQ 16(CX)
|
||||||
|
MOVQ AX, R10
|
||||||
|
MOVQ DX, R9
|
||||||
|
|
||||||
|
// r2 += l1×l1
|
||||||
|
MOVQ 8(CX), AX
|
||||||
|
MULQ 8(CX)
|
||||||
|
ADDQ AX, R10
|
||||||
|
ADCQ DX, R9
|
||||||
|
|
||||||
|
// r2 += 38×l3×l4
|
||||||
|
MOVQ 24(CX), AX
|
||||||
|
IMUL3Q $0x26, AX, AX
|
||||||
|
MULQ 32(CX)
|
||||||
|
ADDQ AX, R10
|
||||||
|
ADCQ DX, R9
|
||||||
|
|
||||||
|
// r3 = 2×l0×l3
|
||||||
|
MOVQ (CX), AX
|
||||||
|
SHLQ $0x01, AX
|
||||||
|
MULQ 24(CX)
|
||||||
|
MOVQ AX, R12
|
||||||
|
MOVQ DX, R11
|
||||||
|
|
||||||
|
// r3 += 2×l1×l2
|
||||||
|
MOVQ 8(CX), AX
|
||||||
|
IMUL3Q $0x02, AX, AX
|
||||||
|
MULQ 16(CX)
|
||||||
|
ADDQ AX, R12
|
||||||
|
ADCQ DX, R11
|
||||||
|
|
||||||
|
// r3 += 19×l4×l4
|
||||||
|
MOVQ 32(CX), AX
|
||||||
|
IMUL3Q $0x13, AX, AX
|
||||||
|
MULQ 32(CX)
|
||||||
|
ADDQ AX, R12
|
||||||
|
ADCQ DX, R11
|
||||||
|
|
||||||
|
// r4 = 2×l0×l4
|
||||||
|
MOVQ (CX), AX
|
||||||
|
SHLQ $0x01, AX
|
||||||
|
MULQ 32(CX)
|
||||||
|
MOVQ AX, R14
|
||||||
|
MOVQ DX, R13
|
||||||
|
|
||||||
|
// r4 += 2×l1×l3
|
||||||
|
MOVQ 8(CX), AX
|
||||||
|
IMUL3Q $0x02, AX, AX
|
||||||
|
MULQ 24(CX)
|
||||||
|
ADDQ AX, R14
|
||||||
|
ADCQ DX, R13
|
||||||
|
|
||||||
|
// r4 += l2×l2
|
||||||
|
MOVQ 16(CX), AX
|
||||||
|
MULQ 16(CX)
|
||||||
|
ADDQ AX, R14
|
||||||
|
ADCQ DX, R13
|
||||||
|
|
||||||
|
// First reduction chain
|
||||||
|
MOVQ $0x0007ffffffffffff, AX
|
||||||
|
SHLQ $0x0d, SI, BX
|
||||||
|
SHLQ $0x0d, R8, DI
|
||||||
|
SHLQ $0x0d, R10, R9
|
||||||
|
SHLQ $0x0d, R12, R11
|
||||||
|
SHLQ $0x0d, R14, R13
|
||||||
|
ANDQ AX, SI
|
||||||
|
IMUL3Q $0x13, R13, R13
|
||||||
|
ADDQ R13, SI
|
||||||
|
ANDQ AX, R8
|
||||||
|
ADDQ BX, R8
|
||||||
|
ANDQ AX, R10
|
||||||
|
ADDQ DI, R10
|
||||||
|
ANDQ AX, R12
|
||||||
|
ADDQ R9, R12
|
||||||
|
ANDQ AX, R14
|
||||||
|
ADDQ R11, R14
|
||||||
|
|
||||||
|
// Second reduction chain (carryPropagate)
|
||||||
|
MOVQ SI, BX
|
||||||
|
SHRQ $0x33, BX
|
||||||
|
MOVQ R8, DI
|
||||||
|
SHRQ $0x33, DI
|
||||||
|
MOVQ R10, R9
|
||||||
|
SHRQ $0x33, R9
|
||||||
|
MOVQ R12, R11
|
||||||
|
SHRQ $0x33, R11
|
||||||
|
MOVQ R14, R13
|
||||||
|
SHRQ $0x33, R13
|
||||||
|
ANDQ AX, SI
|
||||||
|
IMUL3Q $0x13, R13, R13
|
||||||
|
ADDQ R13, SI
|
||||||
|
ANDQ AX, R8
|
||||||
|
ADDQ BX, R8
|
||||||
|
ANDQ AX, R10
|
||||||
|
ADDQ DI, R10
|
||||||
|
ANDQ AX, R12
|
||||||
|
ADDQ R9, R12
|
||||||
|
ANDQ AX, R14
|
||||||
|
ADDQ R11, R14
|
||||||
|
|
||||||
|
// Store output
|
||||||
|
MOVQ out+0(FP), AX
|
||||||
|
MOVQ SI, (AX)
|
||||||
|
MOVQ R8, 8(AX)
|
||||||
|
MOVQ R10, 16(AX)
|
||||||
|
MOVQ R12, 24(AX)
|
||||||
|
MOVQ R14, 32(AX)
|
||||||
|
RET
|
||||||
12
vendor/filippo.io/edwards25519/field/fe_amd64_noasm.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// Copyright (c) 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !amd64 || !gc || purego
|
||||||
|
// +build !amd64 !gc purego
|
||||||
|
|
||||||
|
package field
|
||||||
|
|
||||||
|
func feMul(v, x, y *Element) { feMulGeneric(v, x, y) }
|
||||||
|
|
||||||
|
func feSquare(v, x *Element) { feSquareGeneric(v, x) }
|
||||||
16
vendor/filippo.io/edwards25519/field/fe_arm64.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// Copyright (c) 2020 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build arm64 && gc && !purego
|
||||||
|
// +build arm64,gc,!purego
|
||||||
|
|
||||||
|
package field
|
||||||
|
|
||||||
|
//go:noescape
|
||||||
|
func carryPropagate(v *Element)
|
||||||
|
|
||||||
|
func (v *Element) carryPropagate() *Element {
|
||||||
|
carryPropagate(v)
|
||||||
|
return v
|
||||||
|
}
|
||||||
42
vendor/filippo.io/edwards25519/field/fe_arm64.s
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// Copyright (c) 2020 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build arm64,gc,!purego
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// carryPropagate works exactly like carryPropagateGeneric and uses the
|
||||||
|
// same AND, ADD, and LSR+MADD instructions emitted by the compiler, but
|
||||||
|
// avoids loading R0-R4 twice and uses LDP and STP.
|
||||||
|
//
|
||||||
|
// See https://golang.org/issues/43145 for the main compiler issue.
|
||||||
|
//
|
||||||
|
// func carryPropagate(v *Element)
|
||||||
|
TEXT ·carryPropagate(SB),NOFRAME|NOSPLIT,$0-8
|
||||||
|
MOVD v+0(FP), R20
|
||||||
|
|
||||||
|
LDP 0(R20), (R0, R1)
|
||||||
|
LDP 16(R20), (R2, R3)
|
||||||
|
MOVD 32(R20), R4
|
||||||
|
|
||||||
|
AND $0x7ffffffffffff, R0, R10
|
||||||
|
AND $0x7ffffffffffff, R1, R11
|
||||||
|
AND $0x7ffffffffffff, R2, R12
|
||||||
|
AND $0x7ffffffffffff, R3, R13
|
||||||
|
AND $0x7ffffffffffff, R4, R14
|
||||||
|
|
||||||
|
ADD R0>>51, R11, R11
|
||||||
|
ADD R1>>51, R12, R12
|
||||||
|
ADD R2>>51, R13, R13
|
||||||
|
ADD R3>>51, R14, R14
|
||||||
|
// R4>>51 * 19 + R10 -> R10
|
||||||
|
LSR $51, R4, R21
|
||||||
|
MOVD $19, R22
|
||||||
|
MADD R22, R10, R21, R10
|
||||||
|
|
||||||
|
STP (R10, R11), 0(R20)
|
||||||
|
STP (R12, R13), 16(R20)
|
||||||
|
MOVD R14, 32(R20)
|
||||||
|
|
||||||
|
RET
|
||||||
12
vendor/filippo.io/edwards25519/field/fe_arm64_noasm.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// Copyright (c) 2021 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !arm64 || !gc || purego
|
||||||
|
// +build !arm64 !gc purego
|
||||||
|
|
||||||
|
package field
|
||||||
|
|
||||||
|
func (v *Element) carryPropagate() *Element {
|
||||||
|
return v.carryPropagateGeneric()
|
||||||
|
}
|
||||||
264
vendor/filippo.io/edwards25519/field/fe_generic.go
generated
vendored
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
// Copyright (c) 2017 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package field
|
||||||
|
|
||||||
|
import "math/bits"
|
||||||
|
|
||||||
|
// uint128 holds a 128-bit number as two 64-bit limbs, for use with the
|
||||||
|
// bits.Mul64 and bits.Add64 intrinsics.
|
||||||
|
type uint128 struct {
|
||||||
|
lo, hi uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// mul64 returns a * b.
|
||||||
|
func mul64(a, b uint64) uint128 {
|
||||||
|
hi, lo := bits.Mul64(a, b)
|
||||||
|
return uint128{lo, hi}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addMul64 returns v + a * b.
|
||||||
|
func addMul64(v uint128, a, b uint64) uint128 {
|
||||||
|
hi, lo := bits.Mul64(a, b)
|
||||||
|
lo, c := bits.Add64(lo, v.lo, 0)
|
||||||
|
hi, _ = bits.Add64(hi, v.hi, c)
|
||||||
|
return uint128{lo, hi}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shiftRightBy51 returns a >> 51. a is assumed to be at most 115 bits.
|
||||||
|
func shiftRightBy51(a uint128) uint64 {
|
||||||
|
return (a.hi << (64 - 51)) | (a.lo >> 51)
|
||||||
|
}
|
||||||
|
|
||||||
|
func feMulGeneric(v, a, b *Element) {
|
||||||
|
a0 := a.l0
|
||||||
|
a1 := a.l1
|
||||||
|
a2 := a.l2
|
||||||
|
a3 := a.l3
|
||||||
|
a4 := a.l4
|
||||||
|
|
||||||
|
b0 := b.l0
|
||||||
|
b1 := b.l1
|
||||||
|
b2 := b.l2
|
||||||
|
b3 := b.l3
|
||||||
|
b4 := b.l4
|
||||||
|
|
||||||
|
// Limb multiplication works like pen-and-paper columnar multiplication, but
|
||||||
|
// with 51-bit limbs instead of digits.
|
||||||
|
//
|
||||||
|
// a4 a3 a2 a1 a0 x
|
||||||
|
// b4 b3 b2 b1 b0 =
|
||||||
|
// ------------------------
|
||||||
|
// a4b0 a3b0 a2b0 a1b0 a0b0 +
|
||||||
|
// a4b1 a3b1 a2b1 a1b1 a0b1 +
|
||||||
|
// a4b2 a3b2 a2b2 a1b2 a0b2 +
|
||||||
|
// a4b3 a3b3 a2b3 a1b3 a0b3 +
|
||||||
|
// a4b4 a3b4 a2b4 a1b4 a0b4 =
|
||||||
|
// ----------------------------------------------
|
||||||
|
// r8 r7 r6 r5 r4 r3 r2 r1 r0
|
||||||
|
//
|
||||||
|
// We can then use the reduction identity (a * 2²⁵⁵ + b = a * 19 + b) to
|
||||||
|
// reduce the limbs that would overflow 255 bits. r5 * 2²⁵⁵ becomes 19 * r5,
|
||||||
|
// r6 * 2³⁰⁶ becomes 19 * r6 * 2⁵¹, etc.
|
||||||
|
//
|
||||||
|
// Reduction can be carried out simultaneously to multiplication. For
|
||||||
|
// example, we do not compute r5: whenever the result of a multiplication
|
||||||
|
// belongs to r5, like a1b4, we multiply it by 19 and add the result to r0.
|
||||||
|
//
|
||||||
|
// a4b0 a3b0 a2b0 a1b0 a0b0 +
|
||||||
|
// a3b1 a2b1 a1b1 a0b1 19×a4b1 +
|
||||||
|
// a2b2 a1b2 a0b2 19×a4b2 19×a3b2 +
|
||||||
|
// a1b3 a0b3 19×a4b3 19×a3b3 19×a2b3 +
|
||||||
|
// a0b4 19×a4b4 19×a3b4 19×a2b4 19×a1b4 =
|
||||||
|
// --------------------------------------
|
||||||
|
// r4 r3 r2 r1 r0
|
||||||
|
//
|
||||||
|
// Finally we add up the columns into wide, overlapping limbs.
|
||||||
|
|
||||||
|
a1_19 := a1 * 19
|
||||||
|
a2_19 := a2 * 19
|
||||||
|
a3_19 := a3 * 19
|
||||||
|
a4_19 := a4 * 19
|
||||||
|
|
||||||
|
// r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1)
|
||||||
|
r0 := mul64(a0, b0)
|
||||||
|
r0 = addMul64(r0, a1_19, b4)
|
||||||
|
r0 = addMul64(r0, a2_19, b3)
|
||||||
|
r0 = addMul64(r0, a3_19, b2)
|
||||||
|
r0 = addMul64(r0, a4_19, b1)
|
||||||
|
|
||||||
|
// r1 = a0×b1 + a1×b0 + 19×(a2×b4 + a3×b3 + a4×b2)
|
||||||
|
r1 := mul64(a0, b1)
|
||||||
|
r1 = addMul64(r1, a1, b0)
|
||||||
|
r1 = addMul64(r1, a2_19, b4)
|
||||||
|
r1 = addMul64(r1, a3_19, b3)
|
||||||
|
r1 = addMul64(r1, a4_19, b2)
|
||||||
|
|
||||||
|
// r2 = a0×b2 + a1×b1 + a2×b0 + 19×(a3×b4 + a4×b3)
|
||||||
|
r2 := mul64(a0, b2)
|
||||||
|
r2 = addMul64(r2, a1, b1)
|
||||||
|
r2 = addMul64(r2, a2, b0)
|
||||||
|
r2 = addMul64(r2, a3_19, b4)
|
||||||
|
r2 = addMul64(r2, a4_19, b3)
|
||||||
|
|
||||||
|
// r3 = a0×b3 + a1×b2 + a2×b1 + a3×b0 + 19×a4×b4
|
||||||
|
r3 := mul64(a0, b3)
|
||||||
|
r3 = addMul64(r3, a1, b2)
|
||||||
|
r3 = addMul64(r3, a2, b1)
|
||||||
|
r3 = addMul64(r3, a3, b0)
|
||||||
|
r3 = addMul64(r3, a4_19, b4)
|
||||||
|
|
||||||
|
// r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0
|
||||||
|
r4 := mul64(a0, b4)
|
||||||
|
r4 = addMul64(r4, a1, b3)
|
||||||
|
r4 = addMul64(r4, a2, b2)
|
||||||
|
r4 = addMul64(r4, a3, b1)
|
||||||
|
r4 = addMul64(r4, a4, b0)
|
||||||
|
|
||||||
|
// After the multiplication, we need to reduce (carry) the five coefficients
|
||||||
|
// to obtain a result with limbs that are at most slightly larger than 2⁵¹,
|
||||||
|
// to respect the Element invariant.
|
||||||
|
//
|
||||||
|
// Overall, the reduction works the same as carryPropagate, except with
|
||||||
|
// wider inputs: we take the carry for each coefficient by shifting it right
|
||||||
|
// by 51, and add it to the limb above it. The top carry is multiplied by 19
|
||||||
|
// according to the reduction identity and added to the lowest limb.
|
||||||
|
//
|
||||||
|
// The largest coefficient (r0) will be at most 111 bits, which guarantees
|
||||||
|
// that all carries are at most 111 - 51 = 60 bits, which fits in a uint64.
|
||||||
|
//
|
||||||
|
// r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1)
|
||||||
|
// r0 < 2⁵²×2⁵² + 19×(2⁵²×2⁵² + 2⁵²×2⁵² + 2⁵²×2⁵² + 2⁵²×2⁵²)
|
||||||
|
// r0 < (1 + 19 × 4) × 2⁵² × 2⁵²
|
||||||
|
// r0 < 2⁷ × 2⁵² × 2⁵²
|
||||||
|
// r0 < 2¹¹¹
|
||||||
|
//
|
||||||
|
// Moreover, the top coefficient (r4) is at most 107 bits, so c4 is at most
|
||||||
|
// 56 bits, and c4 * 19 is at most 61 bits, which again fits in a uint64 and
|
||||||
|
// allows us to easily apply the reduction identity.
|
||||||
|
//
|
||||||
|
// r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0
|
||||||
|
// r4 < 5 × 2⁵² × 2⁵²
|
||||||
|
// r4 < 2¹⁰⁷
|
||||||
|
//
|
||||||
|
|
||||||
|
c0 := shiftRightBy51(r0)
|
||||||
|
c1 := shiftRightBy51(r1)
|
||||||
|
c2 := shiftRightBy51(r2)
|
||||||
|
c3 := shiftRightBy51(r3)
|
||||||
|
c4 := shiftRightBy51(r4)
|
||||||
|
|
||||||
|
rr0 := r0.lo&maskLow51Bits + c4*19
|
||||||
|
rr1 := r1.lo&maskLow51Bits + c0
|
||||||
|
rr2 := r2.lo&maskLow51Bits + c1
|
||||||
|
rr3 := r3.lo&maskLow51Bits + c2
|
||||||
|
rr4 := r4.lo&maskLow51Bits + c3
|
||||||
|
|
||||||
|
// Now all coefficients fit into 64-bit registers but are still too large to
|
||||||
|
// be passed around as a Element. We therefore do one last carry chain,
|
||||||
|
// where the carries will be small enough to fit in the wiggle room above 2⁵¹.
|
||||||
|
*v = Element{rr0, rr1, rr2, rr3, rr4}
|
||||||
|
v.carryPropagate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func feSquareGeneric(v, a *Element) {
|
||||||
|
l0 := a.l0
|
||||||
|
l1 := a.l1
|
||||||
|
l2 := a.l2
|
||||||
|
l3 := a.l3
|
||||||
|
l4 := a.l4
|
||||||
|
|
||||||
|
// Squaring works precisely like multiplication above, but thanks to its
|
||||||
|
// symmetry we get to group a few terms together.
|
||||||
|
//
|
||||||
|
// l4 l3 l2 l1 l0 x
|
||||||
|
// l4 l3 l2 l1 l0 =
|
||||||
|
// ------------------------
|
||||||
|
// l4l0 l3l0 l2l0 l1l0 l0l0 +
|
||||||
|
// l4l1 l3l1 l2l1 l1l1 l0l1 +
|
||||||
|
// l4l2 l3l2 l2l2 l1l2 l0l2 +
|
||||||
|
// l4l3 l3l3 l2l3 l1l3 l0l3 +
|
||||||
|
// l4l4 l3l4 l2l4 l1l4 l0l4 =
|
||||||
|
// ----------------------------------------------
|
||||||
|
// r8 r7 r6 r5 r4 r3 r2 r1 r0
|
||||||
|
//
|
||||||
|
// l4l0 l3l0 l2l0 l1l0 l0l0 +
|
||||||
|
// l3l1 l2l1 l1l1 l0l1 19×l4l1 +
|
||||||
|
// l2l2 l1l2 l0l2 19×l4l2 19×l3l2 +
|
||||||
|
// l1l3 l0l3 19×l4l3 19×l3l3 19×l2l3 +
|
||||||
|
// l0l4 19×l4l4 19×l3l4 19×l2l4 19×l1l4 =
|
||||||
|
// --------------------------------------
|
||||||
|
// r4 r3 r2 r1 r0
|
||||||
|
//
|
||||||
|
// With precomputed 2×, 19×, and 2×19× terms, we can compute each limb with
|
||||||
|
// only three Mul64 and four Add64, instead of five and eight.
|
||||||
|
|
||||||
|
l0_2 := l0 * 2
|
||||||
|
l1_2 := l1 * 2
|
||||||
|
|
||||||
|
l1_38 := l1 * 38
|
||||||
|
l2_38 := l2 * 38
|
||||||
|
l3_38 := l3 * 38
|
||||||
|
|
||||||
|
l3_19 := l3 * 19
|
||||||
|
l4_19 := l4 * 19
|
||||||
|
|
||||||
|
// r0 = l0×l0 + 19×(l1×l4 + l2×l3 + l3×l2 + l4×l1) = l0×l0 + 19×2×(l1×l4 + l2×l3)
|
||||||
|
r0 := mul64(l0, l0)
|
||||||
|
r0 = addMul64(r0, l1_38, l4)
|
||||||
|
r0 = addMul64(r0, l2_38, l3)
|
||||||
|
|
||||||
|
// r1 = l0×l1 + l1×l0 + 19×(l2×l4 + l3×l3 + l4×l2) = 2×l0×l1 + 19×2×l2×l4 + 19×l3×l3
|
||||||
|
r1 := mul64(l0_2, l1)
|
||||||
|
r1 = addMul64(r1, l2_38, l4)
|
||||||
|
r1 = addMul64(r1, l3_19, l3)
|
||||||
|
|
||||||
|
// r2 = l0×l2 + l1×l1 + l2×l0 + 19×(l3×l4 + l4×l3) = 2×l0×l2 + l1×l1 + 19×2×l3×l4
|
||||||
|
r2 := mul64(l0_2, l2)
|
||||||
|
r2 = addMul64(r2, l1, l1)
|
||||||
|
r2 = addMul64(r2, l3_38, l4)
|
||||||
|
|
||||||
|
// r3 = l0×l3 + l1×l2 + l2×l1 + l3×l0 + 19×l4×l4 = 2×l0×l3 + 2×l1×l2 + 19×l4×l4
|
||||||
|
r3 := mul64(l0_2, l3)
|
||||||
|
r3 = addMul64(r3, l1_2, l2)
|
||||||
|
r3 = addMul64(r3, l4_19, l4)
|
||||||
|
|
||||||
|
// r4 = l0×l4 + l1×l3 + l2×l2 + l3×l1 + l4×l0 = 2×l0×l4 + 2×l1×l3 + l2×l2
|
||||||
|
r4 := mul64(l0_2, l4)
|
||||||
|
r4 = addMul64(r4, l1_2, l3)
|
||||||
|
r4 = addMul64(r4, l2, l2)
|
||||||
|
|
||||||
|
c0 := shiftRightBy51(r0)
|
||||||
|
c1 := shiftRightBy51(r1)
|
||||||
|
c2 := shiftRightBy51(r2)
|
||||||
|
c3 := shiftRightBy51(r3)
|
||||||
|
c4 := shiftRightBy51(r4)
|
||||||
|
|
||||||
|
rr0 := r0.lo&maskLow51Bits + c4*19
|
||||||
|
rr1 := r1.lo&maskLow51Bits + c0
|
||||||
|
rr2 := r2.lo&maskLow51Bits + c1
|
||||||
|
rr3 := r3.lo&maskLow51Bits + c2
|
||||||
|
rr4 := r4.lo&maskLow51Bits + c3
|
||||||
|
|
||||||
|
*v = Element{rr0, rr1, rr2, rr3, rr4}
|
||||||
|
v.carryPropagate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// carryPropagate brings the limbs below 52 bits by applying the reduction
|
||||||
|
// identity (a * 2²⁵⁵ + b = a * 19 + b) to the l4 carry.
|
||||||
|
func (v *Element) carryPropagateGeneric() *Element {
|
||||||
|
c0 := v.l0 >> 51
|
||||||
|
c1 := v.l1 >> 51
|
||||||
|
c2 := v.l2 >> 51
|
||||||
|
c3 := v.l3 >> 51
|
||||||
|
c4 := v.l4 >> 51
|
||||||
|
|
||||||
|
v.l0 = v.l0&maskLow51Bits + c4*19
|
||||||
|
v.l1 = v.l1&maskLow51Bits + c0
|
||||||
|
v.l2 = v.l2&maskLow51Bits + c1
|
||||||
|
v.l3 = v.l3&maskLow51Bits + c2
|
||||||
|
v.l4 = v.l4&maskLow51Bits + c3
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
1027
vendor/filippo.io/edwards25519/scalar.go
generated
vendored
Normal file
214
vendor/filippo.io/edwards25519/scalarmult.go
generated
vendored
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
// Copyright (c) 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package edwards25519
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
// basepointTable is a set of 32 affineLookupTables, where table i is generated
|
||||||
|
// from 256i * basepoint. It is precomputed the first time it's used.
|
||||||
|
func basepointTable() *[32]affineLookupTable {
|
||||||
|
basepointTablePrecomp.initOnce.Do(func() {
|
||||||
|
p := NewGeneratorPoint()
|
||||||
|
for i := 0; i < 32; i++ {
|
||||||
|
basepointTablePrecomp.table[i].FromP3(p)
|
||||||
|
for j := 0; j < 8; j++ {
|
||||||
|
p.Add(p, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return &basepointTablePrecomp.table
|
||||||
|
}
|
||||||
|
|
||||||
|
var basepointTablePrecomp struct {
|
||||||
|
table [32]affineLookupTable
|
||||||
|
initOnce sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScalarBaseMult sets v = x * B, where B is the canonical generator, and
|
||||||
|
// returns v.
|
||||||
|
//
|
||||||
|
// The scalar multiplication is done in constant time.
|
||||||
|
func (v *Point) ScalarBaseMult(x *Scalar) *Point {
|
||||||
|
basepointTable := basepointTable()
|
||||||
|
|
||||||
|
// Write x = sum(x_i * 16^i) so x*B = sum( B*x_i*16^i )
|
||||||
|
// as described in the Ed25519 paper
|
||||||
|
//
|
||||||
|
// Group even and odd coefficients
|
||||||
|
// x*B = x_0*16^0*B + x_2*16^2*B + ... + x_62*16^62*B
|
||||||
|
// + x_1*16^1*B + x_3*16^3*B + ... + x_63*16^63*B
|
||||||
|
// x*B = x_0*16^0*B + x_2*16^2*B + ... + x_62*16^62*B
|
||||||
|
// + 16*( x_1*16^0*B + x_3*16^2*B + ... + x_63*16^62*B)
|
||||||
|
//
|
||||||
|
// We use a lookup table for each i to get x_i*16^(2*i)*B
|
||||||
|
// and do four doublings to multiply by 16.
|
||||||
|
digits := x.signedRadix16()
|
||||||
|
|
||||||
|
multiple := &affineCached{}
|
||||||
|
tmp1 := &projP1xP1{}
|
||||||
|
tmp2 := &projP2{}
|
||||||
|
|
||||||
|
// Accumulate the odd components first
|
||||||
|
v.Set(NewIdentityPoint())
|
||||||
|
for i := 1; i < 64; i += 2 {
|
||||||
|
basepointTable[i/2].SelectInto(multiple, digits[i])
|
||||||
|
tmp1.AddAffine(v, multiple)
|
||||||
|
v.fromP1xP1(tmp1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiply by 16
|
||||||
|
tmp2.FromP3(v) // tmp2 = v in P2 coords
|
||||||
|
tmp1.Double(tmp2) // tmp1 = 2*v in P1xP1 coords
|
||||||
|
tmp2.FromP1xP1(tmp1) // tmp2 = 2*v in P2 coords
|
||||||
|
tmp1.Double(tmp2) // tmp1 = 4*v in P1xP1 coords
|
||||||
|
tmp2.FromP1xP1(tmp1) // tmp2 = 4*v in P2 coords
|
||||||
|
tmp1.Double(tmp2) // tmp1 = 8*v in P1xP1 coords
|
||||||
|
tmp2.FromP1xP1(tmp1) // tmp2 = 8*v in P2 coords
|
||||||
|
tmp1.Double(tmp2) // tmp1 = 16*v in P1xP1 coords
|
||||||
|
v.fromP1xP1(tmp1) // now v = 16*(odd components)
|
||||||
|
|
||||||
|
// Accumulate the even components
|
||||||
|
for i := 0; i < 64; i += 2 {
|
||||||
|
basepointTable[i/2].SelectInto(multiple, digits[i])
|
||||||
|
tmp1.AddAffine(v, multiple)
|
||||||
|
v.fromP1xP1(tmp1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScalarMult sets v = x * q, and returns v.
|
||||||
|
//
|
||||||
|
// The scalar multiplication is done in constant time.
|
||||||
|
func (v *Point) ScalarMult(x *Scalar, q *Point) *Point {
|
||||||
|
checkInitialized(q)
|
||||||
|
|
||||||
|
var table projLookupTable
|
||||||
|
table.FromP3(q)
|
||||||
|
|
||||||
|
// Write x = sum(x_i * 16^i)
|
||||||
|
// so x*Q = sum( Q*x_i*16^i )
|
||||||
|
// = Q*x_0 + 16*(Q*x_1 + 16*( ... + Q*x_63) ... )
|
||||||
|
// <------compute inside out---------
|
||||||
|
//
|
||||||
|
// We use the lookup table to get the x_i*Q values
|
||||||
|
// and do four doublings to compute 16*Q
|
||||||
|
digits := x.signedRadix16()
|
||||||
|
|
||||||
|
// Unwrap first loop iteration to save computing 16*identity
|
||||||
|
multiple := &projCached{}
|
||||||
|
tmp1 := &projP1xP1{}
|
||||||
|
tmp2 := &projP2{}
|
||||||
|
table.SelectInto(multiple, digits[63])
|
||||||
|
|
||||||
|
v.Set(NewIdentityPoint())
|
||||||
|
tmp1.Add(v, multiple) // tmp1 = x_63*Q in P1xP1 coords
|
||||||
|
for i := 62; i >= 0; i-- {
|
||||||
|
tmp2.FromP1xP1(tmp1) // tmp2 = (prev) in P2 coords
|
||||||
|
tmp1.Double(tmp2) // tmp1 = 2*(prev) in P1xP1 coords
|
||||||
|
tmp2.FromP1xP1(tmp1) // tmp2 = 2*(prev) in P2 coords
|
||||||
|
tmp1.Double(tmp2) // tmp1 = 4*(prev) in P1xP1 coords
|
||||||
|
tmp2.FromP1xP1(tmp1) // tmp2 = 4*(prev) in P2 coords
|
||||||
|
tmp1.Double(tmp2) // tmp1 = 8*(prev) in P1xP1 coords
|
||||||
|
tmp2.FromP1xP1(tmp1) // tmp2 = 8*(prev) in P2 coords
|
||||||
|
tmp1.Double(tmp2) // tmp1 = 16*(prev) in P1xP1 coords
|
||||||
|
v.fromP1xP1(tmp1) // v = 16*(prev) in P3 coords
|
||||||
|
table.SelectInto(multiple, digits[i])
|
||||||
|
tmp1.Add(v, multiple) // tmp1 = x_i*Q + 16*(prev) in P1xP1 coords
|
||||||
|
}
|
||||||
|
v.fromP1xP1(tmp1)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// basepointNafTable is the nafLookupTable8 for the basepoint.
|
||||||
|
// It is precomputed the first time it's used.
|
||||||
|
func basepointNafTable() *nafLookupTable8 {
|
||||||
|
basepointNafTablePrecomp.initOnce.Do(func() {
|
||||||
|
basepointNafTablePrecomp.table.FromP3(NewGeneratorPoint())
|
||||||
|
})
|
||||||
|
return &basepointNafTablePrecomp.table
|
||||||
|
}
|
||||||
|
|
||||||
|
var basepointNafTablePrecomp struct {
|
||||||
|
table nafLookupTable8
|
||||||
|
initOnce sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
// VarTimeDoubleScalarBaseMult sets v = a * A + b * B, where B is the canonical
|
||||||
|
// generator, and returns v.
|
||||||
|
//
|
||||||
|
// Execution time depends on the inputs.
|
||||||
|
func (v *Point) VarTimeDoubleScalarBaseMult(a *Scalar, A *Point, b *Scalar) *Point {
|
||||||
|
checkInitialized(A)
|
||||||
|
|
||||||
|
// Similarly to the single variable-base approach, we compute
|
||||||
|
// digits and use them with a lookup table. However, because
|
||||||
|
// we are allowed to do variable-time operations, we don't
|
||||||
|
// need constant-time lookups or constant-time digit
|
||||||
|
// computations.
|
||||||
|
//
|
||||||
|
// So we use a non-adjacent form of some width w instead of
|
||||||
|
// radix 16. This is like a binary representation (one digit
|
||||||
|
// for each binary place) but we allow the digits to grow in
|
||||||
|
// magnitude up to 2^{w-1} so that the nonzero digits are as
|
||||||
|
// sparse as possible. Intuitively, this "condenses" the
|
||||||
|
// "mass" of the scalar onto sparse coefficients (meaning
|
||||||
|
// fewer additions).
|
||||||
|
|
||||||
|
basepointNafTable := basepointNafTable()
|
||||||
|
var aTable nafLookupTable5
|
||||||
|
aTable.FromP3(A)
|
||||||
|
// Because the basepoint is fixed, we can use a wider NAF
|
||||||
|
// corresponding to a bigger table.
|
||||||
|
aNaf := a.nonAdjacentForm(5)
|
||||||
|
bNaf := b.nonAdjacentForm(8)
|
||||||
|
|
||||||
|
// Find the first nonzero coefficient.
|
||||||
|
i := 255
|
||||||
|
for j := i; j >= 0; j-- {
|
||||||
|
if aNaf[j] != 0 || bNaf[j] != 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
multA := &projCached{}
|
||||||
|
multB := &affineCached{}
|
||||||
|
tmp1 := &projP1xP1{}
|
||||||
|
tmp2 := &projP2{}
|
||||||
|
tmp2.Zero()
|
||||||
|
|
||||||
|
// Move from high to low bits, doubling the accumulator
|
||||||
|
// at each iteration and checking whether there is a nonzero
|
||||||
|
// coefficient to look up a multiple of.
|
||||||
|
for ; i >= 0; i-- {
|
||||||
|
tmp1.Double(tmp2)
|
||||||
|
|
||||||
|
// Only update v if we have a nonzero coeff to add in.
|
||||||
|
if aNaf[i] > 0 {
|
||||||
|
v.fromP1xP1(tmp1)
|
||||||
|
aTable.SelectInto(multA, aNaf[i])
|
||||||
|
tmp1.Add(v, multA)
|
||||||
|
} else if aNaf[i] < 0 {
|
||||||
|
v.fromP1xP1(tmp1)
|
||||||
|
aTable.SelectInto(multA, -aNaf[i])
|
||||||
|
tmp1.Sub(v, multA)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bNaf[i] > 0 {
|
||||||
|
v.fromP1xP1(tmp1)
|
||||||
|
basepointNafTable.SelectInto(multB, bNaf[i])
|
||||||
|
tmp1.AddAffine(v, multB)
|
||||||
|
} else if bNaf[i] < 0 {
|
||||||
|
v.fromP1xP1(tmp1)
|
||||||
|
basepointNafTable.SelectInto(multB, -bNaf[i])
|
||||||
|
tmp1.SubAffine(v, multB)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp2.FromP1xP1(tmp1)
|
||||||
|
}
|
||||||
|
|
||||||
|
v.fromP2(tmp2)
|
||||||
|
return v
|
||||||
|
}
|
||||||
129
vendor/filippo.io/edwards25519/tables.go
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
// Copyright (c) 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package edwards25519
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/subtle"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A dynamic lookup table for variable-base, constant-time scalar muls.
|
||||||
|
type projLookupTable struct {
|
||||||
|
points [8]projCached
|
||||||
|
}
|
||||||
|
|
||||||
|
// A precomputed lookup table for fixed-base, constant-time scalar muls.
|
||||||
|
type affineLookupTable struct {
|
||||||
|
points [8]affineCached
|
||||||
|
}
|
||||||
|
|
||||||
|
// A dynamic lookup table for variable-base, variable-time scalar muls.
|
||||||
|
type nafLookupTable5 struct {
|
||||||
|
points [8]projCached
|
||||||
|
}
|
||||||
|
|
||||||
|
// A precomputed lookup table for fixed-base, variable-time scalar muls.
|
||||||
|
type nafLookupTable8 struct {
|
||||||
|
points [64]affineCached
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructors.
|
||||||
|
|
||||||
|
// Builds a lookup table at runtime. Fast.
|
||||||
|
func (v *projLookupTable) FromP3(q *Point) {
|
||||||
|
// Goal: v.points[i] = (i+1)*Q, i.e., Q, 2Q, ..., 8Q
|
||||||
|
// This allows lookup of -8Q, ..., -Q, 0, Q, ..., 8Q
|
||||||
|
v.points[0].FromP3(q)
|
||||||
|
tmpP3 := Point{}
|
||||||
|
tmpP1xP1 := projP1xP1{}
|
||||||
|
for i := 0; i < 7; i++ {
|
||||||
|
// Compute (i+1)*Q as Q + i*Q and convert to a ProjCached
|
||||||
|
// This is needlessly complicated because the API has explicit
|
||||||
|
// recievers instead of creating stack objects and relying on RVO
|
||||||
|
v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.Add(q, &v.points[i])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is not optimised for speed; fixed-base tables should be precomputed.
|
||||||
|
func (v *affineLookupTable) FromP3(q *Point) {
|
||||||
|
// Goal: v.points[i] = (i+1)*Q, i.e., Q, 2Q, ..., 8Q
|
||||||
|
// This allows lookup of -8Q, ..., -Q, 0, Q, ..., 8Q
|
||||||
|
v.points[0].FromP3(q)
|
||||||
|
tmpP3 := Point{}
|
||||||
|
tmpP1xP1 := projP1xP1{}
|
||||||
|
for i := 0; i < 7; i++ {
|
||||||
|
// Compute (i+1)*Q as Q + i*Q and convert to AffineCached
|
||||||
|
v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.AddAffine(q, &v.points[i])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builds a lookup table at runtime. Fast.
|
||||||
|
func (v *nafLookupTable5) FromP3(q *Point) {
|
||||||
|
// Goal: v.points[i] = (2*i+1)*Q, i.e., Q, 3Q, 5Q, ..., 15Q
|
||||||
|
// This allows lookup of -15Q, ..., -3Q, -Q, 0, Q, 3Q, ..., 15Q
|
||||||
|
v.points[0].FromP3(q)
|
||||||
|
q2 := Point{}
|
||||||
|
q2.Add(q, q)
|
||||||
|
tmpP3 := Point{}
|
||||||
|
tmpP1xP1 := projP1xP1{}
|
||||||
|
for i := 0; i < 7; i++ {
|
||||||
|
v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.Add(&q2, &v.points[i])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is not optimised for speed; fixed-base tables should be precomputed.
|
||||||
|
func (v *nafLookupTable8) FromP3(q *Point) {
|
||||||
|
v.points[0].FromP3(q)
|
||||||
|
q2 := Point{}
|
||||||
|
q2.Add(q, q)
|
||||||
|
tmpP3 := Point{}
|
||||||
|
tmpP1xP1 := projP1xP1{}
|
||||||
|
for i := 0; i < 63; i++ {
|
||||||
|
v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.AddAffine(&q2, &v.points[i])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selectors.
|
||||||
|
|
||||||
|
// Set dest to x*Q, where -8 <= x <= 8, in constant time.
|
||||||
|
func (v *projLookupTable) SelectInto(dest *projCached, x int8) {
|
||||||
|
// Compute xabs = |x|
|
||||||
|
xmask := x >> 7
|
||||||
|
xabs := uint8((x + xmask) ^ xmask)
|
||||||
|
|
||||||
|
dest.Zero()
|
||||||
|
for j := 1; j <= 8; j++ {
|
||||||
|
// Set dest = j*Q if |x| = j
|
||||||
|
cond := subtle.ConstantTimeByteEq(xabs, uint8(j))
|
||||||
|
dest.Select(&v.points[j-1], dest, cond)
|
||||||
|
}
|
||||||
|
// Now dest = |x|*Q, conditionally negate to get x*Q
|
||||||
|
dest.CondNeg(int(xmask & 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set dest to x*Q, where -8 <= x <= 8, in constant time.
|
||||||
|
func (v *affineLookupTable) SelectInto(dest *affineCached, x int8) {
|
||||||
|
// Compute xabs = |x|
|
||||||
|
xmask := x >> 7
|
||||||
|
xabs := uint8((x + xmask) ^ xmask)
|
||||||
|
|
||||||
|
dest.Zero()
|
||||||
|
for j := 1; j <= 8; j++ {
|
||||||
|
// Set dest = j*Q if |x| = j
|
||||||
|
cond := subtle.ConstantTimeByteEq(xabs, uint8(j))
|
||||||
|
dest.Select(&v.points[j-1], dest, cond)
|
||||||
|
}
|
||||||
|
// Now dest = |x|*Q, conditionally negate to get x*Q
|
||||||
|
dest.CondNeg(int(xmask & 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given odd x with 0 < x < 2^4, return x*Q (in variable time).
|
||||||
|
func (v *nafLookupTable5) SelectInto(dest *projCached, x int8) {
|
||||||
|
*dest = v.points[x/2]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given odd x with 0 < x < 2^7, return x*Q (in variable time).
|
||||||
|
func (v *nafLookupTable8) SelectInto(dest *affineCached, x int8) {
|
||||||
|
*dest = v.points[x/2]
|
||||||
|
}
|
||||||
24
vendor/github.com/Benau/go_rlottie/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
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
Normal file
@@ -0,0 +1 @@
|
|||||||
|
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
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
/*
|
||||||
|
* 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
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#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
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
#!/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
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
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
Normal file
@@ -0,0 +1,457 @@
|
|||||||
|
#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
Normal file
@@ -0,0 +1,435 @@
|
|||||||
|
/*
|
||||||
|
* 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
Normal file
626
vendor/github.com/Benau/go_rlottie/lottie_lottieitem.h
generated
vendored
Normal file
@@ -0,0 +1,626 @@
|
|||||||
|
/*
|
||||||
|
* 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
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
/*
|
||||||
|
* 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
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
#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
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||