Compare commits

..

91 Commits

Author SHA1 Message Date
Wim
6dafebc7cc Release v1.26.0 (#1957) 2023-01-29 18:26:52 +01:00
Wim
d23f2560d5 Make the cgo lottie a build tag (-tag cgolottie) (#1955)
This should fix #1906 as we don't have any cgo dependencies anymore by
default.
2023-01-29 00:34:27 +01:00
Wim
880586bac4 Update dependencies (#1951) 2023-01-28 22:57:53 +01:00
Wim
eac2a8c8dc Remove mattermost 5 support (#1936) 2023-01-28 21:57:06 +01:00
ilmaisin
4cf313c4c6 Fix the "Someone" nickname problem (whatsapp) (#1931)
* Apply patch to work around the "Someone" nickname problem

* Code cleanup (whatsapp)

* Cleanup

* Code cleanup (whatsapp)

* Re-examine if sender exists

* Cleanup (whatsapp)
2022-12-14 23:47:30 +01:00
ilmaisin
91353d0a4d Handle incoming document captions from whatsapp (#1935) 2022-12-14 23:20:00 +01:00
ilmaisin
0a6d64ab48 Improve attachment handling (whatsapp) (#1928)
* Allow captions for document attachments, too

* Handle audio messages

* Improve attachment handling
2022-11-27 00:55:37 +01:00
Wim
4fd0a76727 Update dependencies (#1929) 2022-11-27 00:42:16 +01:00
Sebastian P
6da9d567dc Implement sending of EventJoinLeave both to and from Mumble (#1915)
* mumble: Implement sending of EventJoinLeave both to and from Mumble (Closes #1435)

* mumble: Break handleUserChange into two functions
2022-11-27 00:17:11 +01:00
Kufat
6d5a3dff22 Allow substitution of bot's nick in RunCommands (irc) (#1890)
* Allow substitution of bot's nick in RunCommands

* Tweak description of "{BOTNICK}"

Made the description of "{BOTNICK}" consistent with that of other keywords
2022-11-27 00:01:10 +01:00
Jair Sanchez
3ad5deaff1 Fix empty messages on IRC (#1897) 2022-11-26 23:53:48 +01:00
Alexander
9bbdf70e69 Fix telegram attachment comment formatting and escaping (#1920) 2022-11-26 23:50:46 +01:00
Lucki
0c83946983 Add Matrix username spoofing (#1875)
* Matrix username spoofing

* Add config sample
2022-09-06 00:46:52 +02:00
Wim
fda05f2262 Update dependencies and fix whatsmeow API changes (#1887)
* Update dependencies

* Fix whatsmau API changes
2022-09-05 21:00:54 +02:00
Buckaroo Banzai
7abf1a5884 Add the beerchat project (a minetest mod) (#1877) 2022-09-05 19:32:30 +02:00
Alexander
365acc36ea Fix error messages in telegram and slack bridges (#1862)
* Fix message html entities escaping when sending to Telegram

* Fix error messages in telegram and slack bridges

Co-authored-by: Wim <wim@42.be>
2022-09-05 19:31:45 +02:00
Glandos
0482cd191d Rename freenode to libera in simple config (#1870)
* freenode to libera

The freenode network doesn't really work anymore

* Missing replacement for Libera
2022-08-13 18:51:21 +02:00
Wim
6a3fc71397 Update dependencies and go1.18 (#1873)
* Update dependencies and go1.18

* Exclude unnecessary linters and update build to go1.18
2022-08-13 16:14:26 +02:00
Alexander
3c4192ebf6 Fix message html entities escaping when sending to Telegram (#1855) 2022-07-07 00:47:50 +02:00
Wim
e450e1c447 Bump version 2022-06-25 01:11:49 +02:00
Wim
20f841c513 Release v1.25.2 (#1853) 2022-06-25 01:00:40 +02:00
Wim
d07a3e09c9 Support mattermost v7 (#1852)
Mattermost api (almost) didn't change between v6.7.x and v7.0
Everything should just work
2022-06-25 00:57:17 +02:00
Wim
4649876956 Update dependencies (#1851) 2022-06-25 00:36:16 +02:00
Sam W
5604d140e3 Ignore events from other guilds, add nosendjoinpart support (discord) (#1846)
* discord: add nosendjoinpart support

This allows the discord bridge to be configured with `nosendjoinpart`,
preventing discord-originating join/part messages from being send to
other bridged platforms.

* discord: Ignore incoming events for other guilds

Ignore all incoming discord events originating from Guild IDs other than
the one we have configured.
This is necessary because discord bots receive events for *all* discord
guilds that they are present in.

Fixes #1612
2022-06-24 23:50:48 +02:00
Wim
8751fb4bb1 Update dependencies (#1841) 2022-06-11 23:07:42 +02:00
Wim
3819062574 Bump version 2022-06-04 15:09:13 +02:00
Wim
051e6e76e9 Release v1.25.1 (#1832) 2022-05-09 23:28:02 +02:00
Wim
1e55dd47f2 Update dependencies (#1831) 2022-05-09 23:00:23 +02:00
Andy
700b95546b Improve Slack attachments formatting (slack) (#1807)
* Improve Slack attachments formatting (slack)

* Add TitleLink
* Add Footer

* Fix linter issues
2022-05-09 22:56:19 +02:00
Wim
2fa96ec0ed Add KeepQuotedReply option for matrix to fix regression (#1823)
Matrix quotes replies and as of matterbridge 1.24.0 we strip those as this causes
issues with bridges support threading and have PreserveThreading enabled.

Introduced via 9a8ce9b17e

But if you for example use mattermost or discord with webhooks you'll need to enable
this if you want something that looks like a reply from matrix.
See issues:
- https://github.com/42wim/matterbridge/issues/1819
- https://github.com/42wim/matterbridge/issues/1780
2022-05-06 23:32:25 +02:00
Wim
81e6f75aa4 Update dependencies (#1822) 2022-05-02 00:10:54 +02:00
Wim
888c8b9a84 Add space between filename and URL (mattermost). Fixes #1820 2022-05-01 23:28:51 +02:00
Wim
e775a8a22e Revert "Clear existing IRC event handlers before connecting new ones (#1795)"
This reverts commit f044b948e2.

Fixes #1815
2022-05-01 22:28:42 +02:00
Alexander
99fbd9cae6 Fix telegram message deletion request (#1818) 2022-05-01 22:00:50 +02:00
Wim
67adad3e08 Update dependencies (#1813) 2022-04-25 23:50:10 +02:00
Wim
2fca3c7563 Add CGO_ENABLED info also to whatsappmulti build 2022-04-24 16:52:25 +02:00
Wim
c3573f1a46 Add CGO_ENABLED info to README 2022-04-24 16:51:16 +02:00
Daniil Suvorov
ee932a9f8e Fix UploadMessagesPhoto for vk community chat (vk) (#1812) 2022-04-22 23:37:09 +02:00
ValdikSS
ce18c948e6 Do not apply any markup to URL entities (telegram) (#1808)
handleEntities code uses simple modification offset which does not
allow to detect whether the offset is placed before or after
the element in already modified string.
This works fine is most cases as Telegram server always sort the
elements by offset, in ascending order.
However, this is not the case when the modification, for example bold
text, is applied to the URL. In this case, the offset of URL and
bold entity is equal, which raises the issue.

This commit introduces additional hack for this case, stripping
any entities which intersect with URL.
2022-04-22 01:00:57 +02:00
Wim
7bc93c5506 Do not modify .webm files (telegram). Fixes #17** (#1802) 2022-04-17 13:35:11 +02:00
Wim
d7cad3b404 Update matterbridge/gomatrix. Fixes #1772 (#1803) 2022-04-12 00:59:30 +02:00
Wim
7740a362c9 Fix build command for latest stable 2022-04-12 00:39:06 +02:00
Wim
281ef53e7d Update dependencies (#1800) 2022-04-12 00:30:21 +02:00
Bryan Davis
f044b948e2 Clear existing IRC event handlers before connecting new ones (#1795)
Clear all existing IRC event handler registrations before registering
new handlers in case we are connecting via a BNC and are seeing
a reconnect.

Fixes #1564
2022-04-07 23:00:17 +02:00
Wim
32474a5f4d Bump version 2022-04-07 22:51:22 +02:00
Wim
26596acf80 Release v1.25.0 (#1793) 2022-04-04 00:44:43 +02:00
Wim
e63870a631 Add whatsapp deprecation warning (#1792) 2022-04-04 00:31:18 +02:00
Wim
ce782ff6fb Change discord non-native threading behaviour (discord) (#1791)
Sorta regression introduced by 9a8ce9b17e
which changes the way we get replies of matrix.

This causes issues like https://github.com/42wim/matterbridge/issues/1780
We "fix" this by mimicking the old behaviour when "PreserveThreading" is
disabled.
2022-04-04 00:19:31 +02:00
Wim
c6716e030c Update dependencies (#1784) 2022-04-01 00:23:19 +02:00
Alexander
4ab72acec6 Ignore sending file with comment, if comment contains IgnoreMessages value (#1783)
* Ignore sending file with comment, if comment contains message to ignore

* Fix linter issue
2022-03-31 23:50:38 +02:00
Alexander
30aae8e257 Multiple media in one message (telegram) (#1779)
* Send multiple images/video/documents as media group

* Fix media caption quotting

* Fix errors handling

* Refactor parent id detection

* Try to reduce cognitive complexity of code

* Remove unused conditional
2022-03-30 22:23:52 +02:00
Alexander
d7b7ff7bb4 Preserve threading for messages with files (slack) (#1781)
* Preserve threading for slack messages with files

* Update bridge/slack/slack.go

Co-authored-by: Wim <wim@42.be>
2022-03-30 22:22:37 +02:00
Alexander
6fe0cff342 Use slack real name as user name (slack) (#1775)
* Use slack real name as user name

* Change slack option UseRealName to UseFullName
2022-03-26 20:52:24 +01:00
Wim
5f75f9886d Update lrstanley/girc dep (#1773) 2022-03-25 22:01:02 +01:00
Alexander
5d9604cd15 Preserve threading from telegram replies (telegram) (#1776)
* Preserve threading from telegram replies

* Add fallback for unthreaded telegram message

* Fix linter issue
2022-03-25 21:58:52 +01:00
Alexander
cc36ebf1c9 Add UseFullName option (telegram) (#1777) 2022-03-25 21:42:28 +01:00
Tobias Niepel
e6adecfd81 Add Dockerfile_whatsappmulti for building with WhatsApp Multi-Device support (Whatsmeow) (#1774)
Co-authored-by: Tobias Niepel <tobias.niepel@obi.de>
2022-03-22 10:05:32 +01:00
Wim
5c8f224e3b Update README 2022-03-20 17:04:33 +01:00
Wim
952221d3b9 Fix linting (whatsapp) 2022-03-20 14:57:48 +01:00
Wim
496d5b4ec7 Add whatsappmulti buildflag for whatsapp with multidevice support (whatsapp) 2022-03-20 14:57:48 +01:00
Wim
2623a412c4 Update vendor (whatsapp) 2022-03-20 14:57:48 +01:00
Wim
d64eed49bc Fix linting (whatsapp) 2022-03-20 14:57:48 +01:00
Wim
fffa29c2f3 Fix channel in video/audio/image/document handling (whatsapp) 2022-03-20 14:57:48 +01:00
Wim
4da1444ffc Check for Conversation on receiving messages (whatsapp) 2022-03-20 14:57:48 +01:00
Wim
21c4e56d16 Use Conversation instead of ExtendedTextMessage (whatsapp) 2022-03-20 14:57:48 +01:00
Wim
5356b3856a Update vendor (whatsapp) 2022-03-20 14:57:48 +01:00
Wim
320c996a21 Refactor login logic (whatsapp) 2022-03-20 14:57:48 +01:00
Wim
69c74be7bb Add busy_timeout which fixes SQLITE_BUSY errors (whatsapp) 2022-03-20 14:57:48 +01:00
Wim
aefa70891c Update vendor (whatsapp) 2022-03-20 14:57:48 +01:00
Wim
1b9877fda4 Fetch avatars synchronous (whatsapp) 2022-03-20 14:57:48 +01:00
Wim
0205a67309 Refactor JoinChannel (whatsapp) 2022-03-20 14:57:48 +01:00
Wim
e3cafeaf92 Add dependencies/vendor (whatsapp) 2022-03-20 14:57:48 +01:00
Wim
e7b193788a Rewrite whatsapp bridge to use whatsmeow 2022-03-20 14:57:48 +01:00
Wim
17da95b094 Remove go replace by fork (matrix) (#1771) 2022-03-20 01:43:26 +01:00
Wim
c5e49eec96 Bump version 2022-03-19 23:47:50 +01:00
Wim
24bc0f127b Release v1.24.1 (#1768) 2022-03-19 23:28:15 +01:00
Wim
f0f801402d Refactor utf-8 conversion (irc) (#1767) 2022-03-19 23:14:56 +01:00
Sebastian P
663850a2b8 Implement a workaround to signal Opus support (mumble) (#1764)
* Mumble: Implement a workaround to signal Opus support without pulling in the CGO gopus dependency.

* mumble: lowercase error messages

* mumble: Add link to #1750 in bridge/mumble/codec.go
2022-03-19 21:32:00 +01:00
ValdikSS
c51753cab1 Fix for complex-formatted Telegram text (#1765)
* Telegram: handle entities before everything

* Telegram: use runes for text entities

* Telegram: use proper offset and runes for links

* Telegram: put newline after backticks for pre

* Telegram: use utf16 for entity processing
2022-03-19 11:34:46 +01:00
Wim
b3be2e208c Update dependencies and vendor (#1761) 2022-03-12 19:41:07 +01:00
Wim
c30e90ff3f Fix panic in irc. Closes #1751 (#1760) 2022-03-12 17:33:39 +01:00
Wim
e4c0ca0f48 Switch to discordgo upstream again (#1759)
* Switch to upstream discordgo again

* Fix discord api changes
2022-03-12 17:06:39 +01:00
ValdikSS
9c203327c0 Fix Telegram channel title in forwards (#1753)
Forward from channels requires different handling than forward from the regular users.
This patch fixes the issue: it prints channel title instead of "forwarded from unknown".
2022-03-12 00:20:39 +01:00
Jan Martin Reckel
ccb5b1d075 Fix Telegram Problem (unforwarded formatting and skipping of linebreaks) (#1749)
* Change bridge/telegram/handlers.go

Comment out the removing of empty lines
add support for bold, italic and striked telegram messages

* Implement Telegram MessageEntities correctly

* Apply gofmt

Co-authored-by: Jan Martin Reckel <jan-martin.reckel@s2017.tu-chemnitz.de>
Co-authored-by: Wim <wim@42.be>
2022-03-12 00:19:02 +01:00
jan Anja
0dbbd0414c Create inmessage-logger.tengo (#1688) (#1747) 2022-03-11 23:31:45 +01:00
jan Anja
e7b3ebf98a Add OpenRC service file (#1746) 2022-02-20 22:24:42 +01:00
Wim
5bc18fb780 Remove dependabot to fix fork spamming
See https://github.com/dependabot/dependabot-core/issues/2198
2022-02-08 00:13:09 +01:00
Wim
df30366072 Bump version 2022-02-07 23:48:38 +01:00
Wim
65c7ac80b5 Release v1.24.0 (#1732) 2022-02-07 23:10:56 +01:00
dependabot[bot]
dd3fb32ec7 Bump github.com/SevereCloud/vksdk/v2 from 2.13.0 to 2.13.1 (#1730)
Bumps [github.com/SevereCloud/vksdk/v2](https://github.com/SevereCloud/vksdk) from 2.13.0 to 2.13.1.
- [Release notes](https://github.com/SevereCloud/vksdk/releases)
- [Commits](https://github.com/SevereCloud/vksdk/compare/v2.13.0...v2.13.1)

---
updated-dependencies:
- dependency-name: github.com/SevereCloud/vksdk/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-07 20:30:09 +01:00
Wim
2a3f475ff5 Make EditSuffix option actually work (whatsapp). Fixes #1510 (#1728)
To keep it backwards compatible we keep the "(edited)" message when no
editsuffix is configured.
2022-02-06 23:56:54 +01:00
2729 changed files with 3835329 additions and 40303 deletions

View File

@@ -1,22 +0,0 @@
# Docs: <https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/customizing-dependency-updates>
version: 2
updates:
- package-ecosystem: gomod
directory: /
schedule: {interval: weekly}
reviewers: [42wim]
assignees: [42wim]
- package-ecosystem: github-actions
directory: /
schedule: {interval: weekly}
reviewers: [42wim]
assignees: [42wim]
- package-ecosystem: docker
directory: /
schedule: {interval: weekly}
reviewers: [42wim]
assignees: [42wim]

View File

@@ -16,7 +16,7 @@ jobs:
test-build-upload:
strategy:
matrix:
go-version: [1.17.x]
go-version: [1.18.x]
platform: [ubuntu-latest]
runs-on: ${{ matrix.platform }}
steps:
@@ -39,19 +39,19 @@ jobs:
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
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
if: startsWith(matrix.go-version,'1.17')
if: startsWith(matrix.go-version,'1.18')
uses: actions/upload-artifact@v2
with:
name: matterbridge-linux-64bit
path: output/lin
- name: Upload windows 64-bit
if: startsWith(matrix.go-version,'1.17')
if: startsWith(matrix.go-version,'1.18')
uses: actions/upload-artifact@v2
with:
name: matterbridge-windows-64bit
path: output/win
- name: Upload darwin 64-bit
if: startsWith(matrix.go-version,'1.17')
if: startsWith(matrix.go-version,'1.18')
uses: actions/upload-artifact@v2
with:
name: matterbridge-darwin-64bit

View File

@@ -204,6 +204,14 @@ linters:
- tagliatelle
- errname
- typecheck
- grouper
- decorder
- maintidx
- exhaustruct
- asasalint
- execinquery
- nosnakecase
- exhaustive
# rules to deal with reported isues
issues:
# List of regexps of issue texts to exclude, empty list by default.

14
Dockerfile_whatsappmulti Normal file
View 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"]

View File

@@ -58,21 +58,22 @@ And more...
- [Binaries](#binaries)
- [Packages](#packages)
- [Building](#building)
- [Building with whatsapp (beta) multidevice support](#building-with-whatsapp-beta-multidevice-support)
- [Configuration](#configuration)
- [Basic configuration](#basic-configuration)
- [Settings](#settings)
- [Advanced configuration](#advanced-configuration)
- [Examples](#examples)
- [Bridge mattermost (off-topic) - irc (#testing)](#bridge-mattermost-off-topic---irc-testing)
- [Bridge slack (#general) - discord (general)](#bridge-slack-general---discord-general)
- [Running](#running)
- [Docker](#docker)
- [Systemd](#systemd)
- [Changelog](#changelog)
- [FAQ](#faq)
- [Related projects](#related-projects)
- [Articles / Tutorials](#articles--tutorials)
- [Thanks](#thanks)
- [Examples](#examples)
- [Bridge mattermost (off-topic) - irc (#testing)](#bridge-mattermost-off-topic---irc-testing)
- [Bridge slack (#general) - discord (general)](#bridge-slack-general---discord-general)
- [Running](#running)
- [Docker](#docker)
- [Systemd](#systemd)
- [Changelog](#changelog)
- [FAQ](#faq)
- [Related projects](#related-projects)
- [Articles / Tutorials](#articles--tutorials)
- [Thanks](#thanks)
## Features
@@ -89,6 +90,7 @@ And more...
- [Discord](https://discordapp.com)
- [Gitter](https://gitter.im)
- [Harmony](https://harmonyapp.io)
- [IRC](http://www.mirc.com/servers.html)
- [Keybase](https://keybase.io)
- [Matrix](https://matrix.org)
@@ -105,6 +107,8 @@ And more...
- [Twitch](https://twitch.tv)
- [VK](https://vk.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)
- [Zulip](https://zulipchat.com)
@@ -120,6 +124,8 @@ And more...
- [Counter-Strike, half-life and more](https://forums.alliedmods.net/showthread.php?t=319430)
- [MatterAMXX](https://github.com/GabeIggy/MatterAMXX)
- [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
@@ -138,6 +144,9 @@ 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)
- [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)
- [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)
- [beerchat](https://github.com/mt-mods/beerchat) (Matterbridge link for minetest)
## Chat with us
@@ -164,10 +173,10 @@ See <https://github.com/42wim/matterbridge/wiki>
### Binaries
- Latest stable release [v1.23.2](https://github.com/42wim/matterbridge/releases/latest)
- Latest stable release [v1.26.0](https://github.com/42wim/matterbridge/releases/latest)
- Development releases (follows master) can be downloaded [here](https://github.com/42wim/matterbridge/actions) selecting the latest green build and then artifacts.
To install or upgrade just download the latest [binary](https://github.com/42wim/matterbridge/releases/latest). 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
@@ -180,7 +189,12 @@ 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)
If you really want to build from source, follow these instructions:
Go 1.17+ is required. Make sure you have [Go](https://golang.org/doc/install) properly installed.
Go 1.18+ 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:
@@ -194,6 +208,38 @@ To install the latest dev run:
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:
```bash
@@ -323,6 +369,8 @@ See [FAQ](https://github.com/42wim/matterbridge/wiki/FAQ)
- [nextcloud talk](https://github.com/nextcloud/talk_matterbridge) (Integrates matterbridge in Nextcloud Talk)
- [mattercraft](https://github.com/raws/mattercraft) (Minecraft bridge)
- [vs-matterbridge](https://github.com/NikkyAI/vs-matterbridge) (Vintage Story bridge)
- [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
@@ -356,6 +404,7 @@ Matterbridge wouldn't exist without these libraries:
- gops - <https://github.com/google/gops>
- gozulipbot - <https://github.com/ifo/gozulipbot>
- gumble - <https://github.com/layeh/gumble>
- harmony - <https://github.com/harmony-development/shibshib>
- irc - <https://github.com/lrstanley/girc>
- keybase - <https://github.com/keybase/go-keybase-chat-bot>
- matrix - <https://github.com/matrix-org/gomatrix>
@@ -363,6 +412,7 @@ Matterbridge wouldn't exist without these libraries:
- msgraph.go - <https://github.com/yaegashi/msgraph.go>
- mumble - <https://github.com/layeh/gumble>
- nctalk - <https://github.com/gary-kim/go-nc-talk>
- rocketchat - <https://github.com/RocketChat/Rocket.Chat.Go.SDK>
- slack - <https://github.com/nlopes/slack>
- sshchat - <https://github.com/shazow/ssh-chat>
- steam - <https://github.com/Philipp15b/go-steam>
@@ -370,6 +420,7 @@ Matterbridge wouldn't exist without these libraries:
- tengo - <https://github.com/d5/tengo>
- vk - <https://github.com/SevereCloud/vksdk>
- whatsapp - <https://github.com/Rhymen/go-whatsapp>
- whatsapp - <https://github.com/tulir/whatsmeow>
- xmpp - <https://github.com/mattn/go-xmpp>
- zulip - <https://github.com/ifo/gozulipbot>

View File

@@ -10,8 +10,8 @@ import (
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/discord/transmitter"
"github.com/42wim/matterbridge/bridge/helper"
"github.com/bwmarrin/discordgo"
lru "github.com/hashicorp/golang-lru"
"github.com/matterbridge/discordgo"
)
const (
@@ -83,12 +83,12 @@ func (b *Bdiscord) Connect() error {
b.Log.Info("Connection succeeded")
b.c.AddHandler(b.messageCreate)
b.c.AddHandler(b.messageTyping)
b.c.AddHandler(b.memberUpdate)
b.c.AddHandler(b.messageUpdate)
b.c.AddHandler(b.messageDelete)
b.c.AddHandler(b.messageDeleteBulk)
b.c.AddHandler(b.memberAdd)
b.c.AddHandler(b.memberRemove)
b.c.AddHandler(b.memberUpdate)
if b.GetInt("debuglevel") == 1 {
b.c.AddHandler(b.messageEvent)
}
@@ -272,7 +272,6 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
// Handle prefix hint for unthreaded messages.
if msg.ParentNotFound() {
msg.ParentID = ""
msg.Text = fmt.Sprintf("[thread]: %s", msg.Text)
}
// Use webhook to send the message

View File

@@ -2,11 +2,15 @@ package bdiscord
import (
"github.com/42wim/matterbridge/bridge/config"
"github.com/bwmarrin/discordgo"
"github.com/davecgh/go-spew/spew"
"github.com/matterbridge/discordgo"
)
func (b *Bdiscord) messageDelete(s *discordgo.Session, m *discordgo.MessageDelete) { //nolint:unparam
if m.GuildID != b.guildID {
b.Log.Debugf("Ignoring messageDelete because it originates from a different guild")
return
}
rmsg := config.Message{Account: b.Account, ID: m.ID, Event: config.EventMsgDelete, Text: config.EventMsgDelete}
rmsg.Channel = b.getChannelName(m.ChannelID)
@@ -17,6 +21,10 @@ func (b *Bdiscord) messageDelete(s *discordgo.Session, m *discordgo.MessageDelet
// TODO(qaisjp): if other bridges support bulk deletions, it could be fanned out centrally
func (b *Bdiscord) messageDeleteBulk(s *discordgo.Session, m *discordgo.MessageDeleteBulk) { //nolint:unparam
if m.GuildID != b.guildID {
b.Log.Debugf("Ignoring messageDeleteBulk because it originates from a different guild")
return
}
for _, msgID := range m.Messages {
rmsg := config.Message{
Account: b.Account,
@@ -37,6 +45,10 @@ func (b *Bdiscord) messageEvent(s *discordgo.Session, m *discordgo.Event) {
}
func (b *Bdiscord) messageTyping(s *discordgo.Session, m *discordgo.TypingStart) {
if m.GuildID != b.guildID {
b.Log.Debugf("Ignoring messageTyping because it originates from a different guild")
return
}
if !b.GetBool("ShowUserTyping") {
return
}
@@ -52,11 +64,15 @@ func (b *Bdiscord) messageTyping(s *discordgo.Session, m *discordgo.TypingStart)
}
func (b *Bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) { //nolint:unparam
if m.GuildID != b.guildID {
b.Log.Debugf("Ignoring messageUpdate because it originates from a different guild")
return
}
if b.GetBool("EditDisable") {
return
}
// only when message is actually edited
if m.Message.EditedTimestamp != "" {
if m.Message.EditedTimestamp != nil {
b.Log.Debugf("Sending edit message")
m.Content += b.GetString("EditSuffix")
msg := &discordgo.MessageCreate{
@@ -67,6 +83,10 @@ func (b *Bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdat
}
func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { //nolint:unparam
if m.GuildID != b.guildID {
b.Log.Debugf("Ignoring messageCreate because it originates from a different guild")
return
}
var err error
// not relay our own messages
@@ -144,6 +164,10 @@ func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
}
func (b *Bdiscord) memberUpdate(s *discordgo.Session, m *discordgo.GuildMemberUpdate) {
if m.GuildID != b.guildID {
b.Log.Debugf("Ignoring memberUpdate because it originates from a different guild")
return
}
if m.Member == nil {
b.Log.Warnf("Received member update with no member information: %#v", m)
}
@@ -171,6 +195,13 @@ func (b *Bdiscord) memberUpdate(s *discordgo.Session, m *discordgo.GuildMemberUp
}
func (b *Bdiscord) memberAdd(s *discordgo.Session, m *discordgo.GuildMemberAdd) {
if m.GuildID != b.guildID {
b.Log.Debugf("Ignoring memberAdd because it originates from a different guild")
return
}
if b.GetBool("nosendjoinpart") {
return
}
if m.Member == nil {
b.Log.Warnf("Received member update with no member information: %#v", m)
return
@@ -192,6 +223,13 @@ func (b *Bdiscord) memberAdd(s *discordgo.Session, m *discordgo.GuildMemberAdd)
}
func (b *Bdiscord) memberRemove(s *discordgo.Session, m *discordgo.GuildMemberRemove) {
if m.GuildID != b.guildID {
b.Log.Debugf("Ignoring memberRemove because it originates from a different guild")
return
}
if b.GetBool("nosendjoinpart") {
return
}
if m.Member == nil {
b.Log.Warnf("Received member update with no member information: %#v", m)
return

View File

@@ -3,7 +3,7 @@ package bdiscord
import (
"testing"
"github.com/matterbridge/discordgo"
"github.com/bwmarrin/discordgo"
"github.com/stretchr/testify/assert"
)

View File

@@ -6,7 +6,7 @@ import (
"strings"
"unicode"
"github.com/matterbridge/discordgo"
"github.com/bwmarrin/discordgo"
)
func (b *Bdiscord) getAllowedMentions() *discordgo.MessageAllowedMentions {

View File

@@ -20,7 +20,7 @@ import (
"sync"
"time"
"github.com/matterbridge/discordgo"
"github.com/bwmarrin/discordgo"
log "github.com/sirupsen/logrus"
)

View File

@@ -1,7 +1,7 @@
package transmitter
import (
"github.com/matterbridge/discordgo"
"github.com/bwmarrin/discordgo"
)
// isDiscordPermissionError returns false for nil, and true if a Discord RESTError with code discordgo.ErrorCodeMissionPermissions

View File

@@ -5,7 +5,7 @@ import (
"github.com/42wim/matterbridge/bridge/config"
"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
@@ -89,7 +89,7 @@ func (b *Bdiscord) webhookSend(msg *config.Message, channelID string) (*discordg
&discordgo.WebhookParams{
Username: msg.Username,
AvatarURL: msg.Avatar,
File: &file,
Files: []*discordgo.File{&file},
Content: content,
AllowedMentions: b.getAllowedMentions(),
},

View File

@@ -80,8 +80,18 @@ func DownloadFileAuthRocket(url, token, userID string) (*[]byte, error) {
// word boundaries when splitting but this is hard to solve without potentially
// breaking formatting and other stylistic effects.
func GetSubLines(message string, maxLineLength int, clippingMessage string) []string {
if clippingMessage == "" {
clippingMessage = " <clipped message>"
}
var lines []string
for _, line := range strings.Split(strings.TrimSpace(message), "\n") {
if line == "" {
// Prevent sending empty messages, so we'll skip this line
// if it has no content.
continue
}
if maxLineLength == 0 || len([]byte(line)) <= maxLineLength {
lines = append(lines, line)
continue
@@ -94,8 +104,8 @@ func GetSubLines(message string, maxLineLength int, clippingMessage string) []st
var splitStart int
var startOfPreviousRune int
for i := range line {
if i-splitStart > maxLineLength {
lines = append(lines, line[splitStart:startOfPreviousRune])
if i-splitStart > maxLineLength-len([]byte(clippingMessage)) {
lines = append(lines, line[splitStart:startOfPreviousRune]+clippingMessage)
splitStart = startOfPreviousRune
}
startOfPreviousRune = i

View File

@@ -1,5 +1,4 @@
//go:build cgo
// +build cgo
//go:build cgolottie
package helper

View File

@@ -1,4 +1,4 @@
// +build !cgo
//go:build !cgolottie
package helper
@@ -6,6 +6,7 @@ import (
"io/ioutil"
"os"
"os/exec"
"github.com/sirupsen/logrus"
)

32
bridge/irc/charset.go Normal file
View 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
}

View File

@@ -11,7 +11,6 @@ import (
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
"github.com/lrstanley/girc"
"github.com/missdeer/golib/ic"
"github.com/paulrosania/go-charset/charset"
"github.com/saintfish/chardet"
@@ -24,12 +23,12 @@ func (b *Birc) handleCharset(msg *config.Message) error {
if b.GetString("Charset") != "" {
switch b.GetString("Charset") {
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:
buf := new(bytes.Buffer)
w, err := charset.NewWriter(b.GetString("Charset"), buf)
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
}
fmt.Fprint(w, msg.Text)
@@ -227,7 +226,7 @@ func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) {
}
switch mycharset {
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:
r, err := charset.NewReader(mycharset, strings.NewReader(rmsg.Text))
if err != nil {
@@ -244,6 +243,7 @@ func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) {
func (b *Birc) handleRunCommands() {
for _, cmd := range b.GetStringSlice("RunCommands") {
cmd = strings.ReplaceAll(cmd, "{BOTNICK}", b.Nick)
if err := b.i.Cmd.SendRaw(cmd); err != nil {
b.Log.Errorf("RunCommands %s failed: %s", cmd, err)
}

View File

@@ -25,7 +25,7 @@ import (
type Birc struct {
i *girc.Client
Nick, MessageClipped string
Nick string
names map[string][]string
connected chan error
Local chan config.Message // local queue for flood control
@@ -172,11 +172,10 @@ func (b *Birc) Send(msg config.Message) (string, error) {
}
if b.GetBool("MessageSplit") {
msgLines = helper.GetSubLines(msg.Text, b.MessageLength, "")
msgLines = helper.GetSubLines(msg.Text, b.MessageLength, b.GetString("MessageClipped"))
} else {
msgLines = []string{helper.GetSubLines(msg.Text, b.MessageLength, "")[0] + b.getMessageClipped()}
msgLines = helper.GetSubLines(msg.Text, 0, b.GetString("MessageClipped"))
}
for i := range msgLines {
if len(b.Local) >= b.MessageQueue {
b.Log.Debugf("flooding, dropping message (queue at %d)", len(b.Local))
@@ -363,8 +362,10 @@ func (b *Birc) skipPrivMsg(event girc.Event) bool {
return true
}
// don't forward message from ourself
if event.Source.Name == b.Nick {
return true
if event.Source != nil {
if event.Source.Name == b.Nick {
return true
}
}
// don't forward messages we sent via RELAYMSG
if relayedNick, ok := event.Tags.Get("draft/relaymsg"); ok && relayedNick == b.Nick {
@@ -412,11 +413,3 @@ func (b *Birc) getTLSConfig() (*tls.Config, error) {
return tlsConfig, nil
}
func (b *Birc) getMessageClipped() string {
if b.GetString("MessageClipped") == "" {
return " <clipped message>"
}
return " " + b.GetString("MessageClipped")
}

View File

@@ -8,7 +8,7 @@ import (
"strings"
"time"
matrix "github.com/matrix-org/gomatrix"
matrix "github.com/matterbridge/gomatrix"
)
func newMatrixUsername(username string) *matrixUsername {
@@ -51,7 +51,7 @@ func interface2Struct(in interface{}, out interface{}) error {
return json.Unmarshal(jsonObj, out)
}
// getDisplayName retrieves the displayName for mxid, querying the homserver if the mxid is not in the cache.
// getDisplayName retrieves the displayName for mxid, querying the homeserver if the mxid is not in the cache.
func (b *Bmatrix) getDisplayName(mxid string) string {
if b.GetBool("UseUserName") {
return mxid[1:]

View File

@@ -12,7 +12,7 @@ import (
"github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
matrix "github.com/matrix-org/gomatrix"
matrix "github.com/matterbridge/gomatrix"
)
var (
@@ -148,12 +148,37 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
username := newMatrixUsername(msg.Username)
body := username.plain + msg.Text
formattedBody := username.formatted + helper.ParseMarkdown(msg.Text)
if b.GetBool("SpoofUsername") {
// https://spec.matrix.org/v1.3/client-server-api/#mroommember
type stateMember struct {
AvatarURL string `json:"avatar_url,omitempty"`
DisplayName string `json:"displayname"`
Membership string `json:"membership"`
}
// TODO: reset username afterwards with DisplayName: null ?
m := stateMember{
AvatarURL: "",
DisplayName: username.plain,
Membership: "join",
}
_, err := b.mc.SendStateEvent(channel, "m.room.member", b.UserID, m)
if err == nil {
body = msg.Text
formattedBody = helper.ParseMarkdown(msg.Text)
}
}
// Make a action /me of the message
if msg.Event == config.EventUserAction {
m := matrix.TextMessage{
MsgType: "m.emote",
Body: username.plain + msg.Text,
FormattedBody: username.formatted + helper.ParseMarkdown(msg.Text),
Body: body,
FormattedBody: formattedBody,
Format: "org.matrix.custom.html",
}
@@ -224,10 +249,10 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
if msg.ID != "" {
rmsg := EditedMessage{
TextMessage: matrix.TextMessage{
Body: username.plain + msg.Text,
Body: body,
MsgType: "m.text",
Format: "org.matrix.custom.html",
FormattedBody: username.formatted + helper.ParseMarkdown(msg.Text),
FormattedBody: formattedBody,
},
}
@@ -266,8 +291,8 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
if msg.Event == config.EventJoinLeave {
m := matrix.TextMessage{
MsgType: "m.notice",
Body: username.plain + msg.Text,
FormattedBody: username.formatted + msg.Text,
Body: body,
FormattedBody: formattedBody,
Format: "org.matrix.custom.html",
}
@@ -297,8 +322,8 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
m := ReplyMessage{
TextMessage: matrix.TextMessage{
MsgType: "m.text",
Body: username.plain + msg.Text,
FormattedBody: username.formatted + helper.ParseMarkdown(msg.Text),
Body: body,
FormattedBody: formattedBody,
Format: "org.matrix.custom.html",
},
}
@@ -338,7 +363,7 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
)
err = b.retry(func() error {
resp, err = b.mc.SendText(channel, username.plain+msg.Text)
resp, err = b.mc.SendText(channel, body)
return err
})
@@ -356,8 +381,7 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
)
err = b.retry(func() error {
resp, err = b.mc.SendFormattedText(channel, username.plain+msg.Text,
username.formatted+helper.ParseMarkdown(msg.Text))
resp, err = b.mc.SendFormattedText(channel, body, formattedBody)
return err
})
@@ -428,12 +452,15 @@ func (b *Bmatrix) handleReply(ev *matrix.Event, rmsg config.Message) bool {
}
body := rmsg.Text
for strings.HasPrefix(body, "> ") {
lineIdx := strings.IndexRune(body, '\n')
if lineIdx == -1 {
body = ""
} else {
body = body[(lineIdx + 1):]
if !b.GetBool("keepquotedreply") {
for strings.HasPrefix(body, "> ") {
lineIdx := strings.IndexRune(body, '\n')
if lineIdx == -1 {
body = ""
} else {
body = body[(lineIdx + 1):]
}
}
}

View File

@@ -3,10 +3,8 @@ package bmattermost
import (
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
"github.com/42wim/matterbridge/matterclient"
matterclient6 "github.com/matterbridge/matterclient"
"github.com/mattermost/mattermost-server/v5/model"
model6 "github.com/mattermost/mattermost-server/v6/model"
"github.com/matterbridge/matterclient"
"github.com/mattermost/mattermost-server/v6/model"
)
// handleDownloadAvatar downloads the avatar of userid from channel
@@ -26,20 +24,11 @@ func (b *Bmattermost) handleDownloadAvatar(userid string, channel string) {
var (
data []byte
err error
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
}
data, _, err = b.mc.Client.GetProfileImage(userid, "")
if err != nil {
b.Log.Errorf("ProfileImage download failed for %#v %s", userid, err)
return
}
err = helper.HandleDownloadSize(b.Log, &rmsg, userid+".png", int64(len(data)), b.General)
@@ -52,33 +41,10 @@ func (b *Bmattermost) handleDownloadAvatar(userid string, channel string) {
}
}
// handleDownloadFile handles file download
//nolint:wrapcheck
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)
finfo, resp := b.mc.Client.GetFileInfo(id)
if resp.Error != nil {
return resp.Error
}
err := helper.HandleDownloadSize(b.Log, rmsg, finfo.Name, finfo.Size, b.General)
if err != nil {
return err
}
data, resp := b.mc.Client.DownloadFile(id, true)
if resp.Error != nil {
return resp.Error
}
helper.HandleDownloadData(b.Log, rmsg, finfo.Name, rmsg.Text, url, &data, b.General)
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)
url, _, _ := b.mc.Client.GetFileLink(id)
finfo, _, err := b.mc.Client.GetFileInfo(id)
if err != nil {
return err
}
@@ -86,7 +52,7 @@ func (b *Bmattermost) handleDownloadFile6(rmsg *config.Message, id string) error
if err != nil {
return err
}
data, _, err := b.mc6.Client.DownloadFile(id, true)
data, _, err := b.mc.Client.DownloadFile(id, true)
if err != nil {
return err
}
@@ -125,15 +91,10 @@ func (b *Bmattermost) handleMatter() {
}
}
//nolint:cyclop
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 {
b.Log.Debugf("%#v", message.Raw.Data)
b.Log.Debugf("%#v %#v", message.Raw.GetData(), message.Raw.EventType())
if b.skipMessage(message) {
b.Log.Debugf("Skipped message: %#v", message)
@@ -166,11 +127,11 @@ func (b *Bmattermost) handleMatterClient(messages chan *config.Message) {
b.handleProps(rmsg, message)
// create a text for bridges that don't support native editing
if message.Raw.Event == model.WEBSOCKET_EVENT_POST_EDITED && !b.GetBool("EditDisable") {
if message.Raw.EventType() == model.WebsocketEventPostEdited && !b.GetBool("EditDisable") {
rmsg.Text = message.Text + b.GetString("EditSuffix")
}
if message.Raw.Event == model.WEBSOCKET_EVENT_POST_DELETED {
if message.Raw.EventType() == model.WebsocketEventPostDeleted {
rmsg.Event = config.EventMsgDelete
}
@@ -192,68 +153,6 @@ func (b *Bmattermost) handleMatterClient(messages chan *config.Message) {
}
}
// 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
}
}
func (b *Bmattermost) handleMatterHook(messages chan *config.Message) {
for {
message := b.mh.Receive()
@@ -268,12 +167,7 @@ func (b *Bmattermost) handleMatterHook(messages chan *config.Message) {
}
}
// handleUploadFile handles native upload of files
func (b *Bmattermost) handleUploadFile(msg *config.Message) (string, error) {
if b.mc6 != nil {
return b.handleUploadFile6(msg)
}
var err error
var res, id string
channelID := b.getChannelID(msg.Channel)
@@ -292,53 +186,8 @@ func (b *Bmattermost) handleUploadFile(msg *config.Message) (string, error) {
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
}
//nolint:forcetypeassert
func (b *Bmattermost) handleProps(rmsg *config.Message, message *matterclient.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 == "" {
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)
}
}
}
}
}
// nolint:forcetypeassert
func (b *Bmattermost) handleProps6(rmsg *config.Message, message *matterclient6.Message) {
props := message.Post.Props
if props == nil {
return

View File

@@ -6,11 +6,9 @@ import (
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
"github.com/42wim/matterbridge/matterclient"
"github.com/42wim/matterbridge/matterhook"
matterclient6 "github.com/matterbridge/matterclient"
"github.com/mattermost/mattermost-server/v5/model"
model6 "github.com/mattermost/mattermost-server/v6/model"
"github.com/matterbridge/matterclient"
"github.com/mattermost/mattermost-server/v6/model"
)
func (b *Bmattermost) doConnectWebhookBind() error {
@@ -24,33 +22,15 @@ func (b *Bmattermost) doConnectWebhookBind() error {
})
case b.GetString("Token") != "":
b.Log.Info("Connecting using token (sending)")
b.Log.Infof("Using mattermost v6 methods: %t", b.v6)
if b.v6 {
err := b.apiLogin6()
if err != nil {
return err
}
} else {
err := b.apiLogin()
if err != nil {
return err
}
err := b.apiLogin()
if err != nil {
return err
}
case b.GetString("Login") != "":
b.Log.Info("Connecting using login/password (sending)")
b.Log.Infof("Using mattermost v6 methods: %t", b.v6)
if b.v6 {
err := b.apiLogin6()
if err != nil {
return err
}
} else {
err := b.apiLogin()
if err != nil {
return err
}
err := b.apiLogin()
if err != nil {
return err
}
default:
b.Log.Info("Connecting using webhookbindaddress (receiving)")
@@ -72,45 +52,28 @@ func (b *Bmattermost) doConnectWebhookURL() error {
})
if b.GetString("Token") != "" {
b.Log.Info("Connecting using token (receiving)")
b.Log.Infof("Using mattermost v6 methods: %t", b.v6)
if b.v6 {
err := b.apiLogin6()
if err != nil {
return err
}
} else {
err := b.apiLogin()
if err != nil {
return err
}
err := b.apiLogin()
if err != nil {
return err
}
} else if b.GetString("Login") != "" {
b.Log.Info("Connecting using login/password (receiving)")
b.Log.Infof("Using mattermost v6 methods: %t", b.v6)
if b.v6 {
err := b.apiLogin6()
if err != nil {
return err
}
} else {
err := b.apiLogin()
if err != nil {
return err
}
err := b.apiLogin()
if err != nil {
return err
}
}
return nil
}
//nolint:wrapcheck
func (b *Bmattermost) apiLogin() error {
password := b.GetString("Password")
if b.GetString("Token") != "" {
password = "token=" + b.GetString("Token")
}
b.mc = matterclient.New(b.GetString("Login"), password, b.GetString("Team"), b.GetString("Server"))
b.mc = matterclient.New(b.GetString("Login"), password, b.GetString("Team"), b.GetString("Server"), "")
if b.GetBool("debug") {
b.mc.SetLogLevel("debug")
}
@@ -118,39 +81,13 @@ func (b *Bmattermost) apiLogin() error {
b.mc.SkipVersionCheck = b.GetBool("SkipVersionCheck")
b.mc.NoTLS = b.GetBool("NoTLS")
b.Log.Infof("Connecting %s (team: %s) on %s", b.GetString("Login"), b.GetString("Team"), b.GetString("Server"))
err := b.mc.Login()
if err != nil {
return err
}
b.Log.Info("Connection succeeded")
b.TeamID = b.mc.GetTeamId()
go b.mc.WsReceiver()
go b.mc.StatusLoop()
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 {
if err := b.mc.Login(); err != nil {
return err
}
b.Log.Info("Connection succeeded")
b.TeamID = b.mc6.GetTeamID()
b.TeamID = b.mc.GetTeamID()
return nil
}
@@ -183,6 +120,7 @@ func (b *Bmattermost) sendWebhook(msg config.Message) (string, error) {
if b.GetBool("PrefixMessagesWithNick") {
msg.Text = msg.Username + msg.Text
}
if msg.Extra != nil {
// this sends a message only if we received a config.EVENT_FILE_FAILURE_SIZE
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
@@ -206,7 +144,7 @@ func (b *Bmattermost) sendWebhook(msg config.Message) (string, error) {
for _, f := range msg.Extra["file"] {
fi := f.(config.FileInfo)
if fi.URL != "" {
msg.Text += fi.URL
msg.Text += " " + fi.URL
}
}
}
@@ -233,6 +171,7 @@ func (b *Bmattermost) sendWebhook(msg config.Message) (string, error) {
}
// skipMessages returns true if this message should not be handled
//nolint:gocyclo,cyclop
func (b *Bmattermost) skipMessage(message *matterclient.Message) bool {
// Handle join/leave
if message.Type == "system_join_leave" ||
@@ -259,76 +198,7 @@ func (b *Bmattermost) skipMessage(message *matterclient.Message) bool {
}
// Handle edited messages
if (message.Raw.Event == model.WEBSOCKET_EVENT_POST_EDITED) && 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.Debugf("sent by matterbridge, ignoring")
return true
}
}
// Ignore messages sent from a user logged in as the bot
if b.mc.User.Username == message.Username {
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.Data["team_id"].(string) != b.TeamID {
return true
}
// only handle posted, edited or deleted events
if !(message.Raw.Event == "posted" || message.Raw.Event == model.WEBSOCKET_EVENT_POST_EDITED ||
message.Raw.Event == model.WEBSOCKET_EVENT_POST_DELETED) {
return true
}
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") {
if (message.Raw.EventType() == model.WebsocketEventPostEdited) && b.GetBool("EditDisable") {
return true
}
@@ -347,7 +217,7 @@ func (b *Bmattermost) skipMessage6(message *matterclient6.Message) bool {
}
// Ignore messages sent from a user logged in as the bot
if b.mc6.User.Username == message.Username {
if b.mc.User.Username == message.Username {
b.Log.Debug("message from same user as bot, ignoring")
return true
}
@@ -364,8 +234,8 @@ func (b *Bmattermost) skipMessage6(message *matterclient6.Message) bool {
}
// only handle posted, edited or deleted events
if !(message.Raw.EventType() == "posted" || message.Raw.EventType() == model6.WebsocketEventPostEdited ||
message.Raw.EventType() == model6.WebsocketEventPostDeleted) {
if !(message.Raw.EventType() == "posted" || message.Raw.EventType() == model.WebsocketEventPostEdited ||
message.Raw.EventType() == model.WebsocketEventPostDeleted) {
return true
}
return false
@@ -395,11 +265,7 @@ func (b *Bmattermost) getChannelID(name string) string {
return idcheck[1]
}
if b.mc6 != nil {
return b.mc6.GetChannelID(name, b.TeamID)
}
return b.mc.GetChannelId(name, b.TeamID)
return b.mc.GetChannelID(name, b.TeamID)
}
func (b *Bmattermost) getChannelName(id string) string {

View File

@@ -9,16 +9,14 @@ import (
"github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
"github.com/42wim/matterbridge/matterclient"
"github.com/42wim/matterbridge/matterhook"
matterclient6 "github.com/matterbridge/matterclient"
"github.com/matterbridge/matterclient"
"github.com/rs/xid"
)
type Bmattermost struct {
mh *matterhook.Client
mc *matterclient.MMClient
mc6 *matterclient6.Client
mc *matterclient.Client
v6 bool
uuid string
TeamID string
@@ -52,7 +50,7 @@ func (b *Bmattermost) Connect() error {
return nil
}
if strings.HasPrefix(b.getVersion(), "6.") {
if strings.HasPrefix(b.getVersion(), "6.") || strings.HasPrefix(b.getVersion(), "7.") {
if !b.v6 {
b.v6 = true
}
@@ -74,34 +72,17 @@ func (b *Bmattermost) Connect() error {
return nil
case b.GetString("Token") != "":
b.Log.Info("Connecting using token (sending and receiving)")
b.Log.Infof("Using mattermost v6 methods: %t", b.v6)
if b.v6 {
err := b.apiLogin6()
if err != nil {
return err
}
} else {
err := b.apiLogin()
if err != nil {
return err
}
err := b.apiLogin()
if err != nil {
return err
}
go b.handleMatter()
case b.GetString("Login") != "":
b.Log.Info("Connecting using login/password (sending and receiving)")
b.Log.Infof("Using mattermost v6 methods: %t", b.v6)
if b.v6 {
err := b.apiLogin6()
if err != nil {
return err
}
} else {
err := b.apiLogin()
if err != nil {
return err
}
err := b.apiLogin()
if err != nil {
return err
}
go b.handleMatter()
}
@@ -132,10 +113,6 @@ func (b *Bmattermost) JoinChannel(channel config.ChannelInfo) error {
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)
}
@@ -168,9 +145,6 @@ func (b *Bmattermost) Send(msg config.Message) (string, error) {
if msg.ID == "" {
return "", nil
}
if b.mc6 != nil {
return msg.ID, b.mc6.DeleteMessage(msg.ID) // nolint:wrapcheck
}
return msg.ID, b.mc.DeleteMessage(msg.ID)
}
@@ -183,36 +157,20 @@ 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)
if msg.ParentID != "" {
if b.mc6 != nil {
post, _, err := b.mc6.Client.GetPost(msg.ParentID, "")
if err != nil {
b.Log.Errorf("getting post %s failed: %s", msg.ParentID, err)
}
if post.RootId != "" {
msg.ParentID = post.RootId
}
} else {
post, res := b.mc.Client.GetPost(msg.ParentID, "")
if res.Error != nil {
b.Log.Errorf("getting post %s failed: %s", msg.ParentID, res.Error.DetailedError)
}
if post.RootId != "" {
msg.ParentID = post.RootId
}
post, _, err := b.mc.Client.GetPost(msg.ParentID, "")
if err != nil {
b.Log.Errorf("getting post %s failed: %s", msg.ParentID, err)
}
if post.RootId != "" {
msg.ParentID = post.RootId
}
}
// Upload a file if it exists
if msg.Extra != nil {
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
if b.mc6 != nil {
if _, err := b.mc6.PostMessage(b.getChannelID(rmsg.Channel), rmsg.Username+rmsg.Text, msg.ParentID); err != nil {
b.Log.Errorf("PostMessage failed: %s", err)
}
} 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 _, 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 {
@@ -227,17 +185,9 @@ func (b *Bmattermost) Send(msg config.Message) (string, error) {
// Edit message if we have an 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)
}
// Post normal message
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)
}

70
bridge/mumble/codec.go Normal file
View 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() {
}

View File

@@ -78,19 +78,75 @@ func (b *Bmumble) handleConnect(event *gumble.ConnectEvent) {
}
}
func (b *Bmumble) handleUserChange(event *gumble.UserChangeEvent) {
// Only care about changes to self
if event.User != event.Client.Self {
func (b *Bmumble) handleJoinLeave(event *gumble.UserChangeEvent) {
// Ignore events happening before setup is done
if b.Channel == nil {
return
}
// Someone attempted to move the user out of the configured channel; attempt to join back
if b.Channel != nil {
if b.GetBool("nosendjoinpart") {
return
}
b.Log.Debugf("Received gumble user change event: %+v", event)
text := ""
switch {
case event.Type&gumble.UserChangeKicked > 0:
text = " was kicked"
case event.Type&gumble.UserChangeBanned > 0:
text = " was banned"
case event.Type&gumble.UserChangeDisconnected > 0:
if event.User.Channel != nil && event.User.Channel.ID == *b.Channel {
text = " left"
}
case event.Type&gumble.UserChangeConnected > 0:
if event.User.Channel != nil && event.User.Channel.ID == *b.Channel {
text = " joined"
}
case event.Type&gumble.UserChangeChannel > 0:
// Treat Mumble channel changes the same as connects/disconnects; as far as matterbridge is concerned, they are identical
if event.User.Channel != nil && event.User.Channel.ID == *b.Channel {
text = " joined"
} else {
text = " left"
}
}
if text != "" {
b.Remote <- config.Message{
Username: "system",
Text: event.User.Name + text,
Channel: strconv.FormatUint(uint64(*b.Channel), 10),
Account: b.Account,
Event: config.EventJoinLeave,
}
}
}
func (b *Bmumble) handleUserModified(event *gumble.UserChangeEvent) {
// Ignore events happening before setup is done
if b.Channel == nil {
return
}
if event.Type&gumble.UserChangeChannel > 0 {
// Someone attempted to move the user out of the configured channel; attempt to join back
if err := b.doJoin(event.Client, *b.Channel); err != nil {
b.Log.Error(err)
}
}
}
func (b *Bmumble) handleUserChange(event *gumble.UserChangeEvent) {
// The UserChangeEvent is used for both the gumble client itself as well as other clients
if event.User != event.Client.Self {
// other users
b.handleJoinLeave(event)
} else {
// gumble user
b.handleUserModified(event)
}
}
func (b *Bmumble) handleDisconnect(event *gumble.DisconnectEvent) {
b.connected <- *event
}

View File

@@ -93,7 +93,7 @@ func (b *Bmumble) JoinChannel(channel config.ChannelInfo) error {
func (b *Bmumble) Send(msg config.Message) (string, error) {
// Only process text messages
b.Log.Debugf("=> Received local message %#v", msg)
if msg.Event != "" && msg.Event != config.EventUserAction {
if msg.Event != "" && msg.Event != config.EventUserAction && msg.Event != config.EventJoinLeave {
return "", nil
}
@@ -185,6 +185,7 @@ func (b *Bmumble) doConnect() error {
gumbleConfig.Password = password
}
registerNullCodecAsOpus()
client, err := gumble.DialWithDialer(new(net.Dialer), b.GetString("Server"), gumbleConfig, &b.tlsConfig)
if err != nil {
return err

View File

@@ -80,7 +80,7 @@ func (b *Bslack) handleSlackClient(messages chan *config.Message) {
case *slack.FileDeletedEvent:
rmsg, err := b.handleFileDeletedEvent(ev)
if err != nil {
b.Log.Errorf("%#v", err)
b.Log.Printf("%#v", err)
continue
}
messages <- rmsg
@@ -282,6 +282,13 @@ func (b *Bslack) handleStatusEvent(ev *slack.MessageEvent, rmsg *config.Message)
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) {
// File comments are set by the system (because there is no username given).
if ev.SubType == sFileComment {
@@ -290,12 +297,15 @@ func (b *Bslack) handleAttachments(ev *slack.MessageEvent, rmsg *config.Message)
// See if we have some text in the attachments.
if rmsg.Text == "" {
for _, attach := range ev.Attachments {
for i, attach := range ev.Attachments {
if attach.Text != "" {
if attach.Title != "" {
rmsg.Text = attach.Title + "\n"
rmsg.Text = getMessageTitle(&ev.Attachments[i])
}
rmsg.Text += attach.Text
if attach.Footer != "" {
rmsg.Text += "\n\n" + attach.Footer
}
} else {
rmsg.Text = attach.Fallback
}

View File

@@ -87,6 +87,9 @@ func (b *Bslack) populateMessageWithUserInfo(ev *slack.MessageEvent, rmsg *confi
if user.Profile.DisplayName != "" {
rmsg.Username = user.Profile.DisplayName
}
if b.GetBool("UseFullName") && user.Profile.RealName != "" {
rmsg.Username = user.Profile.RealName
}
return nil
}
@@ -124,7 +127,7 @@ var (
mentionRE = regexp.MustCompile(`<@([a-zA-Z0-9]+)>`)
channelRE = regexp.MustCompile(`<#[a-zA-Z0-9]+\|(.+?)>`)
variableRE = regexp.MustCompile(`<!((?:subteam\^)?[a-zA-Z0-9]+)(?:\|@?(.+?))?>`)
urlRE = regexp.MustCompile(`<(.*?)(\|.*?)?>`)
urlRE = regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`)
codeFenceRE = regexp.MustCompile(`(?m)^` + "```" + `\w+$`)
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
func (b *Bslack) replaceURL(text string) string {
for _, r := range urlRE.FindAllStringSubmatch(text, -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
return urlRE.ReplaceAllString(text, "[${2}](${1})")
}
func (b *Bslack) replaceb0rkedMarkDown(text string) string {

View File

@@ -321,7 +321,7 @@ func (b *Bslack) sendRTM(msg config.Message) (string, error) {
}
// Upload a file if it exists.
if msg.Extra != nil {
if len(msg.Extra) > 0 {
extraMsgs := helper.HandleExtra(&msg, b.General)
for i := range extraMsgs {
rmsg := &extraMsgs[i]
@@ -332,7 +332,7 @@ func (b *Bslack) sendRTM(msg config.Message) (string, error) {
}
}
// Upload files if necessary (from Slack, Telegram or Mattermost).
b.uploadFile(&msg, channelInfo.ID)
return b.uploadFile(&msg, channelInfo.ID)
}
// Post message.
@@ -443,7 +443,8 @@ func (b *Bslack) postMessage(msg *config.Message, channelInfo *slack.Channel) (s
}
// 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"] {
fi, ok := f.(config.FileInfo)
if !ok {
@@ -471,13 +472,22 @@ func (b *Bslack) uploadFile(msg *config.Message, channelID string) {
})
if err != nil {
b.Log.Errorf("uploadfile %#v", err)
return
return "", err
}
if res.ID != "" {
b.Log.Debugf("Adding file ID %s to cache with timestamp %s", res.ID, ts.String())
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 {

View File

@@ -57,6 +57,11 @@ func (b *Btelegram) handleForwarded(rmsg *config.Message, message *tgbotapi.Mess
return
}
if message.ForwardFromChat != nil && message.ForwardFrom == nil {
rmsg.Text = "Forwarded from " + message.ForwardFromChat.Title + ": " + rmsg.Text
return
}
if message.ForwardFrom == nil {
rmsg.Text = "Forwarded from " + unknownUser + ": " + rmsg.Text
return
@@ -66,6 +71,9 @@ func (b *Btelegram) handleForwarded(rmsg *config.Message, message *tgbotapi.Mess
if b.GetBool("UseFirstName") {
usernameForward = message.ForwardFrom.FirstName
}
if b.GetBool("UseFullName") {
usernameForward = message.ForwardFrom.FirstName + " " + message.ForwardFrom.LastName
}
if usernameForward == "" {
usernameForward = message.ForwardFrom.UserName
@@ -89,6 +97,9 @@ func (b *Btelegram) handleQuoting(rmsg *config.Message, message *tgbotapi.Messag
if b.GetBool("UseFirstName") {
usernameReply = message.ReplyToMessage.From.FirstName
}
if b.GetBool("UseFullName") {
usernameReply = message.ReplyToMessage.From.FirstName + " " + message.ReplyToMessage.From.LastName
}
if usernameReply == "" {
usernameReply = message.ReplyToMessage.From.UserName
if usernameReply == "" {
@@ -100,7 +111,11 @@ func (b *Btelegram) handleQuoting(rmsg *config.Message, message *tgbotapi.Messag
usernameReply = unknownUser
}
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)
}
}
}
@@ -112,6 +127,9 @@ func (b *Btelegram) handleUsername(rmsg *config.Message, message *tgbotapi.Messa
if b.GetBool("UseFirstName") {
rmsg.Username = message.From.FirstName
}
if b.GetBool("UseFullName") {
rmsg.Username = message.From.FirstName + " " + message.From.LastName
}
if rmsg.Username == "" {
rmsg.Username = message.From.UserName
if rmsg.Username == "" {
@@ -129,6 +147,9 @@ func (b *Btelegram) handleUsername(rmsg *config.Message, message *tgbotapi.Messa
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
@@ -155,7 +176,7 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
if update.Message == nil && update.ChannelPost == nil &&
update.EditedMessage == nil && update.EditedChannelPost == nil {
b.Log.Error("Getting nil messages, this shouldn't happen.")
b.Log.Info("Received event without messages, skipping.")
continue
}
@@ -182,6 +203,14 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
rmsg.ID = strconv.Itoa(message.MessageID)
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
b.handleUsername(&rmsg, message)
@@ -197,11 +226,9 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
// quote the previous message
b.handleQuoting(&rmsg, message)
// handle entities (adding URLs)
b.handleEntities(&rmsg, message)
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
if message.From != nil {
rmsg.Avatar = helper.GetAvatar(b.avatarMap, strconv.FormatInt(message.From.ID, 10), b.General)
@@ -358,7 +385,7 @@ func (b *Btelegram) getDownloadInfo(id string, suffix string, urlpart bool) (str
urlPart := strings.Split(url, "/")
name = urlPart[len(urlPart)-1]
}
if suffix != "" && !strings.HasSuffix(name, suffix) {
if suffix != "" && !strings.HasSuffix(name, suffix) && !strings.HasSuffix(name, ".webm") {
name += suffix
}
text := " " + url
@@ -377,7 +404,7 @@ func (b *Btelegram) handleDelete(msg *config.Message, chatid int64) (string, err
}
cfg := tgbotapi.NewDeleteMessage(chatid, msgid)
_, err = b.c.Send(cfg)
_, err = b.c.Request(cfg)
return "", err
}
@@ -416,42 +443,57 @@ func (b *Btelegram) handleEdit(msg *config.Message, chatid int64) (string, error
}
// handleUploadFile handles native upload of files
func (b *Btelegram) handleUploadFile(msg *config.Message, chatid int64) string {
var c tgbotapi.Chattable
func (b *Btelegram) handleUploadFile(msg *config.Message, chatid int64, parentID int) (string, error) {
var media []interface{}
for _, f := range msg.Extra["file"] {
fi := f.(config.FileInfo)
file := tgbotapi.FileBytes{
Name: fi.Name,
Bytes: *fi.Data,
}
if b.GetString("MessageFormat") == HTMLFormat {
fi.Comment = makeHTML(html.EscapeString(fi.Comment))
}
switch filepath.Ext(fi.Name) {
case ".jpg", ".jpe", ".png":
pc := tgbotapi.NewPhoto(chatid, file)
pc.Caption, pc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
c = pc
pc := tgbotapi.NewInputMediaPhoto(file)
if fi.Comment != "" {
pc.Caption, pc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
}
media = append(media, pc)
case ".mp4", ".m4v":
vc := tgbotapi.NewVideo(chatid, file)
vc.Caption, vc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
c = vc
vc := tgbotapi.NewInputMediaVideo(file)
if fi.Comment != "" {
vc.Caption, vc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
}
media = append(media, vc)
case ".mp3", ".oga":
ac := tgbotapi.NewAudio(chatid, file)
ac.Caption, ac.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
c = ac
ac := tgbotapi.NewInputMediaAudio(file)
if fi.Comment != "" {
ac.Caption, ac.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
}
media = append(media, ac)
case ".ogg":
voc := tgbotapi.NewVoice(chatid, file)
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:
dc := tgbotapi.NewDocument(chatid, file)
dc.Caption, dc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
c = dc
}
_, err := b.c.Send(c)
if err != nil {
b.Log.Errorf("file upload failed: %#v", err)
dc := tgbotapi.NewInputMediaDocument(file)
if fi.Comment != "" {
dc.Caption, dc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
}
media = append(media, dc)
}
}
return ""
return b.sendMediaFiles(msg, chatid, parentID, media)
}
func (b *Btelegram) handleQuote(message, quoteNick, quoteMessage string) string {
@@ -480,34 +522,59 @@ func (b *Btelegram) handleEntities(rmsg *config.Message, message *tgbotapi.Messa
}
indexMovedBy := 0
prevLinkOffset := -1
// for now only do URL replacements
for _, e := range message.Entities {
asRunes := utf16.Encode([]rune(rmsg.Text))
if e.Type == "text_link" {
offset := e.Offset + indexMovedBy
url, err := e.ParseURL()
if err != nil {
b.Log.Errorf("entity text_link url parse failed: %s", err)
continue
}
utfEncodedString := utf16.Encode([]rune(rmsg.Text))
if e.Offset+e.Length > len(utfEncodedString) {
b.Log.Errorf("entity length is too long %d > %d", e.Offset+e.Length, len(utfEncodedString))
if offset+e.Length > len(utfEncodedString) {
b.Log.Errorf("entity length is too long %d > %d", offset+e.Length, len(utfEncodedString))
continue
}
link := utf16.Decode(utfEncodedString[e.Offset : e.Offset+e.Length])
rmsg.Text = strings.Replace(rmsg.Text, string(link), url.String(), 1)
rmsg.Text = string(utf16.Decode(asRunes[:offset+e.Length])) + " (" + url.String() + ")" + string(utf16.Decode(asRunes[offset+e.Length:]))
indexMovedBy += len(url.String()) + 3
prevLinkOffset = e.Offset
}
if e.Offset == prevLinkOffset {
continue
}
if e.Type == "code" {
offset := e.Offset + indexMovedBy
rmsg.Text = rmsg.Text[:offset] + "`" + rmsg.Text[offset:offset+e.Length] + "`" + rmsg.Text[offset+e.Length:]
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 = rmsg.Text[:offset] + "```\n" + rmsg.Text[offset:offset+e.Length] + "\n```" + rmsg.Text[offset+e.Length:]
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
}
}
}

View File

@@ -2,7 +2,6 @@ package btelegram
import (
"bytes"
"html"
"github.com/russross/blackfriday"
)
@@ -24,10 +23,16 @@ func (options *customHTML) Paragraph(out *bytes.Buffer, text func() bool) {
func (options *customHTML) BlockCode(out *bytes.Buffer, text []byte, lang string) {
out.WriteString("<pre>")
out.WriteString(html.EscapeString(string(text)))
out.WriteString(string(text))
out.WriteString("</pre>\n")
}
func (options *customHTML) CodeSpan(out *bytes.Buffer, text []byte) {
out.WriteString("<code>")
out.WriteString(string(text))
out.WriteString("</code>")
}
func (options *customHTML) Header(out *bytes.Buffer, text func() bool, level int, id string) {
options.Paragraph(out, text)
}
@@ -42,6 +47,10 @@ func (options *customHTML) BlockQuote(out *bytes.Buffer, text []byte) {
out.WriteByte('\n')
}
func (options *customHTML) LineBreak(out *bytes.Buffer) {
out.WriteByte('\n')
}
func (options *customHTML) List(out *bytes.Buffer, text func() bool, flags int) {
options.Paragraph(out, text)
}

View File

@@ -1,6 +1,7 @@
package btelegram
import (
"fmt"
"html"
"log"
"strconv"
@@ -100,7 +101,7 @@ func (b *Btelegram) Send(msg config.Message) (string, error) {
}
if b.GetString("MessageFormat") == HTMLFormat {
msg.Text = makeHTML(msg.Text)
msg.Text = makeHTML(html.EscapeString(msg.Text))
}
// Delete message
@@ -108,16 +109,27 @@ func (b *Btelegram) Send(msg config.Message) (string, error) {
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
if msg.Extra != nil {
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)
}
}
// check if we have files to upload (from slack, telegram or mattermost)
if len(msg.Extra["file"]) > 0 {
b.handleUploadFile(&msg, chatid)
return b.handleUploadFile(&msg, chatid, parentID)
}
}
@@ -131,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
// when sending media with text caption
if msg.Text != "" {
return b.sendMessage(chatid, msg.Username, msg.Text)
return b.sendMessage(chatid, msg.Username, msg.Text, parentID)
}
return "", nil
@@ -145,10 +157,10 @@ func (b *Btelegram) getFileDirectURL(id string) string {
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.Text, m.ParseMode = TGGetParseMode(b, username, text)
m.ReplyToMessageID = parentID
m.DisableWebPagePreview = b.GetBool("DisableWebPagePreview")
res, err := b.c.Send(m)
@@ -158,6 +170,29 @@ func (b *Btelegram) sendMessage(chatid int64, username, text string) (string, er
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) {
fi := msg.Extra["file"][0].(config.FileInfo)
/* if we have a sha we have successfully uploaded the file to the media server,

View File

@@ -64,7 +64,7 @@ func (b *Bvk) Connect() error {
go func() {
err := b.lp.Run()
if err != nil {
b.Log.Fatal("Enable longpoll in group management")
b.Log.WithError(err).Fatal("Enable longpoll in group management")
}
}()
@@ -223,7 +223,7 @@ func (b *Bvk) uploadFiles(extra map[string][]interface{}, peerID int) (string, s
}
a, err := b.uploadFile(fi, peerID)
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)
@@ -237,7 +237,8 @@ func (b *Bvk) uploadFile(file config.FileInfo, peerID int) (string, error) {
photoRE := regexp.MustCompile(".(jpg|jpe|png)$")
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 {
return "", err
}

View File

@@ -1,3 +1,4 @@
// nolint:goconst
package bwhatsapp
import (
@@ -134,6 +135,7 @@ func (b *Bwhatsapp) HandleTextMessage(message whatsapp.TextMessage) {
}
// HandleImageMessage sent from WhatsApp, relay it to the brige
// nolint:funlen
func (b *Bwhatsapp) HandleImageMessage(message whatsapp.ImageMessage) {
if message.Info.FromMe || message.Info.Timestamp < b.startedAt {
return

View File

@@ -111,8 +111,7 @@ func (b *Bwhatsapp) getSenderName(senderJid string) string {
}
// try to reload this contact
_, err := b.conn.Contacts()
if err != nil {
if _, err := b.conn.Contacts(); err != nil {
b.Log.Errorf("error on update of contacts: %v", err)
}

View File

@@ -40,6 +40,11 @@ type Bwhatsapp struct {
func New(cfg *bridge.Config) bridge.Bridger {
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 == "" {
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 != "" {
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

View File

@@ -0,0 +1,338 @@
// +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)
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))
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(msg.Info)
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(msg.Info)
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(msg.Info)
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(msg.Info)
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, 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
}

View File

@@ -0,0 +1,124 @@
//go:build whatsappmulti
// +build whatsappmulti
package bwhatsapp
import (
"fmt"
"strings"
"go.mau.fi/whatsmeow"
"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) reloadContacts() {
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
}
}
func (b *Bwhatsapp) getSenderName(info types.MessageInfo) string {
// Parse AD JID
var senderJid types.JID
senderJid.User, senderJid.Server = info.Sender.User, info.Sender.Server
sender, exists := b.contacts[senderJid]
if !exists || (sender.FullName == "" && sender.FirstName == "") {
b.reloadContacts() // Contacts may need to be reloaded
sender, exists = b.contacts[senderJid]
}
if exists && sender.FullName != "" {
return sender.FullName
}
if info.PushName != "" {
return info.PushName
}
if exists && sender.FirstName != "" {
return sender.FirstName
}
return "Someone"
}
func (b *Bwhatsapp) getSenderNotify(senderJid types.JID) string {
sender, exists := b.contacts[senderJid]
if !exists || (sender.FullName == "" && sender.PushName == "" && sender.FirstName == "") {
b.reloadContacts() // Contacts may need to be reloaded
sender, exists = b.contacts[senderJid]
}
if !exists {
return "someone"
}
if exists && sender.FullName != "" {
return sender.FullName
}
if exists && sender.PushName != "" {
return sender.PushName
}
if exists && sender.FirstName != "" {
return sender.FirstName
}
return "someone"
}
func (b *Bwhatsapp) GetProfilePicThumb(jid string) (*types.ProfilePictureInfo, error) {
pjid, _ := types.ParseJID(jid)
info, err := b.wc.GetProfilePictureInfo(pjid, &whatsmeow.GetProfilePictureParams{
Preview: 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
}

View File

@@ -0,0 +1,413 @@
//go:build whatsappmulti
// +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)
caption := msg.Username + fi.Comment
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,
Caption: &caption,
MediaKey: resp.MediaKey,
FileEncSha256: resp.FileEncSHA256,
FileSha256: resp.FileSHA256,
FileLength: goproto.Uint64(resp.FileLength),
Url: &resp.URL,
}
b.Log.Debugf("=> Sending %#v as a document", msg)
ID := whatsmeow.GenerateMessageID()
_, err = b.wc.SendMessage(context.TODO(), groupJID, &message, whatsmeow.SendRequestExtra{ID: ID})
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 as an image", msg)
ID := whatsmeow.GenerateMessageID()
_, err = b.wc.SendMessage(context.TODO(), groupJID, &message, whatsmeow.SendRequestExtra{ID: ID})
return ID, err
}
// Post a video message from the bridge to WhatsApp
func (b *Bwhatsapp) PostVideoMessage(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.MediaVideo)
if err != nil {
return "", err
}
var message proto.Message
message.VideoMessage = &proto.VideoMessage{
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 as a video", msg)
ID := whatsmeow.GenerateMessageID()
_, err = b.wc.SendMessage(context.TODO(), groupJID, &message, whatsmeow.SendRequestExtra{ID: ID})
return ID, err
}
// Post audio inline
func (b *Bwhatsapp) PostAudioMessage(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.MediaAudio)
if err != nil {
return "", err
}
var message proto.Message
message.AudioMessage = &proto.AudioMessage{
Mimetype: &filetype,
MediaKey: resp.MediaKey,
FileEncSha256: resp.FileEncSHA256,
FileSha256: resp.FileSHA256,
FileLength: goproto.Uint64(resp.FileLength),
Url: &resp.URL,
}
b.Log.Debugf("=> Sending %#v as audio", msg)
ID := whatsmeow.GenerateMessageID()
_, err = b.wc.SendMessage(context.TODO(), groupJID, &message, whatsmeow.SendRequestExtra{ID: ID})
var captionMessage proto.Message
caption := msg.Username + fi.Comment + "\u2B06" // the char on the end is upwards arrow emoji
captionMessage.Conversation = &caption
captionID := whatsmeow.GenerateMessageID()
_, err = b.wc.SendMessage(context.TODO(), groupJID, &captionMessage, whatsmeow.SendRequestExtra{ID: captionID})
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)
case "video/mp4", "video/3gpp": // TODO: Check if codecs are supported by WA
return b.PostVideoMessage(msg, filetype)
case "audio/ogg":
return b.PostAudioMessage(msg, "audio/ogg; codecs=opus") // TODO: Detect if it is actually OPUS
case "audio/aac", "audio/mp4", "audio/amr", "audio/mpeg":
return b.PostAudioMessage(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(context.TODO(), groupJID, &message, whatsmeow.SendRequestExtra{ID: ID})
return ID, err
}

View File

@@ -1,3 +1,154 @@
# v1.26.0
## New features
- irc: Allow substitution of bot's nick in RunCommands (irc) (#1890)
- matrix: Add Matrix username spoofing (#1875)
## Enhancements
- general: Update dependencies (#1951)
- mattermost: Remove mattermost 5 support (#1936)
- mumble: Implement sending of EventJoinLeave both to and from Mumble (#1915)
- whatsappmulti: Improve attachment handling (whatsapp) (#1928)
- whatsappmulti: Handle incoming document captions from whatsapp (#1935)
## Bugfix
- irc: Fix empty messages on IRC (#1897)
- telegram: Fix message html entities escaping when sending to Telegram (#1855)
- telegram/slack: Fix error messages in telegram and slack bridges (#1862)
- telegram: Fix telegram attachment comment formatting and escaping (#1920)
- telegram: Make the cgo lottie a build tag (-tag cgolottie) (#1955)
- whatsappmulti: Update dependencies and fix whatsmeow API changes (#1887)
- whatsappmulti: Fix the "Someone" nickname problem (whatsapp) (#1931)
This release couldn't exist without the following contributors:
@s3lph, @sas1024, @Glandos, @jx11r, @Lucki, @BuckarooBanzay, @ilmaisin, @Kufat
# v1.25.2
## Enhancements
- general: Update dependencies (#1851,#1841)
- mattermost: Support mattermost v7.x (#1852)
## Bugfix
- discord: Fix Unwanted join notifications from one Discord server to another (#1612)
- discord: Ignore events from other guilds, add nosendjoinpart support (#1846)
This release couldn't exist without the following contributors:
@wlcx
# 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.
@@ -6,6 +157,9 @@ If you're running whatsapp you should update.
- 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.
@@ -20,6 +174,9 @@ If you're running mattermost 6 you should update.
- 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

View 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
View 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
}

View File

@@ -1,4 +1,5 @@
// +build !nowhatsapp
// +build !whatsappmulti
package bridgemap

View File

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

View File

@@ -299,13 +299,30 @@ func (gw *Gateway) ignoreMessage(msg *config.Message) bool {
igNicks := strings.Fields(gw.Bridges[msg.Account].GetString("IgnoreNicks"))
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 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 {
if dest.GetBool("StripNick") {
re := regexp.MustCompile("[^a-zA-Z0-9]+")

131
go.mod
View File

@@ -6,55 +6,60 @@ require (
github.com/Benau/tgsconverter v0.0.0-20210809170556-99f4a4f6337f
github.com/Philipp15b/go-steam v1.0.1-0.20200727090957-6ae9b3c0a560
github.com/Rhymen/go-whatsapp v0.1.2-0.20211102134409-31a2e740845c
github.com/SevereCloud/vksdk/v2 v2.13.0
github.com/d5/tengo/v2 v2.10.0
github.com/SevereCloud/vksdk/v2 v2.15.0
github.com/bwmarrin/discordgo v0.27.0
github.com/d5/tengo/v2 v2.13.0
github.com/davecgh/go-spew v1.1.1
github.com/fsnotify/fsnotify v1.5.1
github.com/fsnotify/fsnotify v1.6.0
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
github.com/gomarkdown/markdown v0.0.0-20211207152620-5d6539fd8bfc
github.com/google/gops v0.3.22
github.com/gomarkdown/markdown v0.0.0-20221013030248-663e2500819c
github.com/google/gops v0.3.26
github.com/gorilla/schema v1.2.0
github.com/gorilla/websocket v1.4.2
github.com/harmony-development/shibshib v0.0.0-20211127182844-512296f7c548
github.com/hashicorp/golang-lru v0.5.4
github.com/gorilla/websocket v1.5.0
github.com/harmony-development/shibshib v0.0.0-20220101224523-c98059d09cfa
github.com/hashicorp/golang-lru v0.6.0
github.com/jpillora/backoff v1.0.0
github.com/keybase/go-keybase-chat-bot v0.0.0-20211201215354-ee4b23828b55
github.com/kyokomi/emoji/v2 v2.2.8
github.com/labstack/echo/v4 v4.6.3
github.com/lrstanley/girc v0.0.0-20211023233735-147f0ff77566
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16
github.com/keybase/go-keybase-chat-bot v0.0.0-20221220212439-e48d9abd2c20
github.com/kyokomi/emoji/v2 v2.2.11
github.com/labstack/echo/v4 v4.10.0
github.com/lrstanley/girc v0.0.0-20221222153823-a92667a5c9b4
github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20211016222428-79310a412696
github.com/matterbridge/discordgo v0.21.2-0.20210201201054-fb39a175b4f7
github.com/matterbridge/go-xmpp v0.0.0-20211030125215-791a06c5f1be
github.com/matterbridge/gomatrix v0.0.0-20220411225302-271e5088ea27
github.com/matterbridge/gozulipbot v0.0.0-20211023205727-a19d6c1f3b75
github.com/matterbridge/logrus-prefixed-formatter v0.5.3-0.20200523233437-d971309a77ba
github.com/matterbridge/matterclient v0.0.0-20211107234719-faca3cd42315
github.com/matterbridge/matterclient v0.0.0-20220624224459-272af20c7ddf
github.com/mattermost/mattermost-server/v5 v5.39.3
github.com/mattermost/mattermost-server/v6 v6.3.0
github.com/mattermost/mattermost-server/v6 v6.7.2
github.com/mattn/godown v0.0.1
github.com/missdeer/golib v1.0.4
github.com/nelsonken/gomf v0.0.0-20180504123937-a9dd2f9deae9
github.com/mdp/qrterminal v1.0.1
github.com/nelsonken/gomf v0.0.0-20190423072027-c65cc0469e94
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/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d
github.com/shazow/ssh-chat v1.10.1
github.com/sirupsen/logrus v1.8.1
github.com/slack-go/slack v0.10.0
github.com/spf13/viper v1.10.1
github.com/stretchr/testify v1.7.0
github.com/sirupsen/logrus v1.9.0
github.com/slack-go/slack v0.12.1
github.com/spf13/viper v1.15.0
github.com/stretchr/testify v1.8.1
github.com/vincent-petithory/dataurl v1.0.0
github.com/writeas/go-strip-markdown v2.0.1+incompatible
github.com/yaegashi/msgraph.go v0.1.4
github.com/zfjagann/golang-ring v0.0.0-20210116075443-7c86fdb43134
golang.org/x/image v0.0.0-20211028202545-6944b10bf410
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
github.com/zfjagann/golang-ring v0.0.0-20220330170733-19bcea1b6289
go.mau.fi/whatsmeow v0.0.0-20230128195103-dcbc8dd31a22
golang.org/x/image v0.3.0
golang.org/x/oauth2 v0.4.0
golang.org/x/text v0.6.0
gomod.garykim.dev/nc-talk v0.3.0
google.golang.org/protobuf v1.28.1
gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376
layeh.com/gumble v0.0.0-20200818122324-146f9205029b
layeh.com/gumble v0.0.0-20221205141517-d1df60a3cc14
modernc.org/sqlite v1.20.3
)
require (
filippo.io/edwards25519 v1.0.0 // 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
@@ -68,74 +73,90 @@ require (
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.14.1 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/labstack/gommon v0.3.1 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/klauspost/compress v1.15.8 // indirect
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/magiconair/properties v1.8.7 // 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-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/minio-go/v7 v7.0.16 // indirect
github.com/minio/minio-go/v7 v7.0.24 // 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/mitchellh/mapstructure v1.5.0 // 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 v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // 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-20200617195104-da1b6568686e // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9 // indirect
github.com/spf13/afero v1.9.3 // indirect
github.com/spf13/cast v1.5.0 // 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/subosito/gotenv v1.4.2 // 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/valyala/fasttemplate v1.2.2 // 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.1.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.7.0 // indirect
go.uber.org/zap v1.17.0 // indirect
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 // indirect
golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9 // indirect
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.21.0 // indirect
golang.org/x/crypto v0.4.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/net v0.5.0 // indirect
golang.org/x/sys v0.4.0 // indirect
golang.org/x/term v0.4.0 // indirect
golang.org/x/time v0.2.0 // indirect
golang.org/x/tools v0.1.12 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/ini.v1 v1.66.2 // indirect
gopkg.in/ini.v1 v1.67.0 // 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
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect
modernc.org/ccgo/v3 v3.16.13 // indirect
modernc.org/libc v1.22.2 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.4.0 // indirect
modernc.org/opt v0.1.3 // indirect
modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.0.1 // indirect
rsc.io/qr v0.2.0 // indirect
)
replace github.com/matrix-org/gomatrix => github.com/matterbridge/gomatrix v0.0.0-20220205235239-607eb9ee6419
//replace github.com/matrix-org/gomatrix => github.com/matterbridge/gomatrix v0.0.0-20220205235239-607eb9ee6419
go 1.17
go 1.18

615
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -122,10 +122,11 @@ RejoinDelay=0
#Only works in IRC right now.
ColorNicks=false
#RunCommands allows you to send RAW irc commands after connection
#RunCommands allows you to send RAW irc commands after connection.
#The string {BOTNICK} (case sensitive) will be replaced with the bot's current nickname.
#Array of strings
#OPTIONAL (default empty)
RunCommands=["PRIVMSG user hello","PRIVMSG chanserv something"]
RunCommands=["PRIVMSG user hello","PRIVMSG chanserv something", "MODE {BOTNICK} +B"]
#PingDelay specifies how long to wait to send a ping to the irc server.
#You can use s for second, m for minute
@@ -187,7 +188,7 @@ Label=""
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false)
ShowJoinPart=false
@@ -197,7 +198,7 @@ ShowJoinPart=false
VerboseJoinPart=false
#Do not send joins/parts to other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false)
NoSendJoinPart=false
@@ -324,7 +325,7 @@ Label=""
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false)
ShowJoinPart=false
@@ -491,12 +492,12 @@ Label=""
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false)
ShowJoinPart=false
#Do not send joins/parts to other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false)
NoSendJoinPart=false
@@ -578,7 +579,7 @@ Label=""
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false)
ShowJoinPart=false
@@ -686,7 +687,7 @@ Label=""
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false)
ShowJoinPart=false
@@ -825,12 +826,12 @@ Label=""
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false)
ShowJoinPart=false
#Do not send joins/parts to other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false)
NoSendJoinPart=false
@@ -862,6 +863,10 @@ ShowUserTyping=false
#Default "<clipped message>"
MessageClipped="<clipped message>"
#If enabled use the slack "Real Name" as username.
#OPTIONAL (default false)
UseFullName=false
###################################################################
#discord section
###################################################################
@@ -1036,6 +1041,12 @@ DisableWebPagePreview=false
#OPTIONAL (default 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
#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.
@@ -1127,7 +1138,7 @@ Label=""
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false)
ShowJoinPart=false
@@ -1141,6 +1152,12 @@ StripNick=false
#OPTIONAL (default 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
###################################################################
@@ -1258,7 +1275,7 @@ Label=""
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false)
ShowJoinPart=false
@@ -1312,6 +1329,15 @@ HTMLDisable=false
# UseUserName shows the username instead of the server nickname
UseUserName=false
# Matrix quotes replies and as of matterbridge 1.24.0 we strip those as this causes
# issues with bridges support threading and have PreserveThreading enabled.
# But if you for example use mattermost or discord with webhooks you'll need to enable
# this (and keep PreserveThreading disabled) if you want something that looks like a reply from matrix.
# See issues:
# - https://github.com/42wim/matterbridge/issues/1819
# - https://github.com/42wim/matterbridge/issues/1780
KeepQuotedReply=false
#Nicks you want to ignore.
#Regular expressions supported
#Messages from those users will not be sent to other bridges.
@@ -1361,10 +1387,15 @@ Label=""
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false)
ShowJoinPart=false
#Rename the bot in the current room to the username of the message
#This will make an additional API request per message and will probably count towards rate limits
#OPTIONAL (default false)
SpoofUsername=false
#StripNick only allows alphanumerical nicks. See https://github.com/42wim/matterbridge/issues/285
#It will strip other characters from the nick
#OPTIONAL (default false)
@@ -1453,7 +1484,7 @@ Label=""
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false)
ShowJoinPart=false
@@ -1541,6 +1572,15 @@ SkipTLSVerify=false
#Default "<clipped message>"
MessageClipped="<clipped message>"
#Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false)
ShowJoinPart=false
#Do not send joins/parts to other bridges
#OPTIONAL (default false)
NoSendJoinPart=false
###################################################################
#VK
###################################################################
@@ -1653,7 +1693,7 @@ Label=""
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false)
ShowJoinPart=false

View File

@@ -1,7 +1,7 @@
#WARNING: as this file contains credentials, be sure to set correct file permissions
[irc]
[irc.freenode]
Server="irc.freenode.net:6667"
[irc.libera]
Server="irc.libera.chat:6667"
Nick="matterbot"
[mattermost]
@@ -17,7 +17,7 @@
name="gateway1"
enable=true
[[gateway.inout]]
account="irc.freenode"
account="irc.libera"
channel="#testing"
[[gateway.inout]]
@@ -29,6 +29,6 @@ enable=true
#name="gateway2"
#enable=true
#inout = [
# { account="irc.freenode", channel="#testing", options={key="channelkey"}},
# { account="irc.libera", channel="#testing", options={key="channelkey"}},
# { account="mattermost.work", channel="off-topic" },
#]

27
vendor/filippo.io/edwards25519/LICENSE generated vendored Normal file
View 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
View 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, and was finally [merged back into the Go standard library](https://golang.org/cl/276272) as of Go 1.17. It now tracks the upstream codebase and extends it with additional functionality.
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
View 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
View 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
View 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
}

420
vendor/filippo.io/edwards25519/field/fe.go generated vendored Normal file
View File

@@ -0,0 +1,420 @@
// 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, SetBytes 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, but
// consistent with most Ed25519 implementations.
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:255 (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) (R *Element, wasSquare int) {
t0 := new(Element)
// r = (u * v3) * (u * v7)^((p-5)/8)
v2 := new(Element).Square(v)
uv3 := new(Element).Multiply(u, t0.Multiply(v2, v))
uv7 := new(Element).Multiply(uv3, t0.Square(v2))
rr := new(Element).Multiply(uv3, t0.Pow22523(uv7))
check := new(Element).Multiply(v, t0.Square(rr)) // check = v * r^2
uNeg := new(Element).Negate(u)
correctSignSqrt := check.Equal(u)
flippedSignSqrt := check.Equal(uNeg)
flippedSignSqrtI := check.Equal(t0.Multiply(uNeg, sqrtM1))
rPrime := new(Element).Multiply(rr, sqrtM1) // r_prime = SQRT_M1 * r
// r = CT_SELECT(r_prime IF flipped_sign_sqrt | flipped_sign_sqrt_i ELSE r)
rr.Select(rPrime, rr, flippedSignSqrt|flippedSignSqrtI)
r.Absolute(rr) // Choose the nonnegative square root.
return r, correctSignSqrt | flippedSignSqrt
}

13
vendor/filippo.io/edwards25519/field/fe_amd64.go generated vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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()
}

50
vendor/filippo.io/edwards25519/field/fe_extra.go generated vendored Normal file
View File

@@ -0,0 +1,50 @@
// 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 field
import "errors"
// This file contains additional functionality that is not included in the
// upstream crypto/ed25519/internal/edwards25519/field package.
// SetWideBytes sets v to x, where x is a 64-byte little-endian encoding, which
// is reduced modulo the field order. If x is not of the right length,
// SetWideBytes returns nil and an error, and the receiver is unchanged.
//
// SetWideBytes is not necessary to select a uniformly distributed value, and is
// only provided for compatibility: SetBytes can be used instead as the chance
// of bias is less than 2⁻²⁵⁰.
func (v *Element) SetWideBytes(x []byte) (*Element, error) {
if len(x) != 64 {
return nil, errors.New("edwards25519: invalid SetWideBytes input size")
}
// Split the 64 bytes into two elements, and extract the most significant
// bit of each, which is ignored by SetBytes.
lo, _ := new(Element).SetBytes(x[:32])
loMSB := uint64(x[31] >> 7)
hi, _ := new(Element).SetBytes(x[32:])
hiMSB := uint64(x[63] >> 7)
// The output we want is
//
// v = lo + loMSB * 2²⁵⁵ + hi * 2²⁵⁶ + hiMSB * 2⁵¹¹
//
// which applying the reduction identity comes out to
//
// v = lo + loMSB * 19 + hi * 2 * 19 + hiMSB * 2 * 19²
//
// l0 will be the sum of a 52 bits value (lo.l0), plus a 5 bits value
// (loMSB * 19), a 6 bits value (hi.l0 * 2 * 19), and a 10 bits value
// (hiMSB * 2 * 19²), so it fits in a uint64.
v.l0 = lo.l0 + loMSB*19 + hi.l0*2*19 + hiMSB*2*19*19
v.l1 = lo.l1 + hi.l1*2*19
v.l2 = lo.l2 + hi.l2*2*19
v.l3 = lo.l3 + hi.l3*2*19
v.l4 = lo.l4 + hi.l4*2*19
return v.carryPropagate(), nil
}

266
vendor/filippo.io/edwards25519/field/fe_generic.go generated vendored Normal file
View File

@@ -0,0 +1,266 @@
// 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
// c4 is at most 64 - 51 = 13 bits, so c4*19 is at most 18 bits, and
// the final l0 will be at most 52 bits. Similarly for the rest.
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
}

1030
vendor/filippo.io/edwards25519/scalar.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

214
vendor/filippo.io/edwards25519/scalarmult.go generated vendored Normal file
View 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
View 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]
}

View File

@@ -54,6 +54,11 @@ linters:
- nilnil
- tenv
- nestif
- grouper
- decorder
- containedctx
# - execinquery # FIXME: panic in 1.46.0
- nosprintfhostport
# - wrapcheck # TODO: v3 Fix
# - testpackage # TODO: Fix testpackage
@@ -82,6 +87,9 @@ linters:
# - tagliatelle
# - errname
# - varnamelen
# - errchkjson
# - maintidx
# - nonamedreturns
# depricated
# - maligned

View File

@@ -1,7 +1,7 @@
# VK SDK for Golang
[![PkgGoDev](https://pkg.go.dev/badge/github.com/SevereCloud/vksdk/v2/v2)](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2?tab=subdirectories)
[![VK Developers](https://img.shields.io/badge/developers-%234a76a8.svg?logo=VK&logoColor=white)](https://vk.com/dev/)
[![VK Developers](https://img.shields.io/badge/developers-%234a76a8.svg?logo=VK&logoColor=white)](https://dev.vk.com/)
[![codecov](https://codecov.io/gh/SevereCloud/vksdk/branch/master/graph/badge.svg)](https://codecov.io/gh/SevereCloud/vksdk)
[![VK chat](https://img.shields.io/badge/VK%20chat-%234a76a8.svg?logo=VK&logoColor=white)](https://vk.me/join/AJQ1d6Or8Q00Y_CSOESfbqGt)
[![release](https://img.shields.io/github/v/tag/SevereCloud/vksdk?label=release)](https://github.com/SevereCloud/vksdk/releases)

View File

@@ -141,6 +141,8 @@ func (vk *VK) AccountSetInfo(params Params) (response int, err error) {
// AccountSetNameInMenu sets an application screen name
// (up to 17 characters), that is shown to the user in the left menu.
//
// Deprecated: This method is deprecated and may be disabled soon, please avoid
//
// https://vk.com/dev/account.setNameInMenu
func (vk *VK) AccountSetNameInMenu(params Params) (response int, err error) {
err = vk.RequestUnmarshal("account.setNameInMenu", &response, params)

View File

@@ -203,10 +203,13 @@ func buildQuery(sliceParams ...Params) (context.Context, url.Values) {
for _, params := range sliceParams {
for key, value := range params {
if key != ":context" {
query.Set(key, FmtValue(value, 0))
} else {
switch key {
case "access_token":
continue
case ":context":
ctx = value.(context.Context)
default:
query.Set(key, FmtValue(value, 0))
}
}
}
@@ -255,6 +258,9 @@ func (vk *VK) DefaultHandler(method string, sliceParams ...Params) (Response, er
acceptEncoding = "zstd"
}
token := sliceParams[len(sliceParams)-1]["access_token"].(string)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("User-Agent", vk.UserAgent)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
@@ -269,9 +275,15 @@ func (vk *VK) DefaultHandler(method string, sliceParams ...Params) (Response, er
switch resp.Header.Get("Content-Encoding") {
case "zstd":
reader, _ = zstd.NewReader(resp.Body)
zstdReader, _ := zstd.NewReader(resp.Body)
defer zstdReader.Close()
reader = zstdReader
case "gzip":
reader, _ = gzip.NewReader(resp.Body)
gzipReader, _ := gzip.NewReader(resp.Body)
defer gzipReader.Close()
reader = gzipReader
default:
reader = resp.Body
}

View File

@@ -4,6 +4,14 @@ import (
"github.com/SevereCloud/vksdk/v2/object"
)
// AppsAddUsersToTestingGroup method.
//
// https://vk.com/dev/apps.addUsersToTestingGroup
func (vk *VK) AppsAddUsersToTestingGroup(params Params) (response int, err error) {
err = vk.RequestUnmarshal("apps.addUsersToTestingGroup", &response, params)
return
}
// AppsDeleteAppRequests deletes all request notifications from the current app.
//
// https://vk.com/dev/apps.deleteAppRequests
@@ -140,6 +148,33 @@ func (vk *VK) AppsGetScore(params Params) (response string, err error) {
return
}
// AppsGetTestingGroupsResponse struct.
type AppsGetTestingGroupsResponse []object.AppsTestingGroup
// AppsGetTestingGroups method.
//
// https://vk.com/dev/apps.getTestingGroups
func (vk *VK) AppsGetTestingGroups(params Params) (response AppsGetTestingGroupsResponse, err error) {
err = vk.RequestUnmarshal("apps.getTestingGroups", &response, params)
return
}
// AppsRemoveTestingGroup method.
//
// https://vk.com/dev/apps.removeTestingGroup
func (vk *VK) AppsRemoveTestingGroup(params Params) (response int, err error) {
err = vk.RequestUnmarshal("apps.removeTestingGroup", &response, params)
return
}
// AppsRemoveUsersFromTestingGroups method.
//
// https://vk.com/dev/apps.removeUsersFromTestingGroups
func (vk *VK) AppsRemoveUsersFromTestingGroups(params Params) (response int, err error) {
err = vk.RequestUnmarshal("apps.removeUsersFromTestingGroups", &response, params)
return
}
// AppsSendRequest sends a request to another user in an app that uses VK authorization.
//
// https://vk.com/dev/apps.sendRequest
@@ -147,3 +182,16 @@ func (vk *VK) AppsSendRequest(params Params) (response int, err error) {
err = vk.RequestUnmarshal("apps.sendRequest", &response, params)
return
}
// AppsUpdateMetaForTestingGroupResponse struct.
type AppsUpdateMetaForTestingGroupResponse struct {
GroupID int `json:"group_id"`
}
// AppsUpdateMetaForTestingGroup method.
//
// https://vk.com/dev/apps.updateMetaForTestingGroup
func (vk *VK) AppsUpdateMetaForTestingGroup(params Params) (response AppsUpdateMetaForTestingGroupResponse, err error) {
err = vk.RequestUnmarshal("apps.updateMetaForTestingGroup", &response, params)
return
}

View File

@@ -622,6 +622,12 @@ const (
// Anonymous token is invalid.
ErrAnonymousTokenInvalid ErrorType = 1116
// Access token has expired.
ErrAuthAccessTokenHasExpired ErrorType = 1117
// Anonymous token ip mismatch.
ErrAuthAnonymousTokenIPMismatch ErrorType = 1118
// Invalid document id.
ErrParamDocID ErrorType = 1150

View File

@@ -116,6 +116,14 @@ func (vk *VK) MessagesEditChat(params Params) (response int, err error) {
return
}
// MessagesForceCallFinish method.
//
// https://vk.com/dev/messages.forceCallFinish
func (vk *VK) MessagesForceCallFinish(params Params) (response int, err error) {
err = vk.RequestUnmarshal("messages.forceCallFinish", &response, params)
return
}
// MessagesGetByConversationMessageIDResponse struct.
type MessagesGetByConversationMessageIDResponse struct {
Count int `json:"count"`
@@ -633,6 +641,20 @@ func (vk *VK) MessagesSetChatPhoto(params Params) (response MessagesSetChatPhoto
return
}
// MessagesStartCallResponse struct.
type MessagesStartCallResponse struct {
JoinLink string `json:"join_link"`
CallID string `json:"call_id"`
}
// MessagesStartCall method.
//
// https://vk.com/dev/messages.startCall
func (vk *VK) MessagesStartCall(params Params) (response MessagesStartCallResponse, err error) {
err = vk.RequestUnmarshal("messages.startCall", &response, params)
return
}
// MessagesUnpin messages.unpin.
//
// https://vk.com/dev/messages.unpin

View File

@@ -1,4 +1,4 @@
package api // import "github.com/SevereCloud/vksdk/api"
package api // import "github.com/SevereCloud/vksdk/v2/api"
import (
"github.com/SevereCloud/vksdk/v2/object"

View File

@@ -235,6 +235,17 @@ func (vk *VK) VideoGetCommentsExtended(params Params) (response VideoGetComments
return
}
// VideoLiveGetCategoriesResponse struct.
type VideoLiveGetCategoriesResponse []object.VideoLiveCategory
// VideoLiveGetCategories method.
//
// https://vk.com/dev/video.liveGetCategories
func (vk *VK) VideoLiveGetCategories(params Params) (response VideoLiveGetCategoriesResponse, err error) {
err = vk.RequestUnmarshal("video.liveGetCategories", &response, params)
return
}
// VideoRemoveFromAlbum allows you to remove the video from the album.
//
// https://vk.com/dev/video.removeFromAlbum
@@ -336,3 +347,27 @@ func (vk *VK) VideoSearchExtended(params Params) (response VideoSearchExtendedRe
return
}
// VideoStartStreamingResponse struct.
type VideoStartStreamingResponse object.VideoLive
// VideoStartStreaming method.
//
// https://vk.com/dev/video.startStreaming
func (vk *VK) VideoStartStreaming(params Params) (response VideoStartStreamingResponse, err error) {
err = vk.RequestUnmarshal("video.startStreaming", &response, params)
return
}
// VideoStopStreamingResponse struct.
type VideoStopStreamingResponse struct {
UniqueViewers int `json:"unique_viewers"`
}
// VideoStopStreaming method.
//
// https://vk.com/dev/video.stopStreaming
func (vk *VK) VideoStopStreaming(params Params) (response VideoStopStreamingResponse, err error) {
err = vk.RequestUnmarshal("video.stopStreaming", &response, params)
return
}

View File

@@ -7,6 +7,6 @@ package vksdk
// Module constants.
const (
Version = "2.13.0"
Version = "2.15.0"
API = "5.131"
)

View File

@@ -15,3 +15,8 @@ func GroupIDFromContext(ctx context.Context) int {
func EventIDFromContext(ctx context.Context) string {
return ctx.Value(internal.EventIDKey).(string)
}
// VersionFromContext returns the version from context.
func VersionFromContext(ctx context.Context) string {
return ctx.Value(internal.EventVersionKey).(string)
}

View File

@@ -81,6 +81,7 @@ type GroupEvent struct {
Object json.RawMessage `json:"object"`
GroupID int `json:"group_id"`
EventID string `json:"event_id"`
V string `json:"v"`
Secret string `json:"secret"`
}
@@ -158,6 +159,7 @@ func NewFuncList() *FuncList {
func (fl FuncList) Handler(ctx context.Context, e GroupEvent) error { // nolint:gocyclo
ctx = context.WithValue(ctx, internal.GroupIDKey, e.GroupID)
ctx = context.WithValue(ctx, internal.EventIDKey, e.EventID)
ctx = context.WithValue(ctx, internal.EventVersionKey, e.V)
if sliceFunc, ok := fl.special[e.Type]; ok {
for _, f := range sliceFunc {

View File

@@ -28,6 +28,7 @@ const (
CallbackRetryCounterKey
CallbackRetryAfterKey
CallbackRemove
EventVersionKey
)
// ContextClient return *http.Client.

View File

@@ -84,7 +84,6 @@ type AccountAccountCounters struct {
// AccountInfo struct.
type AccountInfo struct {
// Country code.
Country string `json:"country"`

View File

@@ -100,3 +100,12 @@ type AppsScope struct {
Name string `json:"name"` // Scope name
Title string `json:"title"` // Scope title
}
// AppsTestingGroup testing group description.
type AppsTestingGroup struct {
GroupID int `json:"group_id"`
UserIDs []int `json:"user_ids"`
Name string `json:"name"`
Webview string `json:"webview"`
Platforms []string `json:"platforms"`
}

View File

@@ -210,7 +210,7 @@ type GroupsGroup struct {
MainSection int `json:"main_section,omitempty"`
OnlineStatus GroupsOnlineStatus `json:"online_status,omitempty"` // Status of replies in community messages
AgeLimits int `json:"age_limits,omitempty"` // Information whether age limit
BanInfo GroupsGroupBanInfo `json:"ban_info,omitempty"` // User ban info
BanInfo *GroupsGroupBanInfo `json:"ban_info,omitempty"` // User ban info
Addresses GroupsAddressesInfo `json:"addresses,omitempty"` // Info about addresses in Groups
LiveCovers GroupsLiveCovers `json:"live_covers,omitempty"`
CropPhoto UsersCropPhoto `json:"crop_photo,omitempty"`
@@ -963,10 +963,10 @@ type GroupsOnlineStatus struct {
// GroupsOwnerXtrBanInfo struct.
type GroupsOwnerXtrBanInfo struct {
BanInfo GroupsBanInfo `json:"ban_info"`
Group GroupsGroup `json:"group"`
Profile UsersUser `json:"profile"`
Type string `json:"type"`
BanInfo *GroupsBanInfo `json:"ban_info"`
Group GroupsGroup `json:"group"`
Profile UsersUser `json:"profile"`
Type string `json:"type"`
}
// GroupsSubjectItem struct.

View File

@@ -409,7 +409,6 @@ type MessageContentSource struct {
Type string `json:"type"`
MessageContentSourceMessage // type message
MessageContentSourceURL // type url
}
// NewMessageContentSourceMessage ...

View File

@@ -24,110 +24,113 @@ const (
// UsersUser struct.
type UsersUser struct {
ID int `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
FirstNameNom string `json:"first_name_nom"`
FirstNameGen string `json:"first_name_gen"`
FirstNameDat string `json:"first_name_dat"`
FirstNameAcc string `json:"first_name_acc"`
FirstNameIns string `json:"first_name_ins"`
FirstNameAbl string `json:"first_name_abl"`
LastNameNom string `json:"last_name_nom"`
LastNameGen string `json:"last_name_gen"`
LastNameDat string `json:"last_name_dat"`
LastNameAcc string `json:"last_name_acc"`
LastNameIns string `json:"last_name_ins"`
LastNameAbl string `json:"last_name_abl"`
MaidenName string `json:"maiden_name"`
Sex int `json:"sex"`
Nickname string `json:"nickname"`
Domain string `json:"domain"`
ScreenName string `json:"screen_name"`
Bdate string `json:"bdate"`
City BaseObject `json:"city"`
Country BaseObject `json:"country"`
Photo50 string `json:"photo_50"`
Photo100 string `json:"photo_100"`
Photo200 string `json:"photo_200"`
PhotoMax string `json:"photo_max"`
Photo200Orig string `json:"photo_200_orig"`
Photo400Orig string `json:"photo_400_orig"`
PhotoMaxOrig string `json:"photo_max_orig"`
PhotoID string `json:"photo_id"`
FriendStatus int `json:"friend_status"` // see FriendStatus const
OnlineApp int `json:"online_app"`
Online BaseBoolInt `json:"online"`
OnlineMobile BaseBoolInt `json:"online_mobile"`
HasPhoto BaseBoolInt `json:"has_photo"`
HasMobile BaseBoolInt `json:"has_mobile"`
IsClosed BaseBoolInt `json:"is_closed"`
IsFriend BaseBoolInt `json:"is_friend"`
IsFavorite BaseBoolInt `json:"is_favorite"`
IsHiddenFromFeed BaseBoolInt `json:"is_hidden_from_feed"`
CanAccessClosed BaseBoolInt `json:"can_access_closed"`
CanBeInvitedGroup BaseBoolInt `json:"can_be_invited_group"`
CanPost BaseBoolInt `json:"can_post"`
CanSeeAllPosts BaseBoolInt `json:"can_see_all_posts"`
CanSeeAudio BaseBoolInt `json:"can_see_audio"`
CanWritePrivateMessage BaseBoolInt `json:"can_write_private_message"`
CanSendFriendRequest BaseBoolInt `json:"can_send_friend_request"`
CanCallFromGroup BaseBoolInt `json:"can_call_from_group"`
Verified BaseBoolInt `json:"verified"`
Trending BaseBoolInt `json:"trending"`
Blacklisted BaseBoolInt `json:"blacklisted"`
BlacklistedByMe BaseBoolInt `json:"blacklisted_by_me"`
Facebook string `json:"facebook"`
FacebookName string `json:"facebook_name"`
Twitter string `json:"twitter"`
Instagram string `json:"instagram"`
Site string `json:"site"`
Status string `json:"status"`
StatusAudio AudioAudio `json:"status_audio"`
LastSeen UsersLastSeen `json:"last_seen"`
CropPhoto UsersCropPhoto `json:"crop_photo"`
FollowersCount int `json:"followers_count"`
CommonCount int `json:"common_count"`
Occupation UsersOccupation `json:"occupation"`
Career []UsersCareer `json:"career"`
Military []UsersMilitary `json:"military"`
University int `json:"university"`
UniversityName string `json:"university_name"`
Faculty int `json:"faculty"`
FacultyName string `json:"faculty_name"`
Graduation int `json:"graduation"`
EducationForm string `json:"education_form"`
EducationStatus string `json:"education_status"`
HomeTown string `json:"home_town"`
Relation int `json:"relation"`
Personal UsersPersonal `json:"personal"`
Interests string `json:"interests"`
Music string `json:"music"`
Activities string `json:"activities"`
Movies string `json:"movies"`
Tv string `json:"tv"`
Books string `json:"books"`
Games string `json:"games"`
Universities []UsersUniversity `json:"universities"`
Schools []UsersSchool `json:"schools"`
About string `json:"about"`
Relatives []UsersRelative `json:"relatives"`
Quotes string `json:"quotes"`
Lists []int `json:"lists"`
Deactivated string `json:"deactivated"`
WallDefault string `json:"wall_default"`
Timezone int `json:"timezone"`
Exports UsersExports `json:"exports"`
Counters UsersUserCounters `json:"counters"`
MobilePhone string `json:"mobile_phone"`
HomePhone string `json:"home_phone"`
FoundWith int `json:"found_with"` // TODO: check it
OnlineInfo UsersOnlineInfo `json:"online_info"`
Mutual FriendsRequestsMutual `json:"mutual"`
TrackCode string `json:"track_code"`
RelationPartner UsersUserMin `json:"relation_partner"`
Type string `json:"type"`
Skype string `json:"skype"`
ID int `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
FirstNameNom string `json:"first_name_nom"`
FirstNameGen string `json:"first_name_gen"`
FirstNameDat string `json:"first_name_dat"`
FirstNameAcc string `json:"first_name_acc"`
FirstNameIns string `json:"first_name_ins"`
FirstNameAbl string `json:"first_name_abl"`
LastNameNom string `json:"last_name_nom"`
LastNameGen string `json:"last_name_gen"`
LastNameDat string `json:"last_name_dat"`
LastNameAcc string `json:"last_name_acc"`
LastNameIns string `json:"last_name_ins"`
LastNameAbl string `json:"last_name_abl"`
MaidenName string `json:"maiden_name"`
Sex int `json:"sex"`
Nickname string `json:"nickname"`
Domain string `json:"domain"`
ScreenName string `json:"screen_name"`
Bdate string `json:"bdate"`
City BaseObject `json:"city"`
Country BaseObject `json:"country"`
Photo50 string `json:"photo_50"`
Photo100 string `json:"photo_100"`
Photo200 string `json:"photo_200"`
PhotoMax string `json:"photo_max"`
Photo200Orig string `json:"photo_200_orig"`
Photo400Orig string `json:"photo_400_orig"`
PhotoMaxOrig string `json:"photo_max_orig"`
PhotoID string `json:"photo_id"`
FriendStatus int `json:"friend_status"` // see FriendStatus const
OnlineApp int `json:"online_app"`
Online BaseBoolInt `json:"online"`
OnlineMobile BaseBoolInt `json:"online_mobile"`
HasPhoto BaseBoolInt `json:"has_photo"`
HasMobile BaseBoolInt `json:"has_mobile"`
IsClosed BaseBoolInt `json:"is_closed"`
IsFriend BaseBoolInt `json:"is_friend"`
IsFavorite BaseBoolInt `json:"is_favorite"`
IsHiddenFromFeed BaseBoolInt `json:"is_hidden_from_feed"`
CanAccessClosed BaseBoolInt `json:"can_access_closed"`
CanBeInvitedGroup BaseBoolInt `json:"can_be_invited_group"`
CanPost BaseBoolInt `json:"can_post"`
CanSeeAllPosts BaseBoolInt `json:"can_see_all_posts"`
CanSeeAudio BaseBoolInt `json:"can_see_audio"`
CanWritePrivateMessage BaseBoolInt `json:"can_write_private_message"`
CanSendFriendRequest BaseBoolInt `json:"can_send_friend_request"`
CanCallFromGroup BaseBoolInt `json:"can_call_from_group"`
Verified BaseBoolInt `json:"verified"`
Trending BaseBoolInt `json:"trending"`
Blacklisted BaseBoolInt `json:"blacklisted"`
BlacklistedByMe BaseBoolInt `json:"blacklisted_by_me"`
// Deprecated: Facebook и Instagram запрещены в России, Meta признана экстремистской организацией...
Facebook string `json:"facebook"`
// Deprecated: Facebook и Instagram запрещены в России, Meta признана экстремистской организацией...
FacebookName string `json:"facebook_name"`
// Deprecated: Facebook и Instagram запрещены в России, Meta признана экстремистской организацией...
Instagram string `json:"instagram"`
Twitter string `json:"twitter"`
Site string `json:"site"`
Status string `json:"status"`
StatusAudio AudioAudio `json:"status_audio"`
LastSeen UsersLastSeen `json:"last_seen"`
CropPhoto UsersCropPhoto `json:"crop_photo"`
FollowersCount int `json:"followers_count"`
CommonCount int `json:"common_count"`
Occupation UsersOccupation `json:"occupation"`
Career []UsersCareer `json:"career"`
Military []UsersMilitary `json:"military"`
University int `json:"university"`
UniversityName string `json:"university_name"`
Faculty int `json:"faculty"`
FacultyName string `json:"faculty_name"`
Graduation int `json:"graduation"`
EducationForm string `json:"education_form"`
EducationStatus string `json:"education_status"`
HomeTown string `json:"home_town"`
Relation int `json:"relation"`
Personal UsersPersonal `json:"personal"`
Interests string `json:"interests"`
Music string `json:"music"`
Activities string `json:"activities"`
Movies string `json:"movies"`
Tv string `json:"tv"`
Books string `json:"books"`
Games string `json:"games"`
Universities []UsersUniversity `json:"universities"`
Schools []UsersSchool `json:"schools"`
About string `json:"about"`
Relatives []UsersRelative `json:"relatives"`
Quotes string `json:"quotes"`
Lists []int `json:"lists"`
Deactivated string `json:"deactivated"`
WallDefault string `json:"wall_default"`
Timezone int `json:"timezone"`
Exports UsersExports `json:"exports"`
Counters UsersUserCounters `json:"counters"`
MobilePhone string `json:"mobile_phone"`
HomePhone string `json:"home_phone"`
FoundWith int `json:"found_with"` // TODO: check it
OnlineInfo UsersOnlineInfo `json:"online_info"`
Mutual FriendsRequestsMutual `json:"mutual"`
TrackCode string `json:"track_code"`
RelationPartner UsersUserMin `json:"relation_partner"`
Type string `json:"type"`
Skype string `json:"skype"`
}
// ToMention return mention.

View File

@@ -31,7 +31,7 @@ type VideoVideo struct {
CanLike BaseBoolInt `json:"can_like"`
// Information whether current user can download the video.
CanDownload BaseBoolInt `json:"can_download"`
CanDownload int `json:"can_download"`
// Information whether current user can repost this video.
CanRepost BaseBoolInt `json:"can_repost"`
@@ -297,3 +297,27 @@ type VideoVideoImage struct {
BaseImage
WithPadding BaseBoolInt `json:"with_padding"`
}
// VideoLive struct.
type VideoLive struct {
OwnerID int `json:"owner_id"`
VideoID int `json:"video_id"`
Name string `json:"name"`
Description string `json:"description"`
AccessKey string `json:"access_key"`
Stream VideoLiveStream `json:"stream"`
}
// VideoLiveStream struct.
type VideoLiveStream struct {
URL string `json:"url"`
Key string `json:"key"`
OKMPURL string `json:"okmp_url"`
}
// VideoLiveCategory struct.
type VideoLiveCategory struct {
ID int `json:"id"`
Label string `json:"label"`
Sublist []VideoLiveCategory `json:"sublist,omitempty"`
}

5
vendor/github.com/bwmarrin/discordgo/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,5 @@
# IDE-specific metadata
.idea/
# Environment variables. Useful for examples.
.env

19
vendor/github.com/bwmarrin/discordgo/.golangci.yml generated vendored Normal file
View File

@@ -0,0 +1,19 @@
linters:
disable-all: true
enable:
# - staticcheck
# - unused
- golint
linters-settings:
staticcheck:
go: "1.13"
checks: ["all"]
unused:
go: "1.13"
issues:
include:
- EXC0002

View File

@@ -3,6 +3,9 @@ go:
- 1.13.x
- 1.14.x
- 1.15.x
- 1.16.x
- 1.17.x
- 1.18.x
env:
- GO111MODULE=on
install:

87
vendor/github.com/bwmarrin/discordgo/CONTRIBUTING.md generated vendored Normal file
View File

@@ -0,0 +1,87 @@
# Getting started
To start off you can check out existing Pull Requests and Issues to get a gasp of what problems were currently solving and what features you can implement.
## Issues
Our issues are mostly used for bugs, however we welcome refactoring and conceptual issues.
Any other conversation would belong and would be moved into “Discussions”.
## Discussions
We use discussions for ideas, polls, announcements and help questions.
Dont hesitate to ask, we always would try to help.
## Pull Requests
If you want to help us by improving existing or adding new features, you create whats called a Pull Request (aka PR). It allows us to review your code, suggest changes and merge it.
Here are some tips on how to make a good first PR:
- When creating a PR, please consider a distinctive name and description for it, so the maintainers can understand what your PR changes / adds / removes.
- Its always a good idea to link documentation when implementing a new feature / endpoint
- If youre resolving an issue, dont forget to [link it](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) in the description.
- Enable the checkbox to allow maintainers to edit your PR and make commits in the PR branch when necessary.
- We may ask for changes, usually through suggestions or pull request comments. You can apply suggestions right in the UI. Any other change needs to be done manually.
- Dont forget to mark PR comments resolved when youre done applying the changes.
- Be patient and dont close and reopen your PR when no one responds, sometimes it might be held for a while. There might be a lot of reasons: release preparation, the feature is not significant, maintainers are busy, etc.
When your changes are still incomplete (i.e. in Work In Progress state), you can still create a PR, but consider making it a draft.
To make a draft PR, you can change the type of PR by clicking to a triangle next to the “Create Pull Request” button.
Once youre done, you can mark it as “Ready for review”, and well get right on it.
# Code style
To standardize and make things less messy we have a certain code style, that is persistent throughout the codebase.
## Naming
### REST methods
When naming a REST method, while it might seem counterintuitive, we specify the entity before the action verb (for GET endpoints we dont specify one however). Heres an example:
> Endpoint name: Get Channel Message
>
> Method name: `ChannelMessage`
> Endpoint name: Edit Channel Message
>
> Method name: `ChannelMessageEdit`
### Parameter structures
When making a complex REST endpoint, sometimes you might need to implement a `Param` structure. This structure contains parameters for certain endpoint/set of endpoints.
- If an endpoint/set of endpoints have mostly same parameters, its a good idea to use a single `Param` structure for them. Heres an example:
> Endpoint: `GuildMemberEdit`
>
> `Param` structure: `GuildMemberParams`
- If an endpoint/set of endpoints have differentiating parameters, `Param` structure can be named after the endpoints verb. Heres an example:
> Endpoint: `ChannelMessageSendComplex`
>
> `Param` structure: `MessageSend`
> Endpoint: `ChannelMessageEditComplex`
>
> `Param` structure: `MessageEdit`
### Events
When naming an event, we follow gateways internal naming (which often matches with the official event name in the docs). Heres an example:
> Event name: Interaction Create (`INTERACTION_CREATE`)
>
> Structure name: `InteractionCreate`
## Returns
In our REST functions we usually favor named returns instead of regular anonymous returns. This helps readability.
Additionally we try to avoid naked return statements for functions with a long body. Since its easier to loose track of the return result.

View File

@@ -1,8 +1,8 @@
# DiscordGo
[![GoDoc](https://godoc.org/github.com/bwmarrin/discordgo?status.svg)](https://godoc.org/github.com/bwmarrin/discordgo) [![Go report](http://goreportcard.com/badge/bwmarrin/discordgo)](http://goreportcard.com/report/bwmarrin/discordgo) [![Build Status](https://travis-ci.org/bwmarrin/discordgo.svg?branch=master)](https://travis-ci.org/bwmarrin/discordgo) [![Discord Gophers](https://img.shields.io/badge/Discord%20Gophers-%23discordgo-blue.svg)](https://discord.gg/0f1SbxBZjYoCtNPP) [![Discord API](https://img.shields.io/badge/Discord%20API-%23go_discordgo-blue.svg)](https://discord.com/invite/discord-api)
[![Go Reference](https://pkg.go.dev/badge/github.com/bwmarrin/discordgo.svg)](https://pkg.go.dev/github.com/bwmarrin/discordgo) [![Go Report Card](https://goreportcard.com/badge/github.com/bwmarrin/discordgo)](https://goreportcard.com/report/github.com/bwmarrin/discordgo) [![CI](https://github.com/bwmarrin/discordgo/actions/workflows/ci.yml/badge.svg)](https://github.com/bwmarrin/discordgo/actions/workflows/ci.yml) [![Discord Gophers](https://img.shields.io/badge/Discord%20Gophers-%23discordgo-blue.svg)](https://discord.gg/golang) [![Discord API](https://img.shields.io/badge/Discord%20API-%23go_discordgo-blue.svg)](https://discord.com/invite/discord-api)
<img align="right" src="http://bwmarrin.github.io/discordgo/img/discordgo.png">
<img align="right" alt="DiscordGo logo" src="docs/img/discordgo.svg" width="400">
DiscordGo is a [Go](https://golang.org/) package that provides low level
bindings to the [Discord](https://discord.com/) chat client API. DiscordGo
@@ -22,7 +22,7 @@ tool that wraps `ffmpeg` to create opus encoded audio appropriate for use with
Discord (and DiscordGo).
**For help with this package or general Go discussion, please join the [Discord
Gophers](https://discord.gg/0f1SbxBZjYq9jLBk) chat server.**
Gophers](https://discord.gg/golang) chat server.**
## Getting Started
@@ -61,11 +61,9 @@ See Documentation and Examples below for more detailed information.
Because of that there may be major changes to library in the future.
The DiscordGo code is fairly well documented at this point and is currently
the only documentation available. Both GoDoc and GoWalker (below) present
that information in a nice format.
the only documentation available. Go reference (below) presents that information in a nice format.
- [![GoDoc](https://godoc.org/github.com/bwmarrin/discordgo?status.svg)](https://godoc.org/github.com/bwmarrin/discordgo)
- [![Go Walker](http://gowalker.org/api/v1/badge)](https://gowalker.org/github.com/bwmarrin/discordgo)
- [![Go Reference](https://pkg.go.dev/badge/github.com/bwmarrin/discordgo.svg)](https://pkg.go.dev/github.com/bwmarrin/discordgo)
- Hand crafted documentation coming eventually.

267
vendor/github.com/bwmarrin/discordgo/components.go generated vendored Normal file
View File

@@ -0,0 +1,267 @@
package discordgo
import (
"encoding/json"
"fmt"
)
// ComponentType is type of component.
type ComponentType uint
// MessageComponent types.
const (
ActionsRowComponent ComponentType = 1
ButtonComponent ComponentType = 2
SelectMenuComponent ComponentType = 3
TextInputComponent ComponentType = 4
UserSelectMenuComponent ComponentType = 5
RoleSelectMenuComponent ComponentType = 6
MentionableSelectMenuComponent ComponentType = 7
ChannelSelectMenuComponent ComponentType = 8
)
// MessageComponent is a base interface for all message components.
type MessageComponent interface {
json.Marshaler
Type() ComponentType
}
type unmarshalableMessageComponent struct {
MessageComponent
}
// UnmarshalJSON is a helper function to unmarshal MessageComponent object.
func (umc *unmarshalableMessageComponent) UnmarshalJSON(src []byte) error {
var v struct {
Type ComponentType `json:"type"`
}
err := json.Unmarshal(src, &v)
if err != nil {
return err
}
switch v.Type {
case ActionsRowComponent:
umc.MessageComponent = &ActionsRow{}
case ButtonComponent:
umc.MessageComponent = &Button{}
case SelectMenuComponent, ChannelSelectMenuComponent, UserSelectMenuComponent,
RoleSelectMenuComponent, MentionableSelectMenuComponent:
umc.MessageComponent = &SelectMenu{}
case TextInputComponent:
umc.MessageComponent = &TextInput{}
default:
return fmt.Errorf("unknown component type: %d", v.Type)
}
return json.Unmarshal(src, umc.MessageComponent)
}
// MessageComponentFromJSON is a helper function for unmarshaling message components
func MessageComponentFromJSON(b []byte) (MessageComponent, error) {
var u unmarshalableMessageComponent
err := u.UnmarshalJSON(b)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal into MessageComponent: %w", err)
}
return u.MessageComponent, nil
}
// ActionsRow is a container for components within one row.
type ActionsRow struct {
Components []MessageComponent `json:"components"`
}
// MarshalJSON is a method for marshaling ActionsRow to a JSON object.
func (r ActionsRow) MarshalJSON() ([]byte, error) {
type actionsRow ActionsRow
return Marshal(struct {
actionsRow
Type ComponentType `json:"type"`
}{
actionsRow: actionsRow(r),
Type: r.Type(),
})
}
// UnmarshalJSON is a helper function to unmarshal Actions Row.
func (r *ActionsRow) UnmarshalJSON(data []byte) error {
var v struct {
RawComponents []unmarshalableMessageComponent `json:"components"`
}
err := json.Unmarshal(data, &v)
if err != nil {
return err
}
r.Components = make([]MessageComponent, len(v.RawComponents))
for i, v := range v.RawComponents {
r.Components[i] = v.MessageComponent
}
return err
}
// Type is a method to get the type of a component.
func (r ActionsRow) Type() ComponentType {
return ActionsRowComponent
}
// ButtonStyle is style of button.
type ButtonStyle uint
// Button styles.
const (
// PrimaryButton is a button with blurple color.
PrimaryButton ButtonStyle = 1
// SecondaryButton is a button with grey color.
SecondaryButton ButtonStyle = 2
// SuccessButton is a button with green color.
SuccessButton ButtonStyle = 3
// DangerButton is a button with red color.
DangerButton ButtonStyle = 4
// LinkButton is a special type of button which navigates to a URL. Has grey color.
LinkButton ButtonStyle = 5
)
// ComponentEmoji represents button emoji, if it does have one.
type ComponentEmoji struct {
Name string `json:"name,omitempty"`
ID string `json:"id,omitempty"`
Animated bool `json:"animated,omitempty"`
}
// Button represents button component.
type Button struct {
Label string `json:"label"`
Style ButtonStyle `json:"style"`
Disabled bool `json:"disabled"`
Emoji ComponentEmoji `json:"emoji"`
// NOTE: Only button with LinkButton style can have link. Also, URL is mutually exclusive with CustomID.
URL string `json:"url,omitempty"`
CustomID string `json:"custom_id,omitempty"`
}
// MarshalJSON is a method for marshaling Button to a JSON object.
func (b Button) MarshalJSON() ([]byte, error) {
type button Button
if b.Style == 0 {
b.Style = PrimaryButton
}
return Marshal(struct {
button
Type ComponentType `json:"type"`
}{
button: button(b),
Type: b.Type(),
})
}
// Type is a method to get the type of a component.
func (Button) Type() ComponentType {
return ButtonComponent
}
// SelectMenuOption represents an option for a select menu.
type SelectMenuOption struct {
Label string `json:"label,omitempty"`
Value string `json:"value"`
Description string `json:"description"`
Emoji ComponentEmoji `json:"emoji"`
// Determines whenever option is selected by default or not.
Default bool `json:"default"`
}
// SelectMenuType represents select menu type.
type SelectMenuType ComponentType
// SelectMenu types.
const (
StringSelectMenu = SelectMenuType(SelectMenuComponent)
UserSelectMenu = SelectMenuType(UserSelectMenuComponent)
RoleSelectMenu = SelectMenuType(RoleSelectMenuComponent)
MentionableSelectMenu = SelectMenuType(MentionableSelectMenuComponent)
ChannelSelectMenu = SelectMenuType(ChannelSelectMenuComponent)
)
// SelectMenu represents select menu component.
type SelectMenu struct {
// Type of the select menu.
MenuType SelectMenuType `json:"type,omitempty"`
// CustomID is a developer-defined identifier for the select menu.
CustomID string `json:"custom_id,omitempty"`
// The text which will be shown in the menu if there's no default options or all options was deselected and component was closed.
Placeholder string `json:"placeholder"`
// This value determines the minimal amount of selected items in the menu.
MinValues *int `json:"min_values,omitempty"`
// This value determines the maximal amount of selected items in the menu.
// If MaxValues or MinValues are greater than one then the user can select multiple items in the component.
MaxValues int `json:"max_values,omitempty"`
Options []SelectMenuOption `json:"options,omitempty"`
Disabled bool `json:"disabled"`
// NOTE: Can only be used in SelectMenu with Channel menu type.
ChannelTypes []ChannelType `json:"channel_types,omitempty"`
}
// Type is a method to get the type of a component.
func (s SelectMenu) Type() ComponentType {
if s.MenuType != 0 {
return ComponentType(s.MenuType)
}
return SelectMenuComponent
}
// MarshalJSON is a method for marshaling SelectMenu to a JSON object.
func (s SelectMenu) MarshalJSON() ([]byte, error) {
type selectMenu SelectMenu
return Marshal(struct {
selectMenu
Type ComponentType `json:"type"`
}{
selectMenu: selectMenu(s),
Type: s.Type(),
})
}
// TextInput represents text input component.
type TextInput struct {
CustomID string `json:"custom_id"`
Label string `json:"label"`
Style TextInputStyle `json:"style"`
Placeholder string `json:"placeholder,omitempty"`
Value string `json:"value,omitempty"`
Required bool `json:"required"`
MinLength int `json:"min_length,omitempty"`
MaxLength int `json:"max_length,omitempty"`
}
// Type is a method to get the type of a component.
func (TextInput) Type() ComponentType {
return TextInputComponent
}
// MarshalJSON is a method for marshaling TextInput to a JSON object.
func (m TextInput) MarshalJSON() ([]byte, error) {
type inputText TextInput
return Marshal(struct {
inputText
Type ComponentType `json:"type"`
}{
inputText: inputText(m),
Type: m.Type(),
})
}
// TextInputStyle is style of text in TextInput component.
type TextInputStyle uint
// Text styles
const (
TextInputShort TextInputStyle = 1
TextInputParagraph TextInputStyle = 2
)

63
vendor/github.com/bwmarrin/discordgo/discord.go generated vendored Normal file
View File

@@ -0,0 +1,63 @@
// Discordgo - Discord bindings for Go
// Available at https://github.com/bwmarrin/discordgo
// Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains high level helper functions and easy entry points for the
// entire discordgo package. These functions are being developed and are very
// experimental at this point. They will most likely change so please use the
// low level functions if that's a problem.
// Package discordgo provides Discord binding for Go
package discordgo
import (
"net/http"
"runtime"
"time"
"github.com/gorilla/websocket"
)
// VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/)
const VERSION = "0.27.0"
// New creates a new Discord session with provided token.
// If the token is for a bot, it must be prefixed with "Bot "
// e.g. "Bot ..."
// Or if it is an OAuth2 token, it must be prefixed with "Bearer "
// e.g. "Bearer ..."
func New(token string) (s *Session, err error) {
// Create an empty Session interface.
s = &Session{
State: NewState(),
Ratelimiter: NewRatelimiter(),
StateEnabled: true,
Compress: true,
ShouldReconnectOnError: true,
ShouldRetryOnRateLimit: true,
ShardID: 0,
ShardCount: 1,
MaxRestRetries: 3,
Client: &http.Client{Timeout: (20 * time.Second)},
Dialer: websocket.DefaultDialer,
UserAgent: "DiscordBot (https://github.com/bwmarrin/discordgo, v" + VERSION + ")",
sequence: new(int64),
LastHeartbeatAck: time.Now().UTC(),
}
// Initialize the Identify Package with defaults
// These can be modified prior to calling Open()
s.Identify.Compress = true
s.Identify.LargeThreshold = 250
s.Identify.Properties.OS = runtime.GOOS
s.Identify.Properties.Browser = "DiscordGo v" + VERSION
s.Identify.Intents = IntentsAllWithoutPrivileged
s.Identify.Token = token
s.Token = token
return
}

218
vendor/github.com/bwmarrin/discordgo/endpoints.go generated vendored Normal file
View File

@@ -0,0 +1,218 @@
// Discordgo - Discord bindings for Go
// Available at https://github.com/bwmarrin/discordgo
// Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains variables for all known Discord end points. All functions
// throughout the Discordgo package use these variables for all connections
// to Discord. These are all exported and you may modify them if needed.
package discordgo
import "strconv"
// APIVersion is the Discord API version used for the REST and Websocket API.
var APIVersion = "9"
// Known Discord API Endpoints.
var (
EndpointStatus = "https://status.discord.com/api/v2/"
EndpointSm = EndpointStatus + "scheduled-maintenances/"
EndpointSmActive = EndpointSm + "active.json"
EndpointSmUpcoming = EndpointSm + "upcoming.json"
EndpointDiscord = "https://discord.com/"
EndpointAPI = EndpointDiscord + "api/v" + APIVersion + "/"
EndpointGuilds = EndpointAPI + "guilds/"
EndpointChannels = EndpointAPI + "channels/"
EndpointUsers = EndpointAPI + "users/"
EndpointGateway = EndpointAPI + "gateway"
EndpointGatewayBot = EndpointGateway + "/bot"
EndpointWebhooks = EndpointAPI + "webhooks/"
EndpointStickers = EndpointAPI + "stickers/"
EndpointStageInstances = EndpointAPI + "stage-instances"
EndpointCDN = "https://cdn.discordapp.com/"
EndpointCDNAttachments = EndpointCDN + "attachments/"
EndpointCDNAvatars = EndpointCDN + "avatars/"
EndpointCDNIcons = EndpointCDN + "icons/"
EndpointCDNSplashes = EndpointCDN + "splashes/"
EndpointCDNChannelIcons = EndpointCDN + "channel-icons/"
EndpointCDNBanners = EndpointCDN + "banners/"
EndpointCDNGuilds = EndpointCDN + "guilds/"
EndpointVoice = EndpointAPI + "/voice/"
EndpointVoiceRegions = EndpointVoice + "regions"
EndpointUser = func(uID string) string { return EndpointUsers + uID }
EndpointUserAvatar = func(uID, aID string) string { return EndpointCDNAvatars + uID + "/" + aID + ".png" }
EndpointUserAvatarAnimated = func(uID, aID string) string { return EndpointCDNAvatars + uID + "/" + aID + ".gif" }
EndpointDefaultUserAvatar = func(uDiscriminator string) string {
uDiscriminatorInt, _ := strconv.Atoi(uDiscriminator)
return EndpointCDN + "embed/avatars/" + strconv.Itoa(uDiscriminatorInt%5) + ".png"
}
EndpointUserBanner = func(uID, cID string) string {
return EndpointCDNBanners + uID + "/" + cID + ".png"
}
EndpointUserBannerAnimated = func(uID, cID string) string {
return EndpointCDNBanners + uID + "/" + cID + ".gif"
}
EndpointUserGuilds = func(uID string) string { return EndpointUsers + uID + "/guilds" }
EndpointUserGuild = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID }
EndpointUserGuildMember = func(uID, gID string) string { return EndpointUserGuild(uID, gID) + "/member" }
EndpointUserChannels = func(uID string) string { return EndpointUsers + uID + "/channels" }
EndpointUserApplicationRoleConnection = func(aID string) string { return EndpointUsers + "@me/applications/" + aID + "/role-connection" }
EndpointUserConnections = func(uID string) string { return EndpointUsers + uID + "/connections" }
EndpointGuild = func(gID string) string { return EndpointGuilds + gID }
EndpointGuildAutoModeration = func(gID string) string { return EndpointGuild(gID) + "/auto-moderation" }
EndpointGuildAutoModerationRules = func(gID string) string { return EndpointGuildAutoModeration(gID) + "/rules" }
EndpointGuildAutoModerationRule = func(gID, rID string) string { return EndpointGuildAutoModerationRules(gID) + "/" + rID }
EndpointGuildThreads = func(gID string) string { return EndpointGuild(gID) + "/threads" }
EndpointGuildActiveThreads = func(gID string) string { return EndpointGuildThreads(gID) + "/active" }
EndpointGuildPreview = func(gID string) string { return EndpointGuilds + gID + "/preview" }
EndpointGuildChannels = func(gID string) string { return EndpointGuilds + gID + "/channels" }
EndpointGuildMembers = func(gID string) string { return EndpointGuilds + gID + "/members" }
EndpointGuildMembersSearch = func(gID string) string { return EndpointGuildMembers(gID) + "/search" }
EndpointGuildMember = func(gID, uID string) string { return EndpointGuilds + gID + "/members/" + uID }
EndpointGuildMemberRole = func(gID, uID, rID string) string { return EndpointGuilds + gID + "/members/" + uID + "/roles/" + rID }
EndpointGuildBans = func(gID string) string { return EndpointGuilds + gID + "/bans" }
EndpointGuildBan = func(gID, uID string) string { return EndpointGuilds + gID + "/bans/" + uID }
EndpointGuildIntegrations = func(gID string) string { return EndpointGuilds + gID + "/integrations" }
EndpointGuildIntegration = func(gID, iID string) string { return EndpointGuilds + gID + "/integrations/" + iID }
EndpointGuildRoles = func(gID string) string { return EndpointGuilds + gID + "/roles" }
EndpointGuildRole = func(gID, rID string) string { return EndpointGuilds + gID + "/roles/" + rID }
EndpointGuildInvites = func(gID string) string { return EndpointGuilds + gID + "/invites" }
EndpointGuildWidget = func(gID string) string { return EndpointGuilds + gID + "/widget" }
EndpointGuildEmbed = EndpointGuildWidget
EndpointGuildPrune = func(gID string) string { return EndpointGuilds + gID + "/prune" }
EndpointGuildIcon = func(gID, hash string) string { return EndpointCDNIcons + gID + "/" + hash + ".png" }
EndpointGuildIconAnimated = func(gID, hash string) string { return EndpointCDNIcons + gID + "/" + hash + ".gif" }
EndpointGuildSplash = func(gID, hash string) string { return EndpointCDNSplashes + gID + "/" + hash + ".png" }
EndpointGuildWebhooks = func(gID string) string { return EndpointGuilds + gID + "/webhooks" }
EndpointGuildAuditLogs = func(gID string) string { return EndpointGuilds + gID + "/audit-logs" }
EndpointGuildEmojis = func(gID string) string { return EndpointGuilds + gID + "/emojis" }
EndpointGuildEmoji = func(gID, eID string) string { return EndpointGuilds + gID + "/emojis/" + eID }
EndpointGuildBanner = func(gID, hash string) string { return EndpointCDNBanners + gID + "/" + hash + ".png" }
EndpointGuildBannerAnimated = func(gID, hash string) string { return EndpointCDNBanners + gID + "/" + hash + ".gif" }
EndpointGuildStickers = func(gID string) string { return EndpointGuilds + gID + "/stickers" }
EndpointGuildSticker = func(gID, sID string) string { return EndpointGuilds + gID + "/stickers/" + sID }
EndpointStageInstance = func(cID string) string { return EndpointStageInstances + "/" + cID }
EndpointGuildScheduledEvents = func(gID string) string { return EndpointGuilds + gID + "/scheduled-events" }
EndpointGuildScheduledEvent = func(gID, eID string) string { return EndpointGuilds + gID + "/scheduled-events/" + eID }
EndpointGuildScheduledEventUsers = func(gID, eID string) string { return EndpointGuildScheduledEvent(gID, eID) + "/users" }
EndpointGuildTemplate = func(tID string) string { return EndpointGuilds + "/templates/" + tID }
EndpointGuildTemplates = func(gID string) string { return EndpointGuilds + gID + "/templates" }
EndpointGuildTemplateSync = func(gID, tID string) string { return EndpointGuilds + gID + "/templates/" + tID }
EndpointGuildMemberAvatar = func(gId, uID, aID string) string {
return EndpointCDNGuilds + gId + "/users/" + uID + "/avatars/" + aID + ".png"
}
EndpointGuildMemberAvatarAnimated = func(gId, uID, aID string) string {
return EndpointCDNGuilds + gId + "/users/" + uID + "/avatars/" + aID + ".gif"
}
EndpointChannel = func(cID string) string { return EndpointChannels + cID }
EndpointChannelThreads = func(cID string) string { return EndpointChannel(cID) + "/threads" }
EndpointChannelActiveThreads = func(cID string) string { return EndpointChannelThreads(cID) + "/active" }
EndpointChannelPublicArchivedThreads = func(cID string) string { return EndpointChannelThreads(cID) + "/archived/public" }
EndpointChannelPrivateArchivedThreads = func(cID string) string { return EndpointChannelThreads(cID) + "/archived/private" }
EndpointChannelJoinedPrivateArchivedThreads = func(cID string) string { return EndpointChannel(cID) + "/users/@me/threads/archived/private" }
EndpointChannelPermissions = func(cID string) string { return EndpointChannels + cID + "/permissions" }
EndpointChannelPermission = func(cID, tID string) string { return EndpointChannels + cID + "/permissions/" + tID }
EndpointChannelInvites = func(cID string) string { return EndpointChannels + cID + "/invites" }
EndpointChannelTyping = func(cID string) string { return EndpointChannels + cID + "/typing" }
EndpointChannelMessages = func(cID string) string { return EndpointChannels + cID + "/messages" }
EndpointChannelMessage = func(cID, mID string) string { return EndpointChannels + cID + "/messages/" + mID }
EndpointChannelMessageThread = func(cID, mID string) string { return EndpointChannelMessage(cID, mID) + "/threads" }
EndpointChannelMessagesBulkDelete = func(cID string) string { return EndpointChannel(cID) + "/messages/bulk-delete" }
EndpointChannelMessagesPins = func(cID string) string { return EndpointChannel(cID) + "/pins" }
EndpointChannelMessagePin = func(cID, mID string) string { return EndpointChannel(cID) + "/pins/" + mID }
EndpointChannelMessageCrosspost = func(cID, mID string) string { return EndpointChannel(cID) + "/messages/" + mID + "/crosspost" }
EndpointChannelFollow = func(cID string) string { return EndpointChannel(cID) + "/followers" }
EndpointThreadMembers = func(tID string) string { return EndpointChannel(tID) + "/thread-members" }
EndpointThreadMember = func(tID, mID string) string { return EndpointThreadMembers(tID) + "/" + mID }
EndpointGroupIcon = func(cID, hash string) string { return EndpointCDNChannelIcons + cID + "/" + hash + ".png" }
EndpointSticker = func(sID string) string { return EndpointStickers + sID }
EndpointNitroStickersPacks = EndpointAPI + "/sticker-packs"
EndpointChannelWebhooks = func(cID string) string { return EndpointChannel(cID) + "/webhooks" }
EndpointWebhook = func(wID string) string { return EndpointWebhooks + wID }
EndpointWebhookToken = func(wID, token string) string { return EndpointWebhooks + wID + "/" + token }
EndpointWebhookMessage = func(wID, token, messageID string) string {
return EndpointWebhookToken(wID, token) + "/messages/" + messageID
}
EndpointMessageReactionsAll = func(cID, mID string) string {
return EndpointChannelMessage(cID, mID) + "/reactions"
}
EndpointMessageReactions = func(cID, mID, eID string) string {
return EndpointChannelMessage(cID, mID) + "/reactions/" + eID
}
EndpointMessageReaction = func(cID, mID, eID, uID string) string {
return EndpointMessageReactions(cID, mID, eID) + "/" + uID
}
EndpointApplicationGlobalCommands = func(aID string) string {
return EndpointApplication(aID) + "/commands"
}
EndpointApplicationGlobalCommand = func(aID, cID string) string {
return EndpointApplicationGlobalCommands(aID) + "/" + cID
}
EndpointApplicationGuildCommands = func(aID, gID string) string {
return EndpointApplication(aID) + "/guilds/" + gID + "/commands"
}
EndpointApplicationGuildCommand = func(aID, gID, cID string) string {
return EndpointApplicationGuildCommands(aID, gID) + "/" + cID
}
EndpointApplicationCommandPermissions = func(aID, gID, cID string) string {
return EndpointApplicationGuildCommand(aID, gID, cID) + "/permissions"
}
EndpointApplicationCommandsGuildPermissions = func(aID, gID string) string {
return EndpointApplicationGuildCommands(aID, gID) + "/permissions"
}
EndpointInteraction = func(aID, iToken string) string {
return EndpointAPI + "interactions/" + aID + "/" + iToken
}
EndpointInteractionResponse = func(iID, iToken string) string {
return EndpointInteraction(iID, iToken) + "/callback"
}
EndpointInteractionResponseActions = func(aID, iToken string) string {
return EndpointWebhookMessage(aID, iToken, "@original")
}
EndpointFollowupMessage = func(aID, iToken string) string {
return EndpointWebhookToken(aID, iToken)
}
EndpointFollowupMessageActions = func(aID, iToken, mID string) string {
return EndpointWebhookMessage(aID, iToken, mID)
}
EndpointGuildCreate = EndpointAPI + "guilds"
EndpointInvite = func(iID string) string { return EndpointAPI + "invites/" + iID }
EndpointEmoji = func(eID string) string { return EndpointCDN + "emojis/" + eID + ".png" }
EndpointEmojiAnimated = func(eID string) string { return EndpointCDN + "emojis/" + eID + ".gif" }
EndpointApplications = EndpointAPI + "applications"
EndpointApplication = func(aID string) string { return EndpointApplications + "/" + aID }
EndpointApplicationRoleConnectionMetadata = func(aID string) string { return EndpointApplication(aID) + "/role-connections/metadata" }
EndpointOAuth2 = EndpointAPI + "oauth2/"
EndpointOAuth2Applications = EndpointOAuth2 + "applications"
EndpointOAuth2Application = func(aID string) string { return EndpointOAuth2Applications + "/" + aID }
EndpointOAuth2ApplicationsBot = func(aID string) string { return EndpointOAuth2Applications + "/" + aID + "/bot" }
EndpointOAuth2ApplicationAssets = func(aID string) string { return EndpointOAuth2Applications + "/" + aID + "/assets" }
// TODO: Deprecated, remove in the next release
EndpointOauth2 = EndpointOAuth2
EndpointOauth2Applications = EndpointOAuth2Applications
EndpointOauth2Application = EndpointOAuth2Application
EndpointOauth2ApplicationsBot = EndpointOAuth2ApplicationsBot
EndpointOauth2ApplicationAssets = EndpointOAuth2ApplicationAssets
)

View File

@@ -157,7 +157,7 @@ func (s *Session) removeEventHandlerInstance(t string, ehi *eventHandlerInstance
onceHandlers := s.onceHandlers[t]
for i := range onceHandlers {
if onceHandlers[i] == ehi {
s.onceHandlers[t] = append(onceHandlers[:i], handlers[i+1:]...)
s.onceHandlers[t] = append(onceHandlers[:i], onceHandlers[i+1:]...)
}
}
}

View File

@@ -7,52 +7,168 @@ package discordgo
// Event type values are used to match the events returned by Discord.
// EventTypes surrounded by __ are synthetic and are internal to DiscordGo.
const (
channelCreateEventType = "CHANNEL_CREATE"
channelDeleteEventType = "CHANNEL_DELETE"
channelPinsUpdateEventType = "CHANNEL_PINS_UPDATE"
channelUpdateEventType = "CHANNEL_UPDATE"
connectEventType = "__CONNECT__"
disconnectEventType = "__DISCONNECT__"
eventEventType = "__EVENT__"
guildBanAddEventType = "GUILD_BAN_ADD"
guildBanRemoveEventType = "GUILD_BAN_REMOVE"
guildCreateEventType = "GUILD_CREATE"
guildDeleteEventType = "GUILD_DELETE"
guildEmojisUpdateEventType = "GUILD_EMOJIS_UPDATE"
guildIntegrationsUpdateEventType = "GUILD_INTEGRATIONS_UPDATE"
guildMemberAddEventType = "GUILD_MEMBER_ADD"
guildMemberRemoveEventType = "GUILD_MEMBER_REMOVE"
guildMemberUpdateEventType = "GUILD_MEMBER_UPDATE"
guildMembersChunkEventType = "GUILD_MEMBERS_CHUNK"
guildRoleCreateEventType = "GUILD_ROLE_CREATE"
guildRoleDeleteEventType = "GUILD_ROLE_DELETE"
guildRoleUpdateEventType = "GUILD_ROLE_UPDATE"
guildUpdateEventType = "GUILD_UPDATE"
messageAckEventType = "MESSAGE_ACK"
messageCreateEventType = "MESSAGE_CREATE"
messageDeleteEventType = "MESSAGE_DELETE"
messageDeleteBulkEventType = "MESSAGE_DELETE_BULK"
messageReactionAddEventType = "MESSAGE_REACTION_ADD"
messageReactionRemoveEventType = "MESSAGE_REACTION_REMOVE"
messageReactionRemoveAllEventType = "MESSAGE_REACTION_REMOVE_ALL"
messageUpdateEventType = "MESSAGE_UPDATE"
presenceUpdateEventType = "PRESENCE_UPDATE"
presencesReplaceEventType = "PRESENCES_REPLACE"
rateLimitEventType = "__RATE_LIMIT__"
readyEventType = "READY"
relationshipAddEventType = "RELATIONSHIP_ADD"
relationshipRemoveEventType = "RELATIONSHIP_REMOVE"
resumedEventType = "RESUMED"
typingStartEventType = "TYPING_START"
userGuildSettingsUpdateEventType = "USER_GUILD_SETTINGS_UPDATE"
userNoteUpdateEventType = "USER_NOTE_UPDATE"
userSettingsUpdateEventType = "USER_SETTINGS_UPDATE"
userUpdateEventType = "USER_UPDATE"
voiceServerUpdateEventType = "VOICE_SERVER_UPDATE"
voiceStateUpdateEventType = "VOICE_STATE_UPDATE"
webhooksUpdateEventType = "WEBHOOKS_UPDATE"
applicationCommandPermissionsUpdateEventType = "APPLICATION_COMMAND_PERMISSIONS_UPDATE"
autoModerationActionExecutionEventType = "AUTO_MODERATION_ACTION_EXECUTION"
autoModerationRuleCreateEventType = "AUTO_MODERATION_RULE_CREATE"
autoModerationRuleDeleteEventType = "AUTO_MODERATION_RULE_DELETE"
autoModerationRuleUpdateEventType = "AUTO_MODERATION_RULE_UPDATE"
channelCreateEventType = "CHANNEL_CREATE"
channelDeleteEventType = "CHANNEL_DELETE"
channelPinsUpdateEventType = "CHANNEL_PINS_UPDATE"
channelUpdateEventType = "CHANNEL_UPDATE"
connectEventType = "__CONNECT__"
disconnectEventType = "__DISCONNECT__"
eventEventType = "__EVENT__"
guildBanAddEventType = "GUILD_BAN_ADD"
guildBanRemoveEventType = "GUILD_BAN_REMOVE"
guildCreateEventType = "GUILD_CREATE"
guildDeleteEventType = "GUILD_DELETE"
guildEmojisUpdateEventType = "GUILD_EMOJIS_UPDATE"
guildIntegrationsUpdateEventType = "GUILD_INTEGRATIONS_UPDATE"
guildMemberAddEventType = "GUILD_MEMBER_ADD"
guildMemberRemoveEventType = "GUILD_MEMBER_REMOVE"
guildMemberUpdateEventType = "GUILD_MEMBER_UPDATE"
guildMembersChunkEventType = "GUILD_MEMBERS_CHUNK"
guildRoleCreateEventType = "GUILD_ROLE_CREATE"
guildRoleDeleteEventType = "GUILD_ROLE_DELETE"
guildRoleUpdateEventType = "GUILD_ROLE_UPDATE"
guildScheduledEventCreateEventType = "GUILD_SCHEDULED_EVENT_CREATE"
guildScheduledEventDeleteEventType = "GUILD_SCHEDULED_EVENT_DELETE"
guildScheduledEventUpdateEventType = "GUILD_SCHEDULED_EVENT_UPDATE"
guildScheduledEventUserAddEventType = "GUILD_SCHEDULED_EVENT_USER_ADD"
guildScheduledEventUserRemoveEventType = "GUILD_SCHEDULED_EVENT_USER_REMOVE"
guildUpdateEventType = "GUILD_UPDATE"
interactionCreateEventType = "INTERACTION_CREATE"
inviteCreateEventType = "INVITE_CREATE"
inviteDeleteEventType = "INVITE_DELETE"
messageCreateEventType = "MESSAGE_CREATE"
messageDeleteEventType = "MESSAGE_DELETE"
messageDeleteBulkEventType = "MESSAGE_DELETE_BULK"
messageReactionAddEventType = "MESSAGE_REACTION_ADD"
messageReactionRemoveEventType = "MESSAGE_REACTION_REMOVE"
messageReactionRemoveAllEventType = "MESSAGE_REACTION_REMOVE_ALL"
messageUpdateEventType = "MESSAGE_UPDATE"
presenceUpdateEventType = "PRESENCE_UPDATE"
presencesReplaceEventType = "PRESENCES_REPLACE"
rateLimitEventType = "__RATE_LIMIT__"
readyEventType = "READY"
resumedEventType = "RESUMED"
stageInstanceEventCreateEventType = "STAGE_INSTANCE_EVENT_CREATE"
stageInstanceEventDeleteEventType = "STAGE_INSTANCE_EVENT_DELETE"
stageInstanceEventUpdateEventType = "STAGE_INSTANCE_EVENT_UPDATE"
threadCreateEventType = "THREAD_CREATE"
threadDeleteEventType = "THREAD_DELETE"
threadListSyncEventType = "THREAD_LIST_SYNC"
threadMemberUpdateEventType = "THREAD_MEMBER_UPDATE"
threadMembersUpdateEventType = "THREAD_MEMBERS_UPDATE"
threadUpdateEventType = "THREAD_UPDATE"
typingStartEventType = "TYPING_START"
userUpdateEventType = "USER_UPDATE"
voiceServerUpdateEventType = "VOICE_SERVER_UPDATE"
voiceStateUpdateEventType = "VOICE_STATE_UPDATE"
webhooksUpdateEventType = "WEBHOOKS_UPDATE"
)
// applicationCommandPermissionsUpdateEventHandler is an event handler for ApplicationCommandPermissionsUpdate events.
type applicationCommandPermissionsUpdateEventHandler func(*Session, *ApplicationCommandPermissionsUpdate)
// Type returns the event type for ApplicationCommandPermissionsUpdate events.
func (eh applicationCommandPermissionsUpdateEventHandler) Type() string {
return applicationCommandPermissionsUpdateEventType
}
// New returns a new instance of ApplicationCommandPermissionsUpdate.
func (eh applicationCommandPermissionsUpdateEventHandler) New() interface{} {
return &ApplicationCommandPermissionsUpdate{}
}
// Handle is the handler for ApplicationCommandPermissionsUpdate events.
func (eh applicationCommandPermissionsUpdateEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*ApplicationCommandPermissionsUpdate); ok {
eh(s, t)
}
}
// autoModerationActionExecutionEventHandler is an event handler for AutoModerationActionExecution events.
type autoModerationActionExecutionEventHandler func(*Session, *AutoModerationActionExecution)
// Type returns the event type for AutoModerationActionExecution events.
func (eh autoModerationActionExecutionEventHandler) Type() string {
return autoModerationActionExecutionEventType
}
// New returns a new instance of AutoModerationActionExecution.
func (eh autoModerationActionExecutionEventHandler) New() interface{} {
return &AutoModerationActionExecution{}
}
// Handle is the handler for AutoModerationActionExecution events.
func (eh autoModerationActionExecutionEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*AutoModerationActionExecution); ok {
eh(s, t)
}
}
// autoModerationRuleCreateEventHandler is an event handler for AutoModerationRuleCreate events.
type autoModerationRuleCreateEventHandler func(*Session, *AutoModerationRuleCreate)
// Type returns the event type for AutoModerationRuleCreate events.
func (eh autoModerationRuleCreateEventHandler) Type() string {
return autoModerationRuleCreateEventType
}
// New returns a new instance of AutoModerationRuleCreate.
func (eh autoModerationRuleCreateEventHandler) New() interface{} {
return &AutoModerationRuleCreate{}
}
// Handle is the handler for AutoModerationRuleCreate events.
func (eh autoModerationRuleCreateEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*AutoModerationRuleCreate); ok {
eh(s, t)
}
}
// autoModerationRuleDeleteEventHandler is an event handler for AutoModerationRuleDelete events.
type autoModerationRuleDeleteEventHandler func(*Session, *AutoModerationRuleDelete)
// Type returns the event type for AutoModerationRuleDelete events.
func (eh autoModerationRuleDeleteEventHandler) Type() string {
return autoModerationRuleDeleteEventType
}
// New returns a new instance of AutoModerationRuleDelete.
func (eh autoModerationRuleDeleteEventHandler) New() interface{} {
return &AutoModerationRuleDelete{}
}
// Handle is the handler for AutoModerationRuleDelete events.
func (eh autoModerationRuleDeleteEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*AutoModerationRuleDelete); ok {
eh(s, t)
}
}
// autoModerationRuleUpdateEventHandler is an event handler for AutoModerationRuleUpdate events.
type autoModerationRuleUpdateEventHandler func(*Session, *AutoModerationRuleUpdate)
// Type returns the event type for AutoModerationRuleUpdate events.
func (eh autoModerationRuleUpdateEventHandler) Type() string {
return autoModerationRuleUpdateEventType
}
// New returns a new instance of AutoModerationRuleUpdate.
func (eh autoModerationRuleUpdateEventHandler) New() interface{} {
return &AutoModerationRuleUpdate{}
}
// Handle is the handler for AutoModerationRuleUpdate events.
func (eh autoModerationRuleUpdateEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*AutoModerationRuleUpdate); ok {
eh(s, t)
}
}
// channelCreateEventHandler is an event handler for ChannelCreate events.
type channelCreateEventHandler func(*Session, *ChannelCreate)
@@ -438,6 +554,106 @@ func (eh guildRoleUpdateEventHandler) Handle(s *Session, i interface{}) {
}
}
// guildScheduledEventCreateEventHandler is an event handler for GuildScheduledEventCreate events.
type guildScheduledEventCreateEventHandler func(*Session, *GuildScheduledEventCreate)
// Type returns the event type for GuildScheduledEventCreate events.
func (eh guildScheduledEventCreateEventHandler) Type() string {
return guildScheduledEventCreateEventType
}
// New returns a new instance of GuildScheduledEventCreate.
func (eh guildScheduledEventCreateEventHandler) New() interface{} {
return &GuildScheduledEventCreate{}
}
// Handle is the handler for GuildScheduledEventCreate events.
func (eh guildScheduledEventCreateEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*GuildScheduledEventCreate); ok {
eh(s, t)
}
}
// guildScheduledEventDeleteEventHandler is an event handler for GuildScheduledEventDelete events.
type guildScheduledEventDeleteEventHandler func(*Session, *GuildScheduledEventDelete)
// Type returns the event type for GuildScheduledEventDelete events.
func (eh guildScheduledEventDeleteEventHandler) Type() string {
return guildScheduledEventDeleteEventType
}
// New returns a new instance of GuildScheduledEventDelete.
func (eh guildScheduledEventDeleteEventHandler) New() interface{} {
return &GuildScheduledEventDelete{}
}
// Handle is the handler for GuildScheduledEventDelete events.
func (eh guildScheduledEventDeleteEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*GuildScheduledEventDelete); ok {
eh(s, t)
}
}
// guildScheduledEventUpdateEventHandler is an event handler for GuildScheduledEventUpdate events.
type guildScheduledEventUpdateEventHandler func(*Session, *GuildScheduledEventUpdate)
// Type returns the event type for GuildScheduledEventUpdate events.
func (eh guildScheduledEventUpdateEventHandler) Type() string {
return guildScheduledEventUpdateEventType
}
// New returns a new instance of GuildScheduledEventUpdate.
func (eh guildScheduledEventUpdateEventHandler) New() interface{} {
return &GuildScheduledEventUpdate{}
}
// Handle is the handler for GuildScheduledEventUpdate events.
func (eh guildScheduledEventUpdateEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*GuildScheduledEventUpdate); ok {
eh(s, t)
}
}
// guildScheduledEventUserAddEventHandler is an event handler for GuildScheduledEventUserAdd events.
type guildScheduledEventUserAddEventHandler func(*Session, *GuildScheduledEventUserAdd)
// Type returns the event type for GuildScheduledEventUserAdd events.
func (eh guildScheduledEventUserAddEventHandler) Type() string {
return guildScheduledEventUserAddEventType
}
// New returns a new instance of GuildScheduledEventUserAdd.
func (eh guildScheduledEventUserAddEventHandler) New() interface{} {
return &GuildScheduledEventUserAdd{}
}
// Handle is the handler for GuildScheduledEventUserAdd events.
func (eh guildScheduledEventUserAddEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*GuildScheduledEventUserAdd); ok {
eh(s, t)
}
}
// guildScheduledEventUserRemoveEventHandler is an event handler for GuildScheduledEventUserRemove events.
type guildScheduledEventUserRemoveEventHandler func(*Session, *GuildScheduledEventUserRemove)
// Type returns the event type for GuildScheduledEventUserRemove events.
func (eh guildScheduledEventUserRemoveEventHandler) Type() string {
return guildScheduledEventUserRemoveEventType
}
// New returns a new instance of GuildScheduledEventUserRemove.
func (eh guildScheduledEventUserRemoveEventHandler) New() interface{} {
return &GuildScheduledEventUserRemove{}
}
// Handle is the handler for GuildScheduledEventUserRemove events.
func (eh guildScheduledEventUserRemoveEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*GuildScheduledEventUserRemove); ok {
eh(s, t)
}
}
// guildUpdateEventHandler is an event handler for GuildUpdate events.
type guildUpdateEventHandler func(*Session, *GuildUpdate)
@@ -458,22 +674,62 @@ func (eh guildUpdateEventHandler) Handle(s *Session, i interface{}) {
}
}
// messageAckEventHandler is an event handler for MessageAck events.
type messageAckEventHandler func(*Session, *MessageAck)
// interactionCreateEventHandler is an event handler for InteractionCreate events.
type interactionCreateEventHandler func(*Session, *InteractionCreate)
// Type returns the event type for MessageAck events.
func (eh messageAckEventHandler) Type() string {
return messageAckEventType
// Type returns the event type for InteractionCreate events.
func (eh interactionCreateEventHandler) Type() string {
return interactionCreateEventType
}
// New returns a new instance of MessageAck.
func (eh messageAckEventHandler) New() interface{} {
return &MessageAck{}
// New returns a new instance of InteractionCreate.
func (eh interactionCreateEventHandler) New() interface{} {
return &InteractionCreate{}
}
// Handle is the handler for MessageAck events.
func (eh messageAckEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*MessageAck); ok {
// Handle is the handler for InteractionCreate events.
func (eh interactionCreateEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*InteractionCreate); ok {
eh(s, t)
}
}
// inviteCreateEventHandler is an event handler for InviteCreate events.
type inviteCreateEventHandler func(*Session, *InviteCreate)
// Type returns the event type for InviteCreate events.
func (eh inviteCreateEventHandler) Type() string {
return inviteCreateEventType
}
// New returns a new instance of InviteCreate.
func (eh inviteCreateEventHandler) New() interface{} {
return &InviteCreate{}
}
// Handle is the handler for InviteCreate events.
func (eh inviteCreateEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*InviteCreate); ok {
eh(s, t)
}
}
// inviteDeleteEventHandler is an event handler for InviteDelete events.
type inviteDeleteEventHandler func(*Session, *InviteDelete)
// Type returns the event type for InviteDelete events.
func (eh inviteDeleteEventHandler) Type() string {
return inviteDeleteEventType
}
// New returns a new instance of InviteDelete.
func (eh inviteDeleteEventHandler) New() interface{} {
return &InviteDelete{}
}
// Handle is the handler for InviteDelete events.
func (eh inviteDeleteEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*InviteDelete); ok {
eh(s, t)
}
}
@@ -693,46 +949,6 @@ func (eh readyEventHandler) Handle(s *Session, i interface{}) {
}
}
// relationshipAddEventHandler is an event handler for RelationshipAdd events.
type relationshipAddEventHandler func(*Session, *RelationshipAdd)
// Type returns the event type for RelationshipAdd events.
func (eh relationshipAddEventHandler) Type() string {
return relationshipAddEventType
}
// New returns a new instance of RelationshipAdd.
func (eh relationshipAddEventHandler) New() interface{} {
return &RelationshipAdd{}
}
// Handle is the handler for RelationshipAdd events.
func (eh relationshipAddEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*RelationshipAdd); ok {
eh(s, t)
}
}
// relationshipRemoveEventHandler is an event handler for RelationshipRemove events.
type relationshipRemoveEventHandler func(*Session, *RelationshipRemove)
// Type returns the event type for RelationshipRemove events.
func (eh relationshipRemoveEventHandler) Type() string {
return relationshipRemoveEventType
}
// New returns a new instance of RelationshipRemove.
func (eh relationshipRemoveEventHandler) New() interface{} {
return &RelationshipRemove{}
}
// Handle is the handler for RelationshipRemove events.
func (eh relationshipRemoveEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*RelationshipRemove); ok {
eh(s, t)
}
}
// resumedEventHandler is an event handler for Resumed events.
type resumedEventHandler func(*Session, *Resumed)
@@ -753,6 +969,186 @@ func (eh resumedEventHandler) Handle(s *Session, i interface{}) {
}
}
// stageInstanceEventCreateEventHandler is an event handler for StageInstanceEventCreate events.
type stageInstanceEventCreateEventHandler func(*Session, *StageInstanceEventCreate)
// Type returns the event type for StageInstanceEventCreate events.
func (eh stageInstanceEventCreateEventHandler) Type() string {
return stageInstanceEventCreateEventType
}
// New returns a new instance of StageInstanceEventCreate.
func (eh stageInstanceEventCreateEventHandler) New() interface{} {
return &StageInstanceEventCreate{}
}
// Handle is the handler for StageInstanceEventCreate events.
func (eh stageInstanceEventCreateEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*StageInstanceEventCreate); ok {
eh(s, t)
}
}
// stageInstanceEventDeleteEventHandler is an event handler for StageInstanceEventDelete events.
type stageInstanceEventDeleteEventHandler func(*Session, *StageInstanceEventDelete)
// Type returns the event type for StageInstanceEventDelete events.
func (eh stageInstanceEventDeleteEventHandler) Type() string {
return stageInstanceEventDeleteEventType
}
// New returns a new instance of StageInstanceEventDelete.
func (eh stageInstanceEventDeleteEventHandler) New() interface{} {
return &StageInstanceEventDelete{}
}
// Handle is the handler for StageInstanceEventDelete events.
func (eh stageInstanceEventDeleteEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*StageInstanceEventDelete); ok {
eh(s, t)
}
}
// stageInstanceEventUpdateEventHandler is an event handler for StageInstanceEventUpdate events.
type stageInstanceEventUpdateEventHandler func(*Session, *StageInstanceEventUpdate)
// Type returns the event type for StageInstanceEventUpdate events.
func (eh stageInstanceEventUpdateEventHandler) Type() string {
return stageInstanceEventUpdateEventType
}
// New returns a new instance of StageInstanceEventUpdate.
func (eh stageInstanceEventUpdateEventHandler) New() interface{} {
return &StageInstanceEventUpdate{}
}
// Handle is the handler for StageInstanceEventUpdate events.
func (eh stageInstanceEventUpdateEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*StageInstanceEventUpdate); ok {
eh(s, t)
}
}
// threadCreateEventHandler is an event handler for ThreadCreate events.
type threadCreateEventHandler func(*Session, *ThreadCreate)
// Type returns the event type for ThreadCreate events.
func (eh threadCreateEventHandler) Type() string {
return threadCreateEventType
}
// New returns a new instance of ThreadCreate.
func (eh threadCreateEventHandler) New() interface{} {
return &ThreadCreate{}
}
// Handle is the handler for ThreadCreate events.
func (eh threadCreateEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*ThreadCreate); ok {
eh(s, t)
}
}
// threadDeleteEventHandler is an event handler for ThreadDelete events.
type threadDeleteEventHandler func(*Session, *ThreadDelete)
// Type returns the event type for ThreadDelete events.
func (eh threadDeleteEventHandler) Type() string {
return threadDeleteEventType
}
// New returns a new instance of ThreadDelete.
func (eh threadDeleteEventHandler) New() interface{} {
return &ThreadDelete{}
}
// Handle is the handler for ThreadDelete events.
func (eh threadDeleteEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*ThreadDelete); ok {
eh(s, t)
}
}
// threadListSyncEventHandler is an event handler for ThreadListSync events.
type threadListSyncEventHandler func(*Session, *ThreadListSync)
// Type returns the event type for ThreadListSync events.
func (eh threadListSyncEventHandler) Type() string {
return threadListSyncEventType
}
// New returns a new instance of ThreadListSync.
func (eh threadListSyncEventHandler) New() interface{} {
return &ThreadListSync{}
}
// Handle is the handler for ThreadListSync events.
func (eh threadListSyncEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*ThreadListSync); ok {
eh(s, t)
}
}
// threadMemberUpdateEventHandler is an event handler for ThreadMemberUpdate events.
type threadMemberUpdateEventHandler func(*Session, *ThreadMemberUpdate)
// Type returns the event type for ThreadMemberUpdate events.
func (eh threadMemberUpdateEventHandler) Type() string {
return threadMemberUpdateEventType
}
// New returns a new instance of ThreadMemberUpdate.
func (eh threadMemberUpdateEventHandler) New() interface{} {
return &ThreadMemberUpdate{}
}
// Handle is the handler for ThreadMemberUpdate events.
func (eh threadMemberUpdateEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*ThreadMemberUpdate); ok {
eh(s, t)
}
}
// threadMembersUpdateEventHandler is an event handler for ThreadMembersUpdate events.
type threadMembersUpdateEventHandler func(*Session, *ThreadMembersUpdate)
// Type returns the event type for ThreadMembersUpdate events.
func (eh threadMembersUpdateEventHandler) Type() string {
return threadMembersUpdateEventType
}
// New returns a new instance of ThreadMembersUpdate.
func (eh threadMembersUpdateEventHandler) New() interface{} {
return &ThreadMembersUpdate{}
}
// Handle is the handler for ThreadMembersUpdate events.
func (eh threadMembersUpdateEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*ThreadMembersUpdate); ok {
eh(s, t)
}
}
// threadUpdateEventHandler is an event handler for ThreadUpdate events.
type threadUpdateEventHandler func(*Session, *ThreadUpdate)
// Type returns the event type for ThreadUpdate events.
func (eh threadUpdateEventHandler) Type() string {
return threadUpdateEventType
}
// New returns a new instance of ThreadUpdate.
func (eh threadUpdateEventHandler) New() interface{} {
return &ThreadUpdate{}
}
// Handle is the handler for ThreadUpdate events.
func (eh threadUpdateEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*ThreadUpdate); ok {
eh(s, t)
}
}
// typingStartEventHandler is an event handler for TypingStart events.
type typingStartEventHandler func(*Session, *TypingStart)
@@ -773,66 +1169,6 @@ func (eh typingStartEventHandler) Handle(s *Session, i interface{}) {
}
}
// userGuildSettingsUpdateEventHandler is an event handler for UserGuildSettingsUpdate events.
type userGuildSettingsUpdateEventHandler func(*Session, *UserGuildSettingsUpdate)
// Type returns the event type for UserGuildSettingsUpdate events.
func (eh userGuildSettingsUpdateEventHandler) Type() string {
return userGuildSettingsUpdateEventType
}
// New returns a new instance of UserGuildSettingsUpdate.
func (eh userGuildSettingsUpdateEventHandler) New() interface{} {
return &UserGuildSettingsUpdate{}
}
// Handle is the handler for UserGuildSettingsUpdate events.
func (eh userGuildSettingsUpdateEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*UserGuildSettingsUpdate); ok {
eh(s, t)
}
}
// userNoteUpdateEventHandler is an event handler for UserNoteUpdate events.
type userNoteUpdateEventHandler func(*Session, *UserNoteUpdate)
// Type returns the event type for UserNoteUpdate events.
func (eh userNoteUpdateEventHandler) Type() string {
return userNoteUpdateEventType
}
// New returns a new instance of UserNoteUpdate.
func (eh userNoteUpdateEventHandler) New() interface{} {
return &UserNoteUpdate{}
}
// Handle is the handler for UserNoteUpdate events.
func (eh userNoteUpdateEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*UserNoteUpdate); ok {
eh(s, t)
}
}
// userSettingsUpdateEventHandler is an event handler for UserSettingsUpdate events.
type userSettingsUpdateEventHandler func(*Session, *UserSettingsUpdate)
// Type returns the event type for UserSettingsUpdate events.
func (eh userSettingsUpdateEventHandler) Type() string {
return userSettingsUpdateEventType
}
// New returns a new instance of UserSettingsUpdate.
func (eh userSettingsUpdateEventHandler) New() interface{} {
return &UserSettingsUpdate{}
}
// Handle is the handler for UserSettingsUpdate events.
func (eh userSettingsUpdateEventHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*UserSettingsUpdate); ok {
eh(s, t)
}
}
// userUpdateEventHandler is an event handler for UserUpdate events.
type userUpdateEventHandler func(*Session, *UserUpdate)
@@ -917,6 +1253,16 @@ func handlerForInterface(handler interface{}) EventHandler {
switch v := handler.(type) {
case func(*Session, interface{}):
return interfaceEventHandler(v)
case func(*Session, *ApplicationCommandPermissionsUpdate):
return applicationCommandPermissionsUpdateEventHandler(v)
case func(*Session, *AutoModerationActionExecution):
return autoModerationActionExecutionEventHandler(v)
case func(*Session, *AutoModerationRuleCreate):
return autoModerationRuleCreateEventHandler(v)
case func(*Session, *AutoModerationRuleDelete):
return autoModerationRuleDeleteEventHandler(v)
case func(*Session, *AutoModerationRuleUpdate):
return autoModerationRuleUpdateEventHandler(v)
case func(*Session, *ChannelCreate):
return channelCreateEventHandler(v)
case func(*Session, *ChannelDelete):
@@ -957,10 +1303,24 @@ func handlerForInterface(handler interface{}) EventHandler {
return guildRoleDeleteEventHandler(v)
case func(*Session, *GuildRoleUpdate):
return guildRoleUpdateEventHandler(v)
case func(*Session, *GuildScheduledEventCreate):
return guildScheduledEventCreateEventHandler(v)
case func(*Session, *GuildScheduledEventDelete):
return guildScheduledEventDeleteEventHandler(v)
case func(*Session, *GuildScheduledEventUpdate):
return guildScheduledEventUpdateEventHandler(v)
case func(*Session, *GuildScheduledEventUserAdd):
return guildScheduledEventUserAddEventHandler(v)
case func(*Session, *GuildScheduledEventUserRemove):
return guildScheduledEventUserRemoveEventHandler(v)
case func(*Session, *GuildUpdate):
return guildUpdateEventHandler(v)
case func(*Session, *MessageAck):
return messageAckEventHandler(v)
case func(*Session, *InteractionCreate):
return interactionCreateEventHandler(v)
case func(*Session, *InviteCreate):
return inviteCreateEventHandler(v)
case func(*Session, *InviteDelete):
return inviteDeleteEventHandler(v)
case func(*Session, *MessageCreate):
return messageCreateEventHandler(v)
case func(*Session, *MessageDelete):
@@ -983,20 +1343,28 @@ func handlerForInterface(handler interface{}) EventHandler {
return rateLimitEventHandler(v)
case func(*Session, *Ready):
return readyEventHandler(v)
case func(*Session, *RelationshipAdd):
return relationshipAddEventHandler(v)
case func(*Session, *RelationshipRemove):
return relationshipRemoveEventHandler(v)
case func(*Session, *Resumed):
return resumedEventHandler(v)
case func(*Session, *StageInstanceEventCreate):
return stageInstanceEventCreateEventHandler(v)
case func(*Session, *StageInstanceEventDelete):
return stageInstanceEventDeleteEventHandler(v)
case func(*Session, *StageInstanceEventUpdate):
return stageInstanceEventUpdateEventHandler(v)
case func(*Session, *ThreadCreate):
return threadCreateEventHandler(v)
case func(*Session, *ThreadDelete):
return threadDeleteEventHandler(v)
case func(*Session, *ThreadListSync):
return threadListSyncEventHandler(v)
case func(*Session, *ThreadMemberUpdate):
return threadMemberUpdateEventHandler(v)
case func(*Session, *ThreadMembersUpdate):
return threadMembersUpdateEventHandler(v)
case func(*Session, *ThreadUpdate):
return threadUpdateEventHandler(v)
case func(*Session, *TypingStart):
return typingStartEventHandler(v)
case func(*Session, *UserGuildSettingsUpdate):
return userGuildSettingsUpdateEventHandler(v)
case func(*Session, *UserNoteUpdate):
return userNoteUpdateEventHandler(v)
case func(*Session, *UserSettingsUpdate):
return userSettingsUpdateEventHandler(v)
case func(*Session, *UserUpdate):
return userUpdateEventHandler(v)
case func(*Session, *VoiceServerUpdate):
@@ -1011,6 +1379,11 @@ func handlerForInterface(handler interface{}) EventHandler {
}
func init() {
registerInterfaceProvider(applicationCommandPermissionsUpdateEventHandler(nil))
registerInterfaceProvider(autoModerationActionExecutionEventHandler(nil))
registerInterfaceProvider(autoModerationRuleCreateEventHandler(nil))
registerInterfaceProvider(autoModerationRuleDeleteEventHandler(nil))
registerInterfaceProvider(autoModerationRuleUpdateEventHandler(nil))
registerInterfaceProvider(channelCreateEventHandler(nil))
registerInterfaceProvider(channelDeleteEventHandler(nil))
registerInterfaceProvider(channelPinsUpdateEventHandler(nil))
@@ -1028,8 +1401,15 @@ func init() {
registerInterfaceProvider(guildRoleCreateEventHandler(nil))
registerInterfaceProvider(guildRoleDeleteEventHandler(nil))
registerInterfaceProvider(guildRoleUpdateEventHandler(nil))
registerInterfaceProvider(guildScheduledEventCreateEventHandler(nil))
registerInterfaceProvider(guildScheduledEventDeleteEventHandler(nil))
registerInterfaceProvider(guildScheduledEventUpdateEventHandler(nil))
registerInterfaceProvider(guildScheduledEventUserAddEventHandler(nil))
registerInterfaceProvider(guildScheduledEventUserRemoveEventHandler(nil))
registerInterfaceProvider(guildUpdateEventHandler(nil))
registerInterfaceProvider(messageAckEventHandler(nil))
registerInterfaceProvider(interactionCreateEventHandler(nil))
registerInterfaceProvider(inviteCreateEventHandler(nil))
registerInterfaceProvider(inviteDeleteEventHandler(nil))
registerInterfaceProvider(messageCreateEventHandler(nil))
registerInterfaceProvider(messageDeleteEventHandler(nil))
registerInterfaceProvider(messageDeleteBulkEventHandler(nil))
@@ -1040,13 +1420,17 @@ func init() {
registerInterfaceProvider(presenceUpdateEventHandler(nil))
registerInterfaceProvider(presencesReplaceEventHandler(nil))
registerInterfaceProvider(readyEventHandler(nil))
registerInterfaceProvider(relationshipAddEventHandler(nil))
registerInterfaceProvider(relationshipRemoveEventHandler(nil))
registerInterfaceProvider(resumedEventHandler(nil))
registerInterfaceProvider(stageInstanceEventCreateEventHandler(nil))
registerInterfaceProvider(stageInstanceEventDeleteEventHandler(nil))
registerInterfaceProvider(stageInstanceEventUpdateEventHandler(nil))
registerInterfaceProvider(threadCreateEventHandler(nil))
registerInterfaceProvider(threadDeleteEventHandler(nil))
registerInterfaceProvider(threadListSyncEventHandler(nil))
registerInterfaceProvider(threadMemberUpdateEventHandler(nil))
registerInterfaceProvider(threadMembersUpdateEventHandler(nil))
registerInterfaceProvider(threadUpdateEventHandler(nil))
registerInterfaceProvider(typingStartEventHandler(nil))
registerInterfaceProvider(userGuildSettingsUpdateEventHandler(nil))
registerInterfaceProvider(userNoteUpdateEventHandler(nil))
registerInterfaceProvider(userSettingsUpdateEventHandler(nil))
registerInterfaceProvider(userUpdateEventHandler(nil))
registerInterfaceProvider(voiceServerUpdateEventHandler(nil))
registerInterfaceProvider(voiceStateUpdateEventHandler(nil))

View File

@@ -39,16 +39,10 @@ type Ready struct {
Version int `json:"v"`
SessionID string `json:"session_id"`
User *User `json:"user"`
ReadState []*ReadState `json:"read_state"`
PrivateChannels []*Channel `json:"private_channels"`
Shard *[2]int `json:"shard"`
Application *Application `json:"application"`
Guilds []*Guild `json:"guilds"`
// Undocumented fields
Settings *Settings `json:"user_settings"`
UserGuildSettings []*UserGuildSettings `json:"user_guild_settings"`
Relationships []*Relationship `json:"relationships"`
Presences []*Presence `json:"presences"`
Notes map[string]string `json:"notes"`
PrivateChannels []*Channel `json:"private_channels"`
}
// ChannelCreate is the data for a ChannelCreate event.
@@ -73,6 +67,53 @@ type ChannelPinsUpdate struct {
GuildID string `json:"guild_id,omitempty"`
}
// ThreadCreate is the data for a ThreadCreate event.
type ThreadCreate struct {
*Channel
NewlyCreated bool `json:"newly_created"`
}
// ThreadUpdate is the data for a ThreadUpdate event.
type ThreadUpdate struct {
*Channel
BeforeUpdate *Channel `json:"-"`
}
// ThreadDelete is the data for a ThreadDelete event.
type ThreadDelete struct {
*Channel
}
// ThreadListSync is the data for a ThreadListSync event.
type ThreadListSync struct {
// The id of the guild
GuildID string `json:"guild_id"`
// The parent channel ids whose threads are being synced.
// If omitted, then threads were synced for the entire guild.
// This array may contain channel_ids that have no active threads as well, so you know to clear that data.
ChannelIDs []string `json:"channel_ids"`
// All active threads in the given channels that the current user can access
Threads []*Channel `json:"threads"`
// All thread member objects from the synced threads for the current user,
// indicating which threads the current user has been added to
Members []*ThreadMember `json:"members"`
}
// ThreadMemberUpdate is the data for a ThreadMemberUpdate event.
type ThreadMemberUpdate struct {
*ThreadMember
GuildID string `json:"guild_id"`
}
// ThreadMembersUpdate is the data for a ThreadMembersUpdate event.
type ThreadMembersUpdate struct {
ID string `json:"id"`
GuildID string `json:"guild_id"`
MemberCount int `json:"member_count"`
AddedMembers []AddedThreadMember `json:"added_members"`
RemovedMembers []string `json:"removed_member_ids"`
}
// GuildCreate is the data for a GuildCreate event.
type GuildCreate struct {
*Guild
@@ -86,6 +127,7 @@ type GuildUpdate struct {
// GuildDelete is the data for a GuildDelete event.
type GuildDelete struct {
*Guild
BeforeDelete *Guild `json:"-"`
}
// GuildBanAdd is the data for a GuildBanAdd event.
@@ -108,6 +150,7 @@ type GuildMemberAdd struct {
// GuildMemberUpdate is the data for a GuildMemberUpdate event.
type GuildMemberUpdate struct {
*Member
BeforeUpdate *Member `json:"-"`
}
// GuildMemberRemove is the data for a GuildMemberRemove event.
@@ -143,7 +186,9 @@ type GuildMembersChunk struct {
Members []*Member `json:"members"`
ChunkIndex int `json:"chunk_index"`
ChunkCount int `json:"chunk_count"`
NotFound []string `json:"not_found,omitempty"`
Presences []*Presence `json:"presences,omitempty"`
Nonce string `json:"nonce,omitempty"`
}
// GuildIntegrationsUpdate is the data for a GuildIntegrationsUpdate event.
@@ -151,10 +196,48 @@ type GuildIntegrationsUpdate struct {
GuildID string `json:"guild_id"`
}
// MessageAck is the data for a MessageAck event.
type MessageAck struct {
MessageID string `json:"message_id"`
ChannelID string `json:"channel_id"`
// StageInstanceEventCreate is the data for a StageInstanceEventCreate event.
type StageInstanceEventCreate struct {
*StageInstance
}
// StageInstanceEventUpdate is the data for a StageInstanceEventUpdate event.
type StageInstanceEventUpdate struct {
*StageInstance
}
// StageInstanceEventDelete is the data for a StageInstanceEventDelete event.
type StageInstanceEventDelete struct {
*StageInstance
}
// GuildScheduledEventCreate is the data for a GuildScheduledEventCreate event.
type GuildScheduledEventCreate struct {
*GuildScheduledEvent
}
// GuildScheduledEventUpdate is the data for a GuildScheduledEventUpdate event.
type GuildScheduledEventUpdate struct {
*GuildScheduledEvent
}
// GuildScheduledEventDelete is the data for a GuildScheduledEventDelete event.
type GuildScheduledEventDelete struct {
*GuildScheduledEvent
}
// GuildScheduledEventUserAdd is the data for a GuildScheduledEventUserAdd event.
type GuildScheduledEventUserAdd struct {
GuildScheduledEventID string `json:"guild_scheduled_event_id"`
UserID string `json:"user_id"`
GuildID string `json:"guild_id"`
}
// GuildScheduledEventUserRemove is the data for a GuildScheduledEventUserRemove event.
type GuildScheduledEventUserRemove struct {
GuildScheduledEventID string `json:"guild_scheduled_event_id"`
UserID string `json:"user_id"`
GuildID string `json:"guild_id"`
}
// MessageCreate is the data for a MessageCreate event.
@@ -162,6 +245,11 @@ type MessageCreate struct {
*Message
}
// UnmarshalJSON is a helper function to unmarshal MessageCreate object.
func (m *MessageCreate) UnmarshalJSON(b []byte) error {
return json.Unmarshal(b, &m.Message)
}
// MessageUpdate is the data for a MessageUpdate event.
type MessageUpdate struct {
*Message
@@ -169,15 +257,26 @@ type MessageUpdate struct {
BeforeUpdate *Message `json:"-"`
}
// UnmarshalJSON is a helper function to unmarshal MessageUpdate object.
func (m *MessageUpdate) UnmarshalJSON(b []byte) error {
return json.Unmarshal(b, &m.Message)
}
// MessageDelete is the data for a MessageDelete event.
type MessageDelete struct {
*Message
BeforeDelete *Message `json:"-"`
}
// UnmarshalJSON is a helper function to unmarshal MessageDelete object.
func (m *MessageDelete) UnmarshalJSON(b []byte) error {
return json.Unmarshal(b, &m.Message)
}
// MessageReactionAdd is the data for a MessageReactionAdd event.
type MessageReactionAdd struct {
*MessageReaction
Member *Member `json:"member,omitempty"`
}
// MessageReactionRemove is the data for a MessageReactionRemove event.
@@ -204,16 +303,6 @@ type Resumed struct {
Trace []string `json:"_trace"`
}
// RelationshipAdd is the data for a RelationshipAdd event.
type RelationshipAdd struct {
*Relationship
}
// RelationshipRemove is the data for a RelationshipRemove event.
type RelationshipRemove struct {
*Relationship
}
// TypingStart is the data for a TypingStart event.
type TypingStart struct {
UserID string `json:"user_id"`
@@ -227,20 +316,6 @@ type UserUpdate struct {
*User
}
// UserSettingsUpdate is the data for a UserSettingsUpdate event.
type UserSettingsUpdate map[string]interface{}
// UserGuildSettingsUpdate is the data for a UserGuildSettingsUpdate event.
type UserGuildSettingsUpdate struct {
*UserGuildSettings
}
// UserNoteUpdate is the data for a UserNoteUpdate event.
type UserNoteUpdate struct {
ID string `json:"id"`
Note string `json:"note"`
}
// VoiceServerUpdate is the data for a VoiceServerUpdate event.
type VoiceServerUpdate struct {
Token string `json:"token"`
@@ -267,3 +342,62 @@ type WebhooksUpdate struct {
GuildID string `json:"guild_id"`
ChannelID string `json:"channel_id"`
}
// InteractionCreate is the data for a InteractionCreate event
type InteractionCreate struct {
*Interaction
}
// UnmarshalJSON is a helper function to unmarshal Interaction object.
func (i *InteractionCreate) UnmarshalJSON(b []byte) error {
return json.Unmarshal(b, &i.Interaction)
}
// InviteCreate is the data for a InviteCreate event
type InviteCreate struct {
*Invite
ChannelID string `json:"channel_id"`
GuildID string `json:"guild_id"`
}
// InviteDelete is the data for a InviteDelete event
type InviteDelete struct {
ChannelID string `json:"channel_id"`
GuildID string `json:"guild_id"`
Code string `json:"code"`
}
// ApplicationCommandPermissionsUpdate is the data for an ApplicationCommandPermissionsUpdate event
type ApplicationCommandPermissionsUpdate struct {
*GuildApplicationCommandPermissions
}
// AutoModerationRuleCreate is the data for an AutoModerationRuleCreate event.
type AutoModerationRuleCreate struct {
*AutoModerationRule
}
// AutoModerationRuleUpdate is the data for an AutoModerationRuleUpdate event.
type AutoModerationRuleUpdate struct {
*AutoModerationRule
}
// AutoModerationRuleDelete is the data for an AutoModerationRuleDelete event.
type AutoModerationRuleDelete struct {
*AutoModerationRule
}
// AutoModerationActionExecution is the data for an AutoModerationActionExecution event.
type AutoModerationActionExecution struct {
GuildID string `json:"guild_id"`
Action AutoModerationAction `json:"action"`
RuleID string `json:"rule_id"`
RuleTriggerType AutoModerationRuleTriggerType `json:"rule_trigger_type"`
UserID string `json:"user_id"`
ChannelID string `json:"channel_id"`
MessageID string `json:"message_id"`
AlertSystemMessageID string `json:"alert_system_message_id"`
Content string `json:"content"`
MatchedKeyword string `json:"matched_keyword"`
MatchedContent string `json:"matched_content"`
}

611
vendor/github.com/bwmarrin/discordgo/interactions.go generated vendored Normal file
View File

@@ -0,0 +1,611 @@
package discordgo
import (
"bytes"
"crypto/ed25519"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strconv"
"time"
)
// InteractionDeadline is the time allowed to respond to an interaction.
const InteractionDeadline = time.Second * 3
// ApplicationCommandType represents the type of application command.
type ApplicationCommandType uint8
// Application command types
const (
// ChatApplicationCommand is default command type. They are slash commands (i.e. called directly from the chat).
ChatApplicationCommand ApplicationCommandType = 1
// UserApplicationCommand adds command to user context menu.
UserApplicationCommand ApplicationCommandType = 2
// MessageApplicationCommand adds command to message context menu.
MessageApplicationCommand ApplicationCommandType = 3
)
// ApplicationCommand represents an application's slash command.
type ApplicationCommand struct {
ID string `json:"id,omitempty"`
ApplicationID string `json:"application_id,omitempty"`
GuildID string `json:"guild_id,omitempty"`
Version string `json:"version,omitempty"`
Type ApplicationCommandType `json:"type,omitempty"`
Name string `json:"name"`
NameLocalizations *map[Locale]string `json:"name_localizations,omitempty"`
// NOTE: DefaultPermission will be soon deprecated. Use DefaultMemberPermissions and DMPermission instead.
DefaultPermission *bool `json:"default_permission,omitempty"`
DefaultMemberPermissions *int64 `json:"default_member_permissions,string,omitempty"`
DMPermission *bool `json:"dm_permission,omitempty"`
NSFW *bool `json:"nsfw,omitempty"`
// NOTE: Chat commands only. Otherwise it mustn't be set.
Description string `json:"description,omitempty"`
DescriptionLocalizations *map[Locale]string `json:"description_localizations,omitempty"`
Options []*ApplicationCommandOption `json:"options"`
}
// ApplicationCommandOptionType indicates the type of a slash command's option.
type ApplicationCommandOptionType uint8
// Application command option types.
const (
ApplicationCommandOptionSubCommand ApplicationCommandOptionType = 1
ApplicationCommandOptionSubCommandGroup ApplicationCommandOptionType = 2
ApplicationCommandOptionString ApplicationCommandOptionType = 3
ApplicationCommandOptionInteger ApplicationCommandOptionType = 4
ApplicationCommandOptionBoolean ApplicationCommandOptionType = 5
ApplicationCommandOptionUser ApplicationCommandOptionType = 6
ApplicationCommandOptionChannel ApplicationCommandOptionType = 7
ApplicationCommandOptionRole ApplicationCommandOptionType = 8
ApplicationCommandOptionMentionable ApplicationCommandOptionType = 9
ApplicationCommandOptionNumber ApplicationCommandOptionType = 10
ApplicationCommandOptionAttachment ApplicationCommandOptionType = 11
)
func (t ApplicationCommandOptionType) String() string {
switch t {
case ApplicationCommandOptionSubCommand:
return "SubCommand"
case ApplicationCommandOptionSubCommandGroup:
return "SubCommandGroup"
case ApplicationCommandOptionString:
return "String"
case ApplicationCommandOptionInteger:
return "Integer"
case ApplicationCommandOptionBoolean:
return "Boolean"
case ApplicationCommandOptionUser:
return "User"
case ApplicationCommandOptionChannel:
return "Channel"
case ApplicationCommandOptionRole:
return "Role"
case ApplicationCommandOptionMentionable:
return "Mentionable"
case ApplicationCommandOptionNumber:
return "Number"
case ApplicationCommandOptionAttachment:
return "Attachment"
}
return fmt.Sprintf("ApplicationCommandOptionType(%d)", t)
}
// ApplicationCommandOption represents an option/subcommand/subcommands group.
type ApplicationCommandOption struct {
Type ApplicationCommandOptionType `json:"type"`
Name string `json:"name"`
NameLocalizations map[Locale]string `json:"name_localizations,omitempty"`
Description string `json:"description,omitempty"`
DescriptionLocalizations map[Locale]string `json:"description_localizations,omitempty"`
// NOTE: This feature was on the API, but at some point developers decided to remove it.
// So I commented it, until it will be officially on the docs.
// Default bool `json:"default"`
ChannelTypes []ChannelType `json:"channel_types"`
Required bool `json:"required"`
Options []*ApplicationCommandOption `json:"options"`
// NOTE: mutually exclusive with Choices.
Autocomplete bool `json:"autocomplete"`
Choices []*ApplicationCommandOptionChoice `json:"choices"`
// Minimal value of number/integer option.
MinValue *float64 `json:"min_value,omitempty"`
// Maximum value of number/integer option.
MaxValue float64 `json:"max_value,omitempty"`
// Minimum length of string option.
MinLength *int `json:"min_length,omitempty"`
// Maximum length of string option.
MaxLength int `json:"max_length,omitempty"`
}
// ApplicationCommandOptionChoice represents a slash command option choice.
type ApplicationCommandOptionChoice struct {
Name string `json:"name"`
NameLocalizations map[Locale]string `json:"name_localizations,omitempty"`
Value interface{} `json:"value"`
}
// ApplicationCommandPermissions represents a single user or role permission for a command.
type ApplicationCommandPermissions struct {
ID string `json:"id"`
Type ApplicationCommandPermissionType `json:"type"`
Permission bool `json:"permission"`
}
// GuildAllChannelsID is a helper function which returns guild_id-1.
// It is used in ApplicationCommandPermissions to target all the channels within a guild.
func GuildAllChannelsID(guild string) (id string, err error) {
var v uint64
v, err = strconv.ParseUint(guild, 10, 64)
if err != nil {
return
}
return strconv.FormatUint(v-1, 10), nil
}
// ApplicationCommandPermissionsList represents a list of ApplicationCommandPermissions, needed for serializing to JSON.
type ApplicationCommandPermissionsList struct {
Permissions []*ApplicationCommandPermissions `json:"permissions"`
}
// GuildApplicationCommandPermissions represents all permissions for a single guild command.
type GuildApplicationCommandPermissions struct {
ID string `json:"id"`
ApplicationID string `json:"application_id"`
GuildID string `json:"guild_id"`
Permissions []*ApplicationCommandPermissions `json:"permissions"`
}
// ApplicationCommandPermissionType indicates whether a permission is user or role based.
type ApplicationCommandPermissionType uint8
// Application command permission types.
const (
ApplicationCommandPermissionTypeRole ApplicationCommandPermissionType = 1
ApplicationCommandPermissionTypeUser ApplicationCommandPermissionType = 2
ApplicationCommandPermissionTypeChannel ApplicationCommandPermissionType = 3
)
// InteractionType indicates the type of an interaction event.
type InteractionType uint8
// Interaction types
const (
InteractionPing InteractionType = 1
InteractionApplicationCommand InteractionType = 2
InteractionMessageComponent InteractionType = 3
InteractionApplicationCommandAutocomplete InteractionType = 4
InteractionModalSubmit InteractionType = 5
)
func (t InteractionType) String() string {
switch t {
case InteractionPing:
return "Ping"
case InteractionApplicationCommand:
return "ApplicationCommand"
case InteractionMessageComponent:
return "MessageComponent"
case InteractionModalSubmit:
return "ModalSubmit"
}
return fmt.Sprintf("InteractionType(%d)", t)
}
// Interaction represents data of an interaction.
type Interaction struct {
ID string `json:"id"`
AppID string `json:"application_id"`
Type InteractionType `json:"type"`
Data InteractionData `json:"data"`
GuildID string `json:"guild_id"`
ChannelID string `json:"channel_id"`
// The message on which interaction was used.
// NOTE: this field is only filled when a button click triggered the interaction. Otherwise it will be nil.
Message *Message `json:"message"`
// Bitwise set of permissions the app or bot has within the channel the interaction was sent from
AppPermissions int64 `json:"app_permissions,string"`
// The member who invoked this interaction.
// NOTE: this field is only filled when the slash command was invoked in a guild;
// if it was invoked in a DM, the `User` field will be filled instead.
// Make sure to check for `nil` before using this field.
Member *Member `json:"member"`
// The user who invoked this interaction.
// NOTE: this field is only filled when the slash command was invoked in a DM;
// if it was invoked in a guild, the `Member` field will be filled instead.
// Make sure to check for `nil` before using this field.
User *User `json:"user"`
// The user's discord client locale.
Locale Locale `json:"locale"`
// The guild's locale. This defaults to EnglishUS
// NOTE: this field is only filled when the interaction was invoked in a guild.
GuildLocale *Locale `json:"guild_locale"`
Token string `json:"token"`
Version int `json:"version"`
}
type interaction Interaction
type rawInteraction struct {
interaction
Data json.RawMessage `json:"data"`
}
// UnmarshalJSON is a method for unmarshalling JSON object to Interaction.
func (i *Interaction) UnmarshalJSON(raw []byte) error {
var tmp rawInteraction
err := json.Unmarshal(raw, &tmp)
if err != nil {
return err
}
*i = Interaction(tmp.interaction)
switch tmp.Type {
case InteractionApplicationCommand, InteractionApplicationCommandAutocomplete:
v := ApplicationCommandInteractionData{}
err = json.Unmarshal(tmp.Data, &v)
if err != nil {
return err
}
i.Data = v
case InteractionMessageComponent:
v := MessageComponentInteractionData{}
err = json.Unmarshal(tmp.Data, &v)
if err != nil {
return err
}
i.Data = v
case InteractionModalSubmit:
v := ModalSubmitInteractionData{}
err = json.Unmarshal(tmp.Data, &v)
if err != nil {
return err
}
i.Data = v
}
return nil
}
// MessageComponentData is helper function to assert the inner InteractionData to MessageComponentInteractionData.
// Make sure to check that the Type of the interaction is InteractionMessageComponent before calling.
func (i Interaction) MessageComponentData() (data MessageComponentInteractionData) {
if i.Type != InteractionMessageComponent {
panic("MessageComponentData called on interaction of type " + i.Type.String())
}
return i.Data.(MessageComponentInteractionData)
}
// ApplicationCommandData is helper function to assert the inner InteractionData to ApplicationCommandInteractionData.
// Make sure to check that the Type of the interaction is InteractionApplicationCommand before calling.
func (i Interaction) ApplicationCommandData() (data ApplicationCommandInteractionData) {
if i.Type != InteractionApplicationCommand && i.Type != InteractionApplicationCommandAutocomplete {
panic("ApplicationCommandData called on interaction of type " + i.Type.String())
}
return i.Data.(ApplicationCommandInteractionData)
}
// ModalSubmitData is helper function to assert the inner InteractionData to ModalSubmitInteractionData.
// Make sure to check that the Type of the interaction is InteractionModalSubmit before calling.
func (i Interaction) ModalSubmitData() (data ModalSubmitInteractionData) {
if i.Type != InteractionModalSubmit {
panic("ModalSubmitData called on interaction of type " + i.Type.String())
}
return i.Data.(ModalSubmitInteractionData)
}
// InteractionData is a common interface for all types of interaction data.
type InteractionData interface {
Type() InteractionType
}
// ApplicationCommandInteractionData contains the data of application command interaction.
type ApplicationCommandInteractionData struct {
ID string `json:"id"`
Name string `json:"name"`
Resolved *ApplicationCommandInteractionDataResolved `json:"resolved"`
// Slash command options
Options []*ApplicationCommandInteractionDataOption `json:"options"`
// Target (user/message) id on which context menu command was called.
// The details are stored in Resolved according to command type.
TargetID string `json:"target_id"`
}
// ApplicationCommandInteractionDataResolved contains resolved data of command execution.
// Partial Member objects are missing user, deaf and mute fields.
// Partial Channel objects only have id, name, type and permissions fields.
type ApplicationCommandInteractionDataResolved struct {
Users map[string]*User `json:"users"`
Members map[string]*Member `json:"members"`
Roles map[string]*Role `json:"roles"`
Channels map[string]*Channel `json:"channels"`
Messages map[string]*Message `json:"messages"`
Attachments map[string]*MessageAttachment `json:"attachments"`
}
// Type returns the type of interaction data.
func (ApplicationCommandInteractionData) Type() InteractionType {
return InteractionApplicationCommand
}
// MessageComponentInteractionData contains the data of message component interaction.
type MessageComponentInteractionData struct {
CustomID string `json:"custom_id"`
ComponentType ComponentType `json:"component_type"`
Resolved MessageComponentInteractionDataResolved `json:"resolved"`
// NOTE: Only filled when ComponentType is SelectMenuComponent (3). Otherwise is nil.
Values []string `json:"values"`
}
// MessageComponentInteractionDataResolved contains the resolved data of selected option.
type MessageComponentInteractionDataResolved struct {
Users map[string]*User `json:"users"`
Members map[string]*Member `json:"members"`
Roles map[string]*Role `json:"roles"`
Channels map[string]*Channel `json:"channels"`
}
// Type returns the type of interaction data.
func (MessageComponentInteractionData) Type() InteractionType {
return InteractionMessageComponent
}
// ModalSubmitInteractionData contains the data of modal submit interaction.
type ModalSubmitInteractionData struct {
CustomID string `json:"custom_id"`
Components []MessageComponent `json:"-"`
}
// Type returns the type of interaction data.
func (ModalSubmitInteractionData) Type() InteractionType {
return InteractionModalSubmit
}
// UnmarshalJSON is a helper function to correctly unmarshal Components.
func (d *ModalSubmitInteractionData) UnmarshalJSON(data []byte) error {
type modalSubmitInteractionData ModalSubmitInteractionData
var v struct {
modalSubmitInteractionData
RawComponents []unmarshalableMessageComponent `json:"components"`
}
err := json.Unmarshal(data, &v)
if err != nil {
return err
}
*d = ModalSubmitInteractionData(v.modalSubmitInteractionData)
d.Components = make([]MessageComponent, len(v.RawComponents))
for i, v := range v.RawComponents {
d.Components[i] = v.MessageComponent
}
return err
}
// ApplicationCommandInteractionDataOption represents an option of a slash command.
type ApplicationCommandInteractionDataOption struct {
Name string `json:"name"`
Type ApplicationCommandOptionType `json:"type"`
// NOTE: Contains the value specified by Type.
Value interface{} `json:"value,omitempty"`
Options []*ApplicationCommandInteractionDataOption `json:"options,omitempty"`
// NOTE: autocomplete interaction only.
Focused bool `json:"focused,omitempty"`
}
// IntValue is a utility function for casting option value to integer
func (o ApplicationCommandInteractionDataOption) IntValue() int64 {
if o.Type != ApplicationCommandOptionInteger {
panic("IntValue called on data option of type " + o.Type.String())
}
return int64(o.Value.(float64))
}
// UintValue is a utility function for casting option value to unsigned integer
func (o ApplicationCommandInteractionDataOption) UintValue() uint64 {
if o.Type != ApplicationCommandOptionInteger {
panic("UintValue called on data option of type " + o.Type.String())
}
return uint64(o.Value.(float64))
}
// FloatValue is a utility function for casting option value to float
func (o ApplicationCommandInteractionDataOption) FloatValue() float64 {
if o.Type != ApplicationCommandOptionNumber {
panic("FloatValue called on data option of type " + o.Type.String())
}
return o.Value.(float64)
}
// StringValue is a utility function for casting option value to string
func (o ApplicationCommandInteractionDataOption) StringValue() string {
if o.Type != ApplicationCommandOptionString {
panic("StringValue called on data option of type " + o.Type.String())
}
return o.Value.(string)
}
// BoolValue is a utility function for casting option value to bool
func (o ApplicationCommandInteractionDataOption) BoolValue() bool {
if o.Type != ApplicationCommandOptionBoolean {
panic("BoolValue called on data option of type " + o.Type.String())
}
return o.Value.(bool)
}
// ChannelValue is a utility function for casting option value to channel object.
// s : Session object, if not nil, function additionally fetches all channel's data
func (o ApplicationCommandInteractionDataOption) ChannelValue(s *Session) *Channel {
if o.Type != ApplicationCommandOptionChannel {
panic("ChannelValue called on data option of type " + o.Type.String())
}
chanID := o.Value.(string)
if s == nil {
return &Channel{ID: chanID}
}
ch, err := s.State.Channel(chanID)
if err != nil {
ch, err = s.Channel(chanID)
if err != nil {
return &Channel{ID: chanID}
}
}
return ch
}
// RoleValue is a utility function for casting option value to role object.
// s : Session object, if not nil, function additionally fetches all role's data
func (o ApplicationCommandInteractionDataOption) RoleValue(s *Session, gID string) *Role {
if o.Type != ApplicationCommandOptionRole && o.Type != ApplicationCommandOptionMentionable {
panic("RoleValue called on data option of type " + o.Type.String())
}
roleID := o.Value.(string)
if s == nil || gID == "" {
return &Role{ID: roleID}
}
r, err := s.State.Role(gID, roleID)
if err != nil {
roles, err := s.GuildRoles(gID)
if err == nil {
for _, r = range roles {
if r.ID == roleID {
return r
}
}
}
return &Role{ID: roleID}
}
return r
}
// UserValue is a utility function for casting option value to user object.
// s : Session object, if not nil, function additionally fetches all user's data
func (o ApplicationCommandInteractionDataOption) UserValue(s *Session) *User {
if o.Type != ApplicationCommandOptionUser && o.Type != ApplicationCommandOptionMentionable {
panic("UserValue called on data option of type " + o.Type.String())
}
userID := o.Value.(string)
if s == nil {
return &User{ID: userID}
}
u, err := s.User(userID)
if err != nil {
return &User{ID: userID}
}
return u
}
// InteractionResponseType is type of interaction response.
type InteractionResponseType uint8
// Interaction response types.
const (
// InteractionResponsePong is for ACK ping event.
InteractionResponsePong InteractionResponseType = 1
// InteractionResponseChannelMessageWithSource is for responding with a message, showing the user's input.
InteractionResponseChannelMessageWithSource InteractionResponseType = 4
// InteractionResponseDeferredChannelMessageWithSource acknowledges that the event was received, and that a follow-up will come later.
InteractionResponseDeferredChannelMessageWithSource InteractionResponseType = 5
// InteractionResponseDeferredMessageUpdate acknowledges that the message component interaction event was received, and message will be updated later.
InteractionResponseDeferredMessageUpdate InteractionResponseType = 6
// InteractionResponseUpdateMessage is for updating the message to which message component was attached.
InteractionResponseUpdateMessage InteractionResponseType = 7
// InteractionApplicationCommandAutocompleteResult shows autocompletion results. Autocomplete interaction only.
InteractionApplicationCommandAutocompleteResult InteractionResponseType = 8
// InteractionResponseModal is for responding to an interaction with a modal window.
InteractionResponseModal InteractionResponseType = 9
)
// InteractionResponse represents a response for an interaction event.
type InteractionResponse struct {
Type InteractionResponseType `json:"type,omitempty"`
Data *InteractionResponseData `json:"data,omitempty"`
}
// InteractionResponseData is response data for an interaction.
type InteractionResponseData struct {
TTS bool `json:"tts"`
Content string `json:"content"`
Components []MessageComponent `json:"components"`
Embeds []*MessageEmbed `json:"embeds"`
AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"`
Files []*File `json:"-"`
// NOTE: only MessageFlagsSuppressEmbeds and MessageFlagsEphemeral can be set.
Flags MessageFlags `json:"flags,omitempty"`
// NOTE: autocomplete interaction only.
Choices []*ApplicationCommandOptionChoice `json:"choices,omitempty"`
// NOTE: modal interaction only.
CustomID string `json:"custom_id,omitempty"`
Title string `json:"title,omitempty"`
}
// VerifyInteraction implements message verification of the discord interactions api
// signing algorithm, as documented here:
// https://discord.com/developers/docs/interactions/receiving-and-responding#security-and-authorization
func VerifyInteraction(r *http.Request, key ed25519.PublicKey) bool {
var msg bytes.Buffer
signature := r.Header.Get("X-Signature-Ed25519")
if signature == "" {
return false
}
sig, err := hex.DecodeString(signature)
if err != nil {
return false
}
if len(sig) != ed25519.SignatureSize {
return false
}
timestamp := r.Header.Get("X-Signature-Timestamp")
if timestamp == "" {
return false
}
msg.WriteString(timestamp)
defer r.Body.Close()
var body bytes.Buffer
// at the end of the function, copy the original body back into the request
defer func() {
r.Body = ioutil.NopCloser(&body)
}()
// copy body into buffers
_, err = io.Copy(&msg, io.TeeReader(r.Body, &body))
if err != nil {
return false
}
return ed25519.Verify(key, msg.Bytes(), sig)
}

83
vendor/github.com/bwmarrin/discordgo/locales.go generated vendored Normal file
View File

@@ -0,0 +1,83 @@
package discordgo
// Locale represents the accepted languages for Discord.
// https://discord.com/developers/docs/reference#locales
type Locale string
// String returns the human-readable string of the locale
func (l Locale) String() string {
if name, ok := Locales[l]; ok {
return name
}
return Unknown.String()
}
// All defined locales in Discord
const (
EnglishUS Locale = "en-US"
EnglishGB Locale = "en-GB"
Bulgarian Locale = "bg"
ChineseCN Locale = "zh-CN"
ChineseTW Locale = "zh-TW"
Croatian Locale = "hr"
Czech Locale = "cs"
Danish Locale = "da"
Dutch Locale = "nl"
Finnish Locale = "fi"
French Locale = "fr"
German Locale = "de"
Greek Locale = "el"
Hindi Locale = "hi"
Hungarian Locale = "hu"
Italian Locale = "it"
Japanese Locale = "ja"
Korean Locale = "ko"
Lithuanian Locale = "lt"
Norwegian Locale = "no"
Polish Locale = "pl"
PortugueseBR Locale = "pt-BR"
Romanian Locale = "ro"
Russian Locale = "ru"
SpanishES Locale = "es-ES"
Swedish Locale = "sv-SE"
Thai Locale = "th"
Turkish Locale = "tr"
Ukrainian Locale = "uk"
Vietnamese Locale = "vi"
Unknown Locale = ""
)
// Locales is a map of all the languages codes to their names.
var Locales = map[Locale]string{
EnglishUS: "English (United States)",
EnglishGB: "English (Great Britain)",
Bulgarian: "Bulgarian",
ChineseCN: "Chinese (China)",
ChineseTW: "Chinese (Taiwan)",
Croatian: "Croatian",
Czech: "Czech",
Danish: "Danish",
Dutch: "Dutch",
Finnish: "Finnish",
French: "French",
German: "German",
Greek: "Greek",
Hindi: "Hindi",
Hungarian: "Hungarian",
Italian: "Italian",
Japanese: "Japanese",
Korean: "Korean",
Lithuanian: "Lithuanian",
Norwegian: "Norwegian",
Polish: "Polish",
PortugueseBR: "Portuguese (Brazil)",
Romanian: "Romanian",
Russian: "Russian",
SpanishES: "Spanish (Spain)",
Swedish: "Swedish",
Thai: "Thai",
Turkish: "Turkish",
Ukrainian: "Ukrainian",
Vietnamese: "Vietnamese",
Unknown: "unknown",
}

View File

@@ -90,7 +90,7 @@ func (v *VoiceConnection) log(msgL int, format string, a ...interface{}) {
msglog(msgL, 2, format, a...)
}
// printJSON is a helper function to display JSON data in a easy to read format.
// printJSON is a helper function to display JSON data in an easy to read format.
/* NOT USED ATM
func printJSON(body []byte) {
var prettyJSON bytes.Buffer

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