Compare commits
	
		
			107 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 20f841c513 | ||
|   | d07a3e09c9 | ||
|   | 4649876956 | ||
|   | 5604d140e3 | ||
|   | 8751fb4bb1 | ||
|   | 3819062574 | ||
|   | 051e6e76e9 | ||
|   | 1e55dd47f2 | ||
|   | 700b95546b | ||
|   | 2fa96ec0ed | ||
|   | 81e6f75aa4 | ||
|   | 888c8b9a84 | ||
|   | e775a8a22e | ||
|   | 99fbd9cae6 | ||
|   | 67adad3e08 | ||
|   | 2fca3c7563 | ||
|   | c3573f1a46 | ||
|   | ee932a9f8e | ||
|   | ce18c948e6 | ||
|   | 7bc93c5506 | ||
|   | d7cad3b404 | ||
|   | 7740a362c9 | ||
|   | 281ef53e7d | ||
|   | f044b948e2 | ||
|   | 32474a5f4d | ||
|   | 26596acf80 | ||
|   | e63870a631 | ||
|   | ce782ff6fb | ||
|   | c6716e030c | ||
|   | 4ab72acec6 | ||
|   | 30aae8e257 | ||
|   | d7b7ff7bb4 | ||
|   | 6fe0cff342 | ||
|   | 5f75f9886d | ||
|   | 5d9604cd15 | ||
|   | cc36ebf1c9 | ||
|   | e6adecfd81 | ||
|   | 5c8f224e3b | ||
|   | 952221d3b9 | ||
|   | 496d5b4ec7 | ||
|   | 2623a412c4 | ||
|   | d64eed49bc | ||
|   | fffa29c2f3 | ||
|   | 4da1444ffc | ||
|   | 21c4e56d16 | ||
|   | 5356b3856a | ||
|   | 320c996a21 | ||
|   | 69c74be7bb | ||
|   | aefa70891c | ||
|   | 1b9877fda4 | ||
|   | 0205a67309 | ||
|   | e3cafeaf92 | ||
|   | e7b193788a | ||
|   | 17da95b094 | ||
|   | c5e49eec96 | ||
|   | 24bc0f127b | ||
|   | f0f801402d | ||
|   | 663850a2b8 | ||
|   | c51753cab1 | ||
|   | b3be2e208c | ||
|   | c30e90ff3f | ||
|   | e4c0ca0f48 | ||
|   | 9c203327c0 | ||
|   | ccb5b1d075 | ||
|   | 0dbbd0414c | ||
|   | e7b3ebf98a | ||
|   | 5bc18fb780 | ||
|   | df30366072 | ||
|   | 65c7ac80b5 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | dd3fb32ec7 | ||
|   | 2a3f475ff5 | ||
|   | 7288f71201 | ||
|   | 9c43eff753 | ||
|   | c8d7fdeedc | ||
|   | c211152e23 | ||
|   | ab75d5097e | ||
|   | c3644c8d3b | ||
|   | 6438a3dba3 | ||
|   | 4b226a6a63 | ||
|   | 4801850013 | ||
|   | 6a7412bf2b | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 5a1fd7dadd | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | ac06a26809 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 61d56f26f8 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 6aa05b3981 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | aad60c882e | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | fecca57507 | ||
|   | 2bcad846c0 | ||
|   | 15ad0165fc | ||
|   | 2e8ab11978 | ||
|   | 9a8ce9b17e | ||
|   | 16ab4c6fed | ||
|   | e3ee0df7ba | ||
|   | 8f7ab280e2 | ||
|   | dbedc99421 | ||
|   | 6cb359cb80 | ||
|   | ae2ad824a9 | ||
|   | 02e3d7852b | ||
|   | 3893a035be | ||
|   | 658bdd9faa | ||
|   | e1eebcd4e0 | ||
|   | 062b831e88 | ||
|   | b275efaeff | ||
|   | 80d3033456 | ||
|   | bd0516f09a | ||
|   | df4d76e466 | ||
|   | dcbd7f8cad | 
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -4,3 +4,6 @@ | ||||
|  | ||||
| # Exclude configuration file | ||||
| matterbridge.toml | ||||
|  | ||||
| # Exclude IDE Files | ||||
| .vscode | ||||
|   | ||||
| @@ -188,7 +188,22 @@ linters: | ||||
|     - exhaustivestruct | ||||
|     - forbidigo | ||||
|     - wrapcheck | ||||
|  | ||||
|     - varnamelen | ||||
|     - ireturn | ||||
|     - errorlint | ||||
|     - tparallel | ||||
|     - wrapcheck | ||||
|     - paralleltest | ||||
|     - makezero | ||||
|     - thelper | ||||
|     - cyclop | ||||
|     - revive | ||||
|     - importas | ||||
|     - gomoddirectives | ||||
|     - promlinter | ||||
|     - tagliatelle | ||||
|     - errname | ||||
|     - typecheck | ||||
| # rules to deal with reported isues | ||||
| issues: | ||||
|   # List of regexps of issue texts to exclude, empty list by default. | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| FROM alpine:edge AS builder | ||||
| FROM alpine AS builder | ||||
|  | ||||
| COPY . /go/src/matterbridge | ||||
| RUN apk --no-cache add go git \ | ||||
|   | ||||
							
								
								
									
										14
									
								
								Dockerfile_whatsappmulti
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								Dockerfile_whatsappmulti
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| FROM alpine AS builder | ||||
|  | ||||
| COPY . /go/src/matterbridge | ||||
| RUN apk --no-cache add go git \ | ||||
|         && cd /go/src/matterbridge \ | ||||
|         && CGO_ENABLED=0 go build -tags whatsappmulti -mod vendor -ldflags "-X github.com/42wim/matterbridge/version.GitHash=$(git log --pretty=format:'%h' -n 1)" -o /bin/matterbridge | ||||
|  | ||||
| FROM alpine | ||||
| RUN apk --no-cache add ca-certificates mailcap | ||||
| COPY --from=builder /bin/matterbridge /bin/matterbridge | ||||
| RUN mkdir /etc/matterbridge \ | ||||
|   && touch /etc/matterbridge/matterbridge.toml \ | ||||
|   && ln -sf /matterbridge.toml /etc/matterbridge/matterbridge.toml | ||||
| ENTRYPOINT ["/bin/matterbridge", "-conf", "/etc/matterbridge/matterbridge.toml"] | ||||
							
								
								
									
										76
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										76
									
								
								README.md
									
									
									
									
									
								
							| @@ -58,21 +58,22 @@ And more... | ||||
|     - [Binaries](#binaries) | ||||
|     - [Packages](#packages) | ||||
|   - [Building](#building) | ||||
|   - [Building with whatsapp (beta) multidevice support](#building-with-whatsapp-beta-multidevice-support) | ||||
|   - [Configuration](#configuration) | ||||
|     - [Basic configuration](#basic-configuration) | ||||
|     - [Settings](#settings) | ||||
|     - [Advanced configuration](#advanced-configuration) | ||||
|     - [Examples](#examples) | ||||
|       - [Bridge mattermost (off-topic) - irc (#testing)](#bridge-mattermost-off-topic---irc-testing) | ||||
|       - [Bridge slack (#general) - discord (general)](#bridge-slack-general---discord-general) | ||||
|   - [Running](#running) | ||||
|     - [Docker](#docker) | ||||
|     - [Systemd](#systemd) | ||||
|   - [Changelog](#changelog) | ||||
|   - [FAQ](#faq) | ||||
|   - [Related projects](#related-projects) | ||||
|   - [Articles / Tutorials](#articles--tutorials) | ||||
|   - [Thanks](#thanks) | ||||
|       - [Examples](#examples) | ||||
|         - [Bridge mattermost (off-topic) - irc (#testing)](#bridge-mattermost-off-topic---irc-testing) | ||||
|         - [Bridge slack (#general) - discord (general)](#bridge-slack-general---discord-general) | ||||
|     - [Running](#running) | ||||
|       - [Docker](#docker) | ||||
|       - [Systemd](#systemd) | ||||
|     - [Changelog](#changelog) | ||||
|     - [FAQ](#faq) | ||||
|     - [Related projects](#related-projects) | ||||
|     - [Articles / Tutorials](#articles--tutorials) | ||||
|     - [Thanks](#thanks) | ||||
|  | ||||
| ## Features | ||||
|  | ||||
| @@ -89,6 +90,7 @@ And more... | ||||
|  | ||||
| - [Discord](https://discordapp.com) | ||||
| - [Gitter](https://gitter.im) | ||||
| - [Harmony](https://harmonyapp.io) | ||||
| - [IRC](http://www.mirc.com/servers.html) | ||||
| - [Keybase](https://keybase.io) | ||||
| - [Matrix](https://matrix.org) | ||||
| @@ -105,6 +107,8 @@ And more... | ||||
| - [Twitch](https://twitch.tv) | ||||
| - [VK](https://vk.com/) | ||||
| - [WhatsApp](https://www.whatsapp.com/) | ||||
|   - Whatsapp legacy is natively supported | ||||
|   - Whatsapp multidevice beta is natively supported but you need to build yourself, see [here](#building-with-whatsapp-beta-multidevice-support) | ||||
| - [XMPP](https://xmpp.org) | ||||
| - [Zulip](https://zulipchat.com) | ||||
|  | ||||
| @@ -120,6 +124,8 @@ And more... | ||||
| - [Counter-Strike, half-life and more](https://forums.alliedmods.net/showthread.php?t=319430) | ||||
| - [MatterAMXX](https://github.com/GabeIggy/MatterAMXX) | ||||
| - [Vintage Story](https://github.com/NikkyAI/vs-matterbridge) | ||||
| - [Ultima Online Emulator](https://github.com/kuoushi/ServUO-Matterbridge) | ||||
| - [Teamspeak](https://github.com/Archeb/ts-matterbridge) | ||||
|  | ||||
| ### API | ||||
|  | ||||
| @@ -138,6 +144,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) | ||||
| - [MatterAMXX](https://forums.alliedmods.net/showthread.php?t=319430) (Counter-Strike, half-life and more via AMXX mod) | ||||
| - [Vintage Story](https://github.com/NikkyAI/vs-matterbridge) | ||||
| - [ServUO-matterbridge](https://github.com/kuoushi/ServUO-Matterbridge) (A matterbridge connector for ServUO servers) | ||||
| - [ts-matterbridge](https://github.com/Archeb/ts-matterbridge) (Integrate teamspeak chat with matterbridge) | ||||
|  | ||||
| ## Chat with us | ||||
|  | ||||
| @@ -164,10 +172,10 @@ See <https://github.com/42wim/matterbridge/wiki> | ||||
|  | ||||
| ### Binaries | ||||
|  | ||||
| - Latest stable release [v1.23.2](https://github.com/42wim/matterbridge/releases/latest) | ||||
| - Latest stable release [v1.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. | ||||
|  | ||||
| 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 | ||||
|  | ||||
| @@ -182,6 +190,11 @@ 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: | ||||
| Go 1.17+ is required. Make sure you have [Go](https://golang.org/doc/install) properly installed. | ||||
|  | ||||
| Building the binary with **all** the bridges enabled needs about 3GB RAM to compile. | ||||
| You can reduce this memory requirement to 0,5GB RAM by adding the `nomsteams` tag if you don't need/use the Microsoft Teams bridge. | ||||
|  | ||||
| Matterbridge can be build without gcc/c-compiler: If you're running on windows first run `set CGO_ENABLED=0` on other platforms you prepend `CGO_ENABLED=0` to the `go build` command. (eg `CGO_ENABLED=0 go install github.com/42wim/matterbridge`) | ||||
|  | ||||
| To install the latest stable run: | ||||
|  | ||||
| ```bash | ||||
| @@ -194,6 +207,38 @@ To install the latest dev run: | ||||
| go install github.com/42wim/matterbridge@master | ||||
| ``` | ||||
|  | ||||
| To install the latest stable run without msteams or zulip bridge: | ||||
|  | ||||
| ```bash | ||||
| go install -tags nomsteams,nozulip github.com/42wim/matterbridge | ||||
| ``` | ||||
|  | ||||
| You should now have matterbridge binary in the ~/go/bin directory: | ||||
|  | ||||
| ```bash | ||||
| $ ls ~/go/bin/ | ||||
| matterbridge | ||||
| ``` | ||||
|  | ||||
| ## Building with whatsapp (beta) multidevice support | ||||
|  | ||||
| Because the library we use for Whatsapp multidevice support includes a GPL3 library we can not provide you binaries. | ||||
| (as this would require the Matterbridge to change it license to GPL) | ||||
|  | ||||
| Matterbridge can be build without gcc/c-compiler: If you're running on windows first run `set CGO_ENABLED=0` on other platforms you prepend `CGO_ENABLED=0` to the `go build` command. (eg `CGO_ENABLED=0 go install github.com/42wim/matterbridge`) | ||||
|  | ||||
| So this means you have to build it yourself using the instructions below: | ||||
|  | ||||
| ```bash | ||||
| go install -tags whatsappmulti github.com/42wim/matterbridge@master | ||||
| ``` | ||||
|  | ||||
| If you're low on memory and don't need msteams: | ||||
|  | ||||
| ```bash | ||||
| go install -tags nomsteams,whatsappmulti github.com/42wim/matterbridge@master | ||||
| ``` | ||||
|  | ||||
| You should now have matterbridge binary in the ~/go/bin directory: | ||||
|  | ||||
| ```bash | ||||
| @@ -323,6 +368,8 @@ See [FAQ](https://github.com/42wim/matterbridge/wiki/FAQ) | ||||
| - [nextcloud talk](https://github.com/nextcloud/talk_matterbridge) (Integrates matterbridge in Nextcloud Talk) | ||||
| - [mattercraft](https://github.com/raws/mattercraft) (Minecraft bridge) | ||||
| - [vs-matterbridge](https://github.com/NikkyAI/vs-matterbridge) (Vintage Story bridge) | ||||
| - [ServUO-matterbridge](https://github.com/kuoushi/ServUO-Matterbridge) (A matterbridge connector for ServUO servers) | ||||
| - [ts-matterbridge](https://github.com/Archeb/ts-matterbridge) (Integrate teamspeak chat with matterbridge) | ||||
|  | ||||
| ## Articles / Tutorials | ||||
|  | ||||
| @@ -356,6 +403,7 @@ Matterbridge wouldn't exist without these libraries: | ||||
| - gops - <https://github.com/google/gops> | ||||
| - gozulipbot - <https://github.com/ifo/gozulipbot> | ||||
| - gumble - <https://github.com/layeh/gumble> | ||||
| - harmony - <https://github.com/harmony-development/shibshib> | ||||
| - irc - <https://github.com/lrstanley/girc> | ||||
| - keybase - <https://github.com/keybase/go-keybase-chat-bot> | ||||
| - matrix - <https://github.com/matrix-org/gomatrix> | ||||
| @@ -363,6 +411,7 @@ Matterbridge wouldn't exist without these libraries: | ||||
| - msgraph.go - <https://github.com/yaegashi/msgraph.go> | ||||
| - mumble - <https://github.com/layeh/gumble> | ||||
| - nctalk - <https://github.com/gary-kim/go-nc-talk> | ||||
| - rocketchat - <https://github.com/RocketChat/Rocket.Chat.Go.SDK> | ||||
| - slack - <https://github.com/nlopes/slack> | ||||
| - sshchat - <https://github.com/shazow/ssh-chat> | ||||
| - steam - <https://github.com/Philipp15b/go-steam> | ||||
| @@ -370,6 +419,7 @@ Matterbridge wouldn't exist without these libraries: | ||||
| - tengo - <https://github.com/d5/tengo> | ||||
| - vk - <https://github.com/SevereCloud/vksdk> | ||||
| - whatsapp - <https://github.com/Rhymen/go-whatsapp> | ||||
| - whatsapp - <https://github.com/tulir/whatsmeow> | ||||
| - xmpp - <https://github.com/mattn/go-xmpp> | ||||
| - zulip - <https://github.com/ifo/gozulipbot> | ||||
|  | ||||
|   | ||||
| @@ -23,6 +23,7 @@ const ( | ||||
| 	EventRejoinChannels    = "rejoin_channels" | ||||
| 	EventUserAction        = "user_action" | ||||
| 	EventMsgDelete         = "msg_delete" | ||||
| 	EventFileDelete        = "file_delete" | ||||
| 	EventAPIConnected      = "api_connected" | ||||
| 	EventUserTyping        = "user_typing" | ||||
| 	EventGetChannelMembers = "get_channel_members" | ||||
| @@ -56,13 +57,14 @@ func (m Message) ParentValid() bool { | ||||
| } | ||||
|  | ||||
| type FileInfo struct { | ||||
| 	Name    string | ||||
| 	Data    *[]byte | ||||
| 	Comment string | ||||
| 	URL     string | ||||
| 	Size    int64 | ||||
| 	Avatar  bool | ||||
| 	SHA     string | ||||
| 	Name     string | ||||
| 	Data     *[]byte | ||||
| 	Comment  string | ||||
| 	URL      string | ||||
| 	Size     int64 | ||||
| 	Avatar   bool | ||||
| 	SHA      string | ||||
| 	NativeID string | ||||
| } | ||||
|  | ||||
| type ChannelInfo struct { | ||||
| @@ -168,7 +170,7 @@ type Protocol struct { | ||||
| 	UseTLS                 bool       // IRC | ||||
| 	UseDiscriminator       bool       // discord | ||||
| 	UseFirstName           bool       // telegram | ||||
| 	UseUserName            bool       // discord, matrix | ||||
| 	UseUserName            bool       // discord, matrix, mattermost | ||||
| 	UseInsecureURL         bool       // telegram | ||||
| 	UserName               string     // IRC | ||||
| 	VerboseJoinPart        bool       // IRC | ||||
|   | ||||
| @@ -10,10 +10,14 @@ import ( | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/discord/transmitter" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	"github.com/matterbridge/discordgo" | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| 	lru "github.com/hashicorp/golang-lru" | ||||
| ) | ||||
|  | ||||
| const MessageLength = 1950 | ||||
| const ( | ||||
| 	MessageLength = 1950 | ||||
| 	cFileUpload   = "file_upload" | ||||
| ) | ||||
|  | ||||
| type Bdiscord struct { | ||||
| 	*bridge.Config | ||||
| @@ -35,10 +39,20 @@ type Bdiscord struct { | ||||
| 	// Webhook specific logic | ||||
| 	useAutoWebhooks bool | ||||
| 	transmitter     *transmitter.Transmitter | ||||
| 	cache           *lru.Cache | ||||
| } | ||||
|  | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	b := &Bdiscord{Config: cfg} | ||||
| 	newCache, err := lru.New(5000) | ||||
| 	if err != nil { | ||||
| 		cfg.Log.Fatalf("Could not create LRU cache: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	b := &Bdiscord{ | ||||
| 		Config: cfg, | ||||
| 		cache:  newCache, | ||||
| 	} | ||||
|  | ||||
| 	b.userMemberMap = make(map[string]*discordgo.Member) | ||||
| 	b.nickMemberMap = make(map[string]*discordgo.Member) | ||||
| 	b.channelInfoMap = make(map[string]*config.ChannelInfo) | ||||
| @@ -69,12 +83,15 @@ func (b *Bdiscord) Connect() error { | ||||
| 	b.Log.Info("Connection succeeded") | ||||
| 	b.c.AddHandler(b.messageCreate) | ||||
| 	b.c.AddHandler(b.messageTyping) | ||||
| 	b.c.AddHandler(b.memberUpdate) | ||||
| 	b.c.AddHandler(b.messageUpdate) | ||||
| 	b.c.AddHandler(b.messageDelete) | ||||
| 	b.c.AddHandler(b.messageDeleteBulk) | ||||
| 	b.c.AddHandler(b.memberAdd) | ||||
| 	b.c.AddHandler(b.memberRemove) | ||||
| 	b.c.AddHandler(b.memberUpdate) | ||||
| 	if b.GetInt("debuglevel") == 1 { | ||||
| 		b.c.AddHandler(b.messageEvent) | ||||
| 	} | ||||
| 	// Add privileged intent for guild member tracking. This is needed to track nicks | ||||
| 	// for display names and @mention translation | ||||
| 	b.c.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsAllWithoutPrivileged | | ||||
| @@ -153,7 +170,7 @@ func (b *Bdiscord) Connect() error { | ||||
| 		return fmt.Errorf("use of removed WebhookURL setting") | ||||
| 	} | ||||
|  | ||||
| 	if b.GetInt("debuglevel") > 0 { | ||||
| 	if b.GetInt("debuglevel") == 2 { | ||||
| 		b.Log.Debug("enabling even more discord debug") | ||||
| 		b.c.Debug = true | ||||
| 	} | ||||
| @@ -255,7 +272,6 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) { | ||||
| 	// Handle prefix hint for unthreaded messages. | ||||
| 	if msg.ParentNotFound() { | ||||
| 		msg.ParentID = "" | ||||
| 		msg.Text = fmt.Sprintf("[thread]: %s", msg.Text) | ||||
| 	} | ||||
|  | ||||
| 	// Use webhook to send the message | ||||
| @@ -280,6 +296,21 @@ func (b *Bdiscord) handleEventBotUser(msg *config.Message, channelID string) (st | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	// Delete a file | ||||
| 	if msg.Event == config.EventFileDelete { | ||||
| 		if msg.ID == "" { | ||||
| 			return "", nil | ||||
| 		} | ||||
|  | ||||
| 		if fi, ok := b.cache.Get(cFileUpload + msg.ID); ok { | ||||
| 			err := b.c.ChannelMessageDelete(channelID, fi.(string)) // nolint:forcetypeassert | ||||
| 			b.cache.Remove(cFileUpload + msg.ID) | ||||
| 			return "", err | ||||
| 		} | ||||
|  | ||||
| 		return "", fmt.Errorf("file %s not found", msg.ID) | ||||
| 	} | ||||
|  | ||||
| 	// Upload a file if it exists | ||||
| 	if msg.Extra != nil { | ||||
| 		for _, rmsg := range helper.HandleExtra(msg, b.General) { | ||||
| @@ -327,7 +358,6 @@ func (b *Bdiscord) handleEventBotUser(msg *config.Message, channelID string) (st | ||||
|  | ||||
| // handleUploadFile handles native upload of files | ||||
| func (b *Bdiscord) handleUploadFile(msg *config.Message, channelID string) (string, error) { | ||||
| 	var err error | ||||
| 	for _, f := range msg.Extra["file"] { | ||||
| 		fi := f.(config.FileInfo) | ||||
| 		file := discordgo.File{ | ||||
| @@ -340,10 +370,15 @@ func (b *Bdiscord) handleUploadFile(msg *config.Message, channelID string) (stri | ||||
| 			Files:           []*discordgo.File{&file}, | ||||
| 			AllowedMentions: b.getAllowedMentions(), | ||||
| 		} | ||||
| 		_, err = b.c.ChannelMessageSendComplex(channelID, &m) | ||||
| 		res, err := b.c.ChannelMessageSendComplex(channelID, &m) | ||||
| 		if err != nil { | ||||
| 			return "", fmt.Errorf("file upload failed: %s", err) | ||||
| 		} | ||||
|  | ||||
| 		// link file_upload_nativeID (file ID from the original bridge) to our upload id | ||||
| 		// so that we can remove this later when it eg needs to be deleted | ||||
| 		b.cache.Add(cFileUpload+fi.NativeID, res.ID) | ||||
| 	} | ||||
|  | ||||
| 	return "", nil | ||||
| } | ||||
|   | ||||
| @@ -2,10 +2,15 @@ package bdiscord | ||||
|  | ||||
| import ( | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/matterbridge/discordgo" | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| 	"github.com/davecgh/go-spew/spew" | ||||
| ) | ||||
|  | ||||
| func (b *Bdiscord) messageDelete(s *discordgo.Session, m *discordgo.MessageDelete) { //nolint:unparam | ||||
| 	if m.GuildID != b.guildID { | ||||
| 		b.Log.Debugf("Ignoring messageDelete because it originates from a different guild") | ||||
| 		return | ||||
| 	} | ||||
| 	rmsg := config.Message{Account: b.Account, ID: m.ID, Event: config.EventMsgDelete, Text: config.EventMsgDelete} | ||||
| 	rmsg.Channel = b.getChannelName(m.ChannelID) | ||||
|  | ||||
| @@ -16,6 +21,10 @@ func (b *Bdiscord) messageDelete(s *discordgo.Session, m *discordgo.MessageDelet | ||||
|  | ||||
| // TODO(qaisjp): if other bridges support bulk deletions, it could be fanned out centrally | ||||
| func (b *Bdiscord) messageDeleteBulk(s *discordgo.Session, m *discordgo.MessageDeleteBulk) { //nolint:unparam | ||||
| 	if m.GuildID != b.guildID { | ||||
| 		b.Log.Debugf("Ignoring messageDeleteBulk because it originates from a different guild") | ||||
| 		return | ||||
| 	} | ||||
| 	for _, msgID := range m.Messages { | ||||
| 		rmsg := config.Message{ | ||||
| 			Account: b.Account, | ||||
| @@ -31,7 +40,15 @@ func (b *Bdiscord) messageDeleteBulk(s *discordgo.Session, m *discordgo.MessageD | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bdiscord) messageEvent(s *discordgo.Session, m *discordgo.Event) { | ||||
| 	b.Log.Debug(spew.Sdump(m.Struct)) | ||||
| } | ||||
|  | ||||
| func (b *Bdiscord) messageTyping(s *discordgo.Session, m *discordgo.TypingStart) { | ||||
| 	if m.GuildID != b.guildID { | ||||
| 		b.Log.Debugf("Ignoring messageTyping because it originates from a different guild") | ||||
| 		return | ||||
| 	} | ||||
| 	if !b.GetBool("ShowUserTyping") { | ||||
| 		return | ||||
| 	} | ||||
| @@ -47,11 +64,15 @@ func (b *Bdiscord) messageTyping(s *discordgo.Session, m *discordgo.TypingStart) | ||||
| } | ||||
|  | ||||
| func (b *Bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) { //nolint:unparam | ||||
| 	if m.GuildID != b.guildID { | ||||
| 		b.Log.Debugf("Ignoring messageUpdate because it originates from a different guild") | ||||
| 		return | ||||
| 	} | ||||
| 	if b.GetBool("EditDisable") { | ||||
| 		return | ||||
| 	} | ||||
| 	// only when message is actually edited | ||||
| 	if m.Message.EditedTimestamp != "" { | ||||
| 	if m.Message.EditedTimestamp != nil { | ||||
| 		b.Log.Debugf("Sending edit message") | ||||
| 		m.Content += b.GetString("EditSuffix") | ||||
| 		msg := &discordgo.MessageCreate{ | ||||
| @@ -62,6 +83,10 @@ func (b *Bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdat | ||||
| } | ||||
|  | ||||
| func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { //nolint:unparam | ||||
| 	if m.GuildID != b.guildID { | ||||
| 		b.Log.Debugf("Ignoring messageCreate because it originates from a different guild") | ||||
| 		return | ||||
| 	} | ||||
| 	var err error | ||||
|  | ||||
| 	// not relay our own messages | ||||
| @@ -82,8 +107,9 @@ func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat | ||||
|  | ||||
| 	rmsg := config.Message{Account: b.Account, Avatar: "https://cdn.discordapp.com/avatars/" + m.Author.ID + "/" + m.Author.Avatar + ".jpg", UserID: m.Author.ID, ID: m.ID} | ||||
|  | ||||
| 	b.Log.Debugf("== Receiving event %#v", m.Message) | ||||
|  | ||||
| 	if m.Content != "" { | ||||
| 		b.Log.Debugf("== Receiving event %#v", m.Message) | ||||
| 		m.Message.Content = b.replaceChannelMentions(m.Message.Content) | ||||
| 		rmsg.Text, err = m.ContentWithMoreMentionsReplaced(b.c) | ||||
| 		if err != nil { | ||||
| @@ -138,6 +164,10 @@ func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat | ||||
| } | ||||
|  | ||||
| func (b *Bdiscord) memberUpdate(s *discordgo.Session, m *discordgo.GuildMemberUpdate) { | ||||
| 	if m.GuildID != b.guildID { | ||||
| 		b.Log.Debugf("Ignoring memberUpdate because it originates from a different guild") | ||||
| 		return | ||||
| 	} | ||||
| 	if m.Member == nil { | ||||
| 		b.Log.Warnf("Received member update with no member information: %#v", m) | ||||
| 	} | ||||
| @@ -165,6 +195,13 @@ func (b *Bdiscord) memberUpdate(s *discordgo.Session, m *discordgo.GuildMemberUp | ||||
| } | ||||
|  | ||||
| func (b *Bdiscord) memberAdd(s *discordgo.Session, m *discordgo.GuildMemberAdd) { | ||||
| 	if m.GuildID != b.guildID { | ||||
| 		b.Log.Debugf("Ignoring memberAdd because it originates from a different guild") | ||||
| 		return | ||||
| 	} | ||||
| 	if b.GetBool("nosendjoinpart") { | ||||
| 		return | ||||
| 	} | ||||
| 	if m.Member == nil { | ||||
| 		b.Log.Warnf("Received member update with no member information: %#v", m) | ||||
| 		return | ||||
| @@ -186,6 +223,13 @@ func (b *Bdiscord) memberAdd(s *discordgo.Session, m *discordgo.GuildMemberAdd) | ||||
| } | ||||
|  | ||||
| func (b *Bdiscord) memberRemove(s *discordgo.Session, m *discordgo.GuildMemberRemove) { | ||||
| 	if m.GuildID != b.guildID { | ||||
| 		b.Log.Debugf("Ignoring memberRemove because it originates from a different guild") | ||||
| 		return | ||||
| 	} | ||||
| 	if b.GetBool("nosendjoinpart") { | ||||
| 		return | ||||
| 	} | ||||
| 	if m.Member == nil { | ||||
| 		b.Log.Warnf("Received member update with no member information: %#v", m) | ||||
| 		return | ||||
|   | ||||
| @@ -3,7 +3,7 @@ package bdiscord | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/matterbridge/discordgo" | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -6,7 +6,7 @@ import ( | ||||
| 	"strings" | ||||
| 	"unicode" | ||||
|  | ||||
| 	"github.com/matterbridge/discordgo" | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| ) | ||||
|  | ||||
| func (b *Bdiscord) getAllowedMentions() *discordgo.MessageAllowedMentions { | ||||
|   | ||||
| @@ -20,7 +20,7 @@ import ( | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/matterbridge/discordgo" | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package transmitter | ||||
|  | ||||
| import ( | ||||
| 	"github.com/matterbridge/discordgo" | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| ) | ||||
|  | ||||
| // isDiscordPermissionError returns false for nil, and true if a Discord RESTError with code discordgo.ErrorCodeMissionPermissions | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import ( | ||||
|  | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	"github.com/matterbridge/discordgo" | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| ) | ||||
|  | ||||
| // shouldMessageUseWebhooks checks if have a channel specific webhook, if we're not using auto webhooks | ||||
| @@ -82,16 +82,14 @@ func (b *Bdiscord) webhookSend(msg *config.Message, channelID string) (*discordg | ||||
| 				ContentType: "", | ||||
| 				Reader:      bytes.NewReader(*fi.Data), | ||||
| 			} | ||||
| 			content := "" | ||||
| 			if msg.Text == "" { | ||||
| 				content = fi.Comment | ||||
| 			} | ||||
| 			content := fi.Comment | ||||
|  | ||||
| 			_, e2 := b.transmitter.Send( | ||||
| 				channelID, | ||||
| 				&discordgo.WebhookParams{ | ||||
| 					Username:        msg.Username, | ||||
| 					AvatarURL:       msg.Avatar, | ||||
| 					File:            &file, | ||||
| 					Files:           []*discordgo.File{&file}, | ||||
| 					Content:         content, | ||||
| 					AllowedMentions: b.getAllowedMentions(), | ||||
| 				}, | ||||
|   | ||||
							
								
								
									
										252
									
								
								bridge/harmony/harmony.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										252
									
								
								bridge/harmony/harmony.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,252 @@ | ||||
| package harmony | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/harmony-development/shibshib" | ||||
| 	chatv1 "github.com/harmony-development/shibshib/gen/chat/v1" | ||||
| 	typesv1 "github.com/harmony-development/shibshib/gen/harmonytypes/v1" | ||||
| 	profilev1 "github.com/harmony-development/shibshib/gen/profile/v1" | ||||
| ) | ||||
|  | ||||
| type cachedProfile struct { | ||||
| 	data        *profilev1.GetProfileResponse | ||||
| 	lastUpdated time.Time | ||||
| } | ||||
|  | ||||
| type Bharmony struct { | ||||
| 	*bridge.Config | ||||
|  | ||||
| 	c            *shibshib.Client | ||||
| 	profileCache map[uint64]cachedProfile | ||||
| } | ||||
|  | ||||
| func uToStr(in uint64) string { | ||||
| 	return strconv.FormatUint(in, 10) | ||||
| } | ||||
|  | ||||
| func strToU(in string) (uint64, error) { | ||||
| 	return strconv.ParseUint(in, 10, 64) | ||||
| } | ||||
|  | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	b := &Bharmony{ | ||||
| 		Config:       cfg, | ||||
| 		profileCache: map[uint64]cachedProfile{}, | ||||
| 	} | ||||
|  | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func (b *Bharmony) getProfile(u uint64) (*profilev1.GetProfileResponse, error) { | ||||
| 	if v, ok := b.profileCache[u]; ok && time.Since(v.lastUpdated) < time.Minute*10 { | ||||
| 		return v.data, nil | ||||
| 	} | ||||
|  | ||||
| 	resp, err := b.c.ProfileKit.GetProfile(&profilev1.GetProfileRequest{ | ||||
| 		UserId: u, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		if v, ok := b.profileCache[u]; ok { | ||||
| 			return v.data, nil | ||||
| 		} | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	b.profileCache[u] = cachedProfile{ | ||||
| 		data:        resp, | ||||
| 		lastUpdated: time.Now(), | ||||
| 	} | ||||
| 	return resp, nil | ||||
| } | ||||
|  | ||||
| func (b *Bharmony) avatarFor(m *chatv1.Message) string { | ||||
| 	if m.Overrides != nil { | ||||
| 		return m.Overrides.GetAvatar() | ||||
| 	} | ||||
|  | ||||
| 	profi, err := b.getProfile(m.AuthorId) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return b.c.TransformHMCURL(profi.Profile.GetUserAvatar()) | ||||
| } | ||||
|  | ||||
| func (b *Bharmony) usernameFor(m *chatv1.Message) string { | ||||
| 	if m.Overrides != nil { | ||||
| 		return m.Overrides.GetUsername() | ||||
| 	} | ||||
|  | ||||
| 	profi, err := b.getProfile(m.AuthorId) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return profi.Profile.UserName | ||||
| } | ||||
|  | ||||
| func (b *Bharmony) toMessage(msg *shibshib.LocatedMessage) config.Message { | ||||
| 	message := config.Message{} | ||||
| 	message.Account = b.Account | ||||
| 	message.UserID = uToStr(msg.Message.AuthorId) | ||||
| 	message.Avatar = b.avatarFor(msg.Message) | ||||
| 	message.Username = b.usernameFor(msg.Message) | ||||
| 	message.Channel = uToStr(msg.ChannelID) | ||||
| 	message.ID = uToStr(msg.MessageId) | ||||
|  | ||||
| 	switch content := msg.Message.Content.Content.(type) { | ||||
| 	case *chatv1.Content_EmbedMessage: | ||||
| 		message.Text = "Embed" | ||||
| 	case *chatv1.Content_AttachmentMessage: | ||||
| 		var s strings.Builder | ||||
| 		for idx, attach := range content.AttachmentMessage.Files { | ||||
| 			s.WriteString(b.c.TransformHMCURL(attach.Id)) | ||||
| 			if idx < len(content.AttachmentMessage.Files)-1 { | ||||
| 				s.WriteString(", ") | ||||
| 			} | ||||
| 		} | ||||
| 		message.Text = s.String() | ||||
| 	case *chatv1.Content_PhotoMessage: | ||||
| 		var s strings.Builder | ||||
| 		for idx, attach := range content.PhotoMessage.GetPhotos() { | ||||
| 			s.WriteString(attach.GetCaption().GetText()) | ||||
| 			s.WriteString("\n") | ||||
| 			s.WriteString(b.c.TransformHMCURL(attach.GetHmc())) | ||||
| 			if idx < len(content.PhotoMessage.GetPhotos())-1 { | ||||
| 				s.WriteString("\n\n") | ||||
| 			} | ||||
| 		} | ||||
| 		message.Text = s.String() | ||||
| 	case *chatv1.Content_TextMessage: | ||||
| 		message.Text = content.TextMessage.Content.Text | ||||
| 	} | ||||
|  | ||||
| 	return message | ||||
| } | ||||
|  | ||||
| func (b *Bharmony) outputMessages() { | ||||
| 	for { | ||||
| 		msg := <-b.c.EventsStream() | ||||
|  | ||||
| 		if msg.Message.AuthorId == b.c.UserID { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		b.Remote <- b.toMessage(msg) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bharmony) GetUint64(conf string) uint64 { | ||||
| 	num, err := strToU(b.GetString(conf)) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	return num | ||||
| } | ||||
|  | ||||
| func (b *Bharmony) Connect() (err error) { | ||||
| 	b.c, err = shibshib.NewClient(b.GetString("Homeserver"), b.GetString("Token"), b.GetUint64("UserID")) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	b.c.SubscribeToGuild(b.GetUint64("Community")) | ||||
|  | ||||
| 	go b.outputMessages() | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Bharmony) send(msg config.Message) (id string, err error) { | ||||
| 	msgChan, err := strToU(msg.Channel) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	retID, err := b.c.ChatKit.SendMessage(&chatv1.SendMessageRequest{ | ||||
| 		GuildId:   b.GetUint64("Community"), | ||||
| 		ChannelId: msgChan, | ||||
| 		Content: &chatv1.Content{ | ||||
| 			Content: &chatv1.Content_TextMessage{ | ||||
| 				TextMessage: &chatv1.Content_TextContent{ | ||||
| 					Content: &chatv1.FormattedText{ | ||||
| 						Text: msg.Text, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		Overrides: &chatv1.Overrides{ | ||||
| 			Username: &msg.Username, | ||||
| 			Avatar:   &msg.Avatar, | ||||
| 			Reason:   &chatv1.Overrides_Bridge{Bridge: &typesv1.Empty{}}, | ||||
| 		}, | ||||
| 		InReplyTo: nil, | ||||
| 		EchoId:    nil, | ||||
| 		Metadata:  nil, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		err = fmt.Errorf("send: error sending message: %w", err) | ||||
| 		log.Println(err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	return uToStr(retID.MessageId), err | ||||
| } | ||||
|  | ||||
| func (b *Bharmony) delete(msg config.Message) (id string, err error) { | ||||
| 	msgChan, err := strToU(msg.Channel) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	msgID, err := strToU(msg.ID) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	_, err = b.c.ChatKit.DeleteMessage(&chatv1.DeleteMessageRequest{ | ||||
| 		GuildId:   b.GetUint64("Community"), | ||||
| 		ChannelId: msgChan, | ||||
| 		MessageId: msgID, | ||||
| 	}) | ||||
| 	return "", err | ||||
| } | ||||
|  | ||||
| func (b *Bharmony) typing(msg config.Message) (id string, err error) { | ||||
| 	msgChan, err := strToU(msg.Channel) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	_, err = b.c.ChatKit.Typing(&chatv1.TypingRequest{ | ||||
| 		GuildId:   b.GetUint64("Community"), | ||||
| 		ChannelId: msgChan, | ||||
| 	}) | ||||
| 	return "", err | ||||
| } | ||||
|  | ||||
| func (b *Bharmony) Send(msg config.Message) (id string, err error) { | ||||
| 	switch msg.Event { | ||||
| 	case "": | ||||
| 		return b.send(msg) | ||||
| 	case config.EventMsgDelete: | ||||
| 		return b.delete(msg) | ||||
| 	case config.EventUserTyping: | ||||
| 		return b.typing(msg) | ||||
| 	default: | ||||
| 		return "", nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bharmony) JoinChannel(channel config.ChannelInfo) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Bharmony) Disconnect() error { | ||||
| 	return nil | ||||
| } | ||||
| @@ -168,17 +168,23 @@ func HandleDownloadSize(logger *logrus.Entry, msg *config.Message, name string, | ||||
|  | ||||
| // HandleDownloadData adds the data for a remote file into a Matterbridge gateway message. | ||||
| func HandleDownloadData(logger *logrus.Entry, msg *config.Message, name, comment, url string, data *[]byte, general *config.Protocol) { | ||||
| 	HandleDownloadData2(logger, msg, name, "", comment, url, data, general) | ||||
| } | ||||
|  | ||||
| // HandleDownloadData adds the data for a remote file into a Matterbridge gateway message. | ||||
| func HandleDownloadData2(logger *logrus.Entry, msg *config.Message, name, id, comment, url string, data *[]byte, general *config.Protocol) { | ||||
| 	var avatar bool | ||||
| 	logger.Debugf("Download OK %#v %#v", name, len(*data)) | ||||
| 	if msg.Event == config.EventAvatarDownload { | ||||
| 		avatar = true | ||||
| 	} | ||||
| 	msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{ | ||||
| 		Name:    name, | ||||
| 		Data:    data, | ||||
| 		URL:     url, | ||||
| 		Comment: comment, | ||||
| 		Avatar:  avatar, | ||||
| 		Name:     name, | ||||
| 		Data:     data, | ||||
| 		URL:      url, | ||||
| 		Comment:  comment, | ||||
| 		Avatar:   avatar, | ||||
| 		NativeID: id, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										32
									
								
								bridge/irc/charset.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								bridge/irc/charset.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| package birc | ||||
|  | ||||
| import ( | ||||
| 	"golang.org/x/text/encoding" | ||||
| 	"golang.org/x/text/encoding/japanese" | ||||
| 	"golang.org/x/text/encoding/korean" | ||||
| 	"golang.org/x/text/encoding/simplifiedchinese" | ||||
| 	"golang.org/x/text/encoding/traditionalchinese" | ||||
| 	"golang.org/x/text/encoding/unicode" | ||||
| ) | ||||
|  | ||||
| var encoders = map[string]encoding.Encoding{ | ||||
| 	"utf-8":       unicode.UTF8, | ||||
| 	"iso-2022-jp": japanese.ISO2022JP, | ||||
| 	"big5":        traditionalchinese.Big5, | ||||
| 	"gbk":         simplifiedchinese.GBK, | ||||
| 	"euc-kr":      korean.EUCKR, | ||||
| 	"gb2312":      simplifiedchinese.HZGB2312, | ||||
| 	"shift-jis":   japanese.ShiftJIS, | ||||
| 	"euc-jp":      japanese.EUCJP, | ||||
| 	"gb18030":     simplifiedchinese.GB18030, | ||||
| } | ||||
|  | ||||
| func toUTF8(from string, input string) string { | ||||
| 	enc, ok := encoders[from] | ||||
| 	if !ok { | ||||
| 		return input | ||||
| 	} | ||||
|  | ||||
| 	res, _ := enc.NewDecoder().String(input) | ||||
| 	return res | ||||
| } | ||||
| @@ -11,7 +11,6 @@ import ( | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	"github.com/lrstanley/girc" | ||||
| 	"github.com/missdeer/golib/ic" | ||||
| 	"github.com/paulrosania/go-charset/charset" | ||||
| 	"github.com/saintfish/chardet" | ||||
|  | ||||
| @@ -24,12 +23,12 @@ func (b *Birc) handleCharset(msg *config.Message) error { | ||||
| 	if b.GetString("Charset") != "" { | ||||
| 		switch b.GetString("Charset") { | ||||
| 		case "gbk", "gb18030", "gb2312", "big5", "euc-kr", "euc-jp", "shift-jis", "iso-2022-jp": | ||||
| 			msg.Text = ic.ConvertString("utf-8", b.GetString("Charset"), msg.Text) | ||||
| 			msg.Text = toUTF8(b.GetString("Charset"), msg.Text) | ||||
| 		default: | ||||
| 			buf := new(bytes.Buffer) | ||||
| 			w, err := charset.NewWriter(b.GetString("Charset"), buf) | ||||
| 			if err != nil { | ||||
| 				b.Log.Errorf("charset from utf-8 conversion failed: %s", err) | ||||
| 				b.Log.Errorf("charset to utf-8 conversion failed: %s", err) | ||||
| 				return err | ||||
| 			} | ||||
| 			fmt.Fprint(w, msg.Text) | ||||
| @@ -227,7 +226,7 @@ func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) { | ||||
| 	} | ||||
| 	switch mycharset { | ||||
| 	case "gbk", "gb18030", "gb2312", "big5", "euc-kr", "euc-jp", "shift-jis", "iso-2022-jp": | ||||
| 		rmsg.Text = ic.ConvertString("utf-8", b.GetString("Charset"), rmsg.Text) | ||||
| 		rmsg.Text = toUTF8(b.GetString("Charset"), rmsg.Text) | ||||
| 	default: | ||||
| 		r, err := charset.NewReader(mycharset, strings.NewReader(rmsg.Text)) | ||||
| 		if err != nil { | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package birc | ||||
|  | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"hash/crc32" | ||||
| 	"io/ioutil" | ||||
| @@ -72,6 +73,10 @@ func (b *Birc) Command(msg *config.Message) string { | ||||
| } | ||||
|  | ||||
| func (b *Birc) Connect() error { | ||||
| 	if b.GetBool("UseSASL") && b.GetString("TLSClientCertificate") != "" { | ||||
| 		return errors.New("you can't enable SASL and TLSClientCertificate at the same time") | ||||
| 	} | ||||
|  | ||||
| 	b.Local = make(chan config.Message, b.MessageQueue+10) | ||||
| 	b.Log.Infof("Connecting %s", b.GetString("Server")) | ||||
|  | ||||
| @@ -300,6 +305,11 @@ func (b *Birc) getClient() (*girc.Client, error) { | ||||
|  | ||||
| 	b.Log.Debugf("setting pingdelay to %s", pingDelay) | ||||
|  | ||||
| 	tlsConfig, err := b.getTLSConfig() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	i := girc.New(girc.Config{ | ||||
| 		Server:     server, | ||||
| 		ServerPass: b.GetString("Password"), | ||||
| @@ -308,7 +318,8 @@ func (b *Birc) getClient() (*girc.Client, error) { | ||||
| 		User:       user, | ||||
| 		Name:       realName, | ||||
| 		SSL:        b.GetBool("UseTLS"), | ||||
| 		TLSConfig:  &tls.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), ServerName: server}, //nolint:gosec | ||||
| 		Bind:       b.GetString("Bind"), | ||||
| 		TLSConfig:  tlsConfig, | ||||
| 		PingDelay:  pingDelay, | ||||
| 		// skip gIRC internal rate limiting, since we have our own throttling | ||||
| 		AllowFlood:    true, | ||||
| @@ -351,8 +362,10 @@ func (b *Birc) skipPrivMsg(event girc.Event) bool { | ||||
| 		return true | ||||
| 	} | ||||
| 	// don't forward message from ourself | ||||
| 	if event.Source.Name == b.Nick { | ||||
| 		return true | ||||
| 	if event.Source != nil { | ||||
| 		if event.Source.Name == b.Nick { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	// don't forward messages we sent via RELAYMSG | ||||
| 	if relayedNick, ok := event.Tags.Get("draft/relaymsg"); ok && relayedNick == b.Nick { | ||||
| @@ -380,3 +393,23 @@ func (b *Birc) storeNames(client *girc.Client, event girc.Event) { | ||||
| func (b *Birc) formatnicks(nicks []string) string { | ||||
| 	return strings.Join(nicks, ", ") + " currently on IRC" | ||||
| } | ||||
|  | ||||
| func (b *Birc) getTLSConfig() (*tls.Config, error) { | ||||
| 	server, _, _ := net.SplitHostPort(b.GetString("server")) | ||||
|  | ||||
| 	tlsConfig := &tls.Config{ | ||||
| 		InsecureSkipVerify: b.GetBool("skiptlsverify"), //nolint:gosec | ||||
| 		ServerName:         server, | ||||
| 	} | ||||
|  | ||||
| 	if filename := b.GetString("TLSClientCertificate"); filename != "" { | ||||
| 		cert, err := tls.LoadX509KeyPair(filename, filename) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		tlsConfig.Certificates = []tls.Certificate{cert} | ||||
| 	} | ||||
|  | ||||
| 	return tlsConfig, nil | ||||
| } | ||||
|   | ||||
| @@ -8,7 +8,7 @@ import ( | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	matrix "github.com/matrix-org/gomatrix" | ||||
| 	matrix "github.com/matterbridge/gomatrix" | ||||
| ) | ||||
|  | ||||
| func newMatrixUsername(username string) *matrixUsername { | ||||
|   | ||||
| @@ -12,7 +12,7 @@ import ( | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	matrix "github.com/matrix-org/gomatrix" | ||||
| 	matrix "github.com/matterbridge/gomatrix" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| @@ -48,8 +48,10 @@ type matrixUsername struct { | ||||
|  | ||||
| // SubTextMessage represents the new content of the message in edit messages. | ||||
| type SubTextMessage struct { | ||||
| 	MsgType string `json:"msgtype"` | ||||
| 	Body    string `json:"body"` | ||||
| 	MsgType       string `json:"msgtype"` | ||||
| 	Body          string `json:"body"` | ||||
| 	FormattedBody string `json:"formatted_body,omitempty"` | ||||
| 	Format        string `json:"format,omitempty"` | ||||
| } | ||||
|  | ||||
| // MessageRelation explains how the current message relates to a previous message. | ||||
| @@ -65,6 +67,19 @@ type EditedMessage struct { | ||||
| 	matrix.TextMessage | ||||
| } | ||||
|  | ||||
| type InReplyToRelationContent struct { | ||||
| 	EventID string `json:"event_id"` | ||||
| } | ||||
|  | ||||
| type InReplyToRelation struct { | ||||
| 	InReplyTo InReplyToRelationContent `json:"m.in_reply_to"` | ||||
| } | ||||
|  | ||||
| type ReplyMessage struct { | ||||
| 	RelatedTo InReplyToRelation `json:"m.relates_to"` | ||||
| 	matrix.TextMessage | ||||
| } | ||||
|  | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	b := &Bmatrix{Config: cfg} | ||||
| 	b.RoomMap = make(map[string]string) | ||||
| @@ -138,7 +153,13 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) { | ||||
| 		m := matrix.TextMessage{ | ||||
| 			MsgType:       "m.emote", | ||||
| 			Body:          username.plain + msg.Text, | ||||
| 			FormattedBody: username.formatted + msg.Text, | ||||
| 			FormattedBody: username.formatted + helper.ParseMarkdown(msg.Text), | ||||
| 			Format:        "org.matrix.custom.html", | ||||
| 		} | ||||
|  | ||||
| 		if b.GetBool("HTMLDisable") { | ||||
| 			m.Format = "" | ||||
| 			m.FormattedBody = "" | ||||
| 		} | ||||
|  | ||||
| 		msgID := "" | ||||
| @@ -201,20 +222,29 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) { | ||||
|  | ||||
| 	// Edit message if we have an ID | ||||
| 	if msg.ID != "" { | ||||
| 		rmsg := EditedMessage{TextMessage: matrix.TextMessage{ | ||||
| 			Body:    username.plain + msg.Text, | ||||
| 			MsgType: "m.text", | ||||
| 		}} | ||||
| 		if b.GetBool("HTMLDisable") { | ||||
| 			rmsg.TextMessage.FormattedBody = username.formatted + "* " + msg.Text | ||||
| 		} else { | ||||
| 			rmsg.Format = "org.matrix.custom.html" | ||||
| 			rmsg.TextMessage.FormattedBody = username.formatted + "* " + helper.ParseMarkdown(msg.Text) | ||||
| 		rmsg := EditedMessage{ | ||||
| 			TextMessage: matrix.TextMessage{ | ||||
| 				Body:          username.plain + msg.Text, | ||||
| 				MsgType:       "m.text", | ||||
| 				Format:        "org.matrix.custom.html", | ||||
| 				FormattedBody: username.formatted + helper.ParseMarkdown(msg.Text), | ||||
| 			}, | ||||
| 		} | ||||
|  | ||||
| 		rmsg.NewContent = SubTextMessage{ | ||||
| 			Body:    rmsg.TextMessage.Body, | ||||
| 			MsgType: "m.text", | ||||
| 			Body:          rmsg.TextMessage.Body, | ||||
| 			FormattedBody: rmsg.TextMessage.FormattedBody, | ||||
| 			Format:        rmsg.TextMessage.Format, | ||||
| 			MsgType:       "m.text", | ||||
| 		} | ||||
|  | ||||
| 		if b.GetBool("HTMLDisable") { | ||||
| 			rmsg.TextMessage.Format = "" | ||||
| 			rmsg.TextMessage.FormattedBody = "" | ||||
| 			rmsg.NewContent.Format = "" | ||||
| 			rmsg.NewContent.FormattedBody = "" | ||||
| 		} | ||||
|  | ||||
| 		rmsg.RelatedTo = MessageRelation{ | ||||
| 			EventID: msg.ID, | ||||
| 			Type:    "m.replace", | ||||
| @@ -238,6 +268,50 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) { | ||||
| 			MsgType:       "m.notice", | ||||
| 			Body:          username.plain + msg.Text, | ||||
| 			FormattedBody: username.formatted + msg.Text, | ||||
| 			Format:        "org.matrix.custom.html", | ||||
| 		} | ||||
|  | ||||
| 		if b.GetBool("HTMLDisable") { | ||||
| 			m.Format = "" | ||||
| 			m.FormattedBody = "" | ||||
| 		} | ||||
|  | ||||
| 		var ( | ||||
| 			resp *matrix.RespSendEvent | ||||
| 			err  error | ||||
| 		) | ||||
|  | ||||
| 		err = b.retry(func() error { | ||||
| 			resp, err = b.mc.SendMessageEvent(channel, "m.room.message", m) | ||||
|  | ||||
| 			return err | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
|  | ||||
| 		return resp.EventID, err | ||||
| 	} | ||||
|  | ||||
| 	if msg.ParentValid() { | ||||
| 		m := ReplyMessage{ | ||||
| 			TextMessage: matrix.TextMessage{ | ||||
| 				MsgType:       "m.text", | ||||
| 				Body:          username.plain + msg.Text, | ||||
| 				FormattedBody: username.formatted + helper.ParseMarkdown(msg.Text), | ||||
| 				Format:        "org.matrix.custom.html", | ||||
| 			}, | ||||
| 		} | ||||
|  | ||||
| 		if b.GetBool("HTMLDisable") { | ||||
| 			m.TextMessage.Format = "" | ||||
| 			m.TextMessage.FormattedBody = "" | ||||
| 		} | ||||
|  | ||||
| 		m.RelatedTo = InReplyToRelation{ | ||||
| 			InReplyTo: InReplyToRelationContent{ | ||||
| 				EventID: msg.ParentID, | ||||
| 			}, | ||||
| 		} | ||||
|  | ||||
| 		var ( | ||||
| @@ -341,6 +415,38 @@ func (b *Bmatrix) handleEdit(ev *matrix.Event, rmsg config.Message) bool { | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func (b *Bmatrix) handleReply(ev *matrix.Event, rmsg config.Message) bool { | ||||
| 	relationInterface, present := ev.Content["m.relates_to"] | ||||
| 	if !present { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	var relation InReplyToRelation | ||||
| 	if err := interface2Struct(relationInterface, &relation); err != nil { | ||||
| 		// probably fine | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	body := rmsg.Text | ||||
|  | ||||
| 	if !b.GetBool("keepquotedreply") { | ||||
| 		for strings.HasPrefix(body, "> ") { | ||||
| 			lineIdx := strings.IndexRune(body, '\n') | ||||
| 			if lineIdx == -1 { | ||||
| 				body = "" | ||||
| 			} else { | ||||
| 				body = body[(lineIdx + 1):] | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	rmsg.Text = body | ||||
| 	rmsg.ParentID = relation.InReplyTo.EventID | ||||
| 	b.Remote <- rmsg | ||||
|  | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func (b *Bmatrix) handleMemberChange(ev *matrix.Event) { | ||||
| 	// Update the displayname on join messages, according to https://matrix.org/docs/spec/client_server/r0.6.1#events-on-change-of-profile-information | ||||
| 	if ev.Content["membership"] == "join" { | ||||
| @@ -403,6 +509,11 @@ func (b *Bmatrix) handleEvent(ev *matrix.Event) { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		// Is it a reply? | ||||
| 		if b.handleReply(ev, rmsg) { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		// Do we have attachments | ||||
| 		if b.containsAttachment(ev.Content) { | ||||
| 			err := b.handleDownloadFile(&rmsg, ev.Content) | ||||
|   | ||||
| @@ -140,9 +140,14 @@ func (b *Bmattermost) handleMatterClient(messages chan *config.Message) { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		channelName := b.getChannelName(message.Post.ChannelId) | ||||
| 		if channelName == "" { | ||||
| 			channelName = message.Channel | ||||
| 		} | ||||
|  | ||||
| 		// only download avatars if we have a place to upload them (configured mediaserver) | ||||
| 		if b.General.MediaServerUpload != "" || b.General.MediaDownloadPath != "" { | ||||
| 			b.handleDownloadAvatar(message.UserID, message.Channel) | ||||
| 			b.handleDownloadAvatar(message.UserID, channelName) | ||||
| 		} | ||||
|  | ||||
| 		b.Log.Debugf("== Receiving event %#v", message) | ||||
| @@ -150,7 +155,7 @@ func (b *Bmattermost) handleMatterClient(messages chan *config.Message) { | ||||
| 		rmsg := &config.Message{ | ||||
| 			Username: message.Username, | ||||
| 			UserID:   message.UserID, | ||||
| 			Channel:  message.Channel, | ||||
| 			Channel:  channelName, | ||||
| 			Text:     message.Text, | ||||
| 			ID:       message.Post.Id, | ||||
| 			ParentID: message.Post.RootId, // ParentID is obsolete with mattermost | ||||
| @@ -177,8 +182,10 @@ func (b *Bmattermost) handleMatterClient(messages chan *config.Message) { | ||||
| 		} | ||||
|  | ||||
| 		// Use nickname instead of username if defined | ||||
| 		if nick := b.mc.GetNickName(rmsg.UserID); nick != "" { | ||||
| 			rmsg.Username = nick | ||||
| 		if !b.GetBool("useusername") { | ||||
| 			if nick := b.mc.GetNickName(rmsg.UserID); nick != "" { | ||||
| 				rmsg.Username = nick | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		messages <- rmsg | ||||
| @@ -188,16 +195,21 @@ func (b *Bmattermost) handleMatterClient(messages chan *config.Message) { | ||||
| // nolint:cyclop | ||||
| func (b *Bmattermost) handleMatterClient6(messages chan *config.Message) { | ||||
| 	for message := range b.mc6.MessageChan { | ||||
| 		b.Log.Debugf("%#v", message.Raw.GetData()) | ||||
| 		b.Log.Debugf("%#v %#v", message.Raw.GetData(), message.Raw.EventType()) | ||||
|  | ||||
| 		if b.skipMessage6(message) { | ||||
| 			b.Log.Debugf("Skipped message: %#v", message) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		channelName := b.getChannelName(message.Post.ChannelId) | ||||
| 		if channelName == "" { | ||||
| 			channelName = message.Channel | ||||
| 		} | ||||
|  | ||||
| 		// only download avatars if we have a place to upload them (configured mediaserver) | ||||
| 		if b.General.MediaServerUpload != "" || b.General.MediaDownloadPath != "" { | ||||
| 			b.handleDownloadAvatar(message.UserID, message.Channel) | ||||
| 			b.handleDownloadAvatar(message.UserID, channelName) | ||||
| 		} | ||||
|  | ||||
| 		b.Log.Debugf("== Receiving event %#v", message) | ||||
| @@ -205,7 +217,7 @@ func (b *Bmattermost) handleMatterClient6(messages chan *config.Message) { | ||||
| 		rmsg := &config.Message{ | ||||
| 			Username: message.Username, | ||||
| 			UserID:   message.UserID, | ||||
| 			Channel:  message.Channel, | ||||
| 			Channel:  channelName, | ||||
| 			Text:     message.Text, | ||||
| 			ID:       message.Post.Id, | ||||
| 			ParentID: message.Post.RootId, // ParentID is obsolete with mattermost | ||||
| @@ -232,8 +244,10 @@ func (b *Bmattermost) handleMatterClient6(messages chan *config.Message) { | ||||
| 		} | ||||
|  | ||||
| 		// Use nickname instead of username if defined | ||||
| 		if nick := b.mc6.GetNickName(rmsg.UserID); nick != "" { | ||||
| 			rmsg.Username = nick | ||||
| 		if !b.GetBool("useusername") { | ||||
| 			if nick := b.mc6.GetNickName(rmsg.UserID); nick != "" { | ||||
| 				rmsg.Username = nick | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		messages <- rmsg | ||||
| @@ -244,6 +258,7 @@ func (b *Bmattermost) handleMatterHook(messages chan *config.Message) { | ||||
| 	for { | ||||
| 		message := b.mh.Receive() | ||||
| 		b.Log.Debugf("Receiving from matterhook %#v", message) | ||||
|  | ||||
| 		messages <- &config.Message{ | ||||
| 			UserID:   message.UserID, | ||||
| 			Username: message.UserName, | ||||
| @@ -261,7 +276,7 @@ func (b *Bmattermost) handleUploadFile(msg *config.Message) (string, error) { | ||||
|  | ||||
| 	var err error | ||||
| 	var res, id string | ||||
| 	channelID := b.mc.GetChannelId(msg.Channel, b.TeamID) | ||||
| 	channelID := b.getChannelID(msg.Channel) | ||||
| 	for _, f := range msg.Extra["file"] { | ||||
| 		fi := f.(config.FileInfo) | ||||
| 		id, err = b.mc.UploadFile(*fi.Data, channelID, fi.Name) | ||||
| @@ -281,7 +296,7 @@ func (b *Bmattermost) handleUploadFile(msg *config.Message) (string, error) { | ||||
| func (b *Bmattermost) handleUploadFile6(msg *config.Message) (string, error) { | ||||
| 	var err error | ||||
| 	var res, id string | ||||
| 	channelID := b.mc6.GetChannelID(msg.Channel, b.TeamID) | ||||
| 	channelID := b.getChannelID(msg.Channel) | ||||
| 	for _, f := range msg.Extra["file"] { | ||||
| 		fi := f.(config.FileInfo) | ||||
| 		id, err = b.mc6.UploadFile(*fi.Data, channelID, fi.Name) | ||||
|   | ||||
| @@ -183,6 +183,7 @@ func (b *Bmattermost) sendWebhook(msg config.Message) (string, error) { | ||||
| 	if b.GetBool("PrefixMessagesWithNick") { | ||||
| 		msg.Text = msg.Username + msg.Text | ||||
| 	} | ||||
|  | ||||
| 	if msg.Extra != nil { | ||||
| 		// this sends a message only if we received a config.EVENT_FILE_FAILURE_SIZE | ||||
| 		for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||
| @@ -206,7 +207,7 @@ func (b *Bmattermost) sendWebhook(msg config.Message) (string, error) { | ||||
| 			for _, f := range msg.Extra["file"] { | ||||
| 				fi := f.(config.FileInfo) | ||||
| 				if fi.URL != "" { | ||||
| 					msg.Text += fi.URL | ||||
| 					msg.Text += " " + fi.URL | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| @@ -241,11 +242,17 @@ func (b *Bmattermost) skipMessage(message *matterclient.Message) bool { | ||||
| 		if b.GetBool("nosendjoinpart") { | ||||
| 			return true | ||||
| 		} | ||||
|  | ||||
| 		channelName := b.getChannelName(message.Post.ChannelId) | ||||
| 		if channelName == "" { | ||||
| 			channelName = message.Channel | ||||
| 		} | ||||
|  | ||||
| 		b.Log.Debugf("Sending JOIN_LEAVE event from %s to gateway", b.Account) | ||||
| 		b.Remote <- config.Message{ | ||||
| 			Username: "system", | ||||
| 			Text:     message.Text, | ||||
| 			Channel:  message.Channel, | ||||
| 			Channel:  channelName, | ||||
| 			Account:  b.Account, | ||||
| 			Event:    config.EventJoinLeave, | ||||
| 		} | ||||
| @@ -304,11 +311,17 @@ func (b *Bmattermost) skipMessage6(message *matterclient6.Message) bool { | ||||
| 		if b.GetBool("nosendjoinpart") { | ||||
| 			return true | ||||
| 		} | ||||
|  | ||||
| 		channelName := b.getChannelName(message.Post.ChannelId) | ||||
| 		if channelName == "" { | ||||
| 			channelName = message.Channel | ||||
| 		} | ||||
|  | ||||
| 		b.Log.Debugf("Sending JOIN_LEAVE event from %s to gateway", b.Account) | ||||
| 		b.Remote <- config.Message{ | ||||
| 			Username: "system", | ||||
| 			Text:     message.Text, | ||||
| 			Channel:  message.Channel, | ||||
| 			Channel:  channelName, | ||||
| 			Account:  b.Account, | ||||
| 			Event:    config.EventJoinLeave, | ||||
| 		} | ||||
| @@ -329,13 +342,14 @@ func (b *Bmattermost) skipMessage6(message *matterclient6.Message) bool { | ||||
| 	// Ignore messages sent from matterbridge | ||||
| 	if message.Post.Props != nil { | ||||
| 		if _, ok := message.Post.Props["matterbridge_"+b.uuid].(bool); ok { | ||||
| 			b.Log.Debugf("sent by matterbridge, ignoring") | ||||
| 			b.Log.Debug("sent by matterbridge, ignoring") | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Ignore messages sent from a user logged in as the bot | ||||
| 	if b.mc6.User.Username == message.Username { | ||||
| 		b.Log.Debug("message from same user as bot, ignoring") | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| @@ -346,6 +360,7 @@ func (b *Bmattermost) skipMessage6(message *matterclient6.Message) bool { | ||||
|  | ||||
| 	// ignore messages from other teams than ours | ||||
| 	if message.Raw.GetData()["team_id"].(string) != b.TeamID { | ||||
| 		b.Log.Debug("message from other team, ignoring") | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| @@ -374,3 +389,30 @@ func (b *Bmattermost) getVersion() string { | ||||
|  | ||||
| 	return resp.Header.Get("X-Version-Id") | ||||
| } | ||||
|  | ||||
| func (b *Bmattermost) getChannelID(name string) string { | ||||
| 	idcheck := strings.Split(name, "ID:") | ||||
| 	if len(idcheck) > 1 { | ||||
| 		return idcheck[1] | ||||
| 	} | ||||
|  | ||||
| 	if b.mc6 != nil { | ||||
| 		return b.mc6.GetChannelID(name, b.TeamID) | ||||
| 	} | ||||
|  | ||||
| 	return b.mc.GetChannelId(name, b.TeamID) | ||||
| } | ||||
|  | ||||
| func (b *Bmattermost) getChannelName(id string) string { | ||||
| 	b.channelsMutex.RLock() | ||||
| 	defer b.channelsMutex.RUnlock() | ||||
|  | ||||
| 	for _, c := range b.channelInfoMap { | ||||
| 		if c.Name == "ID:"+id { | ||||
| 			// if we have ID: specified in our gateway configuration return this | ||||
| 			return c.Name | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return "" | ||||
| } | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
|  | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| @@ -22,13 +23,19 @@ type Bmattermost struct { | ||||
| 	uuid   string | ||||
| 	TeamID string | ||||
| 	*bridge.Config | ||||
| 	avatarMap map[string]string | ||||
| 	avatarMap      map[string]string | ||||
| 	channelsMutex  sync.RWMutex | ||||
| 	channelInfoMap map[string]*config.ChannelInfo | ||||
| } | ||||
|  | ||||
| const mattermostPlugin = "mattermost.plugin" | ||||
|  | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	b := &Bmattermost{Config: cfg, avatarMap: make(map[string]string)} | ||||
| 	b := &Bmattermost{ | ||||
| 		Config:         cfg, | ||||
| 		avatarMap:      make(map[string]string), | ||||
| 		channelInfoMap: make(map[string]*config.ChannelInfo), | ||||
| 	} | ||||
|  | ||||
| 	b.v6 = b.GetBool("v6") | ||||
| 	b.uuid = xid.New().String() | ||||
| @@ -45,7 +52,7 @@ func (b *Bmattermost) Connect() error { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	if strings.HasPrefix(b.getVersion(), "6.") { | ||||
| 	if strings.HasPrefix(b.getVersion(), "6.") || strings.HasPrefix(b.getVersion(), "7.") { | ||||
| 		if !b.v6 { | ||||
| 			b.v6 = true | ||||
| 		} | ||||
| @@ -113,14 +120,14 @@ func (b *Bmattermost) JoinChannel(channel config.ChannelInfo) error { | ||||
| 	if b.Account == mattermostPlugin { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	b.channelsMutex.Lock() | ||||
| 	b.channelInfoMap[channel.ID] = &channel | ||||
| 	b.channelsMutex.Unlock() | ||||
|  | ||||
| 	// we can only join channels using the API | ||||
| 	if b.GetString("WebhookURL") == "" && b.GetString("WebhookBindAddress") == "" { | ||||
| 		var id string | ||||
| 		if b.mc6 != nil { | ||||
| 			id = b.mc6.GetChannelID(channel.Name, b.TeamID) | ||||
| 		} else { | ||||
| 			id = b.mc.GetChannelId(channel.Name, b.TeamID) | ||||
| 		} | ||||
| 		id := b.getChannelID(channel.Name) | ||||
| 		if id == "" { | ||||
| 			return fmt.Errorf("Could not find channel ID for channel %s", channel.Name) | ||||
| 		} | ||||
| @@ -131,6 +138,7 @@ func (b *Bmattermost) JoinChannel(channel config.ChannelInfo) error { | ||||
|  | ||||
| 		return b.mc.JoinChannel(id) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -180,13 +188,17 @@ func (b *Bmattermost) Send(msg config.Message) (string, error) { | ||||
| 			if err != nil { | ||||
| 				b.Log.Errorf("getting post %s failed: %s", msg.ParentID, err) | ||||
| 			} | ||||
| 			msg.ParentID = post.RootId | ||||
| 			if post.RootId != "" { | ||||
| 				msg.ParentID = post.RootId | ||||
| 			} | ||||
| 		} else { | ||||
| 			post, res := b.mc.Client.GetPost(msg.ParentID, "") | ||||
| 			if res.Error != nil { | ||||
| 				b.Log.Errorf("getting post %s failed: %s", msg.ParentID, res.Error.DetailedError) | ||||
| 			} | ||||
| 			msg.ParentID = post.RootId | ||||
| 			if post.RootId != "" { | ||||
| 				msg.ParentID = post.RootId | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -194,11 +206,11 @@ func (b *Bmattermost) Send(msg config.Message) (string, error) { | ||||
| 	if msg.Extra != nil { | ||||
| 		for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||
| 			if b.mc6 != nil { | ||||
| 				if _, err := b.mc6.PostMessage(b.mc.GetChannelId(rmsg.Channel, b.TeamID), rmsg.Username+rmsg.Text, msg.ParentID); err != nil { | ||||
| 				if _, err := b.mc6.PostMessage(b.getChannelID(rmsg.Channel), rmsg.Username+rmsg.Text, msg.ParentID); err != nil { | ||||
| 					b.Log.Errorf("PostMessage failed: %s", err) | ||||
| 				} | ||||
| 			} else { | ||||
| 				if _, err := b.mc.PostMessage(b.mc.GetChannelId(rmsg.Channel, b.TeamID), rmsg.Username+rmsg.Text, msg.ParentID); err != nil { | ||||
| 				if _, err := b.mc.PostMessage(b.getChannelID(rmsg.Channel), rmsg.Username+rmsg.Text, msg.ParentID); err != nil { | ||||
| 					b.Log.Errorf("PostMessage failed: %s", err) | ||||
| 				} | ||||
| 			} | ||||
| @@ -224,8 +236,8 @@ func (b *Bmattermost) Send(msg config.Message) (string, error) { | ||||
|  | ||||
| 	// Post normal message | ||||
| 	if b.mc6 != nil { | ||||
| 		return b.mc6.PostMessage(b.mc6.GetChannelID(msg.Channel, b.TeamID), msg.Text, msg.ParentID) // nolint:wrapcheck | ||||
| 		return b.mc6.PostMessage(b.getChannelID(msg.Channel), msg.Text, msg.ParentID) // nolint:wrapcheck | ||||
| 	} | ||||
|  | ||||
| 	return b.mc.PostMessage(b.mc.GetChannelId(msg.Channel, b.TeamID), msg.Text, msg.ParentID) | ||||
| 	return b.mc.PostMessage(b.getChannelID(msg.Channel), msg.Text, msg.ParentID) | ||||
| } | ||||
|   | ||||
							
								
								
									
										70
									
								
								bridge/mumble/codec.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								bridge/mumble/codec.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| package bmumble | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"layeh.com/gumble/gumble" | ||||
| ) | ||||
|  | ||||
| // This is a dummy implementation of a Gumble audio codec which claims | ||||
| // to implement Opus, but does not actually do anything.  This serves | ||||
| // as a workaround until https://github.com/layeh/gumble/pull/61 is | ||||
| // merged. | ||||
| // See https://github.com/42wim/matterbridge/issues/1750 for details. | ||||
|  | ||||
| const ( | ||||
| 	audioCodecIDOpus = 4 | ||||
| ) | ||||
|  | ||||
| func registerNullCodecAsOpus() { | ||||
| 	codec := &NullCodec{ | ||||
| 		encoder: &NullAudioEncoder{}, | ||||
| 		decoder: &NullAudioDecoder{}, | ||||
| 	} | ||||
| 	gumble.RegisterAudioCodec(audioCodecIDOpus, codec) | ||||
| } | ||||
|  | ||||
| type NullCodec struct { | ||||
| 	encoder *NullAudioEncoder | ||||
| 	decoder *NullAudioDecoder | ||||
| } | ||||
|  | ||||
| func (c *NullCodec) ID() int { | ||||
| 	return audioCodecIDOpus | ||||
| } | ||||
|  | ||||
| func (c *NullCodec) NewEncoder() gumble.AudioEncoder { | ||||
| 	e := &NullAudioEncoder{} | ||||
| 	return e | ||||
| } | ||||
|  | ||||
| func (c *NullCodec) NewDecoder() gumble.AudioDecoder { | ||||
| 	d := &NullAudioDecoder{} | ||||
| 	return d | ||||
| } | ||||
|  | ||||
| type NullAudioEncoder struct{} | ||||
|  | ||||
| func (e *NullAudioEncoder) ID() int { | ||||
| 	return audioCodecIDOpus | ||||
| } | ||||
|  | ||||
| func (e *NullAudioEncoder) Encode(pcm []int16, mframeSize, maxDataBytes int) ([]byte, error) { | ||||
| 	return nil, fmt.Errorf("not implemented") | ||||
| } | ||||
|  | ||||
| func (e *NullAudioEncoder) Reset() { | ||||
| } | ||||
|  | ||||
| type NullAudioDecoder struct{} | ||||
|  | ||||
| func (d *NullAudioDecoder) ID() int { | ||||
| 	return audioCodecIDOpus | ||||
| } | ||||
|  | ||||
| func (d *NullAudioDecoder) Decode(data []byte, frameSize int) ([]int16, error) { | ||||
| 	return nil, fmt.Errorf("not implemented") | ||||
| } | ||||
|  | ||||
| func (d *NullAudioDecoder) Reset() { | ||||
| } | ||||
| @@ -185,6 +185,7 @@ func (b *Bmumble) doConnect() error { | ||||
| 		gumbleConfig.Password = password | ||||
| 	} | ||||
|  | ||||
| 	registerNullCodecAsOpus() | ||||
| 	client, err := gumble.DialWithDialer(new(net.Dialer), b.GetString("Server"), gumbleConfig, &b.tlsConfig) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
|   | ||||
| @@ -27,7 +27,8 @@ func (b *Bslack) handleSlack() { | ||||
| 	b.Log.Debug("Start listening for Slack messages") | ||||
| 	for message := range messages { | ||||
| 		// don't do any action on deleted/typing messages | ||||
| 		if message.Event != config.EventUserTyping && message.Event != config.EventMsgDelete { | ||||
| 		if message.Event != config.EventUserTyping && message.Event != config.EventMsgDelete && | ||||
| 			message.Event != config.EventFileDelete { | ||||
| 			b.Log.Debugf("<= Sending message from %s on %s to gateway", message.Username, b.Account) | ||||
| 			// cleanup the message | ||||
| 			message.Text = b.replaceMention(message.Text) | ||||
| @@ -76,6 +77,13 @@ func (b *Bslack) handleSlackClient(messages chan *config.Message) { | ||||
| 				continue | ||||
| 			} | ||||
| 			messages <- rmsg | ||||
| 		case *slack.FileDeletedEvent: | ||||
| 			rmsg, err := b.handleFileDeletedEvent(ev) | ||||
| 			if err != nil { | ||||
| 				b.Log.Errorf("%#v", err) | ||||
| 				continue | ||||
| 			} | ||||
| 			messages <- rmsg | ||||
| 		case *slack.OutgoingErrorEvent: | ||||
| 			b.Log.Debugf("%#v", ev.Error()) | ||||
| 		case *slack.ChannelJoinedEvent: | ||||
| @@ -222,6 +230,26 @@ func (b *Bslack) handleMessageEvent(ev *slack.MessageEvent) (*config.Message, er | ||||
| 	return rmsg, nil | ||||
| } | ||||
|  | ||||
| func (b *Bslack) handleFileDeletedEvent(ev *slack.FileDeletedEvent) (*config.Message, error) { | ||||
| 	if rawChannel, ok := b.cache.Get(cfileDownloadChannel + ev.FileID); ok { | ||||
| 		channel, err := b.channels.getChannelByID(rawChannel.(string)) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		return &config.Message{ | ||||
| 			Event:    config.EventFileDelete, | ||||
| 			Text:     config.EventFileDelete, | ||||
| 			Channel:  channel.Name, | ||||
| 			Account:  b.Account, | ||||
| 			ID:       ev.FileID, | ||||
| 			Protocol: b.Protocol, | ||||
| 		}, nil | ||||
| 	} | ||||
|  | ||||
| 	return nil, fmt.Errorf("channel ID for file ID %s not found", ev.FileID) | ||||
| } | ||||
|  | ||||
| func (b *Bslack) handleStatusEvent(ev *slack.MessageEvent, rmsg *config.Message) bool { | ||||
| 	switch ev.SubType { | ||||
| 	case sChannelJoined, sMemberJoined: | ||||
| @@ -254,6 +282,13 @@ func (b *Bslack) handleStatusEvent(ev *slack.MessageEvent, rmsg *config.Message) | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func getMessageTitle(attach *slack.Attachment) string { | ||||
| 	if attach.TitleLink != "" { | ||||
| 		return fmt.Sprintf("[%s](%s)\n", attach.Title, attach.TitleLink) | ||||
| 	} | ||||
| 	return attach.Title | ||||
| } | ||||
|  | ||||
| func (b *Bslack) handleAttachments(ev *slack.MessageEvent, rmsg *config.Message) { | ||||
| 	// File comments are set by the system (because there is no username given). | ||||
| 	if ev.SubType == sFileComment { | ||||
| @@ -262,12 +297,15 @@ func (b *Bslack) handleAttachments(ev *slack.MessageEvent, rmsg *config.Message) | ||||
|  | ||||
| 	// See if we have some text in the attachments. | ||||
| 	if rmsg.Text == "" { | ||||
| 		for _, attach := range ev.Attachments { | ||||
| 		for i, attach := range ev.Attachments { | ||||
| 			if attach.Text != "" { | ||||
| 				if attach.Title != "" { | ||||
| 					rmsg.Text = attach.Title + "\n" | ||||
| 					rmsg.Text = getMessageTitle(&ev.Attachments[i]) | ||||
| 				} | ||||
| 				rmsg.Text += attach.Text | ||||
| 				if attach.Footer != "" { | ||||
| 					rmsg.Text += "\n\n" + attach.Footer | ||||
| 				} | ||||
| 			} else { | ||||
| 				rmsg.Text = attach.Fallback | ||||
| 			} | ||||
| @@ -281,6 +319,8 @@ func (b *Bslack) handleAttachments(ev *slack.MessageEvent, rmsg *config.Message) | ||||
|  | ||||
| 	// If we have files attached, download them (in memory) and put a pointer to it in msg.Extra. | ||||
| 	for i := range ev.Files { | ||||
| 		// keep reference in cache on which channel we added this file | ||||
| 		b.cache.Add(cfileDownloadChannel+ev.Files[i].ID, ev.Channel) | ||||
| 		if err := b.handleDownloadFile(rmsg, &ev.Files[i], false); err != nil { | ||||
| 			b.Log.Errorf("Could not download incoming file: %#v", err) | ||||
| 		} | ||||
| @@ -330,7 +370,7 @@ func (b *Bslack) handleDownloadFile(rmsg *config.Message, file *slack.File, retr | ||||
| 	// that the comment is not duplicated. | ||||
| 	comment := rmsg.Text | ||||
| 	rmsg.Text = "" | ||||
| 	helper.HandleDownloadData(b.Log, rmsg, file.Name, comment, file.URLPrivateDownload, data, b.General) | ||||
| 	helper.HandleDownloadData2(b.Log, rmsg, file.Name, file.ID, comment, file.URLPrivateDownload, data, b.General) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -87,6 +87,9 @@ func (b *Bslack) populateMessageWithUserInfo(ev *slack.MessageEvent, rmsg *confi | ||||
| 	if user.Profile.DisplayName != "" { | ||||
| 		rmsg.Username = user.Profile.DisplayName | ||||
| 	} | ||||
| 	if b.GetBool("UseFullName") && user.Profile.RealName != "" { | ||||
| 		rmsg.Username = user.Profile.RealName | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -124,7 +127,7 @@ var ( | ||||
| 	mentionRE        = regexp.MustCompile(`<@([a-zA-Z0-9]+)>`) | ||||
| 	channelRE        = regexp.MustCompile(`<#[a-zA-Z0-9]+\|(.+?)>`) | ||||
| 	variableRE       = regexp.MustCompile(`<!((?:subteam\^)?[a-zA-Z0-9]+)(?:\|@?(.+?))?>`) | ||||
| 	urlRE            = regexp.MustCompile(`<(.*?)(\|.*?)?>`) | ||||
| 	urlRE            = regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`) | ||||
| 	codeFenceRE      = regexp.MustCompile(`(?m)^` + "```" + `\w+$`) | ||||
| 	topicOrPurposeRE = regexp.MustCompile(`(?s)(@.+) (cleared|set)(?: the)? channel (topic|purpose)(?:: (.*))?`) | ||||
| ) | ||||
| @@ -178,14 +181,7 @@ func (b *Bslack) replaceVariable(text string) string { | ||||
|  | ||||
| // @see https://api.slack.com/docs/message-formatting#linking_to_urls | ||||
| func (b *Bslack) replaceURL(text string) string { | ||||
| 	for _, r := range urlRE.FindAllStringSubmatch(text, -1) { | ||||
| 		if len(strings.TrimSpace(r[2])) == 1 { // A display text separator was found, but the text was blank | ||||
| 			text = strings.Replace(text, r[0], "", 1) | ||||
| 		} else { | ||||
| 			text = strings.Replace(text, r[0], r[1], 1) | ||||
| 		} | ||||
| 	} | ||||
| 	return text | ||||
| 	return urlRE.ReplaceAllString(text, "[${2}](${1})") | ||||
| } | ||||
|  | ||||
| func (b *Bslack) replaceb0rkedMarkDown(text string) string { | ||||
|   | ||||
| @@ -36,24 +36,25 @@ type Bslack struct { | ||||
| } | ||||
|  | ||||
| const ( | ||||
| 	sHello           = "hello" | ||||
| 	sChannelJoin     = "channel_join" | ||||
| 	sChannelLeave    = "channel_leave" | ||||
| 	sChannelJoined   = "channel_joined" | ||||
| 	sMemberJoined    = "member_joined_channel" | ||||
| 	sMessageChanged  = "message_changed" | ||||
| 	sMessageDeleted  = "message_deleted" | ||||
| 	sSlackAttachment = "slack_attachment" | ||||
| 	sPinnedItem      = "pinned_item" | ||||
| 	sUnpinnedItem    = "unpinned_item" | ||||
| 	sChannelTopic    = "channel_topic" | ||||
| 	sChannelPurpose  = "channel_purpose" | ||||
| 	sFileComment     = "file_comment" | ||||
| 	sMeMessage       = "me_message" | ||||
| 	sUserTyping      = "user_typing" | ||||
| 	sLatencyReport   = "latency_report" | ||||
| 	sSystemUser      = "system" | ||||
| 	sSlackBotUser    = "slackbot" | ||||
| 	sHello               = "hello" | ||||
| 	sChannelJoin         = "channel_join" | ||||
| 	sChannelLeave        = "channel_leave" | ||||
| 	sChannelJoined       = "channel_joined" | ||||
| 	sMemberJoined        = "member_joined_channel" | ||||
| 	sMessageChanged      = "message_changed" | ||||
| 	sMessageDeleted      = "message_deleted" | ||||
| 	sSlackAttachment     = "slack_attachment" | ||||
| 	sPinnedItem          = "pinned_item" | ||||
| 	sUnpinnedItem        = "unpinned_item" | ||||
| 	sChannelTopic        = "channel_topic" | ||||
| 	sChannelPurpose      = "channel_purpose" | ||||
| 	sFileComment         = "file_comment" | ||||
| 	sMeMessage           = "me_message" | ||||
| 	sUserTyping          = "user_typing" | ||||
| 	sLatencyReport       = "latency_report" | ||||
| 	sSystemUser          = "system" | ||||
| 	sSlackBotUser        = "slackbot" | ||||
| 	cfileDownloadChannel = "file_download_channel" | ||||
|  | ||||
| 	tokenConfig           = "Token" | ||||
| 	incomingWebhookConfig = "WebhookBindAddress" | ||||
| @@ -320,7 +321,7 @@ func (b *Bslack) sendRTM(msg config.Message) (string, error) { | ||||
| 	} | ||||
|  | ||||
| 	// Upload a file if it exists. | ||||
| 	if msg.Extra != nil { | ||||
| 	if len(msg.Extra) > 0 { | ||||
| 		extraMsgs := helper.HandleExtra(&msg, b.General) | ||||
| 		for i := range extraMsgs { | ||||
| 			rmsg := &extraMsgs[i] | ||||
| @@ -331,7 +332,7 @@ func (b *Bslack) sendRTM(msg config.Message) (string, error) { | ||||
| 			} | ||||
| 		} | ||||
| 		// Upload files if necessary (from Slack, Telegram or Mattermost). | ||||
| 		b.uploadFile(&msg, channelInfo.ID) | ||||
| 		return b.uploadFile(&msg, channelInfo.ID) | ||||
| 	} | ||||
|  | ||||
| 	// Post message. | ||||
| @@ -442,7 +443,8 @@ func (b *Bslack) postMessage(msg *config.Message, channelInfo *slack.Channel) (s | ||||
| } | ||||
|  | ||||
| // uploadFile handles native upload of files | ||||
| func (b *Bslack) uploadFile(msg *config.Message, channelID string) { | ||||
| func (b *Bslack) uploadFile(msg *config.Message, channelID string) (string, error) { | ||||
| 	var messageID string | ||||
| 	for _, f := range msg.Extra["file"] { | ||||
| 		fi, ok := f.(config.FileInfo) | ||||
| 		if !ok { | ||||
| @@ -470,13 +472,22 @@ func (b *Bslack) uploadFile(msg *config.Message, channelID string) { | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			b.Log.Errorf("uploadfile %#v", err) | ||||
| 			return | ||||
| 			return "", err | ||||
| 		} | ||||
| 		if res.ID != "" { | ||||
| 			b.Log.Debugf("Adding file ID %s to cache with timestamp %s", res.ID, ts.String()) | ||||
| 			b.cache.Add("file"+res.ID, ts) | ||||
|  | ||||
| 			// search for message id by uploaded file in private/public channels, get thread timestamp from uploaded file | ||||
| 			if v, ok := res.Shares.Private[channelID]; ok && len(v) > 0 { | ||||
| 				messageID = v[0].Ts | ||||
| 			} | ||||
| 			if v, ok := res.Shares.Public[channelID]; ok && len(v) > 0 { | ||||
| 				messageID = v[0].Ts | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return messageID, nil | ||||
| } | ||||
|  | ||||
| func (b *Bslack) prepareMessageOptions(msg *config.Message) []slack.MsgOption { | ||||
|   | ||||
| @@ -291,6 +291,7 @@ func (b *channels) populateChannels(wait bool) { | ||||
| 	queryParams := &slack.GetConversationsParameters{ | ||||
| 		ExcludeArchived: true, | ||||
| 		Types:           []string{"public_channel,private_channel"}, | ||||
| 		Limit:           1000, | ||||
| 	} | ||||
| 	for { | ||||
| 		channels, nextCursor, err := b.sc.GetConversations(queryParams) | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package btelegram | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"html" | ||||
| 	"path/filepath" | ||||
| 	"strconv" | ||||
| @@ -9,14 +10,27 @@ import ( | ||||
|  | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api" | ||||
| 	"github.com/davecgh/go-spew/spew" | ||||
| 	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" | ||||
| ) | ||||
|  | ||||
| func (b *Btelegram) handleUpdate(rmsg *config.Message, message, posted, edited *tgbotapi.Message) *tgbotapi.Message { | ||||
| 	// handle channels | ||||
| 	if posted != nil { | ||||
| 		message = posted | ||||
| 		rmsg.Text = message.Text | ||||
| 		if posted.Text == "/chatId" { | ||||
| 			chatID := strconv.FormatInt(posted.Chat.ID, 10) | ||||
|  | ||||
| 			_, err := b.Send(config.Message{ | ||||
| 				Channel: chatID, | ||||
| 				Text:    fmt.Sprintf("ID of this chat: %s", chatID), | ||||
| 			}) | ||||
| 			if err != nil { | ||||
| 				b.Log.Warnf("Unable to send chatID to %s", chatID) | ||||
| 			} | ||||
| 		} else { | ||||
| 			message = posted | ||||
| 			rmsg.Text = message.Text | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// edited channel message | ||||
| @@ -43,6 +57,11 @@ func (b *Btelegram) handleForwarded(rmsg *config.Message, message *tgbotapi.Mess | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if message.ForwardFromChat != nil && message.ForwardFrom == nil { | ||||
| 		rmsg.Text = "Forwarded from " + message.ForwardFromChat.Title + ": " + rmsg.Text | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if message.ForwardFrom == nil { | ||||
| 		rmsg.Text = "Forwarded from " + unknownUser + ": " + rmsg.Text | ||||
| 		return | ||||
| @@ -52,6 +71,9 @@ func (b *Btelegram) handleForwarded(rmsg *config.Message, message *tgbotapi.Mess | ||||
| 	if b.GetBool("UseFirstName") { | ||||
| 		usernameForward = message.ForwardFrom.FirstName | ||||
| 	} | ||||
| 	if b.GetBool("UseFullName") { | ||||
| 		usernameForward = message.ForwardFrom.FirstName + " " + message.ForwardFrom.LastName | ||||
| 	} | ||||
|  | ||||
| 	if usernameForward == "" { | ||||
| 		usernameForward = message.ForwardFrom.UserName | ||||
| @@ -75,6 +97,9 @@ func (b *Btelegram) handleQuoting(rmsg *config.Message, message *tgbotapi.Messag | ||||
| 			if b.GetBool("UseFirstName") { | ||||
| 				usernameReply = message.ReplyToMessage.From.FirstName | ||||
| 			} | ||||
| 			if b.GetBool("UseFullName") { | ||||
| 				usernameReply = message.ReplyToMessage.From.FirstName + " " + message.ReplyToMessage.From.LastName | ||||
| 			} | ||||
| 			if usernameReply == "" { | ||||
| 				usernameReply = message.ReplyToMessage.From.UserName | ||||
| 				if usernameReply == "" { | ||||
| @@ -86,7 +111,11 @@ func (b *Btelegram) handleQuoting(rmsg *config.Message, message *tgbotapi.Messag | ||||
| 			usernameReply = unknownUser | ||||
| 		} | ||||
| 		if !b.GetBool("QuoteDisable") { | ||||
| 			rmsg.Text = b.handleQuote(rmsg.Text, usernameReply, message.ReplyToMessage.Text) | ||||
| 			quote := message.ReplyToMessage.Text | ||||
| 			if quote == "" { | ||||
| 				quote = message.ReplyToMessage.Caption | ||||
| 			} | ||||
| 			rmsg.Text = b.handleQuote(rmsg.Text, usernameReply, quote) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -94,10 +123,13 @@ func (b *Btelegram) handleQuoting(rmsg *config.Message, message *tgbotapi.Messag | ||||
| // handleUsername handles the correct setting of the username | ||||
| func (b *Btelegram) handleUsername(rmsg *config.Message, message *tgbotapi.Message) { | ||||
| 	if message.From != nil { | ||||
| 		rmsg.UserID = strconv.Itoa(message.From.ID) | ||||
| 		rmsg.UserID = strconv.FormatInt(message.From.ID, 10) | ||||
| 		if b.GetBool("UseFirstName") { | ||||
| 			rmsg.Username = message.From.FirstName | ||||
| 		} | ||||
| 		if b.GetBool("UseFullName") { | ||||
| 			rmsg.Username = message.From.FirstName + " " + message.From.LastName | ||||
| 		} | ||||
| 		if rmsg.Username == "" { | ||||
| 			rmsg.Username = message.From.UserName | ||||
| 			if rmsg.Username == "" { | ||||
| @@ -110,6 +142,28 @@ func (b *Btelegram) handleUsername(rmsg *config.Message, message *tgbotapi.Messa | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if message.SenderChat != nil { //nolint:nestif | ||||
| 		rmsg.UserID = strconv.FormatInt(message.SenderChat.ID, 10) | ||||
| 		if b.GetBool("UseFirstName") { | ||||
| 			rmsg.Username = message.SenderChat.FirstName | ||||
| 		} | ||||
| 		if b.GetBool("UseFullName") { | ||||
| 			rmsg.Username = message.SenderChat.FirstName + " " + message.SenderChat.LastName | ||||
| 		} | ||||
|  | ||||
| 		if rmsg.Username == "" || rmsg.Username == "Channel_Bot" { | ||||
| 			rmsg.Username = message.SenderChat.UserName | ||||
|  | ||||
| 			if rmsg.Username == "" || rmsg.Username == "Channel_Bot" { | ||||
| 				rmsg.Username = message.SenderChat.FirstName | ||||
| 			} | ||||
| 		} | ||||
| 		// only download avatars if we have a place to upload them (configured mediaserver) | ||||
| 		if b.General.MediaServerUpload != "" || (b.General.MediaServerDownload != "" && b.General.MediaDownloadPath != "") { | ||||
| 			b.handleDownloadAvatar(message.SenderChat.ID, rmsg.Channel) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// if we really didn't find a username, set it to unknown | ||||
| 	if rmsg.Username == "" { | ||||
| 		rmsg.Username = unknownUser | ||||
| @@ -126,6 +180,10 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if b.GetInt("debuglevel") == 1 { | ||||
| 			spew.Dump(update.Message) | ||||
| 		} | ||||
|  | ||||
| 		var message *tgbotapi.Message | ||||
|  | ||||
| 		rmsg := config.Message{Account: b.Account, Extra: make(map[string][]interface{})} | ||||
| @@ -145,6 +203,14 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) { | ||||
| 		rmsg.ID = strconv.Itoa(message.MessageID) | ||||
| 		rmsg.Channel = strconv.FormatInt(message.Chat.ID, 10) | ||||
|  | ||||
| 		// preserve threading from telegram reply | ||||
| 		if message.ReplyToMessage != nil { | ||||
| 			rmsg.ParentID = strconv.Itoa(message.ReplyToMessage.MessageID) | ||||
| 		} | ||||
|  | ||||
| 		// handle entities (adding URLs) | ||||
| 		b.handleEntities(&rmsg, message) | ||||
|  | ||||
| 		// handle username | ||||
| 		b.handleUsername(&rmsg, message) | ||||
|  | ||||
| @@ -160,14 +226,12 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) { | ||||
| 		// quote the previous message | ||||
| 		b.handleQuoting(&rmsg, message) | ||||
|  | ||||
| 		// handle entities (adding URLs) | ||||
| 		b.handleEntities(&rmsg, message) | ||||
|  | ||||
| 		if rmsg.Text != "" || len(rmsg.Extra) > 0 { | ||||
| 			rmsg.Text = helper.RemoveEmptyNewLines(rmsg.Text) | ||||
| 			// Comment the next line out due to avoid removing empty lines in Telegram | ||||
| 			// rmsg.Text = helper.RemoveEmptyNewLines(rmsg.Text) | ||||
| 			// channels don't have (always?) user information. see #410 | ||||
| 			if message.From != nil { | ||||
| 				rmsg.Avatar = helper.GetAvatar(b.avatarMap, strconv.Itoa(message.From.ID), b.General) | ||||
| 				rmsg.Avatar = helper.GetAvatar(b.avatarMap, strconv.FormatInt(message.From.ID, 10), b.General) | ||||
| 			} | ||||
|  | ||||
| 			b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account) | ||||
| @@ -180,42 +244,44 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) { | ||||
| // handleDownloadAvatar downloads the avatar of userid from channel | ||||
| // sends a EVENT_AVATAR_DOWNLOAD message to the gateway if successful. | ||||
| // logs an error message if it fails | ||||
| func (b *Btelegram) handleDownloadAvatar(userid int, channel string) { | ||||
| func (b *Btelegram) handleDownloadAvatar(userid int64, channel string) { | ||||
| 	rmsg := config.Message{ | ||||
| 		Username: "system", | ||||
| 		Text:     "avatar", | ||||
| 		Channel:  channel, | ||||
| 		Account:  b.Account, | ||||
| 		UserID:   strconv.Itoa(userid), | ||||
| 		UserID:   strconv.FormatInt(userid, 10), | ||||
| 		Event:    config.EventAvatarDownload, | ||||
| 		Extra:    make(map[string][]interface{}), | ||||
| 	} | ||||
|  | ||||
| 	if _, ok := b.avatarMap[strconv.Itoa(userid)]; !ok { | ||||
| 		photos, err := b.c.GetUserProfilePhotos(tgbotapi.UserProfilePhotosConfig{UserID: userid, Limit: 1}) | ||||
| 	if _, ok := b.avatarMap[strconv.FormatInt(userid, 10)]; ok { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	photos, err := b.c.GetUserProfilePhotos(tgbotapi.UserProfilePhotosConfig{UserID: userid, Limit: 1}) | ||||
| 	if err != nil { | ||||
| 		b.Log.Errorf("Userprofile download failed for %#v %s", userid, err) | ||||
| 	} | ||||
|  | ||||
| 	if len(photos.Photos) > 0 { | ||||
| 		photo := photos.Photos[0][0] | ||||
| 		url := b.getFileDirectURL(photo.FileID) | ||||
| 		name := strconv.FormatInt(userid, 10) + ".png" | ||||
| 		b.Log.Debugf("trying to download %#v fileid %#v with size %#v", name, photo.FileID, photo.FileSize) | ||||
|  | ||||
| 		err := helper.HandleDownloadSize(b.Log, &rmsg, name, int64(photo.FileSize), b.General) | ||||
| 		if err != nil { | ||||
| 			b.Log.Errorf("Userprofile download failed for %#v %s", userid, err) | ||||
| 			b.Log.Error(err) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		if len(photos.Photos) > 0 { | ||||
| 			photo := photos.Photos[0][0] | ||||
| 			url := b.getFileDirectURL(photo.FileID) | ||||
| 			name := strconv.Itoa(userid) + ".png" | ||||
| 			b.Log.Debugf("trying to download %#v fileid %#v with size %#v", name, photo.FileID, photo.FileSize) | ||||
|  | ||||
| 			err := helper.HandleDownloadSize(b.Log, &rmsg, name, int64(photo.FileSize), b.General) | ||||
| 			if err != nil { | ||||
| 				b.Log.Error(err) | ||||
| 				return | ||||
| 			} | ||||
| 			data, err := helper.DownloadFile(url) | ||||
| 			if err != nil { | ||||
| 				b.Log.Errorf("download %s failed %#v", url, err) | ||||
| 				return | ||||
| 			} | ||||
| 			helper.HandleDownloadData(b.Log, &rmsg, name, rmsg.Text, "", data, b.General) | ||||
| 			b.Remote <- rmsg | ||||
| 		data, err := helper.DownloadFile(url) | ||||
| 		if err != nil { | ||||
| 			b.Log.Errorf("download %s failed %#v", url, err) | ||||
| 			return | ||||
| 		} | ||||
| 		helper.HandleDownloadData(b.Log, &rmsg, name, rmsg.Text, "", data, b.General) | ||||
| 		b.Remote <- rmsg | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -272,7 +338,7 @@ func (b *Btelegram) handleDownload(rmsg *config.Message, message *tgbotapi.Messa | ||||
| 		name = message.Document.FileName | ||||
| 		text = " " + message.Document.FileName + " : " + url | ||||
| 	case message.Photo != nil: | ||||
| 		photos := *message.Photo | ||||
| 		photos := message.Photo | ||||
| 		size = photos[len(photos)-1].FileSize | ||||
| 		text, name, url = b.getDownloadInfo(photos[len(photos)-1].FileID, "", true) | ||||
| 	} | ||||
| @@ -319,7 +385,7 @@ func (b *Btelegram) getDownloadInfo(id string, suffix string, urlpart bool) (str | ||||
| 		urlPart := strings.Split(url, "/") | ||||
| 		name = urlPart[len(urlPart)-1] | ||||
| 	} | ||||
| 	if suffix != "" && !strings.HasSuffix(name, suffix) { | ||||
| 	if suffix != "" && !strings.HasSuffix(name, suffix) && !strings.HasSuffix(name, ".webm") { | ||||
| 		name += suffix | ||||
| 	} | ||||
| 	text := " " + url | ||||
| @@ -331,11 +397,15 @@ func (b *Btelegram) handleDelete(msg *config.Message, chatid int64) (string, err | ||||
| 	if msg.ID == "" { | ||||
| 		return "", nil | ||||
| 	} | ||||
|  | ||||
| 	msgid, err := strconv.Atoi(msg.ID) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	_, err = b.c.DeleteMessage(tgbotapi.DeleteMessageConfig{ChatID: chatid, MessageID: msgid}) | ||||
|  | ||||
| 	cfg := tgbotapi.NewDeleteMessage(chatid, msgid) | ||||
| 	_, err = b.c.Request(cfg) | ||||
|  | ||||
| 	return "", err | ||||
| } | ||||
|  | ||||
| @@ -373,8 +443,8 @@ func (b *Btelegram) handleEdit(msg *config.Message, chatid int64) (string, error | ||||
| } | ||||
|  | ||||
| // handleUploadFile handles native upload of files | ||||
| func (b *Btelegram) handleUploadFile(msg *config.Message, chatid int64) string { | ||||
| 	var c tgbotapi.Chattable | ||||
| func (b *Btelegram) handleUploadFile(msg *config.Message, chatid int64, parentID int) (string, error) { | ||||
| 	var media []interface{} | ||||
| 	for _, f := range msg.Extra["file"] { | ||||
| 		fi := f.(config.FileInfo) | ||||
| 		file := tgbotapi.FileBytes{ | ||||
| @@ -383,32 +453,42 @@ func (b *Btelegram) handleUploadFile(msg *config.Message, chatid int64) string { | ||||
| 		} | ||||
| 		switch filepath.Ext(fi.Name) { | ||||
| 		case ".jpg", ".jpe", ".png": | ||||
| 			pc := tgbotapi.NewPhotoUpload(chatid, file) | ||||
| 			pc.Caption, pc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment) | ||||
| 			c = pc | ||||
| 			pc := tgbotapi.NewInputMediaPhoto(file) | ||||
| 			if fi.Comment != "" { | ||||
| 				pc.Caption, pc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment) | ||||
| 			} | ||||
| 			media = append(media, pc) | ||||
| 		case ".mp4", ".m4v": | ||||
| 			vc := tgbotapi.NewVideoUpload(chatid, file) | ||||
| 			vc.Caption, vc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment) | ||||
| 			c = vc | ||||
| 			vc := tgbotapi.NewInputMediaVideo(file) | ||||
| 			if fi.Comment != "" { | ||||
| 				vc.Caption, vc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment) | ||||
| 			} | ||||
| 			media = append(media, vc) | ||||
| 		case ".mp3", ".oga": | ||||
| 			ac := tgbotapi.NewAudioUpload(chatid, file) | ||||
| 			ac.Caption, ac.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment) | ||||
| 			c = ac | ||||
| 			ac := tgbotapi.NewInputMediaAudio(file) | ||||
| 			if fi.Comment != "" { | ||||
| 				ac.Caption, ac.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment) | ||||
| 			} | ||||
| 			media = append(media, ac) | ||||
| 		case ".ogg": | ||||
| 			voc := tgbotapi.NewVoiceUpload(chatid, file) | ||||
| 			voc := tgbotapi.NewVoice(chatid, file) | ||||
| 			voc.Caption, voc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment) | ||||
| 			c = voc | ||||
| 			voc.ReplyToMessageID = parentID | ||||
| 			res, err := b.c.Send(voc) | ||||
| 			if err != nil { | ||||
| 				return "", err | ||||
| 			} | ||||
| 			return strconv.Itoa(res.MessageID), nil | ||||
| 		default: | ||||
| 			dc := tgbotapi.NewDocumentUpload(chatid, file) | ||||
| 			dc.Caption, dc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment) | ||||
| 			c = dc | ||||
| 		} | ||||
| 		_, err := b.c.Send(c) | ||||
| 		if err != nil { | ||||
| 			b.Log.Errorf("file upload failed: %#v", err) | ||||
| 			dc := tgbotapi.NewInputMediaDocument(file) | ||||
| 			if fi.Comment != "" { | ||||
| 				dc.Caption, dc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment) | ||||
| 			} | ||||
| 			media = append(media, dc) | ||||
| 		} | ||||
| 	} | ||||
| 	return "" | ||||
|  | ||||
| 	return b.sendMediaFiles(msg, chatid, parentID, media) | ||||
| } | ||||
|  | ||||
| func (b *Btelegram) handleQuote(message, quoteNick, quoteMessage string) string { | ||||
| @@ -435,21 +515,61 @@ func (b *Btelegram) handleEntities(rmsg *config.Message, message *tgbotapi.Messa | ||||
| 	if message.Entities == nil { | ||||
| 		return | ||||
| 	} | ||||
| 	// for now only do URL replacements | ||||
| 	for _, e := range *message.Entities { | ||||
|  | ||||
| 	indexMovedBy := 0 | ||||
| 	prevLinkOffset := -1 | ||||
|  | ||||
| 	for _, e := range message.Entities { | ||||
|  | ||||
| 		asRunes := utf16.Encode([]rune(rmsg.Text)) | ||||
|  | ||||
| 		if e.Type == "text_link" { | ||||
| 			offset := e.Offset + indexMovedBy | ||||
| 			url, err := e.ParseURL() | ||||
| 			if err != nil { | ||||
| 				b.Log.Errorf("entity text_link url parse failed: %s", err) | ||||
| 				continue | ||||
| 			} | ||||
| 			utfEncodedString := utf16.Encode([]rune(rmsg.Text)) | ||||
| 			if e.Offset+e.Length > len(utfEncodedString) { | ||||
| 				b.Log.Errorf("entity length is too long %d > %d", e.Offset+e.Length, len(utfEncodedString)) | ||||
| 			if offset+e.Length > len(utfEncodedString) { | ||||
| 				b.Log.Errorf("entity length is too long %d > %d", offset+e.Length, len(utfEncodedString)) | ||||
| 				continue | ||||
| 			} | ||||
| 			link := utf16.Decode(utfEncodedString[e.Offset : e.Offset+e.Length]) | ||||
| 			rmsg.Text = strings.Replace(rmsg.Text, string(link), url.String(), 1) | ||||
| 			rmsg.Text = string(utf16.Decode(asRunes[:offset+e.Length])) + " (" + url.String() + ")" + string(utf16.Decode(asRunes[offset+e.Length:])) | ||||
| 			indexMovedBy += len(url.String()) + 3 | ||||
| 			prevLinkOffset = e.Offset | ||||
| 		} | ||||
|  | ||||
| 		if e.Offset == prevLinkOffset { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if e.Type == "code" { | ||||
| 			offset := e.Offset + indexMovedBy | ||||
| 			rmsg.Text = string(utf16.Decode(asRunes[:offset])) + "`" + string(utf16.Decode(asRunes[offset:offset+e.Length])) + "`" + string(utf16.Decode(asRunes[offset+e.Length:])) | ||||
| 			indexMovedBy += 2 | ||||
| 		} | ||||
|  | ||||
| 		if e.Type == "pre" { | ||||
| 			offset := e.Offset + indexMovedBy | ||||
| 			rmsg.Text = string(utf16.Decode(asRunes[:offset])) + "```\n" + string(utf16.Decode(asRunes[offset:offset+e.Length])) + "```\n" + string(utf16.Decode(asRunes[offset+e.Length:])) | ||||
| 			indexMovedBy += 8 | ||||
| 		} | ||||
|  | ||||
| 		if e.Type == "bold" { | ||||
| 			offset := e.Offset + indexMovedBy | ||||
| 			rmsg.Text = string(utf16.Decode(asRunes[:offset])) + "*" + string(utf16.Decode(asRunes[offset:offset+e.Length])) + "*" + string(utf16.Decode(asRunes[offset+e.Length:])) | ||||
| 			indexMovedBy += 2 | ||||
| 		} | ||||
| 		if e.Type == "italic" { | ||||
| 			offset := e.Offset + indexMovedBy | ||||
| 			rmsg.Text = string(utf16.Decode(asRunes[:offset])) + "_" + string(utf16.Decode(asRunes[offset:offset+e.Length])) + "_" + string(utf16.Decode(asRunes[offset+e.Length:])) | ||||
| 			indexMovedBy += 2 | ||||
| 		} | ||||
| 		if e.Type == "strike" { | ||||
| 			offset := e.Offset + indexMovedBy | ||||
| 			rmsg.Text = string(utf16.Decode(asRunes[:offset])) + "~" + string(utf16.Decode(asRunes[offset:offset+e.Length])) + "~" + string(utf16.Decode(asRunes[offset+e.Length:])) | ||||
| 			indexMovedBy += 2 | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package btelegram | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"html" | ||||
| 	"log" | ||||
| 	"strconv" | ||||
| @@ -9,7 +10,7 @@ import ( | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api" | ||||
| 	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| @@ -49,11 +50,7 @@ func (b *Btelegram) Connect() error { | ||||
| 	} | ||||
| 	u := tgbotapi.NewUpdate(0) | ||||
| 	u.Timeout = 60 | ||||
| 	updates, err := b.c.GetUpdatesChan(u) | ||||
| 	if err != nil { | ||||
| 		b.Log.Debugf("%#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	updates := b.c.GetUpdatesChan(u) | ||||
| 	b.Log.Info("Connection succeeded") | ||||
| 	go b.handleRecv(updates) | ||||
| 	return nil | ||||
| @@ -112,16 +109,27 @@ func (b *Btelegram) Send(msg config.Message) (string, error) { | ||||
| 		return b.handleDelete(&msg, chatid) | ||||
| 	} | ||||
|  | ||||
| 	// Handle prefix hint for unthreaded messages. | ||||
| 	if msg.ParentNotFound() { | ||||
| 		msg.ParentID = "" | ||||
| 		msg.Text = fmt.Sprintf("[reply]: %s", msg.Text) | ||||
| 	} | ||||
|  | ||||
| 	var parentID int | ||||
| 	if msg.ParentID != "" { | ||||
| 		parentID, _ = b.intParentID(msg.ParentID) | ||||
| 	} | ||||
|  | ||||
| 	// Upload a file if it exists | ||||
| 	if msg.Extra != nil { | ||||
| 		for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||
| 			if _, msgErr := b.sendMessage(chatid, rmsg.Username, rmsg.Text); msgErr != nil { | ||||
| 			if _, msgErr := b.sendMessage(chatid, rmsg.Username, rmsg.Text, parentID); msgErr != nil { | ||||
| 				b.Log.Errorf("sendMessage failed: %s", msgErr) | ||||
| 			} | ||||
| 		} | ||||
| 		// check if we have files to upload (from slack, telegram or mattermost) | ||||
| 		if len(msg.Extra["file"]) > 0 { | ||||
| 			b.handleUploadFile(&msg, chatid) | ||||
| 			return b.handleUploadFile(&msg, chatid, parentID) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -135,7 +143,7 @@ func (b *Btelegram) Send(msg config.Message) (string, error) { | ||||
| 	// Ignore empty text field needs for prevent double messages from whatsapp to telegram | ||||
| 	// when sending media with text caption | ||||
| 	if msg.Text != "" { | ||||
| 		return b.sendMessage(chatid, msg.Username, msg.Text) | ||||
| 		return b.sendMessage(chatid, msg.Username, msg.Text, parentID) | ||||
| 	} | ||||
|  | ||||
| 	return "", nil | ||||
| @@ -149,10 +157,10 @@ func (b *Btelegram) getFileDirectURL(id string) string { | ||||
| 	return res | ||||
| } | ||||
|  | ||||
| func (b *Btelegram) sendMessage(chatid int64, username, text string) (string, error) { | ||||
| func (b *Btelegram) sendMessage(chatid int64, username, text string, parentID int) (string, error) { | ||||
| 	m := tgbotapi.NewMessage(chatid, "") | ||||
| 	m.Text, m.ParseMode = TGGetParseMode(b, username, text) | ||||
|  | ||||
| 	m.ReplyToMessageID = parentID | ||||
| 	m.DisableWebPagePreview = b.GetBool("DisableWebPagePreview") | ||||
|  | ||||
| 	res, err := b.c.Send(m) | ||||
| @@ -162,6 +170,29 @@ func (b *Btelegram) sendMessage(chatid int64, username, text string) (string, er | ||||
| 	return strconv.Itoa(res.MessageID), nil | ||||
| } | ||||
|  | ||||
| // sendMediaFiles native upload media files via media group | ||||
| func (b *Btelegram) sendMediaFiles(msg *config.Message, chatid int64, parentID int, media []interface{}) (string, error) { | ||||
| 	if len(media) == 0 { | ||||
| 		return "", nil | ||||
| 	} | ||||
| 	mg := tgbotapi.MediaGroupConfig{ChatID: chatid, ChannelUsername: msg.Username, Media: media, ReplyToMessageID: parentID} | ||||
| 	messages, err := b.c.SendMediaGroup(mg) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	// return first message id | ||||
| 	return strconv.Itoa(messages[0].MessageID), nil | ||||
| } | ||||
|  | ||||
| // intParentID return integer parent id for telegram message | ||||
| func (b *Btelegram) intParentID(parentID string) (int, error) { | ||||
| 	pid, err := strconv.Atoi(parentID) | ||||
| 	if err != nil { | ||||
| 		return 0, err | ||||
| 	} | ||||
| 	return pid, nil | ||||
| } | ||||
|  | ||||
| func (b *Btelegram) cacheAvatar(msg *config.Message) (string, error) { | ||||
| 	fi := msg.Extra["file"][0].(config.FileInfo) | ||||
| 	/* if we have a sha we have successfully uploaded the file to the media server, | ||||
|   | ||||
| @@ -34,6 +34,7 @@ type user struct { | ||||
|  | ||||
| type Bvk struct { | ||||
| 	c            *api.VK | ||||
| 	lp           *longpoll.LongPoll | ||||
| 	usernamesMap map[int]user // cache of user names and avatar URLs | ||||
| 	*bridge.Config | ||||
| } | ||||
| @@ -45,23 +46,25 @@ func New(cfg *bridge.Config) bridge.Bridger { | ||||
| func (b *Bvk) Connect() error { | ||||
| 	b.Log.Info("Connecting") | ||||
| 	b.c = api.NewVK(b.GetString("Token")) | ||||
| 	lp, err := longpoll.NewLongPoll(b.c, b.GetInt("GroupID")) | ||||
|  | ||||
| 	var err error | ||||
| 	b.lp, err = longpoll.NewLongPollCommunity(b.c) | ||||
| 	if err != nil { | ||||
| 		b.Log.Debugf("%#v", err) | ||||
|  | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	lp.MessageNew(func(ctx context.Context, obj events.MessageNewObject) { | ||||
| 	b.lp.MessageNew(func(ctx context.Context, obj events.MessageNewObject) { | ||||
| 		b.handleMessage(obj.Message, false) | ||||
| 	}) | ||||
|  | ||||
| 	b.Log.Info("Connection succeeded") | ||||
|  | ||||
| 	go func() { | ||||
| 		err := lp.Run() | ||||
| 		err := b.lp.Run() | ||||
| 		if err != nil { | ||||
| 			b.Log.Fatal("Enable longpoll in group management") | ||||
| 			b.Log.WithError(err).Fatal("Enable longpoll in group management") | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| @@ -69,6 +72,8 @@ func (b *Bvk) Connect() error { | ||||
| } | ||||
|  | ||||
| func (b *Bvk) Disconnect() error { | ||||
| 	b.lp.Shutdown() | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -218,7 +223,7 @@ func (b *Bvk) uploadFiles(extra map[string][]interface{}, peerID int) (string, s | ||||
| 		} | ||||
| 		a, err := b.uploadFile(fi, peerID) | ||||
| 		if err != nil { | ||||
| 			b.Log.Error("File upload error ", fi.Name) | ||||
| 			b.Log.WithError(err).Error("File upload error ", fi.Name) | ||||
| 		} | ||||
|  | ||||
| 		attachments = append(attachments, a) | ||||
| @@ -232,7 +237,8 @@ func (b *Bvk) uploadFile(file config.FileInfo, peerID int) (string, error) { | ||||
|  | ||||
| 	photoRE := regexp.MustCompile(".(jpg|jpe|png)$") | ||||
| 	if photoRE.MatchString(file.Name) { | ||||
| 		p, err := b.c.UploadMessagesPhoto(peerID, r) | ||||
| 		// BUG(VK): for community chat peerID=0 | ||||
| 		p, err := b.c.UploadMessagesPhoto(0, r) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| // nolint:goconst | ||||
| package bwhatsapp | ||||
|  | ||||
| import ( | ||||
| @@ -134,6 +135,7 @@ func (b *Bwhatsapp) HandleTextMessage(message whatsapp.TextMessage) { | ||||
| } | ||||
|  | ||||
| // HandleImageMessage sent from WhatsApp, relay it to the brige | ||||
| // nolint:funlen | ||||
| func (b *Bwhatsapp) HandleImageMessage(message whatsapp.ImageMessage) { | ||||
| 	if message.Info.FromMe || message.Info.Timestamp < b.startedAt { | ||||
| 		return | ||||
|   | ||||
| @@ -111,8 +111,7 @@ func (b *Bwhatsapp) getSenderName(senderJid string) string { | ||||
| 	} | ||||
|  | ||||
| 	// try to reload this contact | ||||
| 	_, err := b.conn.Contacts() | ||||
| 	if err != nil { | ||||
| 	if _, err := b.conn.Contacts(); err != nil { | ||||
| 		b.Log.Errorf("error on update of contacts: %v", err) | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -40,6 +40,11 @@ type Bwhatsapp struct { | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	number := cfg.GetString(cfgNumber) | ||||
|  | ||||
| 	cfg.Log.Warn("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") | ||||
| 	cfg.Log.Warn("This bridge is deprecated and not supported anymore. Use the new multidevice whatsapp bridge") | ||||
| 	cfg.Log.Warn("See https://github.com/42wim/matterbridge#building-with-whatsapp-beta-multidevice-support for more info") | ||||
| 	cfg.Log.Warn("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") | ||||
|  | ||||
| 	if number == "" { | ||||
| 		cfg.Log.Fatalf("Missing configuration for WhatsApp bridge: Number") | ||||
| 	} | ||||
| @@ -293,7 +298,11 @@ func (b *Bwhatsapp) Send(msg config.Message) (string, error) { | ||||
| 	if msg.ID != "" { | ||||
| 		b.Log.Debugf("updating message with id %s", msg.ID) | ||||
|  | ||||
| 		msg.Text += " (edited)" | ||||
| 		if b.GetString("editsuffix") != "" { | ||||
| 			msg.Text += b.GetString("EditSuffix") | ||||
| 		} else { | ||||
| 			msg.Text += " (edited)" | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Handle Upload a file | ||||
|   | ||||
							
								
								
									
										344
									
								
								bridge/whatsappmulti/handlers.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										344
									
								
								bridge/whatsappmulti/handlers.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,344 @@ | ||||
| // +build whatsappmulti | ||||
|  | ||||
| package bwhatsapp | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"mime" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
|  | ||||
| 	"go.mau.fi/whatsmeow/binary/proto" | ||||
| 	"go.mau.fi/whatsmeow/types" | ||||
| 	"go.mau.fi/whatsmeow/types/events" | ||||
| ) | ||||
|  | ||||
| // nolint:gocritic | ||||
| func (b *Bwhatsapp) eventHandler(evt interface{}) { | ||||
| 	switch e := evt.(type) { | ||||
| 	case *events.Message: | ||||
| 		b.handleMessage(e) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bwhatsapp) handleMessage(message *events.Message) { | ||||
| 	msg := message.Message | ||||
| 	switch { | ||||
| 	case msg == nil, message.Info.IsFromMe, message.Info.Timestamp.Before(b.startedAt): | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	b.Log.Infof("Receiving message %#v", msg) | ||||
|  | ||||
| 	switch { | ||||
| 	case msg.Conversation != nil || msg.ExtendedTextMessage != nil: | ||||
| 		b.handleTextMessage(message.Info, msg) | ||||
| 	case msg.VideoMessage != nil: | ||||
| 		b.handleVideoMessage(message) | ||||
| 	case msg.AudioMessage != nil: | ||||
| 		b.handleAudioMessage(message) | ||||
| 	case msg.DocumentMessage != nil: | ||||
| 		b.handleDocumentMessage(message) | ||||
| 	case msg.ImageMessage != nil: | ||||
| 		b.handleImageMessage(message) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // nolint:funlen | ||||
| func (b *Bwhatsapp) handleTextMessage(messageInfo types.MessageInfo, msg *proto.Message) { | ||||
| 	senderJID := messageInfo.Sender | ||||
| 	channel := messageInfo.Chat | ||||
|  | ||||
| 	senderName := b.getSenderName(messageInfo.Sender) | ||||
| 	if senderName == "" { | ||||
| 		senderName = "Someone" // don't expose telephone number | ||||
| 	} | ||||
|  | ||||
| 	if msg.GetExtendedTextMessage() == nil && msg.GetConversation() == "" { | ||||
| 		b.Log.Debugf("message without text content? %#v", msg) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	var text string | ||||
|  | ||||
| 	// nolint:nestif | ||||
| 	if msg.GetExtendedTextMessage() == nil { | ||||
| 		text = msg.GetConversation() | ||||
| 	} else { | ||||
| 		text = msg.GetExtendedTextMessage().GetText() | ||||
| 		ci := msg.GetExtendedTextMessage().GetContextInfo() | ||||
|  | ||||
| 		if senderJID == (types.JID{}) && ci.Participant != nil { | ||||
| 			senderJID = types.NewJID(ci.GetParticipant(), types.DefaultUserServer) | ||||
| 		} | ||||
|  | ||||
| 		if ci.MentionedJid != nil { | ||||
| 			// handle user mentions | ||||
| 			for _, mentionedJID := range ci.MentionedJid { | ||||
| 				numberAndSuffix := strings.SplitN(mentionedJID, "@", 2) | ||||
|  | ||||
| 				// mentions comes as telephone numbers and we don't want to expose it to other bridges | ||||
| 				// replace it with something more meaninful to others | ||||
| 				mention := b.getSenderNotify(types.NewJID(numberAndSuffix[0], types.DefaultUserServer)) | ||||
| 				if mention == "" { | ||||
| 					mention = "someone" | ||||
| 				} | ||||
|  | ||||
| 				text = strings.Replace(text, "@"+numberAndSuffix[0], "@"+mention, 1) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	rmsg := config.Message{ | ||||
| 		UserID:   senderJID.String(), | ||||
| 		Username: senderName, | ||||
| 		Text:     text, | ||||
| 		Channel:  channel.String(), | ||||
| 		Account:  b.Account, | ||||
| 		Protocol: b.Protocol, | ||||
| 		Extra:    make(map[string][]interface{}), | ||||
| 		//      ParentID: TODO, // TODO handle thread replies  // map from Info.QuotedMessageID string | ||||
| 		ID: messageInfo.ID, | ||||
| 	} | ||||
|  | ||||
| 	if avatarURL, exists := b.userAvatars[senderJID.String()]; exists { | ||||
| 		rmsg.Avatar = avatarURL | ||||
| 	} | ||||
|  | ||||
| 	b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account) | ||||
| 	b.Log.Debugf("<= Message is %#v", rmsg) | ||||
|  | ||||
| 	b.Remote <- rmsg | ||||
| } | ||||
|  | ||||
| // HandleImageMessage sent from WhatsApp, relay it to the brige | ||||
| func (b *Bwhatsapp) handleImageMessage(msg *events.Message) { | ||||
| 	imsg := msg.Message.GetImageMessage() | ||||
|  | ||||
| 	senderJID := msg.Info.Sender | ||||
| 	senderName := b.getSenderName(senderJID) | ||||
| 	ci := imsg.GetContextInfo() | ||||
|  | ||||
| 	if senderJID == (types.JID{}) && ci.Participant != nil { | ||||
| 		senderJID = types.NewJID(ci.GetParticipant(), types.DefaultUserServer) | ||||
| 	} | ||||
|  | ||||
| 	rmsg := config.Message{ | ||||
| 		UserID:   senderJID.String(), | ||||
| 		Username: senderName, | ||||
| 		Channel:  msg.Info.Chat.String(), | ||||
| 		Account:  b.Account, | ||||
| 		Protocol: b.Protocol, | ||||
| 		Extra:    make(map[string][]interface{}), | ||||
| 		ID:       msg.Info.ID, | ||||
| 	} | ||||
|  | ||||
| 	if avatarURL, exists := b.userAvatars[senderJID.String()]; exists { | ||||
| 		rmsg.Avatar = avatarURL | ||||
| 	} | ||||
|  | ||||
| 	fileExt, err := mime.ExtensionsByType(imsg.GetMimetype()) | ||||
| 	if err != nil { | ||||
| 		b.Log.Errorf("Mimetype detection error: %s", err) | ||||
|  | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// rename .jfif to .jpg https://github.com/42wim/matterbridge/issues/1292 | ||||
| 	if fileExt[0] == ".jfif" { | ||||
| 		fileExt[0] = ".jpg" | ||||
| 	} | ||||
|  | ||||
| 	// rename .jpe to .jpg https://github.com/42wim/matterbridge/issues/1463 | ||||
| 	if fileExt[0] == ".jpe" { | ||||
| 		fileExt[0] = ".jpg" | ||||
| 	} | ||||
|  | ||||
| 	filename := fmt.Sprintf("%v%v", msg.Info.ID, fileExt[0]) | ||||
|  | ||||
| 	b.Log.Debugf("Trying to download %s with type %s", filename, imsg.GetMimetype()) | ||||
|  | ||||
| 	data, err := b.wc.Download(imsg) | ||||
| 	if err != nil { | ||||
| 		b.Log.Errorf("Download image failed: %s", err) | ||||
|  | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Move file to bridge storage | ||||
| 	helper.HandleDownloadData(b.Log, &rmsg, filename, imsg.GetCaption(), "", &data, b.General) | ||||
|  | ||||
| 	b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account) | ||||
| 	b.Log.Debugf("<= Message is %#v", rmsg) | ||||
|  | ||||
| 	b.Remote <- rmsg | ||||
| } | ||||
|  | ||||
| // HandleVideoMessage downloads video messages | ||||
| func (b *Bwhatsapp) handleVideoMessage(msg *events.Message) { | ||||
| 	imsg := msg.Message.GetVideoMessage() | ||||
|  | ||||
| 	senderJID := msg.Info.Sender | ||||
| 	senderName := b.getSenderName(senderJID) | ||||
| 	ci := imsg.GetContextInfo() | ||||
|  | ||||
| 	if senderJID == (types.JID{}) && ci.Participant != nil { | ||||
| 		senderJID = types.NewJID(ci.GetParticipant(), types.DefaultUserServer) | ||||
| 	} | ||||
|  | ||||
| 	rmsg := config.Message{ | ||||
| 		UserID:   senderJID.String(), | ||||
| 		Username: senderName, | ||||
| 		Channel:  msg.Info.Chat.String(), | ||||
| 		Account:  b.Account, | ||||
| 		Protocol: b.Protocol, | ||||
| 		Extra:    make(map[string][]interface{}), | ||||
| 		ID:       msg.Info.ID, | ||||
| 	} | ||||
|  | ||||
| 	if avatarURL, exists := b.userAvatars[senderJID.String()]; exists { | ||||
| 		rmsg.Avatar = avatarURL | ||||
| 	} | ||||
|  | ||||
| 	fileExt, err := mime.ExtensionsByType(imsg.GetMimetype()) | ||||
| 	if err != nil { | ||||
| 		b.Log.Errorf("Mimetype detection error: %s", err) | ||||
|  | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if len(fileExt) == 0 { | ||||
| 		fileExt = append(fileExt, ".mp4") | ||||
| 	} | ||||
|  | ||||
| 	filename := fmt.Sprintf("%v%v", msg.Info.ID, fileExt[0]) | ||||
|  | ||||
| 	b.Log.Debugf("Trying to download %s with size %#v and type %s", filename, imsg.GetFileLength(), imsg.GetMimetype()) | ||||
|  | ||||
| 	data, err := b.wc.Download(imsg) | ||||
| 	if err != nil { | ||||
| 		b.Log.Errorf("Download video failed: %s", err) | ||||
|  | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Move file to bridge storage | ||||
| 	helper.HandleDownloadData(b.Log, &rmsg, filename, imsg.GetCaption(), "", &data, b.General) | ||||
|  | ||||
| 	b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account) | ||||
| 	b.Log.Debugf("<= Message is %#v", rmsg) | ||||
|  | ||||
| 	b.Remote <- rmsg | ||||
| } | ||||
|  | ||||
| // HandleAudioMessage downloads audio messages | ||||
| func (b *Bwhatsapp) handleAudioMessage(msg *events.Message) { | ||||
| 	imsg := msg.Message.GetAudioMessage() | ||||
|  | ||||
| 	senderJID := msg.Info.Sender | ||||
| 	senderName := b.getSenderName(senderJID) | ||||
| 	ci := imsg.GetContextInfo() | ||||
|  | ||||
| 	if senderJID == (types.JID{}) && ci.Participant != nil { | ||||
| 		senderJID = types.NewJID(ci.GetParticipant(), types.DefaultUserServer) | ||||
| 	} | ||||
|  | ||||
| 	rmsg := config.Message{ | ||||
| 		UserID:   senderJID.String(), | ||||
| 		Username: senderName, | ||||
| 		Channel:  msg.Info.Chat.String(), | ||||
| 		Account:  b.Account, | ||||
| 		Protocol: b.Protocol, | ||||
| 		Extra:    make(map[string][]interface{}), | ||||
| 		ID:       msg.Info.ID, | ||||
| 	} | ||||
|  | ||||
| 	if avatarURL, exists := b.userAvatars[senderJID.String()]; exists { | ||||
| 		rmsg.Avatar = avatarURL | ||||
| 	} | ||||
|  | ||||
| 	fileExt, err := mime.ExtensionsByType(imsg.GetMimetype()) | ||||
| 	if err != nil { | ||||
| 		b.Log.Errorf("Mimetype detection error: %s", err) | ||||
|  | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if len(fileExt) == 0 { | ||||
| 		fileExt = append(fileExt, ".ogg") | ||||
| 	} | ||||
|  | ||||
| 	filename := fmt.Sprintf("%v%v", msg.Info.ID, fileExt[0]) | ||||
|  | ||||
| 	b.Log.Debugf("Trying to download %s with size %#v and type %s", filename, imsg.GetFileLength(), imsg.GetMimetype()) | ||||
|  | ||||
| 	data, err := b.wc.Download(imsg) | ||||
| 	if err != nil { | ||||
| 		b.Log.Errorf("Download video failed: %s", err) | ||||
|  | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Move file to bridge storage | ||||
| 	helper.HandleDownloadData(b.Log, &rmsg, filename, "audio message", "", &data, b.General) | ||||
|  | ||||
| 	b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account) | ||||
| 	b.Log.Debugf("<= Message is %#v", rmsg) | ||||
|  | ||||
| 	b.Remote <- rmsg | ||||
| } | ||||
|  | ||||
| // HandleDocumentMessage downloads documents | ||||
| func (b *Bwhatsapp) handleDocumentMessage(msg *events.Message) { | ||||
| 	imsg := msg.Message.GetDocumentMessage() | ||||
|  | ||||
| 	senderJID := msg.Info.Sender | ||||
| 	senderName := b.getSenderName(senderJID) | ||||
| 	ci := imsg.GetContextInfo() | ||||
|  | ||||
| 	if senderJID == (types.JID{}) && ci.Participant != nil { | ||||
| 		senderJID = types.NewJID(ci.GetParticipant(), types.DefaultUserServer) | ||||
| 	} | ||||
|  | ||||
| 	rmsg := config.Message{ | ||||
| 		UserID:   senderJID.String(), | ||||
| 		Username: senderName, | ||||
| 		Channel:  msg.Info.Chat.String(), | ||||
| 		Account:  b.Account, | ||||
| 		Protocol: b.Protocol, | ||||
| 		Extra:    make(map[string][]interface{}), | ||||
| 		ID:       msg.Info.ID, | ||||
| 	} | ||||
|  | ||||
| 	if avatarURL, exists := b.userAvatars[senderJID.String()]; exists { | ||||
| 		rmsg.Avatar = avatarURL | ||||
| 	} | ||||
|  | ||||
| 	fileExt, err := mime.ExtensionsByType(imsg.GetMimetype()) | ||||
| 	if err != nil { | ||||
| 		b.Log.Errorf("Mimetype detection error: %s", err) | ||||
|  | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	filename := fmt.Sprintf("%v", imsg.GetFileName()) | ||||
|  | ||||
| 	b.Log.Debugf("Trying to download %s with extension %s and type %s", filename, fileExt, imsg.GetMimetype()) | ||||
|  | ||||
| 	data, err := b.wc.Download(imsg) | ||||
| 	if err != nil { | ||||
| 		b.Log.Errorf("Download document message failed: %s", err) | ||||
|  | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Move file to bridge storage | ||||
| 	helper.HandleDownloadData(b.Log, &rmsg, filename, "document", "", &data, b.General) | ||||
|  | ||||
| 	b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account) | ||||
| 	b.Log.Debugf("<= Message is %#v", rmsg) | ||||
|  | ||||
| 	b.Remote <- rmsg | ||||
| } | ||||
							
								
								
									
										108
									
								
								bridge/whatsappmulti/helpers.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								bridge/whatsappmulti/helpers.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,108 @@ | ||||
| // +build whatsappmulti | ||||
|  | ||||
| package bwhatsapp | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
|  | ||||
| 	"go.mau.fi/whatsmeow/store" | ||||
| 	"go.mau.fi/whatsmeow/store/sqlstore" | ||||
| 	"go.mau.fi/whatsmeow/types" | ||||
| ) | ||||
|  | ||||
| type ProfilePicInfo struct { | ||||
| 	URL    string `json:"eurl"` | ||||
| 	Tag    string `json:"tag"` | ||||
| 	Status int16  `json:"status"` | ||||
| } | ||||
|  | ||||
| func (b *Bwhatsapp) getSenderName(senderJid types.JID) string { | ||||
| 	if sender, exists := b.contacts[senderJid]; exists { | ||||
| 		if sender.FullName != "" { | ||||
| 			return sender.FullName | ||||
| 		} | ||||
| 		// if user is not in phone contacts | ||||
| 		// it is the most obvious scenario unless you sync your phone contacts with some remote updated source | ||||
| 		// users can change it in their WhatsApp settings -> profile -> click on Avatar | ||||
| 		if sender.PushName != "" { | ||||
| 			return sender.PushName | ||||
| 		} | ||||
|  | ||||
| 		if sender.FirstName != "" { | ||||
| 			return sender.FirstName | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// try to reload this contact | ||||
| 	if _, err := b.wc.Store.Contacts.GetAllContacts(); err != nil { | ||||
| 		b.Log.Errorf("error on update of contacts: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	allcontacts, err := b.wc.Store.Contacts.GetAllContacts() | ||||
| 	if err != nil { | ||||
| 		b.Log.Errorf("error on update of contacts: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	if len(allcontacts) > 0 { | ||||
| 		b.contacts = allcontacts | ||||
| 	} | ||||
|  | ||||
| 	if sender, exists := b.contacts[senderJid]; exists { | ||||
| 		if sender.FullName != "" { | ||||
| 			return sender.FullName | ||||
| 		} | ||||
| 		// if user is not in phone contacts | ||||
| 		// it is the most obvious scenario unless you sync your phone contacts with some remote updated source | ||||
| 		// users can change it in their WhatsApp settings -> profile -> click on Avatar | ||||
| 		if sender.PushName != "" { | ||||
| 			return sender.PushName | ||||
| 		} | ||||
|  | ||||
| 		if sender.FirstName != "" { | ||||
| 			return sender.FirstName | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return "Someone" | ||||
| } | ||||
|  | ||||
| func (b *Bwhatsapp) getSenderNotify(senderJid types.JID) string { | ||||
| 	if sender, exists := b.contacts[senderJid]; exists { | ||||
| 		return sender.PushName | ||||
| 	} | ||||
|  | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (b *Bwhatsapp) GetProfilePicThumb(jid string) (*types.ProfilePictureInfo, error) { | ||||
| 	pjid, _ := types.ParseJID(jid) | ||||
| 	info, err := b.wc.GetProfilePictureInfo(pjid, true) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to get avatar: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	return info, nil | ||||
| } | ||||
|  | ||||
| func isGroupJid(identifier string) bool { | ||||
| 	return strings.HasSuffix(identifier, "@g.us") || | ||||
| 		strings.HasSuffix(identifier, "@temp") || | ||||
| 		strings.HasSuffix(identifier, "@broadcast") | ||||
| } | ||||
|  | ||||
| func (b *Bwhatsapp) getDevice() (*store.Device, error) { | ||||
| 	device := &store.Device{} | ||||
|  | ||||
| 	storeContainer, err := sqlstore.New("sqlite", "file:"+b.Config.GetString("sessionfile")+".db?_foreign_keys=on&_pragma=busy_timeout=10000", nil) | ||||
| 	if err != nil { | ||||
| 		return device, fmt.Errorf("failed to connect to database: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	device, err = storeContainer.GetFirstDevice() | ||||
| 	if err != nil { | ||||
| 		return device, fmt.Errorf("failed to get device: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	return device, nil | ||||
| } | ||||
							
								
								
									
										333
									
								
								bridge/whatsappmulti/whatsapp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										333
									
								
								bridge/whatsappmulti/whatsapp.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,333 @@ | ||||
| // +build whatsappmulti | ||||
|  | ||||
| package bwhatsapp | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"mime" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/mdp/qrterminal" | ||||
|  | ||||
| 	"go.mau.fi/whatsmeow" | ||||
| 	"go.mau.fi/whatsmeow/binary/proto" | ||||
| 	"go.mau.fi/whatsmeow/types" | ||||
| 	waLog "go.mau.fi/whatsmeow/util/log" | ||||
|  | ||||
| 	goproto "google.golang.org/protobuf/proto" | ||||
|  | ||||
| 	_ "modernc.org/sqlite" // needed for sqlite | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	// Account config parameters | ||||
| 	cfgNumber = "Number" | ||||
| ) | ||||
|  | ||||
| // Bwhatsapp Bridge structure keeping all the information needed for relying | ||||
| type Bwhatsapp struct { | ||||
| 	*bridge.Config | ||||
|  | ||||
| 	startedAt   time.Time | ||||
| 	wc          *whatsmeow.Client | ||||
| 	contacts    map[types.JID]types.ContactInfo | ||||
| 	users       map[string]types.ContactInfo | ||||
| 	userAvatars map[string]string | ||||
| } | ||||
|  | ||||
| // New Create a new WhatsApp bridge. This will be called for each [whatsapp.<server>] entry you have in the config file | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	number := cfg.GetString(cfgNumber) | ||||
|  | ||||
| 	if number == "" { | ||||
| 		cfg.Log.Fatalf("Missing configuration for WhatsApp bridge: Number") | ||||
| 	} | ||||
|  | ||||
| 	b := &Bwhatsapp{ | ||||
| 		Config: cfg, | ||||
|  | ||||
| 		users:       make(map[string]types.ContactInfo), | ||||
| 		userAvatars: make(map[string]string), | ||||
| 	} | ||||
|  | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| // Connect to WhatsApp. Required implementation of the Bridger interface | ||||
| func (b *Bwhatsapp) Connect() error { | ||||
| 	device, err := b.getDevice() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	number := b.GetString(cfgNumber) | ||||
| 	if number == "" { | ||||
| 		return errors.New("whatsapp's telephone number need to be configured") | ||||
| 	} | ||||
|  | ||||
| 	b.Log.Debugln("Connecting to WhatsApp..") | ||||
|  | ||||
| 	b.wc = whatsmeow.NewClient(device, waLog.Stdout("Client", "INFO", true)) | ||||
| 	b.wc.AddEventHandler(b.eventHandler) | ||||
|  | ||||
| 	firstlogin := false | ||||
| 	var qrChan <-chan whatsmeow.QRChannelItem | ||||
| 	if b.wc.Store.ID == nil { | ||||
| 		firstlogin = true | ||||
| 		qrChan, err = b.wc.GetQRChannel(context.Background()) | ||||
| 		if err != nil && !errors.Is(err, whatsmeow.ErrQRStoreContainsID) { | ||||
| 			return errors.New("failed to to get QR channel:" + err.Error()) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	err = b.wc.Connect() | ||||
| 	if err != nil { | ||||
| 		return errors.New("failed to connect to WhatsApp: " + err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	if b.wc.Store.ID == nil { | ||||
| 		for evt := range qrChan { | ||||
| 			if evt.Event == "code" { | ||||
| 				qrterminal.GenerateHalfBlock(evt.Code, qrterminal.L, os.Stdout) | ||||
| 			} else { | ||||
| 				b.Log.Infof("QR channel result: %s", evt.Event) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// disconnect and reconnect on our first login/pairing | ||||
| 	// for some reason the GetJoinedGroups in JoinChannel doesn't work on first login | ||||
| 	if firstlogin { | ||||
| 		b.wc.Disconnect() | ||||
| 		time.Sleep(time.Second) | ||||
|  | ||||
| 		err = b.wc.Connect() | ||||
| 		if err != nil { | ||||
| 			return errors.New("failed to connect to WhatsApp: " + err.Error()) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	b.Log.Infoln("WhatsApp connection successful") | ||||
|  | ||||
| 	b.contacts, err = b.wc.Store.Contacts.GetAllContacts() | ||||
| 	if err != nil { | ||||
| 		return errors.New("failed to get contacts: " + err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	b.startedAt = time.Now() | ||||
|  | ||||
| 	// map all the users | ||||
| 	for id, contact := range b.contacts { | ||||
| 		if !isGroupJid(id.String()) && id.String() != "status@broadcast" { | ||||
| 			// it is user | ||||
| 			b.users[id.String()] = contact | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// get user avatar asynchronously | ||||
| 	b.Log.Info("Getting user avatars..") | ||||
|  | ||||
| 	for jid := range b.users { | ||||
| 		info, err := b.GetProfilePicThumb(jid) | ||||
| 		if err != nil { | ||||
| 			b.Log.Warnf("Could not get profile photo of %s: %v", jid, err) | ||||
| 		} else { | ||||
| 			b.Lock() | ||||
| 			if info != nil { | ||||
| 				b.userAvatars[jid] = info.URL | ||||
| 			} | ||||
| 			b.Unlock() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	b.Log.Info("Finished getting avatars..") | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Disconnect is called while reconnecting to the bridge | ||||
| // Required implementation of the Bridger interface | ||||
| func (b *Bwhatsapp) Disconnect() error { | ||||
| 	b.wc.Disconnect() | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // JoinChannel Join a WhatsApp group specified in gateway config as channel='number-id@g.us' or channel='Channel name' | ||||
| // Required implementation of the Bridger interface | ||||
| // https://github.com/42wim/matterbridge/blob/2cfd880cdb0df29771bf8f31df8d990ab897889d/bridge/bridge.go#L11-L16 | ||||
| func (b *Bwhatsapp) JoinChannel(channel config.ChannelInfo) error { | ||||
| 	byJid := isGroupJid(channel.Name) | ||||
|  | ||||
| 	groups, err := b.wc.GetJoinedGroups() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// verify if we are member of the given group | ||||
| 	if byJid { | ||||
| 		gJID, err := types.ParseJID(channel.Name) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		for _, group := range groups { | ||||
| 			if group.JID == gJID { | ||||
| 				return nil | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	foundGroups := []string{} | ||||
|  | ||||
| 	for _, group := range groups { | ||||
| 		if group.Name == channel.Name { | ||||
| 			foundGroups = append(foundGroups, group.Name) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	switch len(foundGroups) { | ||||
| 	case 0: | ||||
| 		// didn't match any group - print out possibilites | ||||
| 		for _, group := range groups { | ||||
| 			b.Log.Infof("%s %s", group.JID, group.Name) | ||||
| 		} | ||||
| 		return fmt.Errorf("please specify group's JID from the list above instead of the name '%s'", channel.Name) | ||||
| 	case 1: | ||||
| 		return fmt.Errorf("group name might change. Please configure gateway with channel=\"%v\" instead of channel=\"%v\"", foundGroups[0], channel.Name) | ||||
| 	default: | ||||
| 		return fmt.Errorf("there is more than one group with name '%s'. Please specify one of JIDs as channel name: %v", channel.Name, foundGroups) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Post a document message from the bridge to WhatsApp | ||||
| func (b *Bwhatsapp) PostDocumentMessage(msg config.Message, filetype string) (string, error) { | ||||
| 	groupJID, _ := types.ParseJID(msg.Channel) | ||||
|  | ||||
| 	fi := msg.Extra["file"][0].(config.FileInfo) | ||||
|  | ||||
| 	resp, err := b.wc.Upload(context.Background(), *fi.Data, whatsmeow.MediaDocument) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	// Post document message | ||||
| 	var message proto.Message | ||||
|  | ||||
| 	message.DocumentMessage = &proto.DocumentMessage{ | ||||
| 		Title:         &fi.Name, | ||||
| 		FileName:      &fi.Name, | ||||
| 		Mimetype:      &filetype, | ||||
| 		MediaKey:      resp.MediaKey, | ||||
| 		FileEncSha256: resp.FileEncSHA256, | ||||
| 		FileSha256:    resp.FileSHA256, | ||||
| 		FileLength:    goproto.Uint64(resp.FileLength), | ||||
| 		Url:           &resp.URL, | ||||
| 	} | ||||
|  | ||||
| 	b.Log.Debugf("=> Sending %#v", msg) | ||||
|  | ||||
| 	ID := whatsmeow.GenerateMessageID() | ||||
| 	_, err = b.wc.SendMessage(groupJID, ID, &message) | ||||
|  | ||||
| 	return ID, err | ||||
| } | ||||
|  | ||||
| // Post an image message from the bridge to WhatsApp | ||||
| // Handle, for sure image/jpeg, image/png and image/gif MIME types | ||||
| func (b *Bwhatsapp) PostImageMessage(msg config.Message, filetype string) (string, error) { | ||||
| 	groupJID, _ := types.ParseJID(msg.Channel) | ||||
|  | ||||
| 	fi := msg.Extra["file"][0].(config.FileInfo) | ||||
|  | ||||
| 	caption := msg.Username + fi.Comment | ||||
|  | ||||
| 	resp, err := b.wc.Upload(context.Background(), *fi.Data, whatsmeow.MediaImage) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	var message proto.Message | ||||
|  | ||||
| 	message.ImageMessage = &proto.ImageMessage{ | ||||
| 		Mimetype:      &filetype, | ||||
| 		Caption:       &caption, | ||||
| 		MediaKey:      resp.MediaKey, | ||||
| 		FileEncSha256: resp.FileEncSHA256, | ||||
| 		FileSha256:    resp.FileSHA256, | ||||
| 		FileLength:    goproto.Uint64(resp.FileLength), | ||||
| 		Url:           &resp.URL, | ||||
| 	} | ||||
|  | ||||
| 	b.Log.Debugf("=> Sending %#v", msg) | ||||
|  | ||||
| 	ID := whatsmeow.GenerateMessageID() | ||||
| 	_, err = b.wc.SendMessage(groupJID, ID, &message) | ||||
|  | ||||
| 	return ID, err | ||||
| } | ||||
|  | ||||
| // Send a message from the bridge to WhatsApp | ||||
| func (b *Bwhatsapp) Send(msg config.Message) (string, error) { | ||||
| 	groupJID, _ := types.ParseJID(msg.Channel) | ||||
|  | ||||
| 	b.Log.Debugf("=> Receiving %#v", msg) | ||||
|  | ||||
| 	// Delete message | ||||
| 	if msg.Event == config.EventMsgDelete { | ||||
| 		if msg.ID == "" { | ||||
| 			// No message ID in case action is executed on a message sent before the bridge was started | ||||
| 			// and then the bridge cache doesn't have this message ID mapped | ||||
| 			return "", nil | ||||
| 		} | ||||
|  | ||||
| 		_, err := b.wc.RevokeMessage(groupJID, msg.ID) | ||||
|  | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	// Edit message | ||||
| 	if msg.ID != "" { | ||||
| 		b.Log.Debugf("updating message with id %s", msg.ID) | ||||
|  | ||||
| 		if b.GetString("editsuffix") != "" { | ||||
| 			msg.Text += b.GetString("EditSuffix") | ||||
| 		} else { | ||||
| 			msg.Text += " (edited)" | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Handle Upload a file | ||||
| 	if msg.Extra["file"] != nil { | ||||
| 		fi := msg.Extra["file"][0].(config.FileInfo) | ||||
| 		filetype := mime.TypeByExtension(filepath.Ext(fi.Name)) | ||||
|  | ||||
| 		b.Log.Debugf("Extra file is %#v", filetype) | ||||
|  | ||||
| 		// TODO: add different types | ||||
| 		// TODO: add webp conversion | ||||
| 		switch filetype { | ||||
| 		case "image/jpeg", "image/png", "image/gif": | ||||
| 			return b.PostImageMessage(msg, filetype) | ||||
| 		default: | ||||
| 			return b.PostDocumentMessage(msg, filetype) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	text := msg.Username + msg.Text | ||||
|  | ||||
| 	var message proto.Message | ||||
|  | ||||
| 	message.Conversation = &text | ||||
|  | ||||
| 	ID := whatsmeow.GenerateMessageID() | ||||
| 	_, err := b.wc.SendMessage(groupJID, ID, &message) | ||||
|  | ||||
| 	return ID, err | ||||
| } | ||||
							
								
								
									
										129
									
								
								changelog.md
									
									
									
									
									
								
							
							
						
						
									
										129
									
								
								changelog.md
									
									
									
									
									
								
							| @@ -1,3 +1,126 @@ | ||||
| # v1.25.2 | ||||
|  | ||||
| ## Enhancements | ||||
|  | ||||
| - general: Update dependencies (#1851,#1841) | ||||
| - mattermost: Support mattermost v7.x (#1852) | ||||
|  | ||||
| ## Bugfix | ||||
|  | ||||
| - discord: Fix Unwanted join notifications from one Discord server to another (#1612) | ||||
| - discord: Ignore events from other guilds, add nosendjoinpart support (#1846) | ||||
|  | ||||
| This release couldn't exist without the following contributors: | ||||
| @wlcx | ||||
|  | ||||
| # v1.25.1 | ||||
|  | ||||
| ## Enhancements | ||||
|  | ||||
| - matrix: Add KeepQuotedReply option for matrix to fix regression (#1823) | ||||
| - slack: Improve Slack attachments formatting (slack) (#1807) | ||||
|  | ||||
| ## Bugfix | ||||
|  | ||||
| - general: Update dependencies (#1813,#1822,#1833) | ||||
| - mattermost: Add space between filename and URL (mattermost). Fixes #1820 | ||||
| - matrix: Update matterbridge/gomatrix. Fixes #1772 (#1803) | ||||
| - telegram: Do not modify .webm files (telegram). Fixes #17**88 (#1802) | ||||
| - telegram: Do not apply any markup to URL entities (telegram) (#1808) | ||||
| - telegram: Fix telegram message deletion request (#1818) | ||||
| - vk: Fix UploadMessagesPhoto for vk community chat (vk) (#1812) | ||||
|  | ||||
| This release couldn't exist without the following contributors: | ||||
| @bd808, @chugunov, @sas1024, @SevereCloud, @ValdikSS | ||||
|  | ||||
| # v1.25.0 | ||||
|  | ||||
| ## Breaking changes | ||||
|  | ||||
| - whatsapp: deprecated, the library <https://github.com/Rhymen/go-whatsapp> isn't maintained anymore. | ||||
| We're switching to <https://github.com/tulir/whatsmeow> but as this uses a GPL3 licensed library we can't provide you with binaries. | ||||
| You'll need to build it yourself. More information about this can be found here: <https://github.com/42wim/matterbridge#building-with-whatsapp-beta-multidevice-support> | ||||
|  | ||||
| ## New features | ||||
|  | ||||
| - whatsappmulti: whatsapp multidevice support added - more info <https://github.com/42wim/matterbridge#building-with-whatsapp-beta-multidevice-support> | ||||
| - general: Add Dockerfile_whatsappmulti for building with WhatsApp Multi-Device support (Whatsmeow) (#1774) | ||||
| - telegram: Add UseFullName option (telegram) (#1777) | ||||
| - slack: Use slack real name as user name (slack) (#1775) | ||||
|  | ||||
| ## Enhancements | ||||
|  | ||||
| - general: Ignore sending file with comment, if comment contains IgnoreMessages value (#1783) | ||||
| - general: Update dependencies (#1784) | ||||
| - irc: Update lrstanley/girc dep (#1773) | ||||
| - slack: Preserve threading for messages with files (slack) (#1781) | ||||
| - telegram: Preserve threading from telegram replies (telegram) (#1776) | ||||
| - telegram: Multiple media in one message (telegram) (#1779) | ||||
| - whatsapp: Add whatsapp deprecation warning (#1792) | ||||
|  | ||||
| ## Bugfix | ||||
|  | ||||
| - discord: Change discord non-native threading behaviour (discord) (#1791) | ||||
|  | ||||
| This release couldn't exist without the following contributors: | ||||
| @sas1024, @tpxtron | ||||
|  | ||||
| # v1.24.1 | ||||
|  | ||||
| ## Enhancements | ||||
|  | ||||
| - discord: Switch to discordgo upstream again (#1759) | ||||
| - general: Update dependencies and vendor (#1761) | ||||
| - general: Create inmessage-logger.tengo (#1688) (#1747) | ||||
| - general: Add OpenRC service file (#1746) | ||||
| - irc: Refactor utf-8 conversion (irc) (#1767) | ||||
|  | ||||
| ## Bugfixes | ||||
|  | ||||
| - irc: Fix panic in irc. Closes #1751 (#1760) | ||||
| - mumble: Implement a workaround to signal Opus support (mumble) (#1764) | ||||
| - telegram: Fix for complex-formatted Telegram text (#1765) | ||||
| - telegram: Fix Telegram channel title in forwards (#1753) | ||||
| - telegram: Fix Telegram Problem (unforwarded formatting and skipping of linebreaks) (#1749) | ||||
|  | ||||
| This release couldn't exist without the following contributors: | ||||
| @s3lph, @ValdikSS, @reckel-jm, @CyberTailor | ||||
|  | ||||
| # v1.24.0 | ||||
|  | ||||
| ## New features | ||||
|  | ||||
| - harmony: new protocol added: Add support for Harmony (#1656) | ||||
| - irc: Allow binding to IP on IRC (#1640) | ||||
| - irc: Add support for client certificate (irc) (#1710) | ||||
| - mattermost: Add UseUsername option (mattermost). Fixes #1665 (#1714) | ||||
| - mattermost: Add support for using ID in channel config (mattermost) (#1715) | ||||
| - matrix: Reply support for Matrix (#1664) | ||||
| - telegram: Add Telegram Bot Command /chatId (telegram) (#1703) | ||||
|  | ||||
| ## Enhancements | ||||
|  | ||||
| - general: Update dependencies/vendor (#1659) | ||||
| - discord: Add more debug options for discord (#1712) | ||||
| - docker: Use Alpine stable again in Dockerfile (#1643) | ||||
| - mattermost: Log eventtype in debug (mattermost) (#1676) | ||||
| - mattermost: Add more ignore debug messages (mattermost) (#1678) | ||||
| - slack: Add support for deleting files from slack to discord. Fixes #1705 (#1709) | ||||
| - telegram: Add support for code blocks in telegram (#1650) | ||||
| - telegram: Update telegram-bot-api to v5 (#1660) | ||||
| - telegram: Add comments to messages (telegram) (#1652) | ||||
| - telegram: Add support for sender_chat (telegram) (#1677) | ||||
| - vk: Remove GroupID (vk) (#1668) | ||||
|  | ||||
| ## Bugfix | ||||
|  | ||||
| - mattermost: Use current parentID if rootId is not set (mattermost) (#1675) | ||||
| - matrix: Make HTMLDisable work correct (matrix) (#1716) | ||||
| - whatsapp: Make EditSuffix option actually work (whatsapp). Fixes #1510 (#1728) | ||||
|  | ||||
| This release couldn't exist without the following contributors: | ||||
| @DavyJohnesev, @GoliathLabs, @pontaoski, @PeGaSuS-Coder, @dependabot[bot], @vpzomtrrfrt, @SevereCloud, @soloam, @YashRE42, @danwalmsley, @SuperSandro2000, @inzanity | ||||
|  | ||||
| # v1.23.2 | ||||
|  | ||||
| If you're running whatsapp you should update. | ||||
| @@ -6,6 +129,9 @@ If you're running whatsapp you should update. | ||||
|  | ||||
| - whatsapp: Update go-whatsapp version (#1630) | ||||
|  | ||||
| This release couldn't exist without the following contributors: | ||||
| @snikpic | ||||
|  | ||||
| # v1.23.1 | ||||
|  | ||||
| If you're running mattermost 6 you should update. | ||||
| @@ -20,6 +146,9 @@ If you're running mattermost 6 you should update. | ||||
| - xmpp: Use a new msgID when replacing messages (xmpp). Fixes #1584 (#1623) | ||||
| - zulip: Add better error handling on Zulip (#1589) | ||||
|  | ||||
| This release couldn't exist without the following contributors: | ||||
| @Polynomdivision, @minecraftchest1, @alexmv | ||||
|  | ||||
| # v1.23.0 | ||||
|  | ||||
| ## New features | ||||
|   | ||||
							
								
								
									
										15
									
								
								contrib/inmessage-logger.tengo
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								contrib/inmessage-logger.tengo
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| fmt := import("fmt") | ||||
| os := import("os") | ||||
| times := import("times") | ||||
|  | ||||
| if msgText != "" && msgUsername != "system" { | ||||
|     os.chdir("/var/www/matterbridge") | ||||
|     file := os.open_file("inmessage.log", os.o_append|os.o_wronly|os.o_create, 0644) | ||||
|     file.write_string(fmt.sprintf( | ||||
|         "[%s] <%s> %s\n", | ||||
|         times.time_format(times.now(), times.format_rfc1123), | ||||
|         msgUsername, | ||||
|         msgText | ||||
|     )) | ||||
|     file.close() | ||||
| } | ||||
							
								
								
									
										19
									
								
								contrib/matterbridge.openrc
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										19
									
								
								contrib/matterbridge.openrc
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| #!/sbin/openrc-run | ||||
| # Copyright 2021-2022 Gentoo Authors | ||||
| # Distributed under the terms of the GNU General Public License v2 | ||||
|  | ||||
| command=/usr/bin/matterbridge | ||||
| command_args="-conf ${MATTERBRIDGE_CONF:-/etc/matterbridge/bridge.toml} ${MATTERBRIDGE_ARGS}" | ||||
| command_user="matterbridge:matterbridge" | ||||
| pidfile="/run/${RC_SVCNAME}.pid" | ||||
| command_background=1 | ||||
| output_log="/var/log/${RC_SVCNAME}.log" | ||||
| error_log="${output_log}" | ||||
|  | ||||
| depend() { | ||||
| 	need net | ||||
| } | ||||
|  | ||||
| start_pre() { | ||||
| 	checkpath -f "${output_log}" -o "${command_user}" || return 1 | ||||
| } | ||||
							
								
								
									
										12
									
								
								gateway/bridgemap/bharmony.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								gateway/bridgemap/bharmony.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| //go:build !noharmony | ||||
| // +build !noharmony | ||||
|  | ||||
| package bridgemap | ||||
|  | ||||
| import ( | ||||
| 	bharmony "github.com/42wim/matterbridge/bridge/harmony" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	FullMap["harmony"] = bharmony.New | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| // +build !nowhatsapp | ||||
| // +build !whatsappmulti | ||||
|  | ||||
| package bridgemap | ||||
|  | ||||
|   | ||||
							
								
								
									
										11
									
								
								gateway/bridgemap/bwhatsappmulti.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								gateway/bridgemap/bwhatsappmulti.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| // +build whatsappmulti | ||||
|  | ||||
| package bridgemap | ||||
|  | ||||
| import ( | ||||
| 	bwhatsapp "github.com/42wim/matterbridge/bridge/whatsappmulti" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	FullMap["whatsapp"] = bwhatsapp.New | ||||
| } | ||||
| @@ -66,7 +66,7 @@ func New(rootLogger *logrus.Logger, cfg *config.Gateway, r *Router) *Gateway { | ||||
| func (gw *Gateway) FindCanonicalMsgID(protocol string, mID string) string { | ||||
| 	ID := protocol + " " + mID | ||||
| 	if gw.Messages.Contains(ID) { | ||||
| 		return mID | ||||
| 		return ID | ||||
| 	} | ||||
|  | ||||
| 	// If not keyed, iterate through cache for downstream, and infer upstream. | ||||
| @@ -75,7 +75,7 @@ func (gw *Gateway) FindCanonicalMsgID(protocol string, mID string) string { | ||||
| 		ids := v.([]*BrMsgID) | ||||
| 		for _, downstreamMsgObj := range ids { | ||||
| 			if ID == downstreamMsgObj.ID { | ||||
| 				return strings.Replace(mid.(string), protocol+" ", "", 1) | ||||
| 				return mid.(string) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| @@ -299,13 +299,30 @@ func (gw *Gateway) ignoreMessage(msg *config.Message) bool { | ||||
|  | ||||
| 	igNicks := strings.Fields(gw.Bridges[msg.Account].GetString("IgnoreNicks")) | ||||
| 	igMessages := strings.Fields(gw.Bridges[msg.Account].GetString("IgnoreMessages")) | ||||
| 	if gw.ignoreTextEmpty(msg) || gw.ignoreText(msg.Username, igNicks) || gw.ignoreText(msg.Text, igMessages) { | ||||
| 	if gw.ignoreTextEmpty(msg) || gw.ignoreText(msg.Username, igNicks) || gw.ignoreText(msg.Text, igMessages) || gw.ignoreFilesComment(msg.Extra, igMessages) { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // ignoreFilesComment returns true if we need to ignore a file with matched comment. | ||||
| func (gw *Gateway) ignoreFilesComment(extra map[string][]interface{}, igMessages []string) bool { | ||||
| 	if extra == nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	for _, f := range extra["file"] { | ||||
| 		fi, ok := f.(config.FileInfo) | ||||
| 		if !ok { | ||||
| 			continue | ||||
| 		} | ||||
| 		if gw.ignoreText(fi.Comment, igMessages) { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (gw *Gateway) modifyUsername(msg *config.Message, dest *bridge.Bridge) string { | ||||
| 	if dest.GetBool("StripNick") { | ||||
| 		re := regexp.MustCompile("[^a-zA-Z0-9]+") | ||||
| @@ -447,16 +464,19 @@ func (gw *Gateway) SendMessage( | ||||
| 	msg.Avatar = gw.modifyAvatar(rmsg, dest) | ||||
| 	msg.Username = gw.modifyUsername(rmsg, dest) | ||||
|  | ||||
| 	msg.ID = gw.getDestMsgID(rmsg.Protocol+" "+rmsg.ID, dest, channel) | ||||
| 	// exclude file delete event as the msg ID here is the native file ID that needs to be deleted | ||||
| 	if msg.Event != config.EventFileDelete { | ||||
| 		msg.ID = gw.getDestMsgID(rmsg.Protocol+" "+rmsg.ID, dest, channel) | ||||
| 	} | ||||
|  | ||||
| 	// for api we need originchannel as channel | ||||
| 	if dest.Protocol == apiProtocol { | ||||
| 		msg.Channel = rmsg.Channel | ||||
| 	} | ||||
|  | ||||
| 	msg.ParentID = gw.getDestMsgID(rmsg.Protocol+" "+canonicalParentMsgID, dest, channel) | ||||
| 	msg.ParentID = gw.getDestMsgID(canonicalParentMsgID, dest, channel) | ||||
| 	if msg.ParentID == "" { | ||||
| 		msg.ParentID = canonicalParentMsgID | ||||
| 		msg.ParentID = strings.Replace(canonicalParentMsgID, dest.Protocol+" ", "", 1) | ||||
| 	} | ||||
|  | ||||
| 	// if the parentID is still empty and we have a parentID set in the original message | ||||
|   | ||||
							
								
								
									
										131
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										131
									
								
								go.mod
									
									
									
									
									
								
							| @@ -6,54 +6,60 @@ require ( | ||||
| 	github.com/Benau/tgsconverter v0.0.0-20210809170556-99f4a4f6337f | ||||
| 	github.com/Philipp15b/go-steam v1.0.1-0.20200727090957-6ae9b3c0a560 | ||||
| 	github.com/Rhymen/go-whatsapp v0.1.2-0.20211102134409-31a2e740845c | ||||
| 	github.com/SevereCloud/vksdk/v2 v2.10.0 | ||||
| 	github.com/d5/tengo/v2 v2.8.0 | ||||
| 	github.com/SevereCloud/vksdk/v2 v2.14.1 | ||||
| 	github.com/bwmarrin/discordgo v0.25.0 | ||||
| 	github.com/d5/tengo/v2 v2.12.0 | ||||
| 	github.com/davecgh/go-spew v1.1.1 | ||||
| 	github.com/fsnotify/fsnotify v1.5.1 | ||||
| 	github.com/go-telegram-bot-api/telegram-bot-api v1.0.1-0.20200524105306-7434b0456e81 | ||||
| 	github.com/gomarkdown/markdown v0.0.0-20210918233619-6c1113f12c4a | ||||
| 	github.com/google/gops v0.3.22 | ||||
| 	github.com/fsnotify/fsnotify v1.5.4 | ||||
| 	github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 | ||||
| 	github.com/gomarkdown/markdown v0.0.0-20220607163217-45f7c050e2d1 | ||||
| 	github.com/google/gops v0.3.23 | ||||
| 	github.com/gorilla/schema v1.2.0 | ||||
| 	github.com/gorilla/websocket v1.4.2 | ||||
| 	github.com/gorilla/websocket v1.5.0 | ||||
| 	github.com/harmony-development/shibshib v0.0.0-20220101224523-c98059d09cfa | ||||
| 	github.com/hashicorp/golang-lru v0.5.4 | ||||
| 	github.com/jpillora/backoff v1.0.0 | ||||
| 	github.com/keybase/go-keybase-chat-bot v0.0.0-20211004153716-fd2ee4d6be11 | ||||
| 	github.com/kyokomi/emoji/v2 v2.2.8 | ||||
| 	github.com/labstack/echo/v4 v4.6.1 | ||||
| 	github.com/lrstanley/girc v0.0.0-20210611213246-771323f1624b | ||||
| 	github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 | ||||
| 	github.com/keybase/go-keybase-chat-bot v0.0.0-20220322223021-75d497527469 | ||||
| 	github.com/kyokomi/emoji/v2 v2.2.9 | ||||
| 	github.com/labstack/echo/v4 v4.7.2 | ||||
| 	github.com/lrstanley/girc v0.0.0-20220507183218-96757fe3d2a2 | ||||
| 	github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20211016222428-79310a412696 | ||||
| 	github.com/matterbridge/discordgo v0.21.2-0.20210201201054-fb39a175b4f7 | ||||
| 	github.com/matterbridge/go-xmpp v0.0.0-20210731150933-5702291c239f | ||||
| 	github.com/matterbridge/go-xmpp v0.0.0-20211030125215-791a06c5f1be | ||||
| 	github.com/matterbridge/gomatrix v0.0.0-20220411225302-271e5088ea27 | ||||
| 	github.com/matterbridge/gozulipbot v0.0.0-20211023205727-a19d6c1f3b75 | ||||
| 	github.com/matterbridge/logrus-prefixed-formatter v0.5.3-0.20200523233437-d971309a77ba | ||||
| 	github.com/matterbridge/matterclient v0.0.0-20211024214211-22e762684b4a | ||||
| 	github.com/mattermost/mattermost-server/v5 v5.39.0 | ||||
| 	github.com/mattermost/mattermost-server/v6 v6.0.2 | ||||
| 	github.com/matterbridge/matterclient v0.0.0-20220430213656-07aca2731bc9 | ||||
| 	github.com/mattermost/mattermost-server/v5 v5.39.3 | ||||
| 	github.com/mattermost/mattermost-server/v6 v6.7.0 | ||||
| 	github.com/mattn/godown v0.0.1 | ||||
| 	github.com/missdeer/golib v1.0.4 | ||||
| 	github.com/nelsonken/gomf v0.0.0-20180504123937-a9dd2f9deae9 | ||||
| 	github.com/mdp/qrterminal v1.0.1 | ||||
| 	github.com/nelsonken/gomf v0.0.0-20190423072027-c65cc0469e94 | ||||
| 	github.com/paulrosania/go-charset v0.0.0-20190326053356-55c9d7a5834c | ||||
| 	github.com/rs/xid v1.3.0 | ||||
| 	github.com/rs/xid v1.4.0 | ||||
| 	github.com/russross/blackfriday v1.6.0 | ||||
| 	github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca | ||||
| 	github.com/shazow/ssh-chat v1.10.1 | ||||
| 	github.com/sirupsen/logrus v1.8.1 | ||||
| 	github.com/slack-go/slack v0.9.5 | ||||
| 	github.com/spf13/viper v1.9.0 | ||||
| 	github.com/stretchr/testify v1.7.0 | ||||
| 	github.com/vincent-petithory/dataurl v0.0.0-20191104211930-d1553a71de50 | ||||
| 	github.com/slack-go/slack v0.11.0 | ||||
| 	github.com/spf13/viper v1.12.0 | ||||
| 	github.com/stretchr/testify v1.7.2 | ||||
| 	github.com/vincent-petithory/dataurl v1.0.0 | ||||
| 	github.com/writeas/go-strip-markdown v2.0.1+incompatible | ||||
| 	github.com/yaegashi/msgraph.go v0.1.4 | ||||
| 	github.com/zfjagann/golang-ring v0.0.0-20210116075443-7c86fdb43134 | ||||
| 	golang.org/x/image v0.0.0-20211028202545-6944b10bf410 | ||||
| 	golang.org/x/oauth2 v0.0.0-20211028175245-ba495a64dcb5 | ||||
| 	github.com/zfjagann/golang-ring v0.0.0-20220330170733-19bcea1b6289 | ||||
| 	go.mau.fi/whatsmeow v0.0.0-20220624184947-57a69a641154 | ||||
| 	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 | ||||
| 	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 | ||||
| 	layeh.com/gumble v0.0.0-20200818122324-146f9205029b | ||||
| 	modernc.org/sqlite v1.17.3 | ||||
| ) | ||||
|  | ||||
| require ( | ||||
| 	filippo.io/edwards25519 v1.0.0-rc.1 // indirect | ||||
| 	github.com/Benau/go_rlottie v0.0.0-20210807002906-98c1b2421989 // indirect | ||||
| 	github.com/Jeffail/gabs v1.4.0 // indirect | ||||
| 	github.com/apex/log v1.9.0 // indirect | ||||
| @@ -65,72 +71,93 @@ require ( | ||||
| 	github.com/go-asn1-ber/asn1-ber v1.5.3 // indirect | ||||
| 	github.com/golang-jwt/jwt v3.2.2+incompatible // indirect | ||||
| 	github.com/golang/protobuf v1.5.2 // indirect | ||||
| 	github.com/google/uuid v1.2.0 // indirect | ||||
| 	github.com/google/uuid v1.3.0 // indirect | ||||
| 	github.com/gopackage/ddp v0.0.3 // indirect | ||||
| 	github.com/graph-gophers/graphql-go v1.3.0 // indirect | ||||
| 	github.com/hashicorp/errwrap v1.1.0 // indirect | ||||
| 	github.com/hashicorp/go-multierror v1.1.1 // indirect | ||||
| 	github.com/hashicorp/hcl v1.0.0 // indirect | ||||
| 	github.com/json-iterator/go v1.1.11 // indirect | ||||
| 	github.com/json-iterator/go v1.1.12 // indirect | ||||
| 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect | ||||
| 	github.com/kettek/apng v0.0.0-20191108220231-414630eed80f // indirect | ||||
| 	github.com/klauspost/cpuid/v2 v2.0.6 // indirect | ||||
| 	github.com/labstack/gommon v0.3.0 // indirect | ||||
| 	github.com/magiconair/properties v1.8.5 // indirect | ||||
| 	github.com/klauspost/compress v1.15.6 // indirect | ||||
| 	github.com/klauspost/cpuid/v2 v2.0.12 // indirect | ||||
| 	github.com/labstack/gommon v0.3.1 // indirect | ||||
| 	github.com/magiconair/properties v1.8.6 // indirect | ||||
| 	github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 // indirect | ||||
| 	github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d // indirect | ||||
| 	github.com/mattermost/logr v1.0.13 // indirect | ||||
| 	github.com/mattermost/logr/v2 v2.0.10 // indirect | ||||
| 	github.com/mattn/go-colorable v0.1.8 // indirect | ||||
| 	github.com/mattermost/logr/v2 v2.0.15 // indirect | ||||
| 	github.com/mattn/go-colorable v0.1.12 // indirect | ||||
| 	github.com/mattn/go-isatty v0.0.14 // indirect | ||||
| 	github.com/mattn/go-runewidth v0.0.13 // indirect | ||||
| 	github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect | ||||
| 	github.com/minio/md5-simd v1.1.2 // indirect | ||||
| 	github.com/minio/minio-go/v7 v7.0.11 // indirect | ||||
| 	github.com/minio/minio-go/v7 v7.0.24 // indirect | ||||
| 	github.com/minio/sha256-simd v1.0.0 // indirect | ||||
| 	github.com/mitchellh/go-homedir v1.1.0 // indirect | ||||
| 	github.com/mitchellh/mapstructure v1.4.2 // indirect | ||||
| 	github.com/mitchellh/mapstructure v1.5.0 // indirect | ||||
| 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | ||||
| 	github.com/modern-go/reflect2 v1.0.1 // indirect | ||||
| 	github.com/modern-go/reflect2 v1.0.2 // indirect | ||||
| 	github.com/monaco-io/request v1.0.5 // indirect | ||||
| 	github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d // indirect | ||||
| 	github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff // indirect | ||||
| 	github.com/opentracing/opentracing-go v1.2.0 // indirect | ||||
| 	github.com/pborman/uuid v1.2.1 // indirect | ||||
| 	github.com/pelletier/go-toml v1.9.4 // indirect | ||||
| 	github.com/pelletier/go-toml v1.9.5 // indirect | ||||
| 	github.com/pelletier/go-toml/v2 v2.0.1 // indirect | ||||
| 	github.com/philhofer/fwd v1.1.1 // indirect | ||||
| 	github.com/pkg/errors v0.9.1 // indirect | ||||
| 	github.com/pmezard/go-difflib v1.0.0 // indirect | ||||
| 	github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect | ||||
| 	github.com/rickb777/date v1.12.4 // indirect | ||||
| 	github.com/rickb777/plural v1.2.0 // indirect | ||||
| 	github.com/rivo/uniseg v0.2.0 // indirect | ||||
| 	github.com/shazow/rateio v0.0.0-20200113175441-4461efc8bdc4 // indirect | ||||
| 	github.com/sizeofint/webpanimation v0.0.0-20210809145948-1d2b32119882 // indirect | ||||
| 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect | ||||
| 	github.com/spf13/afero v1.6.0 // indirect | ||||
| 	github.com/spf13/cast v1.4.1 // indirect | ||||
| 	github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9 // indirect | ||||
| 	github.com/spf13/afero v1.8.2 // indirect | ||||
| 	github.com/spf13/cast v1.5.0 // indirect | ||||
| 	github.com/spf13/jwalterweatherman v1.1.0 // indirect | ||||
| 	github.com/spf13/pflag v1.0.5 // indirect | ||||
| 	github.com/subosito/gotenv v1.2.0 // indirect | ||||
| 	github.com/technoweenie/multipartstreamer v1.0.1 // indirect | ||||
| 	github.com/subosito/gotenv v1.3.0 // indirect | ||||
| 	github.com/tinylib/msgp v1.1.6 // indirect | ||||
| 	github.com/valyala/bytebufferpool v1.0.0 // indirect | ||||
| 	github.com/valyala/fasttemplate v1.2.1 // indirect | ||||
| 	github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect | ||||
| 	github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect | ||||
| 	github.com/wiggin77/cfg v1.0.2 // indirect | ||||
| 	github.com/wiggin77/merror v1.0.3 // indirect | ||||
| 	github.com/wiggin77/srslog v1.0.1 // indirect | ||||
| 	go.uber.org/atomic v1.8.0 // indirect | ||||
| 	go.mau.fi/libsignal v0.0.0-20220425070825-c40c839ee6a0 // indirect | ||||
| 	go.uber.org/atomic v1.9.0 // indirect | ||||
| 	go.uber.org/multierr v1.7.0 // indirect | ||||
| 	go.uber.org/zap v1.17.0 // indirect | ||||
| 	golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect | ||||
| 	golang.org/x/net v0.0.0-20210913180222-943fd674d43e // indirect | ||||
| 	golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0 // indirect | ||||
| 	golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect | ||||
| 	golang.org/x/text v0.3.7 // indirect | ||||
| 	golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect | ||||
| 	golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // 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/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/protobuf v1.27.1 // indirect | ||||
| 	gopkg.in/ini.v1 v1.63.2 // indirect | ||||
| 	gopkg.in/ini.v1 v1.66.4 // indirect | ||||
| 	gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect | ||||
| 	gopkg.in/yaml.v2 v2.4.0 // indirect | ||||
| 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect | ||||
| 	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 | ||||
|  | ||||
| go 1.17 | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import ( | ||||
| 	"log" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"regexp" | ||||
| ) | ||||
|  | ||||
| // Message for rocketchat outgoing webhook. | ||||
| @@ -68,7 +69,6 @@ func (c *Client) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 	} | ||||
| 	msg := Message{} | ||||
| 	body, err := ioutil.ReadAll(r.Body) | ||||
| 	log.Println(string(body)) | ||||
| 	if err != nil { | ||||
| 		log.Println(err) | ||||
| 		http.NotFound(w, r) | ||||
| @@ -89,7 +89,11 @@ func (c *Client) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 	msg.ChannelName = "#" + msg.ChannelName | ||||
| 	if c.Token != "" { | ||||
| 		if msg.Token != c.Token { | ||||
| 			log.Println("invalid token " + msg.Token + " from " + r.RemoteAddr) | ||||
| 			if regexp.MustCompile(`[^a-zA-Z0-9]+`).MatchString(msg.Token) { | ||||
| 				log.Println("invalid token " + msg.Token + " from " + r.RemoteAddr) | ||||
| 			} else { | ||||
| 				log.Println("invalid token from " + r.RemoteAddr) | ||||
| 			} | ||||
| 			http.NotFound(w, r) | ||||
| 			return | ||||
| 		} | ||||
|   | ||||
| @@ -24,6 +24,13 @@ Password="" | ||||
| #OPTIONAL (default false) | ||||
| UseTLS=false | ||||
|  | ||||
| #Use client certificate - see CertFP https://libera.chat/guides/certfp.html | ||||
| #Specify filename which contains private key and cert | ||||
| #OPTIONAL (default "") | ||||
| # | ||||
| #TLSClientCertificate="cert.pem" | ||||
| TLSClientCertificate="" | ||||
|  | ||||
| #Enable SASL (PLAIN) authentication. (libera requires this from eg AWS hosts) | ||||
| #It uses NickServNick and NickServPassword as login and password | ||||
| #OPTIONAL (default false) | ||||
| @@ -34,6 +41,11 @@ UseSASL=false | ||||
| #OPTIONAL (default false) | ||||
| SkipTLSVerify=true | ||||
|  | ||||
| #Local address to use for server connection | ||||
| #Note that Server and Bind must resolve to addresses of the same family. | ||||
| #OPTIONAL (default "") | ||||
| Bind="" | ||||
|  | ||||
| #If you know your charset, you can specify it manually. | ||||
| #Otherwise it tries to detect this automatically. Select one below | ||||
| # "iso-8859-2:1987", "iso-8859-9:1989", "866", "latin9", "iso-8859-10:1992", "iso-ir-109", "hebrew", | ||||
| @@ -185,7 +197,7 @@ ShowJoinPart=false | ||||
| VerboseJoinPart=false | ||||
|  | ||||
| #Do not send joins/parts to other bridges | ||||
| #Currently works for messages from the following bridges: irc, mattermost, slack | ||||
| #Currently works for messages from the following bridges: irc, mattermost, slack, discord | ||||
| #OPTIONAL (default false) | ||||
| NoSendJoinPart=false | ||||
|  | ||||
| @@ -396,6 +408,10 @@ SkipTLSVerify=true | ||||
| ## RELOADABLE SETTINGS | ||||
| ## Settings below can be reloaded by editing the file | ||||
|  | ||||
| # UseUserName shows the username instead of the server nickname | ||||
| # OPTIONAL (default false) | ||||
| UseUserName=false | ||||
|  | ||||
| #how to format the list of IRC nicks when displayed in mattermost. | ||||
| #Possible options are "table" and "plain" | ||||
| #OPTIONAL (default plain) | ||||
| @@ -480,7 +496,7 @@ RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||
| ShowJoinPart=false | ||||
|  | ||||
| #Do not send joins/parts to other bridges | ||||
| #Currently works for messages from the following bridges: irc, mattermost, slack | ||||
| #Currently works for messages from the following bridges: irc, mattermost, slack, discord | ||||
| #OPTIONAL (default false) | ||||
| NoSendJoinPart=false | ||||
|  | ||||
| @@ -814,7 +830,7 @@ RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||
| ShowJoinPart=false | ||||
|  | ||||
| #Do not send joins/parts to other bridges | ||||
| #Currently works for messages from the following bridges: irc, mattermost, slack | ||||
| #Currently works for messages from the following bridges: irc, mattermost, slack, discord | ||||
| #OPTIONAL (default false) | ||||
| NoSendJoinPart=false | ||||
|  | ||||
| @@ -846,6 +862,10 @@ ShowUserTyping=false | ||||
| #Default "<clipped message>" | ||||
| MessageClipped="<clipped message>" | ||||
|  | ||||
| #If enabled use the slack "Real Name" as username. | ||||
| #OPTIONAL (default false) | ||||
| UseFullName=false | ||||
|  | ||||
| ################################################################### | ||||
| #discord section | ||||
| ################################################################### | ||||
| @@ -1020,6 +1040,12 @@ DisableWebPagePreview=false | ||||
| #OPTIONAL (default false) | ||||
| UseFirstName=false | ||||
|  | ||||
| #If enabled use the "Full Name" as username. If this is empty use the Username | ||||
| #If disabled use the "Username" as username. If this is empty use the First Name and Last Name as Full Name | ||||
| #If all names are empty, username will be "unknown" | ||||
| #OPTIONAL (default false) | ||||
| UseFullName=false | ||||
|  | ||||
| #WARNING! If enabled this will relay GIF/stickers/documents and other attachments as URLs | ||||
| #Those URLs will contain your bot-token. This may not be what you want. | ||||
| #For now there is no secure way to relay GIF/stickers/documents without seeing your token. | ||||
| @@ -1125,6 +1151,12 @@ StripNick=false | ||||
| #OPTIONAL (default false) | ||||
| ShowTopicChange=false | ||||
|  | ||||
| #Opportunistically preserve threaded replies between Telegram groups. | ||||
| #This only works if the parent message is still in the cache. | ||||
| #Cache is flushed between restarts. | ||||
| #OPTIONAL (default false) | ||||
| PreserveThreading=false | ||||
|  | ||||
| ################################################################### | ||||
| #rocketchat section | ||||
| ################################################################### | ||||
| @@ -1296,6 +1328,15 @@ HTMLDisable=false | ||||
| # UseUserName shows the username instead of the server nickname | ||||
| UseUserName=false | ||||
|  | ||||
| # Matrix quotes replies and as of matterbridge 1.24.0 we strip those as this causes | ||||
| # issues with bridges support threading and have PreserveThreading enabled. | ||||
| # But if you for example use mattermost or discord with webhooks you'll need to enable  | ||||
| # this (and keep PreserveThreading disabled) if you want something that looks like a reply from matrix. | ||||
| # See issues:  | ||||
| # - https://github.com/42wim/matterbridge/issues/1819 | ||||
| # - https://github.com/42wim/matterbridge/issues/1780 | ||||
| KeepQuotedReply=false | ||||
|  | ||||
| #Nicks you want to ignore. | ||||
| #Regular expressions supported | ||||
| #Messages from those users will not be sent to other bridges. | ||||
| @@ -1534,10 +1575,6 @@ MessageClipped="<clipped message>" | ||||
| #See https://vk.com/dev/bots_docs | ||||
| Token="Yourtokenhere" | ||||
|  | ||||
| #Group ID | ||||
| #For example in URL https://vk.com/public168963511 group ID is 168963511 | ||||
| GroupID=123456789 | ||||
|  | ||||
| ################################################################### | ||||
| # WhatsApp | ||||
| ################################################################### | ||||
| @@ -1655,6 +1692,18 @@ StripNick=false | ||||
| #OPTIONAL (default false) | ||||
| ShowTopicChange=false | ||||
|  | ||||
| ################################################################### | ||||
| # Harmony | ||||
| ################################################################### | ||||
|  | ||||
| [harmony.chat_harmonyapp_io] | ||||
| Homeserver = "https://chat.harmonyapp.io:2289" | ||||
| Token = "your token goes here" | ||||
| UserID = "user id of the bot account" | ||||
| Community = "community id that channels will be located in" | ||||
| UseUserName = true | ||||
| RemoteNickFormat = "{NICK}" | ||||
|  | ||||
| ################################################################### | ||||
| #API | ||||
| ################################################################### | ||||
| @@ -1700,6 +1749,7 @@ RemoteNickFormat="{NICK}" | ||||
|  | ||||
| #RemoteNickFormat defines how remote users appear on this bridge | ||||
| #The string "{NICK}" (case sensitive) will be replaced by the actual nick. | ||||
| #The string "{NOPINGNICK}" (case sensitive) will be replaced by the actual nick / username, but with a ZWSP inside the nick, so the irc user with the same nick won't get pinged. | ||||
| #The string "{USERID}" (case sensitive) will be replaced by the user ID. | ||||
| #The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge | ||||
| #The string "{LABEL}" (case sensitive) will be replaced by label= field of the sending bridge | ||||
| @@ -1872,7 +1922,8 @@ enable=true | ||||
|     # ------------------------------------------------------------------------------------------------------------------------------------- | ||||
|     #    irc     |      channel       |            #general           | The # symbol is required and should be lowercase! | ||||
|     # ------------------------------------------------------------------------------------------------------------------------------------- | ||||
|     # mattermost |      channel       |            general            | This is the channel name as seen in the URL, not the display name | ||||
|     #            |      channel       |            general            | This is the channel name as seen in the URL, not the display name | ||||
|     # mattermost |    channel id      | ID:oc4wifyuojgw5f3nsuweesmz8w | This is the channel ID (only use if you know what you're doing) | ||||
|     # ------------------------------------------------------------------------------------------------------------------------------------- | ||||
|     #   matrix   | #channel:server    |    #yourchannel:matrix.org    | Encrypted rooms are not supported in matrix | ||||
|     # ------------------------------------------------------------------------------------------------------------------------------------- | ||||
| @@ -1898,7 +1949,7 @@ enable=true | ||||
|     # ------------------------------------------------------------------------------------------------------------------------------------- | ||||
|     #    xmpp    |      channel       |            general            | The room name | ||||
|     # ------------------------------------------------------------------------------------------------------------------------------------- | ||||
|     #   zulip    | stream/topic:topic |     general/off-topic:food    | Do not use the # when specifying a topic | ||||
|     #   zulip    | stream/topic:topic |      general/topic:food       | Do not use the # when specifying a topic | ||||
|     # ------------------------------------------------------------------------------------------------------------------------------------- | ||||
|  | ||||
|     # | ||||
| @@ -1947,6 +1998,10 @@ enable=true | ||||
|     account="zulip.streamchat" | ||||
|     channel="general/topic:mytopic" | ||||
|  | ||||
|     [[gateway.inout]] | ||||
|     account="harmony.chat_harmonyapp_io" | ||||
|     channel="channel id goes here" | ||||
|  | ||||
|     #API example | ||||
|     #[[gateway.inout]] | ||||
|     #account="api.local" | ||||
|   | ||||
| @@ -5,7 +5,7 @@ RUN apk add \ | ||||
|     go \ | ||||
|     git \ | ||||
|   && cd /go/src/matterbridge \ | ||||
|   && go build -mod vendor -ldflags "-X github.com/42wim/matterbridge/version.GitHash=$(git log --pretty=format:'%h' -n 1)" -o /bin/matterbridge | ||||
|   && CGO_ENABLED=0 go build -mod vendor -ldflags "-X github.com/42wim/matterbridge/version.GitHash=$(git log --pretty=format:'%h' -n 1)" -o /bin/matterbridge | ||||
|  | ||||
| FROM alpine | ||||
| RUN apk --no-cache add \ | ||||
|   | ||||
							
								
								
									
										27
									
								
								vendor/filippo.io/edwards25519/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								vendor/filippo.io/edwards25519/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| Copyright (c) 2009 The Go Authors. All rights reserved. | ||||
|  | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions are | ||||
| met: | ||||
|  | ||||
|    * Redistributions of source code must retain the above copyright | ||||
| notice, this list of conditions and the following disclaimer. | ||||
|    * Redistributions in binary form must reproduce the above | ||||
| copyright notice, this list of conditions and the following disclaimer | ||||
| in the documentation and/or other materials provided with the | ||||
| distribution. | ||||
|    * Neither the name of Google Inc. nor the names of its | ||||
| contributors may be used to endorse or promote products derived from | ||||
| this software without specific prior written permission. | ||||
|  | ||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
							
								
								
									
										14
									
								
								vendor/filippo.io/edwards25519/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								vendor/filippo.io/edwards25519/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| # filippo.io/edwards25519 | ||||
|  | ||||
| ``` | ||||
| import "filippo.io/edwards25519" | ||||
| ``` | ||||
|  | ||||
| This library implements the edwards25519 elliptic curve, exposing the necessary APIs to build a wide array of higher-level primitives. | ||||
| Read the docs at [pkg.go.dev/filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519). | ||||
|  | ||||
| The code is originally derived from Adam Langley's internal implementation in the Go standard library, and includes George Tankersley's [performance improvements](https://golang.org/cl/71950). It was then further developed by Henry de Valence for use in ristretto255. | ||||
|  | ||||
| Most users don't need this package, and should instead use `crypto/ed25519` for signatures, `golang.org/x/crypto/curve25519` for Diffie-Hellman, or `github.com/gtank/ristretto255` for prime order group logic. However, for anyone currently using a fork of `crypto/ed25519/internal/edwards25519` or `github.com/agl/edwards25519`, this package should be a safer, faster, and more powerful alternative. | ||||
|  | ||||
| Since this package is meant to curb proliferation of edwards25519 implementations in the Go ecosystem, it welcomes requests for new APIs or reviewable performance improvements. | ||||
							
								
								
									
										20
									
								
								vendor/filippo.io/edwards25519/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								vendor/filippo.io/edwards25519/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| // Copyright (c) 2021 The Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| // Package edwards25519 implements group logic for the twisted Edwards curve | ||||
| // | ||||
| //     -x^2 + y^2 = 1 + -(121665/121666)*x^2*y^2 | ||||
| // | ||||
| // This is better known as the Edwards curve equivalent to Curve25519, and is | ||||
| // the curve used by the Ed25519 signature scheme. | ||||
| // | ||||
| // Most users don't need this package, and should instead use crypto/ed25519 for | ||||
| // signatures, golang.org/x/crypto/curve25519 for Diffie-Hellman, or | ||||
| // github.com/gtank/ristretto255 for prime order group logic. | ||||
| // | ||||
| // However, developers who do need to interact with low-level edwards25519 | ||||
| // operations can use this package, which is an extended version of | ||||
| // crypto/ed25519/internal/edwards25519 from the standard library repackaged as | ||||
| // an importable module. | ||||
| package edwards25519 | ||||
							
								
								
									
										428
									
								
								vendor/filippo.io/edwards25519/edwards25519.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										428
									
								
								vendor/filippo.io/edwards25519/edwards25519.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,428 @@ | ||||
| // Copyright (c) 2017 The Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package edwards25519 | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
|  | ||||
| 	"filippo.io/edwards25519/field" | ||||
| ) | ||||
|  | ||||
| // Point types. | ||||
|  | ||||
| type projP1xP1 struct { | ||||
| 	X, Y, Z, T field.Element | ||||
| } | ||||
|  | ||||
| type projP2 struct { | ||||
| 	X, Y, Z field.Element | ||||
| } | ||||
|  | ||||
| // Point represents a point on the edwards25519 curve. | ||||
| // | ||||
| // This type works similarly to math/big.Int, and all arguments and receivers | ||||
| // are allowed to alias. | ||||
| // | ||||
| // The zero value is NOT valid, and it may be used only as a receiver. | ||||
| type Point struct { | ||||
| 	// The point is internally represented in extended coordinates (X, Y, Z, T) | ||||
| 	// where x = X/Z, y = Y/Z, and xy = T/Z per https://eprint.iacr.org/2008/522. | ||||
| 	x, y, z, t field.Element | ||||
|  | ||||
| 	// Make the type not comparable (i.e. used with == or as a map key), as | ||||
| 	// equivalent points can be represented by different Go values. | ||||
| 	_ incomparable | ||||
| } | ||||
|  | ||||
| type incomparable [0]func() | ||||
|  | ||||
| func checkInitialized(points ...*Point) { | ||||
| 	for _, p := range points { | ||||
| 		if p.x == (field.Element{}) && p.y == (field.Element{}) { | ||||
| 			panic("edwards25519: use of uninitialized Point") | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type projCached struct { | ||||
| 	YplusX, YminusX, Z, T2d field.Element | ||||
| } | ||||
|  | ||||
| type affineCached struct { | ||||
| 	YplusX, YminusX, T2d field.Element | ||||
| } | ||||
|  | ||||
| // Constructors. | ||||
|  | ||||
| func (v *projP2) Zero() *projP2 { | ||||
| 	v.X.Zero() | ||||
| 	v.Y.One() | ||||
| 	v.Z.One() | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // identity is the point at infinity. | ||||
| var identity, _ = new(Point).SetBytes([]byte{ | ||||
| 	1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||||
| 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) | ||||
|  | ||||
| // NewIdentityPoint returns a new Point set to the identity. | ||||
| func NewIdentityPoint() *Point { | ||||
| 	return new(Point).Set(identity) | ||||
| } | ||||
|  | ||||
| // generator is the canonical curve basepoint. See TestGenerator for the | ||||
| // correspondence of this encoding with the values in RFC 8032. | ||||
| var generator, _ = new(Point).SetBytes([]byte{ | ||||
| 	0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, | ||||
| 	0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, | ||||
| 	0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, | ||||
| 	0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66}) | ||||
|  | ||||
| // NewGeneratorPoint returns a new Point set to the canonical generator. | ||||
| func NewGeneratorPoint() *Point { | ||||
| 	return new(Point).Set(generator) | ||||
| } | ||||
|  | ||||
| func (v *projCached) Zero() *projCached { | ||||
| 	v.YplusX.One() | ||||
| 	v.YminusX.One() | ||||
| 	v.Z.One() | ||||
| 	v.T2d.Zero() | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| func (v *affineCached) Zero() *affineCached { | ||||
| 	v.YplusX.One() | ||||
| 	v.YminusX.One() | ||||
| 	v.T2d.Zero() | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // Assignments. | ||||
|  | ||||
| // Set sets v = u, and returns v. | ||||
| func (v *Point) Set(u *Point) *Point { | ||||
| 	*v = *u | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // Encoding. | ||||
|  | ||||
| // Bytes returns the canonical 32-byte encoding of v, according to RFC 8032, | ||||
| // Section 5.1.2. | ||||
| func (v *Point) Bytes() []byte { | ||||
| 	// This function is outlined to make the allocations inline in the caller | ||||
| 	// rather than happen on the heap. | ||||
| 	var buf [32]byte | ||||
| 	return v.bytes(&buf) | ||||
| } | ||||
|  | ||||
| func (v *Point) bytes(buf *[32]byte) []byte { | ||||
| 	checkInitialized(v) | ||||
|  | ||||
| 	var zInv, x, y field.Element | ||||
| 	zInv.Invert(&v.z)       // zInv = 1 / Z | ||||
| 	x.Multiply(&v.x, &zInv) // x = X / Z | ||||
| 	y.Multiply(&v.y, &zInv) // y = Y / Z | ||||
|  | ||||
| 	out := copyFieldElement(buf, &y) | ||||
| 	out[31] |= byte(x.IsNegative() << 7) | ||||
| 	return out | ||||
| } | ||||
|  | ||||
| var feOne = new(field.Element).One() | ||||
|  | ||||
| // SetBytes sets v = x, where x is a 32-byte encoding of v. If x does not | ||||
| // represent a valid point on the curve, SetBytes returns nil and an error and | ||||
| // the receiver is unchanged. Otherwise, SetBytes returns v. | ||||
| // | ||||
| // Note that SetBytes accepts all non-canonical encodings of valid points. | ||||
| // That is, it follows decoding rules that match most implementations in | ||||
| // the ecosystem rather than RFC 8032. | ||||
| func (v *Point) SetBytes(x []byte) (*Point, error) { | ||||
| 	// Specifically, the non-canonical encodings that are accepted are | ||||
| 	//   1) the ones where the field element is not reduced (see the | ||||
| 	//      (*field.Element).SetBytes docs) and | ||||
| 	//   2) the ones where the x-coordinate is zero and the sign bit is set. | ||||
| 	// | ||||
| 	// This is consistent with crypto/ed25519/internal/edwards25519. Read more | ||||
| 	// at https://hdevalence.ca/blog/2020-10-04-its-25519am, specifically the | ||||
| 	// "Canonical A, R" section. | ||||
|  | ||||
| 	y, err := new(field.Element).SetBytes(x) | ||||
| 	if err != nil { | ||||
| 		return nil, errors.New("edwards25519: invalid point encoding length") | ||||
| 	} | ||||
|  | ||||
| 	// -x² + y² = 1 + dx²y² | ||||
| 	// x² + dx²y² = x²(dy² + 1) = y² - 1 | ||||
| 	// x² = (y² - 1) / (dy² + 1) | ||||
|  | ||||
| 	// u = y² - 1 | ||||
| 	y2 := new(field.Element).Square(y) | ||||
| 	u := new(field.Element).Subtract(y2, feOne) | ||||
|  | ||||
| 	// v = dy² + 1 | ||||
| 	vv := new(field.Element).Multiply(y2, d) | ||||
| 	vv = vv.Add(vv, feOne) | ||||
|  | ||||
| 	// x = +√(u/v) | ||||
| 	xx, wasSquare := new(field.Element).SqrtRatio(u, vv) | ||||
| 	if wasSquare == 0 { | ||||
| 		return nil, errors.New("edwards25519: invalid point encoding") | ||||
| 	} | ||||
|  | ||||
| 	// Select the negative square root if the sign bit is set. | ||||
| 	xxNeg := new(field.Element).Negate(xx) | ||||
| 	xx = xx.Select(xxNeg, xx, int(x[31]>>7)) | ||||
|  | ||||
| 	v.x.Set(xx) | ||||
| 	v.y.Set(y) | ||||
| 	v.z.One() | ||||
| 	v.t.Multiply(xx, y) // xy = T / Z | ||||
|  | ||||
| 	return v, nil | ||||
| } | ||||
|  | ||||
| func copyFieldElement(buf *[32]byte, v *field.Element) []byte { | ||||
| 	copy(buf[:], v.Bytes()) | ||||
| 	return buf[:] | ||||
| } | ||||
|  | ||||
| // Conversions. | ||||
|  | ||||
| func (v *projP2) FromP1xP1(p *projP1xP1) *projP2 { | ||||
| 	v.X.Multiply(&p.X, &p.T) | ||||
| 	v.Y.Multiply(&p.Y, &p.Z) | ||||
| 	v.Z.Multiply(&p.Z, &p.T) | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| func (v *projP2) FromP3(p *Point) *projP2 { | ||||
| 	v.X.Set(&p.x) | ||||
| 	v.Y.Set(&p.y) | ||||
| 	v.Z.Set(&p.z) | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| func (v *Point) fromP1xP1(p *projP1xP1) *Point { | ||||
| 	v.x.Multiply(&p.X, &p.T) | ||||
| 	v.y.Multiply(&p.Y, &p.Z) | ||||
| 	v.z.Multiply(&p.Z, &p.T) | ||||
| 	v.t.Multiply(&p.X, &p.Y) | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| func (v *Point) fromP2(p *projP2) *Point { | ||||
| 	v.x.Multiply(&p.X, &p.Z) | ||||
| 	v.y.Multiply(&p.Y, &p.Z) | ||||
| 	v.z.Square(&p.Z) | ||||
| 	v.t.Multiply(&p.X, &p.Y) | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // d is a constant in the curve equation. | ||||
| var d, _ = new(field.Element).SetBytes([]byte{ | ||||
| 	0xa3, 0x78, 0x59, 0x13, 0xca, 0x4d, 0xeb, 0x75, | ||||
| 	0xab, 0xd8, 0x41, 0x41, 0x4d, 0x0a, 0x70, 0x00, | ||||
| 	0x98, 0xe8, 0x79, 0x77, 0x79, 0x40, 0xc7, 0x8c, | ||||
| 	0x73, 0xfe, 0x6f, 0x2b, 0xee, 0x6c, 0x03, 0x52}) | ||||
| var d2 = new(field.Element).Add(d, d) | ||||
|  | ||||
| func (v *projCached) FromP3(p *Point) *projCached { | ||||
| 	v.YplusX.Add(&p.y, &p.x) | ||||
| 	v.YminusX.Subtract(&p.y, &p.x) | ||||
| 	v.Z.Set(&p.z) | ||||
| 	v.T2d.Multiply(&p.t, d2) | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| func (v *affineCached) FromP3(p *Point) *affineCached { | ||||
| 	v.YplusX.Add(&p.y, &p.x) | ||||
| 	v.YminusX.Subtract(&p.y, &p.x) | ||||
| 	v.T2d.Multiply(&p.t, d2) | ||||
|  | ||||
| 	var invZ field.Element | ||||
| 	invZ.Invert(&p.z) | ||||
| 	v.YplusX.Multiply(&v.YplusX, &invZ) | ||||
| 	v.YminusX.Multiply(&v.YminusX, &invZ) | ||||
| 	v.T2d.Multiply(&v.T2d, &invZ) | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // (Re)addition and subtraction. | ||||
|  | ||||
| // Add sets v = p + q, and returns v. | ||||
| func (v *Point) Add(p, q *Point) *Point { | ||||
| 	checkInitialized(p, q) | ||||
| 	qCached := new(projCached).FromP3(q) | ||||
| 	result := new(projP1xP1).Add(p, qCached) | ||||
| 	return v.fromP1xP1(result) | ||||
| } | ||||
|  | ||||
| // Subtract sets v = p - q, and returns v. | ||||
| func (v *Point) Subtract(p, q *Point) *Point { | ||||
| 	checkInitialized(p, q) | ||||
| 	qCached := new(projCached).FromP3(q) | ||||
| 	result := new(projP1xP1).Sub(p, qCached) | ||||
| 	return v.fromP1xP1(result) | ||||
| } | ||||
|  | ||||
| func (v *projP1xP1) Add(p *Point, q *projCached) *projP1xP1 { | ||||
| 	var YplusX, YminusX, PP, MM, TT2d, ZZ2 field.Element | ||||
|  | ||||
| 	YplusX.Add(&p.y, &p.x) | ||||
| 	YminusX.Subtract(&p.y, &p.x) | ||||
|  | ||||
| 	PP.Multiply(&YplusX, &q.YplusX) | ||||
| 	MM.Multiply(&YminusX, &q.YminusX) | ||||
| 	TT2d.Multiply(&p.t, &q.T2d) | ||||
| 	ZZ2.Multiply(&p.z, &q.Z) | ||||
|  | ||||
| 	ZZ2.Add(&ZZ2, &ZZ2) | ||||
|  | ||||
| 	v.X.Subtract(&PP, &MM) | ||||
| 	v.Y.Add(&PP, &MM) | ||||
| 	v.Z.Add(&ZZ2, &TT2d) | ||||
| 	v.T.Subtract(&ZZ2, &TT2d) | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| func (v *projP1xP1) Sub(p *Point, q *projCached) *projP1xP1 { | ||||
| 	var YplusX, YminusX, PP, MM, TT2d, ZZ2 field.Element | ||||
|  | ||||
| 	YplusX.Add(&p.y, &p.x) | ||||
| 	YminusX.Subtract(&p.y, &p.x) | ||||
|  | ||||
| 	PP.Multiply(&YplusX, &q.YminusX) // flipped sign | ||||
| 	MM.Multiply(&YminusX, &q.YplusX) // flipped sign | ||||
| 	TT2d.Multiply(&p.t, &q.T2d) | ||||
| 	ZZ2.Multiply(&p.z, &q.Z) | ||||
|  | ||||
| 	ZZ2.Add(&ZZ2, &ZZ2) | ||||
|  | ||||
| 	v.X.Subtract(&PP, &MM) | ||||
| 	v.Y.Add(&PP, &MM) | ||||
| 	v.Z.Subtract(&ZZ2, &TT2d) // flipped sign | ||||
| 	v.T.Add(&ZZ2, &TT2d)      // flipped sign | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| func (v *projP1xP1) AddAffine(p *Point, q *affineCached) *projP1xP1 { | ||||
| 	var YplusX, YminusX, PP, MM, TT2d, Z2 field.Element | ||||
|  | ||||
| 	YplusX.Add(&p.y, &p.x) | ||||
| 	YminusX.Subtract(&p.y, &p.x) | ||||
|  | ||||
| 	PP.Multiply(&YplusX, &q.YplusX) | ||||
| 	MM.Multiply(&YminusX, &q.YminusX) | ||||
| 	TT2d.Multiply(&p.t, &q.T2d) | ||||
|  | ||||
| 	Z2.Add(&p.z, &p.z) | ||||
|  | ||||
| 	v.X.Subtract(&PP, &MM) | ||||
| 	v.Y.Add(&PP, &MM) | ||||
| 	v.Z.Add(&Z2, &TT2d) | ||||
| 	v.T.Subtract(&Z2, &TT2d) | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| func (v *projP1xP1) SubAffine(p *Point, q *affineCached) *projP1xP1 { | ||||
| 	var YplusX, YminusX, PP, MM, TT2d, Z2 field.Element | ||||
|  | ||||
| 	YplusX.Add(&p.y, &p.x) | ||||
| 	YminusX.Subtract(&p.y, &p.x) | ||||
|  | ||||
| 	PP.Multiply(&YplusX, &q.YminusX) // flipped sign | ||||
| 	MM.Multiply(&YminusX, &q.YplusX) // flipped sign | ||||
| 	TT2d.Multiply(&p.t, &q.T2d) | ||||
|  | ||||
| 	Z2.Add(&p.z, &p.z) | ||||
|  | ||||
| 	v.X.Subtract(&PP, &MM) | ||||
| 	v.Y.Add(&PP, &MM) | ||||
| 	v.Z.Subtract(&Z2, &TT2d) // flipped sign | ||||
| 	v.T.Add(&Z2, &TT2d)      // flipped sign | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // Doubling. | ||||
|  | ||||
| func (v *projP1xP1) Double(p *projP2) *projP1xP1 { | ||||
| 	var XX, YY, ZZ2, XplusYsq field.Element | ||||
|  | ||||
| 	XX.Square(&p.X) | ||||
| 	YY.Square(&p.Y) | ||||
| 	ZZ2.Square(&p.Z) | ||||
| 	ZZ2.Add(&ZZ2, &ZZ2) | ||||
| 	XplusYsq.Add(&p.X, &p.Y) | ||||
| 	XplusYsq.Square(&XplusYsq) | ||||
|  | ||||
| 	v.Y.Add(&YY, &XX) | ||||
| 	v.Z.Subtract(&YY, &XX) | ||||
|  | ||||
| 	v.X.Subtract(&XplusYsq, &v.Y) | ||||
| 	v.T.Subtract(&ZZ2, &v.Z) | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // Negation. | ||||
|  | ||||
| // Negate sets v = -p, and returns v. | ||||
| func (v *Point) Negate(p *Point) *Point { | ||||
| 	checkInitialized(p) | ||||
| 	v.x.Negate(&p.x) | ||||
| 	v.y.Set(&p.y) | ||||
| 	v.z.Set(&p.z) | ||||
| 	v.t.Negate(&p.t) | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // Equal returns 1 if v is equivalent to u, and 0 otherwise. | ||||
| func (v *Point) Equal(u *Point) int { | ||||
| 	checkInitialized(v, u) | ||||
|  | ||||
| 	var t1, t2, t3, t4 field.Element | ||||
| 	t1.Multiply(&v.x, &u.z) | ||||
| 	t2.Multiply(&u.x, &v.z) | ||||
| 	t3.Multiply(&v.y, &u.z) | ||||
| 	t4.Multiply(&u.y, &v.z) | ||||
|  | ||||
| 	return t1.Equal(&t2) & t3.Equal(&t4) | ||||
| } | ||||
|  | ||||
| // Constant-time operations | ||||
|  | ||||
| // Select sets v to a if cond == 1 and to b if cond == 0. | ||||
| func (v *projCached) Select(a, b *projCached, cond int) *projCached { | ||||
| 	v.YplusX.Select(&a.YplusX, &b.YplusX, cond) | ||||
| 	v.YminusX.Select(&a.YminusX, &b.YminusX, cond) | ||||
| 	v.Z.Select(&a.Z, &b.Z, cond) | ||||
| 	v.T2d.Select(&a.T2d, &b.T2d, cond) | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // Select sets v to a if cond == 1 and to b if cond == 0. | ||||
| func (v *affineCached) Select(a, b *affineCached, cond int) *affineCached { | ||||
| 	v.YplusX.Select(&a.YplusX, &b.YplusX, cond) | ||||
| 	v.YminusX.Select(&a.YminusX, &b.YminusX, cond) | ||||
| 	v.T2d.Select(&a.T2d, &b.T2d, cond) | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // CondNeg negates v if cond == 1 and leaves it unchanged if cond == 0. | ||||
| func (v *projCached) CondNeg(cond int) *projCached { | ||||
| 	v.YplusX.Swap(&v.YminusX, cond) | ||||
| 	v.T2d.Select(new(field.Element).Negate(&v.T2d), &v.T2d, cond) | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // CondNeg negates v if cond == 1 and leaves it unchanged if cond == 0. | ||||
| func (v *affineCached) CondNeg(cond int) *affineCached { | ||||
| 	v.YplusX.Swap(&v.YminusX, cond) | ||||
| 	v.T2d.Select(new(field.Element).Negate(&v.T2d), &v.T2d, cond) | ||||
| 	return v | ||||
| } | ||||
							
								
								
									
										343
									
								
								vendor/filippo.io/edwards25519/extra.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										343
									
								
								vendor/filippo.io/edwards25519/extra.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,343 @@ | ||||
| // Copyright (c) 2021 The Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package edwards25519 | ||||
|  | ||||
| // This file contains additional functionality that is not included in the | ||||
| // upstream crypto/ed25519/internal/edwards25519 package. | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
|  | ||||
| 	"filippo.io/edwards25519/field" | ||||
| ) | ||||
|  | ||||
| // ExtendedCoordinates returns v in extended coordinates (X:Y:Z:T) where | ||||
| // x = X/Z, y = Y/Z, and xy = T/Z as in https://eprint.iacr.org/2008/522. | ||||
| func (v *Point) ExtendedCoordinates() (X, Y, Z, T *field.Element) { | ||||
| 	// This function is outlined to make the allocations inline in the caller | ||||
| 	// rather than happen on the heap. Don't change the style without making | ||||
| 	// sure it doesn't increase the inliner cost. | ||||
| 	var e [4]field.Element | ||||
| 	X, Y, Z, T = v.extendedCoordinates(&e) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (v *Point) extendedCoordinates(e *[4]field.Element) (X, Y, Z, T *field.Element) { | ||||
| 	checkInitialized(v) | ||||
| 	X = e[0].Set(&v.x) | ||||
| 	Y = e[1].Set(&v.y) | ||||
| 	Z = e[2].Set(&v.z) | ||||
| 	T = e[3].Set(&v.t) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // SetExtendedCoordinates sets v = (X:Y:Z:T) in extended coordinates where | ||||
| // x = X/Z, y = Y/Z, and xy = T/Z as in https://eprint.iacr.org/2008/522. | ||||
| // | ||||
| // If the coordinates are invalid or don't represent a valid point on the curve, | ||||
| // SetExtendedCoordinates returns nil and an error and the receiver is | ||||
| // unchanged. Otherwise, SetExtendedCoordinates returns v. | ||||
| func (v *Point) SetExtendedCoordinates(X, Y, Z, T *field.Element) (*Point, error) { | ||||
| 	if !isOnCurve(X, Y, Z, T) { | ||||
| 		return nil, errors.New("edwards25519: invalid point coordinates") | ||||
| 	} | ||||
| 	v.x.Set(X) | ||||
| 	v.y.Set(Y) | ||||
| 	v.z.Set(Z) | ||||
| 	v.t.Set(T) | ||||
| 	return v, nil | ||||
| } | ||||
|  | ||||
| func isOnCurve(X, Y, Z, T *field.Element) bool { | ||||
| 	var lhs, rhs field.Element | ||||
| 	XX := new(field.Element).Square(X) | ||||
| 	YY := new(field.Element).Square(Y) | ||||
| 	ZZ := new(field.Element).Square(Z) | ||||
| 	TT := new(field.Element).Square(T) | ||||
| 	// -x² + y² = 1 + dx²y² | ||||
| 	// -(X/Z)² + (Y/Z)² = 1 + d(T/Z)² | ||||
| 	// -X² + Y² = Z² + dT² | ||||
| 	lhs.Subtract(YY, XX) | ||||
| 	rhs.Multiply(d, TT).Add(&rhs, ZZ) | ||||
| 	if lhs.Equal(&rhs) != 1 { | ||||
| 		return false | ||||
| 	} | ||||
| 	// xy = T/Z | ||||
| 	// XY/Z² = T/Z | ||||
| 	// XY = TZ | ||||
| 	lhs.Multiply(X, Y) | ||||
| 	rhs.Multiply(T, Z) | ||||
| 	return lhs.Equal(&rhs) == 1 | ||||
| } | ||||
|  | ||||
| // BytesMontgomery converts v to a point on the birationally-equivalent | ||||
| // Curve25519 Montgomery curve, and returns its canonical 32 bytes encoding | ||||
| // according to RFC 7748. | ||||
| // | ||||
| // Note that BytesMontgomery only encodes the u-coordinate, so v and -v encode | ||||
| // to the same value. If v is the identity point, BytesMontgomery returns 32 | ||||
| // zero bytes, analogously to the X25519 function. | ||||
| func (v *Point) BytesMontgomery() []byte { | ||||
| 	// This function is outlined to make the allocations inline in the caller | ||||
| 	// rather than happen on the heap. | ||||
| 	var buf [32]byte | ||||
| 	return v.bytesMontgomery(&buf) | ||||
| } | ||||
|  | ||||
| func (v *Point) bytesMontgomery(buf *[32]byte) []byte { | ||||
| 	checkInitialized(v) | ||||
|  | ||||
| 	// RFC 7748, Section 4.1 provides the bilinear map to calculate the | ||||
| 	// Montgomery u-coordinate | ||||
| 	// | ||||
| 	//              u = (1 + y) / (1 - y) | ||||
| 	// | ||||
| 	// where y = Y / Z. | ||||
|  | ||||
| 	var y, recip, u field.Element | ||||
|  | ||||
| 	y.Multiply(&v.y, y.Invert(&v.z))        // y = Y / Z | ||||
| 	recip.Invert(recip.Subtract(feOne, &y)) // r = 1/(1 - y) | ||||
| 	u.Multiply(u.Add(feOne, &y), &recip)    // u = (1 + y)*r | ||||
|  | ||||
| 	return copyFieldElement(buf, &u) | ||||
| } | ||||
|  | ||||
| // MultByCofactor sets v = 8 * p, and returns v. | ||||
| func (v *Point) MultByCofactor(p *Point) *Point { | ||||
| 	checkInitialized(p) | ||||
| 	result := projP1xP1{} | ||||
| 	pp := (&projP2{}).FromP3(p) | ||||
| 	result.Double(pp) | ||||
| 	pp.FromP1xP1(&result) | ||||
| 	result.Double(pp) | ||||
| 	pp.FromP1xP1(&result) | ||||
| 	result.Double(pp) | ||||
| 	return v.fromP1xP1(&result) | ||||
| } | ||||
|  | ||||
| // Given k > 0, set s = s**(2*i). | ||||
| func (s *Scalar) pow2k(k int) { | ||||
| 	for i := 0; i < k; i++ { | ||||
| 		s.Multiply(s, s) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Invert sets s to the inverse of a nonzero scalar v, and returns s. | ||||
| // | ||||
| // If t is zero, Invert returns zero. | ||||
| func (s *Scalar) Invert(t *Scalar) *Scalar { | ||||
| 	// Uses a hardcoded sliding window of width 4. | ||||
| 	var table [8]Scalar | ||||
| 	var tt Scalar | ||||
| 	tt.Multiply(t, t) | ||||
| 	table[0] = *t | ||||
| 	for i := 0; i < 7; i++ { | ||||
| 		table[i+1].Multiply(&table[i], &tt) | ||||
| 	} | ||||
| 	// Now table = [t**1, t**3, t**7, t**11, t**13, t**15] | ||||
| 	// so t**k = t[k/2] for odd k | ||||
|  | ||||
| 	// To compute the sliding window digits, use the following Sage script: | ||||
|  | ||||
| 	// sage: import itertools | ||||
| 	// sage: def sliding_window(w,k): | ||||
| 	// ....:     digits = [] | ||||
| 	// ....:     while k > 0: | ||||
| 	// ....:         if k % 2 == 1: | ||||
| 	// ....:             kmod = k % (2**w) | ||||
| 	// ....:             digits.append(kmod) | ||||
| 	// ....:             k = k - kmod | ||||
| 	// ....:         else: | ||||
| 	// ....:             digits.append(0) | ||||
| 	// ....:         k = k // 2 | ||||
| 	// ....:     return digits | ||||
|  | ||||
| 	// Now we can compute s roughly as follows: | ||||
|  | ||||
| 	// sage: s = 1 | ||||
| 	// sage: for coeff in reversed(sliding_window(4,l-2)): | ||||
| 	// ....:     s = s*s | ||||
| 	// ....:     if coeff > 0 : | ||||
| 	// ....:         s = s*t**coeff | ||||
|  | ||||
| 	// This works on one bit at a time, with many runs of zeros. | ||||
| 	// The digits can be collapsed into [(count, coeff)] as follows: | ||||
|  | ||||
| 	// sage: [(len(list(group)),d) for d,group in itertools.groupby(sliding_window(4,l-2))] | ||||
|  | ||||
| 	// Entries of the form (k, 0) turn into pow2k(k) | ||||
| 	// Entries of the form (1, coeff) turn into a squaring and then a table lookup. | ||||
| 	// We can fold the squaring into the previous pow2k(k) as pow2k(k+1). | ||||
|  | ||||
| 	*s = table[1/2] | ||||
| 	s.pow2k(127 + 1) | ||||
| 	s.Multiply(s, &table[1/2]) | ||||
| 	s.pow2k(4 + 1) | ||||
| 	s.Multiply(s, &table[9/2]) | ||||
| 	s.pow2k(3 + 1) | ||||
| 	s.Multiply(s, &table[11/2]) | ||||
| 	s.pow2k(3 + 1) | ||||
| 	s.Multiply(s, &table[13/2]) | ||||
| 	s.pow2k(3 + 1) | ||||
| 	s.Multiply(s, &table[15/2]) | ||||
| 	s.pow2k(4 + 1) | ||||
| 	s.Multiply(s, &table[7/2]) | ||||
| 	s.pow2k(4 + 1) | ||||
| 	s.Multiply(s, &table[15/2]) | ||||
| 	s.pow2k(3 + 1) | ||||
| 	s.Multiply(s, &table[5/2]) | ||||
| 	s.pow2k(3 + 1) | ||||
| 	s.Multiply(s, &table[1/2]) | ||||
| 	s.pow2k(4 + 1) | ||||
| 	s.Multiply(s, &table[15/2]) | ||||
| 	s.pow2k(4 + 1) | ||||
| 	s.Multiply(s, &table[15/2]) | ||||
| 	s.pow2k(4 + 1) | ||||
| 	s.Multiply(s, &table[7/2]) | ||||
| 	s.pow2k(3 + 1) | ||||
| 	s.Multiply(s, &table[3/2]) | ||||
| 	s.pow2k(4 + 1) | ||||
| 	s.Multiply(s, &table[11/2]) | ||||
| 	s.pow2k(5 + 1) | ||||
| 	s.Multiply(s, &table[11/2]) | ||||
| 	s.pow2k(9 + 1) | ||||
| 	s.Multiply(s, &table[9/2]) | ||||
| 	s.pow2k(3 + 1) | ||||
| 	s.Multiply(s, &table[3/2]) | ||||
| 	s.pow2k(4 + 1) | ||||
| 	s.Multiply(s, &table[3/2]) | ||||
| 	s.pow2k(4 + 1) | ||||
| 	s.Multiply(s, &table[3/2]) | ||||
| 	s.pow2k(4 + 1) | ||||
| 	s.Multiply(s, &table[9/2]) | ||||
| 	s.pow2k(3 + 1) | ||||
| 	s.Multiply(s, &table[7/2]) | ||||
| 	s.pow2k(3 + 1) | ||||
| 	s.Multiply(s, &table[3/2]) | ||||
| 	s.pow2k(3 + 1) | ||||
| 	s.Multiply(s, &table[13/2]) | ||||
| 	s.pow2k(3 + 1) | ||||
| 	s.Multiply(s, &table[7/2]) | ||||
| 	s.pow2k(4 + 1) | ||||
| 	s.Multiply(s, &table[9/2]) | ||||
| 	s.pow2k(3 + 1) | ||||
| 	s.Multiply(s, &table[15/2]) | ||||
| 	s.pow2k(4 + 1) | ||||
| 	s.Multiply(s, &table[11/2]) | ||||
|  | ||||
| 	return s | ||||
| } | ||||
|  | ||||
| // MultiScalarMult sets v = sum(scalars[i] * points[i]), and returns v. | ||||
| // | ||||
| // Execution time depends only on the lengths of the two slices, which must match. | ||||
| func (v *Point) MultiScalarMult(scalars []*Scalar, points []*Point) *Point { | ||||
| 	if len(scalars) != len(points) { | ||||
| 		panic("edwards25519: called MultiScalarMult with different size inputs") | ||||
| 	} | ||||
| 	checkInitialized(points...) | ||||
|  | ||||
| 	// Proceed as in the single-base case, but share doublings | ||||
| 	// between each point in the multiscalar equation. | ||||
|  | ||||
| 	// Build lookup tables for each point | ||||
| 	tables := make([]projLookupTable, len(points)) | ||||
| 	for i := range tables { | ||||
| 		tables[i].FromP3(points[i]) | ||||
| 	} | ||||
| 	// Compute signed radix-16 digits for each scalar | ||||
| 	digits := make([][64]int8, len(scalars)) | ||||
| 	for i := range digits { | ||||
| 		digits[i] = scalars[i].signedRadix16() | ||||
| 	} | ||||
|  | ||||
| 	// Unwrap first loop iteration to save computing 16*identity | ||||
| 	multiple := &projCached{} | ||||
| 	tmp1 := &projP1xP1{} | ||||
| 	tmp2 := &projP2{} | ||||
| 	// Lookup-and-add the appropriate multiple of each input point | ||||
| 	for j := range tables { | ||||
| 		tables[j].SelectInto(multiple, digits[j][63]) | ||||
| 		tmp1.Add(v, multiple) // tmp1 = v + x_(j,63)*Q in P1xP1 coords | ||||
| 		v.fromP1xP1(tmp1)     // update v | ||||
| 	} | ||||
| 	tmp2.FromP3(v) // set up tmp2 = v in P2 coords for next iteration | ||||
| 	for i := 62; i >= 0; i-- { | ||||
| 		tmp1.Double(tmp2)    // tmp1 =  2*(prev) in P1xP1 coords | ||||
| 		tmp2.FromP1xP1(tmp1) // tmp2 =  2*(prev) in P2 coords | ||||
| 		tmp1.Double(tmp2)    // tmp1 =  4*(prev) in P1xP1 coords | ||||
| 		tmp2.FromP1xP1(tmp1) // tmp2 =  4*(prev) in P2 coords | ||||
| 		tmp1.Double(tmp2)    // tmp1 =  8*(prev) in P1xP1 coords | ||||
| 		tmp2.FromP1xP1(tmp1) // tmp2 =  8*(prev) in P2 coords | ||||
| 		tmp1.Double(tmp2)    // tmp1 = 16*(prev) in P1xP1 coords | ||||
| 		v.fromP1xP1(tmp1)    //    v = 16*(prev) in P3 coords | ||||
| 		// Lookup-and-add the appropriate multiple of each input point | ||||
| 		for j := range tables { | ||||
| 			tables[j].SelectInto(multiple, digits[j][i]) | ||||
| 			tmp1.Add(v, multiple) // tmp1 = v + x_(j,i)*Q in P1xP1 coords | ||||
| 			v.fromP1xP1(tmp1)     // update v | ||||
| 		} | ||||
| 		tmp2.FromP3(v) // set up tmp2 = v in P2 coords for next iteration | ||||
| 	} | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // VarTimeMultiScalarMult sets v = sum(scalars[i] * points[i]), and returns v. | ||||
| // | ||||
| // Execution time depends on the inputs. | ||||
| func (v *Point) VarTimeMultiScalarMult(scalars []*Scalar, points []*Point) *Point { | ||||
| 	if len(scalars) != len(points) { | ||||
| 		panic("edwards25519: called VarTimeMultiScalarMult with different size inputs") | ||||
| 	} | ||||
| 	checkInitialized(points...) | ||||
|  | ||||
| 	// Generalize double-base NAF computation to arbitrary sizes. | ||||
| 	// Here all the points are dynamic, so we only use the smaller | ||||
| 	// tables. | ||||
|  | ||||
| 	// Build lookup tables for each point | ||||
| 	tables := make([]nafLookupTable5, len(points)) | ||||
| 	for i := range tables { | ||||
| 		tables[i].FromP3(points[i]) | ||||
| 	} | ||||
| 	// Compute a NAF for each scalar | ||||
| 	nafs := make([][256]int8, len(scalars)) | ||||
| 	for i := range nafs { | ||||
| 		nafs[i] = scalars[i].nonAdjacentForm(5) | ||||
| 	} | ||||
|  | ||||
| 	multiple := &projCached{} | ||||
| 	tmp1 := &projP1xP1{} | ||||
| 	tmp2 := &projP2{} | ||||
| 	tmp2.Zero() | ||||
|  | ||||
| 	// Move from high to low bits, doubling the accumulator | ||||
| 	// at each iteration and checking whether there is a nonzero | ||||
| 	// coefficient to look up a multiple of. | ||||
| 	// | ||||
| 	// Skip trying to find the first nonzero coefficent, because | ||||
| 	// searching might be more work than a few extra doublings. | ||||
| 	for i := 255; i >= 0; i-- { | ||||
| 		tmp1.Double(tmp2) | ||||
|  | ||||
| 		for j := range nafs { | ||||
| 			if nafs[j][i] > 0 { | ||||
| 				v.fromP1xP1(tmp1) | ||||
| 				tables[j].SelectInto(multiple, nafs[j][i]) | ||||
| 				tmp1.Add(v, multiple) | ||||
| 			} else if nafs[j][i] < 0 { | ||||
| 				v.fromP1xP1(tmp1) | ||||
| 				tables[j].SelectInto(multiple, -nafs[j][i]) | ||||
| 				tmp1.Sub(v, multiple) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		tmp2.FromP1xP1(tmp1) | ||||
| 	} | ||||
|  | ||||
| 	v.fromP2(tmp2) | ||||
| 	return v | ||||
| } | ||||
							
								
								
									
										419
									
								
								vendor/filippo.io/edwards25519/field/fe.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										419
									
								
								vendor/filippo.io/edwards25519/field/fe.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,419 @@ | ||||
| // Copyright (c) 2017 The Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| // Package field implements fast arithmetic modulo 2^255-19. | ||||
| package field | ||||
|  | ||||
| import ( | ||||
| 	"crypto/subtle" | ||||
| 	"encoding/binary" | ||||
| 	"errors" | ||||
| 	"math/bits" | ||||
| ) | ||||
|  | ||||
| // Element represents an element of the field GF(2^255-19). Note that this | ||||
| // is not a cryptographically secure group, and should only be used to interact | ||||
| // with edwards25519.Point coordinates. | ||||
| // | ||||
| // This type works similarly to math/big.Int, and all arguments and receivers | ||||
| // are allowed to alias. | ||||
| // | ||||
| // The zero value is a valid zero element. | ||||
| type Element struct { | ||||
| 	// An element t represents the integer | ||||
| 	//     t.l0 + t.l1*2^51 + t.l2*2^102 + t.l3*2^153 + t.l4*2^204 | ||||
| 	// | ||||
| 	// Between operations, all limbs are expected to be lower than 2^52. | ||||
| 	l0 uint64 | ||||
| 	l1 uint64 | ||||
| 	l2 uint64 | ||||
| 	l3 uint64 | ||||
| 	l4 uint64 | ||||
| } | ||||
|  | ||||
| const maskLow51Bits uint64 = (1 << 51) - 1 | ||||
|  | ||||
| var feZero = &Element{0, 0, 0, 0, 0} | ||||
|  | ||||
| // Zero sets v = 0, and returns v. | ||||
| func (v *Element) Zero() *Element { | ||||
| 	*v = *feZero | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| var feOne = &Element{1, 0, 0, 0, 0} | ||||
|  | ||||
| // One sets v = 1, and returns v. | ||||
| func (v *Element) One() *Element { | ||||
| 	*v = *feOne | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // reduce reduces v modulo 2^255 - 19 and returns it. | ||||
| func (v *Element) reduce() *Element { | ||||
| 	v.carryPropagate() | ||||
|  | ||||
| 	// After the light reduction we now have a field element representation | ||||
| 	// v < 2^255 + 2^13 * 19, but need v < 2^255 - 19. | ||||
|  | ||||
| 	// If v >= 2^255 - 19, then v + 19 >= 2^255, which would overflow 2^255 - 1, | ||||
| 	// generating a carry. That is, c will be 0 if v < 2^255 - 19, and 1 otherwise. | ||||
| 	c := (v.l0 + 19) >> 51 | ||||
| 	c = (v.l1 + c) >> 51 | ||||
| 	c = (v.l2 + c) >> 51 | ||||
| 	c = (v.l3 + c) >> 51 | ||||
| 	c = (v.l4 + c) >> 51 | ||||
|  | ||||
| 	// If v < 2^255 - 19 and c = 0, this will be a no-op. Otherwise, it's | ||||
| 	// effectively applying the reduction identity to the carry. | ||||
| 	v.l0 += 19 * c | ||||
|  | ||||
| 	v.l1 += v.l0 >> 51 | ||||
| 	v.l0 = v.l0 & maskLow51Bits | ||||
| 	v.l2 += v.l1 >> 51 | ||||
| 	v.l1 = v.l1 & maskLow51Bits | ||||
| 	v.l3 += v.l2 >> 51 | ||||
| 	v.l2 = v.l2 & maskLow51Bits | ||||
| 	v.l4 += v.l3 >> 51 | ||||
| 	v.l3 = v.l3 & maskLow51Bits | ||||
| 	// no additional carry | ||||
| 	v.l4 = v.l4 & maskLow51Bits | ||||
|  | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // Add sets v = a + b, and returns v. | ||||
| func (v *Element) Add(a, b *Element) *Element { | ||||
| 	v.l0 = a.l0 + b.l0 | ||||
| 	v.l1 = a.l1 + b.l1 | ||||
| 	v.l2 = a.l2 + b.l2 | ||||
| 	v.l3 = a.l3 + b.l3 | ||||
| 	v.l4 = a.l4 + b.l4 | ||||
| 	// Using the generic implementation here is actually faster than the | ||||
| 	// assembly. Probably because the body of this function is so simple that | ||||
| 	// the compiler can figure out better optimizations by inlining the carry | ||||
| 	// propagation. | ||||
| 	return v.carryPropagateGeneric() | ||||
| } | ||||
|  | ||||
| // Subtract sets v = a - b, and returns v. | ||||
| func (v *Element) Subtract(a, b *Element) *Element { | ||||
| 	// We first add 2 * p, to guarantee the subtraction won't underflow, and | ||||
| 	// then subtract b (which can be up to 2^255 + 2^13 * 19). | ||||
| 	v.l0 = (a.l0 + 0xFFFFFFFFFFFDA) - b.l0 | ||||
| 	v.l1 = (a.l1 + 0xFFFFFFFFFFFFE) - b.l1 | ||||
| 	v.l2 = (a.l2 + 0xFFFFFFFFFFFFE) - b.l2 | ||||
| 	v.l3 = (a.l3 + 0xFFFFFFFFFFFFE) - b.l3 | ||||
| 	v.l4 = (a.l4 + 0xFFFFFFFFFFFFE) - b.l4 | ||||
| 	return v.carryPropagate() | ||||
| } | ||||
|  | ||||
| // Negate sets v = -a, and returns v. | ||||
| func (v *Element) Negate(a *Element) *Element { | ||||
| 	return v.Subtract(feZero, a) | ||||
| } | ||||
|  | ||||
| // Invert sets v = 1/z mod p, and returns v. | ||||
| // | ||||
| // If z == 0, Invert returns v = 0. | ||||
| func (v *Element) Invert(z *Element) *Element { | ||||
| 	// Inversion is implemented as exponentiation with exponent p − 2. It uses the | ||||
| 	// same sequence of 255 squarings and 11 multiplications as [Curve25519]. | ||||
| 	var z2, z9, z11, z2_5_0, z2_10_0, z2_20_0, z2_50_0, z2_100_0, t Element | ||||
|  | ||||
| 	z2.Square(z)             // 2 | ||||
| 	t.Square(&z2)            // 4 | ||||
| 	t.Square(&t)             // 8 | ||||
| 	z9.Multiply(&t, z)       // 9 | ||||
| 	z11.Multiply(&z9, &z2)   // 11 | ||||
| 	t.Square(&z11)           // 22 | ||||
| 	z2_5_0.Multiply(&t, &z9) // 31 = 2^5 - 2^0 | ||||
|  | ||||
| 	t.Square(&z2_5_0) // 2^6 - 2^1 | ||||
| 	for i := 0; i < 4; i++ { | ||||
| 		t.Square(&t) // 2^10 - 2^5 | ||||
| 	} | ||||
| 	z2_10_0.Multiply(&t, &z2_5_0) // 2^10 - 2^0 | ||||
|  | ||||
| 	t.Square(&z2_10_0) // 2^11 - 2^1 | ||||
| 	for i := 0; i < 9; i++ { | ||||
| 		t.Square(&t) // 2^20 - 2^10 | ||||
| 	} | ||||
| 	z2_20_0.Multiply(&t, &z2_10_0) // 2^20 - 2^0 | ||||
|  | ||||
| 	t.Square(&z2_20_0) // 2^21 - 2^1 | ||||
| 	for i := 0; i < 19; i++ { | ||||
| 		t.Square(&t) // 2^40 - 2^20 | ||||
| 	} | ||||
| 	t.Multiply(&t, &z2_20_0) // 2^40 - 2^0 | ||||
|  | ||||
| 	t.Square(&t) // 2^41 - 2^1 | ||||
| 	for i := 0; i < 9; i++ { | ||||
| 		t.Square(&t) // 2^50 - 2^10 | ||||
| 	} | ||||
| 	z2_50_0.Multiply(&t, &z2_10_0) // 2^50 - 2^0 | ||||
|  | ||||
| 	t.Square(&z2_50_0) // 2^51 - 2^1 | ||||
| 	for i := 0; i < 49; i++ { | ||||
| 		t.Square(&t) // 2^100 - 2^50 | ||||
| 	} | ||||
| 	z2_100_0.Multiply(&t, &z2_50_0) // 2^100 - 2^0 | ||||
|  | ||||
| 	t.Square(&z2_100_0) // 2^101 - 2^1 | ||||
| 	for i := 0; i < 99; i++ { | ||||
| 		t.Square(&t) // 2^200 - 2^100 | ||||
| 	} | ||||
| 	t.Multiply(&t, &z2_100_0) // 2^200 - 2^0 | ||||
|  | ||||
| 	t.Square(&t) // 2^201 - 2^1 | ||||
| 	for i := 0; i < 49; i++ { | ||||
| 		t.Square(&t) // 2^250 - 2^50 | ||||
| 	} | ||||
| 	t.Multiply(&t, &z2_50_0) // 2^250 - 2^0 | ||||
|  | ||||
| 	t.Square(&t) // 2^251 - 2^1 | ||||
| 	t.Square(&t) // 2^252 - 2^2 | ||||
| 	t.Square(&t) // 2^253 - 2^3 | ||||
| 	t.Square(&t) // 2^254 - 2^4 | ||||
| 	t.Square(&t) // 2^255 - 2^5 | ||||
|  | ||||
| 	return v.Multiply(&t, &z11) // 2^255 - 21 | ||||
| } | ||||
|  | ||||
| // Set sets v = a, and returns v. | ||||
| func (v *Element) Set(a *Element) *Element { | ||||
| 	*v = *a | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // SetBytes sets v to x, where x is a 32-byte little-endian encoding. If x is | ||||
| // not of the right length, SetUniformBytes returns nil and an error, and the | ||||
| // receiver is unchanged. | ||||
| // | ||||
| // Consistent with RFC 7748, the most significant bit (the high bit of the | ||||
| // last byte) is ignored, and non-canonical values (2^255-19 through 2^255-1) | ||||
| // are accepted. Note that this is laxer than specified by RFC 8032. | ||||
| func (v *Element) SetBytes(x []byte) (*Element, error) { | ||||
| 	if len(x) != 32 { | ||||
| 		return nil, errors.New("edwards25519: invalid field element input size") | ||||
| 	} | ||||
|  | ||||
| 	// Bits 0:51 (bytes 0:8, bits 0:64, shift 0, mask 51). | ||||
| 	v.l0 = binary.LittleEndian.Uint64(x[0:8]) | ||||
| 	v.l0 &= maskLow51Bits | ||||
| 	// Bits 51:102 (bytes 6:14, bits 48:112, shift 3, mask 51). | ||||
| 	v.l1 = binary.LittleEndian.Uint64(x[6:14]) >> 3 | ||||
| 	v.l1 &= maskLow51Bits | ||||
| 	// Bits 102:153 (bytes 12:20, bits 96:160, shift 6, mask 51). | ||||
| 	v.l2 = binary.LittleEndian.Uint64(x[12:20]) >> 6 | ||||
| 	v.l2 &= maskLow51Bits | ||||
| 	// Bits 153:204 (bytes 19:27, bits 152:216, shift 1, mask 51). | ||||
| 	v.l3 = binary.LittleEndian.Uint64(x[19:27]) >> 1 | ||||
| 	v.l3 &= maskLow51Bits | ||||
| 	// Bits 204:251 (bytes 24:32, bits 192:256, shift 12, mask 51). | ||||
| 	// Note: not bytes 25:33, shift 4, to avoid overread. | ||||
| 	v.l4 = binary.LittleEndian.Uint64(x[24:32]) >> 12 | ||||
| 	v.l4 &= maskLow51Bits | ||||
|  | ||||
| 	return v, nil | ||||
| } | ||||
|  | ||||
| // Bytes returns the canonical 32-byte little-endian encoding of v. | ||||
| func (v *Element) Bytes() []byte { | ||||
| 	// This function is outlined to make the allocations inline in the caller | ||||
| 	// rather than happen on the heap. | ||||
| 	var out [32]byte | ||||
| 	return v.bytes(&out) | ||||
| } | ||||
|  | ||||
| func (v *Element) bytes(out *[32]byte) []byte { | ||||
| 	t := *v | ||||
| 	t.reduce() | ||||
|  | ||||
| 	var buf [8]byte | ||||
| 	for i, l := range [5]uint64{t.l0, t.l1, t.l2, t.l3, t.l4} { | ||||
| 		bitsOffset := i * 51 | ||||
| 		binary.LittleEndian.PutUint64(buf[:], l<<uint(bitsOffset%8)) | ||||
| 		for i, bb := range buf { | ||||
| 			off := bitsOffset/8 + i | ||||
| 			if off >= len(out) { | ||||
| 				break | ||||
| 			} | ||||
| 			out[off] |= bb | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return out[:] | ||||
| } | ||||
|  | ||||
| // Equal returns 1 if v and u are equal, and 0 otherwise. | ||||
| func (v *Element) Equal(u *Element) int { | ||||
| 	sa, sv := u.Bytes(), v.Bytes() | ||||
| 	return subtle.ConstantTimeCompare(sa, sv) | ||||
| } | ||||
|  | ||||
| // mask64Bits returns 0xffffffff if cond is 1, and 0 otherwise. | ||||
| func mask64Bits(cond int) uint64 { return ^(uint64(cond) - 1) } | ||||
|  | ||||
| // Select sets v to a if cond == 1, and to b if cond == 0. | ||||
| func (v *Element) Select(a, b *Element, cond int) *Element { | ||||
| 	m := mask64Bits(cond) | ||||
| 	v.l0 = (m & a.l0) | (^m & b.l0) | ||||
| 	v.l1 = (m & a.l1) | (^m & b.l1) | ||||
| 	v.l2 = (m & a.l2) | (^m & b.l2) | ||||
| 	v.l3 = (m & a.l3) | (^m & b.l3) | ||||
| 	v.l4 = (m & a.l4) | (^m & b.l4) | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // Swap swaps v and u if cond == 1 or leaves them unchanged if cond == 0, and returns v. | ||||
| func (v *Element) Swap(u *Element, cond int) { | ||||
| 	m := mask64Bits(cond) | ||||
| 	t := m & (v.l0 ^ u.l0) | ||||
| 	v.l0 ^= t | ||||
| 	u.l0 ^= t | ||||
| 	t = m & (v.l1 ^ u.l1) | ||||
| 	v.l1 ^= t | ||||
| 	u.l1 ^= t | ||||
| 	t = m & (v.l2 ^ u.l2) | ||||
| 	v.l2 ^= t | ||||
| 	u.l2 ^= t | ||||
| 	t = m & (v.l3 ^ u.l3) | ||||
| 	v.l3 ^= t | ||||
| 	u.l3 ^= t | ||||
| 	t = m & (v.l4 ^ u.l4) | ||||
| 	v.l4 ^= t | ||||
| 	u.l4 ^= t | ||||
| } | ||||
|  | ||||
| // IsNegative returns 1 if v is negative, and 0 otherwise. | ||||
| func (v *Element) IsNegative() int { | ||||
| 	return int(v.Bytes()[0] & 1) | ||||
| } | ||||
|  | ||||
| // Absolute sets v to |u|, and returns v. | ||||
| func (v *Element) Absolute(u *Element) *Element { | ||||
| 	return v.Select(new(Element).Negate(u), u, u.IsNegative()) | ||||
| } | ||||
|  | ||||
| // Multiply sets v = x * y, and returns v. | ||||
| func (v *Element) Multiply(x, y *Element) *Element { | ||||
| 	feMul(v, x, y) | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // Square sets v = x * x, and returns v. | ||||
| func (v *Element) Square(x *Element) *Element { | ||||
| 	feSquare(v, x) | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // Mult32 sets v = x * y, and returns v. | ||||
| func (v *Element) Mult32(x *Element, y uint32) *Element { | ||||
| 	x0lo, x0hi := mul51(x.l0, y) | ||||
| 	x1lo, x1hi := mul51(x.l1, y) | ||||
| 	x2lo, x2hi := mul51(x.l2, y) | ||||
| 	x3lo, x3hi := mul51(x.l3, y) | ||||
| 	x4lo, x4hi := mul51(x.l4, y) | ||||
| 	v.l0 = x0lo + 19*x4hi // carried over per the reduction identity | ||||
| 	v.l1 = x1lo + x0hi | ||||
| 	v.l2 = x2lo + x1hi | ||||
| 	v.l3 = x3lo + x2hi | ||||
| 	v.l4 = x4lo + x3hi | ||||
| 	// The hi portions are going to be only 32 bits, plus any previous excess, | ||||
| 	// so we can skip the carry propagation. | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // mul51 returns lo + hi * 2⁵¹ = a * b. | ||||
| func mul51(a uint64, b uint32) (lo uint64, hi uint64) { | ||||
| 	mh, ml := bits.Mul64(a, uint64(b)) | ||||
| 	lo = ml & maskLow51Bits | ||||
| 	hi = (mh << 13) | (ml >> 51) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Pow22523 set v = x^((p-5)/8), and returns v. (p-5)/8 is 2^252-3. | ||||
| func (v *Element) Pow22523(x *Element) *Element { | ||||
| 	var t0, t1, t2 Element | ||||
|  | ||||
| 	t0.Square(x)             // x^2 | ||||
| 	t1.Square(&t0)           // x^4 | ||||
| 	t1.Square(&t1)           // x^8 | ||||
| 	t1.Multiply(x, &t1)      // x^9 | ||||
| 	t0.Multiply(&t0, &t1)    // x^11 | ||||
| 	t0.Square(&t0)           // x^22 | ||||
| 	t0.Multiply(&t1, &t0)    // x^31 | ||||
| 	t1.Square(&t0)           // x^62 | ||||
| 	for i := 1; i < 5; i++ { // x^992 | ||||
| 		t1.Square(&t1) | ||||
| 	} | ||||
| 	t0.Multiply(&t1, &t0)     // x^1023 -> 1023 = 2^10 - 1 | ||||
| 	t1.Square(&t0)            // 2^11 - 2 | ||||
| 	for i := 1; i < 10; i++ { // 2^20 - 2^10 | ||||
| 		t1.Square(&t1) | ||||
| 	} | ||||
| 	t1.Multiply(&t1, &t0)     // 2^20 - 1 | ||||
| 	t2.Square(&t1)            // 2^21 - 2 | ||||
| 	for i := 1; i < 20; i++ { // 2^40 - 2^20 | ||||
| 		t2.Square(&t2) | ||||
| 	} | ||||
| 	t1.Multiply(&t2, &t1)     // 2^40 - 1 | ||||
| 	t1.Square(&t1)            // 2^41 - 2 | ||||
| 	for i := 1; i < 10; i++ { // 2^50 - 2^10 | ||||
| 		t1.Square(&t1) | ||||
| 	} | ||||
| 	t0.Multiply(&t1, &t0)     // 2^50 - 1 | ||||
| 	t1.Square(&t0)            // 2^51 - 2 | ||||
| 	for i := 1; i < 50; i++ { // 2^100 - 2^50 | ||||
| 		t1.Square(&t1) | ||||
| 	} | ||||
| 	t1.Multiply(&t1, &t0)      // 2^100 - 1 | ||||
| 	t2.Square(&t1)             // 2^101 - 2 | ||||
| 	for i := 1; i < 100; i++ { // 2^200 - 2^100 | ||||
| 		t2.Square(&t2) | ||||
| 	} | ||||
| 	t1.Multiply(&t2, &t1)     // 2^200 - 1 | ||||
| 	t1.Square(&t1)            // 2^201 - 2 | ||||
| 	for i := 1; i < 50; i++ { // 2^250 - 2^50 | ||||
| 		t1.Square(&t1) | ||||
| 	} | ||||
| 	t0.Multiply(&t1, &t0)     // 2^250 - 1 | ||||
| 	t0.Square(&t0)            // 2^251 - 2 | ||||
| 	t0.Square(&t0)            // 2^252 - 4 | ||||
| 	return v.Multiply(&t0, x) // 2^252 - 3 -> x^(2^252-3) | ||||
| } | ||||
|  | ||||
| // sqrtM1 is 2^((p-1)/4), which squared is equal to -1 by Euler's Criterion. | ||||
| var sqrtM1 = &Element{1718705420411056, 234908883556509, | ||||
| 	2233514472574048, 2117202627021982, 765476049583133} | ||||
|  | ||||
| // SqrtRatio sets r to the non-negative square root of the ratio of u and v. | ||||
| // | ||||
| // If u/v is square, SqrtRatio returns r and 1. If u/v is not square, SqrtRatio | ||||
| // sets r according to Section 4.3 of draft-irtf-cfrg-ristretto255-decaf448-00, | ||||
| // and returns r and 0. | ||||
| func (r *Element) SqrtRatio(u, v *Element) (rr *Element, wasSquare int) { | ||||
| 	var a, b Element | ||||
|  | ||||
| 	// r = (u * v3) * (u * v7)^((p-5)/8) | ||||
| 	v2 := a.Square(v) | ||||
| 	uv3 := b.Multiply(u, b.Multiply(v2, v)) | ||||
| 	uv7 := a.Multiply(uv3, a.Square(v2)) | ||||
| 	r.Multiply(uv3, r.Pow22523(uv7)) | ||||
|  | ||||
| 	check := a.Multiply(v, a.Square(r)) // check = v * r^2 | ||||
|  | ||||
| 	uNeg := b.Negate(u) | ||||
| 	correctSignSqrt := check.Equal(u) | ||||
| 	flippedSignSqrt := check.Equal(uNeg) | ||||
| 	flippedSignSqrtI := check.Equal(uNeg.Multiply(uNeg, sqrtM1)) | ||||
|  | ||||
| 	rPrime := b.Multiply(r, sqrtM1) // r_prime = SQRT_M1 * r | ||||
| 	// r = CT_SELECT(r_prime IF flipped_sign_sqrt | flipped_sign_sqrt_i ELSE r) | ||||
| 	r.Select(rPrime, r, flippedSignSqrt|flippedSignSqrtI) | ||||
|  | ||||
| 	r.Absolute(r) // Choose the nonnegative square root. | ||||
| 	return r, correctSignSqrt | flippedSignSqrt | ||||
| } | ||||
							
								
								
									
										13
									
								
								vendor/filippo.io/edwards25519/field/fe_amd64.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								vendor/filippo.io/edwards25519/field/fe_amd64.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| // Code generated by command: go run fe_amd64_asm.go -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field. DO NOT EDIT. | ||||
|  | ||||
| // +build amd64,gc,!purego | ||||
|  | ||||
| package field | ||||
|  | ||||
| // feMul sets out = a * b. It works like feMulGeneric. | ||||
| //go:noescape | ||||
| func feMul(out *Element, a *Element, b *Element) | ||||
|  | ||||
| // feSquare sets out = a * a. It works like feSquareGeneric. | ||||
| //go:noescape | ||||
| func feSquare(out *Element, a *Element) | ||||
							
								
								
									
										378
									
								
								vendor/filippo.io/edwards25519/field/fe_amd64.s
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										378
									
								
								vendor/filippo.io/edwards25519/field/fe_amd64.s
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,378 @@ | ||||
| // Code generated by command: go run fe_amd64_asm.go -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field. DO NOT EDIT. | ||||
|  | ||||
| // +build amd64,gc,!purego | ||||
|  | ||||
| #include "textflag.h" | ||||
|  | ||||
| // func feMul(out *Element, a *Element, b *Element) | ||||
| TEXT ·feMul(SB), NOSPLIT, $0-24 | ||||
| 	MOVQ a+8(FP), CX | ||||
| 	MOVQ b+16(FP), BX | ||||
|  | ||||
| 	// r0 = a0×b0 | ||||
| 	MOVQ (CX), AX | ||||
| 	MULQ (BX) | ||||
| 	MOVQ AX, DI | ||||
| 	MOVQ DX, SI | ||||
|  | ||||
| 	// r0 += 19×a1×b4 | ||||
| 	MOVQ   8(CX), AX | ||||
| 	IMUL3Q $0x13, AX, AX | ||||
| 	MULQ   32(BX) | ||||
| 	ADDQ   AX, DI | ||||
| 	ADCQ   DX, SI | ||||
|  | ||||
| 	// r0 += 19×a2×b3 | ||||
| 	MOVQ   16(CX), AX | ||||
| 	IMUL3Q $0x13, AX, AX | ||||
| 	MULQ   24(BX) | ||||
| 	ADDQ   AX, DI | ||||
| 	ADCQ   DX, SI | ||||
|  | ||||
| 	// r0 += 19×a3×b2 | ||||
| 	MOVQ   24(CX), AX | ||||
| 	IMUL3Q $0x13, AX, AX | ||||
| 	MULQ   16(BX) | ||||
| 	ADDQ   AX, DI | ||||
| 	ADCQ   DX, SI | ||||
|  | ||||
| 	// r0 += 19×a4×b1 | ||||
| 	MOVQ   32(CX), AX | ||||
| 	IMUL3Q $0x13, AX, AX | ||||
| 	MULQ   8(BX) | ||||
| 	ADDQ   AX, DI | ||||
| 	ADCQ   DX, SI | ||||
|  | ||||
| 	// r1 = a0×b1 | ||||
| 	MOVQ (CX), AX | ||||
| 	MULQ 8(BX) | ||||
| 	MOVQ AX, R9 | ||||
| 	MOVQ DX, R8 | ||||
|  | ||||
| 	// r1 += a1×b0 | ||||
| 	MOVQ 8(CX), AX | ||||
| 	MULQ (BX) | ||||
| 	ADDQ AX, R9 | ||||
| 	ADCQ DX, R8 | ||||
|  | ||||
| 	// r1 += 19×a2×b4 | ||||
| 	MOVQ   16(CX), AX | ||||
| 	IMUL3Q $0x13, AX, AX | ||||
| 	MULQ   32(BX) | ||||
| 	ADDQ   AX, R9 | ||||
| 	ADCQ   DX, R8 | ||||
|  | ||||
| 	// r1 += 19×a3×b3 | ||||
| 	MOVQ   24(CX), AX | ||||
| 	IMUL3Q $0x13, AX, AX | ||||
| 	MULQ   24(BX) | ||||
| 	ADDQ   AX, R9 | ||||
| 	ADCQ   DX, R8 | ||||
|  | ||||
| 	// r1 += 19×a4×b2 | ||||
| 	MOVQ   32(CX), AX | ||||
| 	IMUL3Q $0x13, AX, AX | ||||
| 	MULQ   16(BX) | ||||
| 	ADDQ   AX, R9 | ||||
| 	ADCQ   DX, R8 | ||||
|  | ||||
| 	// r2 = a0×b2 | ||||
| 	MOVQ (CX), AX | ||||
| 	MULQ 16(BX) | ||||
| 	MOVQ AX, R11 | ||||
| 	MOVQ DX, R10 | ||||
|  | ||||
| 	// r2 += a1×b1 | ||||
| 	MOVQ 8(CX), AX | ||||
| 	MULQ 8(BX) | ||||
| 	ADDQ AX, R11 | ||||
| 	ADCQ DX, R10 | ||||
|  | ||||
| 	// r2 += a2×b0 | ||||
| 	MOVQ 16(CX), AX | ||||
| 	MULQ (BX) | ||||
| 	ADDQ AX, R11 | ||||
| 	ADCQ DX, R10 | ||||
|  | ||||
| 	// r2 += 19×a3×b4 | ||||
| 	MOVQ   24(CX), AX | ||||
| 	IMUL3Q $0x13, AX, AX | ||||
| 	MULQ   32(BX) | ||||
| 	ADDQ   AX, R11 | ||||
| 	ADCQ   DX, R10 | ||||
|  | ||||
| 	// r2 += 19×a4×b3 | ||||
| 	MOVQ   32(CX), AX | ||||
| 	IMUL3Q $0x13, AX, AX | ||||
| 	MULQ   24(BX) | ||||
| 	ADDQ   AX, R11 | ||||
| 	ADCQ   DX, R10 | ||||
|  | ||||
| 	// r3 = a0×b3 | ||||
| 	MOVQ (CX), AX | ||||
| 	MULQ 24(BX) | ||||
| 	MOVQ AX, R13 | ||||
| 	MOVQ DX, R12 | ||||
|  | ||||
| 	// r3 += a1×b2 | ||||
| 	MOVQ 8(CX), AX | ||||
| 	MULQ 16(BX) | ||||
| 	ADDQ AX, R13 | ||||
| 	ADCQ DX, R12 | ||||
|  | ||||
| 	// r3 += a2×b1 | ||||
| 	MOVQ 16(CX), AX | ||||
| 	MULQ 8(BX) | ||||
| 	ADDQ AX, R13 | ||||
| 	ADCQ DX, R12 | ||||
|  | ||||
| 	// r3 += a3×b0 | ||||
| 	MOVQ 24(CX), AX | ||||
| 	MULQ (BX) | ||||
| 	ADDQ AX, R13 | ||||
| 	ADCQ DX, R12 | ||||
|  | ||||
| 	// r3 += 19×a4×b4 | ||||
| 	MOVQ   32(CX), AX | ||||
| 	IMUL3Q $0x13, AX, AX | ||||
| 	MULQ   32(BX) | ||||
| 	ADDQ   AX, R13 | ||||
| 	ADCQ   DX, R12 | ||||
|  | ||||
| 	// r4 = a0×b4 | ||||
| 	MOVQ (CX), AX | ||||
| 	MULQ 32(BX) | ||||
| 	MOVQ AX, R15 | ||||
| 	MOVQ DX, R14 | ||||
|  | ||||
| 	// r4 += a1×b3 | ||||
| 	MOVQ 8(CX), AX | ||||
| 	MULQ 24(BX) | ||||
| 	ADDQ AX, R15 | ||||
| 	ADCQ DX, R14 | ||||
|  | ||||
| 	// r4 += a2×b2 | ||||
| 	MOVQ 16(CX), AX | ||||
| 	MULQ 16(BX) | ||||
| 	ADDQ AX, R15 | ||||
| 	ADCQ DX, R14 | ||||
|  | ||||
| 	// r4 += a3×b1 | ||||
| 	MOVQ 24(CX), AX | ||||
| 	MULQ 8(BX) | ||||
| 	ADDQ AX, R15 | ||||
| 	ADCQ DX, R14 | ||||
|  | ||||
| 	// r4 += a4×b0 | ||||
| 	MOVQ 32(CX), AX | ||||
| 	MULQ (BX) | ||||
| 	ADDQ AX, R15 | ||||
| 	ADCQ DX, R14 | ||||
|  | ||||
| 	// First reduction chain | ||||
| 	MOVQ   $0x0007ffffffffffff, AX | ||||
| 	SHLQ   $0x0d, DI, SI | ||||
| 	SHLQ   $0x0d, R9, R8 | ||||
| 	SHLQ   $0x0d, R11, R10 | ||||
| 	SHLQ   $0x0d, R13, R12 | ||||
| 	SHLQ   $0x0d, R15, R14 | ||||
| 	ANDQ   AX, DI | ||||
| 	IMUL3Q $0x13, R14, R14 | ||||
| 	ADDQ   R14, DI | ||||
| 	ANDQ   AX, R9 | ||||
| 	ADDQ   SI, R9 | ||||
| 	ANDQ   AX, R11 | ||||
| 	ADDQ   R8, R11 | ||||
| 	ANDQ   AX, R13 | ||||
| 	ADDQ   R10, R13 | ||||
| 	ANDQ   AX, R15 | ||||
| 	ADDQ   R12, R15 | ||||
|  | ||||
| 	// Second reduction chain (carryPropagate) | ||||
| 	MOVQ   DI, SI | ||||
| 	SHRQ   $0x33, SI | ||||
| 	MOVQ   R9, R8 | ||||
| 	SHRQ   $0x33, R8 | ||||
| 	MOVQ   R11, R10 | ||||
| 	SHRQ   $0x33, R10 | ||||
| 	MOVQ   R13, R12 | ||||
| 	SHRQ   $0x33, R12 | ||||
| 	MOVQ   R15, R14 | ||||
| 	SHRQ   $0x33, R14 | ||||
| 	ANDQ   AX, DI | ||||
| 	IMUL3Q $0x13, R14, R14 | ||||
| 	ADDQ   R14, DI | ||||
| 	ANDQ   AX, R9 | ||||
| 	ADDQ   SI, R9 | ||||
| 	ANDQ   AX, R11 | ||||
| 	ADDQ   R8, R11 | ||||
| 	ANDQ   AX, R13 | ||||
| 	ADDQ   R10, R13 | ||||
| 	ANDQ   AX, R15 | ||||
| 	ADDQ   R12, R15 | ||||
|  | ||||
| 	// Store output | ||||
| 	MOVQ out+0(FP), AX | ||||
| 	MOVQ DI, (AX) | ||||
| 	MOVQ R9, 8(AX) | ||||
| 	MOVQ R11, 16(AX) | ||||
| 	MOVQ R13, 24(AX) | ||||
| 	MOVQ R15, 32(AX) | ||||
| 	RET | ||||
|  | ||||
| // func feSquare(out *Element, a *Element) | ||||
| TEXT ·feSquare(SB), NOSPLIT, $0-16 | ||||
| 	MOVQ a+8(FP), CX | ||||
|  | ||||
| 	// r0 = l0×l0 | ||||
| 	MOVQ (CX), AX | ||||
| 	MULQ (CX) | ||||
| 	MOVQ AX, SI | ||||
| 	MOVQ DX, BX | ||||
|  | ||||
| 	// r0 += 38×l1×l4 | ||||
| 	MOVQ   8(CX), AX | ||||
| 	IMUL3Q $0x26, AX, AX | ||||
| 	MULQ   32(CX) | ||||
| 	ADDQ   AX, SI | ||||
| 	ADCQ   DX, BX | ||||
|  | ||||
| 	// r0 += 38×l2×l3 | ||||
| 	MOVQ   16(CX), AX | ||||
| 	IMUL3Q $0x26, AX, AX | ||||
| 	MULQ   24(CX) | ||||
| 	ADDQ   AX, SI | ||||
| 	ADCQ   DX, BX | ||||
|  | ||||
| 	// r1 = 2×l0×l1 | ||||
| 	MOVQ (CX), AX | ||||
| 	SHLQ $0x01, AX | ||||
| 	MULQ 8(CX) | ||||
| 	MOVQ AX, R8 | ||||
| 	MOVQ DX, DI | ||||
|  | ||||
| 	// r1 += 38×l2×l4 | ||||
| 	MOVQ   16(CX), AX | ||||
| 	IMUL3Q $0x26, AX, AX | ||||
| 	MULQ   32(CX) | ||||
| 	ADDQ   AX, R8 | ||||
| 	ADCQ   DX, DI | ||||
|  | ||||
| 	// r1 += 19×l3×l3 | ||||
| 	MOVQ   24(CX), AX | ||||
| 	IMUL3Q $0x13, AX, AX | ||||
| 	MULQ   24(CX) | ||||
| 	ADDQ   AX, R8 | ||||
| 	ADCQ   DX, DI | ||||
|  | ||||
| 	// r2 = 2×l0×l2 | ||||
| 	MOVQ (CX), AX | ||||
| 	SHLQ $0x01, AX | ||||
| 	MULQ 16(CX) | ||||
| 	MOVQ AX, R10 | ||||
| 	MOVQ DX, R9 | ||||
|  | ||||
| 	// r2 += l1×l1 | ||||
| 	MOVQ 8(CX), AX | ||||
| 	MULQ 8(CX) | ||||
| 	ADDQ AX, R10 | ||||
| 	ADCQ DX, R9 | ||||
|  | ||||
| 	// r2 += 38×l3×l4 | ||||
| 	MOVQ   24(CX), AX | ||||
| 	IMUL3Q $0x26, AX, AX | ||||
| 	MULQ   32(CX) | ||||
| 	ADDQ   AX, R10 | ||||
| 	ADCQ   DX, R9 | ||||
|  | ||||
| 	// r3 = 2×l0×l3 | ||||
| 	MOVQ (CX), AX | ||||
| 	SHLQ $0x01, AX | ||||
| 	MULQ 24(CX) | ||||
| 	MOVQ AX, R12 | ||||
| 	MOVQ DX, R11 | ||||
|  | ||||
| 	// r3 += 2×l1×l2 | ||||
| 	MOVQ   8(CX), AX | ||||
| 	IMUL3Q $0x02, AX, AX | ||||
| 	MULQ   16(CX) | ||||
| 	ADDQ   AX, R12 | ||||
| 	ADCQ   DX, R11 | ||||
|  | ||||
| 	// r3 += 19×l4×l4 | ||||
| 	MOVQ   32(CX), AX | ||||
| 	IMUL3Q $0x13, AX, AX | ||||
| 	MULQ   32(CX) | ||||
| 	ADDQ   AX, R12 | ||||
| 	ADCQ   DX, R11 | ||||
|  | ||||
| 	// r4 = 2×l0×l4 | ||||
| 	MOVQ (CX), AX | ||||
| 	SHLQ $0x01, AX | ||||
| 	MULQ 32(CX) | ||||
| 	MOVQ AX, R14 | ||||
| 	MOVQ DX, R13 | ||||
|  | ||||
| 	// r4 += 2×l1×l3 | ||||
| 	MOVQ   8(CX), AX | ||||
| 	IMUL3Q $0x02, AX, AX | ||||
| 	MULQ   24(CX) | ||||
| 	ADDQ   AX, R14 | ||||
| 	ADCQ   DX, R13 | ||||
|  | ||||
| 	// r4 += l2×l2 | ||||
| 	MOVQ 16(CX), AX | ||||
| 	MULQ 16(CX) | ||||
| 	ADDQ AX, R14 | ||||
| 	ADCQ DX, R13 | ||||
|  | ||||
| 	// First reduction chain | ||||
| 	MOVQ   $0x0007ffffffffffff, AX | ||||
| 	SHLQ   $0x0d, SI, BX | ||||
| 	SHLQ   $0x0d, R8, DI | ||||
| 	SHLQ   $0x0d, R10, R9 | ||||
| 	SHLQ   $0x0d, R12, R11 | ||||
| 	SHLQ   $0x0d, R14, R13 | ||||
| 	ANDQ   AX, SI | ||||
| 	IMUL3Q $0x13, R13, R13 | ||||
| 	ADDQ   R13, SI | ||||
| 	ANDQ   AX, R8 | ||||
| 	ADDQ   BX, R8 | ||||
| 	ANDQ   AX, R10 | ||||
| 	ADDQ   DI, R10 | ||||
| 	ANDQ   AX, R12 | ||||
| 	ADDQ   R9, R12 | ||||
| 	ANDQ   AX, R14 | ||||
| 	ADDQ   R11, R14 | ||||
|  | ||||
| 	// Second reduction chain (carryPropagate) | ||||
| 	MOVQ   SI, BX | ||||
| 	SHRQ   $0x33, BX | ||||
| 	MOVQ   R8, DI | ||||
| 	SHRQ   $0x33, DI | ||||
| 	MOVQ   R10, R9 | ||||
| 	SHRQ   $0x33, R9 | ||||
| 	MOVQ   R12, R11 | ||||
| 	SHRQ   $0x33, R11 | ||||
| 	MOVQ   R14, R13 | ||||
| 	SHRQ   $0x33, R13 | ||||
| 	ANDQ   AX, SI | ||||
| 	IMUL3Q $0x13, R13, R13 | ||||
| 	ADDQ   R13, SI | ||||
| 	ANDQ   AX, R8 | ||||
| 	ADDQ   BX, R8 | ||||
| 	ANDQ   AX, R10 | ||||
| 	ADDQ   DI, R10 | ||||
| 	ANDQ   AX, R12 | ||||
| 	ADDQ   R9, R12 | ||||
| 	ANDQ   AX, R14 | ||||
| 	ADDQ   R11, R14 | ||||
|  | ||||
| 	// Store output | ||||
| 	MOVQ out+0(FP), AX | ||||
| 	MOVQ SI, (AX) | ||||
| 	MOVQ R8, 8(AX) | ||||
| 	MOVQ R10, 16(AX) | ||||
| 	MOVQ R12, 24(AX) | ||||
| 	MOVQ R14, 32(AX) | ||||
| 	RET | ||||
							
								
								
									
										12
									
								
								vendor/filippo.io/edwards25519/field/fe_amd64_noasm.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								vendor/filippo.io/edwards25519/field/fe_amd64_noasm.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| // Copyright (c) 2019 The Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| //go:build !amd64 || !gc || purego | ||||
| // +build !amd64 !gc purego | ||||
|  | ||||
| package field | ||||
|  | ||||
| func feMul(v, x, y *Element) { feMulGeneric(v, x, y) } | ||||
|  | ||||
| func feSquare(v, x *Element) { feSquareGeneric(v, x) } | ||||
							
								
								
									
										16
									
								
								vendor/filippo.io/edwards25519/field/fe_arm64.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								vendor/filippo.io/edwards25519/field/fe_arm64.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| // Copyright (c) 2020 The Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| //go:build arm64 && gc && !purego | ||||
| // +build arm64,gc,!purego | ||||
|  | ||||
| package field | ||||
|  | ||||
| //go:noescape | ||||
| func carryPropagate(v *Element) | ||||
|  | ||||
| func (v *Element) carryPropagate() *Element { | ||||
| 	carryPropagate(v) | ||||
| 	return v | ||||
| } | ||||
							
								
								
									
										42
									
								
								vendor/filippo.io/edwards25519/field/fe_arm64.s
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								vendor/filippo.io/edwards25519/field/fe_arm64.s
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| // Copyright (c) 2020 The Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| // +build arm64,gc,!purego | ||||
|  | ||||
| #include "textflag.h" | ||||
|  | ||||
| // carryPropagate works exactly like carryPropagateGeneric and uses the | ||||
| // same AND, ADD, and LSR+MADD instructions emitted by the compiler, but | ||||
| // avoids loading R0-R4 twice and uses LDP and STP. | ||||
| // | ||||
| // See https://golang.org/issues/43145 for the main compiler issue. | ||||
| // | ||||
| // func carryPropagate(v *Element) | ||||
| TEXT ·carryPropagate(SB),NOFRAME|NOSPLIT,$0-8 | ||||
| 	MOVD v+0(FP), R20 | ||||
|  | ||||
| 	LDP 0(R20), (R0, R1) | ||||
| 	LDP 16(R20), (R2, R3) | ||||
| 	MOVD 32(R20), R4 | ||||
|  | ||||
| 	AND $0x7ffffffffffff, R0, R10 | ||||
| 	AND $0x7ffffffffffff, R1, R11 | ||||
| 	AND $0x7ffffffffffff, R2, R12 | ||||
| 	AND $0x7ffffffffffff, R3, R13 | ||||
| 	AND $0x7ffffffffffff, R4, R14 | ||||
|  | ||||
| 	ADD R0>>51, R11, R11 | ||||
| 	ADD R1>>51, R12, R12 | ||||
| 	ADD R2>>51, R13, R13 | ||||
| 	ADD R3>>51, R14, R14 | ||||
| 	// R4>>51 * 19 + R10 -> R10 | ||||
| 	LSR $51, R4, R21 | ||||
| 	MOVD $19, R22 | ||||
| 	MADD R22, R10, R21, R10 | ||||
|  | ||||
| 	STP (R10, R11), 0(R20) | ||||
| 	STP (R12, R13), 16(R20) | ||||
| 	MOVD R14, 32(R20) | ||||
|  | ||||
| 	RET | ||||
							
								
								
									
										12
									
								
								vendor/filippo.io/edwards25519/field/fe_arm64_noasm.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								vendor/filippo.io/edwards25519/field/fe_arm64_noasm.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| // Copyright (c) 2021 The Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| //go:build !arm64 || !gc || purego | ||||
| // +build !arm64 !gc purego | ||||
|  | ||||
| package field | ||||
|  | ||||
| func (v *Element) carryPropagate() *Element { | ||||
| 	return v.carryPropagateGeneric() | ||||
| } | ||||
							
								
								
									
										264
									
								
								vendor/filippo.io/edwards25519/field/fe_generic.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										264
									
								
								vendor/filippo.io/edwards25519/field/fe_generic.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,264 @@ | ||||
| // Copyright (c) 2017 The Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package field | ||||
|  | ||||
| import "math/bits" | ||||
|  | ||||
| // uint128 holds a 128-bit number as two 64-bit limbs, for use with the | ||||
| // bits.Mul64 and bits.Add64 intrinsics. | ||||
| type uint128 struct { | ||||
| 	lo, hi uint64 | ||||
| } | ||||
|  | ||||
| // mul64 returns a * b. | ||||
| func mul64(a, b uint64) uint128 { | ||||
| 	hi, lo := bits.Mul64(a, b) | ||||
| 	return uint128{lo, hi} | ||||
| } | ||||
|  | ||||
| // addMul64 returns v + a * b. | ||||
| func addMul64(v uint128, a, b uint64) uint128 { | ||||
| 	hi, lo := bits.Mul64(a, b) | ||||
| 	lo, c := bits.Add64(lo, v.lo, 0) | ||||
| 	hi, _ = bits.Add64(hi, v.hi, c) | ||||
| 	return uint128{lo, hi} | ||||
| } | ||||
|  | ||||
| // shiftRightBy51 returns a >> 51. a is assumed to be at most 115 bits. | ||||
| func shiftRightBy51(a uint128) uint64 { | ||||
| 	return (a.hi << (64 - 51)) | (a.lo >> 51) | ||||
| } | ||||
|  | ||||
| func feMulGeneric(v, a, b *Element) { | ||||
| 	a0 := a.l0 | ||||
| 	a1 := a.l1 | ||||
| 	a2 := a.l2 | ||||
| 	a3 := a.l3 | ||||
| 	a4 := a.l4 | ||||
|  | ||||
| 	b0 := b.l0 | ||||
| 	b1 := b.l1 | ||||
| 	b2 := b.l2 | ||||
| 	b3 := b.l3 | ||||
| 	b4 := b.l4 | ||||
|  | ||||
| 	// Limb multiplication works like pen-and-paper columnar multiplication, but | ||||
| 	// with 51-bit limbs instead of digits. | ||||
| 	// | ||||
| 	//                          a4   a3   a2   a1   a0  x | ||||
| 	//                          b4   b3   b2   b1   b0  = | ||||
| 	//                         ------------------------ | ||||
| 	//                        a4b0 a3b0 a2b0 a1b0 a0b0  + | ||||
| 	//                   a4b1 a3b1 a2b1 a1b1 a0b1       + | ||||
| 	//              a4b2 a3b2 a2b2 a1b2 a0b2            + | ||||
| 	//         a4b3 a3b3 a2b3 a1b3 a0b3                 + | ||||
| 	//    a4b4 a3b4 a2b4 a1b4 a0b4                      = | ||||
| 	//   ---------------------------------------------- | ||||
| 	//      r8   r7   r6   r5   r4   r3   r2   r1   r0 | ||||
| 	// | ||||
| 	// We can then use the reduction identity (a * 2²⁵⁵ + b = a * 19 + b) to | ||||
| 	// reduce the limbs that would overflow 255 bits. r5 * 2²⁵⁵ becomes 19 * r5, | ||||
| 	// r6 * 2³⁰⁶ becomes 19 * r6 * 2⁵¹, etc. | ||||
| 	// | ||||
| 	// Reduction can be carried out simultaneously to multiplication. For | ||||
| 	// example, we do not compute r5: whenever the result of a multiplication | ||||
| 	// belongs to r5, like a1b4, we multiply it by 19 and add the result to r0. | ||||
| 	// | ||||
| 	//            a4b0    a3b0    a2b0    a1b0    a0b0  + | ||||
| 	//            a3b1    a2b1    a1b1    a0b1 19×a4b1  + | ||||
| 	//            a2b2    a1b2    a0b2 19×a4b2 19×a3b2  + | ||||
| 	//            a1b3    a0b3 19×a4b3 19×a3b3 19×a2b3  + | ||||
| 	//            a0b4 19×a4b4 19×a3b4 19×a2b4 19×a1b4  = | ||||
| 	//           -------------------------------------- | ||||
| 	//              r4      r3      r2      r1      r0 | ||||
| 	// | ||||
| 	// Finally we add up the columns into wide, overlapping limbs. | ||||
|  | ||||
| 	a1_19 := a1 * 19 | ||||
| 	a2_19 := a2 * 19 | ||||
| 	a3_19 := a3 * 19 | ||||
| 	a4_19 := a4 * 19 | ||||
|  | ||||
| 	// r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1) | ||||
| 	r0 := mul64(a0, b0) | ||||
| 	r0 = addMul64(r0, a1_19, b4) | ||||
| 	r0 = addMul64(r0, a2_19, b3) | ||||
| 	r0 = addMul64(r0, a3_19, b2) | ||||
| 	r0 = addMul64(r0, a4_19, b1) | ||||
|  | ||||
| 	// r1 = a0×b1 + a1×b0 + 19×(a2×b4 + a3×b3 + a4×b2) | ||||
| 	r1 := mul64(a0, b1) | ||||
| 	r1 = addMul64(r1, a1, b0) | ||||
| 	r1 = addMul64(r1, a2_19, b4) | ||||
| 	r1 = addMul64(r1, a3_19, b3) | ||||
| 	r1 = addMul64(r1, a4_19, b2) | ||||
|  | ||||
| 	// r2 = a0×b2 + a1×b1 + a2×b0 + 19×(a3×b4 + a4×b3) | ||||
| 	r2 := mul64(a0, b2) | ||||
| 	r2 = addMul64(r2, a1, b1) | ||||
| 	r2 = addMul64(r2, a2, b0) | ||||
| 	r2 = addMul64(r2, a3_19, b4) | ||||
| 	r2 = addMul64(r2, a4_19, b3) | ||||
|  | ||||
| 	// r3 = a0×b3 + a1×b2 + a2×b1 + a3×b0 + 19×a4×b4 | ||||
| 	r3 := mul64(a0, b3) | ||||
| 	r3 = addMul64(r3, a1, b2) | ||||
| 	r3 = addMul64(r3, a2, b1) | ||||
| 	r3 = addMul64(r3, a3, b0) | ||||
| 	r3 = addMul64(r3, a4_19, b4) | ||||
|  | ||||
| 	// r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0 | ||||
| 	r4 := mul64(a0, b4) | ||||
| 	r4 = addMul64(r4, a1, b3) | ||||
| 	r4 = addMul64(r4, a2, b2) | ||||
| 	r4 = addMul64(r4, a3, b1) | ||||
| 	r4 = addMul64(r4, a4, b0) | ||||
|  | ||||
| 	// After the multiplication, we need to reduce (carry) the five coefficients | ||||
| 	// to obtain a result with limbs that are at most slightly larger than 2⁵¹, | ||||
| 	// to respect the Element invariant. | ||||
| 	// | ||||
| 	// Overall, the reduction works the same as carryPropagate, except with | ||||
| 	// wider inputs: we take the carry for each coefficient by shifting it right | ||||
| 	// by 51, and add it to the limb above it. The top carry is multiplied by 19 | ||||
| 	// according to the reduction identity and added to the lowest limb. | ||||
| 	// | ||||
| 	// The largest coefficient (r0) will be at most 111 bits, which guarantees | ||||
| 	// that all carries are at most 111 - 51 = 60 bits, which fits in a uint64. | ||||
| 	// | ||||
| 	//     r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1) | ||||
| 	//     r0 < 2⁵²×2⁵² + 19×(2⁵²×2⁵² + 2⁵²×2⁵² + 2⁵²×2⁵² + 2⁵²×2⁵²) | ||||
| 	//     r0 < (1 + 19 × 4) × 2⁵² × 2⁵² | ||||
| 	//     r0 < 2⁷ × 2⁵² × 2⁵² | ||||
| 	//     r0 < 2¹¹¹ | ||||
| 	// | ||||
| 	// Moreover, the top coefficient (r4) is at most 107 bits, so c4 is at most | ||||
| 	// 56 bits, and c4 * 19 is at most 61 bits, which again fits in a uint64 and | ||||
| 	// allows us to easily apply the reduction identity. | ||||
| 	// | ||||
| 	//     r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0 | ||||
| 	//     r4 < 5 × 2⁵² × 2⁵² | ||||
| 	//     r4 < 2¹⁰⁷ | ||||
| 	// | ||||
|  | ||||
| 	c0 := shiftRightBy51(r0) | ||||
| 	c1 := shiftRightBy51(r1) | ||||
| 	c2 := shiftRightBy51(r2) | ||||
| 	c3 := shiftRightBy51(r3) | ||||
| 	c4 := shiftRightBy51(r4) | ||||
|  | ||||
| 	rr0 := r0.lo&maskLow51Bits + c4*19 | ||||
| 	rr1 := r1.lo&maskLow51Bits + c0 | ||||
| 	rr2 := r2.lo&maskLow51Bits + c1 | ||||
| 	rr3 := r3.lo&maskLow51Bits + c2 | ||||
| 	rr4 := r4.lo&maskLow51Bits + c3 | ||||
|  | ||||
| 	// Now all coefficients fit into 64-bit registers but are still too large to | ||||
| 	// be passed around as a Element. We therefore do one last carry chain, | ||||
| 	// where the carries will be small enough to fit in the wiggle room above 2⁵¹. | ||||
| 	*v = Element{rr0, rr1, rr2, rr3, rr4} | ||||
| 	v.carryPropagate() | ||||
| } | ||||
|  | ||||
| func feSquareGeneric(v, a *Element) { | ||||
| 	l0 := a.l0 | ||||
| 	l1 := a.l1 | ||||
| 	l2 := a.l2 | ||||
| 	l3 := a.l3 | ||||
| 	l4 := a.l4 | ||||
|  | ||||
| 	// Squaring works precisely like multiplication above, but thanks to its | ||||
| 	// symmetry we get to group a few terms together. | ||||
| 	// | ||||
| 	//                          l4   l3   l2   l1   l0  x | ||||
| 	//                          l4   l3   l2   l1   l0  = | ||||
| 	//                         ------------------------ | ||||
| 	//                        l4l0 l3l0 l2l0 l1l0 l0l0  + | ||||
| 	//                   l4l1 l3l1 l2l1 l1l1 l0l1       + | ||||
| 	//              l4l2 l3l2 l2l2 l1l2 l0l2            + | ||||
| 	//         l4l3 l3l3 l2l3 l1l3 l0l3                 + | ||||
| 	//    l4l4 l3l4 l2l4 l1l4 l0l4                      = | ||||
| 	//   ---------------------------------------------- | ||||
| 	//      r8   r7   r6   r5   r4   r3   r2   r1   r0 | ||||
| 	// | ||||
| 	//            l4l0    l3l0    l2l0    l1l0    l0l0  + | ||||
| 	//            l3l1    l2l1    l1l1    l0l1 19×l4l1  + | ||||
| 	//            l2l2    l1l2    l0l2 19×l4l2 19×l3l2  + | ||||
| 	//            l1l3    l0l3 19×l4l3 19×l3l3 19×l2l3  + | ||||
| 	//            l0l4 19×l4l4 19×l3l4 19×l2l4 19×l1l4  = | ||||
| 	//           -------------------------------------- | ||||
| 	//              r4      r3      r2      r1      r0 | ||||
| 	// | ||||
| 	// With precomputed 2×, 19×, and 2×19× terms, we can compute each limb with | ||||
| 	// only three Mul64 and four Add64, instead of five and eight. | ||||
|  | ||||
| 	l0_2 := l0 * 2 | ||||
| 	l1_2 := l1 * 2 | ||||
|  | ||||
| 	l1_38 := l1 * 38 | ||||
| 	l2_38 := l2 * 38 | ||||
| 	l3_38 := l3 * 38 | ||||
|  | ||||
| 	l3_19 := l3 * 19 | ||||
| 	l4_19 := l4 * 19 | ||||
|  | ||||
| 	// r0 = l0×l0 + 19×(l1×l4 + l2×l3 + l3×l2 + l4×l1) = l0×l0 + 19×2×(l1×l4 + l2×l3) | ||||
| 	r0 := mul64(l0, l0) | ||||
| 	r0 = addMul64(r0, l1_38, l4) | ||||
| 	r0 = addMul64(r0, l2_38, l3) | ||||
|  | ||||
| 	// r1 = l0×l1 + l1×l0 + 19×(l2×l4 + l3×l3 + l4×l2) = 2×l0×l1 + 19×2×l2×l4 + 19×l3×l3 | ||||
| 	r1 := mul64(l0_2, l1) | ||||
| 	r1 = addMul64(r1, l2_38, l4) | ||||
| 	r1 = addMul64(r1, l3_19, l3) | ||||
|  | ||||
| 	// r2 = l0×l2 + l1×l1 + l2×l0 + 19×(l3×l4 + l4×l3) = 2×l0×l2 + l1×l1 + 19×2×l3×l4 | ||||
| 	r2 := mul64(l0_2, l2) | ||||
| 	r2 = addMul64(r2, l1, l1) | ||||
| 	r2 = addMul64(r2, l3_38, l4) | ||||
|  | ||||
| 	// r3 = l0×l3 + l1×l2 + l2×l1 + l3×l0 + 19×l4×l4 = 2×l0×l3 + 2×l1×l2 + 19×l4×l4 | ||||
| 	r3 := mul64(l0_2, l3) | ||||
| 	r3 = addMul64(r3, l1_2, l2) | ||||
| 	r3 = addMul64(r3, l4_19, l4) | ||||
|  | ||||
| 	// r4 = l0×l4 + l1×l3 + l2×l2 + l3×l1 + l4×l0 = 2×l0×l4 + 2×l1×l3 + l2×l2 | ||||
| 	r4 := mul64(l0_2, l4) | ||||
| 	r4 = addMul64(r4, l1_2, l3) | ||||
| 	r4 = addMul64(r4, l2, l2) | ||||
|  | ||||
| 	c0 := shiftRightBy51(r0) | ||||
| 	c1 := shiftRightBy51(r1) | ||||
| 	c2 := shiftRightBy51(r2) | ||||
| 	c3 := shiftRightBy51(r3) | ||||
| 	c4 := shiftRightBy51(r4) | ||||
|  | ||||
| 	rr0 := r0.lo&maskLow51Bits + c4*19 | ||||
| 	rr1 := r1.lo&maskLow51Bits + c0 | ||||
| 	rr2 := r2.lo&maskLow51Bits + c1 | ||||
| 	rr3 := r3.lo&maskLow51Bits + c2 | ||||
| 	rr4 := r4.lo&maskLow51Bits + c3 | ||||
|  | ||||
| 	*v = Element{rr0, rr1, rr2, rr3, rr4} | ||||
| 	v.carryPropagate() | ||||
| } | ||||
|  | ||||
| // carryPropagate brings the limbs below 52 bits by applying the reduction | ||||
| // identity (a * 2²⁵⁵ + b = a * 19 + b) to the l4 carry. | ||||
| func (v *Element) carryPropagateGeneric() *Element { | ||||
| 	c0 := v.l0 >> 51 | ||||
| 	c1 := v.l1 >> 51 | ||||
| 	c2 := v.l2 >> 51 | ||||
| 	c3 := v.l3 >> 51 | ||||
| 	c4 := v.l4 >> 51 | ||||
|  | ||||
| 	v.l0 = v.l0&maskLow51Bits + c4*19 | ||||
| 	v.l1 = v.l1&maskLow51Bits + c0 | ||||
| 	v.l2 = v.l2&maskLow51Bits + c1 | ||||
| 	v.l3 = v.l3&maskLow51Bits + c2 | ||||
| 	v.l4 = v.l4&maskLow51Bits + c3 | ||||
|  | ||||
| 	return v | ||||
| } | ||||
							
								
								
									
										1027
									
								
								vendor/filippo.io/edwards25519/scalar.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										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
									
								
							
							
						
						
									
										214
									
								
								vendor/filippo.io/edwards25519/scalarmult.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,214 @@ | ||||
| // Copyright (c) 2019 The Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package edwards25519 | ||||
|  | ||||
| import "sync" | ||||
|  | ||||
| // basepointTable is a set of 32 affineLookupTables, where table i is generated | ||||
| // from 256i * basepoint. It is precomputed the first time it's used. | ||||
| func basepointTable() *[32]affineLookupTable { | ||||
| 	basepointTablePrecomp.initOnce.Do(func() { | ||||
| 		p := NewGeneratorPoint() | ||||
| 		for i := 0; i < 32; i++ { | ||||
| 			basepointTablePrecomp.table[i].FromP3(p) | ||||
| 			for j := 0; j < 8; j++ { | ||||
| 				p.Add(p, p) | ||||
| 			} | ||||
| 		} | ||||
| 	}) | ||||
| 	return &basepointTablePrecomp.table | ||||
| } | ||||
|  | ||||
| var basepointTablePrecomp struct { | ||||
| 	table    [32]affineLookupTable | ||||
| 	initOnce sync.Once | ||||
| } | ||||
|  | ||||
| // ScalarBaseMult sets v = x * B, where B is the canonical generator, and | ||||
| // returns v. | ||||
| // | ||||
| // The scalar multiplication is done in constant time. | ||||
| func (v *Point) ScalarBaseMult(x *Scalar) *Point { | ||||
| 	basepointTable := basepointTable() | ||||
|  | ||||
| 	// Write x = sum(x_i * 16^i) so  x*B = sum( B*x_i*16^i ) | ||||
| 	// as described in the Ed25519 paper | ||||
| 	// | ||||
| 	// Group even and odd coefficients | ||||
| 	// x*B     = x_0*16^0*B + x_2*16^2*B + ... + x_62*16^62*B | ||||
| 	//         + x_1*16^1*B + x_3*16^3*B + ... + x_63*16^63*B | ||||
| 	// x*B     = x_0*16^0*B + x_2*16^2*B + ... + x_62*16^62*B | ||||
| 	//    + 16*( x_1*16^0*B + x_3*16^2*B + ... + x_63*16^62*B) | ||||
| 	// | ||||
| 	// We use a lookup table for each i to get x_i*16^(2*i)*B | ||||
| 	// and do four doublings to multiply by 16. | ||||
| 	digits := x.signedRadix16() | ||||
|  | ||||
| 	multiple := &affineCached{} | ||||
| 	tmp1 := &projP1xP1{} | ||||
| 	tmp2 := &projP2{} | ||||
|  | ||||
| 	// Accumulate the odd components first | ||||
| 	v.Set(NewIdentityPoint()) | ||||
| 	for i := 1; i < 64; i += 2 { | ||||
| 		basepointTable[i/2].SelectInto(multiple, digits[i]) | ||||
| 		tmp1.AddAffine(v, multiple) | ||||
| 		v.fromP1xP1(tmp1) | ||||
| 	} | ||||
|  | ||||
| 	// Multiply by 16 | ||||
| 	tmp2.FromP3(v)       // tmp2 =    v in P2 coords | ||||
| 	tmp1.Double(tmp2)    // tmp1 =  2*v in P1xP1 coords | ||||
| 	tmp2.FromP1xP1(tmp1) // tmp2 =  2*v in P2 coords | ||||
| 	tmp1.Double(tmp2)    // tmp1 =  4*v in P1xP1 coords | ||||
| 	tmp2.FromP1xP1(tmp1) // tmp2 =  4*v in P2 coords | ||||
| 	tmp1.Double(tmp2)    // tmp1 =  8*v in P1xP1 coords | ||||
| 	tmp2.FromP1xP1(tmp1) // tmp2 =  8*v in P2 coords | ||||
| 	tmp1.Double(tmp2)    // tmp1 = 16*v in P1xP1 coords | ||||
| 	v.fromP1xP1(tmp1)    // now v = 16*(odd components) | ||||
|  | ||||
| 	// Accumulate the even components | ||||
| 	for i := 0; i < 64; i += 2 { | ||||
| 		basepointTable[i/2].SelectInto(multiple, digits[i]) | ||||
| 		tmp1.AddAffine(v, multiple) | ||||
| 		v.fromP1xP1(tmp1) | ||||
| 	} | ||||
|  | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // ScalarMult sets v = x * q, and returns v. | ||||
| // | ||||
| // The scalar multiplication is done in constant time. | ||||
| func (v *Point) ScalarMult(x *Scalar, q *Point) *Point { | ||||
| 	checkInitialized(q) | ||||
|  | ||||
| 	var table projLookupTable | ||||
| 	table.FromP3(q) | ||||
|  | ||||
| 	// Write x = sum(x_i * 16^i) | ||||
| 	// so  x*Q = sum( Q*x_i*16^i ) | ||||
| 	//         = Q*x_0 + 16*(Q*x_1 + 16*( ... + Q*x_63) ... ) | ||||
| 	//           <------compute inside out--------- | ||||
| 	// | ||||
| 	// We use the lookup table to get the x_i*Q values | ||||
| 	// and do four doublings to compute 16*Q | ||||
| 	digits := x.signedRadix16() | ||||
|  | ||||
| 	// Unwrap first loop iteration to save computing 16*identity | ||||
| 	multiple := &projCached{} | ||||
| 	tmp1 := &projP1xP1{} | ||||
| 	tmp2 := &projP2{} | ||||
| 	table.SelectInto(multiple, digits[63]) | ||||
|  | ||||
| 	v.Set(NewIdentityPoint()) | ||||
| 	tmp1.Add(v, multiple) // tmp1 = x_63*Q in P1xP1 coords | ||||
| 	for i := 62; i >= 0; i-- { | ||||
| 		tmp2.FromP1xP1(tmp1) // tmp2 =    (prev) in P2 coords | ||||
| 		tmp1.Double(tmp2)    // tmp1 =  2*(prev) in P1xP1 coords | ||||
| 		tmp2.FromP1xP1(tmp1) // tmp2 =  2*(prev) in P2 coords | ||||
| 		tmp1.Double(tmp2)    // tmp1 =  4*(prev) in P1xP1 coords | ||||
| 		tmp2.FromP1xP1(tmp1) // tmp2 =  4*(prev) in P2 coords | ||||
| 		tmp1.Double(tmp2)    // tmp1 =  8*(prev) in P1xP1 coords | ||||
| 		tmp2.FromP1xP1(tmp1) // tmp2 =  8*(prev) in P2 coords | ||||
| 		tmp1.Double(tmp2)    // tmp1 = 16*(prev) in P1xP1 coords | ||||
| 		v.fromP1xP1(tmp1)    //    v = 16*(prev) in P3 coords | ||||
| 		table.SelectInto(multiple, digits[i]) | ||||
| 		tmp1.Add(v, multiple) // tmp1 = x_i*Q + 16*(prev) in P1xP1 coords | ||||
| 	} | ||||
| 	v.fromP1xP1(tmp1) | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // basepointNafTable is the nafLookupTable8 for the basepoint. | ||||
| // It is precomputed the first time it's used. | ||||
| func basepointNafTable() *nafLookupTable8 { | ||||
| 	basepointNafTablePrecomp.initOnce.Do(func() { | ||||
| 		basepointNafTablePrecomp.table.FromP3(NewGeneratorPoint()) | ||||
| 	}) | ||||
| 	return &basepointNafTablePrecomp.table | ||||
| } | ||||
|  | ||||
| var basepointNafTablePrecomp struct { | ||||
| 	table    nafLookupTable8 | ||||
| 	initOnce sync.Once | ||||
| } | ||||
|  | ||||
| // VarTimeDoubleScalarBaseMult sets v = a * A + b * B, where B is the canonical | ||||
| // generator, and returns v. | ||||
| // | ||||
| // Execution time depends on the inputs. | ||||
| func (v *Point) VarTimeDoubleScalarBaseMult(a *Scalar, A *Point, b *Scalar) *Point { | ||||
| 	checkInitialized(A) | ||||
|  | ||||
| 	// Similarly to the single variable-base approach, we compute | ||||
| 	// digits and use them with a lookup table.  However, because | ||||
| 	// we are allowed to do variable-time operations, we don't | ||||
| 	// need constant-time lookups or constant-time digit | ||||
| 	// computations. | ||||
| 	// | ||||
| 	// So we use a non-adjacent form of some width w instead of | ||||
| 	// radix 16.  This is like a binary representation (one digit | ||||
| 	// for each binary place) but we allow the digits to grow in | ||||
| 	// magnitude up to 2^{w-1} so that the nonzero digits are as | ||||
| 	// sparse as possible.  Intuitively, this "condenses" the | ||||
| 	// "mass" of the scalar onto sparse coefficients (meaning | ||||
| 	// fewer additions). | ||||
|  | ||||
| 	basepointNafTable := basepointNafTable() | ||||
| 	var aTable nafLookupTable5 | ||||
| 	aTable.FromP3(A) | ||||
| 	// Because the basepoint is fixed, we can use a wider NAF | ||||
| 	// corresponding to a bigger table. | ||||
| 	aNaf := a.nonAdjacentForm(5) | ||||
| 	bNaf := b.nonAdjacentForm(8) | ||||
|  | ||||
| 	// Find the first nonzero coefficient. | ||||
| 	i := 255 | ||||
| 	for j := i; j >= 0; j-- { | ||||
| 		if aNaf[j] != 0 || bNaf[j] != 0 { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	multA := &projCached{} | ||||
| 	multB := &affineCached{} | ||||
| 	tmp1 := &projP1xP1{} | ||||
| 	tmp2 := &projP2{} | ||||
| 	tmp2.Zero() | ||||
|  | ||||
| 	// Move from high to low bits, doubling the accumulator | ||||
| 	// at each iteration and checking whether there is a nonzero | ||||
| 	// coefficient to look up a multiple of. | ||||
| 	for ; i >= 0; i-- { | ||||
| 		tmp1.Double(tmp2) | ||||
|  | ||||
| 		// Only update v if we have a nonzero coeff to add in. | ||||
| 		if aNaf[i] > 0 { | ||||
| 			v.fromP1xP1(tmp1) | ||||
| 			aTable.SelectInto(multA, aNaf[i]) | ||||
| 			tmp1.Add(v, multA) | ||||
| 		} else if aNaf[i] < 0 { | ||||
| 			v.fromP1xP1(tmp1) | ||||
| 			aTable.SelectInto(multA, -aNaf[i]) | ||||
| 			tmp1.Sub(v, multA) | ||||
| 		} | ||||
|  | ||||
| 		if bNaf[i] > 0 { | ||||
| 			v.fromP1xP1(tmp1) | ||||
| 			basepointNafTable.SelectInto(multB, bNaf[i]) | ||||
| 			tmp1.AddAffine(v, multB) | ||||
| 		} else if bNaf[i] < 0 { | ||||
| 			v.fromP1xP1(tmp1) | ||||
| 			basepointNafTable.SelectInto(multB, -bNaf[i]) | ||||
| 			tmp1.SubAffine(v, multB) | ||||
| 		} | ||||
|  | ||||
| 		tmp2.FromP1xP1(tmp1) | ||||
| 	} | ||||
|  | ||||
| 	v.fromP2(tmp2) | ||||
| 	return v | ||||
| } | ||||
							
								
								
									
										129
									
								
								vendor/filippo.io/edwards25519/tables.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								vendor/filippo.io/edwards25519/tables.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,129 @@ | ||||
| // Copyright (c) 2019 The Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package edwards25519 | ||||
|  | ||||
| import ( | ||||
| 	"crypto/subtle" | ||||
| ) | ||||
|  | ||||
| // A dynamic lookup table for variable-base, constant-time scalar muls. | ||||
| type projLookupTable struct { | ||||
| 	points [8]projCached | ||||
| } | ||||
|  | ||||
| // A precomputed lookup table for fixed-base, constant-time scalar muls. | ||||
| type affineLookupTable struct { | ||||
| 	points [8]affineCached | ||||
| } | ||||
|  | ||||
| // A dynamic lookup table for variable-base, variable-time scalar muls. | ||||
| type nafLookupTable5 struct { | ||||
| 	points [8]projCached | ||||
| } | ||||
|  | ||||
| // A precomputed lookup table for fixed-base, variable-time scalar muls. | ||||
| type nafLookupTable8 struct { | ||||
| 	points [64]affineCached | ||||
| } | ||||
|  | ||||
| // Constructors. | ||||
|  | ||||
| // Builds a lookup table at runtime. Fast. | ||||
| func (v *projLookupTable) FromP3(q *Point) { | ||||
| 	// Goal: v.points[i] = (i+1)*Q, i.e., Q, 2Q, ..., 8Q | ||||
| 	// This allows lookup of -8Q, ..., -Q, 0, Q, ..., 8Q | ||||
| 	v.points[0].FromP3(q) | ||||
| 	tmpP3 := Point{} | ||||
| 	tmpP1xP1 := projP1xP1{} | ||||
| 	for i := 0; i < 7; i++ { | ||||
| 		// Compute (i+1)*Q as Q + i*Q and convert to a ProjCached | ||||
| 		// This is needlessly complicated because the API has explicit | ||||
| 		// recievers instead of creating stack objects and relying on RVO | ||||
| 		v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.Add(q, &v.points[i]))) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // This is not optimised for speed; fixed-base tables should be precomputed. | ||||
| func (v *affineLookupTable) FromP3(q *Point) { | ||||
| 	// Goal: v.points[i] = (i+1)*Q, i.e., Q, 2Q, ..., 8Q | ||||
| 	// This allows lookup of -8Q, ..., -Q, 0, Q, ..., 8Q | ||||
| 	v.points[0].FromP3(q) | ||||
| 	tmpP3 := Point{} | ||||
| 	tmpP1xP1 := projP1xP1{} | ||||
| 	for i := 0; i < 7; i++ { | ||||
| 		// Compute (i+1)*Q as Q + i*Q and convert to AffineCached | ||||
| 		v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.AddAffine(q, &v.points[i]))) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Builds a lookup table at runtime. Fast. | ||||
| func (v *nafLookupTable5) FromP3(q *Point) { | ||||
| 	// Goal: v.points[i] = (2*i+1)*Q, i.e., Q, 3Q, 5Q, ..., 15Q | ||||
| 	// This allows lookup of -15Q, ..., -3Q, -Q, 0, Q, 3Q, ..., 15Q | ||||
| 	v.points[0].FromP3(q) | ||||
| 	q2 := Point{} | ||||
| 	q2.Add(q, q) | ||||
| 	tmpP3 := Point{} | ||||
| 	tmpP1xP1 := projP1xP1{} | ||||
| 	for i := 0; i < 7; i++ { | ||||
| 		v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.Add(&q2, &v.points[i]))) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // This is not optimised for speed; fixed-base tables should be precomputed. | ||||
| func (v *nafLookupTable8) FromP3(q *Point) { | ||||
| 	v.points[0].FromP3(q) | ||||
| 	q2 := Point{} | ||||
| 	q2.Add(q, q) | ||||
| 	tmpP3 := Point{} | ||||
| 	tmpP1xP1 := projP1xP1{} | ||||
| 	for i := 0; i < 63; i++ { | ||||
| 		v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.AddAffine(&q2, &v.points[i]))) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Selectors. | ||||
|  | ||||
| // Set dest to x*Q, where -8 <= x <= 8, in constant time. | ||||
| func (v *projLookupTable) SelectInto(dest *projCached, x int8) { | ||||
| 	// Compute xabs = |x| | ||||
| 	xmask := x >> 7 | ||||
| 	xabs := uint8((x + xmask) ^ xmask) | ||||
|  | ||||
| 	dest.Zero() | ||||
| 	for j := 1; j <= 8; j++ { | ||||
| 		// Set dest = j*Q if |x| = j | ||||
| 		cond := subtle.ConstantTimeByteEq(xabs, uint8(j)) | ||||
| 		dest.Select(&v.points[j-1], dest, cond) | ||||
| 	} | ||||
| 	// Now dest = |x|*Q, conditionally negate to get x*Q | ||||
| 	dest.CondNeg(int(xmask & 1)) | ||||
| } | ||||
|  | ||||
| // Set dest to x*Q, where -8 <= x <= 8, in constant time. | ||||
| func (v *affineLookupTable) SelectInto(dest *affineCached, x int8) { | ||||
| 	// Compute xabs = |x| | ||||
| 	xmask := x >> 7 | ||||
| 	xabs := uint8((x + xmask) ^ xmask) | ||||
|  | ||||
| 	dest.Zero() | ||||
| 	for j := 1; j <= 8; j++ { | ||||
| 		// Set dest = j*Q if |x| = j | ||||
| 		cond := subtle.ConstantTimeByteEq(xabs, uint8(j)) | ||||
| 		dest.Select(&v.points[j-1], dest, cond) | ||||
| 	} | ||||
| 	// Now dest = |x|*Q, conditionally negate to get x*Q | ||||
| 	dest.CondNeg(int(xmask & 1)) | ||||
| } | ||||
|  | ||||
| // Given odd x with 0 < x < 2^4, return x*Q (in variable time). | ||||
| func (v *nafLookupTable5) SelectInto(dest *projCached, x int8) { | ||||
| 	*dest = v.points[x/2] | ||||
| } | ||||
|  | ||||
| // Given odd x with 0 < x < 2^7, return x*Q (in variable time). | ||||
| func (v *nafLookupTable8) SelectInto(dest *affineCached, x int8) { | ||||
| 	*dest = v.points[x/2] | ||||
| } | ||||
							
								
								
									
										2
									
								
								vendor/github.com/Rhymen/go-whatsapp/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/Rhymen/go-whatsapp/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -70,7 +70,7 @@ func (myHandler) HandleContactMessage(message whatsapp.ContactMessage) { | ||||
| 	fmt.Println(message) | ||||
| } | ||||
|  | ||||
| func (myHandler) HandleBatteryMessage(msg whatsapp.BatteryMessage) { | ||||
| func (myHandler) HandleBatteryMessage(message whatsapp.BatteryMessage) { | ||||
| 	fmt.Println(message) | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										2
									
								
								vendor/github.com/Rhymen/go-whatsapp/session.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/Rhymen/go-whatsapp/session.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -526,5 +526,7 @@ func (wac *Conn) Logout() error { | ||||
| 		return fmt.Errorf("error writing logout: %v\n", err) | ||||
| 	} | ||||
|  | ||||
| 	wac.loggedIn = false | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
							
								
								
									
										17
									
								
								vendor/github.com/SevereCloud/vksdk/v2/.golangci.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										17
									
								
								vendor/github.com/SevereCloud/vksdk/v2/.golangci.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -48,10 +48,20 @@ linters: | ||||
|     - nilerr | ||||
|     - revive | ||||
|     - wastedassign | ||||
|     - bidichk | ||||
|     - contextcheck | ||||
|     - ireturn | ||||
|     - nilnil | ||||
|     - tenv | ||||
|     - nestif | ||||
|     - grouper | ||||
|     - decorder | ||||
|     - containedctx | ||||
|     # - execinquery # FIXME: panic in 1.46.0 | ||||
|     - nosprintfhostport | ||||
|  | ||||
| # - wrapcheck # TODO: v3 Fix | ||||
| # - testpackage # TODO: Fix testpackage | ||||
| # - nestif # TODO: Fix nestif | ||||
| # - noctx # TODO: Fix noctx | ||||
|  | ||||
| # don't enable: | ||||
| @@ -75,6 +85,11 @@ linters: | ||||
| # - cyclop | ||||
| # - promlinter | ||||
| # - tagliatelle | ||||
| # - errname | ||||
| # - varnamelen | ||||
| # - errchkjson | ||||
| # - maintidx | ||||
| # - nonamedreturns | ||||
|  | ||||
| # depricated | ||||
| # - maligned | ||||
|   | ||||
							
								
								
									
										1
									
								
								vendor/github.com/SevereCloud/vksdk/v2/.markdownlint.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								vendor/github.com/SevereCloud/vksdk/v2/.markdownlint.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,2 +1,3 @@ | ||||
| --- | ||||
| no-hard-tabs: false | ||||
| no-duplicate-heading: false | ||||
|   | ||||
							
								
								
									
										20
									
								
								vendor/github.com/SevereCloud/vksdk/v2/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								vendor/github.com/SevereCloud/vksdk/v2/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,20 +0,0 @@ | ||||
| --- | ||||
| language: go | ||||
|  | ||||
| cache: | ||||
|   directories: | ||||
|     - $HOME/.cache/go-build | ||||
|     - $HOME/gopath/pkg/mod | ||||
|  | ||||
| go: | ||||
|   - 1.x | ||||
|  | ||||
| before_script: | ||||
|   - git fetch --depth=1 origin +refs/tags/*:refs/tags/* | ||||
|   - git describe --tags $(git rev-list --tags --max-count=1) --always | ||||
|  | ||||
| script: | ||||
|   - go test -v -race -coverprofile=coverage.txt -covermode=atomic -p=1 ./... | ||||
|  | ||||
| after_success: | ||||
|   - bash <(curl -s https://codecov.io/bash) | ||||
							
								
								
									
										4
									
								
								vendor/github.com/SevereCloud/vksdk/v2/CONTRIBUTING.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								vendor/github.com/SevereCloud/vksdk/v2/CONTRIBUTING.md
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -6,7 +6,7 @@ | ||||
|  | ||||
| Требования: | ||||
|  | ||||
| - [Go 1.13+](https://golang.org/doc/install) | ||||
| - [Go 1.16+](https://golang.org/doc/install) | ||||
| - [golangci-lint](https://github.com/golangci/golangci-lint) | ||||
| - [global .gitignore](https://help.github.com/en/articles/ignoring-files#create-a-global-gitignore) | ||||
|  | ||||
| @@ -39,6 +39,7 @@ golangci-lint run | ||||
| # CLIENT_SECRET="" | ||||
| # USER_TOKEN="" | ||||
| # WIDGET_TOKEN="" | ||||
| # MARUSIA_TOKEN="" | ||||
| # CLIENT_ID="123456" | ||||
| # GROUP_ID="123456" | ||||
| # ACCOUNT_ID="123456" | ||||
| @@ -56,6 +57,7 @@ go test ./... | ||||
|     "go.testEnvVars": { | ||||
|         "SERVICE_TOKEN": "", | ||||
|         "WIDGET_TOKEN": "", | ||||
|         "MARUSIA_TOKEN": "", | ||||
|         "GROUP_TOKEN": "", | ||||
|         "CLIENT_SECRET": "", | ||||
|         "USER_TOKEN": "", | ||||
|   | ||||
							
								
								
									
										252
									
								
								vendor/github.com/SevereCloud/vksdk/v2/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										252
									
								
								vendor/github.com/SevereCloud/vksdk/v2/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,125 +1,127 @@ | ||||
| # VK SDK for Golang | ||||
|  | ||||
| [](https://travis-ci.com/SevereCloud/vksdk) | ||||
| [](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2?tab=subdirectories) | ||||
| [](https://vk.com/dev/) | ||||
| [](https://codecov.io/gh/SevereCloud/vksdk) | ||||
| [](https://vk.me/join/AJQ1d6Or8Q00Y_CSOESfbqGt) | ||||
| [](https://github.com/SevereCloud/vksdk/releases) | ||||
| [](https://github.com/SevereCloud/vksdk/blob/master/LICENSE) | ||||
|  | ||||
| **VK SDK for Golang** ready implementation of the main VK API functions for Go. | ||||
|  | ||||
| [Russian documentation](https://github.com/SevereCloud/vksdk/wiki) | ||||
|  | ||||
| ## Features | ||||
|  | ||||
| Version API 5.131. | ||||
|  | ||||
| - [API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/api) | ||||
|   - 400+ methods | ||||
|   - Ability to change the request handler | ||||
|   - Ability to modify HTTP client | ||||
|   - Request Limiter | ||||
|   - Token pool | ||||
| - [Callback API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/callback) | ||||
|   - Tracking tool for users activity in your VK communities | ||||
|   - Supports all events | ||||
|   - Auto setting callback | ||||
| - [Bots Long Poll API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/longpoll-bot) | ||||
|   - Allows you to work with community events in real time | ||||
|   - Supports all events | ||||
|   - Ability to modify HTTP client | ||||
| - [User Long Poll API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/longpoll-user) | ||||
|   - Allows you to work with user events in real time | ||||
|   - Ability to modify HTTP client | ||||
| - [Streaming API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/streaming) | ||||
|   - Receiving public data from VK by specified keywords | ||||
|   - Ability to modify HTTP client | ||||
| - [FOAF](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/foaf) | ||||
|   - Machine-readable ontology describing persons | ||||
|   - Works with users and groups | ||||
|   - The only place to get page creation date | ||||
| - [Games](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/games) | ||||
|   - Checking launch parameters | ||||
|   - Intermediate http handler | ||||
| - [VK Mini Apps](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/vkapps) | ||||
|   - Checking launch parameters | ||||
|   - Intermediate http handler | ||||
| - [Payments API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/payments) | ||||
|   - Processes payment notifications | ||||
| - [Marusia Skills](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/marusia) | ||||
|   - For creating Marusia Skills | ||||
|   - Support SSML | ||||
|  | ||||
| ## Install | ||||
|  | ||||
| ```bash | ||||
| # go mod init mymodulename | ||||
| go get github.com/SevereCloud/vksdk/v2@latest | ||||
| ``` | ||||
|  | ||||
| ## Use by | ||||
|  | ||||
| - [Joe](https://github.com/go-joe/joe) adapter: <https://github.com/tdakkota/joe-vk-adapter> | ||||
| - [Logrus](https://github.com/sirupsen/logrus) hook: <https://github.com/SevereCloud/vkrus> | ||||
|  | ||||
| ### Example | ||||
|  | ||||
| ```go | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"log" | ||||
|  | ||||
| 	"github.com/SevereCloud/vksdk/v2/api" | ||||
| 	"github.com/SevereCloud/vksdk/v2/api/params" | ||||
| 	"github.com/SevereCloud/vksdk/v2/events" | ||||
| 	"github.com/SevereCloud/vksdk/v2/longpoll-bot" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	token := "<TOKEN>" // use os.Getenv("TOKEN") | ||||
| 	vk := api.NewVK(token) | ||||
|  | ||||
| 	// get information about the group | ||||
| 	group, err := vk.GroupsGetByID(nil) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// Initializing Long Poll | ||||
| 	lp, err := longpoll.NewLongPoll(vk, group[0].ID) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// New message event | ||||
| 	lp.MessageNew(func(_ context.Context, obj events.MessageNewObject) { | ||||
| 		log.Printf("%d: %s", obj.Message.PeerID, obj.Message.Text) | ||||
|  | ||||
| 		if obj.Message.Text == "ping" { | ||||
| 			b := params.NewMessagesSendBuilder() | ||||
| 			b.Message("pong") | ||||
| 			b.RandomID(0) | ||||
| 			b.PeerID(obj.Message.PeerID) | ||||
|  | ||||
| 			_, err := vk.MessagesSend(b.Params) | ||||
| 			if err != nil { | ||||
| 				log.Fatal(err) | ||||
| 			} | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| 	// Run Bots Long Poll | ||||
| 	log.Println("Start Long Poll") | ||||
| 	if err := lp.Run(); err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ## LICENSE | ||||
|  | ||||
| [](https://app.fossa.io/projects/git%2Bgithub.com%2FSevereCloud%2Fvksdk?ref=badge_large) | ||||
| # VK SDK for Golang | ||||
|  | ||||
| [](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2?tab=subdirectories) | ||||
| [](https://dev.vk.com/) | ||||
| [](https://codecov.io/gh/SevereCloud/vksdk) | ||||
| [](https://vk.me/join/AJQ1d6Or8Q00Y_CSOESfbqGt) | ||||
| [](https://github.com/SevereCloud/vksdk/releases) | ||||
| [](https://github.com/SevereCloud/vksdk/blob/master/LICENSE) | ||||
|  | ||||
| **VK SDK for Golang** ready implementation of the main VK API functions for Go. | ||||
|  | ||||
| [Russian documentation](https://github.com/SevereCloud/vksdk/wiki) | ||||
|  | ||||
| ## Features | ||||
|  | ||||
| Version API 5.131. | ||||
|  | ||||
| - [API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/api) | ||||
|   - 500+ methods | ||||
|   - Ability to modify HTTP client | ||||
|   - Request Limiter | ||||
|   - Support [zstd](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/api#VK.EnableZstd) | ||||
| and [MessagePack](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/api#VK.EnableMessagePack) | ||||
|   - Token pool | ||||
|   - [OAuth](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/api/oauth) | ||||
| - [Callback API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/callback) | ||||
|   - Tracking tool for users activity in your VK communities | ||||
|   - Supports all events | ||||
|   - Auto setting callback | ||||
| - [Bots Long Poll API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/longpoll-bot) | ||||
|   - Allows you to work with community events in real time | ||||
|   - Supports all events | ||||
|   - Ability to modify HTTP client | ||||
| - [User Long Poll API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/longpoll-user) | ||||
|   - Allows you to work with user events in real time | ||||
|   - Ability to modify HTTP client | ||||
| - [Streaming API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/streaming) | ||||
|   - Receiving public data from VK by specified keywords | ||||
|   - Ability to modify HTTP client | ||||
| - [FOAF](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/foaf) | ||||
|   - Machine-readable ontology describing persons | ||||
|   - Works with users and groups | ||||
|   - The only place to get page creation date | ||||
| - [Games](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/games) | ||||
|   - Checking launch parameters | ||||
|   - Intermediate http handler | ||||
| - [VK Mini Apps](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/vkapps) | ||||
|   - Checking launch parameters | ||||
|   - Intermediate http handler | ||||
| - [Payments API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/payments) | ||||
|   - Processes payment notifications | ||||
| - [Marusia Skills](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/marusia) | ||||
|   - For creating Marusia Skills | ||||
|   - Support SSML | ||||
|  | ||||
| ## Install | ||||
|  | ||||
| ```bash | ||||
| # go mod init mymodulename | ||||
| go get github.com/SevereCloud/vksdk/v2@latest | ||||
| ``` | ||||
|  | ||||
| ## Use by | ||||
|  | ||||
| - A simple chat bridge: <https://github.com/42wim/matterbridge> | ||||
| - [Joe](https://github.com/go-joe/joe) adapter: <https://github.com/tdakkota/joe-vk-adapter> | ||||
| - [Logrus](https://github.com/sirupsen/logrus) hook: <https://github.com/SevereCloud/vkrus> | ||||
|  | ||||
| ### Example | ||||
|  | ||||
| ```go | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"log" | ||||
|  | ||||
| 	"github.com/SevereCloud/vksdk/v2/api" | ||||
| 	"github.com/SevereCloud/vksdk/v2/api/params" | ||||
| 	"github.com/SevereCloud/vksdk/v2/events" | ||||
| 	"github.com/SevereCloud/vksdk/v2/longpoll-bot" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	token := "<TOKEN>" // use os.Getenv("TOKEN") | ||||
| 	vk := api.NewVK(token) | ||||
|  | ||||
| 	// get information about the group | ||||
| 	group, err := vk.GroupsGetByID(nil) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// Initializing Long Poll | ||||
| 	lp, err := longpoll.NewLongPoll(vk, group[0].ID) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// New message event | ||||
| 	lp.MessageNew(func(_ context.Context, obj events.MessageNewObject) { | ||||
| 		log.Printf("%d: %s", obj.Message.PeerID, obj.Message.Text) | ||||
|  | ||||
| 		if obj.Message.Text == "ping" { | ||||
| 			b := params.NewMessagesSendBuilder() | ||||
| 			b.Message("pong") | ||||
| 			b.RandomID(0) | ||||
| 			b.PeerID(obj.Message.PeerID) | ||||
|  | ||||
| 			_, err := vk.MessagesSend(b.Params) | ||||
| 			if err != nil { | ||||
| 				log.Fatal(err) | ||||
| 			} | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| 	// Run Bots Long Poll | ||||
| 	log.Println("Start Long Poll") | ||||
| 	if err := lp.Run(); err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ## LICENSE | ||||
|  | ||||
| [](https://app.fossa.io/projects/git%2Bgithub.com%2FSevereCloud%2Fvksdk?ref=badge_large) | ||||
|   | ||||
							
								
								
									
										50
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										50
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -3,7 +3,7 @@ | ||||
| [](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/api) | ||||
| [](https://vk.com/dev/first_guide) | ||||
|  | ||||
| Данная библиотека поддерживает версию API **5.122**. | ||||
| Данная библиотека поддерживает версию API **5.131**. | ||||
|  | ||||
| ## Запросы | ||||
|  | ||||
| @@ -80,6 +80,54 @@ if errors.As(err, &e) { | ||||
|  | ||||
| Для Execute существует отдельная ошибка `ExecuteErrors` | ||||
|  | ||||
| ### Поддержка MessagePack и zstd | ||||
|  | ||||
| > Результат перехода с gzip (JSON) на zstd (msgpack): | ||||
| > | ||||
| > - в 7 раз быстрее сжатие (–1 мкс); | ||||
| > - на 10% меньше размер данных (8 Кбайт вместо 9 Кбайт); | ||||
| > - продуктовый эффект не статзначимый :( | ||||
| > | ||||
| > [Как мы отказались от JPEG, JSON, TCP и ускорили ВКонтакте в два раза](https://habr.com/ru/company/vk/blog/594633/) | ||||
|  | ||||
| VK API способно возвращать ответ в виде [MessagePack](https://msgpack.org/). | ||||
| Это эффективный формат двоичной сериализации, похожий на JSON, только быстрее | ||||
| и меньше по размеру. | ||||
|  | ||||
| ВНИМАНИЕ, C MessagePack НЕКОТОРЫЕ МЕТОДЫ МОГУТ ВОЗВРАЩАТЬ | ||||
| СЛОМАННУЮ КОДИРОВКУ. | ||||
|  | ||||
| Для сжатия, вместо классического gzip, можно использовать | ||||
| [zstd](https://github.com/facebook/zstd). Сейчас vksdk поддерживает zstd без | ||||
| словаря. Если кто знает как получать словарь, | ||||
| [отпишитесь сюда](https://github.com/SevereCloud/vksdk/issues/180). | ||||
|  | ||||
| ```go | ||||
| vk := api.NewVK(os.Getenv("USER_TOKEN")) | ||||
|  | ||||
| method := "store.getStickersKeywords" | ||||
| params := api.Params{ | ||||
| 	"aliases":       true, | ||||
| 	"all_products":  true, | ||||
| 	"need_stickers": true, | ||||
| } | ||||
|  | ||||
| r, err := vk.Request(method, params) // Content-Length: 44758 | ||||
| if err != nil { | ||||
| 	log.Fatal(err) | ||||
| } | ||||
| log.Println("json:", len(r)) // json: 814231 | ||||
|  | ||||
| vk.EnableMessagePack() // Включаем поддержку MessagePack | ||||
| vk.EnableZstd() // Включаем поддержку zstd | ||||
|  | ||||
| r, err = vk.Request(method, params) // Content-Length: 35755 | ||||
| if err != nil { | ||||
| 	log.Fatal(err) | ||||
| } | ||||
| log.Println("msgpack:", len(r)) // msgpack: 650775 | ||||
| ``` | ||||
|  | ||||
| ### Запрос любого метода | ||||
|  | ||||
| Пример запроса [users.get](https://vk.com/dev/users.get) | ||||
|   | ||||
							
								
								
									
										2
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/account.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/account.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -141,6 +141,8 @@ func (vk *VK) AccountSetInfo(params Params) (response int, err error) { | ||||
| // AccountSetNameInMenu sets an application screen name | ||||
| // (up to 17 characters), that is shown to the user in the left menu. | ||||
| // | ||||
| // Deprecated: This method is deprecated and may be disabled soon, please avoid | ||||
| // | ||||
| // https://vk.com/dev/account.setNameInMenu | ||||
| func (vk *VK) AccountSetNameInMenu(params Params) (response int, err error) { | ||||
| 	err = vk.RequestUnmarshal("account.setNameInMenu", &response, params) | ||||
|   | ||||
							
								
								
									
										21
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/ads.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/ads.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,9 +1,11 @@ | ||||
| package api // import "github.com/SevereCloud/vksdk/v2/api" | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
|  | ||||
| 	"github.com/SevereCloud/vksdk/v2/object" | ||||
| 	"github.com/vmihailenco/msgpack/v5" | ||||
| ) | ||||
|  | ||||
| // AdsAddOfficeUsersItem struct. | ||||
| @@ -21,6 +23,23 @@ func (r *AdsAddOfficeUsersItem) UnmarshalJSON(data []byte) (err error) { | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // DecodeMsgpack func. | ||||
| func (r *AdsAddOfficeUsersItem) DecodeMsgpack(dec *msgpack.Decoder) error { | ||||
| 	data, err := dec.DecodeRaw() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if msgpack.Unmarshal(data, &r.OK) != nil { | ||||
| 		d := msgpack.NewDecoder(bytes.NewReader(data)) | ||||
| 		d.SetCustomStructTag("json") | ||||
|  | ||||
| 		return d.Decode(&r.Error) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // AdsAddOfficeUsersResponse struct. | ||||
| type AdsAddOfficeUsersResponse []AdsAddOfficeUsersItem | ||||
|  | ||||
| @@ -349,7 +368,7 @@ func (vk *VK) AdsGetAdsLayout(params Params) (response AdsGetAdsLayoutResponse, | ||||
|  | ||||
| // AdsGetMusiciansResponse struct. | ||||
| type AdsGetMusiciansResponse struct { | ||||
| 	Items []object.BaseObjectWithName | ||||
| 	Items []object.AdsMusician | ||||
| } | ||||
|  | ||||
| // AdsGetMusicians returns a list of musicians. | ||||
|   | ||||
							
								
								
									
										98
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/api.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										98
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/api.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -7,9 +7,11 @@ package api // import "github.com/SevereCloud/vksdk/v2/api" | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"compress/gzip" | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"mime" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| @@ -21,6 +23,8 @@ import ( | ||||
| 	"github.com/SevereCloud/vksdk/v2" | ||||
| 	"github.com/SevereCloud/vksdk/v2/internal" | ||||
| 	"github.com/SevereCloud/vksdk/v2/object" | ||||
| 	"github.com/klauspost/compress/zstd" | ||||
| 	"github.com/vmihailenco/msgpack/v5" | ||||
| ) | ||||
|  | ||||
| // Api constants. | ||||
| @@ -91,6 +95,9 @@ type VK struct { | ||||
| 	UserAgent    string | ||||
| 	Handler      func(method string, params ...Params) (Response, error) | ||||
|  | ||||
| 	msgpack bool | ||||
| 	zstd    bool | ||||
|  | ||||
| 	mux      sync.Mutex | ||||
| 	lastTime time.Time | ||||
| 	rps      int | ||||
| @@ -98,9 +105,9 @@ type VK struct { | ||||
|  | ||||
| // Response struct. | ||||
| type Response struct { | ||||
| 	Response      json.RawMessage `json:"response"` | ||||
| 	Error         Error           `json:"error"` | ||||
| 	ExecuteErrors ExecuteErrors   `json:"execute_errors"` | ||||
| 	Response      object.RawMessage `json:"response"` | ||||
| 	Error         Error             `json:"error"` | ||||
| 	ExecuteErrors ExecuteErrors     `json:"execute_errors"` | ||||
| } | ||||
|  | ||||
| // NewVK returns a new VK. | ||||
| @@ -121,7 +128,7 @@ func NewVK(tokens ...string) *VK { | ||||
| 	vk.accessTokens = tokens | ||||
| 	vk.Version = Version | ||||
|  | ||||
| 	vk.Handler = vk.defaultHandler | ||||
| 	vk.Handler = vk.DefaultHandler | ||||
|  | ||||
| 	vk.MethodURL = MethodURL | ||||
| 	vk.Client = http.DefaultClient | ||||
| @@ -207,8 +214,8 @@ func buildQuery(sliceParams ...Params) (context.Context, url.Values) { | ||||
| 	return ctx, query | ||||
| } | ||||
|  | ||||
| // defaultHandler provides access to VK API methods. | ||||
| func (vk *VK) defaultHandler(method string, sliceParams ...Params) (Response, error) { | ||||
| // DefaultHandler provides access to VK API methods. | ||||
| func (vk *VK) DefaultHandler(method string, sliceParams ...Params) (Response, error) { | ||||
| 	u := vk.MethodURL + method | ||||
| 	ctx, query := buildQuery(sliceParams...) | ||||
| 	attempt := 0 | ||||
| @@ -243,24 +250,58 @@ func (vk *VK) defaultHandler(method string, sliceParams ...Params) (Response, er | ||||
| 			return response, err | ||||
| 		} | ||||
|  | ||||
| 		acceptEncoding := "gzip" | ||||
| 		if vk.zstd { | ||||
| 			acceptEncoding = "zstd" | ||||
| 		} | ||||
|  | ||||
| 		req.Header.Set("User-Agent", vk.UserAgent) | ||||
| 		req.Header.Set("Content-Type", "application/x-www-form-urlencoded") | ||||
|  | ||||
| 		req.Header.Set("Accept-Encoding", acceptEncoding) | ||||
|  | ||||
| 		var reader io.Reader | ||||
|  | ||||
| 		resp, err := vk.Client.Do(req) | ||||
| 		if err != nil { | ||||
| 			return response, err | ||||
| 		} | ||||
|  | ||||
| 		mediatype, _, _ := mime.ParseMediaType(resp.Header.Get("Content-Type")) | ||||
| 		if mediatype != "application/json" { | ||||
| 			_ = resp.Body.Close() | ||||
| 			return response, &InvalidContentType{mediatype} | ||||
| 		switch resp.Header.Get("Content-Encoding") { | ||||
| 		case "zstd": | ||||
| 			zstdReader, _ := zstd.NewReader(resp.Body) | ||||
| 			defer zstdReader.Close() | ||||
|  | ||||
| 			reader = zstdReader | ||||
| 		case "gzip": | ||||
| 			gzipReader, _ := gzip.NewReader(resp.Body) | ||||
| 			defer gzipReader.Close() | ||||
|  | ||||
| 			reader = gzipReader | ||||
| 		default: | ||||
| 			reader = resp.Body | ||||
| 		} | ||||
|  | ||||
| 		err = json.NewDecoder(resp.Body).Decode(&response) | ||||
| 		if err != nil { | ||||
| 		mediatype, _, _ := mime.ParseMediaType(resp.Header.Get("Content-Type")) | ||||
| 		switch mediatype { | ||||
| 		case "application/json": | ||||
| 			err = json.NewDecoder(reader).Decode(&response) | ||||
| 			if err != nil { | ||||
| 				_ = resp.Body.Close() | ||||
| 				return response, err | ||||
| 			} | ||||
| 		case "application/x-msgpack": | ||||
| 			dec := msgpack.NewDecoder(reader) | ||||
| 			dec.SetCustomStructTag("json") | ||||
|  | ||||
| 			err = dec.Decode(&response) | ||||
| 			if err != nil { | ||||
| 				_ = resp.Body.Close() | ||||
| 				return response, err | ||||
| 			} | ||||
| 		default: | ||||
| 			_ = resp.Body.Close() | ||||
| 			return response, err | ||||
| 			return response, &InvalidContentType{mediatype} | ||||
| 		} | ||||
|  | ||||
| 		_ = resp.Body.Close() | ||||
| @@ -291,6 +332,10 @@ func (vk *VK) Request(method string, sliceParams ...Params) ([]byte, error) { | ||||
|  | ||||
| 	sliceParams = append(sliceParams, reqParams) | ||||
|  | ||||
| 	if vk.msgpack { | ||||
| 		method += ".msgpack" | ||||
| 	} | ||||
|  | ||||
| 	resp, err := vk.Handler(method, sliceParams...) | ||||
|  | ||||
| 	return resp.Response, err | ||||
| @@ -303,7 +348,32 @@ func (vk *VK) RequestUnmarshal(method string, obj interface{}, sliceParams ...Pa | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return json.Unmarshal(rawResponse, &obj) | ||||
| 	if vk.msgpack { | ||||
| 		dec := msgpack.NewDecoder(bytes.NewReader(rawResponse)) | ||||
| 		dec.SetCustomStructTag("json") | ||||
|  | ||||
| 		err = dec.Decode(&obj) | ||||
| 	} else { | ||||
| 		err = json.Unmarshal(rawResponse, &obj) | ||||
| 	} | ||||
|  | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // EnableMessagePack enable using MessagePack instead of JSON. | ||||
| // | ||||
| // THIS IS EXPERIMENTAL FUNCTION! Broken encoding returned in some methods. | ||||
| // | ||||
| // See https://msgpack.org | ||||
| func (vk *VK) EnableMessagePack() { | ||||
| 	vk.msgpack = true | ||||
| } | ||||
|  | ||||
| // EnableZstd enable using zstd instead of gzip. | ||||
| // | ||||
| // This not use dict. | ||||
| func (vk *VK) EnableZstd() { | ||||
| 	vk.zstd = true | ||||
| } | ||||
|  | ||||
| func fmtReflectValue(value reflect.Value, depth int) string { | ||||
|   | ||||
							
								
								
									
										59
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/auth.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										59
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/auth.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,5 +1,9 @@ | ||||
| package api // import "github.com/SevereCloud/vksdk/v2/api" | ||||
|  | ||||
| import ( | ||||
| 	"github.com/SevereCloud/vksdk/v2/object" | ||||
| ) | ||||
|  | ||||
| // AuthCheckPhone checks a user's phone number for correctness. | ||||
| // | ||||
| // https://vk.com/dev/auth.checkPhone | ||||
| @@ -24,3 +28,58 @@ func (vk *VK) AuthRestore(params Params) (response AuthRestoreResponse, err erro | ||||
| 	err = vk.RequestUnmarshal("auth.restore", &response, params) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // AuthGetProfileInfoBySilentTokenResponse struct. | ||||
| type AuthGetProfileInfoBySilentTokenResponse struct { | ||||
| 	Success []object.AuthSilentTokenProfile `json:"success"` | ||||
| 	Errors  []AuthSilentTokenError          `json:"errors"` | ||||
| } | ||||
|  | ||||
| // AuthGetProfileInfoBySilentToken method. | ||||
| // | ||||
| // https://platform.vk.com/?p=DocsDashboard&docs=tokens_silent-token | ||||
| func (vk *VK) AuthGetProfileInfoBySilentToken(params Params) (response AuthGetProfileInfoBySilentTokenResponse, err error) { | ||||
| 	err = vk.RequestUnmarshal("auth.getProfileInfoBySilentToken", &response, params) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // ExchangeSilentTokenSource call conditions exchangeSilentToken. | ||||
| // | ||||
| // 	0	Unknown | ||||
| // 	1	Silent authentication | ||||
| // 	2	Auth by login and password | ||||
| // 	3	Extended registration | ||||
| // 	4	Auth by exchange token | ||||
| // 	5	Auth by exchange token on reset password | ||||
| // 	6	Auth by exchange token on unblock | ||||
| // 	7	Auth by exchange token on reset session | ||||
| // 	8	Auth by exchange token on change password | ||||
| // 	9	Finish phone validation on authentication | ||||
| // 	10	Auth by code | ||||
| // 	11	Auth by external oauth | ||||
| // 	12	Reactivation | ||||
| // 	15	Auth by SDK temporary access-token | ||||
| type ExchangeSilentTokenSource int | ||||
|  | ||||
| // AuthExchangeSilentAuthTokenResponse struct. | ||||
| type AuthExchangeSilentAuthTokenResponse struct { | ||||
| 	AccessToken              string                    `json:"access_token"` | ||||
| 	AccessTokenID            string                    `json:"access_token_id"` | ||||
| 	UserID                   int                       `json:"user_id"` | ||||
| 	Phone                    string                    `json:"phone"` | ||||
| 	PhoneValidated           interface{}               `json:"phone_validated"` | ||||
| 	IsPartial                bool                      `json:"is_partial"` | ||||
| 	IsService                bool                      `json:"is_service"` | ||||
| 	AdditionalSignupRequired bool                      `json:"additional_signup_required"` | ||||
| 	Email                    string                    `json:"email"` | ||||
| 	Source                   ExchangeSilentTokenSource `json:"source"` | ||||
| 	SourceDescription        string                    `json:"source_description"` | ||||
| } | ||||
|  | ||||
| // AuthExchangeSilentAuthToken method. | ||||
| // | ||||
| // https://platform.vk.com/?p=DocsDashboard&docs=tokens_access-token | ||||
| func (vk *VK) AuthExchangeSilentAuthToken(params Params) (response AuthExchangeSilentAuthTokenResponse, err error) { | ||||
| 	err = vk.RequestUnmarshal("auth.exchangeSilentAuthToken", &response, params) | ||||
| 	return | ||||
| } | ||||
|   | ||||
							
								
								
									
										82
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/errors.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										82
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/errors.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -159,6 +159,9 @@ const ( | ||||
| 	ErrRateLimit      ErrorType = 29 | ||||
| 	ErrPrivateProfile ErrorType = 30 // This profile is private | ||||
|  | ||||
| 	// Client version deprecated. | ||||
| 	ErrClientVersionDeprecated ErrorType = 34 | ||||
|  | ||||
| 	// Method execution was interrupted due to timeout. | ||||
| 	ErrExecutionTimeout ErrorType = 36 | ||||
|  | ||||
| @@ -177,6 +180,9 @@ const ( | ||||
| 	// Additional signup required. | ||||
| 	ErrAdditionalSignupRequired ErrorType = 41 | ||||
|  | ||||
| 	// IP is not allowed. | ||||
| 	ErrIPNotAllowed ErrorType = 42 | ||||
|  | ||||
| 	// One of the parameters specified was missing or invalid | ||||
| 	// | ||||
| 	// Check the required parameters list and their format on a method | ||||
| @@ -586,6 +592,18 @@ const ( | ||||
| 	// Can't send message, reply timed out. | ||||
| 	ErrMessagesReplyTimedOut ErrorType = 950 | ||||
|  | ||||
| 	// You can't access donut chat without subscription. | ||||
| 	ErrMessagesAccessDonutChat ErrorType = 962 | ||||
|  | ||||
| 	// This user can't be added to the work chat, as they aren't an employe. | ||||
| 	ErrMessagesAccessWorkChat ErrorType = 967 | ||||
|  | ||||
| 	// Message cannot be forwarded. | ||||
| 	ErrMessagesCantForwarded ErrorType = 969 | ||||
|  | ||||
| 	// Cannot pin an expiring message. | ||||
| 	ErrMessagesPinExpiringMessage ErrorType = 970 | ||||
|  | ||||
| 	// Invalid phone number. | ||||
| 	ErrParamPhone ErrorType = 1000 | ||||
|  | ||||
| @@ -598,6 +616,12 @@ const ( | ||||
| 	// Processing.. Try later. | ||||
| 	ErrAuthDelay ErrorType = 1112 | ||||
|  | ||||
| 	// Anonymous token has expired. | ||||
| 	ErrAnonymousTokenExpired ErrorType = 1114 | ||||
|  | ||||
| 	// Anonymous token is invalid. | ||||
| 	ErrAnonymousTokenInvalid ErrorType = 1116 | ||||
|  | ||||
| 	// Invalid document id. | ||||
| 	ErrParamDocID ErrorType = 1150 | ||||
|  | ||||
| @@ -724,6 +748,9 @@ const ( | ||||
| 	// Market was already disabled in this group. | ||||
| 	ErrMarketAlreadyDisabled ErrorType = 1432 | ||||
|  | ||||
| 	// Main album can not be hidden. | ||||
| 	ErrMainAlbumCantHidden ErrorType = 1446 | ||||
|  | ||||
| 	// Story has already expired. | ||||
| 	ErrStoryExpired ErrorType = 1600 | ||||
|  | ||||
| @@ -783,6 +810,33 @@ const ( | ||||
|  | ||||
| 	// Can't set AliExpress tag to this type of object. | ||||
| 	ErrAliExpressTag ErrorType = 3800 | ||||
|  | ||||
| 	// Invalid upload response. | ||||
| 	ErrInvalidUploadResponse ErrorType = 5701 | ||||
|  | ||||
| 	// Invalid upload hash. | ||||
| 	ErrInvalidUploadHash ErrorType = 5702 | ||||
|  | ||||
| 	// Invalid upload user. | ||||
| 	ErrInvalidUploadUser ErrorType = 5703 | ||||
|  | ||||
| 	// Invalid upload group. | ||||
| 	ErrInvalidUploadGroup ErrorType = 5704 | ||||
|  | ||||
| 	// Invalid crop data. | ||||
| 	ErrInvalidCropData ErrorType = 5705 | ||||
|  | ||||
| 	// To small avatar. | ||||
| 	ErrToSmallAvatar ErrorType = 5706 | ||||
|  | ||||
| 	// Photo not found. | ||||
| 	ErrPhotoNotFound ErrorType = 5708 | ||||
|  | ||||
| 	// Invalid Photo. | ||||
| 	ErrInvalidPhoto ErrorType = 5709 | ||||
|  | ||||
| 	// Invalid hash. | ||||
| 	ErrInvalidHash ErrorType = 5710 | ||||
| ) | ||||
|  | ||||
| // ErrorSubtype is the subtype of an error. | ||||
| @@ -946,3 +1000,31 @@ func (e AdsError) Is(target error) bool { | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // AuthSilentTokenError struct. | ||||
| type AuthSilentTokenError struct { | ||||
| 	Token       string    `json:"token"` | ||||
| 	Code        ErrorType `json:"code"` | ||||
| 	Description string    `json:"description"` | ||||
| } | ||||
|  | ||||
| // Error returns the description of a AuthSilentTokenError. | ||||
| func (e AuthSilentTokenError) Error() string { | ||||
| 	return "api: " + e.Description | ||||
| } | ||||
|  | ||||
| // Is unwraps its first argument sequentially looking for an error that matches | ||||
| // the second. | ||||
| func (e AuthSilentTokenError) Is(target error) bool { | ||||
| 	var tError *AuthSilentTokenError | ||||
| 	if errors.As(target, &tError) { | ||||
| 		return e.Code == tError.Code && e.Description == tError.Description | ||||
| 	} | ||||
|  | ||||
| 	var tErrorType ErrorType | ||||
| 	if errors.As(target, &tErrorType) { | ||||
| 		return e.Code == tErrorType | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|   | ||||
							
								
								
									
										26
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/execute.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										26
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/execute.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,6 +1,11 @@ | ||||
| package api | ||||
|  | ||||
| import "encoding/json" | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
|  | ||||
| 	"github.com/vmihailenco/msgpack/v5" | ||||
| ) | ||||
|  | ||||
| // ExecuteWithArgs a universal method for calling a sequence of other methods | ||||
| // while saving and filtering interim results. | ||||
| @@ -22,10 +27,23 @@ func (vk *VK) ExecuteWithArgs(code string, params Params, obj interface{}) error | ||||
| 	} | ||||
|  | ||||
| 	resp, err := vk.Handler("execute", params, reqParams) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	jsonErr := json.Unmarshal(resp.Response, &obj) | ||||
| 	if jsonErr != nil { | ||||
| 		return jsonErr | ||||
| 	var decoderErr error | ||||
|  | ||||
| 	if vk.msgpack { | ||||
| 		dec := msgpack.NewDecoder(bytes.NewReader(resp.Response)) | ||||
| 		dec.SetCustomStructTag("json") | ||||
|  | ||||
| 		decoderErr = dec.Decode(&obj) | ||||
| 	} else { | ||||
| 		decoderErr = json.Unmarshal(resp.Response, &obj) | ||||
| 	} | ||||
|  | ||||
| 	if decoderErr != nil { | ||||
| 		return decoderErr | ||||
| 	} | ||||
|  | ||||
| 	if resp.ExecuteErrors != nil { | ||||
|   | ||||
							
								
								
									
										17
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/market.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										17
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/market.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -20,6 +20,7 @@ func (vk *VK) MarketAdd(params Params) (response MarketAddResponse, err error) { | ||||
| // MarketAddAlbumResponse struct. | ||||
| type MarketAddAlbumResponse struct { | ||||
| 	MarketAlbumID int `json:"market_album_id"` // Album ID | ||||
| 	AlbumsCount   int `json:"albums_count"` | ||||
| } | ||||
|  | ||||
| // MarketAddAlbum creates new collection of items. | ||||
| @@ -318,3 +319,19 @@ func (vk *VK) MarketSearch(params Params) (response MarketSearchResponse, err er | ||||
| 	err = vk.RequestUnmarshal("market.search", &response, params) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // MarketSearchItemsResponse struct. | ||||
| type MarketSearchItemsResponse struct { | ||||
| 	Count    int                       `json:"count"` | ||||
| 	ViewType int                       `json:"view_type"` | ||||
| 	Items    []object.MarketMarketItem `json:"items"` | ||||
| 	Groups   []object.GroupsGroup      `json:"groups,omitempty"` | ||||
| } | ||||
|  | ||||
| // MarketSearchItems method. | ||||
| // | ||||
| // https://vk.com/dev/market.searchItems | ||||
| func (vk *VK) MarketSearchItems(params Params) (response MarketSearchItemsResponse, err error) { | ||||
| 	err = vk.RequestUnmarshal("market.searchItems", &response, params) | ||||
| 	return | ||||
| } | ||||
|   | ||||
							
								
								
									
										103
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/marusia.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/marusia.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | ||||
| package api // import "github.com/SevereCloud/vksdk/v2/api" | ||||
|  | ||||
| import ( | ||||
| 	"github.com/SevereCloud/vksdk/v2/object" | ||||
| ) | ||||
|  | ||||
| // MarusiaGetPictureUploadLinkResponse struct. | ||||
| type MarusiaGetPictureUploadLinkResponse struct { | ||||
| 	PictureUploadLink string `json:"picture_upload_link"` // Link | ||||
| } | ||||
|  | ||||
| // MarusiaGetPictureUploadLink method. | ||||
| // | ||||
| // https://vk.com/dev/marusia_skill_docs10 | ||||
| func (vk *VK) MarusiaGetPictureUploadLink(params Params) (response MarusiaGetPictureUploadLinkResponse, err error) { | ||||
| 	err = vk.RequestUnmarshal("marusia.getPictureUploadLink", &response, params) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // MarusiaSavePictureResponse struct. | ||||
| type MarusiaSavePictureResponse struct { | ||||
| 	AppID   int `json:"app_id"` | ||||
| 	PhotoID int `json:"photo_id"` | ||||
| } | ||||
|  | ||||
| // MarusiaSavePicture method. | ||||
| // | ||||
| // https://vk.com/dev/marusia_skill_docs10 | ||||
| func (vk *VK) MarusiaSavePicture(params Params) (response MarusiaSavePictureResponse, err error) { | ||||
| 	err = vk.RequestUnmarshal("marusia.savePicture", &response, params) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // MarusiaGetPicturesResponse struct. | ||||
| type MarusiaGetPicturesResponse struct { | ||||
| 	Count int                     `json:"count"` | ||||
| 	Items []object.MarusiaPicture `json:"items"` | ||||
| } | ||||
|  | ||||
| // MarusiaGetPictures method. | ||||
| // | ||||
| // https://vk.com/dev/marusia_skill_docs10 | ||||
| func (vk *VK) MarusiaGetPictures(params Params) (response MarusiaGetPicturesResponse, err error) { | ||||
| 	err = vk.RequestUnmarshal("marusia.getPictures", &response, params) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // MarusiaDeletePicture delete picture. | ||||
| // | ||||
| // https://vk.com/dev/marusia_skill_docs10 | ||||
| func (vk *VK) MarusiaDeletePicture(params Params) (response int, err error) { | ||||
| 	err = vk.RequestUnmarshal("marusia.deletePicture", &response, params) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // MarusiaGetAudioUploadLinkResponse struct. | ||||
| type MarusiaGetAudioUploadLinkResponse struct { | ||||
| 	AudioUploadLink string `json:"audio_upload_link"` // Link | ||||
| } | ||||
|  | ||||
| // MarusiaGetAudioUploadLink method. | ||||
| // | ||||
| // https://vk.com/dev/marusia_skill_docs10 | ||||
| func (vk *VK) MarusiaGetAudioUploadLink(params Params) (response MarusiaGetAudioUploadLinkResponse, err error) { | ||||
| 	err = vk.RequestUnmarshal("marusia.getAudioUploadLink", &response, params) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // MarusiaCreateAudioResponse struct. | ||||
| type MarusiaCreateAudioResponse struct { | ||||
| 	ID    int    `json:"id"` | ||||
| 	Title string `json:"title"` | ||||
| } | ||||
|  | ||||
| // MarusiaCreateAudio method. | ||||
| // | ||||
| // https://vk.com/dev/marusia_skill_docs10 | ||||
| func (vk *VK) MarusiaCreateAudio(params Params) (response MarusiaCreateAudioResponse, err error) { | ||||
| 	err = vk.RequestUnmarshal("marusia.createAudio", &response, params) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // MarusiaGetAudiosResponse struct. | ||||
| type MarusiaGetAudiosResponse struct { | ||||
| 	Count  int                   `json:"count"` | ||||
| 	Audios []object.MarusiaAudio `json:"audios"` | ||||
| } | ||||
|  | ||||
| // MarusiaGetAudios method. | ||||
| // | ||||
| // https://vk.com/dev/marusia_skill_docs10 | ||||
| func (vk *VK) MarusiaGetAudios(params Params) (response MarusiaGetAudiosResponse, err error) { | ||||
| 	err = vk.RequestUnmarshal("marusia.getAudios", &response, params) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // MarusiaDeleteAudio delete audio. | ||||
| // | ||||
| // https://vk.com/dev/marusia_skill_docs10 | ||||
| func (vk *VK) MarusiaDeleteAudio(params Params) (response int, err error) { | ||||
| 	err = vk.RequestUnmarshal("marusia.deleteAudio", &response, params) | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										26
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/messages.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										26
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/messages.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,7 +1,10 @@ | ||||
| package api // import "github.com/SevereCloud/vksdk/v2/api" | ||||
|  | ||||
| import ( | ||||
| 	"strconv" | ||||
|  | ||||
| 	"github.com/SevereCloud/vksdk/v2/object" | ||||
| 	"github.com/vmihailenco/msgpack/v5" | ||||
| ) | ||||
|  | ||||
| // MessagesAddChatUser adds a new user to a chat. | ||||
| @@ -31,11 +34,34 @@ func (vk *VK) MessagesCreateChat(params Params) (response int, err error) { | ||||
| // MessagesDeleteResponse struct. | ||||
| type MessagesDeleteResponse map[string]int | ||||
|  | ||||
| // DecodeMsgpack funcion. | ||||
| func (resp *MessagesDeleteResponse) DecodeMsgpack(dec *msgpack.Decoder) error { | ||||
| 	data, err := dec.DecodeRaw() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	var respMap map[int]int | ||||
|  | ||||
| 	err = msgpack.Unmarshal(data, &respMap) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	*resp = make(MessagesDeleteResponse) | ||||
| 	for key, val := range respMap { | ||||
| 		(*resp)[strconv.Itoa(key)] = val | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // MessagesDelete deletes one or more messages. | ||||
| // | ||||
| // https://vk.com/dev/messages.delete | ||||
| func (vk *VK) MessagesDelete(params Params) (response MessagesDeleteResponse, err error) { | ||||
| 	err = vk.RequestUnmarshal("messages.delete", &response, params) | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										13
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/photos.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/photos.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -571,12 +571,13 @@ func (vk *VK) PhotosSaveOwnerCoverPhoto(params Params) (response PhotosSaveOwner | ||||
|  | ||||
| // PhotosSaveOwnerPhotoResponse struct. | ||||
| type PhotosSaveOwnerPhotoResponse struct { | ||||
| 	PhotoHash     string `json:"photo_hash"` | ||||
| 	PhotoSrc      string `json:"photo_src"` | ||||
| 	PhotoSrcBig   string `json:"photo_src_big"` | ||||
| 	PhotoSrcSmall string `json:"photo_src_small"` | ||||
| 	Saved         int    `json:"saved"` | ||||
| 	PostID        int    `json:"post_id"` | ||||
| 	PhotoHash string `json:"photo_hash"` | ||||
| 	// BUG(VK): returns false | ||||
| 	// PhotoSrc      string `json:"photo_src"` | ||||
| 	// PhotoSrcBig   string `json:"photo_src_big"` | ||||
| 	// PhotoSrcSmall string `json:"photo_src_small"` | ||||
| 	Saved  int `json:"saved"` | ||||
| 	PostID int `json:"post_id"` | ||||
| } | ||||
|  | ||||
| // PhotosSaveOwnerPhoto saves a profile or community photo. | ||||
|   | ||||
							
								
								
									
										2
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/store.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/store.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,4 +1,4 @@ | ||||
| package api // import "github.com/SevereCloud/vksdk/api" | ||||
| package api // import "github.com/SevereCloud/vksdk/v2/api" | ||||
|  | ||||
| import ( | ||||
| 	"github.com/SevereCloud/vksdk/v2/object" | ||||
|   | ||||
							
								
								
									
										54
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/upload.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										54
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/upload.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -959,3 +959,57 @@ func (vk *VK) UploadGroupImage(imageType string, file io.Reader) (response objec | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // UploadMarusiaPicture uploading picture. | ||||
| // | ||||
| // Limits: height not more than 600 px, | ||||
| // aspect ratio of at least 2:1. | ||||
| func (vk *VK) UploadMarusiaPicture(file io.Reader) (response MarusiaSavePictureResponse, err error) { | ||||
| 	uploadServer, err := vk.MarusiaGetPictureUploadLink(nil) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	bodyContent, err := vk.UploadFile(uploadServer.PictureUploadLink, file, "photo", "photo.jpg") | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	var handler object.MarusiaPictureUploadResponse | ||||
|  | ||||
| 	err = json.Unmarshal(bodyContent, &handler) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	photo, _ := json.Marshal(handler.Photo) | ||||
|  | ||||
| 	response, err = vk.MarusiaSavePicture(Params{ | ||||
| 		"server": handler.Server, | ||||
| 		"photo":  string(photo), | ||||
| 		"hash":   handler.Hash, | ||||
| 	}) | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // UploadMarusiaAudio uploading audio. | ||||
| // | ||||
| // https://vk.com/dev/marusia_skill_docs10 | ||||
| func (vk *VK) UploadMarusiaAudio(file io.Reader) (response MarusiaCreateAudioResponse, err error) { | ||||
| 	uploadServer, err := vk.MarusiaGetAudioUploadLink(nil) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	bodyContent, err := vk.UploadFile(uploadServer.AudioUploadLink, file, "file", "audio.mp3") | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	response, err = vk.MarusiaCreateAudio(Params{ | ||||
| 		"audio_meta": string(bodyContent), | ||||
| 	}) | ||||
|  | ||||
| 	return | ||||
| } | ||||
|   | ||||
							
								
								
									
										36
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/utils.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										36
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/utils.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,9 +1,8 @@ | ||||
| package api // import "github.com/SevereCloud/vksdk/v2/api" | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
|  | ||||
| 	"github.com/SevereCloud/vksdk/v2/object" | ||||
| 	"github.com/vmihailenco/msgpack/v5" | ||||
| ) | ||||
|  | ||||
| // UtilsCheckLinkResponse struct. | ||||
| @@ -89,17 +88,34 @@ func (vk *VK) UtilsGetShortLink(params Params) (response UtilsGetShortLinkRespon | ||||
| // UtilsResolveScreenNameResponse struct. | ||||
| type UtilsResolveScreenNameResponse object.UtilsDomainResolved | ||||
|  | ||||
| // UnmarshalJSON UtilsResolveScreenNameResponse. | ||||
| // | ||||
| // BUG(VK): UtilsResolveScreenNameResponse return []. | ||||
| func (resp *UtilsResolveScreenNameResponse) UnmarshalJSON(data []byte) error { | ||||
| 	var p object.UtilsDomainResolved | ||||
| 	err := p.UnmarshalJSON(data) | ||||
|  | ||||
| 	*resp = UtilsResolveScreenNameResponse(p) | ||||
|  | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // DecodeMsgpack UtilsResolveScreenNameResponse. | ||||
| // | ||||
| // BUG(VK): UtilsResolveScreenNameResponse return []. | ||||
| func (resp *UtilsResolveScreenNameResponse) DecodeMsgpack(dec *msgpack.Decoder) error { | ||||
| 	var p object.UtilsDomainResolved | ||||
| 	err := p.DecodeMsgpack(dec) | ||||
|  | ||||
| 	*resp = UtilsResolveScreenNameResponse(p) | ||||
|  | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // UtilsResolveScreenName detects a type of object (e.g., user, community, application) and its ID by screen name. | ||||
| // | ||||
| // https://vk.com/dev/utils.resolveScreenName | ||||
| func (vk *VK) UtilsResolveScreenName(params Params) (response UtilsResolveScreenNameResponse, err error) { | ||||
| 	rawResponse, err := vk.Request("utils.resolveScreenName", params) | ||||
| 	// Если короткое имя screen_name не занято, то будет возвращён пустой объект. | ||||
| 	if err != nil || string(rawResponse) == "[]" { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	err = json.Unmarshal(rawResponse, &response) | ||||
|  | ||||
| 	err = vk.RequestUnmarshal("utils.resolveScreenName", &response, params) | ||||
| 	return | ||||
| } | ||||
|   | ||||
							
								
								
									
										35
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/video.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										35
									
								
								vendor/github.com/SevereCloud/vksdk/v2/api/video.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -235,6 +235,17 @@ func (vk *VK) VideoGetCommentsExtended(params Params) (response VideoGetComments | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // VideoLiveGetCategoriesResponse struct. | ||||
| type VideoLiveGetCategoriesResponse []object.VideoLiveCategory | ||||
|  | ||||
| // VideoLiveGetCategories method. | ||||
| // | ||||
| // https://vk.com/dev/video.liveGetCategories | ||||
| func (vk *VK) VideoLiveGetCategories(params Params) (response VideoLiveGetCategoriesResponse, err error) { | ||||
| 	err = vk.RequestUnmarshal("video.liveGetCategories", &response, params) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // VideoRemoveFromAlbum allows you to remove the video from the album. | ||||
| // | ||||
| // https://vk.com/dev/video.removeFromAlbum | ||||
| @@ -336,3 +347,27 @@ func (vk *VK) VideoSearchExtended(params Params) (response VideoSearchExtendedRe | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // VideoStartStreamingResponse struct. | ||||
| type VideoStartStreamingResponse object.VideoLive | ||||
|  | ||||
| // VideoStartStreaming method. | ||||
| // | ||||
| // https://vk.com/dev/video.startStreaming | ||||
| func (vk *VK) VideoStartStreaming(params Params) (response VideoStartStreamingResponse, err error) { | ||||
| 	err = vk.RequestUnmarshal("video.startStreaming", &response, params) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // VideoStopStreamingResponse struct. | ||||
| type VideoStopStreamingResponse struct { | ||||
| 	UniqueViewers int `json:"unique_viewers"` | ||||
| } | ||||
|  | ||||
| // VideoStopStreaming method. | ||||
| // | ||||
| // https://vk.com/dev/video.stopStreaming | ||||
| func (vk *VK) VideoStopStreaming(params Params) (response VideoStopStreamingResponse, err error) { | ||||
| 	err = vk.RequestUnmarshal("video.stopStreaming", &response, params) | ||||
| 	return | ||||
| } | ||||
|   | ||||
							
								
								
									
										2
									
								
								vendor/github.com/SevereCloud/vksdk/v2/doc.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/SevereCloud/vksdk/v2/doc.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -7,6 +7,6 @@ package vksdk | ||||
|  | ||||
| // Module constants. | ||||
| const ( | ||||
| 	Version = "2.10.0" | ||||
| 	Version = "2.14.1" | ||||
| 	API     = "5.131" | ||||
| ) | ||||
|   | ||||
							
								
								
									
										5
									
								
								vendor/github.com/SevereCloud/vksdk/v2/events/context.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								vendor/github.com/SevereCloud/vksdk/v2/events/context.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -15,3 +15,8 @@ func GroupIDFromContext(ctx context.Context) int { | ||||
| func EventIDFromContext(ctx context.Context) string { | ||||
| 	return ctx.Value(internal.EventIDKey).(string) | ||||
| } | ||||
|  | ||||
| // VersionFromContext returns the version from context. | ||||
| func VersionFromContext(ctx context.Context) string { | ||||
| 	return ctx.Value(internal.EventVersionKey).(string) | ||||
| } | ||||
|   | ||||
							
								
								
									
										2
									
								
								vendor/github.com/SevereCloud/vksdk/v2/events/events.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/SevereCloud/vksdk/v2/events/events.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -81,6 +81,7 @@ type GroupEvent struct { | ||||
| 	Object  json.RawMessage `json:"object"` | ||||
| 	GroupID int             `json:"group_id"` | ||||
| 	EventID string          `json:"event_id"` | ||||
| 	V       string          `json:"v"` | ||||
| 	Secret  string          `json:"secret"` | ||||
| } | ||||
|  | ||||
| @@ -158,6 +159,7 @@ func NewFuncList() *FuncList { | ||||
| func (fl FuncList) Handler(ctx context.Context, e GroupEvent) error { // nolint:gocyclo | ||||
| 	ctx = context.WithValue(ctx, internal.GroupIDKey, e.GroupID) | ||||
| 	ctx = context.WithValue(ctx, internal.EventIDKey, e.EventID) | ||||
| 	ctx = context.WithValue(ctx, internal.EventVersionKey, e.V) | ||||
|  | ||||
| 	if sliceFunc, ok := fl.special[e.Type]; ok { | ||||
| 		for _, f := range sliceFunc { | ||||
|   | ||||
							
								
								
									
										1
									
								
								vendor/github.com/SevereCloud/vksdk/v2/internal/transport.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								vendor/github.com/SevereCloud/vksdk/v2/internal/transport.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -28,6 +28,7 @@ const ( | ||||
| 	CallbackRetryCounterKey | ||||
| 	CallbackRetryAfterKey | ||||
| 	CallbackRemove | ||||
| 	EventVersionKey | ||||
| ) | ||||
|  | ||||
| // ContextClient return *http.Client. | ||||
|   | ||||
							
								
								
									
										2
									
								
								vendor/github.com/SevereCloud/vksdk/v2/longpoll-bot/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/SevereCloud/vksdk/v2/longpoll-bot/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -10,7 +10,7 @@ Long Poll настраивается автоматически. Вам не т | ||||
|  | ||||
| ### Версия API | ||||
|  | ||||
| Данная библиотека поддерживает версию API **5.122**. | ||||
| Данная библиотека поддерживает версию API **5.131**. | ||||
|  | ||||
| ### Инициализация | ||||
|  | ||||
|   | ||||
							
								
								
									
										58
									
								
								vendor/github.com/SevereCloud/vksdk/v2/longpoll-bot/longpoll.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										58
									
								
								vendor/github.com/SevereCloud/vksdk/v2/longpoll-bot/longpoll.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -8,8 +8,11 @@ package longpoll // import "github.com/SevereCloud/vksdk/v2/longpoll-bot" | ||||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
|  | ||||
| 	"github.com/SevereCloud/vksdk/v2" | ||||
| 	"github.com/SevereCloud/vksdk/v2/api" | ||||
| @@ -117,7 +120,7 @@ func (lp *LongPoll) check(ctx context.Context) (response Response, err error) { | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	err = json.NewDecoder(resp.Body).Decode(&response) | ||||
| 	response, err = parseResponse(resp.Body) | ||||
| 	if err != nil { | ||||
| 		return response, err | ||||
| 	} | ||||
| @@ -127,6 +130,59 @@ func (lp *LongPoll) check(ctx context.Context) (response Response, err error) { | ||||
| 	return response, err | ||||
| } | ||||
|  | ||||
| func parseResponse(reader io.Reader) (response Response, err error) { | ||||
| 	decoder := json.NewDecoder(reader) | ||||
| 	for decoder.More() { | ||||
| 		token, err := decoder.Token() | ||||
| 		if err != nil { | ||||
| 			if errors.Is(err, io.EOF) { | ||||
| 				break | ||||
| 			} | ||||
|  | ||||
| 			return response, err | ||||
| 		} | ||||
|  | ||||
| 		t, ok := token.(string) | ||||
| 		if !ok { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		switch t { | ||||
| 		case "failed": | ||||
| 			raw, err := decoder.Token() | ||||
| 			if err != nil { | ||||
| 				return response, err | ||||
| 			} | ||||
|  | ||||
| 			response.Failed = int(raw.(float64)) | ||||
| 		case "updates": | ||||
| 			var updates []events.GroupEvent | ||||
|  | ||||
| 			err = decoder.Decode(&updates) | ||||
| 			if err != nil { | ||||
| 				return response, err | ||||
| 			} | ||||
|  | ||||
| 			response.Updates = updates | ||||
| 		case "ts": | ||||
| 			// can be a number in the response with "failed" field: {"ts":8,"failed":1} | ||||
| 			// or string, e.g. {"ts":"8","updates":[]} | ||||
| 			rawTs, err := decoder.Token() | ||||
| 			if err != nil { | ||||
| 				return response, err | ||||
| 			} | ||||
|  | ||||
| 			if ts, isNumber := rawTs.(float64); isNumber { | ||||
| 				response.Ts = strconv.Itoa(int(ts)) | ||||
| 			} else { | ||||
| 				response.Ts = rawTs.(string) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return response, err | ||||
| } | ||||
|  | ||||
| func (lp *LongPoll) checkResponse(response Response) (err error) { | ||||
| 	switch response.Failed { | ||||
| 	case 0: | ||||
|   | ||||
							
								
								
									
										35
									
								
								vendor/github.com/SevereCloud/vksdk/v2/object/account.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										35
									
								
								vendor/github.com/SevereCloud/vksdk/v2/object/account.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -62,26 +62,28 @@ type AccountOffer struct { | ||||
|  | ||||
| // AccountAccountCounters struct. | ||||
| type AccountAccountCounters struct { | ||||
| 	AppRequests            int `json:"app_requests"`            // New app requests number | ||||
| 	Events                 int `json:"events"`                  // New events number | ||||
| 	Friends                int `json:"friends"`                 // New friends requests number | ||||
| 	FriendsRecommendations int `json:"friends_recommendations"` // New friends recommendations number | ||||
| 	FriendsSuggestions     int `json:"friends_suggestions"`     // New friends suggestions number | ||||
| 	Gifts                  int `json:"gifts"`                   // New gifts number | ||||
| 	Groups                 int `json:"groups"`                  // New groups number | ||||
| 	Messages               int `json:"messages"`                // New messages number | ||||
| 	Notifications          int `json:"notifications"`           // New notifications number | ||||
| 	Photos                 int `json:"photos"`                  // New photo tags number | ||||
| 	SDK                    int `json:"sdk"`                     // New SDK number | ||||
| 	MenuDiscoverBadge      int `json:"menu_discover_badge"`     // New menu discover badge number | ||||
| 	MenuClipsBadge         int `json:"menu_clips_badge"`        // New menu clips badge number | ||||
| 	Videos                 int `json:"videos"`                  // New video tags number | ||||
| 	Faves                  int `json:"faves"`                   // New faves number | ||||
| 	AppRequests              int `json:"app_requests"`            // New app requests number | ||||
| 	Events                   int `json:"events"`                  // New events number | ||||
| 	Friends                  int `json:"friends"`                 // New friends requests number | ||||
| 	FriendsRecommendations   int `json:"friends_recommendations"` // New friends recommendations number | ||||
| 	FriendsSuggestions       int `json:"friends_suggestions"`     // New friends suggestions number | ||||
| 	Gifts                    int `json:"gifts"`                   // New gifts number | ||||
| 	Groups                   int `json:"groups"`                  // New groups number | ||||
| 	Messages                 int `json:"messages"`                // New messages number | ||||
| 	Notifications            int `json:"notifications"`           // New notifications number | ||||
| 	Photos                   int `json:"photos"`                  // New photo tags number | ||||
| 	SDK                      int `json:"sdk"`                     // New SDK number | ||||
| 	MenuDiscoverBadge        int `json:"menu_discover_badge"`     // New menu discover badge number | ||||
| 	MenuClipsBadge           int `json:"menu_clips_badge"`        // New menu clips badge number | ||||
| 	Videos                   int `json:"videos"`                  // New video tags number | ||||
| 	Faves                    int `json:"faves"`                   // New faves number | ||||
| 	Calls                    int `json:"calls"`                   // New calls number | ||||
| 	MenuSuperappFriendsBadge int `json:"menu_superapp_friends_badge"` | ||||
| 	MenuNewClipsBadge        int `json:"menu_new_clips_badge"` | ||||
| } | ||||
|  | ||||
| // AccountInfo struct. | ||||
| type AccountInfo struct { | ||||
|  | ||||
| 	// Country code. | ||||
| 	Country string `json:"country"` | ||||
|  | ||||
| @@ -107,6 +109,7 @@ type AccountInfo struct { | ||||
| 	IsLiveStreamingEnabled    BaseBoolInt       `json:"is_live_streaming_enabled"` | ||||
| 	IsNewLiveStreamingEnabled BaseBoolInt       `json:"is_new_live_streaming_enabled"` | ||||
| 	LinkRedirects             map[string]string `json:"link_redirects"` | ||||
| 	VkPayEndpointV2           string            `json:"vk_pay_endpoint_v2"` | ||||
| } | ||||
|  | ||||
| // AccountPushSettings struct. | ||||
|   | ||||
							
								
								
									
										20
									
								
								vendor/github.com/SevereCloud/vksdk/v2/object/ads.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								vendor/github.com/SevereCloud/vksdk/v2/object/ads.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -8,12 +8,13 @@ type AdsAccesses struct { | ||||
|  | ||||
| // AdsAccount struct. | ||||
| type AdsAccount struct { | ||||
| 	AccessRole    string      `json:"access_role"` | ||||
| 	AccountID     int         `json:"account_id"` // Account ID | ||||
| 	AccountName   string      `json:"account_name"` | ||||
| 	AccountStatus BaseBoolInt `json:"account_status"` // Information whether account is active | ||||
| 	CanViewBudget BaseBoolInt `json:"can_view_budget"` | ||||
| 	AccountType   string      `json:"account_type"` | ||||
| 	AccessRole                  string      `json:"access_role"` | ||||
| 	AccountID                   int         `json:"account_id"` // Account ID | ||||
| 	AccountName                 string      `json:"account_name"` | ||||
| 	AccountStatus               BaseBoolInt `json:"account_status"` // Information whether account is active | ||||
| 	CanViewBudget               BaseBoolInt `json:"can_view_budget"` | ||||
| 	AdNetworkAllowedPotentially BaseBoolInt `json:"ad_network_allowed_potentially"` | ||||
| 	AccountType                 string      `json:"account_type"` | ||||
| } | ||||
|  | ||||
| // AdsAdLayout struct. | ||||
| @@ -318,3 +319,10 @@ type AdsPromotedPostReach struct { | ||||
| 	VideoViews75p    int `json:"video_views_75p"`   // Video views for 75 percent | ||||
| 	VideoViewsStart  int `json:"video_views_start"` // Video starts | ||||
| } | ||||
|  | ||||
| // AdsMusician struct. | ||||
| type AdsMusician struct { | ||||
| 	ID     int    `json:"id"`               // Targeting music artist ID | ||||
| 	Name   string `json:"name"`             // Music artist name | ||||
| 	Avatar string `json:"avatar,omitempty"` // Music artist photo. | ||||
| } | ||||
|   | ||||
							
								
								
									
										3
									
								
								vendor/github.com/SevereCloud/vksdk/v2/object/apps.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/SevereCloud/vksdk/v2/object/apps.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -60,6 +60,7 @@ type AppsApp struct { | ||||
| 	IsNew           BaseBoolInt `json:"is_new"` | ||||
| 	New             BaseBoolInt `json:"new"` | ||||
| 	IsInstalled     BaseBoolInt `json:"is_installed"` | ||||
| 	HasVkConnect    BaseBoolInt `json:"has_vk_connect"` | ||||
| 	LeaderboardType int         `json:"leaderboard_type"` | ||||
| 	MembersCount    int         `json:"members_count"` // Members number | ||||
| 	PlatformID      int         `json:"platform_id"`   // Application ID in store | ||||
| @@ -78,7 +79,7 @@ type AppsApp struct { | ||||
|  | ||||
| 	// mobile_controls_type = 0 - прозрачный элемент управления поверх области с игрой; | ||||
| 	// mobile_controls_type = 1 - чёрная полоска над областью с игрой; | ||||
| 	// mobile_controls_type = 2 - только для vk apps, без контроллов. | ||||
| 	// mobile_controls_type = 2 - только для vk apps, без элементов управления'. | ||||
| 	MobileControlsType int `json:"mobile_controls_type"` | ||||
|  | ||||
| 	// mobile_view_support_type = 0 - игра не использует нижнюю часть экрана на iPhoneX, черная полоса есть. | ||||
|   | ||||
							
								
								
									
										17
									
								
								vendor/github.com/SevereCloud/vksdk/v2/object/auth.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								vendor/github.com/SevereCloud/vksdk/v2/object/auth.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| package object // import "github.com/SevereCloud/vksdk/v2/object" | ||||
|  | ||||
| // AuthSilentTokenProfile struct. | ||||
| type AuthSilentTokenProfile struct { | ||||
| 	Token          string      `json:"token"` | ||||
| 	Expires        int         `json:"expires"` | ||||
| 	FirstName      string      `json:"first_name"` | ||||
| 	LastName       string      `json:"last_name"` | ||||
| 	Photo50        string      `json:"photo_50"` | ||||
| 	Photo100       string      `json:"photo_100"` | ||||
| 	Photo200       string      `json:"photo_200"` | ||||
| 	Phone          string      `json:"phone"` | ||||
| 	PhoneValidated interface{} `json:"phone_validated"` // int | bool | ||||
| 	UserID         int         `json:"user_id"` | ||||
| 	IsPartial      BaseBoolInt `json:"is_partial"` | ||||
| 	IsService      BaseBoolInt `json:"is_service"` | ||||
| } | ||||
							
								
								
									
										6
									
								
								vendor/github.com/SevereCloud/vksdk/v2/object/database.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								vendor/github.com/SevereCloud/vksdk/v2/object/database.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -4,9 +4,9 @@ package object // import "github.com/SevereCloud/vksdk/v2/object" | ||||
| type DatabaseCity struct { | ||||
| 	ID        int         `json:"id"`    // City ID | ||||
| 	Title     string      `json:"title"` // City title | ||||
| 	Area      string      `json:"area"` | ||||
| 	Region    string      `json:"region"` | ||||
| 	Important BaseBoolInt `json:"important"` | ||||
| 	Area      string      `json:"area,omitempty"` | ||||
| 	Region    string      `json:"region,omitempty"` | ||||
| 	Important BaseBoolInt `json:"important,omitempty"` | ||||
| } | ||||
|  | ||||
| // DatabaseMetroStation  struct. | ||||
|   | ||||
							
								
								
									
										333
									
								
								vendor/github.com/SevereCloud/vksdk/v2/object/groups.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										333
									
								
								vendor/github.com/SevereCloud/vksdk/v2/object/groups.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,9 +1,13 @@ | ||||
| package object // import "github.com/SevereCloud/vksdk/v2/object" | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
|  | ||||
| 	"github.com/vmihailenco/msgpack/v5" | ||||
| 	"github.com/vmihailenco/msgpack/v5/msgpcode" | ||||
| ) | ||||
|  | ||||
| // GroupsAddress WorkInfoStatus of information about timetable. | ||||
| @@ -110,112 +114,113 @@ const ( | ||||
|  | ||||
| // GroupsGroup struct. | ||||
| type GroupsGroup struct { | ||||
| 	AdminLevel   int              `json:"admin_level"` | ||||
| 	Deactivated  string           `json:"deactivated"` // Information whether community is banned | ||||
| 	FinishDate   int              `json:"finish_date"` // Finish date in Unixtime format | ||||
| 	ID           int              `json:"id"`          // Community ID | ||||
| 	Name         string           `json:"name"`        // Community name | ||||
| 	Photo100     string           `json:"photo_100"`   // URL of square photo of the community with 100 pixels in width | ||||
| 	Photo200     string           `json:"photo_200"`   // URL of square photo of the community with 200 pixels in width | ||||
| 	Photo50      string           `json:"photo_50"`    // URL of square photo of the community with 50 pixels in width | ||||
| 	ScreenName   string           `json:"screen_name"` // Domain of the community page | ||||
| 	StartDate    int              `json:"start_date"`  // Start date in Unixtime format | ||||
| 	Type         string           `json:"type"` | ||||
| 	Market       GroupsMarketInfo `json:"market"` | ||||
| 	MemberStatus int              `json:"member_status"` // Current user's member status | ||||
| 	IsClosed     int              `json:"is_closed"` | ||||
| 	City         BaseObject       `json:"city"` | ||||
| 	Country      BaseCountry      `json:"country"` | ||||
| 	AdminLevel   int              `json:"admin_level,omitempty"` | ||||
| 	Deactivated  string           `json:"deactivated,omitempty"` // Information whether community is banned | ||||
| 	FinishDate   int              `json:"finish_date,omitempty"` // Finish date in Unixtime format | ||||
| 	Photo100     string           `json:"photo_100,omitempty"`   // URL of square photo of the community with 100 pixels in width | ||||
| 	Photo200     string           `json:"photo_200,omitempty"`   // URL of square photo of the community with 200 pixels in width | ||||
| 	Photo50      string           `json:"photo_50,omitempty"`    // URL of square photo of the community with 50 pixels in width | ||||
| 	StartDate    int              `json:"start_date,omitempty"`  // Start date in Unixtime format | ||||
| 	Market       GroupsMarketInfo `json:"market,omitempty"` | ||||
| 	MemberStatus int              `json:"member_status,omitempty"` // Current user's member status | ||||
| 	City         BaseObject       `json:"city,omitempty"` | ||||
| 	Country      BaseCountry      `json:"country,omitempty"` | ||||
|  | ||||
| 	// Information whether current user is administrator. | ||||
| 	IsAdmin BaseBoolInt `json:"is_admin"` | ||||
|  | ||||
| 	// Information whether current user is advertiser. | ||||
| 	IsAdvertiser BaseBoolInt `json:"is_advertiser"` | ||||
| 	IsAdvertiser BaseBoolInt `json:"is_advertiser,omitempty"` | ||||
|  | ||||
| 	// Information whether current user is member. | ||||
| 	IsMember BaseBoolInt `json:"is_member"` | ||||
| 	IsMember BaseBoolInt `json:"is_member,omitempty"` | ||||
|  | ||||
| 	// Information whether community is in faves. | ||||
| 	IsFavorite BaseBoolInt `json:"is_favorite"` | ||||
| 	IsFavorite BaseBoolInt `json:"is_favorite,omitempty"` | ||||
|  | ||||
| 	// Information whether community is adult. | ||||
| 	IsAdult BaseBoolInt `json:"is_adult"` | ||||
| 	IsAdult BaseBoolInt `json:"is_adult,omitempty"` | ||||
|  | ||||
| 	// Information whether current user is subscribed. | ||||
| 	IsSubscribed BaseBoolInt `json:"is_subscribed"` | ||||
| 	IsSubscribed BaseBoolInt `json:"is_subscribed,omitempty"` | ||||
|  | ||||
| 	// Information whether current user can post on community's wall. | ||||
| 	CanPost BaseBoolInt `json:"can_post"` | ||||
| 	CanPost BaseBoolInt `json:"can_post,omitempty"` | ||||
|  | ||||
| 	// Information whether current user can see all posts on community's wall. | ||||
| 	CanSeeAllPosts BaseBoolInt `json:"can_see_all_posts"` | ||||
| 	CanSeeAllPosts BaseBoolInt `json:"can_see_all_posts,omitempty"` | ||||
|  | ||||
| 	// Information whether current user can create topic. | ||||
| 	CanCreateTopic BaseBoolInt `json:"can_create_topic"` | ||||
| 	CanCreateTopic BaseBoolInt `json:"can_create_topic,omitempty"` | ||||
|  | ||||
| 	// Information whether current user can upload video. | ||||
| 	CanUploadVideo BaseBoolInt `json:"can_upload_video"` | ||||
| 	CanUploadVideo BaseBoolInt `json:"can_upload_video,omitempty"` | ||||
|  | ||||
| 	// Information whether current user can upload doc. | ||||
| 	CanUploadDoc BaseBoolInt `json:"can_upload_doc"` | ||||
| 	CanUploadDoc BaseBoolInt `json:"can_upload_doc,omitempty"` | ||||
|  | ||||
| 	// Information whether community has photo. | ||||
| 	HasPhoto BaseBoolInt `json:"has_photo"` | ||||
| 	HasPhoto BaseBoolInt `json:"has_photo,omitempty"` | ||||
|  | ||||
| 	// Information whether current user can send a message to community. | ||||
| 	CanMessage BaseBoolInt `json:"can_message"` | ||||
| 	CanMessage BaseBoolInt `json:"can_message,omitempty"` | ||||
|  | ||||
| 	// Information whether community can send a message to current user. | ||||
| 	IsMessagesBlocked BaseBoolInt `json:"is_messages_blocked"` | ||||
| 	IsMessagesBlocked BaseBoolInt `json:"is_messages_blocked,omitempty"` | ||||
|  | ||||
| 	// Information whether community can send notifications by phone number to current user. | ||||
| 	CanSendNotify BaseBoolInt `json:"can_send_notify"` | ||||
| 	CanSendNotify BaseBoolInt `json:"can_send_notify,omitempty"` | ||||
|  | ||||
| 	// Information whether current user is subscribed to podcasts. | ||||
| 	IsSubscribedPodcasts BaseBoolInt `json:"is_subscribed_podcasts"` | ||||
| 	IsSubscribedPodcasts BaseBoolInt `json:"is_subscribed_podcasts,omitempty"` | ||||
|  | ||||
| 	// Owner in whitelist or not. | ||||
| 	CanSubscribePodcasts BaseBoolInt `json:"can_subscribe_podcasts"` | ||||
| 	CanSubscribePodcasts BaseBoolInt `json:"can_subscribe_podcasts,omitempty"` | ||||
|  | ||||
| 	// Can subscribe to wall. | ||||
| 	CanSubscribePosts BaseBoolInt `json:"can_subscribe_posts"` | ||||
| 	CanSubscribePosts BaseBoolInt `json:"can_subscribe_posts,omitempty"` | ||||
|  | ||||
| 	// Information whether community has market app. | ||||
| 	HasMarketApp        BaseBoolInt `json:"has_market_app"` | ||||
| 	IsHiddenFromFeed    BaseBoolInt `json:"is_hidden_from_feed"` | ||||
| 	IsMarketCartEnabled BaseBoolInt `json:"is_market_cart_enabled"` | ||||
| 	Verified            BaseBoolInt `json:"verified"` // Information whether community is verified | ||||
| 	HasMarketApp        BaseBoolInt `json:"has_market_app,omitempty"` | ||||
| 	IsHiddenFromFeed    BaseBoolInt `json:"is_hidden_from_feed,omitempty"` | ||||
| 	IsMarketCartEnabled BaseBoolInt `json:"is_market_cart_enabled,omitempty"` | ||||
| 	Verified            BaseBoolInt `json:"verified,omitempty"` // Information whether community is verified | ||||
|  | ||||
| 	// Information whether the community has a fire pictogram. | ||||
| 	Trending     BaseBoolInt         `json:"trending"` | ||||
| 	Description  string              `json:"description"`   // Community description | ||||
| 	WikiPage     string              `json:"wiki_page"`     // Community's main wiki page title | ||||
| 	MembersCount int                 `json:"members_count"` // Community members number | ||||
| 	Counters     GroupsCountersGroup `json:"counters"` | ||||
| 	Cover        GroupsCover         `json:"cover"` | ||||
| 	Trending     BaseBoolInt         `json:"trending,omitempty"` | ||||
| 	Description  string              `json:"description,omitempty"`   // Community description | ||||
| 	WikiPage     string              `json:"wiki_page,omitempty"`     // Community's main wiki page title | ||||
| 	MembersCount int                 `json:"members_count,omitempty"` // Community members number | ||||
| 	Counters     GroupsCountersGroup `json:"counters,omitempty"` | ||||
| 	Cover        GroupsCover         `json:"cover,omitempty"` | ||||
|  | ||||
| 	// Type of group, start date of event or category of public page. | ||||
| 	Activity        string               `json:"activity"` | ||||
| 	FixedPost       int                  `json:"fixed_post"`    // Fixed post ID | ||||
| 	Status          string               `json:"status"`        // Community status | ||||
| 	MainAlbumID     int                  `json:"main_album_id"` // Community's main photo album ID | ||||
| 	Links           []GroupsLinksItem    `json:"links"` | ||||
| 	Contacts        []GroupsContactsItem `json:"contacts"` | ||||
| 	Site            string               `json:"site"` // Community's website | ||||
| 	MainSection     int                  `json:"main_section"` | ||||
| 	OnlineStatus    GroupsOnlineStatus   `json:"online_status"` // Status of replies in community messages | ||||
| 	AgeLimits       int                  `json:"age_limits"`    // Information whether age limit | ||||
| 	BanInfo         GroupsGroupBanInfo   `json:"ban_info"`      // User ban info | ||||
| 	Addresses       GroupsAddressesInfo  `json:"addresses"`     // Info about addresses in Groups | ||||
| 	LiveCovers      GroupsLiveCovers     `json:"live_covers"` | ||||
| 	CropPhoto       UsersCropPhoto       `json:"crop_photo"` | ||||
| 	Wall            int                  `json:"wall"` | ||||
| 	ActionButton    GroupsActionButton   `json:"action_button"` | ||||
| 	TrackCode       string               `json:"track_code"` | ||||
| 	PublicDateLabel string               `json:"public_date_label"` | ||||
| 	AuthorID        int                  `json:"author_id"` | ||||
| 	Phone           string               `json:"phone"` | ||||
| 	Activity        string               `json:"activity,omitempty"` | ||||
| 	FixedPost       int                  `json:"fixed_post,omitempty"`    // Fixed post ID | ||||
| 	Status          string               `json:"status,omitempty"`        // Community status | ||||
| 	MainAlbumID     int                  `json:"main_album_id,omitempty"` // Community's main photo album ID | ||||
| 	Links           []GroupsLinksItem    `json:"links,omitempty"` | ||||
| 	Contacts        []GroupsContactsItem `json:"contacts,omitempty"` | ||||
| 	Site            string               `json:"site,omitempty"` // Community's website | ||||
| 	MainSection     int                  `json:"main_section,omitempty"` | ||||
| 	OnlineStatus    GroupsOnlineStatus   `json:"online_status,omitempty"` // Status of replies in community messages | ||||
| 	AgeLimits       int                  `json:"age_limits,omitempty"`    // Information whether age limit | ||||
| 	BanInfo         *GroupsGroupBanInfo  `json:"ban_info,omitempty"`      // User ban info | ||||
| 	Addresses       GroupsAddressesInfo  `json:"addresses,omitempty"`     // Info about addresses in Groups | ||||
| 	LiveCovers      GroupsLiveCovers     `json:"live_covers,omitempty"` | ||||
| 	CropPhoto       UsersCropPhoto       `json:"crop_photo,omitempty"` | ||||
| 	Wall            int                  `json:"wall,omitempty"` | ||||
| 	ActionButton    GroupsActionButton   `json:"action_button,omitempty"` | ||||
| 	TrackCode       string               `json:"track_code,omitempty"` | ||||
| 	PublicDateLabel string               `json:"public_date_label,omitempty"` | ||||
| 	AuthorID        int                  `json:"author_id,omitempty"` | ||||
| 	Phone           string               `json:"phone,omitempty"` | ||||
| 	Like            GroupsGroupLike      `json:"like"` | ||||
| } | ||||
|  | ||||
| // ToMention return mention. | ||||
| @@ -223,6 +228,18 @@ func (group GroupsGroup) ToMention() string { | ||||
| 	return fmt.Sprintf("[club%d|%s]", group.ID, group.Name) | ||||
| } | ||||
|  | ||||
| // GroupsGroupLike struct. | ||||
| type GroupsGroupLike struct { | ||||
| 	IsLiked BaseBoolInt            `json:"is_liked"` | ||||
| 	Friends GroupsGroupLikeFriends `json:"friends"` | ||||
| } | ||||
|  | ||||
| // GroupsGroupLikeFriends struct. | ||||
| type GroupsGroupLikeFriends struct { | ||||
| 	Count   int   `json:"count"` | ||||
| 	Preview []int `json:"preview"` | ||||
| } | ||||
|  | ||||
| // GroupsLiveCovers struct. | ||||
| type GroupsLiveCovers struct { | ||||
| 	IsEnabled  BaseBoolInt `json:"is_enabled"` | ||||
| @@ -275,16 +292,70 @@ type GroupsContactsItem struct { | ||||
|  | ||||
| // GroupsCountersGroup struct. | ||||
| type GroupsCountersGroup struct { | ||||
| 	Addresses  int `json:"addresses"`  // Addresses number | ||||
| 	Albums     int `json:"albums"`     // Photo albums number | ||||
| 	Articles   int `json:"articles"`   // Articles number | ||||
| 	Audios     int `json:"audios"`     // Audios number | ||||
| 	Docs       int `json:"docs"`       // Docs number | ||||
| 	Market     int `json:"market"`     // Market items number | ||||
| 	Photos     int `json:"photos"`     // Photos number | ||||
| 	Topics     int `json:"topics"`     // Topics number | ||||
| 	Videos     int `json:"videos"`     // Videos number | ||||
| 	Narratives int `json:"narratives"` // Narratives number | ||||
| 	Addresses      int `json:"addresses"`       // Addresses number | ||||
| 	Albums         int `json:"albums"`          // Photo albums number | ||||
| 	Articles       int `json:"articles"`        // Articles number | ||||
| 	Audios         int `json:"audios"`          // Audios number | ||||
| 	Docs           int `json:"docs"`            // Docs number | ||||
| 	Market         int `json:"market"`          // Market items number | ||||
| 	Photos         int `json:"photos"`          // Photos number | ||||
| 	Topics         int `json:"topics"`          // Topics number | ||||
| 	Videos         int `json:"videos"`          // Videos number | ||||
| 	Narratives     int `json:"narratives"`      // Narratives number | ||||
| 	Clips          int `json:"clips"`           // Clips number | ||||
| 	ClipsFollowers int `json:"clips_followers"` // Clips followers number | ||||
| } | ||||
|  | ||||
| // UnmarshalJSON GroupsCountersGroup. | ||||
| // | ||||
| // BUG(VK): GroupsCountersGroup return []. | ||||
| func (personal *GroupsCountersGroup) UnmarshalJSON(data []byte) error { | ||||
| 	if bytes.Equal(data, []byte("[]")) { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	type renamedGroupsCountersGroup GroupsCountersGroup | ||||
|  | ||||
| 	var r renamedGroupsCountersGroup | ||||
|  | ||||
| 	err := json.Unmarshal(data, &r) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	*personal = GroupsCountersGroup(r) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // DecodeMsgpack GroupsCountersGroup. | ||||
| // | ||||
| // BUG(VK): GroupsCountersGroup return []. | ||||
| func (personal *GroupsCountersGroup) DecodeMsgpack(dec *msgpack.Decoder) error { | ||||
| 	data, err := dec.DecodeRaw() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if bytes.Equal(data, []byte{msgpcode.FixedArrayLow}) { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	type renamedGroupsCountersGroup GroupsCountersGroup | ||||
|  | ||||
| 	var r renamedGroupsCountersGroup | ||||
|  | ||||
| 	d := msgpack.NewDecoder(bytes.NewReader(data)) | ||||
| 	d.SetCustomStructTag("json") | ||||
|  | ||||
| 	err = d.Decode(&r) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	*personal = GroupsCountersGroup(r) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // GroupsCover struct. | ||||
| @@ -479,6 +550,70 @@ type GroupsGroupSettings struct { | ||||
| 	SecondarySection int                  `json:"secondary_section"` | ||||
| 	ActionButton     GroupsActionButton   `json:"action_button"` | ||||
| 	Phone            string               `json:"phone"` | ||||
|  | ||||
| 	RecognizePhoto int `json:"recognize_photo"` | ||||
|  | ||||
| 	MarketServices GroupsMarketServices `json:"market_services"` | ||||
| 	Narratives     int                  `json:"narratives"` | ||||
| 	Clips          int                  `json:"clips"` | ||||
| 	Textlives      int                  `json:"textlives"` | ||||
| 	Youla          GroupsYoula          `json:"youla"` | ||||
| } | ||||
|  | ||||
| // GroupsMarketServices struct. | ||||
| type GroupsMarketServices struct { | ||||
| 	Enabled         BaseBoolInt         `json:"enabled"` | ||||
| 	CanMessage      BaseBoolInt         `json:"can_message"` | ||||
| 	CommentsEnabled BaseBoolInt         `json:"comments_enabled"` | ||||
| 	ContactID       int                 `json:"contact_id"` | ||||
| 	Currency        MarketCurrency      `json:"currency"` | ||||
| 	ViewType        GroupsSelectedItems `json:"view_type"` | ||||
| 	BlockName       GroupsSelectedItems `json:"block_name"` | ||||
| 	ButtonLabel     GroupsSelectedItems `json:"button_label"` | ||||
| } | ||||
|  | ||||
| // GroupsSelectedItems struct. | ||||
| type GroupsSelectedItems struct { | ||||
| 	SelectedItemID int64                `json:"selected_item_id"` | ||||
| 	Items          []BaseObjectWithName `json:"items"` | ||||
| } | ||||
|  | ||||
| // GroupsYoula struct. | ||||
| type GroupsYoula struct { | ||||
| 	CategoryTree  GroupsYoulaCategory `json:"category_tree"` | ||||
| 	GroupSettings GroupsYoulaSettings `json:"group_settings"` | ||||
| } | ||||
|  | ||||
| // GroupsYoulaCategory struct. | ||||
| type GroupsYoulaCategory struct { | ||||
| 	ID            int                      `json:"id"` | ||||
| 	Title         string                   `json:"title"` | ||||
| 	Subcategories []GroupsYoulaSubcategory `json:"subcategories"` | ||||
| } | ||||
|  | ||||
| // GroupsYoulaSubcategory struct. | ||||
| type GroupsYoulaSubcategory struct { | ||||
| 	ID            int                      `json:"id"` | ||||
| 	Title         string                   `json:"title"` | ||||
| 	ParentID      int                      `json:"parent_id"` | ||||
| 	Subcategories []GroupsYoulaSubcategory `json:"subcategories"` | ||||
| } | ||||
|  | ||||
| // GroupsYoulaSettings struct. | ||||
| type GroupsYoulaSettings struct { | ||||
| 	IsActive              BaseBoolInt `json:"is_active"` | ||||
| 	IsModerated           BaseBoolInt `json:"is_moderated"` | ||||
| 	ShowModerationSetting BaseBoolInt `json:"show_moderation_setting"` | ||||
| 	ModerationStatus      int         `json:"moderation_status"` | ||||
| 	DeclineReason         string      `json:"decline_reason"` | ||||
| 	GroupMode             int         `json:"group_mode"` | ||||
| 	SelectedCategoryIDS   []int       `json:"selected_category_ids"` | ||||
| 	Lat                   float64     `json:"lat"` | ||||
| 	Long                  float64     `json:"long"` | ||||
| 	Radius                float64     `json:"radius"` | ||||
| 	RadiusArea            string      `json:"radius_area"` | ||||
| 	Address               string      `json:"address"` | ||||
| 	Radiuses              []float64   `json:"radiuses"` | ||||
| } | ||||
|  | ||||
| // GroupsSectionsList struct. | ||||
| @@ -532,6 +667,53 @@ func (g *GroupsSectionsList) UnmarshalJSON(data []byte) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // DecodeMsgpack need for decode dynamic array (Example: [1, "Фотографии"]) to struct. | ||||
| func (g *GroupsSectionsList) DecodeMsgpack(dec *msgpack.Decoder) error { | ||||
| 	data, err := dec.DecodeRaw() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	var alias []interface{} | ||||
|  | ||||
| 	err = msgpack.Unmarshal(data, &alias) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if len(alias) != 2 { | ||||
| 		return &json.UnmarshalTypeError{ | ||||
| 			Value: string(data), | ||||
| 			Type:  reflect.TypeOf((*GroupsSectionsList)(nil)), | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	id, ok := alias[0].(int8) | ||||
| 	if !ok { | ||||
| 		return &json.UnmarshalTypeError{ | ||||
| 			Value:  string(data), | ||||
| 			Type:   reflect.TypeOf((*GroupsSectionsList)(nil)), | ||||
| 			Struct: "GroupsSectionsList", | ||||
| 			Field:  "ID", | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	name, ok := alias[1].(string) | ||||
| 	if !ok { | ||||
| 		return &json.UnmarshalTypeError{ | ||||
| 			Value:  string(data), | ||||
| 			Type:   reflect.TypeOf((*GroupsSectionsList)(nil)), | ||||
| 			Struct: "GroupsSectionsList", | ||||
| 			Field:  "Name", | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	g.ID = int(id) | ||||
| 	g.Name = name | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // GroupsActionType for action_button in groups. | ||||
| type GroupsActionType string | ||||
|  | ||||
| @@ -685,7 +867,10 @@ type GroupsLongPollServer struct { | ||||
| 	Ts     string `json:"ts"`     // Number of the last event | ||||
| } | ||||
|  | ||||
| // TODO: func (g GroupsLongPollServer) GetURL() string { | ||||
| // GetURL return link. | ||||
| func (lp GroupsLongPollServer) GetURL(wait int) string { | ||||
| 	return fmt.Sprintf("%s?act=a_check&key=%s&ts=%s&wait=%d", lp.Server, lp.Key, lp.Ts, wait) | ||||
| } | ||||
|  | ||||
| // GroupsLongPollSettings struct. | ||||
| type GroupsLongPollSettings struct { | ||||
| @@ -714,12 +899,14 @@ type GroupsMarketInfo struct { | ||||
| 	Enabled         BaseBoolInt       `json:"enabled"`                 // Information whether the market is enabled | ||||
| 	CommentsEnabled BaseBoolInt       `json:"comments_enabled,omitempty"` | ||||
| 	CanMessage      BaseBoolInt       `json:"can_message,omitempty"` | ||||
| 	IsHsEnabled     BaseBoolInt       `json:"is_hs_enabled,omitempty"` | ||||
| 	MainAlbumID     int               `json:"main_album_id,omitempty"` // Main market album ID | ||||
| 	PriceMax        string            `json:"price_max,omitempty"`     // Maximum price | ||||
| 	PriceMin        string            `json:"price_min,omitempty"`     // Minimum price | ||||
| 	Wiki            PagesWikipageFull `json:"wiki,omitempty"` | ||||
| 	CityIDs         []int             `json:"city_ids"` | ||||
| 	CountryIDs      []int             `json:"country_ids,omitempty"` | ||||
| 	MinOrderPrice   MarketPrice       `json:"min_order_price,omitempty"` | ||||
| } | ||||
|  | ||||
| // GroupsGroupRole Role type. | ||||
| @@ -776,10 +963,10 @@ type GroupsOnlineStatus struct { | ||||
|  | ||||
| // GroupsOwnerXtrBanInfo struct. | ||||
| type GroupsOwnerXtrBanInfo struct { | ||||
| 	BanInfo GroupsBanInfo `json:"ban_info"` | ||||
| 	Group   GroupsGroup   `json:"group"` | ||||
| 	Profile UsersUser     `json:"profile"` | ||||
| 	Type    string        `json:"type"` | ||||
| 	BanInfo *GroupsBanInfo `json:"ban_info"` | ||||
| 	Group   GroupsGroup    `json:"group"` | ||||
| 	Profile UsersUser      `json:"profile"` | ||||
| 	Type    string         `json:"type"` | ||||
| } | ||||
|  | ||||
| // GroupsSubjectItem struct. | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user