1
0
forked from lug/matterbridge

Compare commits

...

55 Commits

Author SHA1 Message Date
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
1848 changed files with 4077041 additions and 9915 deletions

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) - [Binaries](#binaries)
- [Packages](#packages) - [Packages](#packages)
- [Building](#building) - [Building](#building)
- [Building with whatsapp (beta) multidevice support](#building-with-whatsapp-beta-multidevice-support)
- [Configuration](#configuration) - [Configuration](#configuration)
- [Basic configuration](#basic-configuration) - [Basic configuration](#basic-configuration)
- [Settings](#settings) - [Settings](#settings)
- [Advanced configuration](#advanced-configuration) - [Advanced configuration](#advanced-configuration)
- [Examples](#examples) - [Examples](#examples)
- [Bridge mattermost (off-topic) - irc (#testing)](#bridge-mattermost-off-topic---irc-testing) - [Bridge mattermost (off-topic) - irc (#testing)](#bridge-mattermost-off-topic---irc-testing)
- [Bridge slack (#general) - discord (general)](#bridge-slack-general---discord-general) - [Bridge slack (#general) - discord (general)](#bridge-slack-general---discord-general)
- [Running](#running) - [Running](#running)
- [Docker](#docker) - [Docker](#docker)
- [Systemd](#systemd) - [Systemd](#systemd)
- [Changelog](#changelog) - [Changelog](#changelog)
- [FAQ](#faq) - [FAQ](#faq)
- [Related projects](#related-projects) - [Related projects](#related-projects)
- [Articles / Tutorials](#articles--tutorials) - [Articles / Tutorials](#articles--tutorials)
- [Thanks](#thanks) - [Thanks](#thanks)
## Features ## Features
@@ -89,6 +90,7 @@ And more...
- [Discord](https://discordapp.com) - [Discord](https://discordapp.com)
- [Gitter](https://gitter.im) - [Gitter](https://gitter.im)
- [Harmony](https://harmonyapp.io)
- [IRC](http://www.mirc.com/servers.html) - [IRC](http://www.mirc.com/servers.html)
- [Keybase](https://keybase.io) - [Keybase](https://keybase.io)
- [Matrix](https://matrix.org) - [Matrix](https://matrix.org)
@@ -105,6 +107,8 @@ And more...
- [Twitch](https://twitch.tv) - [Twitch](https://twitch.tv)
- [VK](https://vk.com/) - [VK](https://vk.com/)
- [WhatsApp](https://www.whatsapp.com/) - [WhatsApp](https://www.whatsapp.com/)
- Whatsapp legacy is natively supported
- Whatsapp multidevice beta is natively supported but you need to build yourself, see [here](#building-with-whatsapp-beta-multidevice-support)
- [XMPP](https://xmpp.org) - [XMPP](https://xmpp.org)
- [Zulip](https://zulipchat.com) - [Zulip](https://zulipchat.com)
@@ -120,6 +124,8 @@ And more...
- [Counter-Strike, half-life and more](https://forums.alliedmods.net/showthread.php?t=319430) - [Counter-Strike, half-life and more](https://forums.alliedmods.net/showthread.php?t=319430)
- [MatterAMXX](https://github.com/GabeIggy/MatterAMXX) - [MatterAMXX](https://github.com/GabeIggy/MatterAMXX)
- [Vintage Story](https://github.com/NikkyAI/vs-matterbridge) - [Vintage Story](https://github.com/NikkyAI/vs-matterbridge)
- [Ultima Online Emulator](https://github.com/kuoushi/ServUO-Matterbridge)
- [Teamspeak](https://github.com/Archeb/ts-matterbridge)
### API ### API
@@ -138,6 +144,8 @@ Used by the projects below. Feel free to make a PR to add your project to this l
- [matterbabble](https://github.com/DeclanHoare/matterbabble) (Discourse support) - [matterbabble](https://github.com/DeclanHoare/matterbabble) (Discourse support)
- [MatterAMXX](https://forums.alliedmods.net/showthread.php?t=319430) (Counter-Strike, half-life and more via AMXX mod) - [MatterAMXX](https://forums.alliedmods.net/showthread.php?t=319430) (Counter-Strike, half-life and more via AMXX mod)
- [Vintage Story](https://github.com/NikkyAI/vs-matterbridge) - [Vintage Story](https://github.com/NikkyAI/vs-matterbridge)
- [ServUO-matterbridge](https://github.com/kuoushi/ServUO-Matterbridge) (A matterbridge connector for ServUO servers)
- [ts-matterbridge](https://github.com/Archeb/ts-matterbridge) (Integrate teamspeak chat with matterbridge)
## Chat with us ## Chat with us
@@ -164,10 +172,10 @@ See <https://github.com/42wim/matterbridge/wiki>
### Binaries ### Binaries
- Latest stable release [v1.24.1](https://github.com/42wim/matterbridge/releases/latest) - Latest stable release [v1.25.2](https://github.com/42wim/matterbridge/releases/latest)
- Development releases (follows master) can be downloaded [here](https://github.com/42wim/matterbridge/actions) selecting the latest green build and then artifacts. - Development releases (follows master) can be downloaded [here](https://github.com/42wim/matterbridge/actions) selecting the latest green build and then artifacts.
To install or upgrade just download the latest [binary](https://github.com/42wim/matterbridge/releases/latest). On \*nix platforms you may need to make the binary executable - you can do this by running `chmod a+x` on the binary (example: `chmod a+x matterbridge-1.20.0-linux-64bit`). After downloading (and making the binary executable, if necessary), follow the instructions on the [howto](https://github.com/42wim/matterbridge/wiki/How-to-create-your-config) for a step by step walkthrough for creating your configuration. To install or upgrade just download the latest [binary](https://github.com/42wim/matterbridge/releases/latest). On \*nix platforms you may need to make the binary executable - you can do this by running `chmod a+x` on the binary (example: `chmod a+x matterbridge-1.24.1-linux-64bit`). After downloading (and making the binary executable, if necessary), follow the instructions on the [howto](https://github.com/42wim/matterbridge/wiki/How-to-create-your-config) for a step by step walkthrough for creating your configuration.
### Packages ### Packages
@@ -182,16 +190,53 @@ Most people just want to use binaries, you can find those [here](https://github.
If you really want to build from source, follow these instructions: If you really want to build from source, follow these instructions:
Go 1.17+ is required. Make sure you have [Go](https://golang.org/doc/install) properly installed. Go 1.17+ is required. Make sure you have [Go](https://golang.org/doc/install) properly installed.
Building the binary with **all** the bridges enabled needs about 3GB RAM to compile.
You can reduce this memory requirement to 0,5GB RAM by adding the `nomsteams` tag if you don't need/use the Microsoft Teams bridge.
Matterbridge can be build without gcc/c-compiler: If you're running on windows first run `set CGO_ENABLED=0` on other platforms you prepend `CGO_ENABLED=0` to the `go build` command. (eg `CGO_ENABLED=0 go install github.com/42wim/matterbridge`)
To install the latest stable run: To install the latest stable run:
```bash ```bash
go install github.com/42wim/matterbridge@v1.24.1 go install github.com/42wim/matterbridge
``` ```
To install the latest dev run: To install the latest dev run:
```bash ```bash
go install github.com/42wim/matterbridge@latest go install github.com/42wim/matterbridge@master
```
To install the latest stable run without msteams or zulip bridge:
```bash
go install -tags nomsteams,nozulip github.com/42wim/matterbridge
```
You should now have matterbridge binary in the ~/go/bin directory:
```bash
$ ls ~/go/bin/
matterbridge
```
## Building with whatsapp (beta) multidevice support
Because the library we use for Whatsapp multidevice support includes a GPL3 library we can not provide you binaries.
(as this would require the Matterbridge to change it license to GPL)
Matterbridge can be build without gcc/c-compiler: If you're running on windows first run `set CGO_ENABLED=0` on other platforms you prepend `CGO_ENABLED=0` to the `go build` command. (eg `CGO_ENABLED=0 go install github.com/42wim/matterbridge`)
So this means you have to build it yourself using the instructions below:
```bash
go install -tags whatsappmulti github.com/42wim/matterbridge@master
```
If you're low on memory and don't need msteams:
```bash
go install -tags nomsteams,whatsappmulti github.com/42wim/matterbridge@master
``` ```
You should now have matterbridge binary in the ~/go/bin directory: You should now have matterbridge binary in the ~/go/bin directory:
@@ -323,6 +368,8 @@ See [FAQ](https://github.com/42wim/matterbridge/wiki/FAQ)
- [nextcloud talk](https://github.com/nextcloud/talk_matterbridge) (Integrates matterbridge in Nextcloud Talk) - [nextcloud talk](https://github.com/nextcloud/talk_matterbridge) (Integrates matterbridge in Nextcloud Talk)
- [mattercraft](https://github.com/raws/mattercraft) (Minecraft bridge) - [mattercraft](https://github.com/raws/mattercraft) (Minecraft bridge)
- [vs-matterbridge](https://github.com/NikkyAI/vs-matterbridge) (Vintage Story bridge) - [vs-matterbridge](https://github.com/NikkyAI/vs-matterbridge) (Vintage Story bridge)
- [ServUO-matterbridge](https://github.com/kuoushi/ServUO-Matterbridge) (A matterbridge connector for ServUO servers)
- [ts-matterbridge](https://github.com/Archeb/ts-matterbridge) (Integrate teamspeak chat with matterbridge)
## Articles / Tutorials ## Articles / Tutorials
@@ -356,6 +403,7 @@ Matterbridge wouldn't exist without these libraries:
- gops - <https://github.com/google/gops> - gops - <https://github.com/google/gops>
- gozulipbot - <https://github.com/ifo/gozulipbot> - gozulipbot - <https://github.com/ifo/gozulipbot>
- gumble - <https://github.com/layeh/gumble> - gumble - <https://github.com/layeh/gumble>
- harmony - <https://github.com/harmony-development/shibshib>
- irc - <https://github.com/lrstanley/girc> - irc - <https://github.com/lrstanley/girc>
- keybase - <https://github.com/keybase/go-keybase-chat-bot> - keybase - <https://github.com/keybase/go-keybase-chat-bot>
- matrix - <https://github.com/matrix-org/gomatrix> - matrix - <https://github.com/matrix-org/gomatrix>
@@ -363,6 +411,7 @@ Matterbridge wouldn't exist without these libraries:
- msgraph.go - <https://github.com/yaegashi/msgraph.go> - msgraph.go - <https://github.com/yaegashi/msgraph.go>
- mumble - <https://github.com/layeh/gumble> - mumble - <https://github.com/layeh/gumble>
- nctalk - <https://github.com/gary-kim/go-nc-talk> - nctalk - <https://github.com/gary-kim/go-nc-talk>
- rocketchat - <https://github.com/RocketChat/Rocket.Chat.Go.SDK>
- slack - <https://github.com/nlopes/slack> - slack - <https://github.com/nlopes/slack>
- sshchat - <https://github.com/shazow/ssh-chat> - sshchat - <https://github.com/shazow/ssh-chat>
- steam - <https://github.com/Philipp15b/go-steam> - steam - <https://github.com/Philipp15b/go-steam>
@@ -370,6 +419,7 @@ Matterbridge wouldn't exist without these libraries:
- tengo - <https://github.com/d5/tengo> - tengo - <https://github.com/d5/tengo>
- vk - <https://github.com/SevereCloud/vksdk> - vk - <https://github.com/SevereCloud/vksdk>
- whatsapp - <https://github.com/Rhymen/go-whatsapp> - whatsapp - <https://github.com/Rhymen/go-whatsapp>
- whatsapp - <https://github.com/tulir/whatsmeow>
- xmpp - <https://github.com/mattn/go-xmpp> - xmpp - <https://github.com/mattn/go-xmpp>
- zulip - <https://github.com/ifo/gozulipbot> - zulip - <https://github.com/ifo/gozulipbot>

View File

@@ -83,12 +83,12 @@ func (b *Bdiscord) Connect() error {
b.Log.Info("Connection succeeded") b.Log.Info("Connection succeeded")
b.c.AddHandler(b.messageCreate) b.c.AddHandler(b.messageCreate)
b.c.AddHandler(b.messageTyping) b.c.AddHandler(b.messageTyping)
b.c.AddHandler(b.memberUpdate)
b.c.AddHandler(b.messageUpdate) b.c.AddHandler(b.messageUpdate)
b.c.AddHandler(b.messageDelete) b.c.AddHandler(b.messageDelete)
b.c.AddHandler(b.messageDeleteBulk) b.c.AddHandler(b.messageDeleteBulk)
b.c.AddHandler(b.memberAdd) b.c.AddHandler(b.memberAdd)
b.c.AddHandler(b.memberRemove) b.c.AddHandler(b.memberRemove)
b.c.AddHandler(b.memberUpdate)
if b.GetInt("debuglevel") == 1 { if b.GetInt("debuglevel") == 1 {
b.c.AddHandler(b.messageEvent) b.c.AddHandler(b.messageEvent)
} }
@@ -272,7 +272,6 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
// Handle prefix hint for unthreaded messages. // Handle prefix hint for unthreaded messages.
if msg.ParentNotFound() { if msg.ParentNotFound() {
msg.ParentID = "" msg.ParentID = ""
msg.Text = fmt.Sprintf("[thread]: %s", msg.Text)
} }
// Use webhook to send the message // Use webhook to send the message

View File

@@ -7,6 +7,10 @@ import (
) )
func (b *Bdiscord) messageDelete(s *discordgo.Session, m *discordgo.MessageDelete) { //nolint:unparam 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 := config.Message{Account: b.Account, ID: m.ID, Event: config.EventMsgDelete, Text: config.EventMsgDelete}
rmsg.Channel = b.getChannelName(m.ChannelID) rmsg.Channel = b.getChannelName(m.ChannelID)
@@ -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 // 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 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 { for _, msgID := range m.Messages {
rmsg := config.Message{ rmsg := config.Message{
Account: b.Account, 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) { 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") { if !b.GetBool("ShowUserTyping") {
return return
} }
@@ -52,6 +64,10 @@ func (b *Bdiscord) messageTyping(s *discordgo.Session, m *discordgo.TypingStart)
} }
func (b *Bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) { //nolint:unparam func (b *Bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) { //nolint:unparam
if m.GuildID != b.guildID {
b.Log.Debugf("Ignoring messageUpdate because it originates from a different guild")
return
}
if b.GetBool("EditDisable") { if b.GetBool("EditDisable") {
return return
} }
@@ -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 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 var err error
// not relay our own messages // 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) { 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 { if m.Member == nil {
b.Log.Warnf("Received member update with no member information: %#v", m) 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) { 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 { if m.Member == nil {
b.Log.Warnf("Received member update with no member information: %#v", m) b.Log.Warnf("Received member update with no member information: %#v", m)
return 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) { 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 { if m.Member == nil {
b.Log.Warnf("Received member update with no member information: %#v", m) b.Log.Warnf("Received member update with no member information: %#v", m)
return return

View File

@@ -8,7 +8,7 @@ import (
"strings" "strings"
"time" "time"
matrix "github.com/matrix-org/gomatrix" matrix "github.com/matterbridge/gomatrix"
) )
func newMatrixUsername(username string) *matrixUsername { func newMatrixUsername(username string) *matrixUsername {

View File

@@ -12,7 +12,7 @@ import (
"github.com/42wim/matterbridge/bridge" "github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config" "github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper" "github.com/42wim/matterbridge/bridge/helper"
matrix "github.com/matrix-org/gomatrix" matrix "github.com/matterbridge/gomatrix"
) )
var ( var (
@@ -428,12 +428,15 @@ func (b *Bmatrix) handleReply(ev *matrix.Event, rmsg config.Message) bool {
} }
body := rmsg.Text body := rmsg.Text
for strings.HasPrefix(body, "> ") {
lineIdx := strings.IndexRune(body, '\n') if !b.GetBool("keepquotedreply") {
if lineIdx == -1 { for strings.HasPrefix(body, "> ") {
body = "" lineIdx := strings.IndexRune(body, '\n')
} else { if lineIdx == -1 {
body = body[(lineIdx + 1):] body = ""
} else {
body = body[(lineIdx + 1):]
}
} }
} }

View File

@@ -183,6 +183,7 @@ func (b *Bmattermost) sendWebhook(msg config.Message) (string, error) {
if b.GetBool("PrefixMessagesWithNick") { if b.GetBool("PrefixMessagesWithNick") {
msg.Text = msg.Username + msg.Text msg.Text = msg.Username + msg.Text
} }
if msg.Extra != nil { if msg.Extra != nil {
// this sends a message only if we received a config.EVENT_FILE_FAILURE_SIZE // this sends a message only if we received a config.EVENT_FILE_FAILURE_SIZE
for _, rmsg := range helper.HandleExtra(&msg, b.General) { for _, rmsg := range helper.HandleExtra(&msg, b.General) {
@@ -206,7 +207,7 @@ func (b *Bmattermost) sendWebhook(msg config.Message) (string, error) {
for _, f := range msg.Extra["file"] { for _, f := range msg.Extra["file"] {
fi := f.(config.FileInfo) fi := f.(config.FileInfo)
if fi.URL != "" { if fi.URL != "" {
msg.Text += fi.URL msg.Text += " " + fi.URL
} }
} }
} }

View File

@@ -52,7 +52,7 @@ func (b *Bmattermost) Connect() error {
return nil return nil
} }
if strings.HasPrefix(b.getVersion(), "6.") { if strings.HasPrefix(b.getVersion(), "6.") || strings.HasPrefix(b.getVersion(), "7.") {
if !b.v6 { if !b.v6 {
b.v6 = true b.v6 = true
} }

View File

@@ -282,6 +282,13 @@ func (b *Bslack) handleStatusEvent(ev *slack.MessageEvent, rmsg *config.Message)
return false return false
} }
func getMessageTitle(attach *slack.Attachment) string {
if attach.TitleLink != "" {
return fmt.Sprintf("[%s](%s)\n", attach.Title, attach.TitleLink)
}
return attach.Title
}
func (b *Bslack) handleAttachments(ev *slack.MessageEvent, rmsg *config.Message) { func (b *Bslack) handleAttachments(ev *slack.MessageEvent, rmsg *config.Message) {
// File comments are set by the system (because there is no username given). // File comments are set by the system (because there is no username given).
if ev.SubType == sFileComment { if ev.SubType == sFileComment {
@@ -290,12 +297,15 @@ func (b *Bslack) handleAttachments(ev *slack.MessageEvent, rmsg *config.Message)
// See if we have some text in the attachments. // See if we have some text in the attachments.
if rmsg.Text == "" { if rmsg.Text == "" {
for _, attach := range ev.Attachments { for i, attach := range ev.Attachments {
if attach.Text != "" { if attach.Text != "" {
if attach.Title != "" { if attach.Title != "" {
rmsg.Text = attach.Title + "\n" rmsg.Text = getMessageTitle(&ev.Attachments[i])
} }
rmsg.Text += attach.Text rmsg.Text += attach.Text
if attach.Footer != "" {
rmsg.Text += "\n\n" + attach.Footer
}
} else { } else {
rmsg.Text = attach.Fallback rmsg.Text = attach.Fallback
} }

View File

@@ -87,6 +87,9 @@ func (b *Bslack) populateMessageWithUserInfo(ev *slack.MessageEvent, rmsg *confi
if user.Profile.DisplayName != "" { if user.Profile.DisplayName != "" {
rmsg.Username = user.Profile.DisplayName rmsg.Username = user.Profile.DisplayName
} }
if b.GetBool("UseFullName") && user.Profile.RealName != "" {
rmsg.Username = user.Profile.RealName
}
return nil return nil
} }
@@ -124,7 +127,7 @@ var (
mentionRE = regexp.MustCompile(`<@([a-zA-Z0-9]+)>`) mentionRE = regexp.MustCompile(`<@([a-zA-Z0-9]+)>`)
channelRE = regexp.MustCompile(`<#[a-zA-Z0-9]+\|(.+?)>`) channelRE = regexp.MustCompile(`<#[a-zA-Z0-9]+\|(.+?)>`)
variableRE = regexp.MustCompile(`<!((?:subteam\^)?[a-zA-Z0-9]+)(?:\|@?(.+?))?>`) variableRE = regexp.MustCompile(`<!((?:subteam\^)?[a-zA-Z0-9]+)(?:\|@?(.+?))?>`)
urlRE = regexp.MustCompile(`<(.*?)(\|.*?)?>`) urlRE = regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`)
codeFenceRE = regexp.MustCompile(`(?m)^` + "```" + `\w+$`) codeFenceRE = regexp.MustCompile(`(?m)^` + "```" + `\w+$`)
topicOrPurposeRE = regexp.MustCompile(`(?s)(@.+) (cleared|set)(?: the)? channel (topic|purpose)(?:: (.*))?`) topicOrPurposeRE = regexp.MustCompile(`(?s)(@.+) (cleared|set)(?: the)? channel (topic|purpose)(?:: (.*))?`)
) )
@@ -178,14 +181,7 @@ func (b *Bslack) replaceVariable(text string) string {
// @see https://api.slack.com/docs/message-formatting#linking_to_urls // @see https://api.slack.com/docs/message-formatting#linking_to_urls
func (b *Bslack) replaceURL(text string) string { func (b *Bslack) replaceURL(text string) string {
for _, r := range urlRE.FindAllStringSubmatch(text, -1) { return urlRE.ReplaceAllString(text, "[${2}](${1})")
if len(strings.TrimSpace(r[2])) == 1 { // A display text separator was found, but the text was blank
text = strings.Replace(text, r[0], "", 1)
} else {
text = strings.Replace(text, r[0], r[1], 1)
}
}
return text
} }
func (b *Bslack) replaceb0rkedMarkDown(text string) string { func (b *Bslack) replaceb0rkedMarkDown(text string) string {

View File

@@ -321,7 +321,7 @@ func (b *Bslack) sendRTM(msg config.Message) (string, error) {
} }
// Upload a file if it exists. // Upload a file if it exists.
if msg.Extra != nil { if len(msg.Extra) > 0 {
extraMsgs := helper.HandleExtra(&msg, b.General) extraMsgs := helper.HandleExtra(&msg, b.General)
for i := range extraMsgs { for i := range extraMsgs {
rmsg := &extraMsgs[i] rmsg := &extraMsgs[i]
@@ -332,7 +332,7 @@ func (b *Bslack) sendRTM(msg config.Message) (string, error) {
} }
} }
// Upload files if necessary (from Slack, Telegram or Mattermost). // Upload files if necessary (from Slack, Telegram or Mattermost).
b.uploadFile(&msg, channelInfo.ID) return b.uploadFile(&msg, channelInfo.ID)
} }
// Post message. // Post message.
@@ -443,7 +443,8 @@ func (b *Bslack) postMessage(msg *config.Message, channelInfo *slack.Channel) (s
} }
// uploadFile handles native upload of files // uploadFile handles native upload of files
func (b *Bslack) uploadFile(msg *config.Message, channelID string) { func (b *Bslack) uploadFile(msg *config.Message, channelID string) (string, error) {
var messageID string
for _, f := range msg.Extra["file"] { for _, f := range msg.Extra["file"] {
fi, ok := f.(config.FileInfo) fi, ok := f.(config.FileInfo)
if !ok { if !ok {
@@ -471,13 +472,22 @@ func (b *Bslack) uploadFile(msg *config.Message, channelID string) {
}) })
if err != nil { if err != nil {
b.Log.Errorf("uploadfile %#v", err) b.Log.Errorf("uploadfile %#v", err)
return return "", err
} }
if res.ID != "" { if res.ID != "" {
b.Log.Debugf("Adding file ID %s to cache with timestamp %s", res.ID, ts.String()) b.Log.Debugf("Adding file ID %s to cache with timestamp %s", res.ID, ts.String())
b.cache.Add("file"+res.ID, ts) b.cache.Add("file"+res.ID, ts)
// search for message id by uploaded file in private/public channels, get thread timestamp from uploaded file
if v, ok := res.Shares.Private[channelID]; ok && len(v) > 0 {
messageID = v[0].Ts
}
if v, ok := res.Shares.Public[channelID]; ok && len(v) > 0 {
messageID = v[0].Ts
}
} }
} }
return messageID, nil
} }
func (b *Bslack) prepareMessageOptions(msg *config.Message) []slack.MsgOption { func (b *Bslack) prepareMessageOptions(msg *config.Message) []slack.MsgOption {

View File

@@ -71,6 +71,9 @@ func (b *Btelegram) handleForwarded(rmsg *config.Message, message *tgbotapi.Mess
if b.GetBool("UseFirstName") { if b.GetBool("UseFirstName") {
usernameForward = message.ForwardFrom.FirstName usernameForward = message.ForwardFrom.FirstName
} }
if b.GetBool("UseFullName") {
usernameForward = message.ForwardFrom.FirstName + " " + message.ForwardFrom.LastName
}
if usernameForward == "" { if usernameForward == "" {
usernameForward = message.ForwardFrom.UserName usernameForward = message.ForwardFrom.UserName
@@ -94,6 +97,9 @@ func (b *Btelegram) handleQuoting(rmsg *config.Message, message *tgbotapi.Messag
if b.GetBool("UseFirstName") { if b.GetBool("UseFirstName") {
usernameReply = message.ReplyToMessage.From.FirstName usernameReply = message.ReplyToMessage.From.FirstName
} }
if b.GetBool("UseFullName") {
usernameReply = message.ReplyToMessage.From.FirstName + " " + message.ReplyToMessage.From.LastName
}
if usernameReply == "" { if usernameReply == "" {
usernameReply = message.ReplyToMessage.From.UserName usernameReply = message.ReplyToMessage.From.UserName
if usernameReply == "" { if usernameReply == "" {
@@ -105,7 +111,11 @@ func (b *Btelegram) handleQuoting(rmsg *config.Message, message *tgbotapi.Messag
usernameReply = unknownUser usernameReply = unknownUser
} }
if !b.GetBool("QuoteDisable") { if !b.GetBool("QuoteDisable") {
rmsg.Text = b.handleQuote(rmsg.Text, usernameReply, message.ReplyToMessage.Text) quote := message.ReplyToMessage.Text
if quote == "" {
quote = message.ReplyToMessage.Caption
}
rmsg.Text = b.handleQuote(rmsg.Text, usernameReply, quote)
} }
} }
} }
@@ -117,6 +127,9 @@ func (b *Btelegram) handleUsername(rmsg *config.Message, message *tgbotapi.Messa
if b.GetBool("UseFirstName") { if b.GetBool("UseFirstName") {
rmsg.Username = message.From.FirstName rmsg.Username = message.From.FirstName
} }
if b.GetBool("UseFullName") {
rmsg.Username = message.From.FirstName + " " + message.From.LastName
}
if rmsg.Username == "" { if rmsg.Username == "" {
rmsg.Username = message.From.UserName rmsg.Username = message.From.UserName
if rmsg.Username == "" { if rmsg.Username == "" {
@@ -134,6 +147,9 @@ func (b *Btelegram) handleUsername(rmsg *config.Message, message *tgbotapi.Messa
if b.GetBool("UseFirstName") { if b.GetBool("UseFirstName") {
rmsg.Username = message.SenderChat.FirstName rmsg.Username = message.SenderChat.FirstName
} }
if b.GetBool("UseFullName") {
rmsg.Username = message.SenderChat.FirstName + " " + message.SenderChat.LastName
}
if rmsg.Username == "" || rmsg.Username == "Channel_Bot" { if rmsg.Username == "" || rmsg.Username == "Channel_Bot" {
rmsg.Username = message.SenderChat.UserName rmsg.Username = message.SenderChat.UserName
@@ -187,6 +203,11 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
rmsg.ID = strconv.Itoa(message.MessageID) rmsg.ID = strconv.Itoa(message.MessageID)
rmsg.Channel = strconv.FormatInt(message.Chat.ID, 10) rmsg.Channel = strconv.FormatInt(message.Chat.ID, 10)
// preserve threading from telegram reply
if message.ReplyToMessage != nil {
rmsg.ParentID = strconv.Itoa(message.ReplyToMessage.MessageID)
}
// handle entities (adding URLs) // handle entities (adding URLs)
b.handleEntities(&rmsg, message) b.handleEntities(&rmsg, message)
@@ -364,7 +385,7 @@ func (b *Btelegram) getDownloadInfo(id string, suffix string, urlpart bool) (str
urlPart := strings.Split(url, "/") urlPart := strings.Split(url, "/")
name = urlPart[len(urlPart)-1] name = urlPart[len(urlPart)-1]
} }
if suffix != "" && !strings.HasSuffix(name, suffix) { if suffix != "" && !strings.HasSuffix(name, suffix) && !strings.HasSuffix(name, ".webm") {
name += suffix name += suffix
} }
text := " " + url text := " " + url
@@ -383,7 +404,7 @@ func (b *Btelegram) handleDelete(msg *config.Message, chatid int64) (string, err
} }
cfg := tgbotapi.NewDeleteMessage(chatid, msgid) cfg := tgbotapi.NewDeleteMessage(chatid, msgid)
_, err = b.c.Send(cfg) _, err = b.c.Request(cfg)
return "", err return "", err
} }
@@ -422,8 +443,8 @@ func (b *Btelegram) handleEdit(msg *config.Message, chatid int64) (string, error
} }
// handleUploadFile handles native upload of files // handleUploadFile handles native upload of files
func (b *Btelegram) handleUploadFile(msg *config.Message, chatid int64) string { func (b *Btelegram) handleUploadFile(msg *config.Message, chatid int64, parentID int) (string, error) {
var c tgbotapi.Chattable var media []interface{}
for _, f := range msg.Extra["file"] { for _, f := range msg.Extra["file"] {
fi := f.(config.FileInfo) fi := f.(config.FileInfo)
file := tgbotapi.FileBytes{ file := tgbotapi.FileBytes{
@@ -432,32 +453,42 @@ func (b *Btelegram) handleUploadFile(msg *config.Message, chatid int64) string {
} }
switch filepath.Ext(fi.Name) { switch filepath.Ext(fi.Name) {
case ".jpg", ".jpe", ".png": case ".jpg", ".jpe", ".png":
pc := tgbotapi.NewPhoto(chatid, file) pc := tgbotapi.NewInputMediaPhoto(file)
pc.Caption, pc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment) if fi.Comment != "" {
c = pc pc.Caption, pc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
}
media = append(media, pc)
case ".mp4", ".m4v": case ".mp4", ".m4v":
vc := tgbotapi.NewVideo(chatid, file) vc := tgbotapi.NewInputMediaVideo(file)
vc.Caption, vc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment) if fi.Comment != "" {
c = vc vc.Caption, vc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
}
media = append(media, vc)
case ".mp3", ".oga": case ".mp3", ".oga":
ac := tgbotapi.NewAudio(chatid, file) ac := tgbotapi.NewInputMediaAudio(file)
ac.Caption, ac.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment) if fi.Comment != "" {
c = ac ac.Caption, ac.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
}
media = append(media, ac)
case ".ogg": case ".ogg":
voc := tgbotapi.NewVoice(chatid, file) voc := tgbotapi.NewVoice(chatid, file)
voc.Caption, voc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment) voc.Caption, voc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
c = voc voc.ReplyToMessageID = parentID
res, err := b.c.Send(voc)
if err != nil {
return "", err
}
return strconv.Itoa(res.MessageID), nil
default: default:
dc := tgbotapi.NewDocument(chatid, file) dc := tgbotapi.NewInputMediaDocument(file)
dc.Caption, dc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment) if fi.Comment != "" {
c = dc dc.Caption, dc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
} }
_, err := b.c.Send(c) media = append(media, dc)
if err != nil {
b.Log.Errorf("file upload failed: %#v", err)
} }
} }
return ""
return b.sendMediaFiles(msg, chatid, parentID, media)
} }
func (b *Btelegram) handleQuote(message, quoteNick, quoteMessage string) string { func (b *Btelegram) handleQuote(message, quoteNick, quoteMessage string) string {
@@ -486,8 +517,8 @@ func (b *Btelegram) handleEntities(rmsg *config.Message, message *tgbotapi.Messa
} }
indexMovedBy := 0 indexMovedBy := 0
prevLinkOffset := -1
// for now only do URL replacements
for _, e := range message.Entities { for _, e := range message.Entities {
asRunes := utf16.Encode([]rune(rmsg.Text)) asRunes := utf16.Encode([]rune(rmsg.Text))
@@ -506,6 +537,11 @@ func (b *Btelegram) handleEntities(rmsg *config.Message, message *tgbotapi.Messa
} }
rmsg.Text = string(utf16.Decode(asRunes[:offset+e.Length])) + " (" + url.String() + ")" + string(utf16.Decode(asRunes[offset+e.Length:])) rmsg.Text = string(utf16.Decode(asRunes[:offset+e.Length])) + " (" + url.String() + ")" + string(utf16.Decode(asRunes[offset+e.Length:]))
indexMovedBy += len(url.String()) + 3 indexMovedBy += len(url.String()) + 3
prevLinkOffset = e.Offset
}
if e.Offset == prevLinkOffset {
continue
} }
if e.Type == "code" { if e.Type == "code" {

View File

@@ -1,6 +1,7 @@
package btelegram package btelegram
import ( import (
"fmt"
"html" "html"
"log" "log"
"strconv" "strconv"
@@ -108,16 +109,27 @@ func (b *Btelegram) Send(msg config.Message) (string, error) {
return b.handleDelete(&msg, chatid) return b.handleDelete(&msg, chatid)
} }
// Handle prefix hint for unthreaded messages.
if msg.ParentNotFound() {
msg.ParentID = ""
msg.Text = fmt.Sprintf("[reply]: %s", msg.Text)
}
var parentID int
if msg.ParentID != "" {
parentID, _ = b.intParentID(msg.ParentID)
}
// Upload a file if it exists // Upload a file if it exists
if msg.Extra != nil { if msg.Extra != nil {
for _, rmsg := range helper.HandleExtra(&msg, b.General) { for _, rmsg := range helper.HandleExtra(&msg, b.General) {
if _, msgErr := b.sendMessage(chatid, rmsg.Username, rmsg.Text); msgErr != nil { if _, msgErr := b.sendMessage(chatid, rmsg.Username, rmsg.Text, parentID); msgErr != nil {
b.Log.Errorf("sendMessage failed: %s", msgErr) b.Log.Errorf("sendMessage failed: %s", msgErr)
} }
} }
// check if we have files to upload (from slack, telegram or mattermost) // check if we have files to upload (from slack, telegram or mattermost)
if len(msg.Extra["file"]) > 0 { if len(msg.Extra["file"]) > 0 {
b.handleUploadFile(&msg, chatid) return b.handleUploadFile(&msg, chatid, parentID)
} }
} }
@@ -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 // Ignore empty text field needs for prevent double messages from whatsapp to telegram
// when sending media with text caption // when sending media with text caption
if msg.Text != "" { if msg.Text != "" {
return b.sendMessage(chatid, msg.Username, msg.Text) return b.sendMessage(chatid, msg.Username, msg.Text, parentID)
} }
return "", nil return "", nil
@@ -145,10 +157,10 @@ func (b *Btelegram) getFileDirectURL(id string) string {
return res return res
} }
func (b *Btelegram) sendMessage(chatid int64, username, text string) (string, error) { func (b *Btelegram) sendMessage(chatid int64, username, text string, parentID int) (string, error) {
m := tgbotapi.NewMessage(chatid, "") m := tgbotapi.NewMessage(chatid, "")
m.Text, m.ParseMode = TGGetParseMode(b, username, text) m.Text, m.ParseMode = TGGetParseMode(b, username, text)
m.ReplyToMessageID = parentID
m.DisableWebPagePreview = b.GetBool("DisableWebPagePreview") m.DisableWebPagePreview = b.GetBool("DisableWebPagePreview")
res, err := b.c.Send(m) res, err := b.c.Send(m)
@@ -158,6 +170,29 @@ func (b *Btelegram) sendMessage(chatid int64, username, text string) (string, er
return strconv.Itoa(res.MessageID), nil return strconv.Itoa(res.MessageID), nil
} }
// sendMediaFiles native upload media files via media group
func (b *Btelegram) sendMediaFiles(msg *config.Message, chatid int64, parentID int, media []interface{}) (string, error) {
if len(media) == 0 {
return "", nil
}
mg := tgbotapi.MediaGroupConfig{ChatID: chatid, ChannelUsername: msg.Username, Media: media, ReplyToMessageID: parentID}
messages, err := b.c.SendMediaGroup(mg)
if err != nil {
return "", err
}
// return first message id
return strconv.Itoa(messages[0].MessageID), nil
}
// intParentID return integer parent id for telegram message
func (b *Btelegram) intParentID(parentID string) (int, error) {
pid, err := strconv.Atoi(parentID)
if err != nil {
return 0, err
}
return pid, nil
}
func (b *Btelegram) cacheAvatar(msg *config.Message) (string, error) { func (b *Btelegram) cacheAvatar(msg *config.Message) (string, error) {
fi := msg.Extra["file"][0].(config.FileInfo) fi := msg.Extra["file"][0].(config.FileInfo)
/* if we have a sha we have successfully uploaded the file to the media server, /* if we have a sha we have successfully uploaded the file to the media server,

View File

@@ -64,7 +64,7 @@ func (b *Bvk) Connect() error {
go func() { go func() {
err := b.lp.Run() err := b.lp.Run()
if err != nil { if err != nil {
b.Log.Fatal("Enable longpoll in group management") b.Log.WithError(err).Fatal("Enable longpoll in group management")
} }
}() }()
@@ -223,7 +223,7 @@ func (b *Bvk) uploadFiles(extra map[string][]interface{}, peerID int) (string, s
} }
a, err := b.uploadFile(fi, peerID) a, err := b.uploadFile(fi, peerID)
if err != nil { if err != nil {
b.Log.Error("File upload error ", fi.Name) b.Log.WithError(err).Error("File upload error ", fi.Name)
} }
attachments = append(attachments, a) attachments = append(attachments, a)
@@ -237,7 +237,8 @@ func (b *Bvk) uploadFile(file config.FileInfo, peerID int) (string, error) {
photoRE := regexp.MustCompile(".(jpg|jpe|png)$") photoRE := regexp.MustCompile(".(jpg|jpe|png)$")
if photoRE.MatchString(file.Name) { if photoRE.MatchString(file.Name) {
p, err := b.c.UploadMessagesPhoto(peerID, r) // BUG(VK): for community chat peerID=0
p, err := b.c.UploadMessagesPhoto(0, r)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

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

View File

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

View File

@@ -40,6 +40,11 @@ type Bwhatsapp struct {
func New(cfg *bridge.Config) bridge.Bridger { func New(cfg *bridge.Config) bridge.Bridger {
number := cfg.GetString(cfgNumber) number := cfg.GetString(cfgNumber)
cfg.Log.Warn("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
cfg.Log.Warn("This bridge is deprecated and not supported anymore. Use the new multidevice whatsapp bridge")
cfg.Log.Warn("See https://github.com/42wim/matterbridge#building-with-whatsapp-beta-multidevice-support for more info")
cfg.Log.Warn("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
if number == "" { if number == "" {
cfg.Log.Fatalf("Missing configuration for WhatsApp bridge: Number") cfg.Log.Fatalf("Missing configuration for WhatsApp bridge: Number")
} }

View File

@@ -0,0 +1,344 @@
// +build whatsappmulti
package bwhatsapp
import (
"fmt"
"mime"
"strings"
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
"go.mau.fi/whatsmeow/binary/proto"
"go.mau.fi/whatsmeow/types"
"go.mau.fi/whatsmeow/types/events"
)
// nolint:gocritic
func (b *Bwhatsapp) eventHandler(evt interface{}) {
switch e := evt.(type) {
case *events.Message:
b.handleMessage(e)
}
}
func (b *Bwhatsapp) handleMessage(message *events.Message) {
msg := message.Message
switch {
case msg == nil, message.Info.IsFromMe, message.Info.Timestamp.Before(b.startedAt):
return
}
b.Log.Infof("Receiving message %#v", msg)
switch {
case msg.Conversation != nil || msg.ExtendedTextMessage != nil:
b.handleTextMessage(message.Info, msg)
case msg.VideoMessage != nil:
b.handleVideoMessage(message)
case msg.AudioMessage != nil:
b.handleAudioMessage(message)
case msg.DocumentMessage != nil:
b.handleDocumentMessage(message)
case msg.ImageMessage != nil:
b.handleImageMessage(message)
}
}
// nolint:funlen
func (b *Bwhatsapp) handleTextMessage(messageInfo types.MessageInfo, msg *proto.Message) {
senderJID := messageInfo.Sender
channel := messageInfo.Chat
senderName := b.getSenderName(messageInfo.Sender)
if senderName == "" {
senderName = "Someone" // don't expose telephone number
}
if msg.GetExtendedTextMessage() == nil && msg.GetConversation() == "" {
b.Log.Debugf("message without text content? %#v", msg)
return
}
var text string
// nolint:nestif
if msg.GetExtendedTextMessage() == nil {
text = msg.GetConversation()
} else {
text = msg.GetExtendedTextMessage().GetText()
ci := msg.GetExtendedTextMessage().GetContextInfo()
if senderJID == (types.JID{}) && ci.Participant != nil {
senderJID = types.NewJID(ci.GetParticipant(), types.DefaultUserServer)
}
if ci.MentionedJid != nil {
// handle user mentions
for _, mentionedJID := range ci.MentionedJid {
numberAndSuffix := strings.SplitN(mentionedJID, "@", 2)
// mentions comes as telephone numbers and we don't want to expose it to other bridges
// replace it with something more meaninful to others
mention := b.getSenderNotify(types.NewJID(numberAndSuffix[0], types.DefaultUserServer))
if mention == "" {
mention = "someone"
}
text = strings.Replace(text, "@"+numberAndSuffix[0], "@"+mention, 1)
}
}
}
rmsg := config.Message{
UserID: senderJID.String(),
Username: senderName,
Text: text,
Channel: channel.String(),
Account: b.Account,
Protocol: b.Protocol,
Extra: make(map[string][]interface{}),
// ParentID: TODO, // TODO handle thread replies // map from Info.QuotedMessageID string
ID: messageInfo.ID,
}
if avatarURL, exists := b.userAvatars[senderJID.String()]; exists {
rmsg.Avatar = avatarURL
}
b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account)
b.Log.Debugf("<= Message is %#v", rmsg)
b.Remote <- rmsg
}
// HandleImageMessage sent from WhatsApp, relay it to the brige
func (b *Bwhatsapp) handleImageMessage(msg *events.Message) {
imsg := msg.Message.GetImageMessage()
senderJID := msg.Info.Sender
senderName := b.getSenderName(senderJID)
ci := imsg.GetContextInfo()
if senderJID == (types.JID{}) && ci.Participant != nil {
senderJID = types.NewJID(ci.GetParticipant(), types.DefaultUserServer)
}
rmsg := config.Message{
UserID: senderJID.String(),
Username: senderName,
Channel: msg.Info.Chat.String(),
Account: b.Account,
Protocol: b.Protocol,
Extra: make(map[string][]interface{}),
ID: msg.Info.ID,
}
if avatarURL, exists := b.userAvatars[senderJID.String()]; exists {
rmsg.Avatar = avatarURL
}
fileExt, err := mime.ExtensionsByType(imsg.GetMimetype())
if err != nil {
b.Log.Errorf("Mimetype detection error: %s", err)
return
}
// rename .jfif to .jpg https://github.com/42wim/matterbridge/issues/1292
if fileExt[0] == ".jfif" {
fileExt[0] = ".jpg"
}
// rename .jpe to .jpg https://github.com/42wim/matterbridge/issues/1463
if fileExt[0] == ".jpe" {
fileExt[0] = ".jpg"
}
filename := fmt.Sprintf("%v%v", msg.Info.ID, fileExt[0])
b.Log.Debugf("Trying to download %s with type %s", filename, imsg.GetMimetype())
data, err := b.wc.Download(imsg)
if err != nil {
b.Log.Errorf("Download image failed: %s", err)
return
}
// Move file to bridge storage
helper.HandleDownloadData(b.Log, &rmsg, filename, imsg.GetCaption(), "", &data, b.General)
b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account)
b.Log.Debugf("<= Message is %#v", rmsg)
b.Remote <- rmsg
}
// HandleVideoMessage downloads video messages
func (b *Bwhatsapp) handleVideoMessage(msg *events.Message) {
imsg := msg.Message.GetVideoMessage()
senderJID := msg.Info.Sender
senderName := b.getSenderName(senderJID)
ci := imsg.GetContextInfo()
if senderJID == (types.JID{}) && ci.Participant != nil {
senderJID = types.NewJID(ci.GetParticipant(), types.DefaultUserServer)
}
rmsg := config.Message{
UserID: senderJID.String(),
Username: senderName,
Channel: msg.Info.Chat.String(),
Account: b.Account,
Protocol: b.Protocol,
Extra: make(map[string][]interface{}),
ID: msg.Info.ID,
}
if avatarURL, exists := b.userAvatars[senderJID.String()]; exists {
rmsg.Avatar = avatarURL
}
fileExt, err := mime.ExtensionsByType(imsg.GetMimetype())
if err != nil {
b.Log.Errorf("Mimetype detection error: %s", err)
return
}
if len(fileExt) == 0 {
fileExt = append(fileExt, ".mp4")
}
filename := fmt.Sprintf("%v%v", msg.Info.ID, fileExt[0])
b.Log.Debugf("Trying to download %s with size %#v and type %s", filename, imsg.GetFileLength(), imsg.GetMimetype())
data, err := b.wc.Download(imsg)
if err != nil {
b.Log.Errorf("Download video failed: %s", err)
return
}
// Move file to bridge storage
helper.HandleDownloadData(b.Log, &rmsg, filename, imsg.GetCaption(), "", &data, b.General)
b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account)
b.Log.Debugf("<= Message is %#v", rmsg)
b.Remote <- rmsg
}
// HandleAudioMessage downloads audio messages
func (b *Bwhatsapp) handleAudioMessage(msg *events.Message) {
imsg := msg.Message.GetAudioMessage()
senderJID := msg.Info.Sender
senderName := b.getSenderName(senderJID)
ci := imsg.GetContextInfo()
if senderJID == (types.JID{}) && ci.Participant != nil {
senderJID = types.NewJID(ci.GetParticipant(), types.DefaultUserServer)
}
rmsg := config.Message{
UserID: senderJID.String(),
Username: senderName,
Channel: msg.Info.Chat.String(),
Account: b.Account,
Protocol: b.Protocol,
Extra: make(map[string][]interface{}),
ID: msg.Info.ID,
}
if avatarURL, exists := b.userAvatars[senderJID.String()]; exists {
rmsg.Avatar = avatarURL
}
fileExt, err := mime.ExtensionsByType(imsg.GetMimetype())
if err != nil {
b.Log.Errorf("Mimetype detection error: %s", err)
return
}
if len(fileExt) == 0 {
fileExt = append(fileExt, ".ogg")
}
filename := fmt.Sprintf("%v%v", msg.Info.ID, fileExt[0])
b.Log.Debugf("Trying to download %s with size %#v and type %s", filename, imsg.GetFileLength(), imsg.GetMimetype())
data, err := b.wc.Download(imsg)
if err != nil {
b.Log.Errorf("Download video failed: %s", err)
return
}
// Move file to bridge storage
helper.HandleDownloadData(b.Log, &rmsg, filename, "audio message", "", &data, b.General)
b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account)
b.Log.Debugf("<= Message is %#v", rmsg)
b.Remote <- rmsg
}
// HandleDocumentMessage downloads documents
func (b *Bwhatsapp) handleDocumentMessage(msg *events.Message) {
imsg := msg.Message.GetDocumentMessage()
senderJID := msg.Info.Sender
senderName := b.getSenderName(senderJID)
ci := imsg.GetContextInfo()
if senderJID == (types.JID{}) && ci.Participant != nil {
senderJID = types.NewJID(ci.GetParticipant(), types.DefaultUserServer)
}
rmsg := config.Message{
UserID: senderJID.String(),
Username: senderName,
Channel: msg.Info.Chat.String(),
Account: b.Account,
Protocol: b.Protocol,
Extra: make(map[string][]interface{}),
ID: msg.Info.ID,
}
if avatarURL, exists := b.userAvatars[senderJID.String()]; exists {
rmsg.Avatar = avatarURL
}
fileExt, err := mime.ExtensionsByType(imsg.GetMimetype())
if err != nil {
b.Log.Errorf("Mimetype detection error: %s", err)
return
}
filename := fmt.Sprintf("%v", imsg.GetFileName())
b.Log.Debugf("Trying to download %s with extension %s and type %s", filename, fileExt, imsg.GetMimetype())
data, err := b.wc.Download(imsg)
if err != nil {
b.Log.Errorf("Download document message failed: %s", err)
return
}
// Move file to bridge storage
helper.HandleDownloadData(b.Log, &rmsg, filename, "document", "", &data, b.General)
b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account)
b.Log.Debugf("<= Message is %#v", rmsg)
b.Remote <- rmsg
}

View File

@@ -0,0 +1,108 @@
// +build whatsappmulti
package bwhatsapp
import (
"fmt"
"strings"
"go.mau.fi/whatsmeow/store"
"go.mau.fi/whatsmeow/store/sqlstore"
"go.mau.fi/whatsmeow/types"
)
type ProfilePicInfo struct {
URL string `json:"eurl"`
Tag string `json:"tag"`
Status int16 `json:"status"`
}
func (b *Bwhatsapp) getSenderName(senderJid types.JID) string {
if sender, exists := b.contacts[senderJid]; exists {
if sender.FullName != "" {
return sender.FullName
}
// if user is not in phone contacts
// it is the most obvious scenario unless you sync your phone contacts with some remote updated source
// users can change it in their WhatsApp settings -> profile -> click on Avatar
if sender.PushName != "" {
return sender.PushName
}
if sender.FirstName != "" {
return sender.FirstName
}
}
// try to reload this contact
if _, err := b.wc.Store.Contacts.GetAllContacts(); err != nil {
b.Log.Errorf("error on update of contacts: %v", err)
}
allcontacts, err := b.wc.Store.Contacts.GetAllContacts()
if err != nil {
b.Log.Errorf("error on update of contacts: %v", err)
}
if len(allcontacts) > 0 {
b.contacts = allcontacts
}
if sender, exists := b.contacts[senderJid]; exists {
if sender.FullName != "" {
return sender.FullName
}
// if user is not in phone contacts
// it is the most obvious scenario unless you sync your phone contacts with some remote updated source
// users can change it in their WhatsApp settings -> profile -> click on Avatar
if sender.PushName != "" {
return sender.PushName
}
if sender.FirstName != "" {
return sender.FirstName
}
}
return "Someone"
}
func (b *Bwhatsapp) getSenderNotify(senderJid types.JID) string {
if sender, exists := b.contacts[senderJid]; exists {
return sender.PushName
}
return ""
}
func (b *Bwhatsapp) GetProfilePicThumb(jid string) (*types.ProfilePictureInfo, error) {
pjid, _ := types.ParseJID(jid)
info, err := b.wc.GetProfilePictureInfo(pjid, true)
if err != nil {
return nil, fmt.Errorf("failed to get avatar: %v", err)
}
return info, nil
}
func isGroupJid(identifier string) bool {
return strings.HasSuffix(identifier, "@g.us") ||
strings.HasSuffix(identifier, "@temp") ||
strings.HasSuffix(identifier, "@broadcast")
}
func (b *Bwhatsapp) getDevice() (*store.Device, error) {
device := &store.Device{}
storeContainer, err := sqlstore.New("sqlite", "file:"+b.Config.GetString("sessionfile")+".db?_foreign_keys=on&_pragma=busy_timeout=10000", nil)
if err != nil {
return device, fmt.Errorf("failed to connect to database: %v", err)
}
device, err = storeContainer.GetFirstDevice()
if err != nil {
return device, fmt.Errorf("failed to get device: %v", err)
}
return device, nil
}

View File

@@ -0,0 +1,333 @@
// +build whatsappmulti
package bwhatsapp
import (
"context"
"errors"
"fmt"
"mime"
"os"
"path/filepath"
"time"
"github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config"
"github.com/mdp/qrterminal"
"go.mau.fi/whatsmeow"
"go.mau.fi/whatsmeow/binary/proto"
"go.mau.fi/whatsmeow/types"
waLog "go.mau.fi/whatsmeow/util/log"
goproto "google.golang.org/protobuf/proto"
_ "modernc.org/sqlite" // needed for sqlite
)
const (
// Account config parameters
cfgNumber = "Number"
)
// Bwhatsapp Bridge structure keeping all the information needed for relying
type Bwhatsapp struct {
*bridge.Config
startedAt time.Time
wc *whatsmeow.Client
contacts map[types.JID]types.ContactInfo
users map[string]types.ContactInfo
userAvatars map[string]string
}
// New Create a new WhatsApp bridge. This will be called for each [whatsapp.<server>] entry you have in the config file
func New(cfg *bridge.Config) bridge.Bridger {
number := cfg.GetString(cfgNumber)
if number == "" {
cfg.Log.Fatalf("Missing configuration for WhatsApp bridge: Number")
}
b := &Bwhatsapp{
Config: cfg,
users: make(map[string]types.ContactInfo),
userAvatars: make(map[string]string),
}
return b
}
// Connect to WhatsApp. Required implementation of the Bridger interface
func (b *Bwhatsapp) Connect() error {
device, err := b.getDevice()
if err != nil {
return err
}
number := b.GetString(cfgNumber)
if number == "" {
return errors.New("whatsapp's telephone number need to be configured")
}
b.Log.Debugln("Connecting to WhatsApp..")
b.wc = whatsmeow.NewClient(device, waLog.Stdout("Client", "INFO", true))
b.wc.AddEventHandler(b.eventHandler)
firstlogin := false
var qrChan <-chan whatsmeow.QRChannelItem
if b.wc.Store.ID == nil {
firstlogin = true
qrChan, err = b.wc.GetQRChannel(context.Background())
if err != nil && !errors.Is(err, whatsmeow.ErrQRStoreContainsID) {
return errors.New("failed to to get QR channel:" + err.Error())
}
}
err = b.wc.Connect()
if err != nil {
return errors.New("failed to connect to WhatsApp: " + err.Error())
}
if b.wc.Store.ID == nil {
for evt := range qrChan {
if evt.Event == "code" {
qrterminal.GenerateHalfBlock(evt.Code, qrterminal.L, os.Stdout)
} else {
b.Log.Infof("QR channel result: %s", evt.Event)
}
}
}
// disconnect and reconnect on our first login/pairing
// for some reason the GetJoinedGroups in JoinChannel doesn't work on first login
if firstlogin {
b.wc.Disconnect()
time.Sleep(time.Second)
err = b.wc.Connect()
if err != nil {
return errors.New("failed to connect to WhatsApp: " + err.Error())
}
}
b.Log.Infoln("WhatsApp connection successful")
b.contacts, err = b.wc.Store.Contacts.GetAllContacts()
if err != nil {
return errors.New("failed to get contacts: " + err.Error())
}
b.startedAt = time.Now()
// map all the users
for id, contact := range b.contacts {
if !isGroupJid(id.String()) && id.String() != "status@broadcast" {
// it is user
b.users[id.String()] = contact
}
}
// get user avatar asynchronously
b.Log.Info("Getting user avatars..")
for jid := range b.users {
info, err := b.GetProfilePicThumb(jid)
if err != nil {
b.Log.Warnf("Could not get profile photo of %s: %v", jid, err)
} else {
b.Lock()
if info != nil {
b.userAvatars[jid] = info.URL
}
b.Unlock()
}
}
b.Log.Info("Finished getting avatars..")
return nil
}
// Disconnect is called while reconnecting to the bridge
// Required implementation of the Bridger interface
func (b *Bwhatsapp) Disconnect() error {
b.wc.Disconnect()
return nil
}
// JoinChannel Join a WhatsApp group specified in gateway config as channel='number-id@g.us' or channel='Channel name'
// Required implementation of the Bridger interface
// https://github.com/42wim/matterbridge/blob/2cfd880cdb0df29771bf8f31df8d990ab897889d/bridge/bridge.go#L11-L16
func (b *Bwhatsapp) JoinChannel(channel config.ChannelInfo) error {
byJid := isGroupJid(channel.Name)
groups, err := b.wc.GetJoinedGroups()
if err != nil {
return err
}
// verify if we are member of the given group
if byJid {
gJID, err := types.ParseJID(channel.Name)
if err != nil {
return err
}
for _, group := range groups {
if group.JID == gJID {
return nil
}
}
}
foundGroups := []string{}
for _, group := range groups {
if group.Name == channel.Name {
foundGroups = append(foundGroups, group.Name)
}
}
switch len(foundGroups) {
case 0:
// didn't match any group - print out possibilites
for _, group := range groups {
b.Log.Infof("%s %s", group.JID, group.Name)
}
return fmt.Errorf("please specify group's JID from the list above instead of the name '%s'", channel.Name)
case 1:
return fmt.Errorf("group name might change. Please configure gateway with channel=\"%v\" instead of channel=\"%v\"", foundGroups[0], channel.Name)
default:
return fmt.Errorf("there is more than one group with name '%s'. Please specify one of JIDs as channel name: %v", channel.Name, foundGroups)
}
}
// Post a document message from the bridge to WhatsApp
func (b *Bwhatsapp) PostDocumentMessage(msg config.Message, filetype string) (string, error) {
groupJID, _ := types.ParseJID(msg.Channel)
fi := msg.Extra["file"][0].(config.FileInfo)
resp, err := b.wc.Upload(context.Background(), *fi.Data, whatsmeow.MediaDocument)
if err != nil {
return "", err
}
// Post document message
var message proto.Message
message.DocumentMessage = &proto.DocumentMessage{
Title: &fi.Name,
FileName: &fi.Name,
Mimetype: &filetype,
MediaKey: resp.MediaKey,
FileEncSha256: resp.FileEncSHA256,
FileSha256: resp.FileSHA256,
FileLength: goproto.Uint64(resp.FileLength),
Url: &resp.URL,
}
b.Log.Debugf("=> Sending %#v", msg)
ID := whatsmeow.GenerateMessageID()
_, err = b.wc.SendMessage(groupJID, ID, &message)
return ID, err
}
// Post an image message from the bridge to WhatsApp
// Handle, for sure image/jpeg, image/png and image/gif MIME types
func (b *Bwhatsapp) PostImageMessage(msg config.Message, filetype string) (string, error) {
groupJID, _ := types.ParseJID(msg.Channel)
fi := msg.Extra["file"][0].(config.FileInfo)
caption := msg.Username + fi.Comment
resp, err := b.wc.Upload(context.Background(), *fi.Data, whatsmeow.MediaImage)
if err != nil {
return "", err
}
var message proto.Message
message.ImageMessage = &proto.ImageMessage{
Mimetype: &filetype,
Caption: &caption,
MediaKey: resp.MediaKey,
FileEncSha256: resp.FileEncSHA256,
FileSha256: resp.FileSHA256,
FileLength: goproto.Uint64(resp.FileLength),
Url: &resp.URL,
}
b.Log.Debugf("=> Sending %#v", msg)
ID := whatsmeow.GenerateMessageID()
_, err = b.wc.SendMessage(groupJID, ID, &message)
return ID, err
}
// Send a message from the bridge to WhatsApp
func (b *Bwhatsapp) Send(msg config.Message) (string, error) {
groupJID, _ := types.ParseJID(msg.Channel)
b.Log.Debugf("=> Receiving %#v", msg)
// Delete message
if msg.Event == config.EventMsgDelete {
if msg.ID == "" {
// No message ID in case action is executed on a message sent before the bridge was started
// and then the bridge cache doesn't have this message ID mapped
return "", nil
}
_, err := b.wc.RevokeMessage(groupJID, msg.ID)
return "", err
}
// Edit message
if msg.ID != "" {
b.Log.Debugf("updating message with id %s", msg.ID)
if b.GetString("editsuffix") != "" {
msg.Text += b.GetString("EditSuffix")
} else {
msg.Text += " (edited)"
}
}
// Handle Upload a file
if msg.Extra["file"] != nil {
fi := msg.Extra["file"][0].(config.FileInfo)
filetype := mime.TypeByExtension(filepath.Ext(fi.Name))
b.Log.Debugf("Extra file is %#v", filetype)
// TODO: add different types
// TODO: add webp conversion
switch filetype {
case "image/jpeg", "image/png", "image/gif":
return b.PostImageMessage(msg, filetype)
default:
return b.PostDocumentMessage(msg, filetype)
}
}
text := msg.Username + msg.Text
var message proto.Message
message.Conversation = &text
ID := whatsmeow.GenerateMessageID()
_, err := b.wc.SendMessage(groupJID, ID, &message)
return ID, err
}

View File

@@ -1,3 +1,70 @@
# 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 # v1.24.1
## Enhancements ## Enhancements

View File

@@ -1,4 +1,5 @@
// +build !nowhatsapp // +build !nowhatsapp
// +build !whatsappmulti
package bridgemap 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")) igNicks := strings.Fields(gw.Bridges[msg.Account].GetString("IgnoreNicks"))
igMessages := strings.Fields(gw.Bridges[msg.Account].GetString("IgnoreMessages")) igMessages := strings.Fields(gw.Bridges[msg.Account].GetString("IgnoreMessages"))
if gw.ignoreTextEmpty(msg) || gw.ignoreText(msg.Username, igNicks) || gw.ignoreText(msg.Text, igMessages) { if gw.ignoreTextEmpty(msg) || gw.ignoreText(msg.Username, igNicks) || gw.ignoreText(msg.Text, igMessages) || gw.ignoreFilesComment(msg.Extra, igMessages) {
return true return true
} }
return false return false
} }
// ignoreFilesComment returns true if we need to ignore a file with matched comment.
func (gw *Gateway) ignoreFilesComment(extra map[string][]interface{}, igMessages []string) bool {
if extra == nil {
return false
}
for _, f := range extra["file"] {
fi, ok := f.(config.FileInfo)
if !ok {
continue
}
if gw.ignoreText(fi.Comment, igMessages) {
return true
}
}
return false
}
func (gw *Gateway) modifyUsername(msg *config.Message, dest *bridge.Bridge) string { func (gw *Gateway) modifyUsername(msg *config.Message, dest *bridge.Bridge) string {
if dest.GetBool("StripNick") { if dest.GetBool("StripNick") {
re := regexp.MustCompile("[^a-zA-Z0-9]+") re := regexp.MustCompile("[^a-zA-Z0-9]+")

97
go.mod
View File

@@ -6,55 +6,60 @@ require (
github.com/Benau/tgsconverter v0.0.0-20210809170556-99f4a4f6337f github.com/Benau/tgsconverter v0.0.0-20210809170556-99f4a4f6337f
github.com/Philipp15b/go-steam v1.0.1-0.20200727090957-6ae9b3c0a560 github.com/Philipp15b/go-steam v1.0.1-0.20200727090957-6ae9b3c0a560
github.com/Rhymen/go-whatsapp v0.1.2-0.20211102134409-31a2e740845c github.com/Rhymen/go-whatsapp v0.1.2-0.20211102134409-31a2e740845c
github.com/SevereCloud/vksdk/v2 v2.13.1 github.com/SevereCloud/vksdk/v2 v2.14.1
github.com/bwmarrin/discordgo v0.24.0 github.com/bwmarrin/discordgo v0.25.0
github.com/d5/tengo/v2 v2.10.1 github.com/d5/tengo/v2 v2.12.0
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/fsnotify/fsnotify v1.5.1 github.com/fsnotify/fsnotify v1.5.4
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
github.com/gomarkdown/markdown v0.0.0-20220310201231-552c6011c0b8 github.com/gomarkdown/markdown v0.0.0-20220607163217-45f7c050e2d1
github.com/google/gops v0.3.22 github.com/google/gops v0.3.23
github.com/gorilla/schema v1.2.0 github.com/gorilla/schema v1.2.0
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
github.com/harmony-development/shibshib v0.0.0-20220101224523-c98059d09cfa github.com/harmony-development/shibshib v0.0.0-20220101224523-c98059d09cfa
github.com/hashicorp/golang-lru v0.5.4 github.com/hashicorp/golang-lru v0.5.4
github.com/jpillora/backoff v1.0.0 github.com/jpillora/backoff v1.0.0
github.com/keybase/go-keybase-chat-bot v0.0.0-20211201215354-ee4b23828b55 github.com/keybase/go-keybase-chat-bot v0.0.0-20220322223021-75d497527469
github.com/kyokomi/emoji/v2 v2.2.9 github.com/kyokomi/emoji/v2 v2.2.9
github.com/labstack/echo/v4 v4.7.0 github.com/labstack/echo/v4 v4.7.2
github.com/lrstanley/girc v0.0.0-20211023233735-147f0ff77566 github.com/lrstanley/girc v0.0.0-20220507183218-96757fe3d2a2
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16
github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20211016222428-79310a412696 github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20211016222428-79310a412696
github.com/matterbridge/go-xmpp v0.0.0-20211030125215-791a06c5f1be 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/gozulipbot v0.0.0-20211023205727-a19d6c1f3b75
github.com/matterbridge/logrus-prefixed-formatter v0.5.3-0.20200523233437-d971309a77ba github.com/matterbridge/logrus-prefixed-formatter v0.5.3-0.20200523233437-d971309a77ba
github.com/matterbridge/matterclient v0.0.0-20211107234719-faca3cd42315 github.com/matterbridge/matterclient v0.0.0-20220430213656-07aca2731bc9
github.com/mattermost/mattermost-server/v5 v5.39.3 github.com/mattermost/mattermost-server/v5 v5.39.3
github.com/mattermost/mattermost-server/v6 v6.4.2 github.com/mattermost/mattermost-server/v6 v6.7.0
github.com/mattn/godown v0.0.1 github.com/mattn/godown v0.0.1
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/paulrosania/go-charset v0.0.0-20190326053356-55c9d7a5834c
github.com/rs/xid v1.3.0 github.com/rs/xid v1.4.0
github.com/russross/blackfriday v1.6.0 github.com/russross/blackfriday v1.6.0
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca
github.com/shazow/ssh-chat v1.10.1 github.com/shazow/ssh-chat v1.10.1
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
github.com/slack-go/slack v0.10.2 github.com/slack-go/slack v0.11.0
github.com/spf13/viper v1.10.1 github.com/spf13/viper v1.12.0
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.2
github.com/vincent-petithory/dataurl v1.0.0 github.com/vincent-petithory/dataurl v1.0.0
github.com/writeas/go-strip-markdown v2.0.1+incompatible github.com/writeas/go-strip-markdown v2.0.1+incompatible
github.com/yaegashi/msgraph.go v0.1.4 github.com/yaegashi/msgraph.go v0.1.4
github.com/zfjagann/golang-ring v0.0.0-20210116075443-7c86fdb43134 github.com/zfjagann/golang-ring v0.0.0-20220330170733-19bcea1b6289
golang.org/x/image v0.0.0-20220302094943-723b81ca9867 go.mau.fi/whatsmeow v0.0.0-20220624184947-57a69a641154
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a golang.org/x/image v0.0.0-20220617043117-41969df76e82
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2
golang.org/x/text v0.3.7 golang.org/x/text v0.3.7
gomod.garykim.dev/nc-talk v0.3.0 gomod.garykim.dev/nc-talk v0.3.0
google.golang.org/protobuf v1.28.0
gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376 gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376
layeh.com/gumble v0.0.0-20200818122324-146f9205029b layeh.com/gumble v0.0.0-20200818122324-146f9205029b
modernc.org/sqlite v1.17.3
) )
require ( require (
filippo.io/edwards25519 v1.0.0-rc.1 // indirect
github.com/Benau/go_rlottie v0.0.0-20210807002906-98c1b2421989 // indirect github.com/Benau/go_rlottie v0.0.0-20210807002906-98c1b2421989 // indirect
github.com/Jeffail/gabs v1.4.0 // indirect github.com/Jeffail/gabs v1.4.0 // indirect
github.com/apex/log v1.9.0 // indirect github.com/apex/log v1.9.0 // indirect
@@ -68,15 +73,17 @@ require (
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/gopackage/ddp v0.0.3 // 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/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // 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/kettek/apng v0.0.0-20191108220231-414630eed80f // indirect
github.com/klauspost/compress v1.14.2 // indirect github.com/klauspost/compress v1.15.6 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/klauspost/cpuid/v2 v2.0.12 // indirect
github.com/labstack/gommon v0.3.1 // indirect github.com/labstack/gommon v0.3.1 // indirect
github.com/magiconair/properties v1.8.5 // indirect github.com/magiconair/properties v1.8.6 // indirect
github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 // 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/ldap v0.0.0-20201202150706-ee0e6284187d // indirect
github.com/mattermost/logr v1.0.13 // indirect github.com/mattermost/logr v1.0.13 // indirect
@@ -86,31 +93,34 @@ require (
github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/minio/md5-simd v1.1.2 // 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/minio/sha256-simd v1.0.0 // indirect
github.com/mitchellh/go-homedir v1.1.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/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/monaco-io/request v1.0.5 // indirect github.com/monaco-io/request v1.0.5 // indirect
github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d // indirect github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d // indirect
github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff // 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/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.1 // indirect
github.com/philhofer/fwd v1.1.1 // indirect github.com/philhofer/fwd v1.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // 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/date v1.12.4 // indirect
github.com/rickb777/plural v1.2.0 // indirect github.com/rickb777/plural v1.2.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect
github.com/shazow/rateio v0.0.0-20200113175441-4461efc8bdc4 // indirect github.com/shazow/rateio v0.0.0-20200113175441-4461efc8bdc4 // indirect
github.com/sizeofint/webpanimation v0.0.0-20210809145948-1d2b32119882 // indirect github.com/sizeofint/webpanimation v0.0.0-20210809145948-1d2b32119882 // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9 // indirect
github.com/spf13/afero v1.6.0 // indirect github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/cast v1.4.1 // indirect github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.2.0 // indirect github.com/subosito/gotenv v1.3.0 // indirect
github.com/tinylib/msgp v1.1.6 // indirect github.com/tinylib/msgp v1.1.6 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect github.com/valyala/fasttemplate v1.2.1 // indirect
@@ -119,22 +129,35 @@ require (
github.com/wiggin77/cfg v1.0.2 // indirect github.com/wiggin77/cfg v1.0.2 // indirect
github.com/wiggin77/merror v1.0.3 // indirect github.com/wiggin77/merror v1.0.3 // indirect
github.com/wiggin77/srslog v1.0.1 // indirect github.com/wiggin77/srslog v1.0.1 // indirect
go.mau.fi/libsignal v0.0.0-20220425070825-c40c839ee6a0 // indirect
go.uber.org/atomic v1.9.0 // indirect go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.7.0 // indirect go.uber.org/multierr v1.7.0 // indirect
go.uber.org/zap v1.17.0 // indirect go.uber.org/zap v1.17.0 // indirect
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 // indirect golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect
golang.org/x/tools v0.1.10 // indirect
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/ini.v1 v1.66.2 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v2 v2.4.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.1.1 // indirect
modernc.org/cc/v3 v3.36.0 // indirect
modernc.org/ccgo/v3 v3.16.6 // indirect
modernc.org/libc v1.16.7 // indirect
modernc.org/mathutil v1.4.1 // indirect
modernc.org/memory v1.1.1 // indirect
modernc.org/opt v0.1.1 // indirect
modernc.org/strutil v1.1.1 // indirect
modernc.org/token v1.0.0 // indirect
rsc.io/qr v0.2.0 // indirect
) )
replace github.com/matrix-org/gomatrix => github.com/matterbridge/gomatrix v0.0.0-20220205235239-607eb9ee6419 //replace github.com/matrix-org/gomatrix => github.com/matterbridge/gomatrix v0.0.0-20220205235239-607eb9ee6419
go 1.17 go 1.17

493
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -197,7 +197,7 @@ ShowJoinPart=false
VerboseJoinPart=false VerboseJoinPart=false
#Do not send joins/parts to other bridges #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, slack, discord
#OPTIONAL (default false) #OPTIONAL (default false)
NoSendJoinPart=false NoSendJoinPart=false
@@ -496,7 +496,7 @@ RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
ShowJoinPart=false ShowJoinPart=false
#Do not send joins/parts to other bridges #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, slack, discord
#OPTIONAL (default false) #OPTIONAL (default false)
NoSendJoinPart=false NoSendJoinPart=false
@@ -830,7 +830,7 @@ RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
ShowJoinPart=false ShowJoinPart=false
#Do not send joins/parts to other bridges #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, slack, discord
#OPTIONAL (default false) #OPTIONAL (default false)
NoSendJoinPart=false NoSendJoinPart=false
@@ -862,6 +862,10 @@ ShowUserTyping=false
#Default "<clipped message>" #Default "<clipped message>"
MessageClipped="<clipped message>" MessageClipped="<clipped message>"
#If enabled use the slack "Real Name" as username.
#OPTIONAL (default false)
UseFullName=false
################################################################### ###################################################################
#discord section #discord section
################################################################### ###################################################################
@@ -1036,6 +1040,12 @@ DisableWebPagePreview=false
#OPTIONAL (default false) #OPTIONAL (default false)
UseFirstName=false UseFirstName=false
#If enabled use the "Full Name" as username. If this is empty use the Username
#If disabled use the "Username" as username. If this is empty use the First Name and Last Name as Full Name
#If all names are empty, username will be "unknown"
#OPTIONAL (default false)
UseFullName=false
#WARNING! If enabled this will relay GIF/stickers/documents and other attachments as URLs #WARNING! If enabled this will relay GIF/stickers/documents and other attachments as URLs
#Those URLs will contain your bot-token. This may not be what you want. #Those URLs will contain your bot-token. This may not be what you want.
#For now there is no secure way to relay GIF/stickers/documents without seeing your token. #For now there is no secure way to relay GIF/stickers/documents without seeing your token.
@@ -1141,6 +1151,12 @@ StripNick=false
#OPTIONAL (default false) #OPTIONAL (default false)
ShowTopicChange=false ShowTopicChange=false
#Opportunistically preserve threaded replies between Telegram groups.
#This only works if the parent message is still in the cache.
#Cache is flushed between restarts.
#OPTIONAL (default false)
PreserveThreading=false
################################################################### ###################################################################
#rocketchat section #rocketchat section
################################################################### ###################################################################
@@ -1312,6 +1328,15 @@ HTMLDisable=false
# UseUserName shows the username instead of the server nickname # UseUserName shows the username instead of the server nickname
UseUserName=false 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. #Nicks you want to ignore.
#Regular expressions supported #Regular expressions supported
#Messages from those users will not be sent to other bridges. #Messages from those users will not be sent to other bridges.

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

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

@@ -0,0 +1,419 @@
// Copyright (c) 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package field implements fast arithmetic modulo 2^255-19.
package field
import (
"crypto/subtle"
"encoding/binary"
"errors"
"math/bits"
)
// Element represents an element of the field GF(2^255-19). Note that this
// is not a cryptographically secure group, and should only be used to interact
// with edwards25519.Point coordinates.
//
// This type works similarly to math/big.Int, and all arguments and receivers
// are allowed to alias.
//
// The zero value is a valid zero element.
type Element struct {
// An element t represents the integer
// t.l0 + t.l1*2^51 + t.l2*2^102 + t.l3*2^153 + t.l4*2^204
//
// Between operations, all limbs are expected to be lower than 2^52.
l0 uint64
l1 uint64
l2 uint64
l3 uint64
l4 uint64
}
const maskLow51Bits uint64 = (1 << 51) - 1
var feZero = &Element{0, 0, 0, 0, 0}
// Zero sets v = 0, and returns v.
func (v *Element) Zero() *Element {
*v = *feZero
return v
}
var feOne = &Element{1, 0, 0, 0, 0}
// One sets v = 1, and returns v.
func (v *Element) One() *Element {
*v = *feOne
return v
}
// reduce reduces v modulo 2^255 - 19 and returns it.
func (v *Element) reduce() *Element {
v.carryPropagate()
// After the light reduction we now have a field element representation
// v < 2^255 + 2^13 * 19, but need v < 2^255 - 19.
// If v >= 2^255 - 19, then v + 19 >= 2^255, which would overflow 2^255 - 1,
// generating a carry. That is, c will be 0 if v < 2^255 - 19, and 1 otherwise.
c := (v.l0 + 19) >> 51
c = (v.l1 + c) >> 51
c = (v.l2 + c) >> 51
c = (v.l3 + c) >> 51
c = (v.l4 + c) >> 51
// If v < 2^255 - 19 and c = 0, this will be a no-op. Otherwise, it's
// effectively applying the reduction identity to the carry.
v.l0 += 19 * c
v.l1 += v.l0 >> 51
v.l0 = v.l0 & maskLow51Bits
v.l2 += v.l1 >> 51
v.l1 = v.l1 & maskLow51Bits
v.l3 += v.l2 >> 51
v.l2 = v.l2 & maskLow51Bits
v.l4 += v.l3 >> 51
v.l3 = v.l3 & maskLow51Bits
// no additional carry
v.l4 = v.l4 & maskLow51Bits
return v
}
// Add sets v = a + b, and returns v.
func (v *Element) Add(a, b *Element) *Element {
v.l0 = a.l0 + b.l0
v.l1 = a.l1 + b.l1
v.l2 = a.l2 + b.l2
v.l3 = a.l3 + b.l3
v.l4 = a.l4 + b.l4
// Using the generic implementation here is actually faster than the
// assembly. Probably because the body of this function is so simple that
// the compiler can figure out better optimizations by inlining the carry
// propagation.
return v.carryPropagateGeneric()
}
// Subtract sets v = a - b, and returns v.
func (v *Element) Subtract(a, b *Element) *Element {
// We first add 2 * p, to guarantee the subtraction won't underflow, and
// then subtract b (which can be up to 2^255 + 2^13 * 19).
v.l0 = (a.l0 + 0xFFFFFFFFFFFDA) - b.l0
v.l1 = (a.l1 + 0xFFFFFFFFFFFFE) - b.l1
v.l2 = (a.l2 + 0xFFFFFFFFFFFFE) - b.l2
v.l3 = (a.l3 + 0xFFFFFFFFFFFFE) - b.l3
v.l4 = (a.l4 + 0xFFFFFFFFFFFFE) - b.l4
return v.carryPropagate()
}
// Negate sets v = -a, and returns v.
func (v *Element) Negate(a *Element) *Element {
return v.Subtract(feZero, a)
}
// Invert sets v = 1/z mod p, and returns v.
//
// If z == 0, Invert returns v = 0.
func (v *Element) Invert(z *Element) *Element {
// Inversion is implemented as exponentiation with exponent p 2. It uses the
// same sequence of 255 squarings and 11 multiplications as [Curve25519].
var z2, z9, z11, z2_5_0, z2_10_0, z2_20_0, z2_50_0, z2_100_0, t Element
z2.Square(z) // 2
t.Square(&z2) // 4
t.Square(&t) // 8
z9.Multiply(&t, z) // 9
z11.Multiply(&z9, &z2) // 11
t.Square(&z11) // 22
z2_5_0.Multiply(&t, &z9) // 31 = 2^5 - 2^0
t.Square(&z2_5_0) // 2^6 - 2^1
for i := 0; i < 4; i++ {
t.Square(&t) // 2^10 - 2^5
}
z2_10_0.Multiply(&t, &z2_5_0) // 2^10 - 2^0
t.Square(&z2_10_0) // 2^11 - 2^1
for i := 0; i < 9; i++ {
t.Square(&t) // 2^20 - 2^10
}
z2_20_0.Multiply(&t, &z2_10_0) // 2^20 - 2^0
t.Square(&z2_20_0) // 2^21 - 2^1
for i := 0; i < 19; i++ {
t.Square(&t) // 2^40 - 2^20
}
t.Multiply(&t, &z2_20_0) // 2^40 - 2^0
t.Square(&t) // 2^41 - 2^1
for i := 0; i < 9; i++ {
t.Square(&t) // 2^50 - 2^10
}
z2_50_0.Multiply(&t, &z2_10_0) // 2^50 - 2^0
t.Square(&z2_50_0) // 2^51 - 2^1
for i := 0; i < 49; i++ {
t.Square(&t) // 2^100 - 2^50
}
z2_100_0.Multiply(&t, &z2_50_0) // 2^100 - 2^0
t.Square(&z2_100_0) // 2^101 - 2^1
for i := 0; i < 99; i++ {
t.Square(&t) // 2^200 - 2^100
}
t.Multiply(&t, &z2_100_0) // 2^200 - 2^0
t.Square(&t) // 2^201 - 2^1
for i := 0; i < 49; i++ {
t.Square(&t) // 2^250 - 2^50
}
t.Multiply(&t, &z2_50_0) // 2^250 - 2^0
t.Square(&t) // 2^251 - 2^1
t.Square(&t) // 2^252 - 2^2
t.Square(&t) // 2^253 - 2^3
t.Square(&t) // 2^254 - 2^4
t.Square(&t) // 2^255 - 2^5
return v.Multiply(&t, &z11) // 2^255 - 21
}
// Set sets v = a, and returns v.
func (v *Element) Set(a *Element) *Element {
*v = *a
return v
}
// SetBytes sets v to x, where x is a 32-byte little-endian encoding. If x is
// not of the right length, SetUniformBytes returns nil and an error, and the
// receiver is unchanged.
//
// Consistent with RFC 7748, the most significant bit (the high bit of the
// last byte) is ignored, and non-canonical values (2^255-19 through 2^255-1)
// are accepted. Note that this is laxer than specified by RFC 8032.
func (v *Element) SetBytes(x []byte) (*Element, error) {
if len(x) != 32 {
return nil, errors.New("edwards25519: invalid field element input size")
}
// Bits 0:51 (bytes 0:8, bits 0:64, shift 0, mask 51).
v.l0 = binary.LittleEndian.Uint64(x[0:8])
v.l0 &= maskLow51Bits
// Bits 51:102 (bytes 6:14, bits 48:112, shift 3, mask 51).
v.l1 = binary.LittleEndian.Uint64(x[6:14]) >> 3
v.l1 &= maskLow51Bits
// Bits 102:153 (bytes 12:20, bits 96:160, shift 6, mask 51).
v.l2 = binary.LittleEndian.Uint64(x[12:20]) >> 6
v.l2 &= maskLow51Bits
// Bits 153:204 (bytes 19:27, bits 152:216, shift 1, mask 51).
v.l3 = binary.LittleEndian.Uint64(x[19:27]) >> 1
v.l3 &= maskLow51Bits
// Bits 204:251 (bytes 24:32, bits 192:256, shift 12, mask 51).
// Note: not bytes 25:33, shift 4, to avoid overread.
v.l4 = binary.LittleEndian.Uint64(x[24:32]) >> 12
v.l4 &= maskLow51Bits
return v, nil
}
// Bytes returns the canonical 32-byte little-endian encoding of v.
func (v *Element) Bytes() []byte {
// This function is outlined to make the allocations inline in the caller
// rather than happen on the heap.
var out [32]byte
return v.bytes(&out)
}
func (v *Element) bytes(out *[32]byte) []byte {
t := *v
t.reduce()
var buf [8]byte
for i, l := range [5]uint64{t.l0, t.l1, t.l2, t.l3, t.l4} {
bitsOffset := i * 51
binary.LittleEndian.PutUint64(buf[:], l<<uint(bitsOffset%8))
for i, bb := range buf {
off := bitsOffset/8 + i
if off >= len(out) {
break
}
out[off] |= bb
}
}
return out[:]
}
// Equal returns 1 if v and u are equal, and 0 otherwise.
func (v *Element) Equal(u *Element) int {
sa, sv := u.Bytes(), v.Bytes()
return subtle.ConstantTimeCompare(sa, sv)
}
// mask64Bits returns 0xffffffff if cond is 1, and 0 otherwise.
func mask64Bits(cond int) uint64 { return ^(uint64(cond) - 1) }
// Select sets v to a if cond == 1, and to b if cond == 0.
func (v *Element) Select(a, b *Element, cond int) *Element {
m := mask64Bits(cond)
v.l0 = (m & a.l0) | (^m & b.l0)
v.l1 = (m & a.l1) | (^m & b.l1)
v.l2 = (m & a.l2) | (^m & b.l2)
v.l3 = (m & a.l3) | (^m & b.l3)
v.l4 = (m & a.l4) | (^m & b.l4)
return v
}
// Swap swaps v and u if cond == 1 or leaves them unchanged if cond == 0, and returns v.
func (v *Element) Swap(u *Element, cond int) {
m := mask64Bits(cond)
t := m & (v.l0 ^ u.l0)
v.l0 ^= t
u.l0 ^= t
t = m & (v.l1 ^ u.l1)
v.l1 ^= t
u.l1 ^= t
t = m & (v.l2 ^ u.l2)
v.l2 ^= t
u.l2 ^= t
t = m & (v.l3 ^ u.l3)
v.l3 ^= t
u.l3 ^= t
t = m & (v.l4 ^ u.l4)
v.l4 ^= t
u.l4 ^= t
}
// IsNegative returns 1 if v is negative, and 0 otherwise.
func (v *Element) IsNegative() int {
return int(v.Bytes()[0] & 1)
}
// Absolute sets v to |u|, and returns v.
func (v *Element) Absolute(u *Element) *Element {
return v.Select(new(Element).Negate(u), u, u.IsNegative())
}
// Multiply sets v = x * y, and returns v.
func (v *Element) Multiply(x, y *Element) *Element {
feMul(v, x, y)
return v
}
// Square sets v = x * x, and returns v.
func (v *Element) Square(x *Element) *Element {
feSquare(v, x)
return v
}
// Mult32 sets v = x * y, and returns v.
func (v *Element) Mult32(x *Element, y uint32) *Element {
x0lo, x0hi := mul51(x.l0, y)
x1lo, x1hi := mul51(x.l1, y)
x2lo, x2hi := mul51(x.l2, y)
x3lo, x3hi := mul51(x.l3, y)
x4lo, x4hi := mul51(x.l4, y)
v.l0 = x0lo + 19*x4hi // carried over per the reduction identity
v.l1 = x1lo + x0hi
v.l2 = x2lo + x1hi
v.l3 = x3lo + x2hi
v.l4 = x4lo + x3hi
// The hi portions are going to be only 32 bits, plus any previous excess,
// so we can skip the carry propagation.
return v
}
// mul51 returns lo + hi * 2⁵¹ = a * b.
func mul51(a uint64, b uint32) (lo uint64, hi uint64) {
mh, ml := bits.Mul64(a, uint64(b))
lo = ml & maskLow51Bits
hi = (mh << 13) | (ml >> 51)
return
}
// Pow22523 set v = x^((p-5)/8), and returns v. (p-5)/8 is 2^252-3.
func (v *Element) Pow22523(x *Element) *Element {
var t0, t1, t2 Element
t0.Square(x) // x^2
t1.Square(&t0) // x^4
t1.Square(&t1) // x^8
t1.Multiply(x, &t1) // x^9
t0.Multiply(&t0, &t1) // x^11
t0.Square(&t0) // x^22
t0.Multiply(&t1, &t0) // x^31
t1.Square(&t0) // x^62
for i := 1; i < 5; i++ { // x^992
t1.Square(&t1)
}
t0.Multiply(&t1, &t0) // x^1023 -> 1023 = 2^10 - 1
t1.Square(&t0) // 2^11 - 2
for i := 1; i < 10; i++ { // 2^20 - 2^10
t1.Square(&t1)
}
t1.Multiply(&t1, &t0) // 2^20 - 1
t2.Square(&t1) // 2^21 - 2
for i := 1; i < 20; i++ { // 2^40 - 2^20
t2.Square(&t2)
}
t1.Multiply(&t2, &t1) // 2^40 - 1
t1.Square(&t1) // 2^41 - 2
for i := 1; i < 10; i++ { // 2^50 - 2^10
t1.Square(&t1)
}
t0.Multiply(&t1, &t0) // 2^50 - 1
t1.Square(&t0) // 2^51 - 2
for i := 1; i < 50; i++ { // 2^100 - 2^50
t1.Square(&t1)
}
t1.Multiply(&t1, &t0) // 2^100 - 1
t2.Square(&t1) // 2^101 - 2
for i := 1; i < 100; i++ { // 2^200 - 2^100
t2.Square(&t2)
}
t1.Multiply(&t2, &t1) // 2^200 - 1
t1.Square(&t1) // 2^201 - 2
for i := 1; i < 50; i++ { // 2^250 - 2^50
t1.Square(&t1)
}
t0.Multiply(&t1, &t0) // 2^250 - 1
t0.Square(&t0) // 2^251 - 2
t0.Square(&t0) // 2^252 - 4
return v.Multiply(&t0, x) // 2^252 - 3 -> x^(2^252-3)
}
// sqrtM1 is 2^((p-1)/4), which squared is equal to -1 by Euler's Criterion.
var sqrtM1 = &Element{1718705420411056, 234908883556509,
2233514472574048, 2117202627021982, 765476049583133}
// SqrtRatio sets r to the non-negative square root of the ratio of u and v.
//
// If u/v is square, SqrtRatio returns r and 1. If u/v is not square, SqrtRatio
// sets r according to Section 4.3 of draft-irtf-cfrg-ristretto255-decaf448-00,
// and returns r and 0.
func (r *Element) SqrtRatio(u, v *Element) (rr *Element, wasSquare int) {
var a, b Element
// r = (u * v3) * (u * v7)^((p-5)/8)
v2 := a.Square(v)
uv3 := b.Multiply(u, b.Multiply(v2, v))
uv7 := a.Multiply(uv3, a.Square(v2))
r.Multiply(uv3, r.Pow22523(uv7))
check := a.Multiply(v, a.Square(r)) // check = v * r^2
uNeg := b.Negate(u)
correctSignSqrt := check.Equal(u)
flippedSignSqrt := check.Equal(uNeg)
flippedSignSqrtI := check.Equal(uNeg.Multiply(uNeg, sqrtM1))
rPrime := b.Multiply(r, sqrtM1) // r_prime = SQRT_M1 * r
// r = CT_SELECT(r_prime IF flipped_sign_sqrt | flipped_sign_sqrt_i ELSE r)
r.Select(rPrime, r, flippedSignSqrt|flippedSignSqrtI)
r.Absolute(r) // Choose the nonnegative square root.
return r, correctSignSqrt | flippedSignSqrt
}

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

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

@@ -0,0 +1,264 @@
// Copyright (c) 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package field
import "math/bits"
// uint128 holds a 128-bit number as two 64-bit limbs, for use with the
// bits.Mul64 and bits.Add64 intrinsics.
type uint128 struct {
lo, hi uint64
}
// mul64 returns a * b.
func mul64(a, b uint64) uint128 {
hi, lo := bits.Mul64(a, b)
return uint128{lo, hi}
}
// addMul64 returns v + a * b.
func addMul64(v uint128, a, b uint64) uint128 {
hi, lo := bits.Mul64(a, b)
lo, c := bits.Add64(lo, v.lo, 0)
hi, _ = bits.Add64(hi, v.hi, c)
return uint128{lo, hi}
}
// shiftRightBy51 returns a >> 51. a is assumed to be at most 115 bits.
func shiftRightBy51(a uint128) uint64 {
return (a.hi << (64 - 51)) | (a.lo >> 51)
}
func feMulGeneric(v, a, b *Element) {
a0 := a.l0
a1 := a.l1
a2 := a.l2
a3 := a.l3
a4 := a.l4
b0 := b.l0
b1 := b.l1
b2 := b.l2
b3 := b.l3
b4 := b.l4
// Limb multiplication works like pen-and-paper columnar multiplication, but
// with 51-bit limbs instead of digits.
//
// a4 a3 a2 a1 a0 x
// b4 b3 b2 b1 b0 =
// ------------------------
// a4b0 a3b0 a2b0 a1b0 a0b0 +
// a4b1 a3b1 a2b1 a1b1 a0b1 +
// a4b2 a3b2 a2b2 a1b2 a0b2 +
// a4b3 a3b3 a2b3 a1b3 a0b3 +
// a4b4 a3b4 a2b4 a1b4 a0b4 =
// ----------------------------------------------
// r8 r7 r6 r5 r4 r3 r2 r1 r0
//
// We can then use the reduction identity (a * 2²⁵⁵ + b = a * 19 + b) to
// reduce the limbs that would overflow 255 bits. r5 * 2²⁵⁵ becomes 19 * r5,
// r6 * 2³⁰⁶ becomes 19 * r6 * 2⁵¹, etc.
//
// Reduction can be carried out simultaneously to multiplication. For
// example, we do not compute r5: whenever the result of a multiplication
// belongs to r5, like a1b4, we multiply it by 19 and add the result to r0.
//
// a4b0 a3b0 a2b0 a1b0 a0b0 +
// a3b1 a2b1 a1b1 a0b1 19×a4b1 +
// a2b2 a1b2 a0b2 19×a4b2 19×a3b2 +
// a1b3 a0b3 19×a4b3 19×a3b3 19×a2b3 +
// a0b4 19×a4b4 19×a3b4 19×a2b4 19×a1b4 =
// --------------------------------------
// r4 r3 r2 r1 r0
//
// Finally we add up the columns into wide, overlapping limbs.
a1_19 := a1 * 19
a2_19 := a2 * 19
a3_19 := a3 * 19
a4_19 := a4 * 19
// r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1)
r0 := mul64(a0, b0)
r0 = addMul64(r0, a1_19, b4)
r0 = addMul64(r0, a2_19, b3)
r0 = addMul64(r0, a3_19, b2)
r0 = addMul64(r0, a4_19, b1)
// r1 = a0×b1 + a1×b0 + 19×(a2×b4 + a3×b3 + a4×b2)
r1 := mul64(a0, b1)
r1 = addMul64(r1, a1, b0)
r1 = addMul64(r1, a2_19, b4)
r1 = addMul64(r1, a3_19, b3)
r1 = addMul64(r1, a4_19, b2)
// r2 = a0×b2 + a1×b1 + a2×b0 + 19×(a3×b4 + a4×b3)
r2 := mul64(a0, b2)
r2 = addMul64(r2, a1, b1)
r2 = addMul64(r2, a2, b0)
r2 = addMul64(r2, a3_19, b4)
r2 = addMul64(r2, a4_19, b3)
// r3 = a0×b3 + a1×b2 + a2×b1 + a3×b0 + 19×a4×b4
r3 := mul64(a0, b3)
r3 = addMul64(r3, a1, b2)
r3 = addMul64(r3, a2, b1)
r3 = addMul64(r3, a3, b0)
r3 = addMul64(r3, a4_19, b4)
// r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0
r4 := mul64(a0, b4)
r4 = addMul64(r4, a1, b3)
r4 = addMul64(r4, a2, b2)
r4 = addMul64(r4, a3, b1)
r4 = addMul64(r4, a4, b0)
// After the multiplication, we need to reduce (carry) the five coefficients
// to obtain a result with limbs that are at most slightly larger than 2⁵¹,
// to respect the Element invariant.
//
// Overall, the reduction works the same as carryPropagate, except with
// wider inputs: we take the carry for each coefficient by shifting it right
// by 51, and add it to the limb above it. The top carry is multiplied by 19
// according to the reduction identity and added to the lowest limb.
//
// The largest coefficient (r0) will be at most 111 bits, which guarantees
// that all carries are at most 111 - 51 = 60 bits, which fits in a uint64.
//
// r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1)
// r0 < 2⁵²×2⁵² + 19×(2⁵²×2⁵² + 2⁵²×2⁵² + 2⁵²×2⁵² + 2⁵²×2⁵²)
// r0 < (1 + 19 × 4) × 2⁵² × 2⁵²
// r0 < 2⁷ × 2⁵² × 2⁵²
// r0 < 2¹¹¹
//
// Moreover, the top coefficient (r4) is at most 107 bits, so c4 is at most
// 56 bits, and c4 * 19 is at most 61 bits, which again fits in a uint64 and
// allows us to easily apply the reduction identity.
//
// r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0
// r4 < 5 × 2⁵² × 2⁵²
// r4 < 2¹⁰⁷
//
c0 := shiftRightBy51(r0)
c1 := shiftRightBy51(r1)
c2 := shiftRightBy51(r2)
c3 := shiftRightBy51(r3)
c4 := shiftRightBy51(r4)
rr0 := r0.lo&maskLow51Bits + c4*19
rr1 := r1.lo&maskLow51Bits + c0
rr2 := r2.lo&maskLow51Bits + c1
rr3 := r3.lo&maskLow51Bits + c2
rr4 := r4.lo&maskLow51Bits + c3
// Now all coefficients fit into 64-bit registers but are still too large to
// be passed around as a Element. We therefore do one last carry chain,
// where the carries will be small enough to fit in the wiggle room above 2⁵¹.
*v = Element{rr0, rr1, rr2, rr3, rr4}
v.carryPropagate()
}
func feSquareGeneric(v, a *Element) {
l0 := a.l0
l1 := a.l1
l2 := a.l2
l3 := a.l3
l4 := a.l4
// Squaring works precisely like multiplication above, but thanks to its
// symmetry we get to group a few terms together.
//
// l4 l3 l2 l1 l0 x
// l4 l3 l2 l1 l0 =
// ------------------------
// l4l0 l3l0 l2l0 l1l0 l0l0 +
// l4l1 l3l1 l2l1 l1l1 l0l1 +
// l4l2 l3l2 l2l2 l1l2 l0l2 +
// l4l3 l3l3 l2l3 l1l3 l0l3 +
// l4l4 l3l4 l2l4 l1l4 l0l4 =
// ----------------------------------------------
// r8 r7 r6 r5 r4 r3 r2 r1 r0
//
// l4l0 l3l0 l2l0 l1l0 l0l0 +
// l3l1 l2l1 l1l1 l0l1 19×l4l1 +
// l2l2 l1l2 l0l2 19×l4l2 19×l3l2 +
// l1l3 l0l3 19×l4l3 19×l3l3 19×l2l3 +
// l0l4 19×l4l4 19×l3l4 19×l2l4 19×l1l4 =
// --------------------------------------
// r4 r3 r2 r1 r0
//
// With precomputed 2×, 19×, and 2×19× terms, we can compute each limb with
// only three Mul64 and four Add64, instead of five and eight.
l0_2 := l0 * 2
l1_2 := l1 * 2
l1_38 := l1 * 38
l2_38 := l2 * 38
l3_38 := l3 * 38
l3_19 := l3 * 19
l4_19 := l4 * 19
// r0 = l0×l0 + 19×(l1×l4 + l2×l3 + l3×l2 + l4×l1) = l0×l0 + 19×2×(l1×l4 + l2×l3)
r0 := mul64(l0, l0)
r0 = addMul64(r0, l1_38, l4)
r0 = addMul64(r0, l2_38, l3)
// r1 = l0×l1 + l1×l0 + 19×(l2×l4 + l3×l3 + l4×l2) = 2×l0×l1 + 19×2×l2×l4 + 19×l3×l3
r1 := mul64(l0_2, l1)
r1 = addMul64(r1, l2_38, l4)
r1 = addMul64(r1, l3_19, l3)
// r2 = l0×l2 + l1×l1 + l2×l0 + 19×(l3×l4 + l4×l3) = 2×l0×l2 + l1×l1 + 19×2×l3×l4
r2 := mul64(l0_2, l2)
r2 = addMul64(r2, l1, l1)
r2 = addMul64(r2, l3_38, l4)
// r3 = l0×l3 + l1×l2 + l2×l1 + l3×l0 + 19×l4×l4 = 2×l0×l3 + 2×l1×l2 + 19×l4×l4
r3 := mul64(l0_2, l3)
r3 = addMul64(r3, l1_2, l2)
r3 = addMul64(r3, l4_19, l4)
// r4 = l0×l4 + l1×l3 + l2×l2 + l3×l1 + l4×l0 = 2×l0×l4 + 2×l1×l3 + l2×l2
r4 := mul64(l0_2, l4)
r4 = addMul64(r4, l1_2, l3)
r4 = addMul64(r4, l2, l2)
c0 := shiftRightBy51(r0)
c1 := shiftRightBy51(r1)
c2 := shiftRightBy51(r2)
c3 := shiftRightBy51(r3)
c4 := shiftRightBy51(r4)
rr0 := r0.lo&maskLow51Bits + c4*19
rr1 := r1.lo&maskLow51Bits + c0
rr2 := r2.lo&maskLow51Bits + c1
rr3 := r3.lo&maskLow51Bits + c2
rr4 := r4.lo&maskLow51Bits + c3
*v = Element{rr0, rr1, rr2, rr3, rr4}
v.carryPropagate()
}
// carryPropagate brings the limbs below 52 bits by applying the reduction
// identity (a * 2²⁵⁵ + b = a * 19 + b) to the l4 carry.
func (v *Element) carryPropagateGeneric() *Element {
c0 := v.l0 >> 51
c1 := v.l1 >> 51
c2 := v.l2 >> 51
c3 := v.l3 >> 51
c4 := v.l4 >> 51
v.l0 = v.l0&maskLow51Bits + c4*19
v.l1 = v.l1&maskLow51Bits + c0
v.l2 = v.l2&maskLow51Bits + c1
v.l3 = v.l3&maskLow51Bits + c2
v.l4 = v.l4&maskLow51Bits + c3
return v
}

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

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

@@ -57,6 +57,8 @@ linters:
- grouper - grouper
- decorder - decorder
- containedctx - containedctx
# - execinquery # FIXME: panic in 1.46.0
- nosprintfhostport
# - wrapcheck # TODO: v3 Fix # - wrapcheck # TODO: v3 Fix
# - testpackage # TODO: Fix testpackage # - testpackage # TODO: Fix testpackage
@@ -87,6 +89,7 @@ linters:
# - varnamelen # - varnamelen
# - errchkjson # - errchkjson
# - maintidx # - maintidx
# - nonamedreturns
# depricated # depricated
# - maligned # - maligned

View File

@@ -1,7 +1,7 @@
# VK SDK for Golang # 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) [![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) [![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) [![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) [![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 // AccountSetNameInMenu sets an application screen name
// (up to 17 characters), that is shown to the user in the left menu. // (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 // https://vk.com/dev/account.setNameInMenu
func (vk *VK) AccountSetNameInMenu(params Params) (response int, err error) { func (vk *VK) AccountSetNameInMenu(params Params) (response int, err error) {
err = vk.RequestUnmarshal("account.setNameInMenu", &response, params) err = vk.RequestUnmarshal("account.setNameInMenu", &response, params)

View File

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

View File

@@ -235,6 +235,17 @@ func (vk *VK) VideoGetCommentsExtended(params Params) (response VideoGetComments
return 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. // VideoRemoveFromAlbum allows you to remove the video from the album.
// //
// https://vk.com/dev/video.removeFromAlbum // https://vk.com/dev/video.removeFromAlbum
@@ -336,3 +347,27 @@ func (vk *VK) VideoSearchExtended(params Params) (response VideoSearchExtendedRe
return 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. // Module constants.
const ( const (
Version = "2.13.1" Version = "2.14.1"
API = "5.131" API = "5.131"
) )

View File

@@ -15,3 +15,8 @@ func GroupIDFromContext(ctx context.Context) int {
func EventIDFromContext(ctx context.Context) string { func EventIDFromContext(ctx context.Context) string {
return ctx.Value(internal.EventIDKey).(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"` Object json.RawMessage `json:"object"`
GroupID int `json:"group_id"` GroupID int `json:"group_id"`
EventID string `json:"event_id"` EventID string `json:"event_id"`
V string `json:"v"`
Secret string `json:"secret"` Secret string `json:"secret"`
} }
@@ -158,6 +159,7 @@ func NewFuncList() *FuncList {
func (fl FuncList) Handler(ctx context.Context, e GroupEvent) error { // nolint:gocyclo 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.GroupIDKey, e.GroupID)
ctx = context.WithValue(ctx, internal.EventIDKey, e.EventID) ctx = context.WithValue(ctx, internal.EventIDKey, e.EventID)
ctx = context.WithValue(ctx, internal.EventVersionKey, e.V)
if sliceFunc, ok := fl.special[e.Type]; ok { if sliceFunc, ok := fl.special[e.Type]; ok {
for _, f := range sliceFunc { for _, f := range sliceFunc {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -31,7 +31,7 @@ type VideoVideo struct {
CanLike BaseBoolInt `json:"can_like"` CanLike BaseBoolInt `json:"can_like"`
// Information whether current user can download the video. // 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. // Information whether current user can repost this video.
CanRepost BaseBoolInt `json:"can_repost"` CanRepost BaseBoolInt `json:"can_repost"`
@@ -297,3 +297,27 @@ type VideoVideoImage struct {
BaseImage BaseImage
WithPadding BaseBoolInt `json:"with_padding"` 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"`
}

View File

@@ -4,6 +4,8 @@ go:
- 1.14.x - 1.14.x
- 1.15.x - 1.15.x
- 1.16.x - 1.16.x
- 1.17.x
- 1.18.x
env: env:
- GO111MODULE=on - GO111MODULE=on
install: install:

View File

@@ -70,7 +70,7 @@ type ActionsRow struct {
func (r ActionsRow) MarshalJSON() ([]byte, error) { func (r ActionsRow) MarshalJSON() ([]byte, error) {
type actionsRow ActionsRow type actionsRow ActionsRow
return json.Marshal(struct { return Marshal(struct {
actionsRow actionsRow
Type ComponentType `json:"type"` Type ComponentType `json:"type"`
}{ }{
@@ -145,7 +145,7 @@ func (b Button) MarshalJSON() ([]byte, error) {
b.Style = PrimaryButton b.Style = PrimaryButton
} }
return json.Marshal(struct { return Marshal(struct {
button button
Type ComponentType `json:"type"` Type ComponentType `json:"type"`
}{ }{
@@ -192,7 +192,7 @@ func (SelectMenu) Type() ComponentType {
func (m SelectMenu) MarshalJSON() ([]byte, error) { func (m SelectMenu) MarshalJSON() ([]byte, error) {
type selectMenu SelectMenu type selectMenu SelectMenu
return json.Marshal(struct { return Marshal(struct {
selectMenu selectMenu
Type ComponentType `json:"type"` Type ComponentType `json:"type"`
}{ }{
@@ -208,7 +208,7 @@ type TextInput struct {
Style TextInputStyle `json:"style"` Style TextInputStyle `json:"style"`
Placeholder string `json:"placeholder,omitempty"` Placeholder string `json:"placeholder,omitempty"`
Value string `json:"value,omitempty"` Value string `json:"value,omitempty"`
Required bool `json:"required,omitempty"` Required bool `json:"required"`
MinLength int `json:"min_length,omitempty"` MinLength int `json:"min_length,omitempty"`
MaxLength int `json:"max_length,omitempty"` MaxLength int `json:"max_length,omitempty"`
} }
@@ -222,7 +222,7 @@ func (TextInput) Type() ComponentType {
func (m TextInput) MarshalJSON() ([]byte, error) { func (m TextInput) MarshalJSON() ([]byte, error) {
type inputText TextInput type inputText TextInput
return json.Marshal(struct { return Marshal(struct {
inputText inputText
Type ComponentType `json:"type"` Type ComponentType `json:"type"`
}{ }{

View File

@@ -20,7 +20,7 @@ import (
) )
// VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/) // VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/)
const VERSION = "0.24.0" const VERSION = "0.25.0"
// New creates a new Discord session with provided token. // New creates a new Discord session with provided token.
// If the token is for a bot, it must be prefixed with "Bot " // If the token is for a bot, it must be prefixed with "Bot "
@@ -36,6 +36,7 @@ func New(token string) (s *Session, err error) {
StateEnabled: true, StateEnabled: true,
Compress: true, Compress: true,
ShouldReconnectOnError: true, ShouldReconnectOnError: true,
ShouldRetryOnRateLimit: true,
ShardID: 0, ShardID: 0,
ShardCount: 1, ShardCount: 1,
MaxRestRetries: 3, MaxRestRetries: 3,
@@ -49,7 +50,6 @@ func New(token string) (s *Session, err error) {
// These can be modified prior to calling Open() // These can be modified prior to calling Open()
s.Identify.Compress = true s.Identify.Compress = true
s.Identify.LargeThreshold = 250 s.Identify.LargeThreshold = 250
s.Identify.GuildSubscriptions = true
s.Identify.Properties.OS = runtime.GOOS s.Identify.Properties.OS = runtime.GOOS
s.Identify.Properties.Browser = "DiscordGo v" + VERSION s.Identify.Properties.Browser = "DiscordGo v" + VERSION
s.Identify.Intents = IntentsAllWithoutPrivileged s.Identify.Intents = IntentsAllWithoutPrivileged

View File

@@ -23,15 +23,16 @@ var (
EndpointSmActive = EndpointSm + "active.json" EndpointSmActive = EndpointSm + "active.json"
EndpointSmUpcoming = EndpointSm + "upcoming.json" EndpointSmUpcoming = EndpointSm + "upcoming.json"
EndpointDiscord = "https://discord.com/" EndpointDiscord = "https://discord.com/"
EndpointAPI = EndpointDiscord + "api/v" + APIVersion + "/" EndpointAPI = EndpointDiscord + "api/v" + APIVersion + "/"
EndpointGuilds = EndpointAPI + "guilds/" EndpointGuilds = EndpointAPI + "guilds/"
EndpointChannels = EndpointAPI + "channels/" EndpointChannels = EndpointAPI + "channels/"
EndpointUsers = EndpointAPI + "users/" EndpointUsers = EndpointAPI + "users/"
EndpointGateway = EndpointAPI + "gateway" EndpointGateway = EndpointAPI + "gateway"
EndpointGatewayBot = EndpointGateway + "/bot" EndpointGatewayBot = EndpointGateway + "/bot"
EndpointWebhooks = EndpointAPI + "webhooks/" EndpointWebhooks = EndpointAPI + "webhooks/"
EndpointStickers = EndpointAPI + "stickers/" EndpointStickers = EndpointAPI + "stickers/"
EndpointStageInstances = EndpointAPI + "stage-instances"
EndpointCDN = "https://cdn.discordapp.com/" EndpointCDN = "https://cdn.discordapp.com/"
EndpointCDNAttachments = EndpointCDN + "attachments/" EndpointCDNAttachments = EndpointCDN + "attachments/"
@@ -72,6 +73,7 @@ var (
EndpointGuildPreview = func(gID string) string { return EndpointGuilds + gID + "/preview" } EndpointGuildPreview = func(gID string) string { return EndpointGuilds + gID + "/preview" }
EndpointGuildChannels = func(gID string) string { return EndpointGuilds + gID + "/channels" } EndpointGuildChannels = func(gID string) string { return EndpointGuilds + gID + "/channels" }
EndpointGuildMembers = func(gID string) string { return EndpointGuilds + gID + "/members" } 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 } 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 } EndpointGuildMemberRole = func(gID, uID, rID string) string { return EndpointGuilds + gID + "/members/" + uID + "/roles/" + rID }
EndpointGuildBans = func(gID string) string { return EndpointGuilds + gID + "/bans" } EndpointGuildBans = func(gID string) string { return EndpointGuilds + gID + "/bans" }
@@ -94,6 +96,7 @@ var (
EndpointGuildBanner = func(gID, hash string) string { return EndpointCDNBanners + gID + "/" + hash + ".png" } EndpointGuildBanner = func(gID, hash string) string { return EndpointCDNBanners + gID + "/" + hash + ".png" }
EndpointGuildStickers = func(gID string) string { return EndpointGuilds + gID + "/stickers" } EndpointGuildStickers = func(gID string) string { return EndpointGuilds + gID + "/stickers" }
EndpointGuildSticker = func(gID, sID string) string { return EndpointGuilds + gID + "/stickers/" + sID } 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" } EndpointGuildScheduledEvents = func(gID string) string { return EndpointGuilds + gID + "/scheduled-events" }
EndpointGuildScheduledEvent = func(gID, eID string) string { return EndpointGuilds + gID + "/scheduled-events/" + eID } EndpointGuildScheduledEvent = func(gID, eID string) string { return EndpointGuilds + gID + "/scheduled-events/" + eID }
EndpointGuildScheduledEventUsers = func(gID, eID string) string { return EndpointGuildScheduledEvent(gID, eID) + "/users" } EndpointGuildScheduledEventUsers = func(gID, eID string) string { return EndpointGuildScheduledEvent(gID, eID) + "/users" }

View File

@@ -7,62 +7,67 @@ package discordgo
// Event type values are used to match the events returned by Discord. // Event type values are used to match the events returned by Discord.
// EventTypes surrounded by __ are synthetic and are internal to DiscordGo. // EventTypes surrounded by __ are synthetic and are internal to DiscordGo.
const ( const (
channelCreateEventType = "CHANNEL_CREATE" channelCreateEventType = "CHANNEL_CREATE"
channelDeleteEventType = "CHANNEL_DELETE" channelDeleteEventType = "CHANNEL_DELETE"
channelPinsUpdateEventType = "CHANNEL_PINS_UPDATE" channelPinsUpdateEventType = "CHANNEL_PINS_UPDATE"
channelUpdateEventType = "CHANNEL_UPDATE" channelUpdateEventType = "CHANNEL_UPDATE"
connectEventType = "__CONNECT__" connectEventType = "__CONNECT__"
disconnectEventType = "__DISCONNECT__" disconnectEventType = "__DISCONNECT__"
eventEventType = "__EVENT__" eventEventType = "__EVENT__"
guildBanAddEventType = "GUILD_BAN_ADD" guildBanAddEventType = "GUILD_BAN_ADD"
guildBanRemoveEventType = "GUILD_BAN_REMOVE" guildBanRemoveEventType = "GUILD_BAN_REMOVE"
guildCreateEventType = "GUILD_CREATE" guildCreateEventType = "GUILD_CREATE"
guildDeleteEventType = "GUILD_DELETE" guildDeleteEventType = "GUILD_DELETE"
guildEmojisUpdateEventType = "GUILD_EMOJIS_UPDATE" guildEmojisUpdateEventType = "GUILD_EMOJIS_UPDATE"
guildIntegrationsUpdateEventType = "GUILD_INTEGRATIONS_UPDATE" guildIntegrationsUpdateEventType = "GUILD_INTEGRATIONS_UPDATE"
guildMemberAddEventType = "GUILD_MEMBER_ADD" guildMemberAddEventType = "GUILD_MEMBER_ADD"
guildMemberRemoveEventType = "GUILD_MEMBER_REMOVE" guildMemberRemoveEventType = "GUILD_MEMBER_REMOVE"
guildMemberUpdateEventType = "GUILD_MEMBER_UPDATE" guildMemberUpdateEventType = "GUILD_MEMBER_UPDATE"
guildMembersChunkEventType = "GUILD_MEMBERS_CHUNK" guildMembersChunkEventType = "GUILD_MEMBERS_CHUNK"
guildRoleCreateEventType = "GUILD_ROLE_CREATE" guildRoleCreateEventType = "GUILD_ROLE_CREATE"
guildRoleDeleteEventType = "GUILD_ROLE_DELETE" guildRoleDeleteEventType = "GUILD_ROLE_DELETE"
guildRoleUpdateEventType = "GUILD_ROLE_UPDATE" guildRoleUpdateEventType = "GUILD_ROLE_UPDATE"
guildUpdateEventType = "GUILD_UPDATE" guildStageInstanceCreateEventType = "STAGE_INSTANCE_CREATE"
guildScheduledEventCreateEventType = "GUILD_SCHEDULED_EVENT_CREATE" guildStageInstanceUpdateEventType = "STAGE_INSTANCE_UPDATE"
guildScheduledEventUpdateEventType = "GUILD_SCHEDULED_EVENT_UPDATE" guildStageInstanceDeleteEventType = "STAGE_INSTANCE_DELETE"
guildScheduledEventDeleteEventType = "GUILD_SCHEDULED_EVENT_DELETE" guildScheduledEventCreateEventType = "GUILD_SCHEDULED_EVENT_CREATE"
interactionCreateEventType = "INTERACTION_CREATE" guildScheduledEventDeleteEventType = "GUILD_SCHEDULED_EVENT_DELETE"
inviteCreateEventType = "INVITE_CREATE" guildScheduledEventUpdateEventType = "GUILD_SCHEDULED_EVENT_UPDATE"
inviteDeleteEventType = "INVITE_DELETE" guildScheduledEventUserAddEventType = "GUILD_SCHEDULED_EVENT_USER_ADD"
messageAckEventType = "MESSAGE_ACK" guildScheduledEventUserRemoveEventType = "GUILD_SCHEDULED_EVENT_USER_REMOVE"
messageCreateEventType = "MESSAGE_CREATE" guildUpdateEventType = "GUILD_UPDATE"
messageDeleteEventType = "MESSAGE_DELETE" interactionCreateEventType = "INTERACTION_CREATE"
messageDeleteBulkEventType = "MESSAGE_DELETE_BULK" inviteCreateEventType = "INVITE_CREATE"
messageReactionAddEventType = "MESSAGE_REACTION_ADD" inviteDeleteEventType = "INVITE_DELETE"
messageReactionRemoveEventType = "MESSAGE_REACTION_REMOVE" messageAckEventType = "MESSAGE_ACK"
messageReactionRemoveAllEventType = "MESSAGE_REACTION_REMOVE_ALL" messageCreateEventType = "MESSAGE_CREATE"
messageUpdateEventType = "MESSAGE_UPDATE" messageDeleteEventType = "MESSAGE_DELETE"
presenceUpdateEventType = "PRESENCE_UPDATE" messageDeleteBulkEventType = "MESSAGE_DELETE_BULK"
presencesReplaceEventType = "PRESENCES_REPLACE" messageReactionAddEventType = "MESSAGE_REACTION_ADD"
rateLimitEventType = "__RATE_LIMIT__" messageReactionRemoveEventType = "MESSAGE_REACTION_REMOVE"
readyEventType = "READY" messageReactionRemoveAllEventType = "MESSAGE_REACTION_REMOVE_ALL"
relationshipAddEventType = "RELATIONSHIP_ADD" messageUpdateEventType = "MESSAGE_UPDATE"
relationshipRemoveEventType = "RELATIONSHIP_REMOVE" presenceUpdateEventType = "PRESENCE_UPDATE"
resumedEventType = "RESUMED" presencesReplaceEventType = "PRESENCES_REPLACE"
threadCreateEventType = "THREAD_CREATE" rateLimitEventType = "__RATE_LIMIT__"
threadDeleteEventType = "THREAD_DELETE" readyEventType = "READY"
threadListSyncEventType = "THREAD_LIST_SYNC" relationshipAddEventType = "RELATIONSHIP_ADD"
threadMemberUpdateEventType = "THREAD_MEMBER_UPDATE" relationshipRemoveEventType = "RELATIONSHIP_REMOVE"
threadMembersUpdateEventType = "THREAD_MEMBERS_UPDATE" resumedEventType = "RESUMED"
threadUpdateEventType = "THREAD_UPDATE" threadCreateEventType = "THREAD_CREATE"
typingStartEventType = "TYPING_START" threadDeleteEventType = "THREAD_DELETE"
userGuildSettingsUpdateEventType = "USER_GUILD_SETTINGS_UPDATE" threadListSyncEventType = "THREAD_LIST_SYNC"
userNoteUpdateEventType = "USER_NOTE_UPDATE" threadMemberUpdateEventType = "THREAD_MEMBER_UPDATE"
userSettingsUpdateEventType = "USER_SETTINGS_UPDATE" threadMembersUpdateEventType = "THREAD_MEMBERS_UPDATE"
userUpdateEventType = "USER_UPDATE" threadUpdateEventType = "THREAD_UPDATE"
voiceServerUpdateEventType = "VOICE_SERVER_UPDATE" typingStartEventType = "TYPING_START"
voiceStateUpdateEventType = "VOICE_STATE_UPDATE" userGuildSettingsUpdateEventType = "USER_GUILD_SETTINGS_UPDATE"
webhooksUpdateEventType = "WEBHOOKS_UPDATE" userNoteUpdateEventType = "USER_NOTE_UPDATE"
userSettingsUpdateEventType = "USER_SETTINGS_UPDATE"
userUpdateEventType = "USER_UPDATE"
voiceServerUpdateEventType = "VOICE_SERVER_UPDATE"
voiceStateUpdateEventType = "VOICE_STATE_UPDATE"
webhooksUpdateEventType = "WEBHOOKS_UPDATE"
) )
// channelCreateEventHandler is an event handler for ChannelCreate events. // channelCreateEventHandler is an event handler for ChannelCreate events.
@@ -310,66 +315,6 @@ func (eh guildIntegrationsUpdateEventHandler) 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)
}
}
// 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)
}
}
// 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)
}
}
// guildMemberAddEventHandler is an event handler for GuildMemberAdd events. // guildMemberAddEventHandler is an event handler for GuildMemberAdd events.
type guildMemberAddEventHandler func(*Session, *GuildMemberAdd) type guildMemberAddEventHandler func(*Session, *GuildMemberAdd)
@@ -510,6 +455,166 @@ func (eh guildRoleUpdateEventHandler) Handle(s *Session, i interface{}) {
} }
} }
// guildStageInstanceEventCreateHandler is an event handler for StageInstanceEventCreate events.
type guildStageInstanceEventCreateHandler func(*Session, *StageInstanceEventCreate)
// Type returns the event type for StageInstanceEventCreate events.
func (eh guildStageInstanceEventCreateHandler) Type() string {
return guildStageInstanceCreateEventType
}
// New returns a new instance of StageInstanceEventCreate.
func (eh guildStageInstanceEventCreateHandler) New() interface{} {
return &StageInstanceEventCreate{}
}
// Handle is the handler for StageInstanceEventCreate events.
func (eh guildStageInstanceEventCreateHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*StageInstanceEventCreate); ok {
eh(s, t)
}
}
// guildStageInstanceEventUpdateHandler is an event handler for StageInstanceEventUpdate events.
type guildStageInstanceEventUpdateHandler func(*Session, *StageInstanceEventUpdate)
// Type returns the event type for StageInstanceEventUpdate events.
func (eh guildStageInstanceEventUpdateHandler) Type() string {
return guildStageInstanceCreateEventType
}
// New returns a new instance of StageInstanceEventUpdate.
func (eh guildStageInstanceEventUpdateHandler) New() interface{} {
return &StageInstanceEventUpdate{}
}
// Handle is the handler for StageInstanceEventUpdate events.
func (eh guildStageInstanceEventUpdateHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*StageInstanceEventUpdate); ok {
eh(s, t)
}
}
// guildStageInstanceEventDeleteHandler is an event handler for StageInstanceEventDelete events.
type guildStageInstanceEventDeleteHandler func(*Session, *StageInstanceEventDelete)
// Type returns the event type for StageInstanceEventDelete events.
func (eh guildStageInstanceEventDeleteHandler) Type() string {
return guildStageInstanceCreateEventType
}
// New returns a new instance of StageInstanceEventDelete.
func (eh guildStageInstanceEventDeleteHandler) New() interface{} {
return &StageInstanceEventDelete{}
}
// Handle is the handler for StageInstanceEventDelete events.
func (eh guildStageInstanceEventDeleteHandler) Handle(s *Session, i interface{}) {
if t, ok := i.(*StageInstanceEventDelete); ok {
eh(s, t)
}
}
// 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. // guildUpdateEventHandler is an event handler for GuildUpdate events.
type guildUpdateEventHandler func(*Session, *GuildUpdate) type guildUpdateEventHandler func(*Session, *GuildUpdate)
@@ -1195,12 +1300,6 @@ func handlerForInterface(handler interface{}) EventHandler {
return guildEmojisUpdateEventHandler(v) return guildEmojisUpdateEventHandler(v)
case func(*Session, *GuildIntegrationsUpdate): case func(*Session, *GuildIntegrationsUpdate):
return guildIntegrationsUpdateEventHandler(v) return guildIntegrationsUpdateEventHandler(v)
case func(*Session, *GuildScheduledEventCreate):
return guildScheduledEventCreateEventHandler(v)
case func(*Session, *GuildScheduledEventUpdate):
return guildScheduledEventUpdateEventHandler(v)
case func(*Session, *GuildScheduledEventDelete):
return guildScheduledEventDeleteEventHandler(v)
case func(*Session, *GuildMemberAdd): case func(*Session, *GuildMemberAdd):
return guildMemberAddEventHandler(v) return guildMemberAddEventHandler(v)
case func(*Session, *GuildMemberRemove): case func(*Session, *GuildMemberRemove):
@@ -1215,6 +1314,16 @@ func handlerForInterface(handler interface{}) EventHandler {
return guildRoleDeleteEventHandler(v) return guildRoleDeleteEventHandler(v)
case func(*Session, *GuildRoleUpdate): case func(*Session, *GuildRoleUpdate):
return guildRoleUpdateEventHandler(v) 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): case func(*Session, *GuildUpdate):
return guildUpdateEventHandler(v) return guildUpdateEventHandler(v)
case func(*Session, *InteractionCreate): case func(*Session, *InteractionCreate):
@@ -1297,9 +1406,6 @@ func init() {
registerInterfaceProvider(guildDeleteEventHandler(nil)) registerInterfaceProvider(guildDeleteEventHandler(nil))
registerInterfaceProvider(guildEmojisUpdateEventHandler(nil)) registerInterfaceProvider(guildEmojisUpdateEventHandler(nil))
registerInterfaceProvider(guildIntegrationsUpdateEventHandler(nil)) registerInterfaceProvider(guildIntegrationsUpdateEventHandler(nil))
registerInterfaceProvider(guildScheduledEventCreateEventHandler(nil))
registerInterfaceProvider(guildScheduledEventUpdateEventHandler(nil))
registerInterfaceProvider(guildScheduledEventDeleteEventHandler(nil))
registerInterfaceProvider(guildMemberAddEventHandler(nil)) registerInterfaceProvider(guildMemberAddEventHandler(nil))
registerInterfaceProvider(guildMemberRemoveEventHandler(nil)) registerInterfaceProvider(guildMemberRemoveEventHandler(nil))
registerInterfaceProvider(guildMemberUpdateEventHandler(nil)) registerInterfaceProvider(guildMemberUpdateEventHandler(nil))
@@ -1307,6 +1413,11 @@ func init() {
registerInterfaceProvider(guildRoleCreateEventHandler(nil)) registerInterfaceProvider(guildRoleCreateEventHandler(nil))
registerInterfaceProvider(guildRoleDeleteEventHandler(nil)) registerInterfaceProvider(guildRoleDeleteEventHandler(nil))
registerInterfaceProvider(guildRoleUpdateEventHandler(nil)) registerInterfaceProvider(guildRoleUpdateEventHandler(nil))
registerInterfaceProvider(guildScheduledEventCreateEventHandler(nil))
registerInterfaceProvider(guildScheduledEventDeleteEventHandler(nil))
registerInterfaceProvider(guildScheduledEventUpdateEventHandler(nil))
registerInterfaceProvider(guildScheduledEventUserAddEventHandler(nil))
registerInterfaceProvider(guildScheduledEventUserRemoveEventHandler(nil))
registerInterfaceProvider(guildUpdateEventHandler(nil)) registerInterfaceProvider(guildUpdateEventHandler(nil))
registerInterfaceProvider(interactionCreateEventHandler(nil)) registerInterfaceProvider(interactionCreateEventHandler(nil))
registerInterfaceProvider(inviteCreateEventHandler(nil)) registerInterfaceProvider(inviteCreateEventHandler(nil))

View File

@@ -191,7 +191,9 @@ type GuildMembersChunk struct {
Members []*Member `json:"members"` Members []*Member `json:"members"`
ChunkIndex int `json:"chunk_index"` ChunkIndex int `json:"chunk_index"`
ChunkCount int `json:"chunk_count"` ChunkCount int `json:"chunk_count"`
NotFound []string `json:"not_found,omitempty"`
Presences []*Presence `json:"presences,omitempty"` Presences []*Presence `json:"presences,omitempty"`
Nonce string `json:"nonce,omitempty"`
} }
// GuildIntegrationsUpdate is the data for a GuildIntegrationsUpdate event. // GuildIntegrationsUpdate is the data for a GuildIntegrationsUpdate event.
@@ -199,6 +201,21 @@ type GuildIntegrationsUpdate struct {
GuildID string `json:"guild_id"` GuildID string `json:"guild_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. // GuildScheduledEventCreate is the data for a GuildScheduledEventCreate event.
type GuildScheduledEventCreate struct { type GuildScheduledEventCreate struct {
*GuildScheduledEvent *GuildScheduledEvent
@@ -214,6 +231,20 @@ type GuildScheduledEventDelete struct {
*GuildScheduledEvent *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"`
}
// MessageAck is the data for a MessageAck event. // MessageAck is the data for a MessageAck event.
type MessageAck struct { type MessageAck struct {
MessageID string `json:"message_id"` MessageID string `json:"message_id"`

View File

@@ -35,12 +35,14 @@ type ApplicationCommand struct {
Version string `json:"version,omitempty"` Version string `json:"version,omitempty"`
Type ApplicationCommandType `json:"type,omitempty"` Type ApplicationCommandType `json:"type,omitempty"`
Name string `json:"name"` Name string `json:"name"`
NameLocalizations *map[Locale]string `json:"name_localizations,omitempty"`
DefaultPermission *bool `json:"default_permission,omitempty"` DefaultPermission *bool `json:"default_permission,omitempty"`
// NOTE: Chat commands only. Otherwise it mustn't be set. // NOTE: Chat commands only. Otherwise it mustn't be set.
Description string `json:"description,omitempty"` Description string `json:"description,omitempty"`
Options []*ApplicationCommandOption `json:"options"` DescriptionLocalizations *map[Locale]string `json:"description_localizations,omitempty"`
Options []*ApplicationCommandOption `json:"options"`
} }
// ApplicationCommandOptionType indicates the type of a slash command's option. // ApplicationCommandOptionType indicates the type of a slash command's option.
@@ -91,9 +93,11 @@ func (t ApplicationCommandOptionType) String() string {
// ApplicationCommandOption represents an option/subcommand/subcommands group. // ApplicationCommandOption represents an option/subcommand/subcommands group.
type ApplicationCommandOption struct { type ApplicationCommandOption struct {
Type ApplicationCommandOptionType `json:"type"` Type ApplicationCommandOptionType `json:"type"`
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description,omitempty"` 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. // 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. // So I commented it, until it will be officially on the docs.
// Default bool `json:"default"` // Default bool `json:"default"`
@@ -113,8 +117,9 @@ type ApplicationCommandOption struct {
// ApplicationCommandOptionChoice represents a slash command option choice. // ApplicationCommandOptionChoice represents a slash command option choice.
type ApplicationCommandOptionChoice struct { type ApplicationCommandOptionChoice struct {
Name string `json:"name"` Name string `json:"name"`
Value interface{} `json:"value"` NameLocalizations map[Locale]string `json:"name_localizations,omitempty"`
Value interface{} `json:"value"`
} }
// ApplicationCommandPermissions represents a single user or role permission for a command. // ApplicationCommandPermissions represents a single user or role permission for a command.
@@ -175,6 +180,7 @@ func (t InteractionType) String() string {
// Interaction represents data of an interaction. // Interaction represents data of an interaction.
type Interaction struct { type Interaction struct {
ID string `json:"id"` ID string `json:"id"`
AppID string `json:"application_id"`
Type InteractionType `json:"type"` Type InteractionType `json:"type"`
Data InteractionData `json:"data"` Data InteractionData `json:"data"`
GuildID string `json:"guild_id"` GuildID string `json:"guild_id"`
@@ -509,7 +515,7 @@ type InteractionResponseData struct {
TTS bool `json:"tts"` TTS bool `json:"tts"`
Content string `json:"content"` Content string `json:"content"`
Components []MessageComponent `json:"components"` Components []MessageComponent `json:"components"`
Embeds []*MessageEmbed `json:"embeds,omitempty"` Embeds []*MessageEmbed `json:"embeds"`
AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"` AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"`
Flags uint64 `json:"flags,omitempty"` Flags uint64 `json:"flags,omitempty"`
Files []*File `json:"-"` Files []*File `json:"-"`

View File

@@ -199,7 +199,9 @@ const (
MessageFlagsCrossPosted MessageFlags = 1 << 0 MessageFlagsCrossPosted MessageFlags = 1 << 0
// MessageFlagsIsCrossPosted this message originated from a message in another channel (via Channel Following). // MessageFlagsIsCrossPosted this message originated from a message in another channel (via Channel Following).
MessageFlagsIsCrossPosted MessageFlags = 1 << 1 MessageFlagsIsCrossPosted MessageFlags = 1 << 1
// MessageFlagsSupressEmbeds do not include any embeds when serializing this message. // MessageFlagsSuppressEmbeds do not include any embeds when serializing this message.
MessageFlagsSuppressEmbeds MessageFlags = 1 << 2
// TODO: deprecated, remove when compatibility is not needed
MessageFlagsSupressEmbeds MessageFlags = 1 << 2 MessageFlagsSupressEmbeds MessageFlags = 1 << 2
// MessageFlagsSourceMessageDeleted the source message for this crosspost has been deleted (via Channel Following). // MessageFlagsSourceMessageDeleted the source message for this crosspost has been deleted (via Channel Following).
MessageFlagsSourceMessageDeleted MessageFlags = 1 << 3 MessageFlagsSourceMessageDeleted MessageFlags = 1 << 3
@@ -225,7 +227,7 @@ type File struct {
// MessageSend stores all parameters you can send with ChannelMessageSendComplex. // MessageSend stores all parameters you can send with ChannelMessageSendComplex.
type MessageSend struct { type MessageSend struct {
Content string `json:"content,omitempty"` Content string `json:"content,omitempty"`
Embeds []*MessageEmbed `json:"embeds,omitempty"` Embeds []*MessageEmbed `json:"embeds"`
TTS bool `json:"tts"` TTS bool `json:"tts"`
Components []MessageComponent `json:"components"` Components []MessageComponent `json:"components"`
Files []*File `json:"-"` Files []*File `json:"-"`
@@ -244,8 +246,9 @@ type MessageSend struct {
type MessageEdit struct { type MessageEdit struct {
Content *string `json:"content,omitempty"` Content *string `json:"content,omitempty"`
Components []MessageComponent `json:"components"` Components []MessageComponent `json:"components"`
Embeds []*MessageEmbed `json:"embeds,omitempty"` Embeds []*MessageEmbed `json:"embeds"`
AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"` AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"`
Flags MessageFlags `json:"flags,omitempty"`
ID string ID string
Channel string Channel string
@@ -342,7 +345,7 @@ type MessageEmbedFooter struct {
// MessageEmbedImage is a part of a MessageEmbed struct. // MessageEmbedImage is a part of a MessageEmbed struct.
type MessageEmbedImage struct { type MessageEmbedImage struct {
URL string `json:"url,omitempty"` URL string `json:"url"`
ProxyURL string `json:"proxy_url,omitempty"` ProxyURL string `json:"proxy_url,omitempty"`
Width int `json:"width,omitempty"` Width int `json:"width,omitempty"`
Height int `json:"height,omitempty"` Height int `json:"height,omitempty"`
@@ -350,7 +353,7 @@ type MessageEmbedImage struct {
// MessageEmbedThumbnail is a part of a MessageEmbed struct. // MessageEmbedThumbnail is a part of a MessageEmbed struct.
type MessageEmbedThumbnail struct { type MessageEmbedThumbnail struct {
URL string `json:"url,omitempty"` URL string `json:"url"`
ProxyURL string `json:"proxy_url,omitempty"` ProxyURL string `json:"proxy_url,omitempty"`
Width int `json:"width,omitempty"` Width int `json:"width,omitempty"`
Height int `json:"height,omitempty"` Height int `json:"height,omitempty"`
@@ -372,7 +375,7 @@ type MessageEmbedProvider struct {
// MessageEmbedAuthor is a part of a MessageEmbed struct. // MessageEmbedAuthor is a part of a MessageEmbed struct.
type MessageEmbedAuthor struct { type MessageEmbedAuthor struct {
URL string `json:"url,omitempty"` URL string `json:"url,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name"`
IconURL string `json:"icon_url,omitempty"` IconURL string `json:"icon_url,omitempty"`
ProxyIconURL string `json:"proxy_icon_url,omitempty"` ProxyIconURL string `json:"proxy_icon_url,omitempty"`
} }

View File

@@ -39,6 +39,59 @@ var (
ErrUnauthorized = errors.New("HTTP request was unauthorized. This could be because the provided token was not a bot token. Please add \"Bot \" to the start of your token. https://discord.com/developers/docs/reference#authentication-example-bot-token-authorization-header") ErrUnauthorized = errors.New("HTTP request was unauthorized. This could be because the provided token was not a bot token. Please add \"Bot \" to the start of your token. https://discord.com/developers/docs/reference#authentication-example-bot-token-authorization-header")
) )
var (
// Marshal defines function used to encode JSON payloads
Marshal func(v interface{}) ([]byte, error) = json.Marshal
// Unmarshal defines function used to decode JSON payloads
Unmarshal func(src []byte, v interface{}) error = json.Unmarshal
)
// RESTError stores error information about a request with a bad response code.
// Message is not always present, there are cases where api calls can fail
// without returning a json message.
type RESTError struct {
Request *http.Request
Response *http.Response
ResponseBody []byte
Message *APIErrorMessage // Message may be nil.
}
// newRestError returns a new REST API error.
func newRestError(req *http.Request, resp *http.Response, body []byte) *RESTError {
restErr := &RESTError{
Request: req,
Response: resp,
ResponseBody: body,
}
// Attempt to decode the error and assume no message was provided if it fails
var msg *APIErrorMessage
err := Unmarshal(body, &msg)
if err == nil {
restErr.Message = msg
}
return restErr
}
// Error returns a Rest API Error with its status code and body.
func (r RESTError) Error() string {
return "HTTP " + r.Response.Status + ", " + string(r.ResponseBody)
}
// RateLimitError is returned when a request exceeds a rate limit
// and ShouldRetryOnRateLimit is false. The request may be manually
// retried after waiting the duration specified by RetryAfter.
type RateLimitError struct {
*RateLimit
}
// Error returns a rate limit error with rate limited endpoint and retry time.
func (e RateLimitError) Error() string {
return "Rate limit exceeded on " + e.URL + ", retry after " + e.RetryAfter.String()
}
// Request is the same as RequestWithBucketID but the bucket id is the same as the urlStr // Request is the same as RequestWithBucketID but the bucket id is the same as the urlStr
func (s *Session) Request(method, urlStr string, data interface{}) (response []byte, err error) { func (s *Session) Request(method, urlStr string, data interface{}) (response []byte, err error) {
return s.RequestWithBucketID(method, urlStr, data, strings.SplitN(urlStr, "?", 2)[0]) return s.RequestWithBucketID(method, urlStr, data, strings.SplitN(urlStr, "?", 2)[0])
@@ -48,7 +101,7 @@ func (s *Session) Request(method, urlStr string, data interface{}) (response []b
func (s *Session) RequestWithBucketID(method, urlStr string, data interface{}, bucketID string) (response []byte, err error) { func (s *Session) RequestWithBucketID(method, urlStr string, data interface{}, bucketID string) (response []byte, err error) {
var body []byte var body []byte
if data != nil { if data != nil {
body, err = json.Marshal(data) body, err = Marshal(data)
if err != nil { if err != nil {
return return
} }
@@ -108,7 +161,7 @@ func (s *Session) RequestWithLockedBucket(method, urlStr, contentType string, b
} }
defer func() { defer func() {
err2 := resp.Body.Close() err2 := resp.Body.Close()
if err2 != nil { if s.Debug && err2 != nil {
log.Println("error closing resp body") log.Println("error closing resp body")
} }
}() }()
@@ -147,19 +200,24 @@ func (s *Session) RequestWithLockedBucket(method, urlStr, contentType string, b
} }
case 429: // TOO MANY REQUESTS - Rate limiting case 429: // TOO MANY REQUESTS - Rate limiting
rl := TooManyRequests{} rl := TooManyRequests{}
err = json.Unmarshal(response, &rl) err = Unmarshal(response, &rl)
if err != nil { if err != nil {
s.log(LogError, "rate limit unmarshal error, %s", err) s.log(LogError, "rate limit unmarshal error, %s", err)
return return
} }
s.log(LogInformational, "Rate Limiting %s, retry in %v", urlStr, rl.RetryAfter)
s.handleEvent(rateLimitEventType, &RateLimit{TooManyRequests: &rl, URL: urlStr})
time.Sleep(rl.RetryAfter) if s.ShouldRetryOnRateLimit {
// we can make the above smarter s.log(LogInformational, "Rate Limiting %s, retry in %v", urlStr, rl.RetryAfter)
// this method can cause longer delays than required s.handleEvent(rateLimitEventType, &RateLimit{TooManyRequests: &rl, URL: urlStr})
response, err = s.RequestWithLockedBucket(method, urlStr, contentType, b, s.Ratelimiter.LockBucketObject(bucket), sequence) time.Sleep(rl.RetryAfter)
// we can make the above smarter
// this method can cause longer delays than required
response, err = s.RequestWithLockedBucket(method, urlStr, contentType, b, s.Ratelimiter.LockBucketObject(bucket), sequence)
} else {
err = &RateLimitError{&RateLimit{TooManyRequests: &rl, URL: urlStr}}
}
case http.StatusUnauthorized: case http.StatusUnauthorized:
if strings.Index(s.Token, "Bot ") != 0 { if strings.Index(s.Token, "Bot ") != 0 {
s.log(LogInformational, ErrUnauthorized.Error()) s.log(LogInformational, ErrUnauthorized.Error())
@@ -174,7 +232,7 @@ func (s *Session) RequestWithLockedBucket(method, urlStr, contentType string, b
} }
func unmarshal(data []byte, v interface{}) error { func unmarshal(data []byte, v interface{}) error {
err := json.Unmarshal(data, v) err := Unmarshal(data, v)
if err != nil { if err != nil {
return fmt.Errorf("%w: %s", ErrJSONUnmarshal, err) return fmt.Errorf("%w: %s", ErrJSONUnmarshal, err)
} }
@@ -438,6 +496,19 @@ func (s *Session) Guild(guildID string) (st *Guild, err error) {
return return
} }
// GuildWithCounts returns a Guild structure of a specific Guild with approximate member and presence counts.
// guildID : The ID of a Guild
func (s *Session) GuildWithCounts(guildID string) (st *Guild, err error) {
body, err := s.RequestWithBucketID("GET", EndpointGuild(guildID)+"?with_counts=true", nil, EndpointGuild(guildID))
if err != nil {
return
}
err = unmarshal(body, &st)
return
}
// GuildPreview returns a GuildPreview structure of a specific public Guild. // GuildPreview returns a GuildPreview structure of a specific public Guild.
// guildID : The ID of a Guild // guildID : The ID of a Guild
func (s *Session) GuildPreview(guildID string) (st *GuildPreview, err error) { func (s *Session) GuildPreview(guildID string) (st *GuildPreview, err error) {
@@ -481,7 +552,7 @@ func (s *Session) GuildEdit(guildID string, g GuildParams) (st *Guild, err error
} }
} }
//Bounds checking for regions // Bounds checking for regions
if g.Region != "" { if g.Region != "" {
isValid := false isValid := false
regions, _ := s.VoiceRegions() regions, _ := s.VoiceRegions()
@@ -530,12 +601,30 @@ func (s *Session) GuildLeave(guildID string) (err error) {
return return
} }
// GuildBans returns an array of GuildBan structures for all bans of a // GuildBans returns an array of GuildBan structures for bans in the given guild.
// given guild. // guildID : The ID of a Guild
// guildID : The ID of a Guild. // limit : Max number of bans to return (max 1000)
func (s *Session) GuildBans(guildID string) (st []*GuildBan, err error) { // beforeID : If not empty all returned users will be after the given id
// afterID : If not empty all returned users will be before the given id
func (s *Session) GuildBans(guildID string, limit int, beforeID, afterID string) (st []*GuildBan, err error) {
uri := EndpointGuildBans(guildID)
body, err := s.RequestWithBucketID("GET", EndpointGuildBans(guildID), nil, EndpointGuildBans(guildID)) v := url.Values{}
if limit != 0 {
v.Set("limit", strconv.Itoa(limit))
}
if beforeID != "" {
v.Set("before", beforeID)
}
if afterID != "" {
v.Set("after", afterID)
}
if len(v) > 0 {
uri += "?" + v.Encode()
}
body, err := s.RequestWithBucketID("GET", uri, nil, EndpointGuildBans(guildID))
if err != nil { if err != nil {
return return
} }
@@ -631,6 +720,29 @@ func (s *Session) GuildMembers(guildID string, after string, limit int) (st []*M
return return
} }
// GuildMembersSearch returns a list of guild member objects whose username or nickname starts with a provided string
// guildID : The ID of a Guild
// query : Query string to match username(s) and nickname(s) against
// limit : Max number of members to return (default 1, min 1, max 1000)
func (s *Session) GuildMembersSearch(guildID, query string, limit int) (st []*Member, err error) {
uri := EndpointGuildMembersSearch(guildID)
queryParams := url.Values{}
queryParams.Set("query", query)
if limit > 1 {
queryParams.Set("limit", strconv.Itoa(limit))
}
body, err := s.RequestWithBucketID("GET", uri+"?"+queryParams.Encode(), nil, uri)
if err != nil {
return
}
err = unmarshal(body, &st)
return
}
// GuildMember returns a member of a guild. // GuildMember returns a member of a guild.
// guildID : The ID of a Guild. // guildID : The ID of a Guild.
// userID : The ID of a User // userID : The ID of a User
@@ -710,6 +822,21 @@ func (s *Session) GuildMemberEdit(guildID, userID string, roles []string) (err e
return return
} }
// GuildMemberEditComplex edits the nickname and roles of a member.
// guildID : The ID of a Guild.
// userID : The ID of a User.
// data : A GuildMemberEditData struct with the new nickname and roles
func (s *Session) GuildMemberEditComplex(guildID, userID string, data GuildMemberParams) (st *Member, err error) {
var body []byte
body, err = s.RequestWithBucketID("PATCH", EndpointGuildMember(guildID, userID), data, EndpointGuildMember(guildID, ""))
if err != nil {
return nil, err
}
err = unmarshal(body, &st)
return
}
// GuildMemberMove moves a guild member from one voice channel to another/none // GuildMemberMove moves a guild member from one voice channel to another/none
// guildID : The ID of a Guild. // guildID : The ID of a Guild.
// userID : The ID of a User. // userID : The ID of a User.
@@ -1218,6 +1345,20 @@ func (s *Session) GuildEmojis(guildID string) (emoji []*Emoji, err error) {
return return
} }
// GuildEmoji returns specified emoji.
// guildID : The ID of a Guild
// emojiID : The ID of an Emoji to retrieve
func (s *Session) GuildEmoji(guildID, emojiID string) (emoji *Emoji, err error) {
var body []byte
body, err = s.RequestWithBucketID("GET", EndpointGuildEmoji(guildID, emojiID), nil, EndpointGuildEmoji(guildID, emojiID))
if err != nil {
return
}
err = unmarshal(body, &emoji)
return
}
// GuildEmojiCreate creates a new emoji // GuildEmojiCreate creates a new emoji
// guildID : The ID of a Guild. // guildID : The ID of a Guild.
// name : The Name of the Emoji. // name : The Name of the Emoji.
@@ -1244,12 +1385,12 @@ func (s *Session) GuildEmojiCreate(guildID, name, image string, roles []string)
// guildID : The ID of a Guild. // guildID : The ID of a Guild.
// emojiID : The ID of an Emoji. // emojiID : The ID of an Emoji.
// name : The Name of the Emoji. // name : The Name of the Emoji.
// roles : The roles for which this emoji will be whitelisted, can be nil. // roles : The roles for which this emoji will be whitelisted, if nil or empty the roles will be reset.
func (s *Session) GuildEmojiEdit(guildID, emojiID, name string, roles []string) (emoji *Emoji, err error) { func (s *Session) GuildEmojiEdit(guildID, emojiID, name string, roles []string) (emoji *Emoji, err error) {
data := struct { data := struct {
Name string `json:"name"` Name string `json:"name"`
Roles []string `json:"roles,omitempty"` Roles []string `json:"roles"`
}{name, roles} }{name, roles}
body, err := s.RequestWithBucketID("PATCH", EndpointGuildEmoji(guildID, emojiID), data, EndpointGuildEmojis(guildID)) body, err := s.RequestWithBucketID("PATCH", EndpointGuildEmoji(guildID, emojiID), data, EndpointGuildEmojis(guildID))
@@ -1851,6 +1992,37 @@ func (s *Session) InviteWithCounts(inviteID string) (st *Invite, err error) {
return return
} }
// InviteComplex returns an Invite structure of the given invite including specified fields.
// inviteID : The invite code
// guildScheduledEventID : If specified, includes specified guild scheduled event.
// withCounts : Whether to include approximate member counts or not
// withExpiration : Whether to include expiration time or not
func (s *Session) InviteComplex(inviteID, guildScheduledEventID string, withCounts, withExpiration bool) (st *Invite, err error) {
endpoint := EndpointInvite(inviteID)
v := url.Values{}
if guildScheduledEventID != "" {
v.Set("guild_scheduled_event_id", guildScheduledEventID)
}
if withCounts {
v.Set("with_counts", "true")
}
if withExpiration {
v.Set("with_expiration", "true")
}
if len(v) != 0 {
endpoint += "?" + v.Encode()
}
body, err := s.RequestWithBucketID("GET", endpoint, nil, EndpointInvite(""))
if err != nil {
return
}
err = unmarshal(body, &st)
return
}
// InviteDelete deletes an existing invite // InviteDelete deletes an existing invite
// inviteID : the code of an invite // inviteID : the code of an invite
func (s *Session) InviteDelete(inviteID string) (st *Invite, err error) { func (s *Session) InviteDelete(inviteID string) (st *Invite, err error) {
@@ -2158,7 +2330,7 @@ func (s *Session) WebhookMessage(webhookID, token, messageID string) (message *M
return return
} }
err = json.Unmarshal(body, &message) err = Unmarshal(body, &message)
return return
} }
@@ -2207,7 +2379,7 @@ func (s *Session) WebhookMessageDelete(webhookID, token, messageID string) (err
// MessageReactionAdd creates an emoji reaction to a message. // MessageReactionAdd creates an emoji reaction to a message.
// channelID : The channel ID. // channelID : The channel ID.
// messageID : The message ID. // messageID : The message ID.
// emojiID : Either the unicode emoji for the reaction, or a guild emoji identifier. // emojiID : Either the unicode emoji for the reaction, or a guild emoji identifier in name:id format (e.g. "hello:1234567654321")
func (s *Session) MessageReactionAdd(channelID, messageID, emojiID string) error { func (s *Session) MessageReactionAdd(channelID, messageID, emojiID string) error {
// emoji such as #⃣ need to have # escaped // emoji such as #⃣ need to have # escaped
@@ -2687,10 +2859,9 @@ func (s *Session) ApplicationCommandPermissionsBatchEdit(appID, guildID string,
} }
// InteractionRespond creates the response to an interaction. // InteractionRespond creates the response to an interaction.
// appID : The application ID.
// interaction : Interaction instance. // interaction : Interaction instance.
// resp : Response message data. // resp : Response message data.
func (s *Session) InteractionRespond(interaction *Interaction, resp *InteractionResponse) (err error) { func (s *Session) InteractionRespond(interaction *Interaction, resp *InteractionResponse) error {
endpoint := EndpointInteractionResponse(interaction.ID, interaction.Token) endpoint := EndpointInteractionResponse(interaction.ID, interaction.Token)
if resp.Data != nil && len(resp.Data.Files) > 0 { if resp.Data != nil && len(resp.Data.Files) > 0 {
@@ -2700,32 +2871,30 @@ func (s *Session) InteractionRespond(interaction *Interaction, resp *Interaction
} }
_, err = s.request("POST", endpoint, contentType, body, endpoint, 0) _, err = s.request("POST", endpoint, contentType, body, endpoint, 0)
} else { return err
_, err = s.RequestWithBucketID("POST", endpoint, *resp, endpoint)
} }
_, err := s.RequestWithBucketID("POST", endpoint, *resp, endpoint)
return err return err
} }
// InteractionResponse gets the response to an interaction. // InteractionResponse gets the response to an interaction.
// appID : The application ID.
// interaction : Interaction instance. // interaction : Interaction instance.
func (s *Session) InteractionResponse(appID string, interaction *Interaction) (*Message, error) { func (s *Session) InteractionResponse(interaction *Interaction) (*Message, error) {
return s.WebhookMessage(appID, interaction.Token, "@original") return s.WebhookMessage(interaction.AppID, interaction.Token, "@original")
} }
// InteractionResponseEdit edits the response to an interaction. // InteractionResponseEdit edits the response to an interaction.
// appID : The application ID.
// interaction : Interaction instance. // interaction : Interaction instance.
// newresp : Updated response message data. // newresp : Updated response message data.
func (s *Session) InteractionResponseEdit(appID string, interaction *Interaction, newresp *WebhookEdit) (*Message, error) { func (s *Session) InteractionResponseEdit(interaction *Interaction, newresp *WebhookEdit) (*Message, error) {
return s.WebhookMessageEdit(appID, interaction.Token, "@original", newresp) return s.WebhookMessageEdit(interaction.AppID, interaction.Token, "@original", newresp)
} }
// InteractionResponseDelete deletes the response to an interaction. // InteractionResponseDelete deletes the response to an interaction.
// appID : The application ID.
// interaction : Interaction instance. // interaction : Interaction instance.
func (s *Session) InteractionResponseDelete(appID string, interaction *Interaction) error { func (s *Session) InteractionResponseDelete(interaction *Interaction) error {
endpoint := EndpointInteractionResponseActions(appID, interaction.Token) endpoint := EndpointInteractionResponseActions(interaction.AppID, interaction.Token)
_, err := s.RequestWithBucketID("DELETE", endpoint, nil, endpoint) _, err := s.RequestWithBucketID("DELETE", endpoint, nil, endpoint)
@@ -2733,29 +2902,76 @@ func (s *Session) InteractionResponseDelete(appID string, interaction *Interacti
} }
// FollowupMessageCreate creates the followup message for an interaction. // FollowupMessageCreate creates the followup message for an interaction.
// appID : The application ID.
// interaction : Interaction instance. // interaction : Interaction instance.
// wait : Waits for server confirmation of message send and ensures that the return struct is populated (it is nil otherwise) // wait : Waits for server confirmation of message send and ensures that the return struct is populated (it is nil otherwise)
// data : Data of the message to send. // data : Data of the message to send.
func (s *Session) FollowupMessageCreate(appID string, interaction *Interaction, wait bool, data *WebhookParams) (*Message, error) { func (s *Session) FollowupMessageCreate(interaction *Interaction, wait bool, data *WebhookParams) (*Message, error) {
return s.WebhookExecute(appID, interaction.Token, wait, data) return s.WebhookExecute(interaction.AppID, interaction.Token, wait, data)
} }
// FollowupMessageEdit edits a followup message of an interaction. // FollowupMessageEdit edits a followup message of an interaction.
// appID : The application ID.
// interaction : Interaction instance. // interaction : Interaction instance.
// messageID : The followup message ID. // messageID : The followup message ID.
// data : Data to update the message // data : Data to update the message
func (s *Session) FollowupMessageEdit(appID string, interaction *Interaction, messageID string, data *WebhookEdit) (*Message, error) { func (s *Session) FollowupMessageEdit(interaction *Interaction, messageID string, data *WebhookEdit) (*Message, error) {
return s.WebhookMessageEdit(appID, interaction.Token, messageID, data) return s.WebhookMessageEdit(interaction.AppID, interaction.Token, messageID, data)
} }
// FollowupMessageDelete deletes a followup message of an interaction. // FollowupMessageDelete deletes a followup message of an interaction.
// appID : The application ID.
// interaction : Interaction instance. // interaction : Interaction instance.
// messageID : The followup message ID. // messageID : The followup message ID.
func (s *Session) FollowupMessageDelete(appID string, interaction *Interaction, messageID string) error { func (s *Session) FollowupMessageDelete(interaction *Interaction, messageID string) error {
return s.WebhookMessageDelete(appID, interaction.Token, messageID) return s.WebhookMessageDelete(interaction.AppID, interaction.Token, messageID)
}
// ------------------------------------------------------------------------------------------------
// Functions specific to stage instances
// ------------------------------------------------------------------------------------------------
// StageInstanceCreate creates and returns a new Stage instance associated to a Stage channel.
// data : Parameters needed to create a stage instance.
// data : The data of the Stage instance to create
func (s *Session) StageInstanceCreate(data *StageInstanceParams) (si *StageInstance, err error) {
body, err := s.RequestWithBucketID("POST", EndpointStageInstances, data, EndpointStageInstances)
if err != nil {
return
}
err = unmarshal(body, &si)
return
}
// StageInstance will retrieve a Stage instance by ID of the Stage channel.
// channelID : The ID of the Stage channel
func (s *Session) StageInstance(channelID string) (si *StageInstance, err error) {
body, err := s.RequestWithBucketID("GET", EndpointStageInstance(channelID), nil, EndpointStageInstance(channelID))
if err != nil {
return
}
err = unmarshal(body, &si)
return
}
// StageInstanceEdit will edit a Stage instance by ID of the Stage channel.
// channelID : The ID of the Stage channel
// data : The data to edit the Stage instance
func (s *Session) StageInstanceEdit(channelID string, data *StageInstanceParams) (si *StageInstance, err error) {
body, err := s.RequestWithBucketID("PATCH", EndpointStageInstance(channelID), data, EndpointStageInstance(channelID))
if err != nil {
return
}
err = unmarshal(body, &si)
return
}
// StageInstanceDelete will delete a Stage instance by ID of the Stage channel.
// channelID : The ID of the Stage channel
func (s *Session) StageInstanceDelete(channelID string) (err error) {
_, err = s.RequestWithBucketID("DELETE", EndpointStageInstance(channelID), nil, EndpointStageInstance(channelID))
return
} }
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------

View File

@@ -979,8 +979,9 @@ func (s *State) OnInterface(se *Session, i interface{}) (err error) {
err = s.GuildRemove(t.Guild) err = s.GuildRemove(t.Guild)
case *GuildMemberAdd: case *GuildMemberAdd:
var guild *Guild
// Updates the MemberCount of the guild. // Updates the MemberCount of the guild.
guild, err := s.Guild(t.Member.GuildID) guild, err = s.Guild(t.Member.GuildID)
if err != nil { if err != nil {
return err return err
} }
@@ -995,8 +996,9 @@ func (s *State) OnInterface(se *Session, i interface{}) (err error) {
err = s.MemberAdd(t.Member) err = s.MemberAdd(t.Member)
} }
case *GuildMemberRemove: case *GuildMemberRemove:
var guild *Guild
// Updates the MemberCount of the guild. // Updates the MemberCount of the guild.
guild, err := s.Guild(t.Member.GuildID) guild, err = s.Guild(t.Member.GuildID)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -43,6 +43,9 @@ type Session struct {
// Should the session reconnect the websocket on errors. // Should the session reconnect the websocket on errors.
ShouldReconnectOnError bool ShouldReconnectOnError bool
// Should the session retry requests when rate limited.
ShouldRetryOnRateLimit bool
// Identify is sent during initial handshake with the discord gateway. // Identify is sent during initial handshake with the discord gateway.
// https://discord.com/developers/docs/topics/gateway#identify // https://discord.com/developers/docs/topics/gateway#identify
Identify Identify Identify Identify
@@ -260,6 +263,7 @@ const (
ChannelTypeGuildNewsThread ChannelType = 10 ChannelTypeGuildNewsThread ChannelType = 10
ChannelTypeGuildPublicThread ChannelType = 11 ChannelTypeGuildPublicThread ChannelType = 11
ChannelTypeGuildPrivateThread ChannelType = 12 ChannelTypeGuildPrivateThread ChannelType = 12
ChannelTypeGuildStageVoice ChannelType = 13
) )
// A Channel holds all data related to an individual Discord channel. // A Channel holds all data related to an individual Discord channel.
@@ -360,7 +364,7 @@ type ChannelEdit struct {
UserLimit int `json:"user_limit,omitempty"` UserLimit int `json:"user_limit,omitempty"`
PermissionOverwrites []*PermissionOverwrite `json:"permission_overwrites,omitempty"` PermissionOverwrites []*PermissionOverwrite `json:"permission_overwrites,omitempty"`
ParentID string `json:"parent_id,omitempty"` ParentID string `json:"parent_id,omitempty"`
RateLimitPerUser int `json:"rate_limit_per_user,omitempty"` RateLimitPerUser *int `json:"rate_limit_per_user,omitempty"`
// NOTE: threads only // NOTE: threads only
@@ -552,6 +556,17 @@ const (
ExplicitContentFilterAllMembers ExplicitContentFilterLevel = 2 ExplicitContentFilterAllMembers ExplicitContentFilterLevel = 2
) )
// GuildNSFWLevel type definition
type GuildNSFWLevel int
// Constants for GuildNSFWLevel levels from 0 to 3 inclusive
const (
GuildNSFWLevelDefault GuildNSFWLevel = 0
GuildNSFWLevelExplicit GuildNSFWLevel = 1
GuildNSFWLevelSafe GuildNSFWLevel = 2
GuildNSFWLevelAgeRestricted GuildNSFWLevel = 3
)
// MfaLevel type definition // MfaLevel type definition
type MfaLevel int type MfaLevel int
@@ -675,6 +690,9 @@ type Guild struct {
// The explicit content filter level // The explicit content filter level
ExplicitContentFilter ExplicitContentFilterLevel `json:"explicit_content_filter"` ExplicitContentFilter ExplicitContentFilterLevel `json:"explicit_content_filter"`
// The NSFW Level of the guild
NSFWLevel GuildNSFWLevel `json:"nsfw_level"`
// The list of enabled guild features // The list of enabled guild features
Features []string `json:"features"` Features []string `json:"features"`
@@ -731,6 +749,9 @@ type Guild struct {
// Permissions of our user // Permissions of our user
Permissions int64 `json:"permissions,string"` Permissions int64 `json:"permissions,string"`
// Stage instances in the guild
StageInstances []*StageInstance `json:"stage_instances"`
} }
// A GuildPreview holds data related to a specific public Discord Guild, even if the user is not in the guild. // A GuildPreview holds data related to a specific public Discord Guild, even if the user is not in the guild.
@@ -757,16 +778,31 @@ type GuildPreview struct {
// The list of enabled guild features // The list of enabled guild features
Features []string `json:"features"` Features []string `json:"features"`
// Approximate number of members in this guild, returned from the GET /guild/<id> endpoint when with_counts is true // Approximate number of members in this guild
// NOTE: this field is only filled when using GuildWithCounts
ApproximateMemberCount int `json:"approximate_member_count"` ApproximateMemberCount int `json:"approximate_member_count"`
// Approximate number of non-offline members in this guild, returned from the GET /guild/<id> endpoint when with_counts is true // Approximate number of non-offline members in this guild
// NOTE: this field is only filled when using GuildWithCounts
ApproximatePresenceCount int `json:"approximate_presence_count"` ApproximatePresenceCount int `json:"approximate_presence_count"`
// the description for the guild // the description for the guild
Description string `json:"description"` Description string `json:"description"`
} }
// IconURL returns a URL to the guild's icon.
func (g *GuildPreview) IconURL() string {
if g.Icon == "" {
return ""
}
if strings.HasPrefix(g.Icon, "a_") {
return EndpointGuildIconAnimated(g.ID, g.Icon)
}
return EndpointGuildIcon(g.ID, g.Icon)
}
// GuildScheduledEvent is a representation of a scheduled event in a guild. Only for retrieval of the data. // GuildScheduledEvent is a representation of a scheduled event in a guild. Only for retrieval of the data.
// https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event // https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event
type GuildScheduledEvent struct { type GuildScheduledEvent struct {
@@ -842,7 +878,7 @@ func (p GuildScheduledEventParams) MarshalJSON() ([]byte, error) {
type guildScheduledEventParams GuildScheduledEventParams type guildScheduledEventParams GuildScheduledEventParams
if p.EntityType == GuildScheduledEventEntityTypeExternal && p.ChannelID == "" { if p.EntityType == GuildScheduledEventEntityTypeExternal && p.ChannelID == "" {
return json.Marshal(struct { return Marshal(struct {
guildScheduledEventParams guildScheduledEventParams
ChannelID json.RawMessage `json:"channel_id"` ChannelID json.RawMessage `json:"channel_id"`
}{ }{
@@ -851,7 +887,7 @@ func (p GuildScheduledEventParams) MarshalJSON() ([]byte, error) {
}) })
} }
return json.Marshal(guildScheduledEventParams(p)) return Marshal(guildScheduledEventParams(p))
} }
// GuildScheduledEventEntityMetadata holds additional metadata for guild scheduled event. // GuildScheduledEventEntityMetadata holds additional metadata for guild scheduled event.
@@ -1093,7 +1129,7 @@ func (t *TimeStamps) UnmarshalJSON(b []byte) error {
End float64 `json:"end,omitempty"` End float64 `json:"end,omitempty"`
Start float64 `json:"start,omitempty"` Start float64 `json:"start,omitempty"`
}{} }{}
err := json.Unmarshal(b, &temp) err := Unmarshal(b, &temp)
if err != nil { if err != nil {
return err return err
} }
@@ -1231,7 +1267,7 @@ func (t *TooManyRequests) UnmarshalJSON(b []byte) error {
Message string `json:"message"` Message string `json:"message"`
RetryAfter float64 `json:"retry_after"` RetryAfter float64 `json:"retry_after"`
}{} }{}
err := json.Unmarshal(b, &u) err := Unmarshal(b, &u)
if err != nil { if err != nil {
return err return err
} }
@@ -1566,6 +1602,15 @@ type UserGuildSettingsEdit struct {
ChannelOverrides map[string]*UserGuildSettingsChannelOverride `json:"channel_overrides"` ChannelOverrides map[string]*UserGuildSettingsChannelOverride `json:"channel_overrides"`
} }
// GuildMemberParams stores data needed to update a member
// https://discord.com/developers/docs/resources/guild#modify-guild-member
type GuildMemberParams struct {
// Value to set user's nickname to
Nick string `json:"nick,omitempty"`
// Array of role ids the member is assigned
Roles *[]string `json:"roles,omitempty"`
}
// An APIErrorMessage is an api error message returned from discord // An APIErrorMessage is an api error message returned from discord
type APIErrorMessage struct { type APIErrorMessage struct {
Code int `json:"code"` Code int `json:"code"`
@@ -1642,7 +1687,7 @@ func (activity *Activity) UnmarshalJSON(b []byte) error {
Instance bool `json:"instance,omitempty"` Instance bool `json:"instance,omitempty"`
Flags int `json:"flags,omitempty"` Flags int `json:"flags,omitempty"`
}{} }{}
err := json.Unmarshal(b, &temp) err := Unmarshal(b, &temp)
if err != nil { if err != nil {
return err return err
} }
@@ -1695,14 +1740,13 @@ const (
// Identify is sent during initial handshake with the discord gateway. // Identify is sent during initial handshake with the discord gateway.
// https://discord.com/developers/docs/topics/gateway#identify // https://discord.com/developers/docs/topics/gateway#identify
type Identify struct { type Identify struct {
Token string `json:"token"` Token string `json:"token"`
Properties IdentifyProperties `json:"properties"` Properties IdentifyProperties `json:"properties"`
Compress bool `json:"compress"` Compress bool `json:"compress"`
LargeThreshold int `json:"large_threshold"` LargeThreshold int `json:"large_threshold"`
Shard *[2]int `json:"shard,omitempty"` Shard *[2]int `json:"shard,omitempty"`
Presence GatewayStatusUpdate `json:"presence,omitempty"` Presence GatewayStatusUpdate `json:"presence,omitempty"`
GuildSubscriptions bool `json:"guild_subscriptions"` Intents Intent `json:"intents"`
Intents Intent `json:"intents"`
} }
// IdentifyProperties contains the "properties" portion of an Identify packet // IdentifyProperties contains the "properties" portion of an Identify packet
@@ -1715,6 +1759,49 @@ type IdentifyProperties struct {
ReferringDomain string `json:"$referring_domain"` ReferringDomain string `json:"$referring_domain"`
} }
// StageInstance holds information about a live stage.
// https://discord.com/developers/docs/resources/stage-instance#stage-instance-resource
type StageInstance struct {
// The id of this Stage instance
ID string `json:"id"`
// The guild id of the associated Stage channel
GuildID string `json:"guild_id"`
// The id of the associated Stage channel
ChannelID string `json:"channel_id"`
// The topic of the Stage instance (1-120 characters)
Topic string `json:"topic"`
// The privacy level of the Stage instance
// https://discord.com/developers/docs/resources/stage-instance#stage-instance-object-privacy-level
PrivacyLevel StageInstancePrivacyLevel `json:"privacy_level"`
// Whether or not Stage Discovery is disabled (deprecated)
DiscoverableDisabled bool `json:"discoverable_disabled"`
// The id of the scheduled event for this Stage instance
GuildScheduledEventID string `json:"guild_scheduled_event_id"`
}
// StageInstanceParams represents the parameters needed to create or edit a stage instance
type StageInstanceParams struct {
// ChannelID represents the id of the Stage channel
ChannelID string `json:"channel_id,omitempty"`
// Topic of the Stage instance (1-120 characters)
Topic string `json:"topic,omitempty"`
// PrivacyLevel of the Stage instance (default GUILD_ONLY)
PrivacyLevel StageInstancePrivacyLevel `json:"privacy_level,omitempty"`
// SendStartNotification will notify @everyone that a Stage instance has started
SendStartNotification bool `json:"send_start_notification,omitempty"`
}
// StageInstancePrivacyLevel represents the privacy level of a Stage instance
// https://discord.com/developers/docs/resources/stage-instance#stage-instance-object-privacy-level
type StageInstancePrivacyLevel int
const (
// StageInstancePrivacyLevelPublic The Stage instance is visible publicly. (deprecated)
StageInstancePrivacyLevelPublic StageInstancePrivacyLevel = 1
// StageInstancePrivacyLevelGuildOnly The Stage instance is visible to only guild members.
StageInstancePrivacyLevelGuildOnly StageInstancePrivacyLevel = 2
)
// Constants for the different bit offsets of text channel permissions // Constants for the different bit offsets of text channel permissions
const ( const (
// Deprecated: PermissionReadMessages has been replaced with PermissionViewChannel for text and voice channels // Deprecated: PermissionReadMessages has been replaced with PermissionViewChannel for text and voice channels
@@ -1731,6 +1818,7 @@ const (
PermissionManageThreads = 0x0000000400000000 PermissionManageThreads = 0x0000000400000000
PermissionCreatePublicThreads = 0x0000000800000000 PermissionCreatePublicThreads = 0x0000000800000000
PermissionCreatePrivateThreads = 0x0000001000000000 PermissionCreatePrivateThreads = 0x0000001000000000
PermissionUseExternalStickers = 0x0000002000000000
PermissionSendMessagesInThreads = 0x0000004000000000 PermissionSendMessagesInThreads = 0x0000004000000000
) )
@@ -1745,6 +1833,7 @@ const (
PermissionVoiceMoveMembers = 0x0000000001000000 PermissionVoiceMoveMembers = 0x0000000001000000
PermissionVoiceUseVAD = 0x0000000002000000 PermissionVoiceUseVAD = 0x0000000002000000
PermissionVoiceRequestToSpeak = 0x0000000100000000 PermissionVoiceRequestToSpeak = 0x0000000100000000
PermissionUseActivities = 0x0000008000000000
) )
// Constants for general management. // Constants for general management.
@@ -1754,6 +1843,7 @@ const (
PermissionManageRoles = 0x0000000010000000 PermissionManageRoles = 0x0000000010000000
PermissionManageWebhooks = 0x0000000020000000 PermissionManageWebhooks = 0x0000000020000000
PermissionManageEmojis = 0x0000000040000000 PermissionManageEmojis = 0x0000000040000000
PermissionManageEvents = 0x0000000200000000
) )
// Constants for the different bit offsets of general permissions // Constants for the different bit offsets of general permissions

View File

@@ -1,47 +0,0 @@
// 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 custom types, currently only a timestamp wrapper.
package discordgo
import (
"encoding/json"
"net/http"
)
// RESTError stores error information about a request with a bad response code.
// Message is not always present, there are cases where api calls can fail
// without returning a json message.
type RESTError struct {
Request *http.Request
Response *http.Response
ResponseBody []byte
Message *APIErrorMessage // Message may be nil.
}
func newRestError(req *http.Request, resp *http.Response, body []byte) *RESTError {
restErr := &RESTError{
Request: req,
Response: resp,
ResponseBody: body,
}
// Attempt to decode the error and assume no message was provided if it fails
var msg *APIErrorMessage
err := json.Unmarshal(body, &msg)
if err == nil {
restErr.Message = msg
}
return restErr
}
func (r RESTError) Error() string {
return "HTTP " + r.Response.Status + ", " + string(r.ResponseBody)
}

View File

@@ -2,7 +2,6 @@ package discordgo
import ( import (
"bytes" "bytes"
"encoding/json"
"fmt" "fmt"
"io" "io"
"mime/multipart" "mime/multipart"
@@ -30,7 +29,7 @@ func MultipartBodyWithJSON(data interface{}, files []*File) (requestContentType
body := &bytes.Buffer{} body := &bytes.Buffer{}
bodywriter := multipart.NewWriter(body) bodywriter := multipart.NewWriter(body)
payload, err := json.Marshal(data) payload, err := Marshal(data)
if err != nil { if err != nil {
return return
} }

View File

@@ -409,10 +409,13 @@ func (s *Session) UpdateStatusComplex(usd UpdateStatusData) (err error) {
} }
type requestGuildMembersData struct { type requestGuildMembersData struct {
GuildIDs []string `json:"guild_id"` // TODO: Deprecated. Use string instead of []string
Query string `json:"query"` GuildIDs []string `json:"guild_id"`
Limit int `json:"limit"` Query *string `json:"query,omitempty"`
Presences bool `json:"presences"` UserIDs *[]string `json:"user_ids,omitempty"`
Limit int `json:"limit"`
Nonce string `json:"nonce,omitempty"`
Presences bool `json:"presences"`
} }
type requestGuildMembersOp struct { type requestGuildMembersOp struct {
@@ -425,16 +428,21 @@ type requestGuildMembersOp struct {
// guildID : Single Guild ID to request members of // guildID : Single Guild ID to request members of
// query : String that username starts with, leave empty to return all members // query : String that username starts with, leave empty to return all members
// limit : Max number of items to return, or 0 to request all members matched // limit : Max number of items to return, or 0 to request all members matched
// nonce : Nonce to identify the Guild Members Chunk response
// presences : Whether to request presences of guild members // presences : Whether to request presences of guild members
func (s *Session) RequestGuildMembers(guildID string, query string, limit int, presences bool) (err error) { func (s *Session) RequestGuildMembers(guildID, query string, limit int, nonce string, presences bool) error {
data := requestGuildMembersData{ return s.RequestGuildMembersBatch([]string{guildID}, query, limit, nonce, presences)
GuildIDs: []string{guildID}, }
Query: query,
Limit: limit, // RequestGuildMembersList requests guild members from the gateway
Presences: presences, // The gateway responds with GuildMembersChunk events
} // guildID : Single Guild ID to request members of
err = s.requestGuildMembers(data) // userIDs : IDs of users to fetch
return // limit : Max number of items to return, or 0 to request all members matched
// nonce : Nonce to identify the Guild Members Chunk response
// presences : Whether to request presences of guild members
func (s *Session) RequestGuildMembersList(guildID string, userIDs []string, limit int, nonce string, presences bool) error {
return s.RequestGuildMembersBatchList([]string{guildID}, userIDs, limit, nonce, presences)
} }
// RequestGuildMembersBatch requests guild members from the gateway // RequestGuildMembersBatch requests guild members from the gateway
@@ -442,12 +450,37 @@ func (s *Session) RequestGuildMembers(guildID string, query string, limit int, p
// guildID : Slice of guild IDs to request members of // guildID : Slice of guild IDs to request members of
// query : String that username starts with, leave empty to return all members // query : String that username starts with, leave empty to return all members
// limit : Max number of items to return, or 0 to request all members matched // limit : Max number of items to return, or 0 to request all members matched
// nonce : Nonce to identify the Guild Members Chunk response
// presences : Whether to request presences of guild members // presences : Whether to request presences of guild members
func (s *Session) RequestGuildMembersBatch(guildIDs []string, query string, limit int, presences bool) (err error) { //
// NOTE: this function is deprecated, please use RequestGuildMembers instead
func (s *Session) RequestGuildMembersBatch(guildIDs []string, query string, limit int, nonce string, presences bool) (err error) {
data := requestGuildMembersData{ data := requestGuildMembersData{
GuildIDs: guildIDs, GuildIDs: guildIDs,
Query: query, Query: &query,
Limit: limit, Limit: limit,
Nonce: nonce,
Presences: presences,
}
err = s.requestGuildMembers(data)
return
}
// RequestGuildMembersBatchList requests guild members from the gateway
// The gateway responds with GuildMembersChunk events
// guildID : Slice of guild IDs to request members of
// userIDs : IDs of users to fetch
// limit : Max number of items to return, or 0 to request all members matched
// nonce : Nonce to identify the Guild Members Chunk response
// presences : Whether to request presences of guild members
//
// NOTE: this function is deprecated, please use RequestGuildMembersList instead
func (s *Session) RequestGuildMembersBatchList(guildIDs []string, userIDs []string, limit int, nonce string, presences bool) (err error) {
data := requestGuildMembersData{
GuildIDs: guildIDs,
UserIDs: &userIDs,
Limit: limit,
Nonce: nonce,
Presences: presences, Presences: presences,
} }
err = s.requestGuildMembers(data) err = s.requestGuildMembers(data)

View File

@@ -1220,14 +1220,14 @@ func (c *Compiler) optimizeFunc(node parser.Node) {
iterateInstructions(c.scopes[c.scopeIndex].Instructions, iterateInstructions(c.scopes[c.scopeIndex].Instructions,
func(pos int, opcode parser.Opcode, operands []int) bool { func(pos int, opcode parser.Opcode, operands []int) bool {
switch { switch {
case dsts[pos]:
dstIdx++
deadCode = false
case opcode == parser.OpReturn: case opcode == parser.OpReturn:
if deadCode { if deadCode {
return true return true
} }
deadCode = true deadCode = true
case dsts[pos]:
dstIdx++
deadCode = false
case deadCode: case deadCode:
return true return true
} }
@@ -1242,6 +1242,7 @@ func (c *Compiler) optimizeFunc(node parser.Node) {
var appendReturn bool var appendReturn bool
endPos := len(c.scopes[c.scopeIndex].Instructions) endPos := len(c.scopes[c.scopeIndex].Instructions)
newEndPost := len(newInsts) newEndPost := len(newInsts)
iterateInstructions(newInsts, iterateInstructions(newInsts,
func(pos int, opcode parser.Opcode, operands []int) bool { func(pos int, opcode parser.Opcode, operands []int) bool {
switch opcode { switch opcode {

View File

@@ -375,7 +375,12 @@ func (p *Parser) parseOperand() Expr {
case token.Ident: case token.Ident:
return p.parseIdent() return p.parseIdent()
case token.Int: case token.Int:
v, _ := strconv.ParseInt(p.tokenLit, 10, 64) v, err := strconv.ParseInt(p.tokenLit, 0, 64)
if err == strconv.ErrRange {
p.error(p.pos, "number out of range")
} else if err != nil {
p.error(p.pos, "invalid integer")
}
x := &IntLit{ x := &IntLit{
Value: v, Value: v,
ValuePos: p.pos, ValuePos: p.pos,
@@ -383,8 +388,14 @@ func (p *Parser) parseOperand() Expr {
} }
p.next() p.next()
return x return x
case token.Float: case token.Float:
v, _ := strconv.ParseFloat(p.tokenLit, 64) v, err := strconv.ParseFloat(p.tokenLit, 64)
if err == strconv.ErrRange {
p.error(p.pos, "number out of range")
} else if err != nil {
p.error(p.pos, "invalid float")
}
x := &FloatLit{ x := &FloatLit{
Value: v, Value: v,
ValuePos: p.pos, ValuePos: p.pos,
@@ -447,10 +458,11 @@ func (p *Parser) parseOperand() Expr {
return p.parseErrorExpr() return p.parseErrorExpr()
case token.Immutable: // immutable expression case token.Immutable: // immutable expression
return p.parseImmutableExpr() return p.parseImmutableExpr()
default:
p.errorExpected(p.pos, "operand")
} }
pos := p.pos pos := p.pos
p.errorExpected(pos, "operand")
p.advance(stmtStart) p.advance(stmtStart)
return &BadExpr{From: pos, To: p.pos} return &BadExpr{From: pos, To: p.pos}
} }

View File

@@ -93,9 +93,9 @@ func (s *Scanner) Scan() (
token.Export, token.True, token.False, token.Undefined: token.Export, token.True, token.False, token.Undefined:
insertSemi = true insertSemi = true
} }
case '0' <= ch && ch <= '9': case ('0' <= ch && ch <= '9') || (ch == '.' && '0' <= s.peek() && s.peek() <= '9'):
insertSemi = true insertSemi = true
tok, literal = s.scanNumber(false) tok, literal = s.scanNumber()
default: default:
s.next() // always make progress s.next() // always make progress
@@ -125,16 +125,11 @@ func (s *Scanner) Scan() (
case ':': case ':':
tok = s.switch2(token.Colon, token.Define) tok = s.switch2(token.Colon, token.Define)
case '.': case '.':
if '0' <= s.ch && s.ch <= '9' { tok = token.Period
insertSemi = true if s.ch == '.' && s.peek() == '.' {
tok, literal = s.scanNumber(true) s.next()
} else { s.next() // consume last '.'
tok = token.Period tok = token.Ellipsis
if s.ch == '.' && s.peek() == '.' {
s.next()
s.next() // consume last '.'
tok = token.Ellipsis
}
} }
case ',': case ',':
tok = token.Comma tok = token.Comma
@@ -379,86 +374,58 @@ func (s *Scanner) scanIdentifier() string {
return string(s.src[offs:s.offset]) return string(s.src[offs:s.offset])
} }
func (s *Scanner) scanMantissa(base int) { func (s *Scanner) scanDigits(base int) {
for digitVal(s.ch) < base { for s.ch == '_' || digitVal(s.ch) < base {
s.next() s.next()
} }
} }
func (s *Scanner) scanNumber( func (s *Scanner) scanNumber() (token.Token, string) {
seenDecimalPoint bool,
) (tok token.Token, lit string) {
// digitVal(s.ch) < 10
offs := s.offset offs := s.offset
tok = token.Int tok := token.Int
base := 10
defer func() { // Determine base
lit = string(s.src[offs:s.offset]) switch {
}() case s.ch == '0' && lower(s.peek()) == 'b':
base = 2
if seenDecimalPoint { s.next()
offs-- s.next()
tok = token.Float case s.ch == '0' && lower(s.peek()) == 'o':
s.scanMantissa(10) base = 8
goto exponent s.next()
} s.next()
case s.ch == '0' && lower(s.peek()) == 'x':
if s.ch == '0' { base = 16
// int or float s.next()
offs := s.offset
s.next() s.next()
if s.ch == 'x' || s.ch == 'X' {
// hexadecimal int
s.next()
s.scanMantissa(16)
if s.offset-offs <= 2 {
// only scanned "0x" or "0X"
s.error(offs, "illegal hexadecimal number")
}
} else {
// octal int or float
seenDecimalDigit := false
s.scanMantissa(8)
if s.ch == '8' || s.ch == '9' {
// illegal octal int or float
seenDecimalDigit = true
s.scanMantissa(10)
}
if s.ch == '.' || s.ch == 'e' || s.ch == 'E' || s.ch == 'i' {
goto fraction
}
// octal int
if seenDecimalDigit {
s.error(offs, "illegal octal number")
}
}
return
} }
// decimal int or float // Scan whole number
s.scanMantissa(10) s.scanDigits(base)
fraction: // Scan fractional part
if s.ch == '.' { if s.ch == '.' && (base == 10 || base == 16) {
tok = token.Float tok = token.Float
s.next() s.next()
s.scanMantissa(10) s.scanDigits(base)
} }
exponent: // Scan exponent
if s.ch == 'e' || s.ch == 'E' { if s.ch == 'e' || s.ch == 'E' || s.ch == 'p' || s.ch == 'P' {
tok = token.Float tok = token.Float
s.next() s.next()
if s.ch == '-' || s.ch == '+' { if s.ch == '-' || s.ch == '+' {
s.next() s.next()
} }
if digitVal(s.ch) < 10 { offs := s.offset
s.scanMantissa(10) s.scanDigits(10)
} else { if offs == s.offset {
s.error(offs, "illegal floating-point exponent") s.error(offs, "exponent has no digits")
} }
} }
return
return tok, string(s.src[offs:s.offset])
} }
func (s *Scanner) scanEscape(quote rune) bool { func (s *Scanner) scanEscape(quote rune) bool {
@@ -687,3 +654,7 @@ func digitVal(ch rune) int {
} }
return 16 // larger than any legal digit val return 16 // larger than any legal digit val
} }
func lower(c byte) byte {
return c | ('x' - 'X')
}

View File

@@ -7,9 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [1.5.4] - 2022-04-25
* Windows: add missing defer to `Watcher.WatchList` [#447](https://github.com/fsnotify/fsnotify/pull/447)
* go.mod: use latest x/sys [#444](https://github.com/fsnotify/fsnotify/pull/444)
* Fix compilation for OpenBSD [#443](https://github.com/fsnotify/fsnotify/pull/443)
## [1.5.3] - 2022-04-22
* This version is retracted. An incorrect branch is published accidentally [#445](https://github.com/fsnotify/fsnotify/issues/445)
## [1.5.2] - 2022-04-21
* Add a feature to return the directories and files that are being monitored [#374](https://github.com/fsnotify/fsnotify/pull/374)
* Fix potential crash on windows if `raw.FileNameLength` exceeds `syscall.MAX_PATH` [#361](https://github.com/fsnotify/fsnotify/pull/361)
* Allow build on unsupported GOOS [#424](https://github.com/fsnotify/fsnotify/pull/424)
* Don't set `poller.fd` twice in `newFdPoller` [#406](https://github.com/fsnotify/fsnotify/pull/406)
* fix go vet warnings: call to `(*T).Fatalf` from a non-test goroutine [#416](https://github.com/fsnotify/fsnotify/pull/416)
## [1.5.1] - 2021-08-24 ## [1.5.1] - 2021-08-24
* Revert Add AddRaw to not follow symlinks * Revert Add AddRaw to not follow symlinks [#394](https://github.com/fsnotify/fsnotify/pull/394)
## [1.5.0] - 2021-08-20 ## [1.5.0] - 2021-08-20

View File

@@ -48,18 +48,6 @@ fsnotify uses build tags to compile different code on Linux, BSD, macOS, and Win
Before doing a pull request, please do your best to test your changes on multiple platforms, and list which platforms you were able/unable to test on. Before doing a pull request, please do your best to test your changes on multiple platforms, and list which platforms you were able/unable to test on.
To aid in cross-platform testing there is a Vagrantfile for Linux and BSD.
* Install [Vagrant](http://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/)
* Setup [Vagrant Gopher](https://github.com/nathany/vagrant-gopher) in your `src` folder.
* Run `vagrant up` from the project folder. You can also setup just one box with `vagrant up linux` or `vagrant up bsd` (note: the BSD box doesn't support Windows hosts at this time, and NFS may prompt for your host OS password)
* Once setup, you can run the test suite on a given OS with a single command `vagrant ssh linux -c 'cd fsnotify/fsnotify; go test'`.
* When you're done, you will want to halt or destroy the Vagrant boxes.
Notice: fsnotify file system events won't trigger in shared folders. The tests get around this limitation by using the /tmp directory.
Right now there is no equivalent solution for Windows and macOS, but there are Windows VMs [freely available from Microsoft](http://www.modern.ie/en-us/virtualization-tools#downloads).
### Maintainers ### Maintainers
Help maintaining fsnotify is welcome. To be a maintainer: Help maintaining fsnotify is welcome. To be a maintainer:
@@ -67,11 +55,6 @@ Help maintaining fsnotify is welcome. To be a maintainer:
* Submit a pull request and sign the CLA as above. * Submit a pull request and sign the CLA as above.
* You must be able to run the test suite on Mac, Windows, Linux and BSD. * You must be able to run the test suite on Mac, Windows, Linux and BSD.
To keep master clean, the fsnotify project uses the "apply mail" workflow outlined in Nathaniel Talbott's post ["Merge pull request" Considered Harmful][am]. This requires installing [hub][].
All code changes should be internal pull requests. All code changes should be internal pull requests.
Releases are tagged using [Semantic Versioning](http://semver.org/). Releases are tagged using [Semantic Versioning](http://semver.org/).
[hub]: https://github.com/github/hub
[am]: http://blog.spreedly.com/2014/06/24/merge-pull-request-considered-harmful/#.VGa5yZPF_Zs

View File

@@ -1,12 +1,8 @@
# File system notifications for Go # File system notifications for Go
[![GoDoc](https://godoc.org/github.com/fsnotify/fsnotify?status.svg)](https://godoc.org/github.com/fsnotify/fsnotify) [![Go Report Card](https://goreportcard.com/badge/github.com/fsnotify/fsnotify)](https://goreportcard.com/report/github.com/fsnotify/fsnotify) [![Go Reference](https://pkg.go.dev/badge/github.com/fsnotify/fsnotify.svg)](https://pkg.go.dev/github.com/fsnotify/fsnotify) [![Go Report Card](https://goreportcard.com/badge/github.com/fsnotify/fsnotify)](https://goreportcard.com/report/github.com/fsnotify/fsnotify) [![Maintainers Wanted](https://img.shields.io/badge/maintainers-wanted-red.svg)](https://github.com/fsnotify/fsnotify/issues/413)
fsnotify utilizes [golang.org/x/sys](https://godoc.org/golang.org/x/sys) rather than `syscall` from the standard library. Ensure you have the latest version installed by running: fsnotify utilizes [`golang.org/x/sys`](https://pkg.go.dev/golang.org/x/sys) rather than [`syscall`](https://pkg.go.dev/syscall) from the standard library.
```console
go get -u golang.org/x/sys/...
```
Cross platform: Windows, Linux, BSD and macOS. Cross platform: Windows, Linux, BSD and macOS.
@@ -16,22 +12,20 @@ Cross platform: Windows, Linux, BSD and macOS.
| kqueue | BSD, macOS, iOS\* | Supported | | kqueue | BSD, macOS, iOS\* | Supported |
| ReadDirectoryChangesW | Windows | Supported | | ReadDirectoryChangesW | Windows | Supported |
| FSEvents | macOS | [Planned](https://github.com/fsnotify/fsnotify/issues/11) | | FSEvents | macOS | [Planned](https://github.com/fsnotify/fsnotify/issues/11) |
| FEN | Solaris 11 | [In Progress](https://github.com/fsnotify/fsnotify/issues/12) | | FEN | Solaris 11 | [In Progress](https://github.com/fsnotify/fsnotify/pull/371) |
| fanotify | Linux 2.6.37+ | [Planned](https://github.com/fsnotify/fsnotify/issues/114) | | fanotify | Linux 2.6.37+ | [Maybe](https://github.com/fsnotify/fsnotify/issues/114) |
| USN Journals | Windows | [Maybe](https://github.com/fsnotify/fsnotify/issues/53) | | USN Journals | Windows | [Maybe](https://github.com/fsnotify/fsnotify/issues/53) |
| Polling | *All* | [Maybe](https://github.com/fsnotify/fsnotify/issues/9) | | Polling | *All* | [Maybe](https://github.com/fsnotify/fsnotify/issues/9) |
\* Android and iOS are untested. \* Android and iOS are untested.
Please see [the documentation](https://godoc.org/github.com/fsnotify/fsnotify) and consult the [FAQ](#faq) for usage information. Please see [the documentation](https://pkg.go.dev/github.com/fsnotify/fsnotify) and consult the [FAQ](#faq) for usage information.
## API stability ## API stability
fsnotify is a fork of [howeyc/fsnotify](https://godoc.org/github.com/howeyc/fsnotify) with a new API as of v1.0. The API is based on [this design document](http://goo.gl/MrYxyA). fsnotify is a fork of [howeyc/fsnotify](https://github.com/howeyc/fsnotify) with a new API as of v1.0. The API is based on [this design document](http://goo.gl/MrYxyA).
All [releases](https://github.com/fsnotify/fsnotify/releases) are tagged based on [Semantic Versioning](http://semver.org/). Further API changes are [planned](https://github.com/fsnotify/fsnotify/milestones), and will be tagged with a new major revision number. All [releases](https://github.com/fsnotify/fsnotify/releases) are tagged based on [Semantic Versioning](http://semver.org/).
Go 1.6 supports dependencies located in the `vendor/` folder. Unless you are creating a library, it is recommended that you copy fsnotify into `vendor/github.com/fsnotify/fsnotify` within your project, and likewise for `golang.org/x/sys`.
## Usage ## Usage
@@ -84,10 +78,6 @@ func main() {
Please refer to [CONTRIBUTING][] before opening an issue or pull request. Please refer to [CONTRIBUTING][] before opening an issue or pull request.
## Example
See [example_test.go](https://github.com/fsnotify/fsnotify/blob/master/example_test.go).
## FAQ ## FAQ
**When a file is moved to another directory is it still being watched?** **When a file is moved to another directory is it still being watched?**

View File

@@ -0,0 +1,36 @@
// Copyright 2022 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 !darwin && !dragonfly && !freebsd && !openbsd && !linux && !netbsd && !solaris && !windows
// +build !darwin,!dragonfly,!freebsd,!openbsd,!linux,!netbsd,!solaris,!windows
package fsnotify
import (
"fmt"
"runtime"
)
// Watcher watches a set of files, delivering events to a channel.
type Watcher struct{}
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
func NewWatcher() (*Watcher, error) {
return nil, fmt.Errorf("fsnotify not supported on %s", runtime.GOOS)
}
// Close removes all watches and closes the events channel.
func (w *Watcher) Close() error {
return nil
}
// Add starts watching the named file or directory (non-recursively).
func (w *Watcher) Add(name string) error {
return nil
}
// Remove stops watching the the named file or directory (non-recursively).
func (w *Watcher) Remove(name string) error {
return nil
}

View File

@@ -163,6 +163,19 @@ func (w *Watcher) Remove(name string) error {
return nil return nil
} }
// WatchList returns the directories and files that are being monitered.
func (w *Watcher) WatchList() []string {
w.mu.Lock()
defer w.mu.Unlock()
entries := make([]string, 0, len(w.watches))
for pathname := range w.watches {
entries = append(entries, pathname)
}
return entries
}
type watch struct { type watch struct {
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall) wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags) flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)

View File

@@ -38,7 +38,6 @@ func newFdPoller(fd int) (*fdPoller, error) {
poller.close() poller.close()
} }
}() }()
poller.fd = fd
// Create epoll fd // Create epoll fd
poller.epfd, errno = unix.EpollCreate1(unix.EPOLL_CLOEXEC) poller.epfd, errno = unix.EpollCreate1(unix.EPOLL_CLOEXEC)

View File

@@ -148,6 +148,19 @@ func (w *Watcher) Remove(name string) error {
return nil return nil
} }
// WatchList returns the directories and files that are being monitered.
func (w *Watcher) WatchList() []string {
w.mu.Lock()
defer w.mu.Unlock()
entries := make([]string, 0, len(w.watches))
for pathname := range w.watches {
entries = append(entries, pathname)
}
return entries
}
// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE) // Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME

View File

@@ -12,6 +12,7 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"runtime" "runtime"
"sync" "sync"
"syscall" "syscall"
@@ -96,6 +97,21 @@ func (w *Watcher) Remove(name string) error {
return <-in.reply return <-in.reply
} }
// WatchList returns the directories and files that are being monitered.
func (w *Watcher) WatchList() []string {
w.mu.Lock()
defer w.mu.Unlock()
entries := make([]string, 0, len(w.watches))
for _, entry := range w.watches {
for _, watchEntry := range entry {
entries = append(entries, watchEntry.path)
}
}
return entries
}
const ( const (
// Options for AddWatch // Options for AddWatch
sysFSONESHOT = 0x80000000 sysFSONESHOT = 0x80000000
@@ -452,8 +468,16 @@ func (w *Watcher) readEvents() {
// Point "raw" to the event in the buffer // Point "raw" to the event in the buffer
raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset])) raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName)) // TODO: Consider using unsafe.Slice that is available from go1.17
name := syscall.UTF16ToString(buf[:raw.FileNameLength/2]) // https://stackoverflow.com/questions/51187973/how-to-create-an-array-or-a-slice-from-an-array-unsafe-pointer-in-golang
// instead of using a fixed syscall.MAX_PATH buf, we create a buf that is the size of the path name
size := int(raw.FileNameLength / 2)
var buf []uint16
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
sh.Data = uintptr(unsafe.Pointer(&raw.FileName))
sh.Len = size
sh.Cap = size
name := syscall.UTF16ToString(buf)
fullname := filepath.Join(watch.path, name) fullname := filepath.Join(watch.path, name)
var mask uint64 var mask uint64

9
vendor/github.com/gomarkdown/markdown/.gitpod.yml generated vendored Normal file
View File

@@ -0,0 +1,9 @@
# This configuration file was automatically generated by Gitpod.
# Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
# and commit this file to your remote git repository to share the goodness with others.
tasks:
- init: go get && go build ./... && go test ./...
command: go run

View File

@@ -1,6 +1,6 @@
# Markdown Parser and HTML Renderer for Go # Markdown Parser and HTML Renderer for Go
[![pkg.go.dev](https://pkg.go.dev/badge/github.com/gomarkdown/markdown)](https://pkg.go.dev/badge/github.com/gomarkdown/markdown) [![pkg.go.dev](https://pkg.go.dev/badge/github.com/gomarkdown/markdown)](https://pkg.go.dev/github.com/gomarkdown/markdown)
Package `github.com/gomarkdown/markdown` is a very fast Go library for parsing [Markdown](https://daringfireball.net/projects/markdown/) documents and rendering them to HTML. Package `github.com/gomarkdown/markdown` is a very fast Go library for parsing [Markdown](https://daringfireball.net/projects/markdown/) documents and rendering them to HTML.

View File

@@ -1,3 +1,4 @@
//go:build gofuzz
// +build gofuzz // +build gofuzz
package markdown package markdown

View File

@@ -26,8 +26,8 @@ links or code blocks.
// a very dummy render hook that will output "code_replacements" instead of // a very dummy render hook that will output "code_replacements" instead of
// <code>${content}</code> emitted by html.Renderer // <code>${content}</code> emitted by html.Renderer
func renderHookCodeBlock(w io.Writer, node *ast.Node, entering bool) (ast.WalkStatus, bool) { func renderHookCodeBlock(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool) {
_, ok := node.Data.(*ast.CodeBlockData) _, ok := node.(*ast.CodeBlock)
if !ok { if !ok {
return ast.GoToNext, false return ast.GoToNext, false
} }

View File

@@ -11,6 +11,7 @@ import (
"strings" "strings"
"github.com/gomarkdown/markdown/ast" "github.com/gomarkdown/markdown/ast"
"github.com/gomarkdown/markdown/internal/valid"
"github.com/gomarkdown/markdown/parser" "github.com/gomarkdown/markdown/parser"
) )
@@ -211,70 +212,6 @@ func NewRenderer(opts RendererOptions) *Renderer {
} }
} }
func isHTMLTag(tag []byte, tagname string) bool {
found, _ := findHTMLTagPos(tag, tagname)
return found
}
// Look for a character, but ignore it when it's in any kind of quotes, it
// might be JavaScript
func skipUntilCharIgnoreQuotes(html []byte, start int, char byte) int {
inSingleQuote := false
inDoubleQuote := false
inGraveQuote := false
i := start
for i < len(html) {
switch {
case html[i] == char && !inSingleQuote && !inDoubleQuote && !inGraveQuote:
return i
case html[i] == '\'':
inSingleQuote = !inSingleQuote
case html[i] == '"':
inDoubleQuote = !inDoubleQuote
case html[i] == '`':
inGraveQuote = !inGraveQuote
}
i++
}
return start
}
func findHTMLTagPos(tag []byte, tagname string) (bool, int) {
i := 0
if i < len(tag) && tag[0] != '<' {
return false, -1
}
i++
i = skipSpace(tag, i)
if i < len(tag) && tag[i] == '/' {
i++
}
i = skipSpace(tag, i)
j := 0
for ; i < len(tag); i, j = i+1, j+1 {
if j >= len(tagname) {
break
}
if strings.ToLower(string(tag[i]))[0] != tagname[j] {
return false, -1
}
}
if i == len(tag) {
return false, -1
}
rightAngle := skipUntilCharIgnoreQuotes(tag, i, '>')
if rightAngle >= i {
return true, rightAngle
}
return false, -1
}
func isRelativeLink(link []byte) (yes bool) { func isRelativeLink(link []byte) (yes bool) {
// a tag begin with '#' // a tag begin with '#'
if link[0] == '#' { if link[0] == '#' {
@@ -351,14 +288,6 @@ func needSkipLink(flags Flags, dest []byte) bool {
return flags&Safelink != 0 && !isSafeLink(dest) && !isMailto(dest) return flags&Safelink != 0 && !isSafeLink(dest) && !isMailto(dest)
} }
func isSmartypantable(node ast.Node) bool {
switch node.GetParent().(type) {
case *ast.Link, *ast.CodeBlock, *ast.Code:
return false
}
return true
}
func appendLanguageAttr(attrs []string, info []byte) []string { func appendLanguageAttr(attrs []string, info []byte) []string {
if len(info) == 0 { if len(info) == 0 {
return attrs return attrs
@@ -1297,21 +1226,8 @@ func isListItemTerm(node ast.Node) bool {
return ok && data.ListFlags&ast.ListTypeTerm != 0 return ok && data.ListFlags&ast.ListTypeTerm != 0
} }
// TODO: move to internal package
func skipSpace(data []byte, i int) int {
n := len(data)
for i < n && isSpace(data[i]) {
i++
}
return i
}
// TODO: move to internal package
var validUris = [][]byte{[]byte("http://"), []byte("https://"), []byte("ftp://"), []byte("mailto://")}
var validPaths = [][]byte{[]byte("/"), []byte("./"), []byte("../")}
func isSafeLink(link []byte) bool { func isSafeLink(link []byte) bool {
for _, path := range validPaths { for _, path := range valid.Paths {
if len(link) >= len(path) && bytes.Equal(link[:len(path)], path) { if len(link) >= len(path) && bytes.Equal(link[:len(path)], path) {
if len(link) == len(path) { if len(link) == len(path) {
return true return true
@@ -1321,7 +1237,7 @@ func isSafeLink(link []byte) bool {
} }
} }
for _, prefix := range validUris { for _, prefix := range valid.URIs {
// TODO: handle unicode here // TODO: handle unicode here
// case-insensitive prefix test // case-insensitive prefix test
if len(link) > len(prefix) && bytes.Equal(bytes.ToLower(link[:len(prefix)]), prefix) && isAlnum(link[len(prefix)]) { if len(link) > len(prefix) && bytes.Equal(bytes.ToLower(link[:len(prefix)]), prefix) && isAlnum(link[len(prefix)]) {

View File

@@ -0,0 +1,14 @@
package valid
var URIs = [][]byte{
[]byte("http://"),
[]byte("https://"),
[]byte("ftp://"),
[]byte("mailto:"),
}
var Paths = [][]byte{
[]byte("/"),
[]byte("./"),
[]byte("../"),
}

View File

@@ -24,8 +24,8 @@ const (
) )
var ( var (
reBackslashOrAmp = regexp.MustCompile("[\\&]") reBackslashOrAmp = regexp.MustCompile(`[\&]`)
reEntityOrEscapedChar = regexp.MustCompile("(?i)\\\\" + escapable + "|" + charEntity) reEntityOrEscapedChar = regexp.MustCompile(`(?i)\\` + escapable + "|" + charEntity)
// blockTags is a set of tags that are recognized as HTML block tags. // blockTags is a set of tags that are recognized as HTML block tags.
// Any of these can be included in markdown text without special escaping. // Any of these can be included in markdown text without special escaping.
@@ -858,12 +858,9 @@ func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker
return 0, "" return 0, ""
} }
// TODO(shurcooL): It's probably a good idea to simplify the 2 code paths here // if just read the beginning marker, read the syntax
// into one, always get the syntax, and discard it if the caller doesn't care. if oldmarker == "" {
if syntax != nil {
syn := 0
i = skipChar(data, i, ' ') i = skipChar(data, i, ' ')
if i >= n { if i >= n {
if i == n { if i == n {
return i, marker return i, marker
@@ -871,41 +868,15 @@ func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker
return 0, "" return 0, ""
} }
syntaxStart := i syntaxStart, syntaxLen := syntaxRange(data, &i)
if syntaxStart == 0 && syntaxLen == 0 {
if data[i] == '{' { return 0, ""
i++
syntaxStart++
for i < n && data[i] != '}' && data[i] != '\n' {
syn++
i++
}
if i >= n || data[i] != '}' {
return 0, ""
}
// strip all whitespace at the beginning and the end
// of the {} block
for syn > 0 && isSpace(data[syntaxStart]) {
syntaxStart++
syn--
}
for syn > 0 && isSpace(data[syntaxStart+syn-1]) {
syn--
}
i++
} else {
for i < n && !isSpace(data[i]) {
syn++
i++
}
} }
*syntax = string(data[syntaxStart : syntaxStart+syn]) // caller wants the syntax
if syntax != nil {
*syntax = string(data[syntaxStart : syntaxStart+syntaxLen])
}
} }
i = skipChar(data, i, ' ') i = skipChar(data, i, ' ')
@@ -918,6 +889,47 @@ func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker
return i + 1, marker // Take newline into account. return i + 1, marker // Take newline into account.
} }
func syntaxRange(data []byte, iout *int) (int, int) {
n := len(data)
syn := 0
i := *iout
syntaxStart := i
if data[i] == '{' {
i++
syntaxStart++
for i < n && data[i] != '}' && data[i] != '\n' {
syn++
i++
}
if i >= n || data[i] != '}' {
return 0, 0
}
// strip all whitespace at the beginning and the end
// of the {} block
for syn > 0 && isSpace(data[syntaxStart]) {
syntaxStart++
syn--
}
for syn > 0 && isSpace(data[syntaxStart+syn-1]) {
syn--
}
i++
} else {
for i < n && !isSpace(data[i]) {
syn++
i++
}
}
*iout = i
return syntaxStart, syn
}
// fencedCodeBlock returns the end index if data contains a fenced code block at the beginning, // fencedCodeBlock returns the end index if data contains a fenced code block at the beginning,
// or 0 otherwise. It writes to out if doRender is true, otherwise it has no side effects. // or 0 otherwise. It writes to out if doRender is true, otherwise it has no side effects.
// If doRender is true, a final newline is mandatory to recognize the fenced code block. // If doRender is true, a final newline is mandatory to recognize the fenced code block.
@@ -1416,7 +1428,7 @@ gatherlines:
// we need to add leadingWhiteSpaces + 1 spaces in the beginning of the chunk // we need to add leadingWhiteSpaces + 1 spaces in the beginning of the chunk
if indentIndex >= 4 && p.dliPrefix(chunk) <= 0 { if indentIndex >= 4 && p.dliPrefix(chunk) <= 0 {
leadingWhiteSpaces := skipChar(chunk, 0, ' ') leadingWhiteSpaces := skipChar(chunk, 0, ' ')
chunk = data[ line+indentIndex - (leadingWhiteSpaces + 1) : i] chunk = data[line+indentIndex-(leadingWhiteSpaces+1) : i]
} }
// to be a nested list, it must be indented more // to be a nested list, it must be indented more
@@ -1663,6 +1675,12 @@ func (p *Parser) paragraph(data []byte) int {
return i return i
} }
// if there's a block quote, paragraph is over
if p.quotePrefix(current) > 0 {
p.renderParagraph(data[:i])
return i
}
// if there's a fenced code block, paragraph is over // if there's a fenced code block, paragraph is over
if p.extensions&FencedCode != 0 { if p.extensions&FencedCode != 0 {
if p.fencedCodeBlock(current, false) > 0 { if p.fencedCodeBlock(current, false) > 0 {

View File

@@ -6,6 +6,7 @@ import (
"strconv" "strconv"
"github.com/gomarkdown/markdown/ast" "github.com/gomarkdown/markdown/ast"
"github.com/gomarkdown/markdown/internal/valid"
) )
// Parsing of inline elements // Parsing of inline elements
@@ -131,7 +132,11 @@ func codeSpan(p *Parser, data []byte, offset int) (int, ast.Node) {
// find the next delimiter // find the next delimiter
i, end := 0, 0 i, end := 0, 0
hasLFBeforeDelimiter := false
for end = nb; end < len(data) && i < nb; end++ { for end = nb; end < len(data) && i < nb; end++ {
if data[end] == '\n' {
hasLFBeforeDelimiter = true
}
if data[end] == '`' { if data[end] == '`' {
i++ i++
} else { } else {
@@ -144,6 +149,18 @@ func codeSpan(p *Parser, data []byte, offset int) (int, ast.Node) {
return 0, nil return 0, nil
} }
// If there are non-space chars after the ending delimiter and before a '\n',
// flag that this is not a well formed fenced code block.
hasCharsAfterDelimiter := false
for j := end; j < len(data); j++ {
if data[j] == '\n' {
break
}
if !isSpace(data[j]) {
hasCharsAfterDelimiter = true
}
}
// trim outside whitespace // trim outside whitespace
fBegin := nb fBegin := nb
for fBegin < end && data[fBegin] == ' ' { for fBegin < end && data[fBegin] == ' ' {
@@ -155,14 +172,31 @@ func codeSpan(p *Parser, data []byte, offset int) (int, ast.Node) {
fEnd-- fEnd--
} }
// render the code span if fBegin == fEnd {
if fBegin != fEnd { return end, nil
code := &ast.Code{}
code.Literal = data[fBegin:fEnd]
return end, code
} }
return end, nil // if delimiter has 3 backticks
if nb == 3 {
i := fBegin
syntaxStart, syntaxLen := syntaxRange(data, &i)
// If we found a '\n' before the end marker and there are only spaces
// after the end marker, then this is a code block.
if hasLFBeforeDelimiter && !hasCharsAfterDelimiter {
codeblock := &ast.CodeBlock{
IsFenced: true,
Info: data[syntaxStart : syntaxStart+syntaxLen],
}
codeblock.Literal = data[i:fEnd]
return end, codeblock
}
}
// render the code span
code := &ast.Code{}
code.Literal = data[fBegin:fEnd]
return end, code
} }
// newline preceded by two spaces becomes <br> // newline preceded by two spaces becomes <br>
@@ -781,7 +815,7 @@ func entity(p *Parser, data []byte, offset int) (int, ast.Node) {
codepoint, err = strconv.ParseUint(string(ent[2:len(ent)-1]), 10, 64) codepoint, err = strconv.ParseUint(string(ent[2:len(ent)-1]), 10, 64)
} }
if err == nil { // only if conversion was valid return here. if err == nil { // only if conversion was valid return here.
return end, newTextNode([]byte(string(codepoint))) return end, newTextNode([]byte(string(rune(codepoint))))
} }
return end, newTextNode(ent) return end, newTextNode(ent)
@@ -961,12 +995,9 @@ func isEndOfLink(char byte) bool {
return isSpace(char) || char == '<' return isSpace(char) || char == '<'
} }
var validUris = [][]byte{[]byte("http://"), []byte("https://"), []byte("ftp://"), []byte("mailto://")}
var validPaths = [][]byte{[]byte("/"), []byte("./"), []byte("../")}
func isSafeLink(link []byte) bool { func isSafeLink(link []byte) bool {
nLink := len(link) nLink := len(link)
for _, path := range validPaths { for _, path := range valid.Paths {
nPath := len(path) nPath := len(path)
linkPrefix := link[:nPath] linkPrefix := link[:nPath]
if nLink >= nPath && bytes.Equal(linkPrefix, path) { if nLink >= nPath && bytes.Equal(linkPrefix, path) {
@@ -978,7 +1009,7 @@ func isSafeLink(link []byte) bool {
} }
} }
for _, prefix := range validUris { for _, prefix := range valid.URIs {
// TODO: handle unicode here // TODO: handle unicode here
// case-insensitive prefix test // case-insensitive prefix test
nPrefix := len(prefix) nPrefix := len(prefix)
@@ -1086,7 +1117,7 @@ func isMailtoAutoLink(data []byte) int {
nb++ nb++
case '-', '.', '_': case '-', '.', '_':
break // no-op but not defult
case '>': case '>':
if nb == 1 { if nb == 1 {

View File

@@ -8,7 +8,6 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
"unicode/utf8"
"github.com/gomarkdown/markdown/ast" "github.com/gomarkdown/markdown/ast"
) )
@@ -720,6 +719,7 @@ func isAlnum(c byte) bool {
// TODO: this is not used // TODO: this is not used
// Replace tab characters with spaces, aligning to the next TAB_SIZE column. // Replace tab characters with spaces, aligning to the next TAB_SIZE column.
// always ends output with a newline // always ends output with a newline
/*
func expandTabs(out *bytes.Buffer, line []byte, tabSize int) { func expandTabs(out *bytes.Buffer, line []byte, tabSize int) {
// first, check for common cases: no tabs, or only tabs at beginning of line // first, check for common cases: no tabs, or only tabs at beginning of line
i, prefix := 0, 0 i, prefix := 0, 0
@@ -775,6 +775,7 @@ func expandTabs(out *bytes.Buffer, line []byte, tabSize int) {
i++ i++
} }
} }
*/
// Find if a line counts as indented or not. // Find if a line counts as indented or not.
// Returns number of characters the indent is (0 = not indented). // Returns number of characters the indent is (0 = not indented).

View File

@@ -0,0 +1,5 @@
/.idea
/.vscode
/internal/validation/testdata/graphql-js
/internal/validation/testdata/node_modules
/vendor

View File

@@ -0,0 +1,35 @@
run:
timeout: 5m
linters-settings:
gofmt:
simplify: true
govet:
check-shadowing: true
enable-all: true
disable:
- fieldalignment
- deepequalerrors # remove later
linters:
disable-all: true
enable:
- deadcode
- gofmt
- gosimple
- govet
- ineffassign
- exportloopref
- structcheck
- staticcheck
- unconvert
- unused
- varcheck
- misspell
- goimports
issues:
exclude-rules:
- linters:
- unused
path: "graphql_test.go"

View File

@@ -0,0 +1,10 @@
CHANGELOG
[v1.1.0](https://github.com/graph-gophers/graphql-go/releases/tag/v1.1.0) Release v1.1.0
* [FEATURE] Add types package #437
* [FEATURE] Expose `packer.Unmarshaler` as `decode.Unmarshaler` to the public #450
* [FEATURE] Add location fields to type definitions #454
* [FEATURE] `errors.Errorf` preserves original error similar to `fmt.Errorf` #456
* [BUGFIX] Fix duplicated __typename in response (fixes #369) #443
[v1.0.0](https://github.com/graph-gophers/graphql-go/releases/tag/v1.0.0) Initial release

View File

@@ -0,0 +1,13 @@
## Contributing
- With issues:
- Use the search tool before opening a new issue.
- Please provide source code and commit sha if you found a bug.
- Review existing issues and provide feedback or react to them.
- With pull requests:
- Open your pull request against `master`
- Your pull request should have no more than two commits, if not you should squash them.
- It should pass all tests in the available continuous integrations systems such as TravisCI.
- You should add/modify tests to cover your proposed code changes.
- If your pull request contains a new feature, please document it on the README.

24
vendor/github.com/graph-gophers/graphql-go/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,24 @@
Copyright (c) 2016 Richard Musiol. 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.
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.

169
vendor/github.com/graph-gophers/graphql-go/README.md generated vendored Normal file
View File

@@ -0,0 +1,169 @@
# graphql-go [![Sourcegraph](https://sourcegraph.com/github.com/graph-gophers/graphql-go/-/badge.svg)](https://sourcegraph.com/github.com/graph-gophers/graphql-go?badge) [![Build Status](https://graph-gophers.semaphoreci.com/badges/graphql-go/branches/master.svg?style=shields)](https://graph-gophers.semaphoreci.com/projects/graphql-go) [![GoDoc](https://godoc.org/github.com/graph-gophers/graphql-go?status.svg)](https://godoc.org/github.com/graph-gophers/graphql-go)
<p align="center"><img src="docs/img/logo.png" width="300"></p>
The goal of this project is to provide full support of the [GraphQL draft specification](https://facebook.github.io/graphql/draft) with a set of idiomatic, easy to use Go packages.
While still under heavy development (`internal` APIs are almost certainly subject to change), this library is
safe for production use.
## Features
- minimal API
- support for `context.Context`
- support for the `OpenTracing` standard
- schema type-checking against resolvers
- resolvers are matched to the schema based on method sets (can resolve a GraphQL schema with a Go interface or Go struct).
- handles panics in resolvers
- parallel execution of resolvers
- subscriptions
- [sample WS transport](https://github.com/graph-gophers/graphql-transport-ws)
## Roadmap
We're trying out the GitHub Project feature to manage `graphql-go`'s [development roadmap](https://github.com/graph-gophers/graphql-go/projects/1).
Feedback is welcome and appreciated.
## (Some) Documentation
### Basic Sample
```go
package main
import (
"log"
"net/http"
graphql "github.com/graph-gophers/graphql-go"
"github.com/graph-gophers/graphql-go/relay"
)
type query struct{}
func (_ *query) Hello() string { return "Hello, world!" }
func main() {
s := `
type Query {
hello: String!
}
`
schema := graphql.MustParseSchema(s, &query{})
http.Handle("/query", &relay.Handler{Schema: schema})
log.Fatal(http.ListenAndServe(":8080", nil))
}
```
To test:
```sh
curl -XPOST -d '{"query": "{ hello }"}' localhost:8080/query
```
### Resolvers
A resolver must have one method or field for each field of the GraphQL type it resolves. The method or field name has to be [exported](https://golang.org/ref/spec#Exported_identifiers) and match the schema's field's name in a non-case-sensitive way.
You can use struct fields as resolvers by using `SchemaOpt: UseFieldResolvers()`. For example,
```
opts := []graphql.SchemaOpt{graphql.UseFieldResolvers()}
schema := graphql.MustParseSchema(s, &query{}, opts...)
```
When using `UseFieldResolvers` schema option, a struct field will be used *only* when:
- there is no method for a struct field
- a struct field does not implement an interface method
- a struct field does not have arguments
The method has up to two arguments:
- Optional `context.Context` argument.
- Mandatory `*struct { ... }` argument if the corresponding GraphQL field has arguments. The names of the struct fields have to be [exported](https://golang.org/ref/spec#Exported_identifiers) and have to match the names of the GraphQL arguments in a non-case-sensitive way.
The method has up to two results:
- The GraphQL field's value as determined by the resolver.
- Optional `error` result.
Example for a simple resolver method:
```go
func (r *helloWorldResolver) Hello() string {
return "Hello world!"
}
```
The following signature is also allowed:
```go
func (r *helloWorldResolver) Hello(ctx context.Context) (string, error) {
return "Hello world!", nil
}
```
### Schema Options
- `UseStringDescriptions()` enables the usage of double quoted and triple quoted. When this is not enabled, comments are parsed as descriptions instead.
- `UseFieldResolvers()` specifies whether to use struct field resolvers.
- `MaxDepth(n int)` specifies the maximum field nesting depth in a query. The default is 0 which disables max depth checking.
- `MaxParallelism(n int)` specifies the maximum number of resolvers per request allowed to run in parallel. The default is 10.
- `Tracer(tracer trace.Tracer)` is used to trace queries and fields. It defaults to `trace.OpenTracingTracer`.
- `ValidationTracer(tracer trace.ValidationTracer)` is used to trace validation errors. It defaults to `trace.NoopValidationTracer`.
- `Logger(logger log.Logger)` is used to log panics during query execution. It defaults to `exec.DefaultLogger`.
- `PanicHandler(panicHandler errors.PanicHandler)` is used to transform panics into errors during query execution. It defaults to `errors.DefaultPanicHandler`.
- `DisableIntrospection()` disables introspection queries.
### Custom Errors
Errors returned by resolvers can include custom extensions by implementing the `ResolverError` interface:
```go
type ResolverError interface {
error
Extensions() map[string]interface{}
}
```
Example of a simple custom error:
```go
type droidNotFoundError struct {
Code string `json:"code"`
Message string `json:"message"`
}
func (e droidNotFoundError) Error() string {
return fmt.Sprintf("error [%s]: %s", e.Code, e.Message)
}
func (e droidNotFoundError) Extensions() map[string]interface{} {
return map[string]interface{}{
"code": e.Code,
"message": e.Message,
}
}
```
Which could produce a GraphQL error such as:
```go
{
"errors": [
{
"message": "error [NotFound]: This is not the droid you are looking for",
"path": [
"droid"
],
"extensions": {
"code": "NotFound",
"message": "This is not the droid you are looking for"
}
}
],
"data": null
}
```
### [Examples](https://github.com/graph-gophers/graphql-go/wiki/Examples)
### [Companies that use this library](https://github.com/graph-gophers/graphql-go/wiki/Users)

View File

@@ -0,0 +1,13 @@
package decode
// Unmarshaler defines the api of Go types mapped to custom GraphQL scalar types
type Unmarshaler interface {
// ImplementsGraphQLType maps the implementing custom Go type
// to the GraphQL scalar type in the schema.
ImplementsGraphQLType(name string) bool
// UnmarshalGraphQL is the custom unmarshaler for the implementing type
//
// This function will be called whenever you use the
// custom GraphQL scalar type as an input
UnmarshalGraphQL(input interface{}) error
}

View File

@@ -0,0 +1,59 @@
package errors
import (
"fmt"
)
type QueryError struct {
Err error `json:"-"` // Err holds underlying if available
Message string `json:"message"`
Locations []Location `json:"locations,omitempty"`
Path []interface{} `json:"path,omitempty"`
Rule string `json:"-"`
ResolverError error `json:"-"`
Extensions map[string]interface{} `json:"extensions,omitempty"`
}
type Location struct {
Line int `json:"line"`
Column int `json:"column"`
}
func (a Location) Before(b Location) bool {
return a.Line < b.Line || (a.Line == b.Line && a.Column < b.Column)
}
func Errorf(format string, a ...interface{}) *QueryError {
// similar to fmt.Errorf, Errorf will wrap the last argument if it is an instance of error
var err error
if n := len(a); n > 0 {
if v, ok := a[n-1].(error); ok {
err = v
}
}
return &QueryError{
Err: err,
Message: fmt.Sprintf(format, a...),
}
}
func (err *QueryError) Error() string {
if err == nil {
return "<nil>"
}
str := fmt.Sprintf("graphql: %s", err.Message)
for _, loc := range err.Locations {
str += fmt.Sprintf(" (line %d, column %d)", loc.Line, loc.Column)
}
return str
}
func (err *QueryError) Unwrap() error {
if err == nil {
return nil
}
return err.Err
}
var _ error = &QueryError{}

View File

@@ -0,0 +1,18 @@
package errors
import (
"context"
)
// PanicHandler is the interface used to create custom panic errors that occur during query execution
type PanicHandler interface {
MakePanicError(ctx context.Context, value interface{}) *QueryError
}
// DefaultPanicHandler is the default PanicHandler
type DefaultPanicHandler struct{}
// MakePanicError creates a new QueryError from a panic that occurred during execution
func (h *DefaultPanicHandler) MakePanicError(ctx context.Context, value interface{}) *QueryError {
return Errorf("panic occurred: %v", value)
}

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