Compare commits

..

127 Commits

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

* Code cleanup (whatsapp)

* Cleanup

* Code cleanup (whatsapp)

* Re-examine if sender exists

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

* Handle audio messages

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

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

* Tweak description of "{BOTNICK}"

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

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

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

* Fix error messages in telegram and slack bridges

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

The freenode network doesn't really work anymore

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

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

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

* discord: Ignore incoming events for other guilds

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

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

* Add TitleLink
* Add Footer

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

Introduced via 9a8ce9b17e

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

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

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

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

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

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

* Fix media caption quotting

* Fix errors handling

* Refactor parent id detection

* Try to reduce cognitive complexity of code

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

* Update bridge/slack/slack.go

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

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

* Add fallback for unthreaded telegram message

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

* mumble: lowercase error messages

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

* Telegram: use runes for text entities

* Telegram: use proper offset and runes for links

* Telegram: put newline after backticks for pre

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

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

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

* Implement Telegram MessageEntities correctly

* Apply gofmt

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

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-07 20:30:09 +01:00
Wim
2a3f475ff5 Make EditSuffix option actually work (whatsapp). Fixes #1510 (#1728)
To keep it backwards compatible we keep the "(edited)" message when no
editsuffix is configured.
2022-02-06 23:56:54 +01:00
Wim
7288f71201 Make HTMLDisable work correct (matrix) (#1716) 2022-02-06 20:58:13 +01:00
Wim
9c43eff753 Add support for using ID in channel config (mattermost) (#1715) 2022-02-06 18:26:30 +01:00
Wim
c8d7fdeedc Add UseUsername option (mattermost). Fixes #1665 (#1714) 2022-02-06 17:33:41 +01:00
Wim
c211152e23 Add more debug options for discord (#1712)
debuglevel=1 dumps every received discord event
debuglevel=2 dumps every discord event we are sending to discord (also
logs sensitive information)
2022-02-06 16:58:35 +01:00
Wim
ab75d5097e Use own gomatrix fork again. Fixes #1382 (#1713) 2022-02-06 00:59:34 +01:00
Wim
c3644c8d3b Add support for client certificate (irc) (#1710)
Supports https://libera.chat/guides/certfp.html
2022-02-05 21:12:03 +01:00
Wim
6438a3dba3 Add support for deleting files from slack to discord. Fixes #1705 (#1709)
We create a new event EventFileDelete which will be used to delete
specific uploaded files using the Extra["file"] in the config.Message.

We also add a new NativeID key to the FileInfo struct which will contain
the native file ID of the sending bridge.

When a new file is added to the config.Message.Extra["file"] map, now
the bridge native file ID should be added here.

When the receiving bridge receives such a message, it should keep an
internal mapping of NativeID <> bridge fileid/message id. In the case of
discord we map it to the resulted discord message ID after uploading it.

Now when a bridge deletes a file, it should send a EventFileDelete and
setting the ID to the native file ID of the bridge.

When the receiving bridge will get this event it'll look into the
NativeID <> bridge id mapping to find their internal ID and use it to
delete the specific file on their side.

For now this is implemented for slack to discord but this will be add to
other bridges where useful.
2022-02-05 14:45:54 +01:00
Wim
4b226a6a63 Add support for sender_chat (telegram) (#1677)
* Add support for sender_chat (telegram)

Fixes #1654
https://core.telegram.org/bots/api#december-7-2021

* Add debuglevel option

Add `debuglevel=1` in telegram config to increase debug
2022-02-04 16:15:19 +01:00
Ivan Zuev
4801850013 Add Telegram Bot Command /chatId (telegram) (#1703)
* feat(telegram): command to get chat id

* Gofumpt

Co-authored-by: Ivan Zuev <i-zuev@yandex-team.ru>
Co-authored-by: Wim <wim@42.be>
2022-02-03 00:20:25 +01:00
Ivan Zuev
6a7412bf2b Increase batch size for conversation.list api method (slack) (#1700)
Co-authored-by: Ivan Zuev <i-zuev@yandex-team.ru>
2022-01-29 00:13:15 +01:00
dependabot[bot]
5a1fd7dadd Bump github.com/SevereCloud/vksdk/v2 from 2.11.0 to 2.13.0 (#1698)
Bumps [github.com/SevereCloud/vksdk/v2](https://github.com/SevereCloud/vksdk) from 2.11.0 to 2.13.0.
- [Release notes](https://github.com/SevereCloud/vksdk/releases)
- [Commits](https://github.com/SevereCloud/vksdk/compare/v2.11.0...v2.13.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-28 23:48:40 +01:00
dependabot[bot]
ac06a26809 Bump github.com/go-telegram-bot-api/telegram-bot-api/v5 (#1693)
Bumps [github.com/go-telegram-bot-api/telegram-bot-api/v5](https://github.com/go-telegram-bot-api/telegram-bot-api) from 5.5.0 to 5.5.1.
- [Release notes](https://github.com/go-telegram-bot-api/telegram-bot-api/releases)
- [Changelog](https://github.com/go-telegram-bot-api/telegram-bot-api/blob/master/docs/changelog.md)
- [Commits](https://github.com/go-telegram-bot-api/telegram-bot-api/compare/v5.5.0...v5.5.1)

---
updated-dependencies:
- dependency-name: github.com/go-telegram-bot-api/telegram-bot-api/v5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-26 00:04:58 +01:00
dependabot[bot]
61d56f26f8 Bump github.com/labstack/echo/v4 from 4.6.1 to 4.6.3 (#1685)
Bumps [github.com/labstack/echo/v4](https://github.com/labstack/echo) from 4.6.1 to 4.6.3.
- [Release notes](https://github.com/labstack/echo/releases)
- [Changelog](https://github.com/labstack/echo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/labstack/echo/compare/v4.6.1...v4.6.3)

---
updated-dependencies:
- dependency-name: github.com/labstack/echo/v4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-18 20:42:25 +01:00
dependabot[bot]
6aa05b3981 Bump github.com/spf13/viper from 1.9.0 to 1.10.1 (#1684)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.9.0 to 1.10.1.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.9.0...v1.10.1)

---
updated-dependencies:
- dependency-name: github.com/spf13/viper
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-18 20:34:42 +01:00
dependabot[bot]
aad60c882e Bump github.com/mattermost/mattermost-server/v6 from 6.1.0 to 6.3.0 (#1686)
Bumps [github.com/mattermost/mattermost-server/v6](https://github.com/mattermost/mattermost-server) from 6.1.0 to 6.3.0.
- [Release notes](https://github.com/mattermost/mattermost-server/releases)
- [Changelog](https://github.com/mattermost/mattermost-server/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mattermost/mattermost-server/compare/v6.1.0...v6.3.0)

---
updated-dependencies:
- dependency-name: github.com/mattermost/mattermost-server/v6
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-18 20:24:14 +01:00
dependabot[bot]
fecca57507 Bump github.com/mattermost/mattermost-server/v5 from 5.39.0 to 5.39.3 (#1682)
Bumps [github.com/mattermost/mattermost-server/v5](https://github.com/mattermost/mattermost-server) from 5.39.0 to 5.39.3.
- [Release notes](https://github.com/mattermost/mattermost-server/releases)
- [Changelog](https://github.com/mattermost/mattermost-server/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mattermost/mattermost-server/compare/v5.39.0...v5.39.3)

---
updated-dependencies:
- dependency-name: github.com/mattermost/mattermost-server/v5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-18 20:19:12 +01:00
Wim
2bcad846c0 Add more ignore debug messages (mattermost) (#1678) 2022-01-10 22:58:53 +01:00
Wim
15ad0165fc Log eventtype in debug (mattermost) (#1676) 2022-01-10 00:50:03 +01:00
Wim
2e8ab11978 Use current parentID if rootId is not set (mattermost) (#1675) 2022-01-10 00:37:09 +01:00
vpzomtrrfrt
9a8ce9b17e Reply support for Matrix (#1664)
* Post replies to matrix

* Handle replies from matrix

* Include protocol in canonical ID return

* fmt
2022-01-09 23:46:59 +01:00
Daniil Suvorov
16ab4c6fed Remove GroupID (vk) (#1668) 2022-01-09 22:50:07 +01:00
Felix
e3ee0df7ba Add Dependabot.yml config (#1663)
* Added: Dependabot.yml config

* Updated: schedule interval

* Updated: Interval to weekly
2021-12-19 21:53:09 +01:00
Wim
8f7ab280e2 Fix codeql warnings 2021-12-19 14:39:24 +01:00
Janet Blackquill
dbedc99421 Add support for Harmony (#1656)
Harmony is a relatively new (1,5yo) chat protocol with a small community.
This introduces support for Harmony into Matterbridge, using the functionality
specifically designed for bridge bots. The implementation is a modest 200 lines
of code.
2021-12-18 22:43:29 +01:00
Wim
6cb359cb80 Fix vendored xmpp (#1661) 2021-12-12 14:11:11 +01:00
Soloam
ae2ad824a9 Add comments to messages (telegram) (#1652)
* Add's comments to message in telegram messages

This is a change to handle comments in telegram messages!

Some messages in telegram have comments added to the message! This normally is the description in images or links. This changes appends the comment to the message if available.

This should fix the issue in #1649

* [fix] discord: send comments in extras

Co-authored-by: Wim <wim@42.be>
2021-12-12 01:40:31 +01:00
Wim
02e3d7852b Update telegram-bot-api to v5 (#1660) 2021-12-12 00:35:32 +01:00
Wim
3893a035be Update dependencies/vendor (#1659) 2021-12-12 00:05:15 +01:00
Wim
658bdd9faa Fix telegram/handlers.go linting (#1658) 2021-12-10 22:13:54 +01:00
Wim
e1eebcd4e0 Disable some more linters 2021-12-10 21:54:09 +01:00
Yash Rathore
062b831e88 Fix Zulip example in matterbridge.toml.sample (#1657)
Commit 11fc4c286f changed the example for
Zulip, in a way that was not accurate to what zulip.go expects, hence
this commit fixes the example.
2021-12-10 21:47:47 +01:00
Dan Walmsley
b275efaeff Add support for code blocks in telegram (#1650)
* handle code blocks in telegram.

* support multi-line code blocks.

* remove import.

* handle code blocks in middle of normal text.

* support multiple code blocks in same message.
2021-12-07 21:26:28 +01:00
PeGaSuS
80d3033456 Update matterbridge.toml.sample (#1644)
Missing `{NOPINGNICK}` example on the general re-loadable settings
2021-12-02 00:49:16 +01:00
Sandro
bd0516f09a Use Alpine stable again in Dockerfile (#1643)
* Use alpine stable again

* fix build for tgs.Dockerfile
2021-11-29 01:19:10 +01:00
Santtu Lakkala
df4d76e466 Allow binding to IP on IRC (#1640)
Add configuration option "Bind" that is passed on to girc, allowing
to choose which IP address to use on systems that have multiple ones.
2021-11-29 01:15:51 +01:00
Wim
dcbd7f8cad Bump version 2021-11-02 23:34:42 +01:00
3019 changed files with 3931568 additions and 46887 deletions

View File

@@ -16,7 +16,7 @@ jobs:
test-build-upload: test-build-upload:
strategy: strategy:
matrix: matrix:
go-version: [1.17.x] go-version: [1.18.x]
platform: [ubuntu-latest] platform: [ubuntu-latest]
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
@@ -39,19 +39,19 @@ jobs:
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-s -X github.com/42wim/matterbridge/version.GitHash=$(git log --pretty=format:'%h' -n 1)" -o output/win/matterbridge-$VERSION-windows-amd64.exe CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-s -X github.com/42wim/matterbridge/version.GitHash=$(git log --pretty=format:'%h' -n 1)" -o output/win/matterbridge-$VERSION-windows-amd64.exe
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-s -X github.com/42wim/matterbridge/version.GitHash=$(git log --pretty=format:'%h' -n 1)" -o output/mac/matterbridge-$VERSION-darwin-amd64 CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-s -X github.com/42wim/matterbridge/version.GitHash=$(git log --pretty=format:'%h' -n 1)" -o output/mac/matterbridge-$VERSION-darwin-amd64
- name: Upload linux 64-bit - name: Upload linux 64-bit
if: startsWith(matrix.go-version,'1.17') if: startsWith(matrix.go-version,'1.18')
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: matterbridge-linux-64bit name: matterbridge-linux-64bit
path: output/lin path: output/lin
- name: Upload windows 64-bit - name: Upload windows 64-bit
if: startsWith(matrix.go-version,'1.17') if: startsWith(matrix.go-version,'1.18')
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: matterbridge-windows-64bit name: matterbridge-windows-64bit
path: output/win path: output/win
- name: Upload darwin 64-bit - name: Upload darwin 64-bit
if: startsWith(matrix.go-version,'1.17') if: startsWith(matrix.go-version,'1.18')
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: matterbridge-darwin-64bit name: matterbridge-darwin-64bit

3
.gitignore vendored
View File

@@ -4,3 +4,6 @@
# Exclude configuration file # Exclude configuration file
matterbridge.toml matterbridge.toml
# Exclude IDE Files
.vscode

View File

@@ -188,7 +188,30 @@ linters:
- exhaustivestruct - exhaustivestruct
- forbidigo - forbidigo
- wrapcheck - wrapcheck
- varnamelen
- ireturn
- errorlint
- tparallel
- wrapcheck
- paralleltest
- makezero
- thelper
- cyclop
- revive
- importas
- gomoddirectives
- promlinter
- tagliatelle
- errname
- typecheck
- grouper
- decorder
- maintidx
- exhaustruct
- asasalint
- execinquery
- nosnakecase
- exhaustive
# rules to deal with reported isues # rules to deal with reported isues
issues: issues:
# List of regexps of issue texts to exclude, empty list by default. # List of regexps of issue texts to exclude, empty list by default.

View File

@@ -1,4 +1,4 @@
FROM alpine:edge AS builder FROM alpine AS builder
COPY . /go/src/matterbridge COPY . /go/src/matterbridge
RUN apk --no-cache add go git \ RUN apk --no-cache add go git \

14
Dockerfile_whatsappmulti Normal file
View File

@@ -0,0 +1,14 @@
FROM alpine AS builder
COPY . /go/src/matterbridge
RUN apk --no-cache add go git \
&& cd /go/src/matterbridge \
&& CGO_ENABLED=0 go build -tags whatsappmulti -mod vendor -ldflags "-X github.com/42wim/matterbridge/version.GitHash=$(git log --pretty=format:'%h' -n 1)" -o /bin/matterbridge
FROM alpine
RUN apk --no-cache add ca-certificates mailcap
COPY --from=builder /bin/matterbridge /bin/matterbridge
RUN mkdir /etc/matterbridge \
&& touch /etc/matterbridge/matterbridge.toml \
&& ln -sf /matterbridge.toml /etc/matterbridge/matterbridge.toml
ENTRYPOINT ["/bin/matterbridge", "-conf", "/etc/matterbridge/matterbridge.toml"]

View File

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

View File

@@ -23,6 +23,7 @@ const (
EventRejoinChannels = "rejoin_channels" EventRejoinChannels = "rejoin_channels"
EventUserAction = "user_action" EventUserAction = "user_action"
EventMsgDelete = "msg_delete" EventMsgDelete = "msg_delete"
EventFileDelete = "file_delete"
EventAPIConnected = "api_connected" EventAPIConnected = "api_connected"
EventUserTyping = "user_typing" EventUserTyping = "user_typing"
EventGetChannelMembers = "get_channel_members" EventGetChannelMembers = "get_channel_members"
@@ -56,13 +57,14 @@ func (m Message) ParentValid() bool {
} }
type FileInfo struct { type FileInfo struct {
Name string Name string
Data *[]byte Data *[]byte
Comment string Comment string
URL string URL string
Size int64 Size int64
Avatar bool Avatar bool
SHA string SHA string
NativeID string
} }
type ChannelInfo struct { type ChannelInfo struct {
@@ -168,7 +170,7 @@ type Protocol struct {
UseTLS bool // IRC UseTLS bool // IRC
UseDiscriminator bool // discord UseDiscriminator bool // discord
UseFirstName bool // telegram UseFirstName bool // telegram
UseUserName bool // discord, matrix UseUserName bool // discord, matrix, mattermost
UseInsecureURL bool // telegram UseInsecureURL bool // telegram
UserName string // IRC UserName string // IRC
VerboseJoinPart bool // IRC VerboseJoinPart bool // IRC

View File

@@ -10,10 +10,14 @@ import (
"github.com/42wim/matterbridge/bridge/config" "github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/discord/transmitter" "github.com/42wim/matterbridge/bridge/discord/transmitter"
"github.com/42wim/matterbridge/bridge/helper" "github.com/42wim/matterbridge/bridge/helper"
"github.com/matterbridge/discordgo" "github.com/bwmarrin/discordgo"
lru "github.com/hashicorp/golang-lru"
) )
const MessageLength = 1950 const (
MessageLength = 1950
cFileUpload = "file_upload"
)
type Bdiscord struct { type Bdiscord struct {
*bridge.Config *bridge.Config
@@ -35,10 +39,20 @@ type Bdiscord struct {
// Webhook specific logic // Webhook specific logic
useAutoWebhooks bool useAutoWebhooks bool
transmitter *transmitter.Transmitter transmitter *transmitter.Transmitter
cache *lru.Cache
} }
func New(cfg *bridge.Config) bridge.Bridger { func New(cfg *bridge.Config) bridge.Bridger {
b := &Bdiscord{Config: cfg} newCache, err := lru.New(5000)
if err != nil {
cfg.Log.Fatalf("Could not create LRU cache: %v", err)
}
b := &Bdiscord{
Config: cfg,
cache: newCache,
}
b.userMemberMap = make(map[string]*discordgo.Member) b.userMemberMap = make(map[string]*discordgo.Member)
b.nickMemberMap = make(map[string]*discordgo.Member) b.nickMemberMap = make(map[string]*discordgo.Member)
b.channelInfoMap = make(map[string]*config.ChannelInfo) b.channelInfoMap = make(map[string]*config.ChannelInfo)
@@ -69,12 +83,15 @@ func (b *Bdiscord) Connect() error {
b.Log.Info("Connection succeeded") b.Log.Info("Connection succeeded")
b.c.AddHandler(b.messageCreate) b.c.AddHandler(b.messageCreate)
b.c.AddHandler(b.messageTyping) b.c.AddHandler(b.messageTyping)
b.c.AddHandler(b.memberUpdate)
b.c.AddHandler(b.messageUpdate) b.c.AddHandler(b.messageUpdate)
b.c.AddHandler(b.messageDelete) b.c.AddHandler(b.messageDelete)
b.c.AddHandler(b.messageDeleteBulk) b.c.AddHandler(b.messageDeleteBulk)
b.c.AddHandler(b.memberAdd) b.c.AddHandler(b.memberAdd)
b.c.AddHandler(b.memberRemove) b.c.AddHandler(b.memberRemove)
b.c.AddHandler(b.memberUpdate)
if b.GetInt("debuglevel") == 1 {
b.c.AddHandler(b.messageEvent)
}
// Add privileged intent for guild member tracking. This is needed to track nicks // Add privileged intent for guild member tracking. This is needed to track nicks
// for display names and @mention translation // for display names and @mention translation
b.c.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsAllWithoutPrivileged | b.c.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsAllWithoutPrivileged |
@@ -153,7 +170,7 @@ func (b *Bdiscord) Connect() error {
return fmt.Errorf("use of removed WebhookURL setting") return fmt.Errorf("use of removed WebhookURL setting")
} }
if b.GetInt("debuglevel") > 0 { if b.GetInt("debuglevel") == 2 {
b.Log.Debug("enabling even more discord debug") b.Log.Debug("enabling even more discord debug")
b.c.Debug = true b.c.Debug = true
} }
@@ -255,7 +272,6 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
// Handle prefix hint for unthreaded messages. // Handle prefix hint for unthreaded messages.
if msg.ParentNotFound() { if msg.ParentNotFound() {
msg.ParentID = "" msg.ParentID = ""
msg.Text = fmt.Sprintf("[thread]: %s", msg.Text)
} }
// Use webhook to send the message // Use webhook to send the message
@@ -280,6 +296,21 @@ func (b *Bdiscord) handleEventBotUser(msg *config.Message, channelID string) (st
return "", err return "", err
} }
// Delete a file
if msg.Event == config.EventFileDelete {
if msg.ID == "" {
return "", nil
}
if fi, ok := b.cache.Get(cFileUpload + msg.ID); ok {
err := b.c.ChannelMessageDelete(channelID, fi.(string)) // nolint:forcetypeassert
b.cache.Remove(cFileUpload + msg.ID)
return "", err
}
return "", fmt.Errorf("file %s not found", msg.ID)
}
// Upload a file if it exists // Upload a file if it exists
if msg.Extra != nil { if msg.Extra != nil {
for _, rmsg := range helper.HandleExtra(msg, b.General) { for _, rmsg := range helper.HandleExtra(msg, b.General) {
@@ -327,7 +358,6 @@ func (b *Bdiscord) handleEventBotUser(msg *config.Message, channelID string) (st
// handleUploadFile handles native upload of files // handleUploadFile handles native upload of files
func (b *Bdiscord) handleUploadFile(msg *config.Message, channelID string) (string, error) { func (b *Bdiscord) handleUploadFile(msg *config.Message, channelID string) (string, error) {
var err error
for _, f := range msg.Extra["file"] { for _, f := range msg.Extra["file"] {
fi := f.(config.FileInfo) fi := f.(config.FileInfo)
file := discordgo.File{ file := discordgo.File{
@@ -340,10 +370,15 @@ func (b *Bdiscord) handleUploadFile(msg *config.Message, channelID string) (stri
Files: []*discordgo.File{&file}, Files: []*discordgo.File{&file},
AllowedMentions: b.getAllowedMentions(), AllowedMentions: b.getAllowedMentions(),
} }
_, err = b.c.ChannelMessageSendComplex(channelID, &m) res, err := b.c.ChannelMessageSendComplex(channelID, &m)
if err != nil { if err != nil {
return "", fmt.Errorf("file upload failed: %s", err) return "", fmt.Errorf("file upload failed: %s", err)
} }
// link file_upload_nativeID (file ID from the original bridge) to our upload id
// so that we can remove this later when it eg needs to be deleted
b.cache.Add(cFileUpload+fi.NativeID, res.ID)
} }
return "", nil return "", nil
} }

View File

@@ -2,10 +2,15 @@ package bdiscord
import ( import (
"github.com/42wim/matterbridge/bridge/config" "github.com/42wim/matterbridge/bridge/config"
"github.com/matterbridge/discordgo" "github.com/bwmarrin/discordgo"
"github.com/davecgh/go-spew/spew"
) )
func (b *Bdiscord) messageDelete(s *discordgo.Session, m *discordgo.MessageDelete) { //nolint:unparam func (b *Bdiscord) messageDelete(s *discordgo.Session, m *discordgo.MessageDelete) { //nolint:unparam
if m.GuildID != b.guildID {
b.Log.Debugf("Ignoring messageDelete because it originates from a different guild")
return
}
rmsg := config.Message{Account: b.Account, ID: m.ID, Event: config.EventMsgDelete, Text: config.EventMsgDelete} rmsg := config.Message{Account: b.Account, ID: m.ID, Event: config.EventMsgDelete, Text: config.EventMsgDelete}
rmsg.Channel = b.getChannelName(m.ChannelID) rmsg.Channel = b.getChannelName(m.ChannelID)
@@ -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 // TODO(qaisjp): if other bridges support bulk deletions, it could be fanned out centrally
func (b *Bdiscord) messageDeleteBulk(s *discordgo.Session, m *discordgo.MessageDeleteBulk) { //nolint:unparam func (b *Bdiscord) messageDeleteBulk(s *discordgo.Session, m *discordgo.MessageDeleteBulk) { //nolint:unparam
if m.GuildID != b.guildID {
b.Log.Debugf("Ignoring messageDeleteBulk because it originates from a different guild")
return
}
for _, msgID := range m.Messages { for _, msgID := range m.Messages {
rmsg := config.Message{ rmsg := config.Message{
Account: b.Account, Account: b.Account,
@@ -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) { func (b *Bdiscord) messageTyping(s *discordgo.Session, m *discordgo.TypingStart) {
if m.GuildID != b.guildID {
b.Log.Debugf("Ignoring messageTyping because it originates from a different guild")
return
}
if !b.GetBool("ShowUserTyping") { if !b.GetBool("ShowUserTyping") {
return return
} }
@@ -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 func (b *Bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) { //nolint:unparam
if m.GuildID != b.guildID {
b.Log.Debugf("Ignoring messageUpdate because it originates from a different guild")
return
}
if b.GetBool("EditDisable") { if b.GetBool("EditDisable") {
return return
} }
// only when message is actually edited // only when message is actually edited
if m.Message.EditedTimestamp != "" { if m.Message.EditedTimestamp != nil {
b.Log.Debugf("Sending edit message") b.Log.Debugf("Sending edit message")
m.Content += b.GetString("EditSuffix") m.Content += b.GetString("EditSuffix")
msg := &discordgo.MessageCreate{ msg := &discordgo.MessageCreate{
@@ -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 func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { //nolint:unparam
if m.GuildID != b.guildID {
b.Log.Debugf("Ignoring messageCreate because it originates from a different guild")
return
}
var err error var err error
// not relay our own messages // not relay our own messages
@@ -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} rmsg := config.Message{Account: b.Account, Avatar: "https://cdn.discordapp.com/avatars/" + m.Author.ID + "/" + m.Author.Avatar + ".jpg", UserID: m.Author.ID, ID: m.ID}
b.Log.Debugf("== Receiving event %#v", m.Message)
if m.Content != "" { if m.Content != "" {
b.Log.Debugf("== Receiving event %#v", m.Message)
m.Message.Content = b.replaceChannelMentions(m.Message.Content) m.Message.Content = b.replaceChannelMentions(m.Message.Content)
rmsg.Text, err = m.ContentWithMoreMentionsReplaced(b.c) rmsg.Text, err = m.ContentWithMoreMentionsReplaced(b.c)
if err != nil { if err != nil {
@@ -138,6 +164,10 @@ func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
} }
func (b *Bdiscord) memberUpdate(s *discordgo.Session, m *discordgo.GuildMemberUpdate) { func (b *Bdiscord) memberUpdate(s *discordgo.Session, m *discordgo.GuildMemberUpdate) {
if m.GuildID != b.guildID {
b.Log.Debugf("Ignoring memberUpdate because it originates from a different guild")
return
}
if m.Member == nil { if m.Member == nil {
b.Log.Warnf("Received member update with no member information: %#v", m) b.Log.Warnf("Received member update with no member information: %#v", m)
} }
@@ -165,6 +195,13 @@ func (b *Bdiscord) memberUpdate(s *discordgo.Session, m *discordgo.GuildMemberUp
} }
func (b *Bdiscord) memberAdd(s *discordgo.Session, m *discordgo.GuildMemberAdd) { func (b *Bdiscord) memberAdd(s *discordgo.Session, m *discordgo.GuildMemberAdd) {
if m.GuildID != b.guildID {
b.Log.Debugf("Ignoring memberAdd because it originates from a different guild")
return
}
if b.GetBool("nosendjoinpart") {
return
}
if m.Member == nil { if m.Member == nil {
b.Log.Warnf("Received member update with no member information: %#v", m) b.Log.Warnf("Received member update with no member information: %#v", m)
return return
@@ -186,6 +223,13 @@ func (b *Bdiscord) memberAdd(s *discordgo.Session, m *discordgo.GuildMemberAdd)
} }
func (b *Bdiscord) memberRemove(s *discordgo.Session, m *discordgo.GuildMemberRemove) { func (b *Bdiscord) memberRemove(s *discordgo.Session, m *discordgo.GuildMemberRemove) {
if m.GuildID != b.guildID {
b.Log.Debugf("Ignoring memberRemove because it originates from a different guild")
return
}
if b.GetBool("nosendjoinpart") {
return
}
if m.Member == nil { if m.Member == nil {
b.Log.Warnf("Received member update with no member information: %#v", m) b.Log.Warnf("Received member update with no member information: %#v", m)
return return

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ import (
"github.com/42wim/matterbridge/bridge/config" "github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper" "github.com/42wim/matterbridge/bridge/helper"
"github.com/matterbridge/discordgo" "github.com/bwmarrin/discordgo"
) )
// shouldMessageUseWebhooks checks if have a channel specific webhook, if we're not using auto webhooks // shouldMessageUseWebhooks checks if have a channel specific webhook, if we're not using auto webhooks
@@ -82,16 +82,14 @@ func (b *Bdiscord) webhookSend(msg *config.Message, channelID string) (*discordg
ContentType: "", ContentType: "",
Reader: bytes.NewReader(*fi.Data), Reader: bytes.NewReader(*fi.Data),
} }
content := "" content := fi.Comment
if msg.Text == "" {
content = fi.Comment
}
_, e2 := b.transmitter.Send( _, e2 := b.transmitter.Send(
channelID, channelID,
&discordgo.WebhookParams{ &discordgo.WebhookParams{
Username: msg.Username, Username: msg.Username,
AvatarURL: msg.Avatar, AvatarURL: msg.Avatar,
File: &file, Files: []*discordgo.File{&file},
Content: content, Content: content,
AllowedMentions: b.getAllowedMentions(), AllowedMentions: b.getAllowedMentions(),
}, },

252
bridge/harmony/harmony.go Normal file
View 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
}

View File

@@ -86,6 +86,12 @@ func GetSubLines(message string, maxLineLength int, clippingMessage string) []st
var lines []string var lines []string
for _, line := range strings.Split(strings.TrimSpace(message), "\n") { for _, line := range strings.Split(strings.TrimSpace(message), "\n") {
if line == "" {
// Prevent sending empty messages, so we'll skip this line
// if it has no content.
continue
}
if maxLineLength == 0 || len([]byte(line)) <= maxLineLength { if maxLineLength == 0 || len([]byte(line)) <= maxLineLength {
lines = append(lines, line) lines = append(lines, line)
continue continue
@@ -168,17 +174,23 @@ func HandleDownloadSize(logger *logrus.Entry, msg *config.Message, name string,
// HandleDownloadData adds the data for a remote file into a Matterbridge gateway message. // HandleDownloadData adds the data for a remote file into a Matterbridge gateway message.
func HandleDownloadData(logger *logrus.Entry, msg *config.Message, name, comment, url string, data *[]byte, general *config.Protocol) { func HandleDownloadData(logger *logrus.Entry, msg *config.Message, name, comment, url string, data *[]byte, general *config.Protocol) {
HandleDownloadData2(logger, msg, name, "", comment, url, data, general)
}
// HandleDownloadData adds the data for a remote file into a Matterbridge gateway message.
func HandleDownloadData2(logger *logrus.Entry, msg *config.Message, name, id, comment, url string, data *[]byte, general *config.Protocol) {
var avatar bool var avatar bool
logger.Debugf("Download OK %#v %#v", name, len(*data)) logger.Debugf("Download OK %#v %#v", name, len(*data))
if msg.Event == config.EventAvatarDownload { if msg.Event == config.EventAvatarDownload {
avatar = true avatar = true
} }
msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{ msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{
Name: name, Name: name,
Data: data, Data: data,
URL: url, URL: url,
Comment: comment, Comment: comment,
Avatar: avatar, Avatar: avatar,
NativeID: id,
}) })
} }

View File

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

View File

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

32
bridge/irc/charset.go Normal file
View File

@@ -0,0 +1,32 @@
package birc
import (
"golang.org/x/text/encoding"
"golang.org/x/text/encoding/japanese"
"golang.org/x/text/encoding/korean"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/encoding/traditionalchinese"
"golang.org/x/text/encoding/unicode"
)
var encoders = map[string]encoding.Encoding{
"utf-8": unicode.UTF8,
"iso-2022-jp": japanese.ISO2022JP,
"big5": traditionalchinese.Big5,
"gbk": simplifiedchinese.GBK,
"euc-kr": korean.EUCKR,
"gb2312": simplifiedchinese.HZGB2312,
"shift-jis": japanese.ShiftJIS,
"euc-jp": japanese.EUCJP,
"gb18030": simplifiedchinese.GB18030,
}
func toUTF8(from string, input string) string {
enc, ok := encoders[from]
if !ok {
return input
}
res, _ := enc.NewDecoder().String(input)
return res
}

View File

@@ -11,7 +11,6 @@ import (
"github.com/42wim/matterbridge/bridge/config" "github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper" "github.com/42wim/matterbridge/bridge/helper"
"github.com/lrstanley/girc" "github.com/lrstanley/girc"
"github.com/missdeer/golib/ic"
"github.com/paulrosania/go-charset/charset" "github.com/paulrosania/go-charset/charset"
"github.com/saintfish/chardet" "github.com/saintfish/chardet"
@@ -24,12 +23,12 @@ func (b *Birc) handleCharset(msg *config.Message) error {
if b.GetString("Charset") != "" { if b.GetString("Charset") != "" {
switch b.GetString("Charset") { switch b.GetString("Charset") {
case "gbk", "gb18030", "gb2312", "big5", "euc-kr", "euc-jp", "shift-jis", "iso-2022-jp": case "gbk", "gb18030", "gb2312", "big5", "euc-kr", "euc-jp", "shift-jis", "iso-2022-jp":
msg.Text = ic.ConvertString("utf-8", b.GetString("Charset"), msg.Text) msg.Text = toUTF8(b.GetString("Charset"), msg.Text)
default: default:
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
w, err := charset.NewWriter(b.GetString("Charset"), buf) w, err := charset.NewWriter(b.GetString("Charset"), buf)
if err != nil { if err != nil {
b.Log.Errorf("charset from utf-8 conversion failed: %s", err) b.Log.Errorf("charset to utf-8 conversion failed: %s", err)
return err return err
} }
fmt.Fprint(w, msg.Text) fmt.Fprint(w, msg.Text)
@@ -227,7 +226,7 @@ func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) {
} }
switch mycharset { switch mycharset {
case "gbk", "gb18030", "gb2312", "big5", "euc-kr", "euc-jp", "shift-jis", "iso-2022-jp": case "gbk", "gb18030", "gb2312", "big5", "euc-kr", "euc-jp", "shift-jis", "iso-2022-jp":
rmsg.Text = ic.ConvertString("utf-8", b.GetString("Charset"), rmsg.Text) rmsg.Text = toUTF8(b.GetString("Charset"), rmsg.Text)
default: default:
r, err := charset.NewReader(mycharset, strings.NewReader(rmsg.Text)) r, err := charset.NewReader(mycharset, strings.NewReader(rmsg.Text))
if err != nil { if err != nil {
@@ -244,6 +243,7 @@ func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) {
func (b *Birc) handleRunCommands() { func (b *Birc) handleRunCommands() {
for _, cmd := range b.GetStringSlice("RunCommands") { for _, cmd := range b.GetStringSlice("RunCommands") {
cmd = strings.ReplaceAll(cmd, "{BOTNICK}", b.Nick)
if err := b.i.Cmd.SendRaw(cmd); err != nil { if err := b.i.Cmd.SendRaw(cmd); err != nil {
b.Log.Errorf("RunCommands %s failed: %s", cmd, err) b.Log.Errorf("RunCommands %s failed: %s", cmd, err)
} }

View File

@@ -2,6 +2,7 @@ package birc
import ( import (
"crypto/tls" "crypto/tls"
"errors"
"fmt" "fmt"
"hash/crc32" "hash/crc32"
"io/ioutil" "io/ioutil"
@@ -72,6 +73,10 @@ func (b *Birc) Command(msg *config.Message) string {
} }
func (b *Birc) Connect() error { func (b *Birc) Connect() error {
if b.GetBool("UseSASL") && b.GetString("TLSClientCertificate") != "" {
return errors.New("you can't enable SASL and TLSClientCertificate at the same time")
}
b.Local = make(chan config.Message, b.MessageQueue+10) b.Local = make(chan config.Message, b.MessageQueue+10)
b.Log.Infof("Connecting %s", b.GetString("Server")) b.Log.Infof("Connecting %s", b.GetString("Server"))
@@ -300,6 +305,11 @@ func (b *Birc) getClient() (*girc.Client, error) {
b.Log.Debugf("setting pingdelay to %s", pingDelay) b.Log.Debugf("setting pingdelay to %s", pingDelay)
tlsConfig, err := b.getTLSConfig()
if err != nil {
return nil, err
}
i := girc.New(girc.Config{ i := girc.New(girc.Config{
Server: server, Server: server,
ServerPass: b.GetString("Password"), ServerPass: b.GetString("Password"),
@@ -308,7 +318,8 @@ func (b *Birc) getClient() (*girc.Client, error) {
User: user, User: user,
Name: realName, Name: realName,
SSL: b.GetBool("UseTLS"), SSL: b.GetBool("UseTLS"),
TLSConfig: &tls.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), ServerName: server}, //nolint:gosec Bind: b.GetString("Bind"),
TLSConfig: tlsConfig,
PingDelay: pingDelay, PingDelay: pingDelay,
// skip gIRC internal rate limiting, since we have our own throttling // skip gIRC internal rate limiting, since we have our own throttling
AllowFlood: true, AllowFlood: true,
@@ -351,8 +362,10 @@ func (b *Birc) skipPrivMsg(event girc.Event) bool {
return true return true
} }
// don't forward message from ourself // don't forward message from ourself
if event.Source.Name == b.Nick { if event.Source != nil {
return true if event.Source.Name == b.Nick {
return true
}
} }
// don't forward messages we sent via RELAYMSG // don't forward messages we sent via RELAYMSG
if relayedNick, ok := event.Tags.Get("draft/relaymsg"); ok && relayedNick == b.Nick { if relayedNick, ok := event.Tags.Get("draft/relaymsg"); ok && relayedNick == b.Nick {
@@ -380,3 +393,23 @@ func (b *Birc) storeNames(client *girc.Client, event girc.Event) {
func (b *Birc) formatnicks(nicks []string) string { func (b *Birc) formatnicks(nicks []string) string {
return strings.Join(nicks, ", ") + " currently on IRC" return strings.Join(nicks, ", ") + " currently on IRC"
} }
func (b *Birc) getTLSConfig() (*tls.Config, error) {
server, _, _ := net.SplitHostPort(b.GetString("server"))
tlsConfig := &tls.Config{
InsecureSkipVerify: b.GetBool("skiptlsverify"), //nolint:gosec
ServerName: server,
}
if filename := b.GetString("TLSClientCertificate"); filename != "" {
cert, err := tls.LoadX509KeyPair(filename, filename)
if err != nil {
return nil, err
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
return tlsConfig, nil
}

View File

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

View File

@@ -12,7 +12,7 @@ import (
"github.com/42wim/matterbridge/bridge" "github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config" "github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper" "github.com/42wim/matterbridge/bridge/helper"
matrix "github.com/matrix-org/gomatrix" matrix "github.com/matterbridge/gomatrix"
) )
var ( var (
@@ -48,8 +48,10 @@ type matrixUsername struct {
// SubTextMessage represents the new content of the message in edit messages. // SubTextMessage represents the new content of the message in edit messages.
type SubTextMessage struct { type SubTextMessage struct {
MsgType string `json:"msgtype"` MsgType string `json:"msgtype"`
Body string `json:"body"` Body string `json:"body"`
FormattedBody string `json:"formatted_body,omitempty"`
Format string `json:"format,omitempty"`
} }
// MessageRelation explains how the current message relates to a previous message. // MessageRelation explains how the current message relates to a previous message.
@@ -65,6 +67,19 @@ type EditedMessage struct {
matrix.TextMessage matrix.TextMessage
} }
type InReplyToRelationContent struct {
EventID string `json:"event_id"`
}
type InReplyToRelation struct {
InReplyTo InReplyToRelationContent `json:"m.in_reply_to"`
}
type ReplyMessage struct {
RelatedTo InReplyToRelation `json:"m.relates_to"`
matrix.TextMessage
}
func New(cfg *bridge.Config) bridge.Bridger { func New(cfg *bridge.Config) bridge.Bridger {
b := &Bmatrix{Config: cfg} b := &Bmatrix{Config: cfg}
b.RoomMap = make(map[string]string) b.RoomMap = make(map[string]string)
@@ -133,12 +148,43 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
username := newMatrixUsername(msg.Username) username := newMatrixUsername(msg.Username)
body := username.plain + msg.Text
formattedBody := username.formatted + helper.ParseMarkdown(msg.Text)
if b.GetBool("SpoofUsername") {
// https://spec.matrix.org/v1.3/client-server-api/#mroommember
type stateMember struct {
AvatarURL string `json:"avatar_url,omitempty"`
DisplayName string `json:"displayname"`
Membership string `json:"membership"`
}
// TODO: reset username afterwards with DisplayName: null ?
m := stateMember{
AvatarURL: "",
DisplayName: username.plain,
Membership: "join",
}
_, err := b.mc.SendStateEvent(channel, "m.room.member", b.UserID, m)
if err == nil {
body = msg.Text
formattedBody = helper.ParseMarkdown(msg.Text)
}
}
// Make a action /me of the message // Make a action /me of the message
if msg.Event == config.EventUserAction { if msg.Event == config.EventUserAction {
m := matrix.TextMessage{ m := matrix.TextMessage{
MsgType: "m.emote", MsgType: "m.emote",
Body: username.plain + msg.Text, Body: body,
FormattedBody: username.formatted + msg.Text, FormattedBody: formattedBody,
Format: "org.matrix.custom.html",
}
if b.GetBool("HTMLDisable") {
m.Format = ""
m.FormattedBody = ""
} }
msgID := "" msgID := ""
@@ -201,20 +247,29 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
// Edit message if we have an ID // Edit message if we have an ID
if msg.ID != "" { if msg.ID != "" {
rmsg := EditedMessage{TextMessage: matrix.TextMessage{ rmsg := EditedMessage{
Body: username.plain + msg.Text, TextMessage: matrix.TextMessage{
MsgType: "m.text", Body: body,
}} MsgType: "m.text",
if b.GetBool("HTMLDisable") { Format: "org.matrix.custom.html",
rmsg.TextMessage.FormattedBody = username.formatted + "* " + msg.Text FormattedBody: formattedBody,
} else { },
rmsg.Format = "org.matrix.custom.html"
rmsg.TextMessage.FormattedBody = username.formatted + "* " + helper.ParseMarkdown(msg.Text)
} }
rmsg.NewContent = SubTextMessage{ rmsg.NewContent = SubTextMessage{
Body: rmsg.TextMessage.Body, Body: rmsg.TextMessage.Body,
MsgType: "m.text", FormattedBody: rmsg.TextMessage.FormattedBody,
Format: rmsg.TextMessage.Format,
MsgType: "m.text",
} }
if b.GetBool("HTMLDisable") {
rmsg.TextMessage.Format = ""
rmsg.TextMessage.FormattedBody = ""
rmsg.NewContent.Format = ""
rmsg.NewContent.FormattedBody = ""
}
rmsg.RelatedTo = MessageRelation{ rmsg.RelatedTo = MessageRelation{
EventID: msg.ID, EventID: msg.ID,
Type: "m.replace", Type: "m.replace",
@@ -236,8 +291,52 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
if msg.Event == config.EventJoinLeave { if msg.Event == config.EventJoinLeave {
m := matrix.TextMessage{ m := matrix.TextMessage{
MsgType: "m.notice", MsgType: "m.notice",
Body: username.plain + msg.Text, Body: body,
FormattedBody: username.formatted + msg.Text, FormattedBody: formattedBody,
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: body,
FormattedBody: formattedBody,
Format: "org.matrix.custom.html",
},
}
if b.GetBool("HTMLDisable") {
m.TextMessage.Format = ""
m.TextMessage.FormattedBody = ""
}
m.RelatedTo = InReplyToRelation{
InReplyTo: InReplyToRelationContent{
EventID: msg.ParentID,
},
} }
var ( var (
@@ -264,7 +363,7 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
) )
err = b.retry(func() error { err = b.retry(func() error {
resp, err = b.mc.SendText(channel, username.plain+msg.Text) resp, err = b.mc.SendText(channel, body)
return err return err
}) })
@@ -282,8 +381,7 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
) )
err = b.retry(func() error { err = b.retry(func() error {
resp, err = b.mc.SendFormattedText(channel, username.plain+msg.Text, resp, err = b.mc.SendFormattedText(channel, body, formattedBody)
username.formatted+helper.ParseMarkdown(msg.Text))
return err return err
}) })
@@ -341,6 +439,38 @@ func (b *Bmatrix) handleEdit(ev *matrix.Event, rmsg config.Message) bool {
return true return true
} }
func (b *Bmatrix) handleReply(ev *matrix.Event, rmsg config.Message) bool {
relationInterface, present := ev.Content["m.relates_to"]
if !present {
return false
}
var relation InReplyToRelation
if err := interface2Struct(relationInterface, &relation); err != nil {
// probably fine
return false
}
body := rmsg.Text
if !b.GetBool("keepquotedreply") {
for strings.HasPrefix(body, "> ") {
lineIdx := strings.IndexRune(body, '\n')
if lineIdx == -1 {
body = ""
} else {
body = body[(lineIdx + 1):]
}
}
}
rmsg.Text = body
rmsg.ParentID = relation.InReplyTo.EventID
b.Remote <- rmsg
return true
}
func (b *Bmatrix) handleMemberChange(ev *matrix.Event) { func (b *Bmatrix) handleMemberChange(ev *matrix.Event) {
// Update the displayname on join messages, according to https://matrix.org/docs/spec/client_server/r0.6.1#events-on-change-of-profile-information // Update the displayname on join messages, according to https://matrix.org/docs/spec/client_server/r0.6.1#events-on-change-of-profile-information
if ev.Content["membership"] == "join" { if ev.Content["membership"] == "join" {
@@ -403,6 +533,11 @@ func (b *Bmatrix) handleEvent(ev *matrix.Event) {
return return
} }
// Is it a reply?
if b.handleReply(ev, rmsg) {
return
}
// Do we have attachments // Do we have attachments
if b.containsAttachment(ev.Content) { if b.containsAttachment(ev.Content) {
err := b.handleDownloadFile(&rmsg, ev.Content) err := b.handleDownloadFile(&rmsg, ev.Content)

View File

@@ -3,10 +3,8 @@ package bmattermost
import ( import (
"github.com/42wim/matterbridge/bridge/config" "github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper" "github.com/42wim/matterbridge/bridge/helper"
"github.com/42wim/matterbridge/matterclient" "github.com/matterbridge/matterclient"
matterclient6 "github.com/matterbridge/matterclient" "github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v5/model"
model6 "github.com/mattermost/mattermost-server/v6/model"
) )
// handleDownloadAvatar downloads the avatar of userid from channel // handleDownloadAvatar downloads the avatar of userid from channel
@@ -26,20 +24,11 @@ func (b *Bmattermost) handleDownloadAvatar(userid string, channel string) {
var ( var (
data []byte data []byte
err error err error
resp *model.Response
) )
if b.mc6 != nil { data, _, err = b.mc.Client.GetProfileImage(userid, "")
data, _, err = b.mc6.Client.GetProfileImage(userid, "") if err != nil {
if err != nil { b.Log.Errorf("ProfileImage download failed for %#v %s", userid, err)
b.Log.Errorf("ProfileImage download failed for %#v %s", userid, err) return
return
}
} else {
data, resp = b.mc.Client.GetProfileImage(userid, "")
if resp.Error != nil {
b.Log.Errorf("ProfileImage download failed for %#v %s", userid, resp.Error)
return
}
} }
err = helper.HandleDownloadSize(b.Log, &rmsg, userid+".png", int64(len(data)), b.General) err = helper.HandleDownloadSize(b.Log, &rmsg, userid+".png", int64(len(data)), b.General)
@@ -52,33 +41,10 @@ func (b *Bmattermost) handleDownloadAvatar(userid string, channel string) {
} }
} }
// handleDownloadFile handles file download //nolint:wrapcheck
func (b *Bmattermost) handleDownloadFile(rmsg *config.Message, id string) error { func (b *Bmattermost) handleDownloadFile(rmsg *config.Message, id string) error {
if b.mc6 != nil { url, _, _ := b.mc.Client.GetFileLink(id)
return b.handleDownloadFile6(rmsg, id) finfo, _, err := b.mc.Client.GetFileInfo(id)
}
url, _ := b.mc.Client.GetFileLink(id)
finfo, resp := b.mc.Client.GetFileInfo(id)
if resp.Error != nil {
return resp.Error
}
err := helper.HandleDownloadSize(b.Log, rmsg, finfo.Name, finfo.Size, b.General)
if err != nil {
return err
}
data, resp := b.mc.Client.DownloadFile(id, true)
if resp.Error != nil {
return resp.Error
}
helper.HandleDownloadData(b.Log, rmsg, finfo.Name, rmsg.Text, url, &data, b.General)
return nil
}
// nolint:wrapcheck
func (b *Bmattermost) handleDownloadFile6(rmsg *config.Message, id string) error {
url, _, _ := b.mc6.Client.GetFileLink(id)
finfo, _, err := b.mc6.Client.GetFileInfo(id)
if err != nil { if err != nil {
return err return err
} }
@@ -86,7 +52,7 @@ func (b *Bmattermost) handleDownloadFile6(rmsg *config.Message, id string) error
if err != nil { if err != nil {
return err return err
} }
data, _, err := b.mc6.Client.DownloadFile(id, true) data, _, err := b.mc.Client.DownloadFile(id, true)
if err != nil { if err != nil {
return err return err
} }
@@ -125,24 +91,24 @@ func (b *Bmattermost) handleMatter() {
} }
} }
//nolint:cyclop
func (b *Bmattermost) handleMatterClient(messages chan *config.Message) { func (b *Bmattermost) handleMatterClient(messages chan *config.Message) {
if b.mc6 != nil {
b.Log.Debug("starting matterclient6")
b.handleMatterClient6(messages)
return
}
for message := range b.mc.MessageChan { for message := range b.mc.MessageChan {
b.Log.Debugf("%#v", message.Raw.Data) b.Log.Debugf("%#v %#v", message.Raw.GetData(), message.Raw.EventType())
if b.skipMessage(message) { if b.skipMessage(message) {
b.Log.Debugf("Skipped message: %#v", message) b.Log.Debugf("Skipped message: %#v", message)
continue continue
} }
channelName := b.getChannelName(message.Post.ChannelId)
if channelName == "" {
channelName = message.Channel
}
// only download avatars if we have a place to upload them (configured mediaserver) // only download avatars if we have a place to upload them (configured mediaserver)
if b.General.MediaServerUpload != "" || b.General.MediaDownloadPath != "" { if b.General.MediaServerUpload != "" || b.General.MediaDownloadPath != "" {
b.handleDownloadAvatar(message.UserID, message.Channel) b.handleDownloadAvatar(message.UserID, channelName)
} }
b.Log.Debugf("== Receiving event %#v", message) b.Log.Debugf("== Receiving event %#v", message)
@@ -150,7 +116,7 @@ func (b *Bmattermost) handleMatterClient(messages chan *config.Message) {
rmsg := &config.Message{ rmsg := &config.Message{
Username: message.Username, Username: message.Username,
UserID: message.UserID, UserID: message.UserID,
Channel: message.Channel, Channel: channelName,
Text: message.Text, Text: message.Text,
ID: message.Post.Id, ID: message.Post.Id,
ParentID: message.Post.RootId, // ParentID is obsolete with mattermost ParentID: message.Post.RootId, // ParentID is obsolete with mattermost
@@ -161,11 +127,11 @@ func (b *Bmattermost) handleMatterClient(messages chan *config.Message) {
b.handleProps(rmsg, message) b.handleProps(rmsg, message)
// create a text for bridges that don't support native editing // create a text for bridges that don't support native editing
if message.Raw.Event == model.WEBSOCKET_EVENT_POST_EDITED && !b.GetBool("EditDisable") { if message.Raw.EventType() == model.WebsocketEventPostEdited && !b.GetBool("EditDisable") {
rmsg.Text = message.Text + b.GetString("EditSuffix") rmsg.Text = message.Text + b.GetString("EditSuffix")
} }
if message.Raw.Event == model.WEBSOCKET_EVENT_POST_DELETED { if message.Raw.EventType() == model.WebsocketEventPostDeleted {
rmsg.Event = config.EventMsgDelete rmsg.Event = config.EventMsgDelete
} }
@@ -177,65 +143,12 @@ func (b *Bmattermost) handleMatterClient(messages chan *config.Message) {
} }
// Use nickname instead of username if defined // Use nickname instead of username if defined
if nick := b.mc.GetNickName(rmsg.UserID); nick != "" { if !b.GetBool("useusername") {
rmsg.Username = nick if nick := b.mc.GetNickName(rmsg.UserID); nick != "" {
} rmsg.Username = nick
messages <- rmsg
}
}
// nolint:cyclop
func (b *Bmattermost) handleMatterClient6(messages chan *config.Message) {
for message := range b.mc6.MessageChan {
b.Log.Debugf("%#v", message.Raw.GetData())
if b.skipMessage6(message) {
b.Log.Debugf("Skipped message: %#v", message)
continue
}
// 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.Log.Debugf("== Receiving event %#v", message)
rmsg := &config.Message{
Username: message.Username,
UserID: message.UserID,
Channel: message.Channel,
Text: message.Text,
ID: message.Post.Id,
ParentID: message.Post.RootId, // ParentID is obsolete with mattermost
Extra: make(map[string][]interface{}),
}
// handle mattermost post properties (override username and attachments)
b.handleProps6(rmsg, message)
// create a text for bridges that don't support native editing
if message.Raw.EventType() == model6.WebsocketEventPostEdited && !b.GetBool("EditDisable") {
rmsg.Text = message.Text + b.GetString("EditSuffix")
}
if message.Raw.EventType() == model6.WebsocketEventPostDeleted {
rmsg.Event = config.EventMsgDelete
}
for _, id := range message.Post.FileIds {
err := b.handleDownloadFile(rmsg, id)
if err != nil {
b.Log.Errorf("download failed: %s", err)
} }
} }
// Use nickname instead of username if defined
if nick := b.mc6.GetNickName(rmsg.UserID); nick != "" {
rmsg.Username = nick
}
messages <- rmsg messages <- rmsg
} }
} }
@@ -244,6 +157,7 @@ func (b *Bmattermost) handleMatterHook(messages chan *config.Message) {
for { for {
message := b.mh.Receive() message := b.mh.Receive()
b.Log.Debugf("Receiving from matterhook %#v", message) b.Log.Debugf("Receiving from matterhook %#v", message)
messages <- &config.Message{ messages <- &config.Message{
UserID: message.UserID, UserID: message.UserID,
Username: message.UserName, Username: message.UserName,
@@ -253,15 +167,10 @@ func (b *Bmattermost) handleMatterHook(messages chan *config.Message) {
} }
} }
// handleUploadFile handles native upload of files
func (b *Bmattermost) handleUploadFile(msg *config.Message) (string, error) { func (b *Bmattermost) handleUploadFile(msg *config.Message) (string, error) {
if b.mc6 != nil {
return b.handleUploadFile6(msg)
}
var err error var err error
var res, id string var res, id string
channelID := b.mc.GetChannelId(msg.Channel, b.TeamID) channelID := b.getChannelID(msg.Channel)
for _, f := range msg.Extra["file"] { for _, f := range msg.Extra["file"] {
fi := f.(config.FileInfo) fi := f.(config.FileInfo)
id, err = b.mc.UploadFile(*fi.Data, channelID, fi.Name) id, err = b.mc.UploadFile(*fi.Data, channelID, fi.Name)
@@ -277,53 +186,8 @@ func (b *Bmattermost) handleUploadFile(msg *config.Message) (string, error) {
return res, err return res, err
} }
// nolint:forcetypeassert,wrapcheck //nolint:forcetypeassert
func (b *Bmattermost) handleUploadFile6(msg *config.Message) (string, error) {
var err error
var res, id string
channelID := b.mc6.GetChannelID(msg.Channel, b.TeamID)
for _, f := range msg.Extra["file"] {
fi := f.(config.FileInfo)
id, err = b.mc6.UploadFile(*fi.Data, channelID, fi.Name)
if err != nil {
return "", err
}
msg.Text = fi.Comment
if b.GetBool("PrefixMessagesWithNick") {
msg.Text = msg.Username + msg.Text
}
res, err = b.mc6.PostMessageWithFiles(channelID, msg.Text, msg.ParentID, []string{id})
}
return res, err
}
func (b *Bmattermost) handleProps(rmsg *config.Message, message *matterclient.Message) { func (b *Bmattermost) handleProps(rmsg *config.Message, message *matterclient.Message) {
props := message.Post.Props
if props == nil {
return
}
if _, ok := props["override_username"].(string); ok {
rmsg.Username = props["override_username"].(string)
}
if _, ok := props["attachments"].([]interface{}); ok {
rmsg.Extra["attachments"] = props["attachments"].([]interface{})
if rmsg.Text == "" {
for _, attachment := range rmsg.Extra["attachments"] {
attach := attachment.(map[string]interface{})
if attach["text"].(string) != "" {
rmsg.Text += attach["text"].(string)
continue
}
if attach["fallback"].(string) != "" {
rmsg.Text += attach["fallback"].(string)
}
}
}
}
}
// nolint:forcetypeassert
func (b *Bmattermost) handleProps6(rmsg *config.Message, message *matterclient6.Message) {
props := message.Post.Props props := message.Post.Props
if props == nil { if props == nil {
return return

View File

@@ -6,11 +6,9 @@ import (
"github.com/42wim/matterbridge/bridge/config" "github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper" "github.com/42wim/matterbridge/bridge/helper"
"github.com/42wim/matterbridge/matterclient"
"github.com/42wim/matterbridge/matterhook" "github.com/42wim/matterbridge/matterhook"
matterclient6 "github.com/matterbridge/matterclient" "github.com/matterbridge/matterclient"
"github.com/mattermost/mattermost-server/v5/model" "github.com/mattermost/mattermost-server/v6/model"
model6 "github.com/mattermost/mattermost-server/v6/model"
) )
func (b *Bmattermost) doConnectWebhookBind() error { func (b *Bmattermost) doConnectWebhookBind() error {
@@ -24,33 +22,15 @@ func (b *Bmattermost) doConnectWebhookBind() error {
}) })
case b.GetString("Token") != "": case b.GetString("Token") != "":
b.Log.Info("Connecting using token (sending)") b.Log.Info("Connecting using token (sending)")
b.Log.Infof("Using mattermost v6 methods: %t", b.v6) err := b.apiLogin()
if err != nil {
if b.v6 { return err
err := b.apiLogin6()
if err != nil {
return err
}
} else {
err := b.apiLogin()
if err != nil {
return err
}
} }
case b.GetString("Login") != "": case b.GetString("Login") != "":
b.Log.Info("Connecting using login/password (sending)") b.Log.Info("Connecting using login/password (sending)")
b.Log.Infof("Using mattermost v6 methods: %t", b.v6) err := b.apiLogin()
if err != nil {
if b.v6 { return err
err := b.apiLogin6()
if err != nil {
return err
}
} else {
err := b.apiLogin()
if err != nil {
return err
}
} }
default: default:
b.Log.Info("Connecting using webhookbindaddress (receiving)") b.Log.Info("Connecting using webhookbindaddress (receiving)")
@@ -72,45 +52,28 @@ func (b *Bmattermost) doConnectWebhookURL() error {
}) })
if b.GetString("Token") != "" { if b.GetString("Token") != "" {
b.Log.Info("Connecting using token (receiving)") b.Log.Info("Connecting using token (receiving)")
b.Log.Infof("Using mattermost v6 methods: %t", b.v6) err := b.apiLogin()
if err != nil {
if b.v6 { return err
err := b.apiLogin6()
if err != nil {
return err
}
} else {
err := b.apiLogin()
if err != nil {
return err
}
} }
} else if b.GetString("Login") != "" { } else if b.GetString("Login") != "" {
b.Log.Info("Connecting using login/password (receiving)") b.Log.Info("Connecting using login/password (receiving)")
b.Log.Infof("Using mattermost v6 methods: %t", b.v6) err := b.apiLogin()
if err != nil {
if b.v6 { return err
err := b.apiLogin6()
if err != nil {
return err
}
} else {
err := b.apiLogin()
if err != nil {
return err
}
} }
} }
return nil return nil
} }
//nolint:wrapcheck
func (b *Bmattermost) apiLogin() error { func (b *Bmattermost) apiLogin() error {
password := b.GetString("Password") password := b.GetString("Password")
if b.GetString("Token") != "" { if b.GetString("Token") != "" {
password = "token=" + b.GetString("Token") password = "token=" + b.GetString("Token")
} }
b.mc = matterclient.New(b.GetString("Login"), password, b.GetString("Team"), b.GetString("Server")) b.mc = matterclient.New(b.GetString("Login"), password, b.GetString("Team"), b.GetString("Server"), "")
if b.GetBool("debug") { if b.GetBool("debug") {
b.mc.SetLogLevel("debug") b.mc.SetLogLevel("debug")
} }
@@ -118,39 +81,13 @@ func (b *Bmattermost) apiLogin() error {
b.mc.SkipVersionCheck = b.GetBool("SkipVersionCheck") b.mc.SkipVersionCheck = b.GetBool("SkipVersionCheck")
b.mc.NoTLS = b.GetBool("NoTLS") b.mc.NoTLS = b.GetBool("NoTLS")
b.Log.Infof("Connecting %s (team: %s) on %s", b.GetString("Login"), b.GetString("Team"), b.GetString("Server")) b.Log.Infof("Connecting %s (team: %s) on %s", b.GetString("Login"), b.GetString("Team"), b.GetString("Server"))
err := b.mc.Login()
if err != nil {
return err
}
b.Log.Info("Connection succeeded")
b.TeamID = b.mc.GetTeamId()
go b.mc.WsReceiver()
go b.mc.StatusLoop()
return nil
}
// nolint:wrapcheck if err := b.mc.Login(); err != nil {
func (b *Bmattermost) apiLogin6() error {
password := b.GetString("Password")
if b.GetString("Token") != "" {
password = "token=" + b.GetString("Token")
}
b.mc6 = matterclient6.New(b.GetString("Login"), password, b.GetString("Team"), b.GetString("Server"), "")
if b.GetBool("debug") {
b.mc6.SetLogLevel("debug")
}
b.mc6.SkipTLSVerify = b.GetBool("SkipTLSVerify")
b.mc6.SkipVersionCheck = b.GetBool("SkipVersionCheck")
b.mc6.NoTLS = b.GetBool("NoTLS")
b.Log.Infof("Connecting %s (team: %s) on %s", b.GetString("Login"), b.GetString("Team"), b.GetString("Server"))
if err := b.mc6.Login(); err != nil {
return err return err
} }
b.Log.Info("Connection succeeded") b.Log.Info("Connection succeeded")
b.TeamID = b.mc6.GetTeamID() b.TeamID = b.mc.GetTeamID()
return nil return nil
} }
@@ -183,6 +120,7 @@ func (b *Bmattermost) sendWebhook(msg config.Message) (string, error) {
if b.GetBool("PrefixMessagesWithNick") { if b.GetBool("PrefixMessagesWithNick") {
msg.Text = msg.Username + msg.Text msg.Text = msg.Username + msg.Text
} }
if msg.Extra != nil { if msg.Extra != nil {
// this sends a message only if we received a config.EVENT_FILE_FAILURE_SIZE // this sends a message only if we received a config.EVENT_FILE_FAILURE_SIZE
for _, rmsg := range helper.HandleExtra(&msg, b.General) { for _, rmsg := range helper.HandleExtra(&msg, b.General) {
@@ -206,7 +144,7 @@ func (b *Bmattermost) sendWebhook(msg config.Message) (string, error) {
for _, f := range msg.Extra["file"] { for _, f := range msg.Extra["file"] {
fi := f.(config.FileInfo) fi := f.(config.FileInfo)
if fi.URL != "" { if fi.URL != "" {
msg.Text += fi.URL msg.Text += " " + fi.URL
} }
} }
} }
@@ -233,6 +171,7 @@ func (b *Bmattermost) sendWebhook(msg config.Message) (string, error) {
} }
// skipMessages returns true if this message should not be handled // skipMessages returns true if this message should not be handled
//nolint:gocyclo,cyclop
func (b *Bmattermost) skipMessage(message *matterclient.Message) bool { func (b *Bmattermost) skipMessage(message *matterclient.Message) bool {
// Handle join/leave // Handle join/leave
if message.Type == "system_join_leave" || if message.Type == "system_join_leave" ||
@@ -241,11 +180,17 @@ func (b *Bmattermost) skipMessage(message *matterclient.Message) bool {
if b.GetBool("nosendjoinpart") { if b.GetBool("nosendjoinpart") {
return true return true
} }
channelName := b.getChannelName(message.Post.ChannelId)
if channelName == "" {
channelName = message.Channel
}
b.Log.Debugf("Sending JOIN_LEAVE event from %s to gateway", b.Account) b.Log.Debugf("Sending JOIN_LEAVE event from %s to gateway", b.Account)
b.Remote <- config.Message{ b.Remote <- config.Message{
Username: "system", Username: "system",
Text: message.Text, Text: message.Text,
Channel: message.Channel, Channel: channelName,
Account: b.Account, Account: b.Account,
Event: config.EventJoinLeave, Event: config.EventJoinLeave,
} }
@@ -253,7 +198,7 @@ func (b *Bmattermost) skipMessage(message *matterclient.Message) bool {
} }
// Handle edited messages // Handle edited messages
if (message.Raw.Event == model.WEBSOCKET_EVENT_POST_EDITED) && b.GetBool("EditDisable") { if (message.Raw.EventType() == model.WebsocketEventPostEdited) && b.GetBool("EditDisable") {
return true return true
} }
@@ -266,76 +211,14 @@ func (b *Bmattermost) skipMessage(message *matterclient.Message) bool {
// Ignore messages sent from matterbridge // Ignore messages sent from matterbridge
if message.Post.Props != nil { if message.Post.Props != nil {
if _, ok := message.Post.Props["matterbridge_"+b.uuid].(bool); ok { 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 return true
} }
} }
// Ignore messages sent from a user logged in as the bot // Ignore messages sent from a user logged in as the bot
if b.mc.User.Username == message.Username { if b.mc.User.Username == message.Username {
return true b.Log.Debug("message from same user as bot, ignoring")
}
// if the message has reactions don't repost it (for now, until we can correlate reaction with message)
if message.Post.HasReactions {
return true
}
// ignore messages from other teams than ours
if message.Raw.Data["team_id"].(string) != b.TeamID {
return true
}
// only handle posted, edited or deleted events
if !(message.Raw.Event == "posted" || message.Raw.Event == model.WEBSOCKET_EVENT_POST_EDITED ||
message.Raw.Event == model.WEBSOCKET_EVENT_POST_DELETED) {
return true
}
return false
}
// skipMessages returns true if this message should not be handled
// nolint:gocyclo,cyclop
func (b *Bmattermost) skipMessage6(message *matterclient6.Message) bool {
// Handle join/leave
if message.Type == "system_join_leave" ||
message.Type == "system_join_channel" ||
message.Type == "system_leave_channel" {
if b.GetBool("nosendjoinpart") {
return true
}
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,
Account: b.Account,
Event: config.EventJoinLeave,
}
return true
}
// Handle edited messages
if (message.Raw.EventType() == model6.WebsocketEventPostEdited) && b.GetBool("EditDisable") {
return true
}
// Ignore non-post messages
if message.Post == nil {
b.Log.Debugf("ignoring nil message.Post: %#v", message)
return true
}
// Ignore messages sent from matterbridge
if message.Post.Props != nil {
if _, ok := message.Post.Props["matterbridge_"+b.uuid].(bool); ok {
b.Log.Debugf("sent by matterbridge, ignoring")
return true
}
}
// Ignore messages sent from a user logged in as the bot
if b.mc6.User.Username == message.Username {
return true return true
} }
@@ -346,12 +229,13 @@ func (b *Bmattermost) skipMessage6(message *matterclient6.Message) bool {
// ignore messages from other teams than ours // ignore messages from other teams than ours
if message.Raw.GetData()["team_id"].(string) != b.TeamID { if message.Raw.GetData()["team_id"].(string) != b.TeamID {
b.Log.Debug("message from other team, ignoring")
return true return true
} }
// only handle posted, edited or deleted events // only handle posted, edited or deleted events
if !(message.Raw.EventType() == "posted" || message.Raw.EventType() == model6.WebsocketEventPostEdited || if !(message.Raw.EventType() == "posted" || message.Raw.EventType() == model.WebsocketEventPostEdited ||
message.Raw.EventType() == model6.WebsocketEventPostDeleted) { message.Raw.EventType() == model.WebsocketEventPostDeleted) {
return true return true
} }
return false return false
@@ -374,3 +258,26 @@ func (b *Bmattermost) getVersion() string {
return resp.Header.Get("X-Version-Id") 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]
}
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 ""
}

View File

@@ -4,31 +4,36 @@ import (
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
"sync"
"github.com/42wim/matterbridge/bridge" "github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config" "github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper" "github.com/42wim/matterbridge/bridge/helper"
"github.com/42wim/matterbridge/matterclient"
"github.com/42wim/matterbridge/matterhook" "github.com/42wim/matterbridge/matterhook"
matterclient6 "github.com/matterbridge/matterclient" "github.com/matterbridge/matterclient"
"github.com/rs/xid" "github.com/rs/xid"
) )
type Bmattermost struct { type Bmattermost struct {
mh *matterhook.Client mh *matterhook.Client
mc *matterclient.MMClient mc *matterclient.Client
mc6 *matterclient6.Client
v6 bool v6 bool
uuid string uuid string
TeamID string TeamID string
*bridge.Config *bridge.Config
avatarMap map[string]string avatarMap map[string]string
channelsMutex sync.RWMutex
channelInfoMap map[string]*config.ChannelInfo
} }
const mattermostPlugin = "mattermost.plugin" const mattermostPlugin = "mattermost.plugin"
func New(cfg *bridge.Config) bridge.Bridger { func New(cfg *bridge.Config) bridge.Bridger {
b := &Bmattermost{Config: cfg, avatarMap: make(map[string]string)} b := &Bmattermost{
Config: cfg,
avatarMap: make(map[string]string),
channelInfoMap: make(map[string]*config.ChannelInfo),
}
b.v6 = b.GetBool("v6") b.v6 = b.GetBool("v6")
b.uuid = xid.New().String() b.uuid = xid.New().String()
@@ -45,7 +50,7 @@ func (b *Bmattermost) Connect() error {
return nil return nil
} }
if strings.HasPrefix(b.getVersion(), "6.") { if strings.HasPrefix(b.getVersion(), "6.") || strings.HasPrefix(b.getVersion(), "7.") {
if !b.v6 { if !b.v6 {
b.v6 = true b.v6 = true
} }
@@ -67,34 +72,17 @@ func (b *Bmattermost) Connect() error {
return nil return nil
case b.GetString("Token") != "": case b.GetString("Token") != "":
b.Log.Info("Connecting using token (sending and receiving)") b.Log.Info("Connecting using token (sending and receiving)")
b.Log.Infof("Using mattermost v6 methods: %t", b.v6) err := b.apiLogin()
if err != nil {
if b.v6 { return err
err := b.apiLogin6()
if err != nil {
return err
}
} else {
err := b.apiLogin()
if err != nil {
return err
}
} }
go b.handleMatter() go b.handleMatter()
case b.GetString("Login") != "": case b.GetString("Login") != "":
b.Log.Info("Connecting using login/password (sending and receiving)") b.Log.Info("Connecting using login/password (sending and receiving)")
b.Log.Infof("Using mattermost v6 methods: %t", b.v6) b.Log.Infof("Using mattermost v6 methods: %t", b.v6)
err := b.apiLogin()
if b.v6 { if err != nil {
err := b.apiLogin6() return err
if err != nil {
return err
}
} else {
err := b.apiLogin()
if err != nil {
return err
}
} }
go b.handleMatter() go b.handleMatter()
} }
@@ -113,24 +101,21 @@ func (b *Bmattermost) JoinChannel(channel config.ChannelInfo) error {
if b.Account == mattermostPlugin { if b.Account == mattermostPlugin {
return nil return nil
} }
b.channelsMutex.Lock()
b.channelInfoMap[channel.ID] = &channel
b.channelsMutex.Unlock()
// we can only join channels using the API // we can only join channels using the API
if b.GetString("WebhookURL") == "" && b.GetString("WebhookBindAddress") == "" { if b.GetString("WebhookURL") == "" && b.GetString("WebhookBindAddress") == "" {
var id string id := b.getChannelID(channel.Name)
if b.mc6 != nil {
id = b.mc6.GetChannelID(channel.Name, b.TeamID)
} else {
id = b.mc.GetChannelId(channel.Name, b.TeamID)
}
if id == "" { if id == "" {
return fmt.Errorf("Could not find channel ID for channel %s", channel.Name) return fmt.Errorf("Could not find channel ID for channel %s", channel.Name)
} }
if b.mc6 != nil {
return b.mc6.JoinChannel(id) // nolint:wrapcheck
}
return b.mc.JoinChannel(id) return b.mc.JoinChannel(id)
} }
return nil return nil
} }
@@ -160,9 +145,6 @@ func (b *Bmattermost) Send(msg config.Message) (string, error) {
if msg.ID == "" { if msg.ID == "" {
return "", nil return "", nil
} }
if b.mc6 != nil {
return msg.ID, b.mc6.DeleteMessage(msg.ID) // nolint:wrapcheck
}
return msg.ID, b.mc.DeleteMessage(msg.ID) return msg.ID, b.mc.DeleteMessage(msg.ID)
} }
@@ -175,17 +157,11 @@ func (b *Bmattermost) Send(msg config.Message) (string, error) {
// we only can reply to the root of the thread, not to a specific ID (like discord for example does) // we only can reply to the root of the thread, not to a specific ID (like discord for example does)
if msg.ParentID != "" { if msg.ParentID != "" {
if b.mc6 != nil { post, _, err := b.mc.Client.GetPost(msg.ParentID, "")
post, _, err := b.mc6.Client.GetPost(msg.ParentID, "") if err != nil {
if err != nil { b.Log.Errorf("getting post %s failed: %s", msg.ParentID, err)
b.Log.Errorf("getting post %s failed: %s", msg.ParentID, err) }
} if post.RootId != "" {
msg.ParentID = post.RootId
} else {
post, res := b.mc.Client.GetPost(msg.ParentID, "")
if res.Error != nil {
b.Log.Errorf("getting post %s failed: %s", msg.ParentID, res.Error.DetailedError)
}
msg.ParentID = post.RootId msg.ParentID = post.RootId
} }
} }
@@ -193,14 +169,8 @@ func (b *Bmattermost) Send(msg config.Message) (string, error) {
// Upload a file if it exists // Upload a file if it exists
if msg.Extra != nil { if msg.Extra != nil {
for _, rmsg := range helper.HandleExtra(&msg, b.General) { for _, rmsg := range helper.HandleExtra(&msg, b.General) {
if b.mc6 != nil { if _, err := b.mc.PostMessage(b.getChannelID(rmsg.Channel), rmsg.Username+rmsg.Text, msg.ParentID); err != nil {
if _, err := b.mc6.PostMessage(b.mc.GetChannelId(rmsg.Channel, b.TeamID), rmsg.Username+rmsg.Text, msg.ParentID); err != nil { b.Log.Errorf("PostMessage failed: %s", err)
b.Log.Errorf("PostMessage failed: %s", err)
}
} else {
if _, err := b.mc.PostMessage(b.mc.GetChannelId(rmsg.Channel, b.TeamID), rmsg.Username+rmsg.Text, msg.ParentID); err != nil {
b.Log.Errorf("PostMessage failed: %s", err)
}
} }
} }
if len(msg.Extra["file"]) > 0 { if len(msg.Extra["file"]) > 0 {
@@ -215,17 +185,9 @@ func (b *Bmattermost) Send(msg config.Message) (string, error) {
// Edit message if we have an ID // Edit message if we have an ID
if msg.ID != "" { if msg.ID != "" {
if b.mc6 != nil {
return b.mc6.EditMessage(msg.ID, msg.Text) // nolint:wrapcheck
}
return b.mc.EditMessage(msg.ID, msg.Text) return b.mc.EditMessage(msg.ID, msg.Text)
} }
// Post normal message // Post normal message
if b.mc6 != nil { return b.mc.PostMessage(b.getChannelID(msg.Channel), msg.Text, msg.ParentID)
return b.mc6.PostMessage(b.mc6.GetChannelID(msg.Channel, b.TeamID), msg.Text, msg.ParentID) // nolint:wrapcheck
}
return b.mc.PostMessage(b.mc.GetChannelId(msg.Channel, b.TeamID), msg.Text, msg.ParentID)
} }

70
bridge/mumble/codec.go Normal file
View File

@@ -0,0 +1,70 @@
package bmumble
import (
"fmt"
"layeh.com/gumble/gumble"
)
// This is a dummy implementation of a Gumble audio codec which claims
// to implement Opus, but does not actually do anything. This serves
// as a workaround until https://github.com/layeh/gumble/pull/61 is
// merged.
// See https://github.com/42wim/matterbridge/issues/1750 for details.
const (
audioCodecIDOpus = 4
)
func registerNullCodecAsOpus() {
codec := &NullCodec{
encoder: &NullAudioEncoder{},
decoder: &NullAudioDecoder{},
}
gumble.RegisterAudioCodec(audioCodecIDOpus, codec)
}
type NullCodec struct {
encoder *NullAudioEncoder
decoder *NullAudioDecoder
}
func (c *NullCodec) ID() int {
return audioCodecIDOpus
}
func (c *NullCodec) NewEncoder() gumble.AudioEncoder {
e := &NullAudioEncoder{}
return e
}
func (c *NullCodec) NewDecoder() gumble.AudioDecoder {
d := &NullAudioDecoder{}
return d
}
type NullAudioEncoder struct{}
func (e *NullAudioEncoder) ID() int {
return audioCodecIDOpus
}
func (e *NullAudioEncoder) Encode(pcm []int16, mframeSize, maxDataBytes int) ([]byte, error) {
return nil, fmt.Errorf("not implemented")
}
func (e *NullAudioEncoder) Reset() {
}
type NullAudioDecoder struct{}
func (d *NullAudioDecoder) ID() int {
return audioCodecIDOpus
}
func (d *NullAudioDecoder) Decode(data []byte, frameSize int) ([]int16, error) {
return nil, fmt.Errorf("not implemented")
}
func (d *NullAudioDecoder) Reset() {
}

View File

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

View File

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

View File

@@ -27,7 +27,8 @@ func (b *Bslack) handleSlack() {
b.Log.Debug("Start listening for Slack messages") b.Log.Debug("Start listening for Slack messages")
for message := range messages { for message := range messages {
// don't do any action on deleted/typing messages // don't do any action on deleted/typing messages
if message.Event != config.EventUserTyping && message.Event != config.EventMsgDelete { if message.Event != config.EventUserTyping && message.Event != config.EventMsgDelete &&
message.Event != config.EventFileDelete {
b.Log.Debugf("<= Sending message from %s on %s to gateway", message.Username, b.Account) b.Log.Debugf("<= Sending message from %s on %s to gateway", message.Username, b.Account)
// cleanup the message // cleanup the message
message.Text = b.replaceMention(message.Text) message.Text = b.replaceMention(message.Text)
@@ -76,6 +77,13 @@ func (b *Bslack) handleSlackClient(messages chan *config.Message) {
continue continue
} }
messages <- rmsg messages <- rmsg
case *slack.FileDeletedEvent:
rmsg, err := b.handleFileDeletedEvent(ev)
if err != nil {
b.Log.Printf("%#v", err)
continue
}
messages <- rmsg
case *slack.OutgoingErrorEvent: case *slack.OutgoingErrorEvent:
b.Log.Debugf("%#v", ev.Error()) b.Log.Debugf("%#v", ev.Error())
case *slack.ChannelJoinedEvent: case *slack.ChannelJoinedEvent:
@@ -222,6 +230,26 @@ func (b *Bslack) handleMessageEvent(ev *slack.MessageEvent) (*config.Message, er
return rmsg, nil return rmsg, nil
} }
func (b *Bslack) handleFileDeletedEvent(ev *slack.FileDeletedEvent) (*config.Message, error) {
if rawChannel, ok := b.cache.Get(cfileDownloadChannel + ev.FileID); ok {
channel, err := b.channels.getChannelByID(rawChannel.(string))
if err != nil {
return nil, err
}
return &config.Message{
Event: config.EventFileDelete,
Text: config.EventFileDelete,
Channel: channel.Name,
Account: b.Account,
ID: ev.FileID,
Protocol: b.Protocol,
}, nil
}
return nil, fmt.Errorf("channel ID for file ID %s not found", ev.FileID)
}
func (b *Bslack) handleStatusEvent(ev *slack.MessageEvent, rmsg *config.Message) bool { func (b *Bslack) handleStatusEvent(ev *slack.MessageEvent, rmsg *config.Message) bool {
switch ev.SubType { switch ev.SubType {
case sChannelJoined, sMemberJoined: case sChannelJoined, sMemberJoined:
@@ -254,6 +282,13 @@ func (b *Bslack) handleStatusEvent(ev *slack.MessageEvent, rmsg *config.Message)
return false return false
} }
func getMessageTitle(attach *slack.Attachment) string {
if attach.TitleLink != "" {
return fmt.Sprintf("[%s](%s)\n", attach.Title, attach.TitleLink)
}
return attach.Title
}
func (b *Bslack) handleAttachments(ev *slack.MessageEvent, rmsg *config.Message) { func (b *Bslack) handleAttachments(ev *slack.MessageEvent, rmsg *config.Message) {
// File comments are set by the system (because there is no username given). // File comments are set by the system (because there is no username given).
if ev.SubType == sFileComment { if ev.SubType == sFileComment {
@@ -262,12 +297,15 @@ func (b *Bslack) handleAttachments(ev *slack.MessageEvent, rmsg *config.Message)
// See if we have some text in the attachments. // See if we have some text in the attachments.
if rmsg.Text == "" { if rmsg.Text == "" {
for _, attach := range ev.Attachments { for i, attach := range ev.Attachments {
if attach.Text != "" { if attach.Text != "" {
if attach.Title != "" { if attach.Title != "" {
rmsg.Text = attach.Title + "\n" rmsg.Text = getMessageTitle(&ev.Attachments[i])
} }
rmsg.Text += attach.Text rmsg.Text += attach.Text
if attach.Footer != "" {
rmsg.Text += "\n\n" + attach.Footer
}
} else { } else {
rmsg.Text = attach.Fallback rmsg.Text = attach.Fallback
} }
@@ -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. // If we have files attached, download them (in memory) and put a pointer to it in msg.Extra.
for i := range ev.Files { for i := range ev.Files {
// keep reference in cache on which channel we added this file
b.cache.Add(cfileDownloadChannel+ev.Files[i].ID, ev.Channel)
if err := b.handleDownloadFile(rmsg, &ev.Files[i], false); err != nil { if err := b.handleDownloadFile(rmsg, &ev.Files[i], false); err != nil {
b.Log.Errorf("Could not download incoming file: %#v", err) b.Log.Errorf("Could not download incoming file: %#v", err)
} }
@@ -330,7 +370,7 @@ func (b *Bslack) handleDownloadFile(rmsg *config.Message, file *slack.File, retr
// that the comment is not duplicated. // that the comment is not duplicated.
comment := rmsg.Text comment := rmsg.Text
rmsg.Text = "" rmsg.Text = ""
helper.HandleDownloadData(b.Log, rmsg, file.Name, comment, file.URLPrivateDownload, data, b.General) helper.HandleDownloadData2(b.Log, rmsg, file.Name, file.ID, comment, file.URLPrivateDownload, data, b.General)
return nil return nil
} }

View File

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

View File

@@ -36,24 +36,25 @@ type Bslack struct {
} }
const ( const (
sHello = "hello" sHello = "hello"
sChannelJoin = "channel_join" sChannelJoin = "channel_join"
sChannelLeave = "channel_leave" sChannelLeave = "channel_leave"
sChannelJoined = "channel_joined" sChannelJoined = "channel_joined"
sMemberJoined = "member_joined_channel" sMemberJoined = "member_joined_channel"
sMessageChanged = "message_changed" sMessageChanged = "message_changed"
sMessageDeleted = "message_deleted" sMessageDeleted = "message_deleted"
sSlackAttachment = "slack_attachment" sSlackAttachment = "slack_attachment"
sPinnedItem = "pinned_item" sPinnedItem = "pinned_item"
sUnpinnedItem = "unpinned_item" sUnpinnedItem = "unpinned_item"
sChannelTopic = "channel_topic" sChannelTopic = "channel_topic"
sChannelPurpose = "channel_purpose" sChannelPurpose = "channel_purpose"
sFileComment = "file_comment" sFileComment = "file_comment"
sMeMessage = "me_message" sMeMessage = "me_message"
sUserTyping = "user_typing" sUserTyping = "user_typing"
sLatencyReport = "latency_report" sLatencyReport = "latency_report"
sSystemUser = "system" sSystemUser = "system"
sSlackBotUser = "slackbot" sSlackBotUser = "slackbot"
cfileDownloadChannel = "file_download_channel"
tokenConfig = "Token" tokenConfig = "Token"
incomingWebhookConfig = "WebhookBindAddress" incomingWebhookConfig = "WebhookBindAddress"
@@ -320,7 +321,7 @@ func (b *Bslack) sendRTM(msg config.Message) (string, error) {
} }
// Upload a file if it exists. // Upload a file if it exists.
if msg.Extra != nil { if len(msg.Extra) > 0 {
extraMsgs := helper.HandleExtra(&msg, b.General) extraMsgs := helper.HandleExtra(&msg, b.General)
for i := range extraMsgs { for i := range extraMsgs {
rmsg := &extraMsgs[i] rmsg := &extraMsgs[i]
@@ -331,7 +332,7 @@ func (b *Bslack) sendRTM(msg config.Message) (string, error) {
} }
} }
// Upload files if necessary (from Slack, Telegram or Mattermost). // Upload files if necessary (from Slack, Telegram or Mattermost).
b.uploadFile(&msg, channelInfo.ID) return b.uploadFile(&msg, channelInfo.ID)
} }
// Post message. // Post message.
@@ -442,7 +443,8 @@ func (b *Bslack) postMessage(msg *config.Message, channelInfo *slack.Channel) (s
} }
// uploadFile handles native upload of files // uploadFile handles native upload of files
func (b *Bslack) uploadFile(msg *config.Message, channelID string) { func (b *Bslack) uploadFile(msg *config.Message, channelID string) (string, error) {
var messageID string
for _, f := range msg.Extra["file"] { for _, f := range msg.Extra["file"] {
fi, ok := f.(config.FileInfo) fi, ok := f.(config.FileInfo)
if !ok { if !ok {
@@ -470,13 +472,22 @@ func (b *Bslack) uploadFile(msg *config.Message, channelID string) {
}) })
if err != nil { if err != nil {
b.Log.Errorf("uploadfile %#v", err) b.Log.Errorf("uploadfile %#v", err)
return return "", err
} }
if res.ID != "" { if res.ID != "" {
b.Log.Debugf("Adding file ID %s to cache with timestamp %s", res.ID, ts.String()) b.Log.Debugf("Adding file ID %s to cache with timestamp %s", res.ID, ts.String())
b.cache.Add("file"+res.ID, ts) b.cache.Add("file"+res.ID, ts)
// search for message id by uploaded file in private/public channels, get thread timestamp from uploaded file
if v, ok := res.Shares.Private[channelID]; ok && len(v) > 0 {
messageID = v[0].Ts
}
if v, ok := res.Shares.Public[channelID]; ok && len(v) > 0 {
messageID = v[0].Ts
}
} }
} }
return messageID, nil
} }
func (b *Bslack) prepareMessageOptions(msg *config.Message) []slack.MsgOption { func (b *Bslack) prepareMessageOptions(msg *config.Message) []slack.MsgOption {

View File

@@ -291,6 +291,7 @@ func (b *channels) populateChannels(wait bool) {
queryParams := &slack.GetConversationsParameters{ queryParams := &slack.GetConversationsParameters{
ExcludeArchived: true, ExcludeArchived: true,
Types: []string{"public_channel,private_channel"}, Types: []string{"public_channel,private_channel"},
Limit: 1000,
} }
for { for {
channels, nextCursor, err := b.sc.GetConversations(queryParams) channels, nextCursor, err := b.sc.GetConversations(queryParams)

View File

@@ -1,6 +1,7 @@
package btelegram package btelegram
import ( import (
"fmt"
"html" "html"
"path/filepath" "path/filepath"
"strconv" "strconv"
@@ -9,14 +10,27 @@ import (
"github.com/42wim/matterbridge/bridge/config" "github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper" "github.com/42wim/matterbridge/bridge/helper"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api" "github.com/davecgh/go-spew/spew"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
) )
func (b *Btelegram) handleUpdate(rmsg *config.Message, message, posted, edited *tgbotapi.Message) *tgbotapi.Message { func (b *Btelegram) handleUpdate(rmsg *config.Message, message, posted, edited *tgbotapi.Message) *tgbotapi.Message {
// handle channels // handle channels
if posted != nil { if posted != nil {
message = posted if posted.Text == "/chatId" {
rmsg.Text = message.Text chatID := strconv.FormatInt(posted.Chat.ID, 10)
_, err := b.Send(config.Message{
Channel: chatID,
Text: fmt.Sprintf("ID of this chat: %s", chatID),
})
if err != nil {
b.Log.Warnf("Unable to send chatID to %s", chatID)
}
} else {
message = posted
rmsg.Text = message.Text
}
} }
// edited channel message // edited channel message
@@ -43,6 +57,11 @@ func (b *Btelegram) handleForwarded(rmsg *config.Message, message *tgbotapi.Mess
return return
} }
if message.ForwardFromChat != nil && message.ForwardFrom == nil {
rmsg.Text = "Forwarded from " + message.ForwardFromChat.Title + ": " + rmsg.Text
return
}
if message.ForwardFrom == nil { if message.ForwardFrom == nil {
rmsg.Text = "Forwarded from " + unknownUser + ": " + rmsg.Text rmsg.Text = "Forwarded from " + unknownUser + ": " + rmsg.Text
return return
@@ -52,6 +71,9 @@ func (b *Btelegram) handleForwarded(rmsg *config.Message, message *tgbotapi.Mess
if b.GetBool("UseFirstName") { if b.GetBool("UseFirstName") {
usernameForward = message.ForwardFrom.FirstName usernameForward = message.ForwardFrom.FirstName
} }
if b.GetBool("UseFullName") {
usernameForward = message.ForwardFrom.FirstName + " " + message.ForwardFrom.LastName
}
if usernameForward == "" { if usernameForward == "" {
usernameForward = message.ForwardFrom.UserName usernameForward = message.ForwardFrom.UserName
@@ -75,6 +97,9 @@ func (b *Btelegram) handleQuoting(rmsg *config.Message, message *tgbotapi.Messag
if b.GetBool("UseFirstName") { if b.GetBool("UseFirstName") {
usernameReply = message.ReplyToMessage.From.FirstName usernameReply = message.ReplyToMessage.From.FirstName
} }
if b.GetBool("UseFullName") {
usernameReply = message.ReplyToMessage.From.FirstName + " " + message.ReplyToMessage.From.LastName
}
if usernameReply == "" { if usernameReply == "" {
usernameReply = message.ReplyToMessage.From.UserName usernameReply = message.ReplyToMessage.From.UserName
if usernameReply == "" { if usernameReply == "" {
@@ -86,7 +111,11 @@ func (b *Btelegram) handleQuoting(rmsg *config.Message, message *tgbotapi.Messag
usernameReply = unknownUser usernameReply = unknownUser
} }
if !b.GetBool("QuoteDisable") { if !b.GetBool("QuoteDisable") {
rmsg.Text = b.handleQuote(rmsg.Text, usernameReply, message.ReplyToMessage.Text) quote := message.ReplyToMessage.Text
if quote == "" {
quote = message.ReplyToMessage.Caption
}
rmsg.Text = b.handleQuote(rmsg.Text, usernameReply, quote)
} }
} }
} }
@@ -94,10 +123,13 @@ func (b *Btelegram) handleQuoting(rmsg *config.Message, message *tgbotapi.Messag
// handleUsername handles the correct setting of the username // handleUsername handles the correct setting of the username
func (b *Btelegram) handleUsername(rmsg *config.Message, message *tgbotapi.Message) { func (b *Btelegram) handleUsername(rmsg *config.Message, message *tgbotapi.Message) {
if message.From != nil { if message.From != nil {
rmsg.UserID = strconv.Itoa(message.From.ID) rmsg.UserID = strconv.FormatInt(message.From.ID, 10)
if b.GetBool("UseFirstName") { if b.GetBool("UseFirstName") {
rmsg.Username = message.From.FirstName rmsg.Username = message.From.FirstName
} }
if b.GetBool("UseFullName") {
rmsg.Username = message.From.FirstName + " " + message.From.LastName
}
if rmsg.Username == "" { if rmsg.Username == "" {
rmsg.Username = message.From.UserName rmsg.Username = message.From.UserName
if rmsg.Username == "" { if rmsg.Username == "" {
@@ -110,6 +142,28 @@ func (b *Btelegram) handleUsername(rmsg *config.Message, message *tgbotapi.Messa
} }
} }
if message.SenderChat != nil { //nolint:nestif
rmsg.UserID = strconv.FormatInt(message.SenderChat.ID, 10)
if b.GetBool("UseFirstName") {
rmsg.Username = message.SenderChat.FirstName
}
if b.GetBool("UseFullName") {
rmsg.Username = message.SenderChat.FirstName + " " + message.SenderChat.LastName
}
if rmsg.Username == "" || rmsg.Username == "Channel_Bot" {
rmsg.Username = message.SenderChat.UserName
if rmsg.Username == "" || rmsg.Username == "Channel_Bot" {
rmsg.Username = message.SenderChat.FirstName
}
}
// only download avatars if we have a place to upload them (configured mediaserver)
if b.General.MediaServerUpload != "" || (b.General.MediaServerDownload != "" && b.General.MediaDownloadPath != "") {
b.handleDownloadAvatar(message.SenderChat.ID, rmsg.Channel)
}
}
// if we really didn't find a username, set it to unknown // if we really didn't find a username, set it to unknown
if rmsg.Username == "" { if rmsg.Username == "" {
rmsg.Username = unknownUser rmsg.Username = unknownUser
@@ -122,10 +176,14 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
if update.Message == nil && update.ChannelPost == nil && if update.Message == nil && update.ChannelPost == nil &&
update.EditedMessage == nil && update.EditedChannelPost == nil { update.EditedMessage == nil && update.EditedChannelPost == nil {
b.Log.Error("Getting nil messages, this shouldn't happen.") b.Log.Info("Received event without messages, skipping.")
continue continue
} }
if b.GetInt("debuglevel") == 1 {
spew.Dump(update.Message)
}
var message *tgbotapi.Message var message *tgbotapi.Message
rmsg := config.Message{Account: b.Account, Extra: make(map[string][]interface{})} rmsg := config.Message{Account: b.Account, Extra: make(map[string][]interface{})}
@@ -145,6 +203,14 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
rmsg.ID = strconv.Itoa(message.MessageID) rmsg.ID = strconv.Itoa(message.MessageID)
rmsg.Channel = strconv.FormatInt(message.Chat.ID, 10) rmsg.Channel = strconv.FormatInt(message.Chat.ID, 10)
// preserve threading from telegram reply
if message.ReplyToMessage != nil {
rmsg.ParentID = strconv.Itoa(message.ReplyToMessage.MessageID)
}
// handle entities (adding URLs)
b.handleEntities(&rmsg, message)
// handle username // handle username
b.handleUsername(&rmsg, message) b.handleUsername(&rmsg, message)
@@ -160,14 +226,12 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
// quote the previous message // quote the previous message
b.handleQuoting(&rmsg, message) b.handleQuoting(&rmsg, message)
// handle entities (adding URLs)
b.handleEntities(&rmsg, message)
if rmsg.Text != "" || len(rmsg.Extra) > 0 { if rmsg.Text != "" || len(rmsg.Extra) > 0 {
rmsg.Text = helper.RemoveEmptyNewLines(rmsg.Text) // Comment the next line out due to avoid removing empty lines in Telegram
// rmsg.Text = helper.RemoveEmptyNewLines(rmsg.Text)
// channels don't have (always?) user information. see #410 // channels don't have (always?) user information. see #410
if message.From != nil { if message.From != nil {
rmsg.Avatar = helper.GetAvatar(b.avatarMap, strconv.Itoa(message.From.ID), b.General) rmsg.Avatar = helper.GetAvatar(b.avatarMap, strconv.FormatInt(message.From.ID, 10), b.General)
} }
b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account) b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account)
@@ -180,42 +244,44 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
// handleDownloadAvatar downloads the avatar of userid from channel // handleDownloadAvatar downloads the avatar of userid from channel
// sends a EVENT_AVATAR_DOWNLOAD message to the gateway if successful. // sends a EVENT_AVATAR_DOWNLOAD message to the gateway if successful.
// logs an error message if it fails // logs an error message if it fails
func (b *Btelegram) handleDownloadAvatar(userid int, channel string) { func (b *Btelegram) handleDownloadAvatar(userid int64, channel string) {
rmsg := config.Message{ rmsg := config.Message{
Username: "system", Username: "system",
Text: "avatar", Text: "avatar",
Channel: channel, Channel: channel,
Account: b.Account, Account: b.Account,
UserID: strconv.Itoa(userid), UserID: strconv.FormatInt(userid, 10),
Event: config.EventAvatarDownload, Event: config.EventAvatarDownload,
Extra: make(map[string][]interface{}), Extra: make(map[string][]interface{}),
} }
if _, ok := b.avatarMap[strconv.Itoa(userid)]; !ok { if _, ok := b.avatarMap[strconv.FormatInt(userid, 10)]; ok {
photos, err := b.c.GetUserProfilePhotos(tgbotapi.UserProfilePhotosConfig{UserID: userid, Limit: 1}) return
}
photos, err := b.c.GetUserProfilePhotos(tgbotapi.UserProfilePhotosConfig{UserID: userid, Limit: 1})
if err != nil {
b.Log.Errorf("Userprofile download failed for %#v %s", userid, err)
}
if len(photos.Photos) > 0 {
photo := photos.Photos[0][0]
url := b.getFileDirectURL(photo.FileID)
name := strconv.FormatInt(userid, 10) + ".png"
b.Log.Debugf("trying to download %#v fileid %#v with size %#v", name, photo.FileID, photo.FileSize)
err := helper.HandleDownloadSize(b.Log, &rmsg, name, int64(photo.FileSize), b.General)
if err != nil { if err != nil {
b.Log.Errorf("Userprofile download failed for %#v %s", userid, err) b.Log.Error(err)
return
} }
data, err := helper.DownloadFile(url)
if len(photos.Photos) > 0 { if err != nil {
photo := photos.Photos[0][0] b.Log.Errorf("download %s failed %#v", url, err)
url := b.getFileDirectURL(photo.FileID) return
name := strconv.Itoa(userid) + ".png"
b.Log.Debugf("trying to download %#v fileid %#v with size %#v", name, photo.FileID, photo.FileSize)
err := helper.HandleDownloadSize(b.Log, &rmsg, name, int64(photo.FileSize), b.General)
if err != nil {
b.Log.Error(err)
return
}
data, err := helper.DownloadFile(url)
if err != nil {
b.Log.Errorf("download %s failed %#v", url, err)
return
}
helper.HandleDownloadData(b.Log, &rmsg, name, rmsg.Text, "", data, b.General)
b.Remote <- rmsg
} }
helper.HandleDownloadData(b.Log, &rmsg, name, rmsg.Text, "", data, b.General)
b.Remote <- rmsg
} }
} }
@@ -272,7 +338,7 @@ func (b *Btelegram) handleDownload(rmsg *config.Message, message *tgbotapi.Messa
name = message.Document.FileName name = message.Document.FileName
text = " " + message.Document.FileName + " : " + url text = " " + message.Document.FileName + " : " + url
case message.Photo != nil: case message.Photo != nil:
photos := *message.Photo photos := message.Photo
size = photos[len(photos)-1].FileSize size = photos[len(photos)-1].FileSize
text, name, url = b.getDownloadInfo(photos[len(photos)-1].FileID, "", true) text, name, url = b.getDownloadInfo(photos[len(photos)-1].FileID, "", true)
} }
@@ -319,7 +385,7 @@ func (b *Btelegram) getDownloadInfo(id string, suffix string, urlpart bool) (str
urlPart := strings.Split(url, "/") urlPart := strings.Split(url, "/")
name = urlPart[len(urlPart)-1] name = urlPart[len(urlPart)-1]
} }
if suffix != "" && !strings.HasSuffix(name, suffix) { if suffix != "" && !strings.HasSuffix(name, suffix) && !strings.HasSuffix(name, ".webm") {
name += suffix name += suffix
} }
text := " " + url text := " " + url
@@ -331,11 +397,15 @@ func (b *Btelegram) handleDelete(msg *config.Message, chatid int64) (string, err
if msg.ID == "" { if msg.ID == "" {
return "", nil return "", nil
} }
msgid, err := strconv.Atoi(msg.ID) msgid, err := strconv.Atoi(msg.ID)
if err != nil { if err != nil {
return "", err return "", err
} }
_, err = b.c.DeleteMessage(tgbotapi.DeleteMessageConfig{ChatID: chatid, MessageID: msgid})
cfg := tgbotapi.NewDeleteMessage(chatid, msgid)
_, err = b.c.Request(cfg)
return "", err return "", err
} }
@@ -373,42 +443,57 @@ func (b *Btelegram) handleEdit(msg *config.Message, chatid int64) (string, error
} }
// handleUploadFile handles native upload of files // handleUploadFile handles native upload of files
func (b *Btelegram) handleUploadFile(msg *config.Message, chatid int64) string { func (b *Btelegram) handleUploadFile(msg *config.Message, chatid int64, parentID int) (string, error) {
var c tgbotapi.Chattable var media []interface{}
for _, f := range msg.Extra["file"] { for _, f := range msg.Extra["file"] {
fi := f.(config.FileInfo) fi := f.(config.FileInfo)
file := tgbotapi.FileBytes{ file := tgbotapi.FileBytes{
Name: fi.Name, Name: fi.Name,
Bytes: *fi.Data, Bytes: *fi.Data,
} }
if b.GetString("MessageFormat") == HTMLFormat {
fi.Comment = makeHTML(html.EscapeString(fi.Comment))
}
switch filepath.Ext(fi.Name) { switch filepath.Ext(fi.Name) {
case ".jpg", ".jpe", ".png": case ".jpg", ".jpe", ".png":
pc := tgbotapi.NewPhotoUpload(chatid, file) pc := tgbotapi.NewInputMediaPhoto(file)
pc.Caption, pc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment) if fi.Comment != "" {
c = pc pc.Caption, pc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
}
media = append(media, pc)
case ".mp4", ".m4v": case ".mp4", ".m4v":
vc := tgbotapi.NewVideoUpload(chatid, file) vc := tgbotapi.NewInputMediaVideo(file)
vc.Caption, vc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment) if fi.Comment != "" {
c = vc vc.Caption, vc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
}
media = append(media, vc)
case ".mp3", ".oga": case ".mp3", ".oga":
ac := tgbotapi.NewAudioUpload(chatid, file) ac := tgbotapi.NewInputMediaAudio(file)
ac.Caption, ac.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment) if fi.Comment != "" {
c = ac ac.Caption, ac.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
}
media = append(media, ac)
case ".ogg": case ".ogg":
voc := tgbotapi.NewVoiceUpload(chatid, file) voc := tgbotapi.NewVoice(chatid, file)
voc.Caption, voc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment) voc.Caption, voc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
c = voc voc.ReplyToMessageID = parentID
res, err := b.c.Send(voc)
if err != nil {
return "", err
}
return strconv.Itoa(res.MessageID), nil
default: default:
dc := tgbotapi.NewDocumentUpload(chatid, file) dc := tgbotapi.NewInputMediaDocument(file)
dc.Caption, dc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment) if fi.Comment != "" {
c = dc dc.Caption, dc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
} }
_, err := b.c.Send(c) media = append(media, dc)
if err != nil {
b.Log.Errorf("file upload failed: %#v", err)
} }
} }
return ""
return b.sendMediaFiles(msg, chatid, parentID, media)
} }
func (b *Btelegram) handleQuote(message, quoteNick, quoteMessage string) string { func (b *Btelegram) handleQuote(message, quoteNick, quoteMessage string) string {
@@ -435,21 +520,61 @@ func (b *Btelegram) handleEntities(rmsg *config.Message, message *tgbotapi.Messa
if message.Entities == nil { if message.Entities == nil {
return return
} }
// for now only do URL replacements
for _, e := range *message.Entities { indexMovedBy := 0
prevLinkOffset := -1
for _, e := range message.Entities {
asRunes := utf16.Encode([]rune(rmsg.Text))
if e.Type == "text_link" { if e.Type == "text_link" {
offset := e.Offset + indexMovedBy
url, err := e.ParseURL() url, err := e.ParseURL()
if err != nil { if err != nil {
b.Log.Errorf("entity text_link url parse failed: %s", err) b.Log.Errorf("entity text_link url parse failed: %s", err)
continue continue
} }
utfEncodedString := utf16.Encode([]rune(rmsg.Text)) utfEncodedString := utf16.Encode([]rune(rmsg.Text))
if e.Offset+e.Length > len(utfEncodedString) { if offset+e.Length > len(utfEncodedString) {
b.Log.Errorf("entity length is too long %d > %d", e.Offset+e.Length, len(utfEncodedString)) b.Log.Errorf("entity length is too long %d > %d", offset+e.Length, len(utfEncodedString))
continue continue
} }
link := utf16.Decode(utfEncodedString[e.Offset : e.Offset+e.Length]) rmsg.Text = string(utf16.Decode(asRunes[:offset+e.Length])) + " (" + url.String() + ")" + string(utf16.Decode(asRunes[offset+e.Length:]))
rmsg.Text = strings.Replace(rmsg.Text, string(link), url.String(), 1) indexMovedBy += len(url.String()) + 3
prevLinkOffset = e.Offset
}
if e.Offset == prevLinkOffset {
continue
}
if e.Type == "code" {
offset := e.Offset + indexMovedBy
rmsg.Text = string(utf16.Decode(asRunes[:offset])) + "`" + string(utf16.Decode(asRunes[offset:offset+e.Length])) + "`" + string(utf16.Decode(asRunes[offset+e.Length:]))
indexMovedBy += 2
}
if e.Type == "pre" {
offset := e.Offset + indexMovedBy
rmsg.Text = string(utf16.Decode(asRunes[:offset])) + "```\n" + string(utf16.Decode(asRunes[offset:offset+e.Length])) + "```\n" + string(utf16.Decode(asRunes[offset+e.Length:]))
indexMovedBy += 8
}
if e.Type == "bold" {
offset := e.Offset + indexMovedBy
rmsg.Text = string(utf16.Decode(asRunes[:offset])) + "*" + string(utf16.Decode(asRunes[offset:offset+e.Length])) + "*" + string(utf16.Decode(asRunes[offset+e.Length:]))
indexMovedBy += 2
}
if e.Type == "italic" {
offset := e.Offset + indexMovedBy
rmsg.Text = string(utf16.Decode(asRunes[:offset])) + "_" + string(utf16.Decode(asRunes[offset:offset+e.Length])) + "_" + string(utf16.Decode(asRunes[offset+e.Length:]))
indexMovedBy += 2
}
if e.Type == "strike" {
offset := e.Offset + indexMovedBy
rmsg.Text = string(utf16.Decode(asRunes[:offset])) + "~" + string(utf16.Decode(asRunes[offset:offset+e.Length])) + "~" + string(utf16.Decode(asRunes[offset+e.Length:]))
indexMovedBy += 2
} }
} }
} }

View File

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

View File

@@ -1,6 +1,7 @@
package btelegram package btelegram
import ( import (
"fmt"
"html" "html"
"log" "log"
"strconv" "strconv"
@@ -9,7 +10,7 @@ import (
"github.com/42wim/matterbridge/bridge" "github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config" "github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper" "github.com/42wim/matterbridge/bridge/helper"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
) )
const ( const (
@@ -49,11 +50,7 @@ func (b *Btelegram) Connect() error {
} }
u := tgbotapi.NewUpdate(0) u := tgbotapi.NewUpdate(0)
u.Timeout = 60 u.Timeout = 60
updates, err := b.c.GetUpdatesChan(u) updates := b.c.GetUpdatesChan(u)
if err != nil {
b.Log.Debugf("%#v", err)
return err
}
b.Log.Info("Connection succeeded") b.Log.Info("Connection succeeded")
go b.handleRecv(updates) go b.handleRecv(updates)
return nil return nil
@@ -104,7 +101,7 @@ func (b *Btelegram) Send(msg config.Message) (string, error) {
} }
if b.GetString("MessageFormat") == HTMLFormat { if b.GetString("MessageFormat") == HTMLFormat {
msg.Text = makeHTML(msg.Text) msg.Text = makeHTML(html.EscapeString(msg.Text))
} }
// Delete message // Delete message
@@ -112,16 +109,27 @@ func (b *Btelegram) Send(msg config.Message) (string, error) {
return b.handleDelete(&msg, chatid) return b.handleDelete(&msg, chatid)
} }
// Handle prefix hint for unthreaded messages.
if msg.ParentNotFound() {
msg.ParentID = ""
msg.Text = fmt.Sprintf("[reply]: %s", msg.Text)
}
var parentID int
if msg.ParentID != "" {
parentID, _ = b.intParentID(msg.ParentID)
}
// Upload a file if it exists // Upload a file if it exists
if msg.Extra != nil { if msg.Extra != nil {
for _, rmsg := range helper.HandleExtra(&msg, b.General) { for _, rmsg := range helper.HandleExtra(&msg, b.General) {
if _, msgErr := b.sendMessage(chatid, rmsg.Username, rmsg.Text); msgErr != nil { if _, msgErr := b.sendMessage(chatid, rmsg.Username, rmsg.Text, parentID); msgErr != nil {
b.Log.Errorf("sendMessage failed: %s", msgErr) b.Log.Errorf("sendMessage failed: %s", msgErr)
} }
} }
// check if we have files to upload (from slack, telegram or mattermost) // check if we have files to upload (from slack, telegram or mattermost)
if len(msg.Extra["file"]) > 0 { if len(msg.Extra["file"]) > 0 {
b.handleUploadFile(&msg, chatid) return b.handleUploadFile(&msg, chatid, parentID)
} }
} }
@@ -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 // Ignore empty text field needs for prevent double messages from whatsapp to telegram
// when sending media with text caption // when sending media with text caption
if msg.Text != "" { if msg.Text != "" {
return b.sendMessage(chatid, msg.Username, msg.Text) return b.sendMessage(chatid, msg.Username, msg.Text, parentID)
} }
return "", nil return "", nil
@@ -149,10 +157,10 @@ func (b *Btelegram) getFileDirectURL(id string) string {
return res return res
} }
func (b *Btelegram) sendMessage(chatid int64, username, text string) (string, error) { func (b *Btelegram) sendMessage(chatid int64, username, text string, parentID int) (string, error) {
m := tgbotapi.NewMessage(chatid, "") m := tgbotapi.NewMessage(chatid, "")
m.Text, m.ParseMode = TGGetParseMode(b, username, text) m.Text, m.ParseMode = TGGetParseMode(b, username, text)
m.ReplyToMessageID = parentID
m.DisableWebPagePreview = b.GetBool("DisableWebPagePreview") m.DisableWebPagePreview = b.GetBool("DisableWebPagePreview")
res, err := b.c.Send(m) res, err := b.c.Send(m)
@@ -162,6 +170,29 @@ func (b *Btelegram) sendMessage(chatid int64, username, text string) (string, er
return strconv.Itoa(res.MessageID), nil return strconv.Itoa(res.MessageID), nil
} }
// sendMediaFiles native upload media files via media group
func (b *Btelegram) sendMediaFiles(msg *config.Message, chatid int64, parentID int, media []interface{}) (string, error) {
if len(media) == 0 {
return "", nil
}
mg := tgbotapi.MediaGroupConfig{ChatID: chatid, ChannelUsername: msg.Username, Media: media, ReplyToMessageID: parentID}
messages, err := b.c.SendMediaGroup(mg)
if err != nil {
return "", err
}
// return first message id
return strconv.Itoa(messages[0].MessageID), nil
}
// intParentID return integer parent id for telegram message
func (b *Btelegram) intParentID(parentID string) (int, error) {
pid, err := strconv.Atoi(parentID)
if err != nil {
return 0, err
}
return pid, nil
}
func (b *Btelegram) cacheAvatar(msg *config.Message) (string, error) { func (b *Btelegram) cacheAvatar(msg *config.Message) (string, error) {
fi := msg.Extra["file"][0].(config.FileInfo) fi := msg.Extra["file"][0].(config.FileInfo)
/* if we have a sha we have successfully uploaded the file to the media server, /* if we have a sha we have successfully uploaded the file to the media server,

View File

@@ -34,6 +34,7 @@ type user struct {
type Bvk struct { type Bvk struct {
c *api.VK c *api.VK
lp *longpoll.LongPoll
usernamesMap map[int]user // cache of user names and avatar URLs usernamesMap map[int]user // cache of user names and avatar URLs
*bridge.Config *bridge.Config
} }
@@ -45,23 +46,25 @@ func New(cfg *bridge.Config) bridge.Bridger {
func (b *Bvk) Connect() error { func (b *Bvk) Connect() error {
b.Log.Info("Connecting") b.Log.Info("Connecting")
b.c = api.NewVK(b.GetString("Token")) b.c = api.NewVK(b.GetString("Token"))
lp, err := longpoll.NewLongPoll(b.c, b.GetInt("GroupID"))
var err error
b.lp, err = longpoll.NewLongPollCommunity(b.c)
if err != nil { if err != nil {
b.Log.Debugf("%#v", err) b.Log.Debugf("%#v", err)
return err return err
} }
lp.MessageNew(func(ctx context.Context, obj events.MessageNewObject) { b.lp.MessageNew(func(ctx context.Context, obj events.MessageNewObject) {
b.handleMessage(obj.Message, false) b.handleMessage(obj.Message, false)
}) })
b.Log.Info("Connection succeeded") b.Log.Info("Connection succeeded")
go func() { go func() {
err := lp.Run() err := b.lp.Run()
if err != nil { if err != nil {
b.Log.Fatal("Enable longpoll in group management") b.Log.WithError(err).Fatal("Enable longpoll in group management")
} }
}() }()
@@ -69,6 +72,8 @@ func (b *Bvk) Connect() error {
} }
func (b *Bvk) Disconnect() error { func (b *Bvk) Disconnect() error {
b.lp.Shutdown()
return nil return nil
} }
@@ -218,7 +223,7 @@ func (b *Bvk) uploadFiles(extra map[string][]interface{}, peerID int) (string, s
} }
a, err := b.uploadFile(fi, peerID) a, err := b.uploadFile(fi, peerID)
if err != nil { if err != nil {
b.Log.Error("File upload error ", fi.Name) b.Log.WithError(err).Error("File upload error ", fi.Name)
} }
attachments = append(attachments, a) attachments = append(attachments, a)
@@ -232,7 +237,8 @@ func (b *Bvk) uploadFile(file config.FileInfo, peerID int) (string, error) {
photoRE := regexp.MustCompile(".(jpg|jpe|png)$") photoRE := regexp.MustCompile(".(jpg|jpe|png)$")
if photoRE.MatchString(file.Name) { if photoRE.MatchString(file.Name) {
p, err := b.c.UploadMessagesPhoto(peerID, r) // BUG(VK): for community chat peerID=0
p, err := b.c.UploadMessagesPhoto(0, r)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

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

View File

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

View File

@@ -40,6 +40,11 @@ type Bwhatsapp struct {
func New(cfg *bridge.Config) bridge.Bridger { func New(cfg *bridge.Config) bridge.Bridger {
number := cfg.GetString(cfgNumber) number := cfg.GetString(cfgNumber)
cfg.Log.Warn("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
cfg.Log.Warn("This bridge is deprecated and not supported anymore. Use the new multidevice whatsapp bridge")
cfg.Log.Warn("See https://github.com/42wim/matterbridge#building-with-whatsapp-beta-multidevice-support for more info")
cfg.Log.Warn("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
if number == "" { if number == "" {
cfg.Log.Fatalf("Missing configuration for WhatsApp bridge: Number") cfg.Log.Fatalf("Missing configuration for WhatsApp bridge: Number")
} }
@@ -293,7 +298,11 @@ func (b *Bwhatsapp) Send(msg config.Message) (string, error) {
if msg.ID != "" { if msg.ID != "" {
b.Log.Debugf("updating message with id %s", msg.ID) b.Log.Debugf("updating message with id %s", msg.ID)
msg.Text += " (edited)" if b.GetString("editsuffix") != "" {
msg.Text += b.GetString("EditSuffix")
} else {
msg.Text += " (edited)"
}
} }
// Handle Upload a file // Handle Upload a file

View File

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

View File

@@ -0,0 +1,124 @@
//go:build whatsappmulti
// +build whatsappmulti
package bwhatsapp
import (
"fmt"
"strings"
"go.mau.fi/whatsmeow"
"go.mau.fi/whatsmeow/store"
"go.mau.fi/whatsmeow/store/sqlstore"
"go.mau.fi/whatsmeow/types"
)
type ProfilePicInfo struct {
URL string `json:"eurl"`
Tag string `json:"tag"`
Status int16 `json:"status"`
}
func (b *Bwhatsapp) reloadContacts() {
if _, err := b.wc.Store.Contacts.GetAllContacts(); err != nil {
b.Log.Errorf("error on update of contacts: %v", err)
}
allcontacts, err := b.wc.Store.Contacts.GetAllContacts()
if err != nil {
b.Log.Errorf("error on update of contacts: %v", err)
}
if len(allcontacts) > 0 {
b.contacts = allcontacts
}
}
func (b *Bwhatsapp) getSenderName(info types.MessageInfo) string {
// Parse AD JID
var senderJid types.JID
senderJid.User, senderJid.Server = info.Sender.User, info.Sender.Server
sender, exists := b.contacts[senderJid]
if !exists || (sender.FullName == "" && sender.FirstName == "") {
b.reloadContacts() // Contacts may need to be reloaded
sender, exists = b.contacts[senderJid]
}
if exists && sender.FullName != "" {
return sender.FullName
}
if info.PushName != "" {
return info.PushName
}
if exists && sender.FirstName != "" {
return sender.FirstName
}
return "Someone"
}
func (b *Bwhatsapp) getSenderNotify(senderJid types.JID) string {
sender, exists := b.contacts[senderJid]
if !exists || (sender.FullName == "" && sender.PushName == "" && sender.FirstName == "") {
b.reloadContacts() // Contacts may need to be reloaded
sender, exists = b.contacts[senderJid]
}
if !exists {
return "someone"
}
if exists && sender.FullName != "" {
return sender.FullName
}
if exists && sender.PushName != "" {
return sender.PushName
}
if exists && sender.FirstName != "" {
return sender.FirstName
}
return "someone"
}
func (b *Bwhatsapp) GetProfilePicThumb(jid string) (*types.ProfilePictureInfo, error) {
pjid, _ := types.ParseJID(jid)
info, err := b.wc.GetProfilePictureInfo(pjid, &whatsmeow.GetProfilePictureParams{
Preview: true,
})
if err != nil {
return nil, fmt.Errorf("failed to get avatar: %v", err)
}
return info, nil
}
func isGroupJid(identifier string) bool {
return strings.HasSuffix(identifier, "@g.us") ||
strings.HasSuffix(identifier, "@temp") ||
strings.HasSuffix(identifier, "@broadcast")
}
func (b *Bwhatsapp) getDevice() (*store.Device, error) {
device := &store.Device{}
storeContainer, err := sqlstore.New("sqlite", "file:"+b.Config.GetString("sessionfile")+".db?_foreign_keys=on&_pragma=busy_timeout=10000", nil)
if err != nil {
return device, fmt.Errorf("failed to connect to database: %v", err)
}
device, err = storeContainer.GetFirstDevice()
if err != nil {
return device, fmt.Errorf("failed to get device: %v", err)
}
return device, nil
}

View File

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

View File

@@ -1,3 +1,154 @@
# v1.26.0
## New features
- irc: Allow substitution of bot's nick in RunCommands (irc) (#1890)
- matrix: Add Matrix username spoofing (#1875)
## Enhancements
- general: Update dependencies (#1951)
- mattermost: Remove mattermost 5 support (#1936)
- mumble: Implement sending of EventJoinLeave both to and from Mumble (#1915)
- whatsappmulti: Improve attachment handling (whatsapp) (#1928)
- whatsappmulti: Handle incoming document captions from whatsapp (#1935)
## Bugfix
- irc: Fix empty messages on IRC (#1897)
- telegram: Fix message html entities escaping when sending to Telegram (#1855)
- telegram/slack: Fix error messages in telegram and slack bridges (#1862)
- telegram: Fix telegram attachment comment formatting and escaping (#1920)
- telegram: Make the cgo lottie a build tag (-tag cgolottie) (#1955)
- whatsappmulti: Update dependencies and fix whatsmeow API changes (#1887)
- whatsappmulti: Fix the "Someone" nickname problem (whatsapp) (#1931)
This release couldn't exist without the following contributors:
@s3lph, @sas1024, @Glandos, @jx11r, @Lucki, @BuckarooBanzay, @ilmaisin, @Kufat
# v1.25.2
## Enhancements
- general: Update dependencies (#1851,#1841)
- mattermost: Support mattermost v7.x (#1852)
## Bugfix
- discord: Fix Unwanted join notifications from one Discord server to another (#1612)
- discord: Ignore events from other guilds, add nosendjoinpart support (#1846)
This release couldn't exist without the following contributors:
@wlcx
# v1.25.1
## Enhancements
- matrix: Add KeepQuotedReply option for matrix to fix regression (#1823)
- slack: Improve Slack attachments formatting (slack) (#1807)
## Bugfix
- general: Update dependencies (#1813,#1822,#1833)
- mattermost: Add space between filename and URL (mattermost). Fixes #1820
- matrix: Update matterbridge/gomatrix. Fixes #1772 (#1803)
- telegram: Do not modify .webm files (telegram). Fixes #17**88 (#1802)
- telegram: Do not apply any markup to URL entities (telegram) (#1808)
- telegram: Fix telegram message deletion request (#1818)
- vk: Fix UploadMessagesPhoto for vk community chat (vk) (#1812)
This release couldn't exist without the following contributors:
@bd808, @chugunov, @sas1024, @SevereCloud, @ValdikSS
# v1.25.0
## Breaking changes
- whatsapp: deprecated, the library <https://github.com/Rhymen/go-whatsapp> isn't maintained anymore.
We're switching to <https://github.com/tulir/whatsmeow> but as this uses a GPL3 licensed library we can't provide you with binaries.
You'll need to build it yourself. More information about this can be found here: <https://github.com/42wim/matterbridge#building-with-whatsapp-beta-multidevice-support>
## New features
- whatsappmulti: whatsapp multidevice support added - more info <https://github.com/42wim/matterbridge#building-with-whatsapp-beta-multidevice-support>
- general: Add Dockerfile_whatsappmulti for building with WhatsApp Multi-Device support (Whatsmeow) (#1774)
- telegram: Add UseFullName option (telegram) (#1777)
- slack: Use slack real name as user name (slack) (#1775)
## Enhancements
- general: Ignore sending file with comment, if comment contains IgnoreMessages value (#1783)
- general: Update dependencies (#1784)
- irc: Update lrstanley/girc dep (#1773)
- slack: Preserve threading for messages with files (slack) (#1781)
- telegram: Preserve threading from telegram replies (telegram) (#1776)
- telegram: Multiple media in one message (telegram) (#1779)
- whatsapp: Add whatsapp deprecation warning (#1792)
## Bugfix
- discord: Change discord non-native threading behaviour (discord) (#1791)
This release couldn't exist without the following contributors:
@sas1024, @tpxtron
# v1.24.1
## Enhancements
- discord: Switch to discordgo upstream again (#1759)
- general: Update dependencies and vendor (#1761)
- general: Create inmessage-logger.tengo (#1688) (#1747)
- general: Add OpenRC service file (#1746)
- irc: Refactor utf-8 conversion (irc) (#1767)
## Bugfixes
- irc: Fix panic in irc. Closes #1751 (#1760)
- mumble: Implement a workaround to signal Opus support (mumble) (#1764)
- telegram: Fix for complex-formatted Telegram text (#1765)
- telegram: Fix Telegram channel title in forwards (#1753)
- telegram: Fix Telegram Problem (unforwarded formatting and skipping of linebreaks) (#1749)
This release couldn't exist without the following contributors:
@s3lph, @ValdikSS, @reckel-jm, @CyberTailor
# v1.24.0
## New features
- harmony: new protocol added: Add support for Harmony (#1656)
- irc: Allow binding to IP on IRC (#1640)
- irc: Add support for client certificate (irc) (#1710)
- mattermost: Add UseUsername option (mattermost). Fixes #1665 (#1714)
- mattermost: Add support for using ID in channel config (mattermost) (#1715)
- matrix: Reply support for Matrix (#1664)
- telegram: Add Telegram Bot Command /chatId (telegram) (#1703)
## Enhancements
- general: Update dependencies/vendor (#1659)
- discord: Add more debug options for discord (#1712)
- docker: Use Alpine stable again in Dockerfile (#1643)
- mattermost: Log eventtype in debug (mattermost) (#1676)
- mattermost: Add more ignore debug messages (mattermost) (#1678)
- slack: Add support for deleting files from slack to discord. Fixes #1705 (#1709)
- telegram: Add support for code blocks in telegram (#1650)
- telegram: Update telegram-bot-api to v5 (#1660)
- telegram: Add comments to messages (telegram) (#1652)
- telegram: Add support for sender_chat (telegram) (#1677)
- vk: Remove GroupID (vk) (#1668)
## Bugfix
- mattermost: Use current parentID if rootId is not set (mattermost) (#1675)
- matrix: Make HTMLDisable work correct (matrix) (#1716)
- whatsapp: Make EditSuffix option actually work (whatsapp). Fixes #1510 (#1728)
This release couldn't exist without the following contributors:
@DavyJohnesev, @GoliathLabs, @pontaoski, @PeGaSuS-Coder, @dependabot[bot], @vpzomtrrfrt, @SevereCloud, @soloam, @YashRE42, @danwalmsley, @SuperSandro2000, @inzanity
# v1.23.2 # v1.23.2
If you're running whatsapp you should update. If you're running whatsapp you should update.
@@ -6,6 +157,9 @@ If you're running whatsapp you should update.
- whatsapp: Update go-whatsapp version (#1630) - whatsapp: Update go-whatsapp version (#1630)
This release couldn't exist without the following contributors:
@snikpic
# v1.23.1 # v1.23.1
If you're running mattermost 6 you should update. If you're running mattermost 6 you should update.
@@ -20,6 +174,9 @@ If you're running mattermost 6 you should update.
- xmpp: Use a new msgID when replacing messages (xmpp). Fixes #1584 (#1623) - xmpp: Use a new msgID when replacing messages (xmpp). Fixes #1584 (#1623)
- zulip: Add better error handling on Zulip (#1589) - zulip: Add better error handling on Zulip (#1589)
This release couldn't exist without the following contributors:
@Polynomdivision, @minecraftchest1, @alexmv
# v1.23.0 # v1.23.0
## New features ## New features

View File

@@ -0,0 +1,15 @@
fmt := import("fmt")
os := import("os")
times := import("times")
if msgText != "" && msgUsername != "system" {
os.chdir("/var/www/matterbridge")
file := os.open_file("inmessage.log", os.o_append|os.o_wronly|os.o_create, 0644)
file.write_string(fmt.sprintf(
"[%s] <%s> %s\n",
times.time_format(times.now(), times.format_rfc1123),
msgUsername,
msgText
))
file.close()
}

19
contrib/matterbridge.openrc Executable file
View File

@@ -0,0 +1,19 @@
#!/sbin/openrc-run
# Copyright 2021-2022 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
command=/usr/bin/matterbridge
command_args="-conf ${MATTERBRIDGE_CONF:-/etc/matterbridge/bridge.toml} ${MATTERBRIDGE_ARGS}"
command_user="matterbridge:matterbridge"
pidfile="/run/${RC_SVCNAME}.pid"
command_background=1
output_log="/var/log/${RC_SVCNAME}.log"
error_log="${output_log}"
depend() {
need net
}
start_pre() {
checkpath -f "${output_log}" -o "${command_user}" || return 1
}

View File

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

View File

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

View File

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

View File

@@ -66,7 +66,7 @@ func New(rootLogger *logrus.Logger, cfg *config.Gateway, r *Router) *Gateway {
func (gw *Gateway) FindCanonicalMsgID(protocol string, mID string) string { func (gw *Gateway) FindCanonicalMsgID(protocol string, mID string) string {
ID := protocol + " " + mID ID := protocol + " " + mID
if gw.Messages.Contains(ID) { if gw.Messages.Contains(ID) {
return mID return ID
} }
// If not keyed, iterate through cache for downstream, and infer upstream. // If not keyed, iterate through cache for downstream, and infer upstream.
@@ -75,7 +75,7 @@ func (gw *Gateway) FindCanonicalMsgID(protocol string, mID string) string {
ids := v.([]*BrMsgID) ids := v.([]*BrMsgID)
for _, downstreamMsgObj := range ids { for _, downstreamMsgObj := range ids {
if ID == downstreamMsgObj.ID { if ID == downstreamMsgObj.ID {
return strings.Replace(mid.(string), protocol+" ", "", 1) return mid.(string)
} }
} }
} }
@@ -299,13 +299,30 @@ func (gw *Gateway) ignoreMessage(msg *config.Message) bool {
igNicks := strings.Fields(gw.Bridges[msg.Account].GetString("IgnoreNicks")) igNicks := strings.Fields(gw.Bridges[msg.Account].GetString("IgnoreNicks"))
igMessages := strings.Fields(gw.Bridges[msg.Account].GetString("IgnoreMessages")) igMessages := strings.Fields(gw.Bridges[msg.Account].GetString("IgnoreMessages"))
if gw.ignoreTextEmpty(msg) || gw.ignoreText(msg.Username, igNicks) || gw.ignoreText(msg.Text, igMessages) { if gw.ignoreTextEmpty(msg) || gw.ignoreText(msg.Username, igNicks) || gw.ignoreText(msg.Text, igMessages) || gw.ignoreFilesComment(msg.Extra, igMessages) {
return true return true
} }
return false return false
} }
// ignoreFilesComment returns true if we need to ignore a file with matched comment.
func (gw *Gateway) ignoreFilesComment(extra map[string][]interface{}, igMessages []string) bool {
if extra == nil {
return false
}
for _, f := range extra["file"] {
fi, ok := f.(config.FileInfo)
if !ok {
continue
}
if gw.ignoreText(fi.Comment, igMessages) {
return true
}
}
return false
}
func (gw *Gateway) modifyUsername(msg *config.Message, dest *bridge.Bridge) string { func (gw *Gateway) modifyUsername(msg *config.Message, dest *bridge.Bridge) string {
if dest.GetBool("StripNick") { if dest.GetBool("StripNick") {
re := regexp.MustCompile("[^a-zA-Z0-9]+") re := regexp.MustCompile("[^a-zA-Z0-9]+")
@@ -447,16 +464,19 @@ func (gw *Gateway) SendMessage(
msg.Avatar = gw.modifyAvatar(rmsg, dest) msg.Avatar = gw.modifyAvatar(rmsg, dest)
msg.Username = gw.modifyUsername(rmsg, dest) msg.Username = gw.modifyUsername(rmsg, dest)
msg.ID = gw.getDestMsgID(rmsg.Protocol+" "+rmsg.ID, dest, channel) // exclude file delete event as the msg ID here is the native file ID that needs to be deleted
if msg.Event != config.EventFileDelete {
msg.ID = gw.getDestMsgID(rmsg.Protocol+" "+rmsg.ID, dest, channel)
}
// for api we need originchannel as channel // for api we need originchannel as channel
if dest.Protocol == apiProtocol { if dest.Protocol == apiProtocol {
msg.Channel = rmsg.Channel msg.Channel = rmsg.Channel
} }
msg.ParentID = gw.getDestMsgID(rmsg.Protocol+" "+canonicalParentMsgID, dest, channel) msg.ParentID = gw.getDestMsgID(canonicalParentMsgID, dest, channel)
if msg.ParentID == "" { if msg.ParentID == "" {
msg.ParentID = canonicalParentMsgID msg.ParentID = strings.Replace(canonicalParentMsgID, dest.Protocol+" ", "", 1)
} }
// if the parentID is still empty and we have a parentID set in the original message // if the parentID is still empty and we have a parentID set in the original message

150
go.mod
View File

@@ -6,54 +6,60 @@ require (
github.com/Benau/tgsconverter v0.0.0-20210809170556-99f4a4f6337f github.com/Benau/tgsconverter v0.0.0-20210809170556-99f4a4f6337f
github.com/Philipp15b/go-steam v1.0.1-0.20200727090957-6ae9b3c0a560 github.com/Philipp15b/go-steam v1.0.1-0.20200727090957-6ae9b3c0a560
github.com/Rhymen/go-whatsapp v0.1.2-0.20211102134409-31a2e740845c github.com/Rhymen/go-whatsapp v0.1.2-0.20211102134409-31a2e740845c
github.com/SevereCloud/vksdk/v2 v2.10.0 github.com/SevereCloud/vksdk/v2 v2.15.0
github.com/d5/tengo/v2 v2.8.0 github.com/bwmarrin/discordgo v0.27.0
github.com/d5/tengo/v2 v2.13.0
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/fsnotify/fsnotify v1.5.1 github.com/fsnotify/fsnotify v1.6.0
github.com/go-telegram-bot-api/telegram-bot-api v1.0.1-0.20200524105306-7434b0456e81 github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
github.com/gomarkdown/markdown v0.0.0-20210918233619-6c1113f12c4a github.com/gomarkdown/markdown v0.0.0-20221013030248-663e2500819c
github.com/google/gops v0.3.22 github.com/google/gops v0.3.26
github.com/gorilla/schema v1.2.0 github.com/gorilla/schema v1.2.0
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.5.0
github.com/hashicorp/golang-lru v0.5.4 github.com/harmony-development/shibshib v0.0.0-20220101224523-c98059d09cfa
github.com/hashicorp/golang-lru v0.6.0
github.com/jpillora/backoff v1.0.0 github.com/jpillora/backoff v1.0.0
github.com/keybase/go-keybase-chat-bot v0.0.0-20211004153716-fd2ee4d6be11 github.com/keybase/go-keybase-chat-bot v0.0.0-20221220212439-e48d9abd2c20
github.com/kyokomi/emoji/v2 v2.2.8 github.com/kyokomi/emoji/v2 v2.2.11
github.com/labstack/echo/v4 v4.6.1 github.com/labstack/echo/v4 v4.10.0
github.com/lrstanley/girc v0.0.0-20210611213246-771323f1624b github.com/lrstanley/girc v0.0.0-20221222153823-a92667a5c9b4
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16
github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20211016222428-79310a412696 github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20211016222428-79310a412696
github.com/matterbridge/discordgo v0.21.2-0.20210201201054-fb39a175b4f7 github.com/matterbridge/go-xmpp v0.0.0-20211030125215-791a06c5f1be
github.com/matterbridge/go-xmpp v0.0.0-20210731150933-5702291c239f github.com/matterbridge/gomatrix v0.0.0-20220411225302-271e5088ea27
github.com/matterbridge/gozulipbot v0.0.0-20211023205727-a19d6c1f3b75 github.com/matterbridge/gozulipbot v0.0.0-20211023205727-a19d6c1f3b75
github.com/matterbridge/logrus-prefixed-formatter v0.5.3-0.20200523233437-d971309a77ba github.com/matterbridge/logrus-prefixed-formatter v0.5.3-0.20200523233437-d971309a77ba
github.com/matterbridge/matterclient v0.0.0-20211024214211-22e762684b4a github.com/matterbridge/matterclient v0.0.0-20220624224459-272af20c7ddf
github.com/mattermost/mattermost-server/v5 v5.39.0 github.com/mattermost/mattermost-server/v5 v5.39.3
github.com/mattermost/mattermost-server/v6 v6.0.2 github.com/mattermost/mattermost-server/v6 v6.7.2
github.com/mattn/godown v0.0.1 github.com/mattn/godown v0.0.1
github.com/missdeer/golib v1.0.4 github.com/mdp/qrterminal v1.0.1
github.com/nelsonken/gomf v0.0.0-20180504123937-a9dd2f9deae9 github.com/nelsonken/gomf v0.0.0-20190423072027-c65cc0469e94
github.com/paulrosania/go-charset v0.0.0-20190326053356-55c9d7a5834c github.com/paulrosania/go-charset v0.0.0-20190326053356-55c9d7a5834c
github.com/rs/xid v1.3.0 github.com/rs/xid v1.4.0
github.com/russross/blackfriday v1.6.0 github.com/russross/blackfriday v1.6.0
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d
github.com/shazow/ssh-chat v1.10.1 github.com/shazow/ssh-chat v1.10.1
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.9.0
github.com/slack-go/slack v0.9.5 github.com/slack-go/slack v0.12.1
github.com/spf13/viper v1.9.0 github.com/spf13/viper v1.15.0
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.8.1
github.com/vincent-petithory/dataurl v0.0.0-20191104211930-d1553a71de50 github.com/vincent-petithory/dataurl v1.0.0
github.com/writeas/go-strip-markdown v2.0.1+incompatible github.com/writeas/go-strip-markdown v2.0.1+incompatible
github.com/yaegashi/msgraph.go v0.1.4 github.com/yaegashi/msgraph.go v0.1.4
github.com/zfjagann/golang-ring v0.0.0-20210116075443-7c86fdb43134 github.com/zfjagann/golang-ring v0.0.0-20220330170733-19bcea1b6289
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 go.mau.fi/whatsmeow v0.0.0-20230128195103-dcbc8dd31a22
golang.org/x/oauth2 v0.0.0-20211028175245-ba495a64dcb5 golang.org/x/image v0.3.0
golang.org/x/oauth2 v0.4.0
golang.org/x/text v0.6.0
gomod.garykim.dev/nc-talk v0.3.0 gomod.garykim.dev/nc-talk v0.3.0
google.golang.org/protobuf v1.28.1
gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376 gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376
layeh.com/gumble v0.0.0-20200818122324-146f9205029b layeh.com/gumble v0.0.0-20221205141517-d1df60a3cc14
modernc.org/sqlite v1.20.3
) )
require ( require (
filippo.io/edwards25519 v1.0.0 // indirect
github.com/Benau/go_rlottie v0.0.0-20210807002906-98c1b2421989 // indirect github.com/Benau/go_rlottie v0.0.0-20210807002906-98c1b2421989 // indirect
github.com/Jeffail/gabs v1.4.0 // indirect github.com/Jeffail/gabs v1.4.0 // indirect
github.com/apex/log v1.9.0 // indirect github.com/apex/log v1.9.0 // indirect
@@ -65,72 +71,92 @@ require (
github.com/go-asn1-ber/asn1-ber v1.5.3 // indirect github.com/go-asn1-ber/asn1-ber v1.5.3 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang/protobuf v1.5.2 // 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/gopackage/ddp v0.0.3 // indirect
github.com/graph-gophers/graphql-go v1.3.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/json-iterator/go v1.1.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/kettek/apng v0.0.0-20191108220231-414630eed80f // indirect
github.com/klauspost/cpuid/v2 v2.0.6 // indirect github.com/klauspost/compress v1.15.8 // indirect
github.com/labstack/gommon v0.3.0 // indirect github.com/klauspost/cpuid/v2 v2.0.12 // indirect
github.com/magiconair/properties v1.8.5 // indirect github.com/labstack/gommon v0.4.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 // indirect github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 // indirect
github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d // indirect github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d // indirect
github.com/mattermost/logr v1.0.13 // indirect github.com/mattermost/logr v1.0.13 // indirect
github.com/mattermost/logr/v2 v2.0.10 // indirect github.com/mattermost/logr/v2 v2.0.15 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/minio/md5-simd v1.1.2 // indirect github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/minio-go/v7 v7.0.11 // indirect github.com/minio/minio-go/v7 v7.0.24 // indirect
github.com/minio/sha256-simd v1.0.0 // indirect github.com/minio/sha256-simd v1.0.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.4.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/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/monaco-io/request v1.0.5 // indirect
github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d // indirect github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d // indirect
github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff // indirect github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pborman/uuid v1.2.1 // indirect github.com/pborman/uuid v1.2.1 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/philhofer/fwd v1.1.1 // indirect github.com/philhofer/fwd v1.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
github.com/rickb777/date v1.12.4 // indirect github.com/rickb777/date v1.12.4 // indirect
github.com/rickb777/plural v1.2.0 // indirect github.com/rickb777/plural v1.2.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect
github.com/shazow/rateio v0.0.0-20200113175441-4461efc8bdc4 // indirect github.com/shazow/rateio v0.0.0-20200113175441-4461efc8bdc4 // indirect
github.com/sizeofint/webpanimation v0.0.0-20210809145948-1d2b32119882 // indirect github.com/sizeofint/webpanimation v0.0.0-20210809145948-1d2b32119882 // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9 // indirect
github.com/spf13/afero v1.6.0 // indirect github.com/spf13/afero v1.9.3 // indirect
github.com/spf13/cast v1.4.1 // indirect github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.2.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
github.com/tinylib/msgp v1.1.6 // indirect github.com/tinylib/msgp v1.1.6 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/wiggin77/cfg v1.0.2 // indirect github.com/wiggin77/cfg v1.0.2 // indirect
github.com/wiggin77/merror v1.0.3 // indirect github.com/wiggin77/merror v1.0.3 // indirect
github.com/wiggin77/srslog v1.0.1 // indirect github.com/wiggin77/srslog v1.0.1 // indirect
go.uber.org/atomic v1.8.0 // indirect go.mau.fi/libsignal v0.1.0 // indirect
go.uber.org/multierr v1.7.0 // indirect go.uber.org/atomic v1.9.0 // indirect
go.uber.org/zap v1.17.0 // indirect go.uber.org/multierr v1.8.0 // indirect
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect go.uber.org/zap v1.21.0 // indirect
golang.org/x/net v0.0.0-20210913180222-943fd674d43e // indirect golang.org/x/crypto v0.4.0 // indirect
golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect golang.org/x/net v0.5.0 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/sys v0.4.0 // indirect
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect golang.org/x/term v0.4.0 // indirect
golang.org/x/time v0.2.0 // indirect
golang.org/x/tools v0.1.12 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/ini.v1 v1.63.2 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect
modernc.org/ccgo/v3 v3.16.13 // indirect
modernc.org/libc v1.22.2 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.4.0 // indirect
modernc.org/opt v0.1.3 // indirect
modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.0.1 // indirect
rsc.io/qr v0.2.0 // indirect
) )
go 1.17 //replace github.com/matrix-org/gomatrix => github.com/matterbridge/gomatrix v0.0.0-20220205235239-607eb9ee6419
go 1.18

1241
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -7,6 +7,7 @@ import (
"log" "log"
"net" "net"
"net/http" "net/http"
"regexp"
) )
// Message for rocketchat outgoing webhook. // Message for rocketchat outgoing webhook.
@@ -68,7 +69,6 @@ func (c *Client) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
msg := Message{} msg := Message{}
body, err := ioutil.ReadAll(r.Body) body, err := ioutil.ReadAll(r.Body)
log.Println(string(body))
if err != nil { if err != nil {
log.Println(err) log.Println(err)
http.NotFound(w, r) http.NotFound(w, r)
@@ -89,7 +89,11 @@ func (c *Client) ServeHTTP(w http.ResponseWriter, r *http.Request) {
msg.ChannelName = "#" + msg.ChannelName msg.ChannelName = "#" + msg.ChannelName
if c.Token != "" { if c.Token != "" {
if msg.Token != c.Token { if msg.Token != c.Token {
log.Println("invalid token " + msg.Token + " from " + r.RemoteAddr) if regexp.MustCompile(`[^a-zA-Z0-9]+`).MatchString(msg.Token) {
log.Println("invalid token " + msg.Token + " from " + r.RemoteAddr)
} else {
log.Println("invalid token from " + r.RemoteAddr)
}
http.NotFound(w, r) http.NotFound(w, r)
return return
} }

View File

@@ -24,6 +24,13 @@ Password=""
#OPTIONAL (default false) #OPTIONAL (default false)
UseTLS=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) #Enable SASL (PLAIN) authentication. (libera requires this from eg AWS hosts)
#It uses NickServNick and NickServPassword as login and password #It uses NickServNick and NickServPassword as login and password
#OPTIONAL (default false) #OPTIONAL (default false)
@@ -34,6 +41,11 @@ UseSASL=false
#OPTIONAL (default false) #OPTIONAL (default false)
SkipTLSVerify=true SkipTLSVerify=true
#Local address to use for server connection
#Note that Server and Bind must resolve to addresses of the same family.
#OPTIONAL (default "")
Bind=""
#If you know your charset, you can specify it manually. #If you know your charset, you can specify it manually.
#Otherwise it tries to detect this automatically. Select one below #Otherwise it tries to detect this automatically. Select one below
# "iso-8859-2:1987", "iso-8859-9:1989", "866", "latin9", "iso-8859-10:1992", "iso-ir-109", "hebrew", # "iso-8859-2:1987", "iso-8859-9:1989", "866", "latin9", "iso-8859-10:1992", "iso-ir-109", "hebrew",
@@ -110,10 +122,11 @@ RejoinDelay=0
#Only works in IRC right now. #Only works in IRC right now.
ColorNicks=false ColorNicks=false
#RunCommands allows you to send RAW irc commands after connection #RunCommands allows you to send RAW irc commands after connection.
#The string {BOTNICK} (case sensitive) will be replaced with the bot's current nickname.
#Array of strings #Array of strings
#OPTIONAL (default empty) #OPTIONAL (default empty)
RunCommands=["PRIVMSG user hello","PRIVMSG chanserv something"] RunCommands=["PRIVMSG user hello","PRIVMSG chanserv something", "MODE {BOTNICK} +B"]
#PingDelay specifies how long to wait to send a ping to the irc server. #PingDelay specifies how long to wait to send a ping to the irc server.
#You can use s for second, m for minute #You can use s for second, m for minute
@@ -175,7 +188,7 @@ Label=""
RemoteNickFormat="[{PROTOCOL}] <{NICK}> " RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges #Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord #Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false) #OPTIONAL (default false)
ShowJoinPart=false ShowJoinPart=false
@@ -185,7 +198,7 @@ ShowJoinPart=false
VerboseJoinPart=false VerboseJoinPart=false
#Do not send joins/parts to other bridges #Do not send joins/parts to other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack #Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false) #OPTIONAL (default false)
NoSendJoinPart=false NoSendJoinPart=false
@@ -312,7 +325,7 @@ Label=""
RemoteNickFormat="[{PROTOCOL}] <{NICK}> " RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges #Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord #Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false) #OPTIONAL (default false)
ShowJoinPart=false ShowJoinPart=false
@@ -396,6 +409,10 @@ SkipTLSVerify=true
## RELOADABLE SETTINGS ## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file ## Settings below can be reloaded by editing the file
# UseUserName shows the username instead of the server nickname
# OPTIONAL (default false)
UseUserName=false
#how to format the list of IRC nicks when displayed in mattermost. #how to format the list of IRC nicks when displayed in mattermost.
#Possible options are "table" and "plain" #Possible options are "table" and "plain"
#OPTIONAL (default plain) #OPTIONAL (default plain)
@@ -475,12 +492,12 @@ Label=""
RemoteNickFormat="[{PROTOCOL}] <{NICK}> " RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges #Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord #Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false) #OPTIONAL (default false)
ShowJoinPart=false ShowJoinPart=false
#Do not send joins/parts to other bridges #Do not send joins/parts to other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack #Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false) #OPTIONAL (default false)
NoSendJoinPart=false NoSendJoinPart=false
@@ -562,7 +579,7 @@ Label=""
RemoteNickFormat="[{PROTOCOL}] <{NICK}> " RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges #Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord #Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false) #OPTIONAL (default false)
ShowJoinPart=false ShowJoinPart=false
@@ -670,7 +687,7 @@ Label=""
RemoteNickFormat="[{PROTOCOL}] <{NICK}> " RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges #Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord #Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false) #OPTIONAL (default false)
ShowJoinPart=false ShowJoinPart=false
@@ -809,12 +826,12 @@ Label=""
RemoteNickFormat="[{PROTOCOL}] <{NICK}> " RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges #Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord #Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false) #OPTIONAL (default false)
ShowJoinPart=false ShowJoinPart=false
#Do not send joins/parts to other bridges #Do not send joins/parts to other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack #Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false) #OPTIONAL (default false)
NoSendJoinPart=false NoSendJoinPart=false
@@ -846,6 +863,10 @@ ShowUserTyping=false
#Default "<clipped message>" #Default "<clipped message>"
MessageClipped="<clipped message>" MessageClipped="<clipped message>"
#If enabled use the slack "Real Name" as username.
#OPTIONAL (default false)
UseFullName=false
################################################################### ###################################################################
#discord section #discord section
################################################################### ###################################################################
@@ -1020,6 +1041,12 @@ DisableWebPagePreview=false
#OPTIONAL (default false) #OPTIONAL (default false)
UseFirstName=false UseFirstName=false
#If enabled use the "Full Name" as username. If this is empty use the Username
#If disabled use the "Username" as username. If this is empty use the First Name and Last Name as Full Name
#If all names are empty, username will be "unknown"
#OPTIONAL (default false)
UseFullName=false
#WARNING! If enabled this will relay GIF/stickers/documents and other attachments as URLs #WARNING! If enabled this will relay GIF/stickers/documents and other attachments as URLs
#Those URLs will contain your bot-token. This may not be what you want. #Those URLs will contain your bot-token. This may not be what you want.
#For now there is no secure way to relay GIF/stickers/documents without seeing your token. #For now there is no secure way to relay GIF/stickers/documents without seeing your token.
@@ -1111,7 +1138,7 @@ Label=""
RemoteNickFormat="[{PROTOCOL}] <{NICK}> " RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges #Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord #Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false) #OPTIONAL (default false)
ShowJoinPart=false ShowJoinPart=false
@@ -1125,6 +1152,12 @@ StripNick=false
#OPTIONAL (default false) #OPTIONAL (default false)
ShowTopicChange=false ShowTopicChange=false
#Opportunistically preserve threaded replies between Telegram groups.
#This only works if the parent message is still in the cache.
#Cache is flushed between restarts.
#OPTIONAL (default false)
PreserveThreading=false
################################################################### ###################################################################
#rocketchat section #rocketchat section
################################################################### ###################################################################
@@ -1242,7 +1275,7 @@ Label=""
RemoteNickFormat="[{PROTOCOL}] <{NICK}> " RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges #Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord #Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false) #OPTIONAL (default false)
ShowJoinPart=false ShowJoinPart=false
@@ -1296,6 +1329,15 @@ HTMLDisable=false
# UseUserName shows the username instead of the server nickname # UseUserName shows the username instead of the server nickname
UseUserName=false UseUserName=false
# Matrix quotes replies and as of matterbridge 1.24.0 we strip those as this causes
# issues with bridges support threading and have PreserveThreading enabled.
# But if you for example use mattermost or discord with webhooks you'll need to enable
# this (and keep PreserveThreading disabled) if you want something that looks like a reply from matrix.
# See issues:
# - https://github.com/42wim/matterbridge/issues/1819
# - https://github.com/42wim/matterbridge/issues/1780
KeepQuotedReply=false
#Nicks you want to ignore. #Nicks you want to ignore.
#Regular expressions supported #Regular expressions supported
#Messages from those users will not be sent to other bridges. #Messages from those users will not be sent to other bridges.
@@ -1345,10 +1387,15 @@ Label=""
RemoteNickFormat="[{PROTOCOL}] <{NICK}> " RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges #Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord #Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false) #OPTIONAL (default false)
ShowJoinPart=false ShowJoinPart=false
#Rename the bot in the current room to the username of the message
#This will make an additional API request per message and will probably count towards rate limits
#OPTIONAL (default false)
SpoofUsername=false
#StripNick only allows alphanumerical nicks. See https://github.com/42wim/matterbridge/issues/285 #StripNick only allows alphanumerical nicks. See https://github.com/42wim/matterbridge/issues/285
#It will strip other characters from the nick #It will strip other characters from the nick
#OPTIONAL (default false) #OPTIONAL (default false)
@@ -1437,7 +1484,7 @@ Label=""
RemoteNickFormat="[{PROTOCOL}] <{NICK}> " RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges #Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord #Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false) #OPTIONAL (default false)
ShowJoinPart=false ShowJoinPart=false
@@ -1525,6 +1572,15 @@ SkipTLSVerify=false
#Default "<clipped message>" #Default "<clipped message>"
MessageClipped="<clipped message>" MessageClipped="<clipped message>"
#Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false)
ShowJoinPart=false
#Do not send joins/parts to other bridges
#OPTIONAL (default false)
NoSendJoinPart=false
################################################################### ###################################################################
#VK #VK
################################################################### ###################################################################
@@ -1534,10 +1590,6 @@ MessageClipped="<clipped message>"
#See https://vk.com/dev/bots_docs #See https://vk.com/dev/bots_docs
Token="Yourtokenhere" Token="Yourtokenhere"
#Group ID
#For example in URL https://vk.com/public168963511 group ID is 168963511
GroupID=123456789
################################################################### ###################################################################
# WhatsApp # WhatsApp
################################################################### ###################################################################
@@ -1641,7 +1693,7 @@ Label=""
RemoteNickFormat="[{PROTOCOL}] <{NICK}> " RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges #Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack, discord #Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
#OPTIONAL (default false) #OPTIONAL (default false)
ShowJoinPart=false ShowJoinPart=false
@@ -1655,6 +1707,18 @@ StripNick=false
#OPTIONAL (default false) #OPTIONAL (default false)
ShowTopicChange=false ShowTopicChange=false
###################################################################
# Harmony
###################################################################
[harmony.chat_harmonyapp_io]
Homeserver = "https://chat.harmonyapp.io:2289"
Token = "your token goes here"
UserID = "user id of the bot account"
Community = "community id that channels will be located in"
UseUserName = true
RemoteNickFormat = "{NICK}"
################################################################### ###################################################################
#API #API
################################################################### ###################################################################
@@ -1700,6 +1764,7 @@ RemoteNickFormat="{NICK}"
#RemoteNickFormat defines how remote users appear on this bridge #RemoteNickFormat defines how remote users appear on this bridge
#The string "{NICK}" (case sensitive) will be replaced by the actual nick. #The string "{NICK}" (case sensitive) will be replaced by the actual nick.
#The string "{NOPINGNICK}" (case sensitive) will be replaced by the actual nick / username, but with a ZWSP inside the nick, so the irc user with the same nick won't get pinged.
#The string "{USERID}" (case sensitive) will be replaced by the user ID. #The string "{USERID}" (case sensitive) will be replaced by the user ID.
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge #The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
#The string "{LABEL}" (case sensitive) will be replaced by label= field of the sending bridge #The string "{LABEL}" (case sensitive) will be replaced by label= field of the sending bridge
@@ -1872,7 +1937,8 @@ enable=true
# ------------------------------------------------------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------------------------------------------------------
# irc | channel | #general | The # symbol is required and should be lowercase! # irc | channel | #general | The # symbol is required and should be lowercase!
# ------------------------------------------------------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------------------------------------------------------
# mattermost | channel | general | This is the channel name as seen in the URL, not the display name # | channel | general | This is the channel name as seen in the URL, not the display name
# mattermost | channel id | ID:oc4wifyuojgw5f3nsuweesmz8w | This is the channel ID (only use if you know what you're doing)
# ------------------------------------------------------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------------------------------------------------------
# matrix | #channel:server | #yourchannel:matrix.org | Encrypted rooms are not supported in matrix # matrix | #channel:server | #yourchannel:matrix.org | Encrypted rooms are not supported in matrix
# ------------------------------------------------------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------------------------------------------------------
@@ -1898,7 +1964,7 @@ enable=true
# ------------------------------------------------------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------------------------------------------------------
# xmpp | channel | general | The room name # xmpp | channel | general | The room name
# ------------------------------------------------------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------------------------------------------------------
# zulip | stream/topic:topic | general/off-topic:food | Do not use the # when specifying a topic # zulip | stream/topic:topic | general/topic:food | Do not use the # when specifying a topic
# ------------------------------------------------------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------------------------------------------------------
# #
@@ -1947,6 +2013,10 @@ enable=true
account="zulip.streamchat" account="zulip.streamchat"
channel="general/topic:mytopic" channel="general/topic:mytopic"
[[gateway.inout]]
account="harmony.chat_harmonyapp_io"
channel="channel id goes here"
#API example #API example
#[[gateway.inout]] #[[gateway.inout]]
#account="api.local" #account="api.local"

View File

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

View File

@@ -5,7 +5,7 @@ RUN apk add \
go \ go \
git \ git \
&& cd /go/src/matterbridge \ && 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 FROM alpine
RUN apk --no-cache add \ RUN apk --no-cache add \

27
vendor/filippo.io/edwards25519/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,27 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

14
vendor/filippo.io/edwards25519/README.md generated vendored Normal file
View File

@@ -0,0 +1,14 @@
# filippo.io/edwards25519
```
import "filippo.io/edwards25519"
```
This library implements the edwards25519 elliptic curve, exposing the necessary APIs to build a wide array of higher-level primitives.
Read the docs at [pkg.go.dev/filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519).
The code is originally derived from Adam Langley's internal implementation in the Go standard library, and includes George Tankersley's [performance improvements](https://golang.org/cl/71950). It was then further developed by Henry de Valence for use in ristretto255, and was finally [merged back into the Go standard library](https://golang.org/cl/276272) as of Go 1.17. It now tracks the upstream codebase and extends it with additional functionality.
Most users don't need this package, and should instead use `crypto/ed25519` for signatures, `golang.org/x/crypto/curve25519` for Diffie-Hellman, or `github.com/gtank/ristretto255` for prime order group logic. However, for anyone currently using a fork of `crypto/ed25519/internal/edwards25519` or `github.com/agl/edwards25519`, this package should be a safer, faster, and more powerful alternative.
Since this package is meant to curb proliferation of edwards25519 implementations in the Go ecosystem, it welcomes requests for new APIs or reviewable performance improvements.

20
vendor/filippo.io/edwards25519/doc.go generated vendored Normal file
View File

@@ -0,0 +1,20 @@
// Copyright (c) 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package edwards25519 implements group logic for the twisted Edwards curve
//
// -x^2 + y^2 = 1 + -(121665/121666)*x^2*y^2
//
// This is better known as the Edwards curve equivalent to Curve25519, and is
// the curve used by the Ed25519 signature scheme.
//
// Most users don't need this package, and should instead use crypto/ed25519 for
// signatures, golang.org/x/crypto/curve25519 for Diffie-Hellman, or
// github.com/gtank/ristretto255 for prime order group logic.
//
// However, developers who do need to interact with low-level edwards25519
// operations can use this package, which is an extended version of
// crypto/ed25519/internal/edwards25519 from the standard library repackaged as
// an importable module.
package edwards25519

428
vendor/filippo.io/edwards25519/edwards25519.go generated vendored Normal file
View File

@@ -0,0 +1,428 @@
// Copyright (c) 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package edwards25519
import (
"errors"
"filippo.io/edwards25519/field"
)
// Point types.
type projP1xP1 struct {
X, Y, Z, T field.Element
}
type projP2 struct {
X, Y, Z field.Element
}
// Point represents a point on the edwards25519 curve.
//
// This type works similarly to math/big.Int, and all arguments and receivers
// are allowed to alias.
//
// The zero value is NOT valid, and it may be used only as a receiver.
type Point struct {
// The point is internally represented in extended coordinates (X, Y, Z, T)
// where x = X/Z, y = Y/Z, and xy = T/Z per https://eprint.iacr.org/2008/522.
x, y, z, t field.Element
// Make the type not comparable (i.e. used with == or as a map key), as
// equivalent points can be represented by different Go values.
_ incomparable
}
type incomparable [0]func()
func checkInitialized(points ...*Point) {
for _, p := range points {
if p.x == (field.Element{}) && p.y == (field.Element{}) {
panic("edwards25519: use of uninitialized Point")
}
}
}
type projCached struct {
YplusX, YminusX, Z, T2d field.Element
}
type affineCached struct {
YplusX, YminusX, T2d field.Element
}
// Constructors.
func (v *projP2) Zero() *projP2 {
v.X.Zero()
v.Y.One()
v.Z.One()
return v
}
// identity is the point at infinity.
var identity, _ = new(Point).SetBytes([]byte{
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
// NewIdentityPoint returns a new Point set to the identity.
func NewIdentityPoint() *Point {
return new(Point).Set(identity)
}
// generator is the canonical curve basepoint. See TestGenerator for the
// correspondence of this encoding with the values in RFC 8032.
var generator, _ = new(Point).SetBytes([]byte{
0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66})
// NewGeneratorPoint returns a new Point set to the canonical generator.
func NewGeneratorPoint() *Point {
return new(Point).Set(generator)
}
func (v *projCached) Zero() *projCached {
v.YplusX.One()
v.YminusX.One()
v.Z.One()
v.T2d.Zero()
return v
}
func (v *affineCached) Zero() *affineCached {
v.YplusX.One()
v.YminusX.One()
v.T2d.Zero()
return v
}
// Assignments.
// Set sets v = u, and returns v.
func (v *Point) Set(u *Point) *Point {
*v = *u
return v
}
// Encoding.
// Bytes returns the canonical 32-byte encoding of v, according to RFC 8032,
// Section 5.1.2.
func (v *Point) Bytes() []byte {
// This function is outlined to make the allocations inline in the caller
// rather than happen on the heap.
var buf [32]byte
return v.bytes(&buf)
}
func (v *Point) bytes(buf *[32]byte) []byte {
checkInitialized(v)
var zInv, x, y field.Element
zInv.Invert(&v.z) // zInv = 1 / Z
x.Multiply(&v.x, &zInv) // x = X / Z
y.Multiply(&v.y, &zInv) // y = Y / Z
out := copyFieldElement(buf, &y)
out[31] |= byte(x.IsNegative() << 7)
return out
}
var feOne = new(field.Element).One()
// SetBytes sets v = x, where x is a 32-byte encoding of v. If x does not
// represent a valid point on the curve, SetBytes returns nil and an error and
// the receiver is unchanged. Otherwise, SetBytes returns v.
//
// Note that SetBytes accepts all non-canonical encodings of valid points.
// That is, it follows decoding rules that match most implementations in
// the ecosystem rather than RFC 8032.
func (v *Point) SetBytes(x []byte) (*Point, error) {
// Specifically, the non-canonical encodings that are accepted are
// 1) the ones where the field element is not reduced (see the
// (*field.Element).SetBytes docs) and
// 2) the ones where the x-coordinate is zero and the sign bit is set.
//
// This is consistent with crypto/ed25519/internal/edwards25519. Read more
// at https://hdevalence.ca/blog/2020-10-04-its-25519am, specifically the
// "Canonical A, R" section.
y, err := new(field.Element).SetBytes(x)
if err != nil {
return nil, errors.New("edwards25519: invalid point encoding length")
}
// -x² + y² = 1 + dx²y²
// x² + dx²y² = x²(dy² + 1) = y² - 1
// x² = (y² - 1) / (dy² + 1)
// u = y² - 1
y2 := new(field.Element).Square(y)
u := new(field.Element).Subtract(y2, feOne)
// v = dy² + 1
vv := new(field.Element).Multiply(y2, d)
vv = vv.Add(vv, feOne)
// x = +√(u/v)
xx, wasSquare := new(field.Element).SqrtRatio(u, vv)
if wasSquare == 0 {
return nil, errors.New("edwards25519: invalid point encoding")
}
// Select the negative square root if the sign bit is set.
xxNeg := new(field.Element).Negate(xx)
xx = xx.Select(xxNeg, xx, int(x[31]>>7))
v.x.Set(xx)
v.y.Set(y)
v.z.One()
v.t.Multiply(xx, y) // xy = T / Z
return v, nil
}
func copyFieldElement(buf *[32]byte, v *field.Element) []byte {
copy(buf[:], v.Bytes())
return buf[:]
}
// Conversions.
func (v *projP2) FromP1xP1(p *projP1xP1) *projP2 {
v.X.Multiply(&p.X, &p.T)
v.Y.Multiply(&p.Y, &p.Z)
v.Z.Multiply(&p.Z, &p.T)
return v
}
func (v *projP2) FromP3(p *Point) *projP2 {
v.X.Set(&p.x)
v.Y.Set(&p.y)
v.Z.Set(&p.z)
return v
}
func (v *Point) fromP1xP1(p *projP1xP1) *Point {
v.x.Multiply(&p.X, &p.T)
v.y.Multiply(&p.Y, &p.Z)
v.z.Multiply(&p.Z, &p.T)
v.t.Multiply(&p.X, &p.Y)
return v
}
func (v *Point) fromP2(p *projP2) *Point {
v.x.Multiply(&p.X, &p.Z)
v.y.Multiply(&p.Y, &p.Z)
v.z.Square(&p.Z)
v.t.Multiply(&p.X, &p.Y)
return v
}
// d is a constant in the curve equation.
var d, _ = new(field.Element).SetBytes([]byte{
0xa3, 0x78, 0x59, 0x13, 0xca, 0x4d, 0xeb, 0x75,
0xab, 0xd8, 0x41, 0x41, 0x4d, 0x0a, 0x70, 0x00,
0x98, 0xe8, 0x79, 0x77, 0x79, 0x40, 0xc7, 0x8c,
0x73, 0xfe, 0x6f, 0x2b, 0xee, 0x6c, 0x03, 0x52})
var d2 = new(field.Element).Add(d, d)
func (v *projCached) FromP3(p *Point) *projCached {
v.YplusX.Add(&p.y, &p.x)
v.YminusX.Subtract(&p.y, &p.x)
v.Z.Set(&p.z)
v.T2d.Multiply(&p.t, d2)
return v
}
func (v *affineCached) FromP3(p *Point) *affineCached {
v.YplusX.Add(&p.y, &p.x)
v.YminusX.Subtract(&p.y, &p.x)
v.T2d.Multiply(&p.t, d2)
var invZ field.Element
invZ.Invert(&p.z)
v.YplusX.Multiply(&v.YplusX, &invZ)
v.YminusX.Multiply(&v.YminusX, &invZ)
v.T2d.Multiply(&v.T2d, &invZ)
return v
}
// (Re)addition and subtraction.
// Add sets v = p + q, and returns v.
func (v *Point) Add(p, q *Point) *Point {
checkInitialized(p, q)
qCached := new(projCached).FromP3(q)
result := new(projP1xP1).Add(p, qCached)
return v.fromP1xP1(result)
}
// Subtract sets v = p - q, and returns v.
func (v *Point) Subtract(p, q *Point) *Point {
checkInitialized(p, q)
qCached := new(projCached).FromP3(q)
result := new(projP1xP1).Sub(p, qCached)
return v.fromP1xP1(result)
}
func (v *projP1xP1) Add(p *Point, q *projCached) *projP1xP1 {
var YplusX, YminusX, PP, MM, TT2d, ZZ2 field.Element
YplusX.Add(&p.y, &p.x)
YminusX.Subtract(&p.y, &p.x)
PP.Multiply(&YplusX, &q.YplusX)
MM.Multiply(&YminusX, &q.YminusX)
TT2d.Multiply(&p.t, &q.T2d)
ZZ2.Multiply(&p.z, &q.Z)
ZZ2.Add(&ZZ2, &ZZ2)
v.X.Subtract(&PP, &MM)
v.Y.Add(&PP, &MM)
v.Z.Add(&ZZ2, &TT2d)
v.T.Subtract(&ZZ2, &TT2d)
return v
}
func (v *projP1xP1) Sub(p *Point, q *projCached) *projP1xP1 {
var YplusX, YminusX, PP, MM, TT2d, ZZ2 field.Element
YplusX.Add(&p.y, &p.x)
YminusX.Subtract(&p.y, &p.x)
PP.Multiply(&YplusX, &q.YminusX) // flipped sign
MM.Multiply(&YminusX, &q.YplusX) // flipped sign
TT2d.Multiply(&p.t, &q.T2d)
ZZ2.Multiply(&p.z, &q.Z)
ZZ2.Add(&ZZ2, &ZZ2)
v.X.Subtract(&PP, &MM)
v.Y.Add(&PP, &MM)
v.Z.Subtract(&ZZ2, &TT2d) // flipped sign
v.T.Add(&ZZ2, &TT2d) // flipped sign
return v
}
func (v *projP1xP1) AddAffine(p *Point, q *affineCached) *projP1xP1 {
var YplusX, YminusX, PP, MM, TT2d, Z2 field.Element
YplusX.Add(&p.y, &p.x)
YminusX.Subtract(&p.y, &p.x)
PP.Multiply(&YplusX, &q.YplusX)
MM.Multiply(&YminusX, &q.YminusX)
TT2d.Multiply(&p.t, &q.T2d)
Z2.Add(&p.z, &p.z)
v.X.Subtract(&PP, &MM)
v.Y.Add(&PP, &MM)
v.Z.Add(&Z2, &TT2d)
v.T.Subtract(&Z2, &TT2d)
return v
}
func (v *projP1xP1) SubAffine(p *Point, q *affineCached) *projP1xP1 {
var YplusX, YminusX, PP, MM, TT2d, Z2 field.Element
YplusX.Add(&p.y, &p.x)
YminusX.Subtract(&p.y, &p.x)
PP.Multiply(&YplusX, &q.YminusX) // flipped sign
MM.Multiply(&YminusX, &q.YplusX) // flipped sign
TT2d.Multiply(&p.t, &q.T2d)
Z2.Add(&p.z, &p.z)
v.X.Subtract(&PP, &MM)
v.Y.Add(&PP, &MM)
v.Z.Subtract(&Z2, &TT2d) // flipped sign
v.T.Add(&Z2, &TT2d) // flipped sign
return v
}
// Doubling.
func (v *projP1xP1) Double(p *projP2) *projP1xP1 {
var XX, YY, ZZ2, XplusYsq field.Element
XX.Square(&p.X)
YY.Square(&p.Y)
ZZ2.Square(&p.Z)
ZZ2.Add(&ZZ2, &ZZ2)
XplusYsq.Add(&p.X, &p.Y)
XplusYsq.Square(&XplusYsq)
v.Y.Add(&YY, &XX)
v.Z.Subtract(&YY, &XX)
v.X.Subtract(&XplusYsq, &v.Y)
v.T.Subtract(&ZZ2, &v.Z)
return v
}
// Negation.
// Negate sets v = -p, and returns v.
func (v *Point) Negate(p *Point) *Point {
checkInitialized(p)
v.x.Negate(&p.x)
v.y.Set(&p.y)
v.z.Set(&p.z)
v.t.Negate(&p.t)
return v
}
// Equal returns 1 if v is equivalent to u, and 0 otherwise.
func (v *Point) Equal(u *Point) int {
checkInitialized(v, u)
var t1, t2, t3, t4 field.Element
t1.Multiply(&v.x, &u.z)
t2.Multiply(&u.x, &v.z)
t3.Multiply(&v.y, &u.z)
t4.Multiply(&u.y, &v.z)
return t1.Equal(&t2) & t3.Equal(&t4)
}
// Constant-time operations
// Select sets v to a if cond == 1 and to b if cond == 0.
func (v *projCached) Select(a, b *projCached, cond int) *projCached {
v.YplusX.Select(&a.YplusX, &b.YplusX, cond)
v.YminusX.Select(&a.YminusX, &b.YminusX, cond)
v.Z.Select(&a.Z, &b.Z, cond)
v.T2d.Select(&a.T2d, &b.T2d, cond)
return v
}
// Select sets v to a if cond == 1 and to b if cond == 0.
func (v *affineCached) Select(a, b *affineCached, cond int) *affineCached {
v.YplusX.Select(&a.YplusX, &b.YplusX, cond)
v.YminusX.Select(&a.YminusX, &b.YminusX, cond)
v.T2d.Select(&a.T2d, &b.T2d, cond)
return v
}
// CondNeg negates v if cond == 1 and leaves it unchanged if cond == 0.
func (v *projCached) CondNeg(cond int) *projCached {
v.YplusX.Swap(&v.YminusX, cond)
v.T2d.Select(new(field.Element).Negate(&v.T2d), &v.T2d, cond)
return v
}
// CondNeg negates v if cond == 1 and leaves it unchanged if cond == 0.
func (v *affineCached) CondNeg(cond int) *affineCached {
v.YplusX.Swap(&v.YminusX, cond)
v.T2d.Select(new(field.Element).Negate(&v.T2d), &v.T2d, cond)
return v
}

343
vendor/filippo.io/edwards25519/extra.go generated vendored Normal file
View File

@@ -0,0 +1,343 @@
// Copyright (c) 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package edwards25519
// This file contains additional functionality that is not included in the
// upstream crypto/ed25519/internal/edwards25519 package.
import (
"errors"
"filippo.io/edwards25519/field"
)
// ExtendedCoordinates returns v in extended coordinates (X:Y:Z:T) where
// x = X/Z, y = Y/Z, and xy = T/Z as in https://eprint.iacr.org/2008/522.
func (v *Point) ExtendedCoordinates() (X, Y, Z, T *field.Element) {
// This function is outlined to make the allocations inline in the caller
// rather than happen on the heap. Don't change the style without making
// sure it doesn't increase the inliner cost.
var e [4]field.Element
X, Y, Z, T = v.extendedCoordinates(&e)
return
}
func (v *Point) extendedCoordinates(e *[4]field.Element) (X, Y, Z, T *field.Element) {
checkInitialized(v)
X = e[0].Set(&v.x)
Y = e[1].Set(&v.y)
Z = e[2].Set(&v.z)
T = e[3].Set(&v.t)
return
}
// SetExtendedCoordinates sets v = (X:Y:Z:T) in extended coordinates where
// x = X/Z, y = Y/Z, and xy = T/Z as in https://eprint.iacr.org/2008/522.
//
// If the coordinates are invalid or don't represent a valid point on the curve,
// SetExtendedCoordinates returns nil and an error and the receiver is
// unchanged. Otherwise, SetExtendedCoordinates returns v.
func (v *Point) SetExtendedCoordinates(X, Y, Z, T *field.Element) (*Point, error) {
if !isOnCurve(X, Y, Z, T) {
return nil, errors.New("edwards25519: invalid point coordinates")
}
v.x.Set(X)
v.y.Set(Y)
v.z.Set(Z)
v.t.Set(T)
return v, nil
}
func isOnCurve(X, Y, Z, T *field.Element) bool {
var lhs, rhs field.Element
XX := new(field.Element).Square(X)
YY := new(field.Element).Square(Y)
ZZ := new(field.Element).Square(Z)
TT := new(field.Element).Square(T)
// -x² + y² = 1 + dx²y²
// -(X/Z)² + (Y/Z)² = 1 + d(T/Z)²
// -X² + Y² = Z² + dT²
lhs.Subtract(YY, XX)
rhs.Multiply(d, TT).Add(&rhs, ZZ)
if lhs.Equal(&rhs) != 1 {
return false
}
// xy = T/Z
// XY/Z² = T/Z
// XY = TZ
lhs.Multiply(X, Y)
rhs.Multiply(T, Z)
return lhs.Equal(&rhs) == 1
}
// BytesMontgomery converts v to a point on the birationally-equivalent
// Curve25519 Montgomery curve, and returns its canonical 32 bytes encoding
// according to RFC 7748.
//
// Note that BytesMontgomery only encodes the u-coordinate, so v and -v encode
// to the same value. If v is the identity point, BytesMontgomery returns 32
// zero bytes, analogously to the X25519 function.
func (v *Point) BytesMontgomery() []byte {
// This function is outlined to make the allocations inline in the caller
// rather than happen on the heap.
var buf [32]byte
return v.bytesMontgomery(&buf)
}
func (v *Point) bytesMontgomery(buf *[32]byte) []byte {
checkInitialized(v)
// RFC 7748, Section 4.1 provides the bilinear map to calculate the
// Montgomery u-coordinate
//
// u = (1 + y) / (1 - y)
//
// where y = Y / Z.
var y, recip, u field.Element
y.Multiply(&v.y, y.Invert(&v.z)) // y = Y / Z
recip.Invert(recip.Subtract(feOne, &y)) // r = 1/(1 - y)
u.Multiply(u.Add(feOne, &y), &recip) // u = (1 + y)*r
return copyFieldElement(buf, &u)
}
// MultByCofactor sets v = 8 * p, and returns v.
func (v *Point) MultByCofactor(p *Point) *Point {
checkInitialized(p)
result := projP1xP1{}
pp := (&projP2{}).FromP3(p)
result.Double(pp)
pp.FromP1xP1(&result)
result.Double(pp)
pp.FromP1xP1(&result)
result.Double(pp)
return v.fromP1xP1(&result)
}
// Given k > 0, set s = s**(2*i).
func (s *Scalar) pow2k(k int) {
for i := 0; i < k; i++ {
s.Multiply(s, s)
}
}
// Invert sets s to the inverse of a nonzero scalar v, and returns s.
//
// If t is zero, Invert returns zero.
func (s *Scalar) Invert(t *Scalar) *Scalar {
// Uses a hardcoded sliding window of width 4.
var table [8]Scalar
var tt Scalar
tt.Multiply(t, t)
table[0] = *t
for i := 0; i < 7; i++ {
table[i+1].Multiply(&table[i], &tt)
}
// Now table = [t**1, t**3, t**7, t**11, t**13, t**15]
// so t**k = t[k/2] for odd k
// To compute the sliding window digits, use the following Sage script:
// sage: import itertools
// sage: def sliding_window(w,k):
// ....: digits = []
// ....: while k > 0:
// ....: if k % 2 == 1:
// ....: kmod = k % (2**w)
// ....: digits.append(kmod)
// ....: k = k - kmod
// ....: else:
// ....: digits.append(0)
// ....: k = k // 2
// ....: return digits
// Now we can compute s roughly as follows:
// sage: s = 1
// sage: for coeff in reversed(sliding_window(4,l-2)):
// ....: s = s*s
// ....: if coeff > 0 :
// ....: s = s*t**coeff
// This works on one bit at a time, with many runs of zeros.
// The digits can be collapsed into [(count, coeff)] as follows:
// sage: [(len(list(group)),d) for d,group in itertools.groupby(sliding_window(4,l-2))]
// Entries of the form (k, 0) turn into pow2k(k)
// Entries of the form (1, coeff) turn into a squaring and then a table lookup.
// We can fold the squaring into the previous pow2k(k) as pow2k(k+1).
*s = table[1/2]
s.pow2k(127 + 1)
s.Multiply(s, &table[1/2])
s.pow2k(4 + 1)
s.Multiply(s, &table[9/2])
s.pow2k(3 + 1)
s.Multiply(s, &table[11/2])
s.pow2k(3 + 1)
s.Multiply(s, &table[13/2])
s.pow2k(3 + 1)
s.Multiply(s, &table[15/2])
s.pow2k(4 + 1)
s.Multiply(s, &table[7/2])
s.pow2k(4 + 1)
s.Multiply(s, &table[15/2])
s.pow2k(3 + 1)
s.Multiply(s, &table[5/2])
s.pow2k(3 + 1)
s.Multiply(s, &table[1/2])
s.pow2k(4 + 1)
s.Multiply(s, &table[15/2])
s.pow2k(4 + 1)
s.Multiply(s, &table[15/2])
s.pow2k(4 + 1)
s.Multiply(s, &table[7/2])
s.pow2k(3 + 1)
s.Multiply(s, &table[3/2])
s.pow2k(4 + 1)
s.Multiply(s, &table[11/2])
s.pow2k(5 + 1)
s.Multiply(s, &table[11/2])
s.pow2k(9 + 1)
s.Multiply(s, &table[9/2])
s.pow2k(3 + 1)
s.Multiply(s, &table[3/2])
s.pow2k(4 + 1)
s.Multiply(s, &table[3/2])
s.pow2k(4 + 1)
s.Multiply(s, &table[3/2])
s.pow2k(4 + 1)
s.Multiply(s, &table[9/2])
s.pow2k(3 + 1)
s.Multiply(s, &table[7/2])
s.pow2k(3 + 1)
s.Multiply(s, &table[3/2])
s.pow2k(3 + 1)
s.Multiply(s, &table[13/2])
s.pow2k(3 + 1)
s.Multiply(s, &table[7/2])
s.pow2k(4 + 1)
s.Multiply(s, &table[9/2])
s.pow2k(3 + 1)
s.Multiply(s, &table[15/2])
s.pow2k(4 + 1)
s.Multiply(s, &table[11/2])
return s
}
// MultiScalarMult sets v = sum(scalars[i] * points[i]), and returns v.
//
// Execution time depends only on the lengths of the two slices, which must match.
func (v *Point) MultiScalarMult(scalars []*Scalar, points []*Point) *Point {
if len(scalars) != len(points) {
panic("edwards25519: called MultiScalarMult with different size inputs")
}
checkInitialized(points...)
// Proceed as in the single-base case, but share doublings
// between each point in the multiscalar equation.
// Build lookup tables for each point
tables := make([]projLookupTable, len(points))
for i := range tables {
tables[i].FromP3(points[i])
}
// Compute signed radix-16 digits for each scalar
digits := make([][64]int8, len(scalars))
for i := range digits {
digits[i] = scalars[i].signedRadix16()
}
// Unwrap first loop iteration to save computing 16*identity
multiple := &projCached{}
tmp1 := &projP1xP1{}
tmp2 := &projP2{}
// Lookup-and-add the appropriate multiple of each input point
for j := range tables {
tables[j].SelectInto(multiple, digits[j][63])
tmp1.Add(v, multiple) // tmp1 = v + x_(j,63)*Q in P1xP1 coords
v.fromP1xP1(tmp1) // update v
}
tmp2.FromP3(v) // set up tmp2 = v in P2 coords for next iteration
for i := 62; i >= 0; i-- {
tmp1.Double(tmp2) // tmp1 = 2*(prev) in P1xP1 coords
tmp2.FromP1xP1(tmp1) // tmp2 = 2*(prev) in P2 coords
tmp1.Double(tmp2) // tmp1 = 4*(prev) in P1xP1 coords
tmp2.FromP1xP1(tmp1) // tmp2 = 4*(prev) in P2 coords
tmp1.Double(tmp2) // tmp1 = 8*(prev) in P1xP1 coords
tmp2.FromP1xP1(tmp1) // tmp2 = 8*(prev) in P2 coords
tmp1.Double(tmp2) // tmp1 = 16*(prev) in P1xP1 coords
v.fromP1xP1(tmp1) // v = 16*(prev) in P3 coords
// Lookup-and-add the appropriate multiple of each input point
for j := range tables {
tables[j].SelectInto(multiple, digits[j][i])
tmp1.Add(v, multiple) // tmp1 = v + x_(j,i)*Q in P1xP1 coords
v.fromP1xP1(tmp1) // update v
}
tmp2.FromP3(v) // set up tmp2 = v in P2 coords for next iteration
}
return v
}
// VarTimeMultiScalarMult sets v = sum(scalars[i] * points[i]), and returns v.
//
// Execution time depends on the inputs.
func (v *Point) VarTimeMultiScalarMult(scalars []*Scalar, points []*Point) *Point {
if len(scalars) != len(points) {
panic("edwards25519: called VarTimeMultiScalarMult with different size inputs")
}
checkInitialized(points...)
// Generalize double-base NAF computation to arbitrary sizes.
// Here all the points are dynamic, so we only use the smaller
// tables.
// Build lookup tables for each point
tables := make([]nafLookupTable5, len(points))
for i := range tables {
tables[i].FromP3(points[i])
}
// Compute a NAF for each scalar
nafs := make([][256]int8, len(scalars))
for i := range nafs {
nafs[i] = scalars[i].nonAdjacentForm(5)
}
multiple := &projCached{}
tmp1 := &projP1xP1{}
tmp2 := &projP2{}
tmp2.Zero()
// Move from high to low bits, doubling the accumulator
// at each iteration and checking whether there is a nonzero
// coefficient to look up a multiple of.
//
// Skip trying to find the first nonzero coefficent, because
// searching might be more work than a few extra doublings.
for i := 255; i >= 0; i-- {
tmp1.Double(tmp2)
for j := range nafs {
if nafs[j][i] > 0 {
v.fromP1xP1(tmp1)
tables[j].SelectInto(multiple, nafs[j][i])
tmp1.Add(v, multiple)
} else if nafs[j][i] < 0 {
v.fromP1xP1(tmp1)
tables[j].SelectInto(multiple, -nafs[j][i])
tmp1.Sub(v, multiple)
}
}
tmp2.FromP1xP1(tmp1)
}
v.fromP2(tmp2)
return v
}

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

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

13
vendor/filippo.io/edwards25519/field/fe_amd64.go generated vendored Normal file
View File

@@ -0,0 +1,13 @@
// Code generated by command: go run fe_amd64_asm.go -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field. DO NOT EDIT.
// +build amd64,gc,!purego
package field
// feMul sets out = a * b. It works like feMulGeneric.
//go:noescape
func feMul(out *Element, a *Element, b *Element)
// feSquare sets out = a * a. It works like feSquareGeneric.
//go:noescape
func feSquare(out *Element, a *Element)

378
vendor/filippo.io/edwards25519/field/fe_amd64.s generated vendored Normal file
View File

@@ -0,0 +1,378 @@
// Code generated by command: go run fe_amd64_asm.go -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field. DO NOT EDIT.
// +build amd64,gc,!purego
#include "textflag.h"
// func feMul(out *Element, a *Element, b *Element)
TEXT ·feMul(SB), NOSPLIT, $0-24
MOVQ a+8(FP), CX
MOVQ b+16(FP), BX
// r0 = a0×b0
MOVQ (CX), AX
MULQ (BX)
MOVQ AX, DI
MOVQ DX, SI
// r0 += 19×a1×b4
MOVQ 8(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 32(BX)
ADDQ AX, DI
ADCQ DX, SI
// r0 += 19×a2×b3
MOVQ 16(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 24(BX)
ADDQ AX, DI
ADCQ DX, SI
// r0 += 19×a3×b2
MOVQ 24(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 16(BX)
ADDQ AX, DI
ADCQ DX, SI
// r0 += 19×a4×b1
MOVQ 32(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 8(BX)
ADDQ AX, DI
ADCQ DX, SI
// r1 = a0×b1
MOVQ (CX), AX
MULQ 8(BX)
MOVQ AX, R9
MOVQ DX, R8
// r1 += a1×b0
MOVQ 8(CX), AX
MULQ (BX)
ADDQ AX, R9
ADCQ DX, R8
// r1 += 19×a2×b4
MOVQ 16(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 32(BX)
ADDQ AX, R9
ADCQ DX, R8
// r1 += 19×a3×b3
MOVQ 24(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 24(BX)
ADDQ AX, R9
ADCQ DX, R8
// r1 += 19×a4×b2
MOVQ 32(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 16(BX)
ADDQ AX, R9
ADCQ DX, R8
// r2 = a0×b2
MOVQ (CX), AX
MULQ 16(BX)
MOVQ AX, R11
MOVQ DX, R10
// r2 += a1×b1
MOVQ 8(CX), AX
MULQ 8(BX)
ADDQ AX, R11
ADCQ DX, R10
// r2 += a2×b0
MOVQ 16(CX), AX
MULQ (BX)
ADDQ AX, R11
ADCQ DX, R10
// r2 += 19×a3×b4
MOVQ 24(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 32(BX)
ADDQ AX, R11
ADCQ DX, R10
// r2 += 19×a4×b3
MOVQ 32(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 24(BX)
ADDQ AX, R11
ADCQ DX, R10
// r3 = a0×b3
MOVQ (CX), AX
MULQ 24(BX)
MOVQ AX, R13
MOVQ DX, R12
// r3 += a1×b2
MOVQ 8(CX), AX
MULQ 16(BX)
ADDQ AX, R13
ADCQ DX, R12
// r3 += a2×b1
MOVQ 16(CX), AX
MULQ 8(BX)
ADDQ AX, R13
ADCQ DX, R12
// r3 += a3×b0
MOVQ 24(CX), AX
MULQ (BX)
ADDQ AX, R13
ADCQ DX, R12
// r3 += 19×a4×b4
MOVQ 32(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 32(BX)
ADDQ AX, R13
ADCQ DX, R12
// r4 = a0×b4
MOVQ (CX), AX
MULQ 32(BX)
MOVQ AX, R15
MOVQ DX, R14
// r4 += a1×b3
MOVQ 8(CX), AX
MULQ 24(BX)
ADDQ AX, R15
ADCQ DX, R14
// r4 += a2×b2
MOVQ 16(CX), AX
MULQ 16(BX)
ADDQ AX, R15
ADCQ DX, R14
// r4 += a3×b1
MOVQ 24(CX), AX
MULQ 8(BX)
ADDQ AX, R15
ADCQ DX, R14
// r4 += a4×b0
MOVQ 32(CX), AX
MULQ (BX)
ADDQ AX, R15
ADCQ DX, R14
// First reduction chain
MOVQ $0x0007ffffffffffff, AX
SHLQ $0x0d, DI, SI
SHLQ $0x0d, R9, R8
SHLQ $0x0d, R11, R10
SHLQ $0x0d, R13, R12
SHLQ $0x0d, R15, R14
ANDQ AX, DI
IMUL3Q $0x13, R14, R14
ADDQ R14, DI
ANDQ AX, R9
ADDQ SI, R9
ANDQ AX, R11
ADDQ R8, R11
ANDQ AX, R13
ADDQ R10, R13
ANDQ AX, R15
ADDQ R12, R15
// Second reduction chain (carryPropagate)
MOVQ DI, SI
SHRQ $0x33, SI
MOVQ R9, R8
SHRQ $0x33, R8
MOVQ R11, R10
SHRQ $0x33, R10
MOVQ R13, R12
SHRQ $0x33, R12
MOVQ R15, R14
SHRQ $0x33, R14
ANDQ AX, DI
IMUL3Q $0x13, R14, R14
ADDQ R14, DI
ANDQ AX, R9
ADDQ SI, R9
ANDQ AX, R11
ADDQ R8, R11
ANDQ AX, R13
ADDQ R10, R13
ANDQ AX, R15
ADDQ R12, R15
// Store output
MOVQ out+0(FP), AX
MOVQ DI, (AX)
MOVQ R9, 8(AX)
MOVQ R11, 16(AX)
MOVQ R13, 24(AX)
MOVQ R15, 32(AX)
RET
// func feSquare(out *Element, a *Element)
TEXT ·feSquare(SB), NOSPLIT, $0-16
MOVQ a+8(FP), CX
// r0 = l0×l0
MOVQ (CX), AX
MULQ (CX)
MOVQ AX, SI
MOVQ DX, BX
// r0 += 38×l1×l4
MOVQ 8(CX), AX
IMUL3Q $0x26, AX, AX
MULQ 32(CX)
ADDQ AX, SI
ADCQ DX, BX
// r0 += 38×l2×l3
MOVQ 16(CX), AX
IMUL3Q $0x26, AX, AX
MULQ 24(CX)
ADDQ AX, SI
ADCQ DX, BX
// r1 = 2×l0×l1
MOVQ (CX), AX
SHLQ $0x01, AX
MULQ 8(CX)
MOVQ AX, R8
MOVQ DX, DI
// r1 += 38×l2×l4
MOVQ 16(CX), AX
IMUL3Q $0x26, AX, AX
MULQ 32(CX)
ADDQ AX, R8
ADCQ DX, DI
// r1 += 19×l3×l3
MOVQ 24(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 24(CX)
ADDQ AX, R8
ADCQ DX, DI
// r2 = 2×l0×l2
MOVQ (CX), AX
SHLQ $0x01, AX
MULQ 16(CX)
MOVQ AX, R10
MOVQ DX, R9
// r2 += l1×l1
MOVQ 8(CX), AX
MULQ 8(CX)
ADDQ AX, R10
ADCQ DX, R9
// r2 += 38×l3×l4
MOVQ 24(CX), AX
IMUL3Q $0x26, AX, AX
MULQ 32(CX)
ADDQ AX, R10
ADCQ DX, R9
// r3 = 2×l0×l3
MOVQ (CX), AX
SHLQ $0x01, AX
MULQ 24(CX)
MOVQ AX, R12
MOVQ DX, R11
// r3 += 2×l1×l2
MOVQ 8(CX), AX
IMUL3Q $0x02, AX, AX
MULQ 16(CX)
ADDQ AX, R12
ADCQ DX, R11
// r3 += 19×l4×l4
MOVQ 32(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 32(CX)
ADDQ AX, R12
ADCQ DX, R11
// r4 = 2×l0×l4
MOVQ (CX), AX
SHLQ $0x01, AX
MULQ 32(CX)
MOVQ AX, R14
MOVQ DX, R13
// r4 += 2×l1×l3
MOVQ 8(CX), AX
IMUL3Q $0x02, AX, AX
MULQ 24(CX)
ADDQ AX, R14
ADCQ DX, R13
// r4 += l2×l2
MOVQ 16(CX), AX
MULQ 16(CX)
ADDQ AX, R14
ADCQ DX, R13
// First reduction chain
MOVQ $0x0007ffffffffffff, AX
SHLQ $0x0d, SI, BX
SHLQ $0x0d, R8, DI
SHLQ $0x0d, R10, R9
SHLQ $0x0d, R12, R11
SHLQ $0x0d, R14, R13
ANDQ AX, SI
IMUL3Q $0x13, R13, R13
ADDQ R13, SI
ANDQ AX, R8
ADDQ BX, R8
ANDQ AX, R10
ADDQ DI, R10
ANDQ AX, R12
ADDQ R9, R12
ANDQ AX, R14
ADDQ R11, R14
// Second reduction chain (carryPropagate)
MOVQ SI, BX
SHRQ $0x33, BX
MOVQ R8, DI
SHRQ $0x33, DI
MOVQ R10, R9
SHRQ $0x33, R9
MOVQ R12, R11
SHRQ $0x33, R11
MOVQ R14, R13
SHRQ $0x33, R13
ANDQ AX, SI
IMUL3Q $0x13, R13, R13
ADDQ R13, SI
ANDQ AX, R8
ADDQ BX, R8
ANDQ AX, R10
ADDQ DI, R10
ANDQ AX, R12
ADDQ R9, R12
ANDQ AX, R14
ADDQ R11, R14
// Store output
MOVQ out+0(FP), AX
MOVQ SI, (AX)
MOVQ R8, 8(AX)
MOVQ R10, 16(AX)
MOVQ R12, 24(AX)
MOVQ R14, 32(AX)
RET

12
vendor/filippo.io/edwards25519/field/fe_amd64_noasm.go generated vendored Normal file
View File

@@ -0,0 +1,12 @@
// Copyright (c) 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !amd64 || !gc || purego
// +build !amd64 !gc purego
package field
func feMul(v, x, y *Element) { feMulGeneric(v, x, y) }
func feSquare(v, x *Element) { feSquareGeneric(v, x) }

16
vendor/filippo.io/edwards25519/field/fe_arm64.go generated vendored Normal file
View File

@@ -0,0 +1,16 @@
// Copyright (c) 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build arm64 && gc && !purego
// +build arm64,gc,!purego
package field
//go:noescape
func carryPropagate(v *Element)
func (v *Element) carryPropagate() *Element {
carryPropagate(v)
return v
}

42
vendor/filippo.io/edwards25519/field/fe_arm64.s generated vendored Normal file
View File

@@ -0,0 +1,42 @@
// Copyright (c) 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build arm64,gc,!purego
#include "textflag.h"
// carryPropagate works exactly like carryPropagateGeneric and uses the
// same AND, ADD, and LSR+MADD instructions emitted by the compiler, but
// avoids loading R0-R4 twice and uses LDP and STP.
//
// See https://golang.org/issues/43145 for the main compiler issue.
//
// func carryPropagate(v *Element)
TEXT ·carryPropagate(SB),NOFRAME|NOSPLIT,$0-8
MOVD v+0(FP), R20
LDP 0(R20), (R0, R1)
LDP 16(R20), (R2, R3)
MOVD 32(R20), R4
AND $0x7ffffffffffff, R0, R10
AND $0x7ffffffffffff, R1, R11
AND $0x7ffffffffffff, R2, R12
AND $0x7ffffffffffff, R3, R13
AND $0x7ffffffffffff, R4, R14
ADD R0>>51, R11, R11
ADD R1>>51, R12, R12
ADD R2>>51, R13, R13
ADD R3>>51, R14, R14
// R4>>51 * 19 + R10 -> R10
LSR $51, R4, R21
MOVD $19, R22
MADD R22, R10, R21, R10
STP (R10, R11), 0(R20)
STP (R12, R13), 16(R20)
MOVD R14, 32(R20)
RET

12
vendor/filippo.io/edwards25519/field/fe_arm64_noasm.go generated vendored Normal file
View File

@@ -0,0 +1,12 @@
// Copyright (c) 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !arm64 || !gc || purego
// +build !arm64 !gc purego
package field
func (v *Element) carryPropagate() *Element {
return v.carryPropagateGeneric()
}

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

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

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

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

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

File diff suppressed because it is too large Load Diff

214
vendor/filippo.io/edwards25519/scalarmult.go generated vendored Normal file
View File

@@ -0,0 +1,214 @@
// Copyright (c) 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package edwards25519
import "sync"
// basepointTable is a set of 32 affineLookupTables, where table i is generated
// from 256i * basepoint. It is precomputed the first time it's used.
func basepointTable() *[32]affineLookupTable {
basepointTablePrecomp.initOnce.Do(func() {
p := NewGeneratorPoint()
for i := 0; i < 32; i++ {
basepointTablePrecomp.table[i].FromP3(p)
for j := 0; j < 8; j++ {
p.Add(p, p)
}
}
})
return &basepointTablePrecomp.table
}
var basepointTablePrecomp struct {
table [32]affineLookupTable
initOnce sync.Once
}
// ScalarBaseMult sets v = x * B, where B is the canonical generator, and
// returns v.
//
// The scalar multiplication is done in constant time.
func (v *Point) ScalarBaseMult(x *Scalar) *Point {
basepointTable := basepointTable()
// Write x = sum(x_i * 16^i) so x*B = sum( B*x_i*16^i )
// as described in the Ed25519 paper
//
// Group even and odd coefficients
// x*B = x_0*16^0*B + x_2*16^2*B + ... + x_62*16^62*B
// + x_1*16^1*B + x_3*16^3*B + ... + x_63*16^63*B
// x*B = x_0*16^0*B + x_2*16^2*B + ... + x_62*16^62*B
// + 16*( x_1*16^0*B + x_3*16^2*B + ... + x_63*16^62*B)
//
// We use a lookup table for each i to get x_i*16^(2*i)*B
// and do four doublings to multiply by 16.
digits := x.signedRadix16()
multiple := &affineCached{}
tmp1 := &projP1xP1{}
tmp2 := &projP2{}
// Accumulate the odd components first
v.Set(NewIdentityPoint())
for i := 1; i < 64; i += 2 {
basepointTable[i/2].SelectInto(multiple, digits[i])
tmp1.AddAffine(v, multiple)
v.fromP1xP1(tmp1)
}
// Multiply by 16
tmp2.FromP3(v) // tmp2 = v in P2 coords
tmp1.Double(tmp2) // tmp1 = 2*v in P1xP1 coords
tmp2.FromP1xP1(tmp1) // tmp2 = 2*v in P2 coords
tmp1.Double(tmp2) // tmp1 = 4*v in P1xP1 coords
tmp2.FromP1xP1(tmp1) // tmp2 = 4*v in P2 coords
tmp1.Double(tmp2) // tmp1 = 8*v in P1xP1 coords
tmp2.FromP1xP1(tmp1) // tmp2 = 8*v in P2 coords
tmp1.Double(tmp2) // tmp1 = 16*v in P1xP1 coords
v.fromP1xP1(tmp1) // now v = 16*(odd components)
// Accumulate the even components
for i := 0; i < 64; i += 2 {
basepointTable[i/2].SelectInto(multiple, digits[i])
tmp1.AddAffine(v, multiple)
v.fromP1xP1(tmp1)
}
return v
}
// ScalarMult sets v = x * q, and returns v.
//
// The scalar multiplication is done in constant time.
func (v *Point) ScalarMult(x *Scalar, q *Point) *Point {
checkInitialized(q)
var table projLookupTable
table.FromP3(q)
// Write x = sum(x_i * 16^i)
// so x*Q = sum( Q*x_i*16^i )
// = Q*x_0 + 16*(Q*x_1 + 16*( ... + Q*x_63) ... )
// <------compute inside out---------
//
// We use the lookup table to get the x_i*Q values
// and do four doublings to compute 16*Q
digits := x.signedRadix16()
// Unwrap first loop iteration to save computing 16*identity
multiple := &projCached{}
tmp1 := &projP1xP1{}
tmp2 := &projP2{}
table.SelectInto(multiple, digits[63])
v.Set(NewIdentityPoint())
tmp1.Add(v, multiple) // tmp1 = x_63*Q in P1xP1 coords
for i := 62; i >= 0; i-- {
tmp2.FromP1xP1(tmp1) // tmp2 = (prev) in P2 coords
tmp1.Double(tmp2) // tmp1 = 2*(prev) in P1xP1 coords
tmp2.FromP1xP1(tmp1) // tmp2 = 2*(prev) in P2 coords
tmp1.Double(tmp2) // tmp1 = 4*(prev) in P1xP1 coords
tmp2.FromP1xP1(tmp1) // tmp2 = 4*(prev) in P2 coords
tmp1.Double(tmp2) // tmp1 = 8*(prev) in P1xP1 coords
tmp2.FromP1xP1(tmp1) // tmp2 = 8*(prev) in P2 coords
tmp1.Double(tmp2) // tmp1 = 16*(prev) in P1xP1 coords
v.fromP1xP1(tmp1) // v = 16*(prev) in P3 coords
table.SelectInto(multiple, digits[i])
tmp1.Add(v, multiple) // tmp1 = x_i*Q + 16*(prev) in P1xP1 coords
}
v.fromP1xP1(tmp1)
return v
}
// basepointNafTable is the nafLookupTable8 for the basepoint.
// It is precomputed the first time it's used.
func basepointNafTable() *nafLookupTable8 {
basepointNafTablePrecomp.initOnce.Do(func() {
basepointNafTablePrecomp.table.FromP3(NewGeneratorPoint())
})
return &basepointNafTablePrecomp.table
}
var basepointNafTablePrecomp struct {
table nafLookupTable8
initOnce sync.Once
}
// VarTimeDoubleScalarBaseMult sets v = a * A + b * B, where B is the canonical
// generator, and returns v.
//
// Execution time depends on the inputs.
func (v *Point) VarTimeDoubleScalarBaseMult(a *Scalar, A *Point, b *Scalar) *Point {
checkInitialized(A)
// Similarly to the single variable-base approach, we compute
// digits and use them with a lookup table. However, because
// we are allowed to do variable-time operations, we don't
// need constant-time lookups or constant-time digit
// computations.
//
// So we use a non-adjacent form of some width w instead of
// radix 16. This is like a binary representation (one digit
// for each binary place) but we allow the digits to grow in
// magnitude up to 2^{w-1} so that the nonzero digits are as
// sparse as possible. Intuitively, this "condenses" the
// "mass" of the scalar onto sparse coefficients (meaning
// fewer additions).
basepointNafTable := basepointNafTable()
var aTable nafLookupTable5
aTable.FromP3(A)
// Because the basepoint is fixed, we can use a wider NAF
// corresponding to a bigger table.
aNaf := a.nonAdjacentForm(5)
bNaf := b.nonAdjacentForm(8)
// Find the first nonzero coefficient.
i := 255
for j := i; j >= 0; j-- {
if aNaf[j] != 0 || bNaf[j] != 0 {
break
}
}
multA := &projCached{}
multB := &affineCached{}
tmp1 := &projP1xP1{}
tmp2 := &projP2{}
tmp2.Zero()
// Move from high to low bits, doubling the accumulator
// at each iteration and checking whether there is a nonzero
// coefficient to look up a multiple of.
for ; i >= 0; i-- {
tmp1.Double(tmp2)
// Only update v if we have a nonzero coeff to add in.
if aNaf[i] > 0 {
v.fromP1xP1(tmp1)
aTable.SelectInto(multA, aNaf[i])
tmp1.Add(v, multA)
} else if aNaf[i] < 0 {
v.fromP1xP1(tmp1)
aTable.SelectInto(multA, -aNaf[i])
tmp1.Sub(v, multA)
}
if bNaf[i] > 0 {
v.fromP1xP1(tmp1)
basepointNafTable.SelectInto(multB, bNaf[i])
tmp1.AddAffine(v, multB)
} else if bNaf[i] < 0 {
v.fromP1xP1(tmp1)
basepointNafTable.SelectInto(multB, -bNaf[i])
tmp1.SubAffine(v, multB)
}
tmp2.FromP1xP1(tmp1)
}
v.fromP2(tmp2)
return v
}

129
vendor/filippo.io/edwards25519/tables.go generated vendored Normal file
View File

@@ -0,0 +1,129 @@
// Copyright (c) 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package edwards25519
import (
"crypto/subtle"
)
// A dynamic lookup table for variable-base, constant-time scalar muls.
type projLookupTable struct {
points [8]projCached
}
// A precomputed lookup table for fixed-base, constant-time scalar muls.
type affineLookupTable struct {
points [8]affineCached
}
// A dynamic lookup table for variable-base, variable-time scalar muls.
type nafLookupTable5 struct {
points [8]projCached
}
// A precomputed lookup table for fixed-base, variable-time scalar muls.
type nafLookupTable8 struct {
points [64]affineCached
}
// Constructors.
// Builds a lookup table at runtime. Fast.
func (v *projLookupTable) FromP3(q *Point) {
// Goal: v.points[i] = (i+1)*Q, i.e., Q, 2Q, ..., 8Q
// This allows lookup of -8Q, ..., -Q, 0, Q, ..., 8Q
v.points[0].FromP3(q)
tmpP3 := Point{}
tmpP1xP1 := projP1xP1{}
for i := 0; i < 7; i++ {
// Compute (i+1)*Q as Q + i*Q and convert to a ProjCached
// This is needlessly complicated because the API has explicit
// recievers instead of creating stack objects and relying on RVO
v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.Add(q, &v.points[i])))
}
}
// This is not optimised for speed; fixed-base tables should be precomputed.
func (v *affineLookupTable) FromP3(q *Point) {
// Goal: v.points[i] = (i+1)*Q, i.e., Q, 2Q, ..., 8Q
// This allows lookup of -8Q, ..., -Q, 0, Q, ..., 8Q
v.points[0].FromP3(q)
tmpP3 := Point{}
tmpP1xP1 := projP1xP1{}
for i := 0; i < 7; i++ {
// Compute (i+1)*Q as Q + i*Q and convert to AffineCached
v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.AddAffine(q, &v.points[i])))
}
}
// Builds a lookup table at runtime. Fast.
func (v *nafLookupTable5) FromP3(q *Point) {
// Goal: v.points[i] = (2*i+1)*Q, i.e., Q, 3Q, 5Q, ..., 15Q
// This allows lookup of -15Q, ..., -3Q, -Q, 0, Q, 3Q, ..., 15Q
v.points[0].FromP3(q)
q2 := Point{}
q2.Add(q, q)
tmpP3 := Point{}
tmpP1xP1 := projP1xP1{}
for i := 0; i < 7; i++ {
v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.Add(&q2, &v.points[i])))
}
}
// This is not optimised for speed; fixed-base tables should be precomputed.
func (v *nafLookupTable8) FromP3(q *Point) {
v.points[0].FromP3(q)
q2 := Point{}
q2.Add(q, q)
tmpP3 := Point{}
tmpP1xP1 := projP1xP1{}
for i := 0; i < 63; i++ {
v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.AddAffine(&q2, &v.points[i])))
}
}
// Selectors.
// Set dest to x*Q, where -8 <= x <= 8, in constant time.
func (v *projLookupTable) SelectInto(dest *projCached, x int8) {
// Compute xabs = |x|
xmask := x >> 7
xabs := uint8((x + xmask) ^ xmask)
dest.Zero()
for j := 1; j <= 8; j++ {
// Set dest = j*Q if |x| = j
cond := subtle.ConstantTimeByteEq(xabs, uint8(j))
dest.Select(&v.points[j-1], dest, cond)
}
// Now dest = |x|*Q, conditionally negate to get x*Q
dest.CondNeg(int(xmask & 1))
}
// Set dest to x*Q, where -8 <= x <= 8, in constant time.
func (v *affineLookupTable) SelectInto(dest *affineCached, x int8) {
// Compute xabs = |x|
xmask := x >> 7
xabs := uint8((x + xmask) ^ xmask)
dest.Zero()
for j := 1; j <= 8; j++ {
// Set dest = j*Q if |x| = j
cond := subtle.ConstantTimeByteEq(xabs, uint8(j))
dest.Select(&v.points[j-1], dest, cond)
}
// Now dest = |x|*Q, conditionally negate to get x*Q
dest.CondNeg(int(xmask & 1))
}
// Given odd x with 0 < x < 2^4, return x*Q (in variable time).
func (v *nafLookupTable5) SelectInto(dest *projCached, x int8) {
*dest = v.points[x/2]
}
// Given odd x with 0 < x < 2^7, return x*Q (in variable time).
func (v *nafLookupTable8) SelectInto(dest *affineCached, x int8) {
*dest = v.points[x/2]
}

View File

@@ -70,7 +70,7 @@ func (myHandler) HandleContactMessage(message whatsapp.ContactMessage) {
fmt.Println(message) fmt.Println(message)
} }
func (myHandler) HandleBatteryMessage(msg whatsapp.BatteryMessage) { func (myHandler) HandleBatteryMessage(message whatsapp.BatteryMessage) {
fmt.Println(message) fmt.Println(message)
} }

View File

@@ -526,5 +526,7 @@ func (wac *Conn) Logout() error {
return fmt.Errorf("error writing logout: %v\n", err) return fmt.Errorf("error writing logout: %v\n", err)
} }
wac.loggedIn = false
return nil return nil
} }

View File

@@ -48,10 +48,20 @@ linters:
- nilerr - nilerr
- revive - revive
- wastedassign - wastedassign
- bidichk
- contextcheck
- ireturn
- nilnil
- tenv
- nestif
- grouper
- decorder
- containedctx
# - execinquery # FIXME: panic in 1.46.0
- nosprintfhostport
# - wrapcheck # TODO: v3 Fix # - wrapcheck # TODO: v3 Fix
# - testpackage # TODO: Fix testpackage # - testpackage # TODO: Fix testpackage
# - nestif # TODO: Fix nestif
# - noctx # TODO: Fix noctx # - noctx # TODO: Fix noctx
# don't enable: # don't enable:
@@ -75,6 +85,11 @@ linters:
# - cyclop # - cyclop
# - promlinter # - promlinter
# - tagliatelle # - tagliatelle
# - errname
# - varnamelen
# - errchkjson
# - maintidx
# - nonamedreturns
# depricated # depricated
# - maligned # - maligned

View File

@@ -1,2 +1,3 @@
--- ---
no-hard-tabs: false no-hard-tabs: false
no-duplicate-heading: false

View File

@@ -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)

View File

@@ -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) - [golangci-lint](https://github.com/golangci/golangci-lint)
- [global .gitignore](https://help.github.com/en/articles/ignoring-files#create-a-global-gitignore) - [global .gitignore](https://help.github.com/en/articles/ignoring-files#create-a-global-gitignore)
@@ -39,6 +39,7 @@ golangci-lint run
# CLIENT_SECRET="" # CLIENT_SECRET=""
# USER_TOKEN="" # USER_TOKEN=""
# WIDGET_TOKEN="" # WIDGET_TOKEN=""
# MARUSIA_TOKEN=""
# CLIENT_ID="123456" # CLIENT_ID="123456"
# GROUP_ID="123456" # GROUP_ID="123456"
# ACCOUNT_ID="123456" # ACCOUNT_ID="123456"
@@ -56,6 +57,7 @@ go test ./...
"go.testEnvVars": { "go.testEnvVars": {
"SERVICE_TOKEN": "", "SERVICE_TOKEN": "",
"WIDGET_TOKEN": "", "WIDGET_TOKEN": "",
"MARUSIA_TOKEN": "",
"GROUP_TOKEN": "", "GROUP_TOKEN": "",
"CLIENT_SECRET": "", "CLIENT_SECRET": "",
"USER_TOKEN": "", "USER_TOKEN": "",

View File

@@ -1,125 +1,127 @@
# VK SDK for Golang # VK SDK for Golang
[![Build Status](https://travis-ci.com/SevereCloud/vksdk.svg?branch=master)](https://travis-ci.com/SevereCloud/vksdk) [![PkgGoDev](https://pkg.go.dev/badge/github.com/SevereCloud/vksdk/v2/v2)](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2?tab=subdirectories)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/SevereCloud/vksdk/v2/v2)](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2?tab=subdirectories) [![VK Developers](https://img.shields.io/badge/developers-%234a76a8.svg?logo=VK&logoColor=white)](https://dev.vk.com/)
[![VK Developers](https://img.shields.io/badge/developers-%234a76a8.svg?logo=VK&logoColor=white)](https://vk.com/dev/) [![codecov](https://codecov.io/gh/SevereCloud/vksdk/branch/master/graph/badge.svg)](https://codecov.io/gh/SevereCloud/vksdk)
[![codecov](https://codecov.io/gh/SevereCloud/vksdk/branch/master/graph/badge.svg)](https://codecov.io/gh/SevereCloud/vksdk) [![VK chat](https://img.shields.io/badge/VK%20chat-%234a76a8.svg?logo=VK&logoColor=white)](https://vk.me/join/AJQ1d6Or8Q00Y_CSOESfbqGt)
[![VK chat](https://img.shields.io/badge/VK%20chat-%234a76a8.svg?logo=VK&logoColor=white)](https://vk.me/join/AJQ1d6Or8Q00Y_CSOESfbqGt) [![release](https://img.shields.io/github/v/tag/SevereCloud/vksdk?label=release)](https://github.com/SevereCloud/vksdk/releases)
[![release](https://img.shields.io/github/v/tag/SevereCloud/vksdk?label=release)](https://github.com/SevereCloud/vksdk/releases) [![license](https://img.shields.io/github/license/SevereCloud/vksdk.svg?maxAge=2592000)](https://github.com/SevereCloud/vksdk/blob/master/LICENSE)
[![license](https://img.shields.io/github/license/SevereCloud/vksdk.svg?maxAge=2592000)](https://github.com/SevereCloud/vksdk/blob/master/LICENSE)
**VK SDK for Golang** ready implementation of the main VK API functions for Go.
**VK SDK for Golang** ready implementation of the main VK API functions for Go.
[Russian documentation](https://github.com/SevereCloud/vksdk/wiki)
[Russian documentation](https://github.com/SevereCloud/vksdk/wiki)
## Features
## Features
Version API 5.131.
Version API 5.131.
- [API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/api)
- [API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/api) - 500+ methods
- 400+ methods - Ability to modify HTTP client
- Ability to change the request handler - Request Limiter
- Ability to modify HTTP client - Support [zstd](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/api#VK.EnableZstd)
- Request Limiter and [MessagePack](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/api#VK.EnableMessagePack)
- Token pool - Token pool
- [Callback API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/callback) - [OAuth](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/api/oauth)
- Tracking tool for users activity in your VK communities - [Callback API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/callback)
- Supports all events - Tracking tool for users activity in your VK communities
- Auto setting callback - Supports all events
- [Bots Long Poll API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/longpoll-bot) - Auto setting callback
- Allows you to work with community events in real time - [Bots Long Poll API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/longpoll-bot)
- Supports all events - Allows you to work with community events in real time
- Ability to modify HTTP client - Supports all events
- [User Long Poll API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/longpoll-user) - Ability to modify HTTP client
- Allows you to work with user events in real time - [User Long Poll API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/longpoll-user)
- Ability to modify HTTP client - Allows you to work with user events in real time
- [Streaming API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/streaming) - Ability to modify HTTP client
- Receiving public data from VK by specified keywords - [Streaming API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/streaming)
- Ability to modify HTTP client - Receiving public data from VK by specified keywords
- [FOAF](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/foaf) - Ability to modify HTTP client
- Machine-readable ontology describing persons - [FOAF](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/foaf)
- Works with users and groups - Machine-readable ontology describing persons
- The only place to get page creation date - Works with users and groups
- [Games](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/games) - The only place to get page creation date
- Checking launch parameters - [Games](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/games)
- Intermediate http handler - Checking launch parameters
- [VK Mini Apps](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/vkapps) - Intermediate http handler
- Checking launch parameters - [VK Mini Apps](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/vkapps)
- Intermediate http handler - Checking launch parameters
- [Payments API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/payments) - Intermediate http handler
- Processes payment notifications - [Payments API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/payments)
- [Marusia Skills](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/marusia) - Processes payment notifications
- For creating Marusia Skills - [Marusia Skills](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/marusia)
- Support SSML - For creating Marusia Skills
- Support SSML
## Install
## Install
```bash
# go mod init mymodulename ```bash
go get github.com/SevereCloud/vksdk/v2@latest # go mod init mymodulename
``` go get github.com/SevereCloud/vksdk/v2@latest
```
## Use by
## 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> - A simple chat bridge: <https://github.com/42wim/matterbridge>
- [Joe](https://github.com/go-joe/joe) adapter: <https://github.com/tdakkota/joe-vk-adapter>
### Example - [Logrus](https://github.com/sirupsen/logrus) hook: <https://github.com/SevereCloud/vkrus>
```go ### Example
package main
```go
import ( package main
"context"
"log" import (
"context"
"github.com/SevereCloud/vksdk/v2/api" "log"
"github.com/SevereCloud/vksdk/v2/api/params"
"github.com/SevereCloud/vksdk/v2/events" "github.com/SevereCloud/vksdk/v2/api"
"github.com/SevereCloud/vksdk/v2/longpoll-bot" "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) func main() {
token := "<TOKEN>" // use os.Getenv("TOKEN")
// get information about the group vk := api.NewVK(token)
group, err := vk.GroupsGetByID(nil)
if err != nil { // get information about the group
log.Fatal(err) 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 { // Initializing Long Poll
log.Fatal(err) 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) // New message event
lp.MessageNew(func(_ context.Context, obj events.MessageNewObject) {
if obj.Message.Text == "ping" { log.Printf("%d: %s", obj.Message.PeerID, obj.Message.Text)
b := params.NewMessagesSendBuilder()
b.Message("pong") if obj.Message.Text == "ping" {
b.RandomID(0) b := params.NewMessagesSendBuilder()
b.PeerID(obj.Message.PeerID) b.Message("pong")
b.RandomID(0)
_, err := vk.MessagesSend(b.Params) b.PeerID(obj.Message.PeerID)
if err != nil {
log.Fatal(err) _, 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 { // Run Bots Long Poll
log.Fatal(err) log.Println("Start Long Poll")
} if err := lp.Run(); err != nil {
} log.Fatal(err)
``` }
}
## LICENSE ```
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FSevereCloud%2Fvksdk.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FSevereCloud%2Fvksdk?ref=badge_large) ## LICENSE
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FSevereCloud%2Fvksdk.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FSevereCloud%2Fvksdk?ref=badge_large)

View File

@@ -3,7 +3,7 @@
[![PkgGoDev](https://pkg.go.dev/badge/github.com/SevereCloud/vksdk/v2/api)](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/api) [![PkgGoDev](https://pkg.go.dev/badge/github.com/SevereCloud/vksdk/v2/api)](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/api)
[![VK](https://img.shields.io/badge/developers-%234a76a8.svg?logo=VK&logoColor=white)](https://vk.com/dev/first_guide) [![VK](https://img.shields.io/badge/developers-%234a76a8.svg?logo=VK&logoColor=white)](https://vk.com/dev/first_guide)
Данная библиотека поддерживает версию API **5.122**. Данная библиотека поддерживает версию API **5.131**.
## Запросы ## Запросы
@@ -80,6 +80,54 @@ if errors.As(err, &e) {
Для Execute существует отдельная ошибка `ExecuteErrors` Для 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) Пример запроса [users.get](https://vk.com/dev/users.get)

View File

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

View File

@@ -1,9 +1,11 @@
package api // import "github.com/SevereCloud/vksdk/v2/api" package api // import "github.com/SevereCloud/vksdk/v2/api"
import ( import (
"bytes"
"encoding/json" "encoding/json"
"github.com/SevereCloud/vksdk/v2/object" "github.com/SevereCloud/vksdk/v2/object"
"github.com/vmihailenco/msgpack/v5"
) )
// AdsAddOfficeUsersItem struct. // AdsAddOfficeUsersItem struct.
@@ -21,6 +23,23 @@ func (r *AdsAddOfficeUsersItem) UnmarshalJSON(data []byte) (err error) {
return 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. // AdsAddOfficeUsersResponse struct.
type AdsAddOfficeUsersResponse []AdsAddOfficeUsersItem type AdsAddOfficeUsersResponse []AdsAddOfficeUsersItem
@@ -349,7 +368,7 @@ func (vk *VK) AdsGetAdsLayout(params Params) (response AdsGetAdsLayoutResponse,
// AdsGetMusiciansResponse struct. // AdsGetMusiciansResponse struct.
type AdsGetMusiciansResponse struct { type AdsGetMusiciansResponse struct {
Items []object.BaseObjectWithName Items []object.AdsMusician
} }
// AdsGetMusicians returns a list of musicians. // AdsGetMusicians returns a list of musicians.

View File

@@ -7,9 +7,11 @@ package api // import "github.com/SevereCloud/vksdk/v2/api"
import ( import (
"bytes" "bytes"
"compress/gzip"
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"mime" "mime"
"net/http" "net/http"
"net/url" "net/url"
@@ -21,6 +23,8 @@ import (
"github.com/SevereCloud/vksdk/v2" "github.com/SevereCloud/vksdk/v2"
"github.com/SevereCloud/vksdk/v2/internal" "github.com/SevereCloud/vksdk/v2/internal"
"github.com/SevereCloud/vksdk/v2/object" "github.com/SevereCloud/vksdk/v2/object"
"github.com/klauspost/compress/zstd"
"github.com/vmihailenco/msgpack/v5"
) )
// Api constants. // Api constants.
@@ -91,6 +95,9 @@ type VK struct {
UserAgent string UserAgent string
Handler func(method string, params ...Params) (Response, error) Handler func(method string, params ...Params) (Response, error)
msgpack bool
zstd bool
mux sync.Mutex mux sync.Mutex
lastTime time.Time lastTime time.Time
rps int rps int
@@ -98,9 +105,9 @@ type VK struct {
// Response struct. // Response struct.
type Response struct { type Response struct {
Response json.RawMessage `json:"response"` Response object.RawMessage `json:"response"`
Error Error `json:"error"` Error Error `json:"error"`
ExecuteErrors ExecuteErrors `json:"execute_errors"` ExecuteErrors ExecuteErrors `json:"execute_errors"`
} }
// NewVK returns a new VK. // NewVK returns a new VK.
@@ -121,7 +128,7 @@ func NewVK(tokens ...string) *VK {
vk.accessTokens = tokens vk.accessTokens = tokens
vk.Version = Version vk.Version = Version
vk.Handler = vk.defaultHandler vk.Handler = vk.DefaultHandler
vk.MethodURL = MethodURL vk.MethodURL = MethodURL
vk.Client = http.DefaultClient vk.Client = http.DefaultClient
@@ -196,10 +203,13 @@ func buildQuery(sliceParams ...Params) (context.Context, url.Values) {
for _, params := range sliceParams { for _, params := range sliceParams {
for key, value := range params { for key, value := range params {
if key != ":context" { switch key {
query.Set(key, FmtValue(value, 0)) case "access_token":
} else { continue
case ":context":
ctx = value.(context.Context) ctx = value.(context.Context)
default:
query.Set(key, FmtValue(value, 0))
} }
} }
} }
@@ -207,8 +217,8 @@ func buildQuery(sliceParams ...Params) (context.Context, url.Values) {
return ctx, query return ctx, query
} }
// defaultHandler provides access to VK API methods. // DefaultHandler provides access to VK API methods.
func (vk *VK) defaultHandler(method string, sliceParams ...Params) (Response, error) { func (vk *VK) DefaultHandler(method string, sliceParams ...Params) (Response, error) {
u := vk.MethodURL + method u := vk.MethodURL + method
ctx, query := buildQuery(sliceParams...) ctx, query := buildQuery(sliceParams...)
attempt := 0 attempt := 0
@@ -243,24 +253,61 @@ func (vk *VK) defaultHandler(method string, sliceParams ...Params) (Response, er
return response, err return response, err
} }
acceptEncoding := "gzip"
if vk.zstd {
acceptEncoding = "zstd"
}
token := sliceParams[len(sliceParams)-1]["access_token"].(string)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("User-Agent", vk.UserAgent) req.Header.Set("User-Agent", vk.UserAgent)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 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) resp, err := vk.Client.Do(req)
if err != nil { if err != nil {
return response, err return response, err
} }
mediatype, _, _ := mime.ParseMediaType(resp.Header.Get("Content-Type")) switch resp.Header.Get("Content-Encoding") {
if mediatype != "application/json" { case "zstd":
_ = resp.Body.Close() zstdReader, _ := zstd.NewReader(resp.Body)
return response, &InvalidContentType{mediatype} 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) mediatype, _, _ := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil { 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() _ = resp.Body.Close()
return response, err return response, &InvalidContentType{mediatype}
} }
_ = resp.Body.Close() _ = resp.Body.Close()
@@ -291,6 +338,10 @@ func (vk *VK) Request(method string, sliceParams ...Params) ([]byte, error) {
sliceParams = append(sliceParams, reqParams) sliceParams = append(sliceParams, reqParams)
if vk.msgpack {
method += ".msgpack"
}
resp, err := vk.Handler(method, sliceParams...) resp, err := vk.Handler(method, sliceParams...)
return resp.Response, err return resp.Response, err
@@ -303,7 +354,32 @@ func (vk *VK) RequestUnmarshal(method string, obj interface{}, sliceParams ...Pa
return err 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 { func fmtReflectValue(value reflect.Value, depth int) string {

View File

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

View File

@@ -1,5 +1,9 @@
package api // import "github.com/SevereCloud/vksdk/v2/api" 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. // AuthCheckPhone checks a user's phone number for correctness.
// //
// https://vk.com/dev/auth.checkPhone // 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) err = vk.RequestUnmarshal("auth.restore", &response, params)
return 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
}

View File

@@ -159,6 +159,9 @@ const (
ErrRateLimit ErrorType = 29 ErrRateLimit ErrorType = 29
ErrPrivateProfile ErrorType = 30 // This profile is private ErrPrivateProfile ErrorType = 30 // This profile is private
// Client version deprecated.
ErrClientVersionDeprecated ErrorType = 34
// Method execution was interrupted due to timeout. // Method execution was interrupted due to timeout.
ErrExecutionTimeout ErrorType = 36 ErrExecutionTimeout ErrorType = 36
@@ -177,6 +180,9 @@ const (
// Additional signup required. // Additional signup required.
ErrAdditionalSignupRequired ErrorType = 41 ErrAdditionalSignupRequired ErrorType = 41
// IP is not allowed.
ErrIPNotAllowed ErrorType = 42
// One of the parameters specified was missing or invalid // One of the parameters specified was missing or invalid
// //
// Check the required parameters list and their format on a method // Check the required parameters list and their format on a method
@@ -586,6 +592,18 @@ const (
// Can't send message, reply timed out. // Can't send message, reply timed out.
ErrMessagesReplyTimedOut ErrorType = 950 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. // Invalid phone number.
ErrParamPhone ErrorType = 1000 ErrParamPhone ErrorType = 1000
@@ -598,6 +616,18 @@ const (
// Processing.. Try later. // Processing.. Try later.
ErrAuthDelay ErrorType = 1112 ErrAuthDelay ErrorType = 1112
// Anonymous token has expired.
ErrAnonymousTokenExpired ErrorType = 1114
// Anonymous token is invalid.
ErrAnonymousTokenInvalid ErrorType = 1116
// Access token has expired.
ErrAuthAccessTokenHasExpired ErrorType = 1117
// Anonymous token ip mismatch.
ErrAuthAnonymousTokenIPMismatch ErrorType = 1118
// Invalid document id. // Invalid document id.
ErrParamDocID ErrorType = 1150 ErrParamDocID ErrorType = 1150
@@ -724,6 +754,9 @@ const (
// Market was already disabled in this group. // Market was already disabled in this group.
ErrMarketAlreadyDisabled ErrorType = 1432 ErrMarketAlreadyDisabled ErrorType = 1432
// Main album can not be hidden.
ErrMainAlbumCantHidden ErrorType = 1446
// Story has already expired. // Story has already expired.
ErrStoryExpired ErrorType = 1600 ErrStoryExpired ErrorType = 1600
@@ -783,6 +816,33 @@ const (
// Can't set AliExpress tag to this type of object. // Can't set AliExpress tag to this type of object.
ErrAliExpressTag ErrorType = 3800 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. // ErrorSubtype is the subtype of an error.
@@ -946,3 +1006,31 @@ func (e AdsError) Is(target error) bool {
return false 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
}

View File

@@ -1,6 +1,11 @@
package api 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 // ExecuteWithArgs a universal method for calling a sequence of other methods
// while saving and filtering interim results. // 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) resp, err := vk.Handler("execute", params, reqParams)
if err != nil {
return err
}
jsonErr := json.Unmarshal(resp.Response, &obj) var decoderErr error
if jsonErr != nil {
return jsonErr 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 { if resp.ExecuteErrors != nil {

View File

@@ -20,6 +20,7 @@ func (vk *VK) MarketAdd(params Params) (response MarketAddResponse, err error) {
// MarketAddAlbumResponse struct. // MarketAddAlbumResponse struct.
type MarketAddAlbumResponse struct { type MarketAddAlbumResponse struct {
MarketAlbumID int `json:"market_album_id"` // Album ID MarketAlbumID int `json:"market_album_id"` // Album ID
AlbumsCount int `json:"albums_count"`
} }
// MarketAddAlbum creates new collection of items. // 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) err = vk.RequestUnmarshal("market.search", &response, params)
return 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
View 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
}

View File

@@ -1,7 +1,10 @@
package api // import "github.com/SevereCloud/vksdk/v2/api" package api // import "github.com/SevereCloud/vksdk/v2/api"
import ( import (
"strconv"
"github.com/SevereCloud/vksdk/v2/object" "github.com/SevereCloud/vksdk/v2/object"
"github.com/vmihailenco/msgpack/v5"
) )
// MessagesAddChatUser adds a new user to a chat. // MessagesAddChatUser adds a new user to a chat.
@@ -31,11 +34,34 @@ func (vk *VK) MessagesCreateChat(params Params) (response int, err error) {
// MessagesDeleteResponse struct. // MessagesDeleteResponse struct.
type MessagesDeleteResponse map[string]int 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. // MessagesDelete deletes one or more messages.
// //
// https://vk.com/dev/messages.delete // https://vk.com/dev/messages.delete
func (vk *VK) MessagesDelete(params Params) (response MessagesDeleteResponse, err error) { func (vk *VK) MessagesDelete(params Params) (response MessagesDeleteResponse, err error) {
err = vk.RequestUnmarshal("messages.delete", &response, params) err = vk.RequestUnmarshal("messages.delete", &response, params)
return return
} }
@@ -90,6 +116,14 @@ func (vk *VK) MessagesEditChat(params Params) (response int, err error) {
return return
} }
// MessagesForceCallFinish method.
//
// https://vk.com/dev/messages.forceCallFinish
func (vk *VK) MessagesForceCallFinish(params Params) (response int, err error) {
err = vk.RequestUnmarshal("messages.forceCallFinish", &response, params)
return
}
// MessagesGetByConversationMessageIDResponse struct. // MessagesGetByConversationMessageIDResponse struct.
type MessagesGetByConversationMessageIDResponse struct { type MessagesGetByConversationMessageIDResponse struct {
Count int `json:"count"` Count int `json:"count"`
@@ -607,6 +641,20 @@ func (vk *VK) MessagesSetChatPhoto(params Params) (response MessagesSetChatPhoto
return return
} }
// MessagesStartCallResponse struct.
type MessagesStartCallResponse struct {
JoinLink string `json:"join_link"`
CallID string `json:"call_id"`
}
// MessagesStartCall method.
//
// https://vk.com/dev/messages.startCall
func (vk *VK) MessagesStartCall(params Params) (response MessagesStartCallResponse, err error) {
err = vk.RequestUnmarshal("messages.startCall", &response, params)
return
}
// MessagesUnpin messages.unpin. // MessagesUnpin messages.unpin.
// //
// https://vk.com/dev/messages.unpin // https://vk.com/dev/messages.unpin

View File

@@ -571,12 +571,13 @@ func (vk *VK) PhotosSaveOwnerCoverPhoto(params Params) (response PhotosSaveOwner
// PhotosSaveOwnerPhotoResponse struct. // PhotosSaveOwnerPhotoResponse struct.
type PhotosSaveOwnerPhotoResponse struct { type PhotosSaveOwnerPhotoResponse struct {
PhotoHash string `json:"photo_hash"` PhotoHash string `json:"photo_hash"`
PhotoSrc string `json:"photo_src"` // BUG(VK): returns false
PhotoSrcBig string `json:"photo_src_big"` // PhotoSrc string `json:"photo_src"`
PhotoSrcSmall string `json:"photo_src_small"` // PhotoSrcBig string `json:"photo_src_big"`
Saved int `json:"saved"` // PhotoSrcSmall string `json:"photo_src_small"`
PostID int `json:"post_id"` Saved int `json:"saved"`
PostID int `json:"post_id"`
} }
// PhotosSaveOwnerPhoto saves a profile or community photo. // PhotosSaveOwnerPhoto saves a profile or community photo.

View File

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

View File

@@ -959,3 +959,57 @@ func (vk *VK) UploadGroupImage(imageType string, file io.Reader) (response objec
return 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
}

View File

@@ -1,9 +1,8 @@
package api // import "github.com/SevereCloud/vksdk/v2/api" package api // import "github.com/SevereCloud/vksdk/v2/api"
import ( import (
"encoding/json"
"github.com/SevereCloud/vksdk/v2/object" "github.com/SevereCloud/vksdk/v2/object"
"github.com/vmihailenco/msgpack/v5"
) )
// UtilsCheckLinkResponse struct. // UtilsCheckLinkResponse struct.
@@ -89,17 +88,34 @@ func (vk *VK) UtilsGetShortLink(params Params) (response UtilsGetShortLinkRespon
// UtilsResolveScreenNameResponse struct. // UtilsResolveScreenNameResponse struct.
type UtilsResolveScreenNameResponse object.UtilsDomainResolved 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. // UtilsResolveScreenName detects a type of object (e.g., user, community, application) and its ID by screen name.
// //
// https://vk.com/dev/utils.resolveScreenName // https://vk.com/dev/utils.resolveScreenName
func (vk *VK) UtilsResolveScreenName(params Params) (response UtilsResolveScreenNameResponse, err error) { func (vk *VK) UtilsResolveScreenName(params Params) (response UtilsResolveScreenNameResponse, err error) {
rawResponse, err := vk.Request("utils.resolveScreenName", params) err = vk.RequestUnmarshal("utils.resolveScreenName", &response, params)
// Если короткое имя screen_name не занято, то будет возвращён пустой объект.
if err != nil || string(rawResponse) == "[]" {
return
}
err = json.Unmarshal(rawResponse, &response)
return return
} }

View File

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

View File

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

View File

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

View File

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

View File

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

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