mirror of
https://github.com/cwinfo/matterbridge.git
synced 2025-06-27 02:59:24 +00:00
Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
612acfddff | |||
932b80d4f7 |
28
.github/ISSUE_TEMPLATE.md
vendored
28
.github/ISSUE_TEMPLATE.md
vendored
@ -1,36 +1,22 @@
|
|||||||
<!-- This is a bug report template. By following the instructions below and
|
If you have a configuration problem, please first try to create a basic configuration following the instructions on [the wiki](https://github.com/42wim/matterbridge/wiki/How-to-create-your-config) before filing an issue.
|
||||||
filling out the sections with your information, you will help the us to get all
|
|
||||||
the necessary data to fix your issue.
|
|
||||||
|
|
||||||
You can also preview your report before submitting it.
|
Please answer the following questions.
|
||||||
|
|
||||||
Text between <!-- and --> marks will be invisible in the report.
|
### Which version of matterbridge are you using?
|
||||||
-->
|
run ```matterbridge -version```
|
||||||
|
|
||||||
<!-- If you have a configuration problem, please first try to create a basic configuration following the instructions on [the wiki](https://github.com/42wim/matterbridge/wiki/How-to-create-your-config) before filing an issue. -->
|
### If you're having problems with mattermost please specify mattermost version.
|
||||||
|
|
||||||
|
|
||||||
### Environment
|
|
||||||
<!-- run `matterbridge -version` -->
|
|
||||||
<!-- If you're having problems with mattermost also specify the mattermost version. -->
|
|
||||||
Version:
|
|
||||||
|
|
||||||
<!-- What operating system are you using ? (be as specific as possible) -->
|
|
||||||
Operating system:
|
|
||||||
|
|
||||||
<!-- If you compiled matterbridge yourself:
|
|
||||||
* Specify the output of `go version`
|
|
||||||
* Specify the output of `git rev-parse HEAD` -->
|
|
||||||
|
|
||||||
### Please describe the expected behavior.
|
### Please describe the expected behavior.
|
||||||
|
|
||||||
|
|
||||||
### Please describe the actual behavior.
|
### Please describe the actual behavior.
|
||||||
<!-- Use logs from running `matterbridge -debug` if possible. -->
|
#### Use logs from running ```matterbridge -debug``` if possible.
|
||||||
|
|
||||||
|
|
||||||
### Any steps to reproduce the behavior?
|
### Any steps to reproduce the behavior?
|
||||||
|
|
||||||
|
|
||||||
### Please add your configuration file
|
### Please add your configuration file
|
||||||
<!-- (be sure to exclude or anonymize private data (tokens/passwords)) -->
|
#### (be sure to exclude or anonymize private data (tokens/passwords))
|
||||||
|
26
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
26
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
@ -1,26 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve. (Check the FAQ on the wiki first)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Screenshots/debug logs**
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
Use logs from running `matterbridge -debug` if possible.
|
|
||||||
|
|
||||||
**Environment (please complete the following information):**
|
|
||||||
- OS: [e.g. linux]
|
|
||||||
- Matterbridge version: output of `matterbridge -version`
|
|
||||||
- If self compiled: output of `git rev-parse HEAD`
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Please add your configuration file (be sure to exclude or anonymize private data (tokens/passwords))
|
|
17
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
17
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
@ -1,17 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context or screenshots about the feature request here.
|
|
@ -1,7 +1,7 @@
|
|||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
#- 1.7.x
|
#- 1.7.x
|
||||||
- 1.10.x
|
- 1.9.x
|
||||||
# - tip
|
# - tip
|
||||||
|
|
||||||
# we have everything vendored
|
# we have everything vendored
|
||||||
@ -34,17 +34,15 @@ before_script:
|
|||||||
# flunk the build and immediately stop. It's sorta like having
|
# flunk the build and immediately stop. It's sorta like having
|
||||||
# set -e enabled in bash.
|
# set -e enabled in bash.
|
||||||
script:
|
script:
|
||||||
#- test -z $(gofmt -s -l $GO_FILES) # Fail if a .go file hasn't been formatted with gofmt
|
- test -z $(gofmt -s -l $GO_FILES) # Fail if a .go file hasn't been formatted with gofmt
|
||||||
- go test -v -race $PKGS # Run all the tests with the race detector enabled
|
- go test -v -race $PKGS # Run all the tests with the race detector enabled
|
||||||
# - go vet $PKGS # go vet is the official Go static analyzer
|
- go vet $PKGS # go vet is the official Go static analyzer
|
||||||
- megacheck $PKGS # "go vet on steroids" + linter
|
- megacheck $PKGS # "go vet on steroids" + linter
|
||||||
- /bin/bash ci/bintray.sh
|
- /bin/bash ci/bintray.sh
|
||||||
#- golint -set_exit_status $PKGS # one last linter
|
#- golint -set_exit_status $PKGS # one last linter
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
provider: bintray
|
provider: bintray
|
||||||
edge:
|
|
||||||
branch: v1.8.47
|
|
||||||
file: ci/deploy.json
|
file: ci/deploy.json
|
||||||
user: 42wim
|
user: 42wim
|
||||||
key:
|
key:
|
||||||
|
45
README.md
45
README.md
@ -1,21 +1,17 @@
|
|||||||
# matterbridge
|
# matterbridge
|
||||||
Click on one of the badges below to join the chat
|
Click on one of the badges below to join the chat
|
||||||
|
|
||||||
[](https://gitter.im/42wim/matterbridge) [](https://webchat.freenode.net/?channels=matterbridgechat) [](https://discord.gg/AkKPtrQ) [](https://riot.im/app/#/room/#matterbridge:matrix.org) [](https://join.slack.com/matterbridgechat/shared_invite/MjEwODMxNjU1NDMwLTE0OTk2MTU3NTMtMzZkZmRiNDZhOA) [](https://framateam.org/signup_user_complete/?id=tfqm33ggop8x3qgu4boeieta6e) [](https://inverse.chat) [](https://www.twitch.tv/matterbridge) [](https://matterbridge.zulipchat.com/register/)
|
[](https://gitter.im/42wim/matterbridge) [](https://webchat.freenode.net/?channels=matterbridgechat) [](https://discord.gg/AkKPtrQ) [](https://riot.im/app/#/room/#matterbridge:matrix.org) [](https://join.slack.com/matterbridgechat/shared_invite/MjEwODMxNjU1NDMwLTE0OTk2MTU3NTMtMzZkZmRiNDZhOA) [](https://framateam.org/signup_user_complete/?id=tfqm33ggop8x3qgu4boeieta6e) 
|
||||||
|
|
||||||
[](https://github.com/42wim/matterbridge/releases/latest) [](https://bintray.com/42wim/nightly/Matterbridge/_latestVersion)
|
[](https://github.com/42wim/matterbridge/releases/latest) [](https://bintray.com/42wim/nightly/Matterbridge/_latestVersion)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Simple bridge between IRC, XMPP, Gitter, Mattermost, Slack, Discord, Telegram, Rocket.Chat, Hipchat(via xmpp), Matrix, Steam, ssh-chat and Zulip
|
Simple bridge between Mattermost, IRC, XMPP, Gitter, Slack, Discord, Telegram, Rocket.Chat, Hipchat(via xmpp), Matrix and Steam.
|
||||||
Has a REST API.
|
Has a REST API.
|
||||||
Minecraft server chat support via [MatterLink](https://github.com/elytra/MatterLink)
|
|
||||||
|
|
||||||
**Mattermost isn't required to run matterbridge. It bridges between any supported protocol.**
|
|
||||||
(The name matterbridge is a remnant when it was only bridging mattermost)
|
|
||||||
|
|
||||||
# Table of Contents
|
# Table of Contents
|
||||||
* [Features](https://github.com/42wim/matterbridge/wiki/Features)
|
* [Features](#features)
|
||||||
* [Requirements](#requirements)
|
* [Requirements](#requirements)
|
||||||
* [Screenshots](https://github.com/42wim/matterbridge/wiki/)
|
* [Screenshots](https://github.com/42wim/matterbridge/wiki/)
|
||||||
* [Installing](#installing)
|
* [Installing](#installing)
|
||||||
@ -31,21 +27,13 @@ Minecraft server chat support via [MatterLink](https://github.com/elytra/MatterL
|
|||||||
* [Thanks](#thanks)
|
* [Thanks](#thanks)
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
* [Support bridging between any protocols](https://github.com/42wim/matterbridge/wiki/Features#support-bridging-between-any-protocols)
|
* Relays public channel messages between multiple mattermost, IRC, XMPP, Gitter, Slack, Discord, Telegram, Rocket.Chat, Hipchat (via xmpp), Matrix and Steam.
|
||||||
* [Support multiple gateways(bridges) for your protocols](https://github.com/42wim/matterbridge/wiki/Features#support-multiple-gatewaysbridges-for-your-protocols)
|
Pick and mix.
|
||||||
* [Message edits and deletes](https://github.com/42wim/matterbridge/wiki/Features#message-edits-and-deletes)
|
* Support private groups on your mattermost/slack.
|
||||||
* [Attachment / files handling](https://github.com/42wim/matterbridge/wiki/Features#attachment--files-handling)
|
* Allow for bridging the same bridges, which means you can eg bridge between multiple mattermosts.
|
||||||
* [Username and avatar spoofing](https://github.com/42wim/matterbridge/wiki/Features#username-and-avatar-spoofing)
|
* The bridge is now a gateway which has support multiple in and out bridges. (and supports multiple gateways).
|
||||||
* [Private groups](https://github.com/42wim/matterbridge/wiki/Features#private-groups)
|
* Edits and delete messages across bridges that support it (mattermost,slack,discord,gitter,telegram)
|
||||||
* [API](https://github.com/42wim/matterbridge/wiki/Features#api)
|
* REST API to read/post messages to bridges (WIP).
|
||||||
|
|
||||||
## API
|
|
||||||
The API is very basic at the moment and rather undocumented.
|
|
||||||
|
|
||||||
Used by at least 2 projects. Feel free to make a PR to add your project to this list.
|
|
||||||
|
|
||||||
* [MatterLink](https://github.com/elytra/MatterLink) (Matterbridge link for Minecraft Server chat)
|
|
||||||
* [pyCord](https://github.com/NikkyAI/pyCord) (crossplatform chatbot)
|
|
||||||
|
|
||||||
# Requirements
|
# Requirements
|
||||||
Accounts to one of the supported bridges
|
Accounts to one of the supported bridges
|
||||||
@ -60,16 +48,13 @@ Accounts to one of the supported bridges
|
|||||||
* [Rocket.chat](https://rocket.chat)
|
* [Rocket.chat](https://rocket.chat)
|
||||||
* [Matrix](https://matrix.org)
|
* [Matrix](https://matrix.org)
|
||||||
* [Steam](https://store.steampowered.com/)
|
* [Steam](https://store.steampowered.com/)
|
||||||
* [Twitch](https://twitch.tv)
|
|
||||||
* [Ssh-chat](https://github.com/shazow/ssh-chat)
|
|
||||||
* [Zulip](https://zulipchat.com)
|
|
||||||
|
|
||||||
# Screenshots
|
# Screenshots
|
||||||
See https://github.com/42wim/matterbridge/wiki
|
See https://github.com/42wim/matterbridge/wiki
|
||||||
|
|
||||||
# Installing
|
# Installing
|
||||||
## Binaries
|
## Binaries
|
||||||
* Latest stable release [v1.10.1](https://github.com/42wim/matterbridge/releases/latest)
|
* Latest stable release [v1.6.1](https://github.com/42wim/matterbridge/releases/latest)
|
||||||
* Development releases (follows master) can be downloaded [here](https://dl.bintray.com/42wim/nightly/)
|
* Development releases (follows master) can be downloaded [here](https://dl.bintray.com/42wim/nightly/)
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
@ -185,14 +170,11 @@ Want to tip ?
|
|||||||
* btc: 1N7cKHj5SfqBHBzDJ6kad4BzeqUBBS2zhs
|
* btc: 1N7cKHj5SfqBHBzDJ6kad4BzeqUBBS2zhs
|
||||||
|
|
||||||
# Thanks
|
# Thanks
|
||||||
[](https://www.digitalocean.com/) for sponsoring demo/testing droplets.
|
|
||||||
|
|
||||||
Matterbridge wouldn't exist without these libraries:
|
Matterbridge wouldn't exist without these libraries:
|
||||||
* discord - https://github.com/bwmarrin/discordgo
|
* discord - https://github.com/bwmarrin/discordgo
|
||||||
* echo - https://github.com/labstack/echo
|
* echo - https://github.com/labstack/echo
|
||||||
* gitter - https://github.com/sromku/go-gitter
|
* gitter - https://github.com/sromku/go-gitter
|
||||||
* gops - https://github.com/google/gops
|
* gops - https://github.com/google/gops
|
||||||
* gozulipbot - https://github.com/ifo/gozulipbot
|
|
||||||
* irc - https://github.com/lrstanley/girc
|
* irc - https://github.com/lrstanley/girc
|
||||||
* mattermost - https://github.com/mattermost/platform
|
* mattermost - https://github.com/mattermost/platform
|
||||||
* matrix - https://github.com/matrix-org/gomatrix
|
* matrix - https://github.com/matrix-org/gomatrix
|
||||||
@ -200,4 +182,3 @@ Matterbridge wouldn't exist without these libraries:
|
|||||||
* steam - https://github.com/Philipp15b/go-steam
|
* steam - https://github.com/Philipp15b/go-steam
|
||||||
* telegram - https://github.com/go-telegram-bot-api/telegram-bot-api
|
* telegram - https://github.com/go-telegram-bot-api/telegram-bot-api
|
||||||
* xmpp - https://github.com/mattn/go-xmpp
|
* xmpp - https://github.com/mattn/go-xmpp
|
||||||
* zulip - https://github.com/ifo/gozulipbot
|
|
||||||
|
@ -2,8 +2,8 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/42wim/matterbridge/bridge"
|
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/labstack/echo"
|
"github.com/labstack/echo"
|
||||||
"github.com/labstack/echo/middleware"
|
"github.com/labstack/echo/middleware"
|
||||||
"github.com/zfjagann/golang-ring"
|
"github.com/zfjagann/golang-ring"
|
||||||
@ -15,7 +15,7 @@ import (
|
|||||||
type Api struct {
|
type Api struct {
|
||||||
Messages ring.Ring
|
Messages ring.Ring
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
*bridge.Config
|
*config.BridgeConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApiMessage struct {
|
type ApiMessage struct {
|
||||||
@ -26,27 +26,28 @@ type ApiMessage struct {
|
|||||||
Gateway string `json:"gateway"`
|
Gateway string `json:"gateway"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(cfg *bridge.Config) bridge.Bridger {
|
var flog *log.Entry
|
||||||
b := &Api{Config: cfg}
|
var protocol = "api"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flog = log.WithFields(log.Fields{"module": protocol})
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg *config.BridgeConfig) *Api {
|
||||||
|
b := &Api{BridgeConfig: cfg}
|
||||||
e := echo.New()
|
e := echo.New()
|
||||||
e.HideBanner = true
|
|
||||||
e.HidePort = true
|
|
||||||
b.Messages = ring.Ring{}
|
b.Messages = ring.Ring{}
|
||||||
b.Messages.SetCapacity(b.GetInt("Buffer"))
|
b.Messages.SetCapacity(b.Config.Buffer)
|
||||||
if b.GetString("Token") != "" {
|
if b.Config.Token != "" {
|
||||||
e.Use(middleware.KeyAuth(func(key string, c echo.Context) (bool, error) {
|
e.Use(middleware.KeyAuth(func(key string, c echo.Context) (bool, error) {
|
||||||
return key == b.GetString("Token"), nil
|
return key == b.Config.Token, nil
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
e.GET("/api/messages", b.handleMessages)
|
e.GET("/api/messages", b.handleMessages)
|
||||||
e.GET("/api/stream", b.handleStream)
|
e.GET("/api/stream", b.handleStream)
|
||||||
e.POST("/api/message", b.handlePostMessage)
|
e.POST("/api/message", b.handlePostMessage)
|
||||||
go func() {
|
go func() {
|
||||||
if b.GetString("BindAddress") == "" {
|
flog.Fatal(e.Start(b.Config.BindAddress))
|
||||||
b.Log.Fatalf("No BindAddress configured.")
|
|
||||||
}
|
|
||||||
b.Log.Infof("Listening on %s", b.GetString("BindAddress"))
|
|
||||||
b.Log.Fatal(e.Start(b.GetString("BindAddress")))
|
|
||||||
}()
|
}()
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
@ -75,18 +76,21 @@ func (b *Api) Send(msg config.Message) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Api) handlePostMessage(c echo.Context) error {
|
func (b *Api) handlePostMessage(c echo.Context) error {
|
||||||
message := config.Message{}
|
message := &ApiMessage{}
|
||||||
if err := c.Bind(&message); err != nil {
|
if err := c.Bind(message); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// these values are fixed
|
flog.Debugf("Sending message from %s on %s to gateway", message.Username, "api")
|
||||||
message.Channel = "api"
|
b.Remote <- config.Message{
|
||||||
message.Protocol = "api"
|
Text: message.Text,
|
||||||
message.Account = b.Account
|
Username: message.Username,
|
||||||
message.ID = ""
|
UserID: message.UserID,
|
||||||
message.Timestamp = time.Now()
|
Channel: "api",
|
||||||
b.Log.Debugf("Sending message from %s on %s to gateway", message.Username, "api")
|
Avatar: message.Avatar,
|
||||||
b.Remote <- message
|
Account: b.Account,
|
||||||
|
Gateway: message.Gateway,
|
||||||
|
Protocol: "api",
|
||||||
|
}
|
||||||
return c.JSON(http.StatusOK, message)
|
return c.JSON(http.StatusOK, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
110
bridge/bridge.go
110
bridge/bridge.go
@ -1,8 +1,20 @@
|
|||||||
package bridge
|
package bridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/42wim/matterbridge/bridge/api"
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
log "github.com/sirupsen/logrus"
|
"github.com/42wim/matterbridge/bridge/discord"
|
||||||
|
"github.com/42wim/matterbridge/bridge/gitter"
|
||||||
|
"github.com/42wim/matterbridge/bridge/irc"
|
||||||
|
"github.com/42wim/matterbridge/bridge/matrix"
|
||||||
|
"github.com/42wim/matterbridge/bridge/mattermost"
|
||||||
|
"github.com/42wim/matterbridge/bridge/rocketchat"
|
||||||
|
"github.com/42wim/matterbridge/bridge/slack"
|
||||||
|
"github.com/42wim/matterbridge/bridge/sshchat"
|
||||||
|
"github.com/42wim/matterbridge/bridge/steam"
|
||||||
|
"github.com/42wim/matterbridge/bridge/telegram"
|
||||||
|
"github.com/42wim/matterbridge/bridge/xmpp"
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@ -15,28 +27,16 @@ type Bridger interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Bridge struct {
|
type Bridge struct {
|
||||||
|
Config config.Protocol
|
||||||
Bridger
|
Bridger
|
||||||
Name string
|
Name string
|
||||||
Account string
|
Account string
|
||||||
Protocol string
|
Protocol string
|
||||||
Channels map[string]config.ChannelInfo
|
Channels map[string]config.ChannelInfo
|
||||||
Joined map[string]bool
|
Joined map[string]bool
|
||||||
Log *log.Entry
|
|
||||||
Config *config.Config
|
|
||||||
General *config.Protocol
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
func New(cfg *config.Config, bridge *config.Bridge, c chan config.Message) *Bridge {
|
||||||
// General *config.Protocol
|
|
||||||
Remote chan config.Message
|
|
||||||
Log *log.Entry
|
|
||||||
*Bridge
|
|
||||||
}
|
|
||||||
|
|
||||||
// Factory is the factory function to create a bridge
|
|
||||||
type Factory func(*Config) Bridger
|
|
||||||
|
|
||||||
func New(bridge *config.Bridge) *Bridge {
|
|
||||||
b := new(Bridge)
|
b := new(Bridge)
|
||||||
b.Channels = make(map[string]config.ChannelInfo)
|
b.Channels = make(map[string]config.ChannelInfo)
|
||||||
accInfo := strings.Split(bridge.Account, ".")
|
accInfo := strings.Split(bridge.Account, ".")
|
||||||
@ -46,6 +46,49 @@ func New(bridge *config.Bridge) *Bridge {
|
|||||||
b.Protocol = protocol
|
b.Protocol = protocol
|
||||||
b.Account = bridge.Account
|
b.Account = bridge.Account
|
||||||
b.Joined = make(map[string]bool)
|
b.Joined = make(map[string]bool)
|
||||||
|
bridgeConfig := &config.BridgeConfig{General: &cfg.General, Account: bridge.Account, Remote: c}
|
||||||
|
|
||||||
|
// override config from environment
|
||||||
|
config.OverrideCfgFromEnv(cfg, protocol, name)
|
||||||
|
switch protocol {
|
||||||
|
case "mattermost":
|
||||||
|
bridgeConfig.Config = cfg.Mattermost[name]
|
||||||
|
b.Bridger = bmattermost.New(bridgeConfig)
|
||||||
|
case "irc":
|
||||||
|
bridgeConfig.Config = cfg.IRC[name]
|
||||||
|
b.Bridger = birc.New(bridgeConfig)
|
||||||
|
case "gitter":
|
||||||
|
bridgeConfig.Config = cfg.Gitter[name]
|
||||||
|
b.Bridger = bgitter.New(bridgeConfig)
|
||||||
|
case "slack":
|
||||||
|
bridgeConfig.Config = cfg.Slack[name]
|
||||||
|
b.Bridger = bslack.New(bridgeConfig)
|
||||||
|
case "xmpp":
|
||||||
|
bridgeConfig.Config = cfg.Xmpp[name]
|
||||||
|
b.Bridger = bxmpp.New(bridgeConfig)
|
||||||
|
case "discord":
|
||||||
|
bridgeConfig.Config = cfg.Discord[name]
|
||||||
|
b.Bridger = bdiscord.New(bridgeConfig)
|
||||||
|
case "telegram":
|
||||||
|
bridgeConfig.Config = cfg.Telegram[name]
|
||||||
|
b.Bridger = btelegram.New(bridgeConfig)
|
||||||
|
case "rocketchat":
|
||||||
|
bridgeConfig.Config = cfg.Rocketchat[name]
|
||||||
|
b.Bridger = brocketchat.New(bridgeConfig)
|
||||||
|
case "matrix":
|
||||||
|
bridgeConfig.Config = cfg.Matrix[name]
|
||||||
|
b.Bridger = bmatrix.New(bridgeConfig)
|
||||||
|
case "steam":
|
||||||
|
bridgeConfig.Config = cfg.Steam[name]
|
||||||
|
b.Bridger = bsteam.New(bridgeConfig)
|
||||||
|
case "sshchat":
|
||||||
|
bridgeConfig.Config = cfg.Sshchat[name]
|
||||||
|
b.Bridger = bsshchat.New(bridgeConfig)
|
||||||
|
case "api":
|
||||||
|
bridgeConfig.Config = cfg.Api[name]
|
||||||
|
b.Bridger = api.New(bridgeConfig)
|
||||||
|
}
|
||||||
|
b.Config = bridgeConfig.Config
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +100,7 @@ func (b *Bridge) JoinChannels() error {
|
|||||||
func (b *Bridge) joinChannels(channels map[string]config.ChannelInfo, exists map[string]bool) error {
|
func (b *Bridge) joinChannels(channels map[string]config.ChannelInfo, exists map[string]bool) error {
|
||||||
for ID, channel := range channels {
|
for ID, channel := range channels {
|
||||||
if !exists[ID] {
|
if !exists[ID] {
|
||||||
b.Log.Infof("%s: joining %s (ID: %s)", b.Account, channel.Name, ID)
|
log.Infof("%s: joining %s (%s)", b.Account, channel.Name, ID)
|
||||||
err := b.JoinChannel(channel)
|
err := b.JoinChannel(channel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -67,38 +110,3 @@ func (b *Bridge) joinChannels(channels map[string]config.ChannelInfo, exists map
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bridge) GetBool(key string) bool {
|
|
||||||
if b.Config.GetBool(b.Account + "." + key) {
|
|
||||||
return b.Config.GetBool(b.Account + "." + key)
|
|
||||||
}
|
|
||||||
return b.Config.GetBool("general." + key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bridge) GetInt(key string) int {
|
|
||||||
if b.Config.GetInt(b.Account+"."+key) != 0 {
|
|
||||||
return b.Config.GetInt(b.Account + "." + key)
|
|
||||||
}
|
|
||||||
return b.Config.GetInt("general." + key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bridge) GetString(key string) string {
|
|
||||||
if b.Config.GetString(b.Account+"."+key) != "" {
|
|
||||||
return b.Config.GetString(b.Account + "." + key)
|
|
||||||
}
|
|
||||||
return b.Config.GetString("general." + key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bridge) GetStringSlice(key string) []string {
|
|
||||||
if len(b.Config.GetStringSlice(b.Account+"."+key)) != 0 {
|
|
||||||
return b.Config.GetStringSlice(b.Account + "." + key)
|
|
||||||
}
|
|
||||||
return b.Config.GetStringSlice("general." + key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bridge) GetStringSlice2D(key string) [][]string {
|
|
||||||
if len(b.Config.GetStringSlice2D(b.Account+"."+key)) != 0 {
|
|
||||||
return b.Config.GetStringSlice2D(b.Account + "." + key)
|
|
||||||
}
|
|
||||||
return b.Config.GetStringSlice2D("general." + key)
|
|
||||||
}
|
|
||||||
|
@ -1,26 +1,20 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"github.com/BurntSushi/toml"
|
||||||
"github.com/fsnotify/fsnotify"
|
"log"
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
prefixed "github.com/x-cray/logrus-prefixed-formatter"
|
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
EVENT_JOIN_LEAVE = "join_leave"
|
EVENT_JOIN_LEAVE = "join_leave"
|
||||||
EVENT_TOPIC_CHANGE = "topic_change"
|
EVENT_FAILURE = "failure"
|
||||||
EVENT_FAILURE = "failure"
|
EVENT_REJOIN_CHANNELS = "rejoin_channels"
|
||||||
EVENT_FILE_FAILURE_SIZE = "file_failure_size"
|
EVENT_USER_ACTION = "user_action"
|
||||||
EVENT_AVATAR_DOWNLOAD = "avatar_download"
|
EVENT_MSG_DELETE = "msg_delete"
|
||||||
EVENT_REJOIN_CHANNELS = "rejoin_channels"
|
|
||||||
EVENT_USER_ACTION = "user_action"
|
|
||||||
EVENT_MSG_DELETE = "msg_delete"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
@ -43,9 +37,6 @@ type FileInfo struct {
|
|||||||
Data *[]byte
|
Data *[]byte
|
||||||
Comment string
|
Comment string
|
||||||
URL string
|
URL string
|
||||||
Size int64
|
|
||||||
Avatar bool
|
|
||||||
SHA string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChannelInfo struct {
|
type ChannelInfo struct {
|
||||||
@ -62,16 +53,12 @@ type Protocol struct {
|
|||||||
BindAddress string // mattermost, slack // DEPRECATED
|
BindAddress string // mattermost, slack // DEPRECATED
|
||||||
Buffer int // api
|
Buffer int // api
|
||||||
Charset string // irc
|
Charset string // irc
|
||||||
ColorNicks bool // only irc for now
|
|
||||||
Debug bool // general
|
|
||||||
DebugLevel int // only for irc now
|
|
||||||
EditSuffix string // mattermost, slack, discord, telegram, gitter
|
EditSuffix string // mattermost, slack, discord, telegram, gitter
|
||||||
EditDisable bool // mattermost, slack, discord, telegram, gitter
|
EditDisable bool // mattermost, slack, discord, telegram, gitter
|
||||||
IconURL string // mattermost, slack
|
IconURL string // mattermost, slack
|
||||||
IgnoreNicks string // all protocols
|
IgnoreNicks string // all protocols
|
||||||
IgnoreMessages string // all protocols
|
IgnoreMessages string // all protocols
|
||||||
Jid string // xmpp
|
Jid string // xmpp
|
||||||
Label string // all protocols
|
|
||||||
Login string // mattermost, matrix
|
Login string // mattermost, matrix
|
||||||
MediaDownloadSize int // all protocols
|
MediaDownloadSize int // all protocols
|
||||||
MediaServerDownload string
|
MediaServerDownload string
|
||||||
@ -90,26 +77,21 @@ type Protocol struct {
|
|||||||
NickServPassword string // IRC
|
NickServPassword string // IRC
|
||||||
NicksPerRow int // mattermost, slack
|
NicksPerRow int // mattermost, slack
|
||||||
NoHomeServerSuffix bool // matrix
|
NoHomeServerSuffix bool // matrix
|
||||||
NoSendJoinPart bool // all protocols
|
|
||||||
NoTLS bool // mattermost
|
NoTLS bool // mattermost
|
||||||
Password string // IRC,mattermost,XMPP,matrix
|
Password string // IRC,mattermost,XMPP,matrix
|
||||||
PrefixMessagesWithNick bool // mattemost, slack
|
PrefixMessagesWithNick bool // mattemost, slack
|
||||||
Protocol string // all protocols
|
Protocol string // all protocols
|
||||||
QuoteDisable bool // telegram
|
|
||||||
QuoteFormat string // telegram
|
|
||||||
RejoinDelay int // IRC
|
RejoinDelay int // IRC
|
||||||
ReplaceMessages [][]string // all protocols
|
ReplaceMessages [][]string // all protocols
|
||||||
ReplaceNicks [][]string // all protocols
|
ReplaceNicks [][]string // all protocols
|
||||||
RemoteNickFormat string // all protocols
|
RemoteNickFormat string // all protocols
|
||||||
Server string // IRC,mattermost,XMPP,discord
|
Server string // IRC,mattermost,XMPP,discord
|
||||||
ShowJoinPart bool // all protocols
|
ShowJoinPart bool // all protocols
|
||||||
ShowTopicChange bool // slack
|
|
||||||
ShowEmbeds bool // discord
|
ShowEmbeds bool // discord
|
||||||
SkipTLSVerify bool // IRC, mattermost
|
SkipTLSVerify bool // IRC, mattermost
|
||||||
StripNick bool // all protocols
|
StripNick bool // all protocols
|
||||||
Team string // mattermost
|
Team string // mattermost
|
||||||
Token string // gitter, slack, discord, api
|
Token string // gitter, slack, discord, api
|
||||||
Topic string // zulip
|
|
||||||
URL string // mattermost, slack // DEPRECATED
|
URL string // mattermost, slack // DEPRECATED
|
||||||
UseAPI bool // mattermost, slack
|
UseAPI bool // mattermost, slack
|
||||||
UseSASL bool // IRC
|
UseSASL bool // IRC
|
||||||
@ -149,9 +131,9 @@ type SameChannelGateway struct {
|
|||||||
Accounts []string
|
Accounts []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConfigValues struct {
|
type Config struct {
|
||||||
Api map[string]Protocol
|
Api map[string]Protocol
|
||||||
Irc map[string]Protocol
|
IRC map[string]Protocol
|
||||||
Mattermost map[string]Protocol
|
Mattermost map[string]Protocol
|
||||||
Matrix map[string]Protocol
|
Matrix map[string]Protocol
|
||||||
Slack map[string]Protocol
|
Slack map[string]Protocol
|
||||||
@ -162,117 +144,90 @@ type ConfigValues struct {
|
|||||||
Telegram map[string]Protocol
|
Telegram map[string]Protocol
|
||||||
Rocketchat map[string]Protocol
|
Rocketchat map[string]Protocol
|
||||||
Sshchat map[string]Protocol
|
Sshchat map[string]Protocol
|
||||||
Zulip map[string]Protocol
|
|
||||||
General Protocol
|
General Protocol
|
||||||
Gateway []Gateway
|
Gateway []Gateway
|
||||||
SameChannelGateway []SameChannelGateway
|
SameChannelGateway []SameChannelGateway
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type BridgeConfig struct {
|
||||||
v *viper.Viper
|
Config Protocol
|
||||||
*ConfigValues
|
General *Protocol
|
||||||
sync.RWMutex
|
Account string
|
||||||
|
Remote chan Message
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfig(cfgfile string) *Config {
|
func NewConfig(cfgfile string) *Config {
|
||||||
log.SetFormatter(&prefixed.TextFormatter{PrefixPadding: 13, DisableColors: true, FullTimestamp: false})
|
var cfg Config
|
||||||
flog := log.WithFields(log.Fields{"prefix": "config"})
|
if _, err := toml.DecodeFile(cfgfile, &cfg); err != nil {
|
||||||
var cfg ConfigValues
|
|
||||||
viper.SetConfigType("toml")
|
|
||||||
viper.SetConfigFile(cfgfile)
|
|
||||||
viper.SetEnvPrefix("matterbridge")
|
|
||||||
viper.AddConfigPath(".")
|
|
||||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
|
||||||
viper.AutomaticEnv()
|
|
||||||
f, err := os.Open(cfgfile)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
err = viper.ReadConfig(f)
|
fail := false
|
||||||
if err != nil {
|
for k, v := range cfg.Mattermost {
|
||||||
log.Fatal(err)
|
res := Deprecated(v, "mattermost."+k)
|
||||||
|
if res {
|
||||||
|
fail = res
|
||||||
|
}
|
||||||
}
|
}
|
||||||
err = viper.Unmarshal(&cfg)
|
for k, v := range cfg.Slack {
|
||||||
if err != nil {
|
res := Deprecated(v, "slack."+k)
|
||||||
log.Fatal("blah", err)
|
if res {
|
||||||
|
fail = res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k, v := range cfg.Rocketchat {
|
||||||
|
res := Deprecated(v, "rocketchat."+k)
|
||||||
|
if res {
|
||||||
|
fail = res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fail {
|
||||||
|
log.Fatalf("Fix your config. Please see changelog for more information")
|
||||||
}
|
}
|
||||||
mycfg := new(Config)
|
|
||||||
mycfg.v = viper.GetViper()
|
|
||||||
if cfg.General.MediaDownloadSize == 0 {
|
if cfg.General.MediaDownloadSize == 0 {
|
||||||
cfg.General.MediaDownloadSize = 1000000
|
cfg.General.MediaDownloadSize = 1000000
|
||||||
}
|
}
|
||||||
viper.WatchConfig()
|
return &cfg
|
||||||
viper.OnConfigChange(func(e fsnotify.Event) {
|
|
||||||
flog.Println("Config file changed:", e.Name)
|
|
||||||
})
|
|
||||||
|
|
||||||
mycfg.ConfigValues = &cfg
|
|
||||||
return mycfg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfigFromString(input []byte) *Config {
|
func OverrideCfgFromEnv(cfg *Config, protocol string, account string) {
|
||||||
var cfg ConfigValues
|
var protoCfg Protocol
|
||||||
viper.SetConfigType("toml")
|
val := reflect.ValueOf(cfg).Elem()
|
||||||
err := viper.ReadConfig(bytes.NewBuffer(input))
|
// loop over the Config struct
|
||||||
if err != nil {
|
for i := 0; i < val.NumField(); i++ {
|
||||||
log.Fatal(err)
|
typeField := val.Type().Field(i)
|
||||||
}
|
// look for the protocol map (both lowercase)
|
||||||
err = viper.Unmarshal(&cfg)
|
if strings.ToLower(typeField.Name) == protocol {
|
||||||
if err != nil {
|
// get the Protocol struct from the map
|
||||||
log.Fatal(err)
|
data := val.Field(i).MapIndex(reflect.ValueOf(account))
|
||||||
}
|
protoCfg = data.Interface().(Protocol)
|
||||||
mycfg := new(Config)
|
protoStruct := reflect.ValueOf(&protoCfg).Elem()
|
||||||
mycfg.v = viper.GetViper()
|
// loop over the found protocol struct
|
||||||
mycfg.ConfigValues = &cfg
|
for i := 0; i < protoStruct.NumField(); i++ {
|
||||||
return mycfg
|
typeField := protoStruct.Type().Field(i)
|
||||||
}
|
// build our environment key (eg MATTERBRIDGE_MATTERMOST_WORK_LOGIN)
|
||||||
|
key := "matterbridge_" + protocol + "_" + account + "_" + typeField.Name
|
||||||
func (c *Config) GetBool(key string) bool {
|
key = strings.ToUpper(key)
|
||||||
c.RLock()
|
// search the environment
|
||||||
defer c.RUnlock()
|
res := os.Getenv(key)
|
||||||
// log.Debugf("getting bool %s = %#v", key, c.v.GetBool(key))
|
// if it exists and the current field is a string
|
||||||
return c.v.GetBool(key)
|
// then update the current field
|
||||||
}
|
if res != "" {
|
||||||
|
fieldVal := protoStruct.Field(i)
|
||||||
func (c *Config) GetInt(key string) int {
|
if fieldVal.Kind() == reflect.String {
|
||||||
c.RLock()
|
log.Printf("config: overriding %s from env with %s\n", key, res)
|
||||||
defer c.RUnlock()
|
fieldVal.Set(reflect.ValueOf(res))
|
||||||
// log.Debugf("getting int %s = %d", key, c.v.GetInt(key))
|
}
|
||||||
return c.v.GetInt(key)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) GetString(key string) string {
|
|
||||||
c.RLock()
|
|
||||||
defer c.RUnlock()
|
|
||||||
// log.Debugf("getting String %s = %s", key, c.v.GetString(key))
|
|
||||||
return c.v.GetString(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) GetStringSlice(key string) []string {
|
|
||||||
c.RLock()
|
|
||||||
defer c.RUnlock()
|
|
||||||
// log.Debugf("getting StringSlice %s = %#v", key, c.v.GetStringSlice(key))
|
|
||||||
return c.v.GetStringSlice(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) GetStringSlice2D(key string) [][]string {
|
|
||||||
c.RLock()
|
|
||||||
defer c.RUnlock()
|
|
||||||
result := [][]string{}
|
|
||||||
if res, ok := c.v.Get(key).([]interface{}); ok {
|
|
||||||
for _, entry := range res {
|
|
||||||
result2 := []string{}
|
|
||||||
for _, entry2 := range entry.([]interface{}) {
|
|
||||||
result2 = append(result2, entry2.(string))
|
|
||||||
}
|
}
|
||||||
result = append(result, result2)
|
// update the map with the modified Protocol (cfg.Protocol[account] = Protocol)
|
||||||
|
val.Field(i).SetMapIndex(reflect.ValueOf(account), reflect.ValueOf(protoCfg))
|
||||||
|
break
|
||||||
}
|
}
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetIconURL(msg *Message, iconURL string) string {
|
func GetIconURL(msg *Message, cfg *Protocol) string {
|
||||||
|
iconURL := cfg.IconURL
|
||||||
info := strings.Split(msg.Account, ".")
|
info := strings.Split(msg.Account, ".")
|
||||||
protocol := info[0]
|
protocol := info[0]
|
||||||
name := info[1]
|
name := info[1]
|
||||||
@ -281,3 +236,17 @@ func GetIconURL(msg *Message, iconURL string) string {
|
|||||||
iconURL = strings.Replace(iconURL, "{PROTOCOL}", protocol, -1)
|
iconURL = strings.Replace(iconURL, "{PROTOCOL}", protocol, -1)
|
||||||
return iconURL
|
return iconURL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Deprecated(cfg Protocol, account string) bool {
|
||||||
|
if cfg.BindAddress != "" {
|
||||||
|
log.Printf("ERROR: %s BindAddress is deprecated, you need to change it to WebhookBindAddress.", account)
|
||||||
|
} else if cfg.URL != "" {
|
||||||
|
log.Printf("ERROR: %s URL is deprecated, you need to change it to WebhookURL.", account)
|
||||||
|
} else if cfg.UseAPI {
|
||||||
|
log.Printf("ERROR: %s UseAPI is deprecated, it's enabled by default, please remove it from your config file.", account)
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
//log.Fatalf("ERROR: Fix your config: %s", account)
|
||||||
|
}
|
||||||
|
@ -2,17 +2,15 @@ package bdiscord
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
"github.com/42wim/matterbridge/bridge"
|
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Bdiscord struct {
|
type bdiscord struct {
|
||||||
c *discordgo.Session
|
c *discordgo.Session
|
||||||
Channels []*discordgo.Channel
|
Channels []*discordgo.Channel
|
||||||
Nick string
|
Nick string
|
||||||
@ -23,74 +21,82 @@ type Bdiscord struct {
|
|||||||
webhookToken string
|
webhookToken string
|
||||||
channelInfoMap map[string]*config.ChannelInfo
|
channelInfoMap map[string]*config.ChannelInfo
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
*bridge.Config
|
*config.BridgeConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(cfg *bridge.Config) bridge.Bridger {
|
var flog *log.Entry
|
||||||
b := &Bdiscord{Config: cfg}
|
var protocol = "discord"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flog = log.WithFields(log.Fields{"module": protocol})
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg *config.BridgeConfig) *bdiscord {
|
||||||
|
b := &bdiscord{BridgeConfig: cfg}
|
||||||
b.userMemberMap = make(map[string]*discordgo.Member)
|
b.userMemberMap = make(map[string]*discordgo.Member)
|
||||||
b.channelInfoMap = make(map[string]*config.ChannelInfo)
|
b.channelInfoMap = make(map[string]*config.ChannelInfo)
|
||||||
if b.GetString("WebhookURL") != "" {
|
if b.Config.WebhookURL != "" {
|
||||||
b.Log.Debug("Configuring Discord Incoming Webhook")
|
flog.Debug("Configuring Discord Incoming Webhook")
|
||||||
b.webhookID, b.webhookToken = b.splitURL(b.GetString("WebhookURL"))
|
b.webhookID, b.webhookToken = b.splitURL(b.Config.WebhookURL)
|
||||||
}
|
}
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bdiscord) Connect() error {
|
func (b *bdiscord) Connect() error {
|
||||||
var err error
|
var err error
|
||||||
var token string
|
flog.Info("Connecting")
|
||||||
b.Log.Info("Connecting")
|
if b.Config.WebhookURL == "" {
|
||||||
if b.GetString("WebhookURL") == "" {
|
flog.Info("Connecting using token")
|
||||||
b.Log.Info("Connecting using token")
|
|
||||||
} else {
|
} else {
|
||||||
b.Log.Info("Connecting using webhookurl (for posting) and token")
|
flog.Info("Connecting using webhookurl (for posting) and token")
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(b.GetString("Token"), "Bot ") {
|
if !strings.HasPrefix(b.Config.Token, "Bot ") {
|
||||||
token = "Bot " + b.GetString("Token")
|
b.Config.Token = "Bot " + b.Config.Token
|
||||||
}
|
}
|
||||||
b.c, err = discordgo.New(token)
|
b.c, err = discordgo.New(b.Config.Token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
flog.Debugf("%#v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
b.Log.Info("Connection succeeded")
|
flog.Info("Connection succeeded")
|
||||||
b.c.AddHandler(b.messageCreate)
|
b.c.AddHandler(b.messageCreate)
|
||||||
b.c.AddHandler(b.memberUpdate)
|
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)
|
||||||
err = b.c.Open()
|
err = b.c.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
flog.Debugf("%#v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
guilds, err := b.c.UserGuilds(100, "", "")
|
guilds, err := b.c.UserGuilds(100, "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
flog.Debugf("%#v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
userinfo, err := b.c.User("@me")
|
userinfo, err := b.c.User("@me")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
flog.Debugf("%#v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
b.Nick = userinfo.Username
|
b.Nick = userinfo.Username
|
||||||
for _, guild := range guilds {
|
for _, guild := range guilds {
|
||||||
if guild.Name == b.GetString("Server") {
|
if guild.Name == b.Config.Server {
|
||||||
b.Channels, err = b.c.GuildChannels(guild.ID)
|
b.Channels, err = b.c.GuildChannels(guild.ID)
|
||||||
b.guildID = guild.ID
|
b.guildID = guild.ID
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
flog.Debugf("%#v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, channel := range b.Channels {
|
|
||||||
b.Log.Debugf("found channel %#v", channel)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bdiscord) Disconnect() error {
|
func (b *bdiscord) Disconnect() error {
|
||||||
return b.c.Close()
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bdiscord) JoinChannel(channel config.ChannelInfo) error {
|
func (b *bdiscord) JoinChannel(channel config.ChannelInfo) error {
|
||||||
b.channelInfoMap[channel.ID] = &channel
|
b.channelInfoMap[channel.ID] = &channel
|
||||||
idcheck := strings.Split(channel.Name, "ID:")
|
idcheck := strings.Split(channel.Name, "ID:")
|
||||||
if len(idcheck) > 1 {
|
if len(idcheck) > 1 {
|
||||||
@ -99,117 +105,99 @@ func (b *Bdiscord) JoinChannel(channel config.ChannelInfo) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bdiscord) Send(msg config.Message) (string, error) {
|
func (b *bdiscord) Send(msg config.Message) (string, error) {
|
||||||
b.Log.Debugf("=> Receiving %#v", msg)
|
flog.Debugf("Receiving %#v", msg)
|
||||||
|
|
||||||
channelID := b.getChannelID(msg.Channel)
|
channelID := b.getChannelID(msg.Channel)
|
||||||
if channelID == "" {
|
if channelID == "" {
|
||||||
return "", fmt.Errorf("Could not find channelID for %v", msg.Channel)
|
flog.Errorf("Could not find channelID for %v", msg.Channel)
|
||||||
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make a action /me of the message
|
|
||||||
if msg.Event == config.EVENT_USER_ACTION {
|
if msg.Event == config.EVENT_USER_ACTION {
|
||||||
msg.Text = "_" + msg.Text + "_"
|
msg.Text = "_" + msg.Text + "_"
|
||||||
}
|
}
|
||||||
|
|
||||||
// use initial webhook
|
|
||||||
wID := b.webhookID
|
wID := b.webhookID
|
||||||
wToken := b.webhookToken
|
wToken := b.webhookToken
|
||||||
|
|
||||||
// check if have a channel specific webhook
|
|
||||||
if ci, ok := b.channelInfoMap[msg.Channel+b.Account]; ok {
|
if ci, ok := b.channelInfoMap[msg.Channel+b.Account]; ok {
|
||||||
if ci.Options.WebhookURL != "" {
|
if ci.Options.WebhookURL != "" {
|
||||||
wID, wToken = b.splitURL(ci.Options.WebhookURL)
|
wID, wToken = b.splitURL(ci.Options.WebhookURL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use webhook to send the message
|
if wID == "" {
|
||||||
if wID != "" {
|
flog.Debugf("Broadcasting using token (API)")
|
||||||
// skip events
|
if msg.Event == config.EVENT_MSG_DELETE {
|
||||||
if msg.Event != "" {
|
if msg.ID == "" {
|
||||||
return "", nil
|
return "", nil
|
||||||
|
}
|
||||||
|
err := b.c.ChannelMessageDelete(channelID, msg.ID)
|
||||||
|
return "", err
|
||||||
}
|
}
|
||||||
b.Log.Debugf("Broadcasting using Webhook")
|
if msg.ID != "" {
|
||||||
for _, f := range msg.Extra["file"] {
|
_, err := b.c.ChannelMessageEdit(channelID, msg.ID, msg.Username+msg.Text)
|
||||||
fi := f.(config.FileInfo)
|
return msg.ID, err
|
||||||
if fi.URL != "" {
|
}
|
||||||
msg.Text += fi.URL + " "
|
|
||||||
|
if msg.Extra != nil {
|
||||||
|
// check if we have files to upload (from slack, telegram or mattermost)
|
||||||
|
if len(msg.Extra["file"]) > 0 {
|
||||||
|
var err error
|
||||||
|
for _, f := range msg.Extra["file"] {
|
||||||
|
fi := f.(config.FileInfo)
|
||||||
|
files := []*discordgo.File{}
|
||||||
|
files = append(files, &discordgo.File{fi.Name, "", bytes.NewReader(*fi.Data)})
|
||||||
|
_, err = b.c.ChannelMessageSendComplex(channelID, &discordgo.MessageSend{Content: msg.Username + fi.Comment, Files: files})
|
||||||
|
if err != nil {
|
||||||
|
flog.Errorf("file upload failed: %#v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err := b.c.WebhookExecute(
|
|
||||||
wID,
|
|
||||||
wToken,
|
|
||||||
true,
|
|
||||||
&discordgo.WebhookParams{
|
|
||||||
Content: msg.Text,
|
|
||||||
Username: msg.Username,
|
|
||||||
AvatarURL: msg.Avatar,
|
|
||||||
})
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Log.Debugf("Broadcasting using token (API)")
|
res, err := b.c.ChannelMessageSend(channelID, msg.Username+msg.Text)
|
||||||
|
if err != nil {
|
||||||
// Delete message
|
return "", err
|
||||||
if msg.Event == config.EVENT_MSG_DELETE {
|
|
||||||
if msg.ID == "" {
|
|
||||||
return "", nil
|
|
||||||
}
|
}
|
||||||
err := b.c.ChannelMessageDelete(channelID, msg.ID)
|
return res.ID, err
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
|
flog.Debugf("Broadcasting using Webhook")
|
||||||
// Upload a file if it exists
|
err := b.c.WebhookExecute(
|
||||||
if msg.Extra != nil {
|
wID,
|
||||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
wToken,
|
||||||
b.c.ChannelMessageSend(channelID, rmsg.Username+rmsg.Text)
|
true,
|
||||||
}
|
&discordgo.WebhookParams{
|
||||||
// check if we have files to upload (from slack, telegram or mattermost)
|
Content: msg.Text,
|
||||||
if len(msg.Extra["file"]) > 0 {
|
Username: msg.Username,
|
||||||
return b.handleUploadFile(&msg, channelID)
|
AvatarURL: msg.Avatar,
|
||||||
}
|
})
|
||||||
}
|
return "", err
|
||||||
|
|
||||||
// Edit message
|
|
||||||
if msg.ID != "" {
|
|
||||||
_, err := b.c.ChannelMessageEdit(channelID, msg.ID, msg.Username+msg.Text)
|
|
||||||
return msg.ID, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Post normal message
|
|
||||||
res, err := b.c.ChannelMessageSend(channelID, msg.Username+msg.Text)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return res.ID, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bdiscord) messageDelete(s *discordgo.Session, m *discordgo.MessageDelete) {
|
func (b *bdiscord) messageDelete(s *discordgo.Session, m *discordgo.MessageDelete) {
|
||||||
rmsg := config.Message{Account: b.Account, ID: m.ID, Event: config.EVENT_MSG_DELETE, Text: config.EVENT_MSG_DELETE}
|
rmsg := config.Message{Account: b.Account, ID: m.ID, Event: config.EVENT_MSG_DELETE, Text: config.EVENT_MSG_DELETE}
|
||||||
rmsg.Channel = b.getChannelName(m.ChannelID)
|
rmsg.Channel = b.getChannelName(m.ChannelID)
|
||||||
if b.UseChannelID {
|
if b.UseChannelID {
|
||||||
rmsg.Channel = "ID:" + m.ChannelID
|
rmsg.Channel = "ID:" + m.ChannelID
|
||||||
}
|
}
|
||||||
b.Log.Debugf("<= Sending message from %s to gateway", b.Account)
|
flog.Debugf("Sending message from %s to gateway", b.Account)
|
||||||
b.Log.Debugf("<= Message is %#v", rmsg)
|
flog.Debugf("Message is %#v", rmsg)
|
||||||
b.Remote <- rmsg
|
b.Remote <- rmsg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) {
|
func (b *bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) {
|
||||||
if b.GetBool("EditDisable") {
|
if b.Config.EditDisable {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// only when message is actually edited
|
// only when message is actually edited
|
||||||
if m.Message.EditedTimestamp != "" {
|
if m.Message.EditedTimestamp != "" {
|
||||||
b.Log.Debugf("Sending edit message")
|
flog.Debugf("Sending edit message")
|
||||||
m.Content = m.Content + b.GetString("EditSuffix")
|
m.Content = m.Content + b.Config.EditSuffix
|
||||||
b.messageCreate(s, (*discordgo.MessageCreate)(m))
|
b.messageCreate(s, (*discordgo.MessageCreate)(m))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
|
func (b *bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||||
var err error
|
|
||||||
|
|
||||||
// not relay our own messages
|
// not relay our own messages
|
||||||
if m.Author.Username == b.Nick {
|
if m.Author.Username == b.Nick {
|
||||||
return
|
return
|
||||||
@ -219,73 +207,69 @@ func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// add the url of the attachments to content
|
|
||||||
if len(m.Attachments) > 0 {
|
if len(m.Attachments) > 0 {
|
||||||
for _, attach := range m.Attachments {
|
for _, attach := range m.Attachments {
|
||||||
m.Content = m.Content + "\n" + attach.URL
|
m.Content = m.Content + "\n" + attach.URL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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}
|
var text string
|
||||||
|
|
||||||
if m.Content != "" {
|
if m.Content != "" {
|
||||||
b.Log.Debugf("== Receiving event %#v", m.Message)
|
flog.Debugf("Receiving message %#v", m.Message)
|
||||||
|
if len(m.MentionRoles) > 0 {
|
||||||
|
m.Message.Content = b.replaceRoleMentions(m.Message.Content)
|
||||||
|
}
|
||||||
m.Message.Content = b.stripCustomoji(m.Message.Content)
|
m.Message.Content = b.stripCustomoji(m.Message.Content)
|
||||||
m.Message.Content = b.replaceChannelMentions(m.Message.Content)
|
m.Message.Content = b.replaceChannelMentions(m.Message.Content)
|
||||||
rmsg.Text, err = m.ContentWithMoreMentionsReplaced(b.c)
|
text = m.ContentWithMentionsReplaced()
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("ContentWithMoreMentionsReplaced failed: %s", err)
|
|
||||||
rmsg.Text = m.ContentWithMentionsReplaced()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// set channel name
|
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.Channel = b.getChannelName(m.ChannelID)
|
rmsg.Channel = b.getChannelName(m.ChannelID)
|
||||||
if b.UseChannelID {
|
if b.UseChannelID {
|
||||||
rmsg.Channel = "ID:" + m.ChannelID
|
rmsg.Channel = "ID:" + m.ChannelID
|
||||||
}
|
}
|
||||||
|
|
||||||
// set username
|
if !b.Config.UseUserName {
|
||||||
if !b.GetBool("UseUserName") {
|
|
||||||
rmsg.Username = b.getNick(m.Author)
|
rmsg.Username = b.getNick(m.Author)
|
||||||
} else {
|
} else {
|
||||||
rmsg.Username = m.Author.Username
|
rmsg.Username = m.Author.Username
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we have embedded content add it to text
|
if b.Config.ShowEmbeds && m.Message.Embeds != nil {
|
||||||
if b.GetBool("ShowEmbeds") && m.Message.Embeds != nil {
|
|
||||||
for _, embed := range m.Message.Embeds {
|
for _, embed := range m.Message.Embeds {
|
||||||
rmsg.Text = rmsg.Text + "embed: " + embed.Title + " - " + embed.Description + " - " + embed.URL + "\n"
|
text = text + "embed: " + embed.Title + " - " + embed.Description + " - " + embed.URL + "\n"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// no empty messages
|
// no empty messages
|
||||||
if rmsg.Text == "" {
|
if text == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// do we have a /me action
|
text, ok := b.replaceAction(text)
|
||||||
var ok bool
|
|
||||||
rmsg.Text, ok = b.replaceAction(rmsg.Text)
|
|
||||||
if ok {
|
if ok {
|
||||||
rmsg.Event = config.EVENT_USER_ACTION
|
rmsg.Event = config.EVENT_USER_ACTION
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", m.Author.Username, b.Account)
|
rmsg.Text = text
|
||||||
b.Log.Debugf("<= Message is %#v", rmsg)
|
flog.Debugf("Sending message from %s on %s to gateway", m.Author.Username, b.Account)
|
||||||
|
flog.Debugf("Message is %#v", rmsg)
|
||||||
b.Remote <- rmsg
|
b.Remote <- rmsg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bdiscord) memberUpdate(s *discordgo.Session, m *discordgo.GuildMemberUpdate) {
|
func (b *bdiscord) memberUpdate(s *discordgo.Session, m *discordgo.GuildMemberUpdate) {
|
||||||
b.Lock()
|
b.Lock()
|
||||||
if _, ok := b.userMemberMap[m.Member.User.ID]; ok {
|
if _, ok := b.userMemberMap[m.Member.User.ID]; ok {
|
||||||
b.Log.Debugf("%s: memberupdate: user %s (nick %s) changes nick to %s", b.Account, m.Member.User.Username, b.userMemberMap[m.Member.User.ID].Nick, m.Member.Nick)
|
flog.Debugf("%s: memberupdate: user %s (nick %s) changes nick to %s", b.Account, m.Member.User.Username, b.userMemberMap[m.Member.User.ID].Nick, m.Member.Nick)
|
||||||
}
|
}
|
||||||
b.userMemberMap[m.Member.User.ID] = m.Member
|
b.userMemberMap[m.Member.User.ID] = m.Member
|
||||||
b.Unlock()
|
b.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bdiscord) getNick(user *discordgo.User) string {
|
func (b *bdiscord) getNick(user *discordgo.User) string {
|
||||||
var err error
|
var err error
|
||||||
b.Lock()
|
b.Lock()
|
||||||
defer b.Unlock()
|
defer b.Unlock()
|
||||||
@ -312,7 +296,7 @@ func (b *Bdiscord) getNick(user *discordgo.User) string {
|
|||||||
return user.Username
|
return user.Username
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bdiscord) getChannelID(name string) string {
|
func (b *bdiscord) getChannelID(name string) string {
|
||||||
idcheck := strings.Split(name, "ID:")
|
idcheck := strings.Split(name, "ID:")
|
||||||
if len(idcheck) > 1 {
|
if len(idcheck) > 1 {
|
||||||
return idcheck[1]
|
return idcheck[1]
|
||||||
@ -325,7 +309,7 @@ func (b *Bdiscord) getChannelID(name string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bdiscord) getChannelName(id string) string {
|
func (b *bdiscord) getChannelName(id string) string {
|
||||||
for _, channel := range b.Channels {
|
for _, channel := range b.Channels {
|
||||||
if channel.ID == id {
|
if channel.ID == id {
|
||||||
return channel.Name
|
return channel.Name
|
||||||
@ -334,7 +318,19 @@ func (b *Bdiscord) getChannelName(id string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bdiscord) replaceChannelMentions(text string) string {
|
func (b *bdiscord) replaceRoleMentions(text string) string {
|
||||||
|
roles, err := b.c.GuildRoles(b.guildID)
|
||||||
|
if err != nil {
|
||||||
|
flog.Debugf("%#v", string(err.(*discordgo.RESTError).ResponseBody))
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
for _, role := range roles {
|
||||||
|
text = strings.Replace(text, "<@&"+role.ID+">", "@"+role.Name, -1)
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bdiscord) replaceChannelMentions(text string) string {
|
||||||
var err error
|
var err error
|
||||||
re := regexp.MustCompile("<#[0-9]+>")
|
re := regexp.MustCompile("<#[0-9]+>")
|
||||||
text = re.ReplaceAllStringFunc(text, func(m string) string {
|
text = re.ReplaceAllStringFunc(text, func(m string) string {
|
||||||
@ -353,31 +349,28 @@ func (b *Bdiscord) replaceChannelMentions(text string) string {
|
|||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bdiscord) replaceAction(text string) (string, bool) {
|
func (b *bdiscord) replaceAction(text string) (string, bool) {
|
||||||
if strings.HasPrefix(text, "_") && strings.HasSuffix(text, "_") {
|
if strings.HasPrefix(text, "_") && strings.HasSuffix(text, "_") {
|
||||||
return strings.Replace(text, "_", "", -1), true
|
return strings.Replace(text, "_", "", -1), true
|
||||||
}
|
}
|
||||||
return text, false
|
return text, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bdiscord) stripCustomoji(text string) string {
|
func (b *bdiscord) stripCustomoji(text string) string {
|
||||||
// <:doge:302803592035958784>
|
// <:doge:302803592035958784>
|
||||||
re := regexp.MustCompile("<(:.*?:)[0-9]+>")
|
re := regexp.MustCompile("<(:.*?:)[0-9]+>")
|
||||||
return re.ReplaceAllString(text, `$1`)
|
return re.ReplaceAllString(text, `$1`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// splitURL splits a webhookURL and returns the id and token
|
// splitURL splits a webhookURL and returns the id and token
|
||||||
func (b *Bdiscord) splitURL(url string) (string, string) {
|
func (b *bdiscord) splitURL(url string) (string, string) {
|
||||||
webhookURLSplit := strings.Split(url, "/")
|
webhookURLSplit := strings.Split(url, "/")
|
||||||
if len(webhookURLSplit) != 7 {
|
|
||||||
b.Log.Fatalf("%s is no correct discord WebhookURL", url)
|
|
||||||
}
|
|
||||||
return webhookURLSplit[len(webhookURLSplit)-2], webhookURLSplit[len(webhookURLSplit)-1]
|
return webhookURLSplit[len(webhookURLSplit)-2], webhookURLSplit[len(webhookURLSplit)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
// useWebhook returns true if we have a webhook defined somewhere
|
// useWebhook returns true if we have a webhook defined somewhere
|
||||||
func (b *Bdiscord) useWebhook() bool {
|
func (b *bdiscord) useWebhook() bool {
|
||||||
if b.GetString("WebhookURL") != "" {
|
if b.Config.WebhookURL != "" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, channel := range b.channelInfoMap {
|
for _, channel := range b.channelInfoMap {
|
||||||
@ -389,9 +382,9 @@ func (b *Bdiscord) useWebhook() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// isWebhookID returns true if the specified id is used in a defined webhook
|
// isWebhookID returns true if the specified id is used in a defined webhook
|
||||||
func (b *Bdiscord) isWebhookID(id string) bool {
|
func (b *bdiscord) isWebhookID(id string) bool {
|
||||||
if b.GetString("WebhookURL") != "" {
|
if b.Config.WebhookURL != "" {
|
||||||
wID, _ := b.splitURL(b.GetString("WebhookURL"))
|
wID, _ := b.splitURL(b.Config.WebhookURL)
|
||||||
if wID == id {
|
if wID == id {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -406,18 +399,3 @@ func (b *Bdiscord) isWebhookID(id string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleUploadFile handles native upload of files
|
|
||||||
func (b *Bdiscord) handleUploadFile(msg *config.Message, channelID string) (string, error) {
|
|
||||||
var err error
|
|
||||||
for _, f := range msg.Extra["file"] {
|
|
||||||
fi := f.(config.FileInfo)
|
|
||||||
files := []*discordgo.File{}
|
|
||||||
files = append(files, &discordgo.File{fi.Name, "", bytes.NewReader(*fi.Data)})
|
|
||||||
_, err = b.c.ChannelMessageSendComplex(channelID, &discordgo.MessageSend{Content: msg.Username + fi.Comment, Files: files})
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("file upload failed: %#v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
@ -3,9 +3,8 @@ package bgitter
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/42wim/go-gitter"
|
"github.com/42wim/go-gitter"
|
||||||
"github.com/42wim/matterbridge/bridge"
|
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
log "github.com/Sirupsen/logrus"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -14,26 +13,31 @@ type Bgitter struct {
|
|||||||
User *gitter.User
|
User *gitter.User
|
||||||
Users []gitter.User
|
Users []gitter.User
|
||||||
Rooms []gitter.Room
|
Rooms []gitter.Room
|
||||||
*bridge.Config
|
*config.BridgeConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(cfg *bridge.Config) bridge.Bridger {
|
var flog *log.Entry
|
||||||
return &Bgitter{Config: cfg}
|
var protocol = "gitter"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flog = log.WithFields(log.Fields{"module": protocol})
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg *config.BridgeConfig) *Bgitter {
|
||||||
|
return &Bgitter{BridgeConfig: cfg}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bgitter) Connect() error {
|
func (b *Bgitter) Connect() error {
|
||||||
var err error
|
var err error
|
||||||
b.Log.Info("Connecting")
|
flog.Info("Connecting")
|
||||||
b.c = gitter.New(b.GetString("Token"))
|
b.c = gitter.New(b.Config.Token)
|
||||||
b.User, err = b.c.GetUser()
|
b.User, err = b.c.GetUser()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
flog.Debugf("%#v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
b.Rooms, err = b.c.GetRooms()
|
flog.Info("Connection succeeded")
|
||||||
if err != nil {
|
b.Rooms, _ = b.c.GetRooms()
|
||||||
return err
|
|
||||||
}
|
|
||||||
b.Log.Info("Connection succeeded")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,9 +73,8 @@ func (b *Bgitter) JoinChannel(channel config.ChannelInfo) error {
|
|||||||
for event := range stream.Event {
|
for event := range stream.Event {
|
||||||
switch ev := event.Data.(type) {
|
switch ev := event.Data.(type) {
|
||||||
case *gitter.MessageReceived:
|
case *gitter.MessageReceived:
|
||||||
// ignore message sent from ourselves
|
|
||||||
if ev.Message.From.ID != b.User.ID {
|
if ev.Message.From.ID != b.User.ID {
|
||||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", ev.Message.From.Username, b.Account)
|
flog.Debugf("Sending message from %s on %s to gateway", ev.Message.From.Username, b.Account)
|
||||||
rmsg := config.Message{Username: ev.Message.From.Username, Text: ev.Message.Text, Channel: room,
|
rmsg := config.Message{Username: ev.Message.From.Username, Text: ev.Message.Text, Channel: room,
|
||||||
Account: b.Account, Avatar: b.getAvatar(ev.Message.From.Username), UserID: ev.Message.From.ID,
|
Account: b.Account, Avatar: b.getAvatar(ev.Message.From.Username), UserID: ev.Message.From.ID,
|
||||||
ID: ev.Message.ID}
|
ID: ev.Message.ID}
|
||||||
@ -79,11 +82,11 @@ func (b *Bgitter) JoinChannel(channel config.ChannelInfo) error {
|
|||||||
rmsg.Event = config.EVENT_USER_ACTION
|
rmsg.Event = config.EVENT_USER_ACTION
|
||||||
rmsg.Text = strings.Replace(rmsg.Text, "@"+ev.Message.From.Username+" ", "", -1)
|
rmsg.Text = strings.Replace(rmsg.Text, "@"+ev.Message.From.Username+" ", "", -1)
|
||||||
}
|
}
|
||||||
b.Log.Debugf("<= Message is %#v", rmsg)
|
flog.Debugf("Message is %#v", rmsg)
|
||||||
b.Remote <- rmsg
|
b.Remote <- rmsg
|
||||||
}
|
}
|
||||||
case *gitter.GitterConnectionClosed:
|
case *gitter.GitterConnectionClosed:
|
||||||
b.Log.Errorf("connection with gitter closed for room %s", room)
|
flog.Errorf("connection with gitter closed for room %s", room)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}(stream, room.URI)
|
}(stream, room.URI)
|
||||||
@ -91,39 +94,25 @@ func (b *Bgitter) JoinChannel(channel config.ChannelInfo) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bgitter) Send(msg config.Message) (string, error) {
|
func (b *Bgitter) Send(msg config.Message) (string, error) {
|
||||||
b.Log.Debugf("=> Receiving %#v", msg)
|
flog.Debugf("Receiving %#v", msg)
|
||||||
roomID := b.getRoomID(msg.Channel)
|
roomID := b.getRoomID(msg.Channel)
|
||||||
if roomID == "" {
|
if roomID == "" {
|
||||||
b.Log.Errorf("Could not find roomID for %v", msg.Channel)
|
flog.Errorf("Could not find roomID for %v", msg.Channel)
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete message
|
|
||||||
if msg.Event == config.EVENT_MSG_DELETE {
|
if msg.Event == config.EVENT_MSG_DELETE {
|
||||||
if msg.ID == "" {
|
if msg.ID == "" {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
// gitter has no delete message api so we edit message to ""
|
// gitter has no delete message api
|
||||||
_, err := b.c.UpdateMessage(roomID, msg.ID, "")
|
_, err := b.c.UpdateMessage(roomID, msg.ID, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload a file (in gitter case send the upload URL because gitter has no native upload support)
|
|
||||||
if msg.Extra != nil {
|
|
||||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
|
||||||
b.c.SendMessage(roomID, rmsg.Username+rmsg.Text)
|
|
||||||
}
|
|
||||||
if len(msg.Extra["file"]) > 0 {
|
|
||||||
return b.handleUploadFile(&msg, roomID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Edit message
|
|
||||||
if msg.ID != "" {
|
if msg.ID != "" {
|
||||||
b.Log.Debugf("updating message with id %s", msg.ID)
|
flog.Debugf("updating message with id %s", msg.ID)
|
||||||
_, err := b.c.UpdateMessage(roomID, msg.ID, msg.Username+msg.Text)
|
_, err := b.c.UpdateMessage(roomID, msg.ID, msg.Username+msg.Text)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@ -131,7 +120,22 @@ func (b *Bgitter) Send(msg config.Message) (string, error) {
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post normal message
|
if msg.Extra != nil {
|
||||||
|
if len(msg.Extra["file"]) > 0 {
|
||||||
|
for _, f := range msg.Extra["file"] {
|
||||||
|
fi := f.(config.FileInfo)
|
||||||
|
if fi.URL != "" {
|
||||||
|
msg.Text = fi.URL
|
||||||
|
}
|
||||||
|
_, err := b.c.SendMessage(roomID, msg.Username+msg.Text)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := b.c.SendMessage(roomID, msg.Username+msg.Text)
|
resp, err := b.c.SendMessage(roomID, msg.Username+msg.Text)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@ -159,23 +163,3 @@ func (b *Bgitter) getAvatar(user string) string {
|
|||||||
}
|
}
|
||||||
return avatar
|
return avatar
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bgitter) handleUploadFile(msg *config.Message, roomID string) (string, error) {
|
|
||||||
for _, f := range msg.Extra["file"] {
|
|
||||||
fi := f.(config.FileInfo)
|
|
||||||
if fi.Comment != "" {
|
|
||||||
msg.Text += fi.Comment + ": "
|
|
||||||
}
|
|
||||||
if fi.URL != "" {
|
|
||||||
msg.Text = fi.URL
|
|
||||||
if fi.Comment != "" {
|
|
||||||
msg.Text = fi.Comment + ": " + fi.URL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, err := b.c.SendMessage(roomID, msg.Username+msg.Text)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
@ -2,38 +2,28 @@ package helper
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DownloadFile(url string) (*[]byte, error) {
|
func DownloadFile(url string) (*[]byte, error) {
|
||||||
return DownloadFileAuth(url, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func DownloadFileAuth(url string, auth string) (*[]byte, error) {
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Timeout: time.Second * 5,
|
Timeout: time.Second * 5,
|
||||||
}
|
}
|
||||||
req, err := http.NewRequest("GET", url, nil)
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
if auth != "" {
|
|
||||||
req.Header.Add("Authorization", auth)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
resp.Body.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
io.Copy(&buf, resp.Body)
|
io.Copy(&buf, resp.Body)
|
||||||
data := buf.Bytes()
|
data := buf.Bytes()
|
||||||
|
resp.Body.Close()
|
||||||
return &data, nil
|
return &data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,55 +38,3 @@ func SplitStringLength(input string, length int) string {
|
|||||||
}
|
}
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle all the stuff we put into extra
|
|
||||||
func HandleExtra(msg *config.Message, general *config.Protocol) []config.Message {
|
|
||||||
extra := msg.Extra
|
|
||||||
rmsg := []config.Message{}
|
|
||||||
if len(extra[config.EVENT_FILE_FAILURE_SIZE]) > 0 {
|
|
||||||
for _, f := range extra[config.EVENT_FILE_FAILURE_SIZE] {
|
|
||||||
fi := f.(config.FileInfo)
|
|
||||||
text := fmt.Sprintf("file %s too big to download (%#v > allowed size: %#v)", fi.Name, fi.Size, general.MediaDownloadSize)
|
|
||||||
rmsg = append(rmsg, config.Message{Text: text, Username: "<system> ", Channel: msg.Channel})
|
|
||||||
}
|
|
||||||
return rmsg
|
|
||||||
}
|
|
||||||
return rmsg
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetAvatar(av map[string]string, userid string, general *config.Protocol) string {
|
|
||||||
if sha, ok := av[userid]; ok {
|
|
||||||
return general.MediaServerDownload + "/" + sha + "/" + userid + ".png"
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleDownloadSize(flog *log.Entry, msg *config.Message, name string, size int64, general *config.Protocol) error {
|
|
||||||
flog.Debugf("Trying to download %#v with size %#v", name, size)
|
|
||||||
if int(size) > general.MediaDownloadSize {
|
|
||||||
msg.Event = config.EVENT_FILE_FAILURE_SIZE
|
|
||||||
msg.Extra[msg.Event] = append(msg.Extra[msg.Event], config.FileInfo{Name: name, Comment: msg.Text, Size: size})
|
|
||||||
return fmt.Errorf("File %#v to large to download (%#v). MediaDownloadSize is %#v", name, size, general.MediaDownloadSize)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleDownloadData(flog *log.Entry, msg *config.Message, name, comment, url string, data *[]byte, general *config.Protocol) {
|
|
||||||
var avatar bool
|
|
||||||
flog.Debugf("Download OK %#v %#v", name, len(*data))
|
|
||||||
if msg.Event == config.EVENT_AVATAR_DOWNLOAD {
|
|
||||||
avatar = true
|
|
||||||
}
|
|
||||||
msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{Name: name, Data: data, URL: url, Comment: comment, Avatar: avatar})
|
|
||||||
}
|
|
||||||
|
|
||||||
func RemoveEmptyNewLines(msg string) string {
|
|
||||||
lines := ""
|
|
||||||
for _, line := range strings.Split(msg, "\n") {
|
|
||||||
if line != "" {
|
|
||||||
lines += line + "\n"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lines = strings.TrimRight(lines, "\n")
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
@ -4,15 +4,13 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"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/dfordsoft/golib/ic"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/lrstanley/girc"
|
"github.com/lrstanley/girc"
|
||||||
"github.com/paulrosania/go-charset/charset"
|
"github.com/paulrosania/go-charset/charset"
|
||||||
_ "github.com/paulrosania/go-charset/data"
|
_ "github.com/paulrosania/go-charset/data"
|
||||||
"github.com/saintfish/chardet"
|
"github.com/saintfish/chardet"
|
||||||
"hash/crc32"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
@ -21,41 +19,40 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Birc struct {
|
type Birc struct {
|
||||||
i *girc.Client
|
i *girc.Client
|
||||||
Nick string
|
Nick string
|
||||||
names map[string][]string
|
names map[string][]string
|
||||||
connected chan struct{}
|
connected chan struct{}
|
||||||
Local chan config.Message // local queue for flood control
|
Local chan config.Message // local queue for flood control
|
||||||
FirstConnection bool
|
FirstConnection bool
|
||||||
MessageDelay, MessageQueue, MessageLength int
|
|
||||||
|
|
||||||
*bridge.Config
|
*config.BridgeConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(cfg *bridge.Config) bridge.Bridger {
|
var flog *log.Entry
|
||||||
|
var protocol = "irc"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flog = log.WithFields(log.Fields{"module": protocol})
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg *config.BridgeConfig) *Birc {
|
||||||
b := &Birc{}
|
b := &Birc{}
|
||||||
b.Config = cfg
|
b.BridgeConfig = cfg
|
||||||
b.Nick = b.GetString("Nick")
|
b.Nick = b.Config.Nick
|
||||||
b.names = make(map[string][]string)
|
b.names = make(map[string][]string)
|
||||||
b.connected = make(chan struct{})
|
b.connected = make(chan struct{})
|
||||||
if b.GetInt("MessageDelay") == 0 {
|
if b.Config.MessageDelay == 0 {
|
||||||
b.MessageDelay = 1300
|
b.Config.MessageDelay = 1300
|
||||||
} else {
|
|
||||||
b.MessageDelay = b.GetInt("MessageDelay")
|
|
||||||
}
|
}
|
||||||
if b.GetInt("MessageQueue") == 0 {
|
if b.Config.MessageQueue == 0 {
|
||||||
b.MessageQueue = 30
|
b.Config.MessageQueue = 30
|
||||||
} else {
|
|
||||||
b.MessageQueue = b.GetInt("MessageQueue")
|
|
||||||
}
|
}
|
||||||
if b.GetInt("MessageLength") == 0 {
|
if b.Config.MessageLength == 0 {
|
||||||
b.MessageLength = 400
|
b.Config.MessageLength = 400
|
||||||
} else {
|
|
||||||
b.MessageLength = b.GetInt("MessageLength")
|
|
||||||
}
|
}
|
||||||
b.FirstConnection = true
|
b.FirstConnection = true
|
||||||
return b
|
return b
|
||||||
@ -72,9 +69,9 @@ func (b *Birc) Command(msg *config.Message) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Birc) Connect() error {
|
func (b *Birc) Connect() error {
|
||||||
b.Local = make(chan config.Message, b.MessageQueue+10)
|
b.Local = make(chan config.Message, b.Config.MessageQueue+10)
|
||||||
b.Log.Infof("Connecting %s", b.GetString("Server"))
|
flog.Infof("Connecting %s", b.Config.Server)
|
||||||
server, portstr, err := net.SplitHostPort(b.GetString("Server"))
|
server, portstr, err := net.SplitHostPort(b.Config.Server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -83,7 +80,7 @@ func (b *Birc) Connect() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// fix strict user handling of girc
|
// fix strict user handling of girc
|
||||||
user := b.GetString("Nick")
|
user := b.Config.Nick
|
||||||
for !girc.IsValidUser(user) {
|
for !girc.IsValidUser(user) {
|
||||||
if len(user) == 1 {
|
if len(user) == 1 {
|
||||||
user = "matterbridge"
|
user = "matterbridge"
|
||||||
@ -94,28 +91,28 @@ func (b *Birc) Connect() error {
|
|||||||
|
|
||||||
i := girc.New(girc.Config{
|
i := girc.New(girc.Config{
|
||||||
Server: server,
|
Server: server,
|
||||||
ServerPass: b.GetString("Password"),
|
ServerPass: b.Config.Password,
|
||||||
Port: port,
|
Port: port,
|
||||||
Nick: b.GetString("Nick"),
|
Nick: b.Config.Nick,
|
||||||
User: user,
|
User: user,
|
||||||
Name: b.GetString("Nick"),
|
Name: b.Config.Nick,
|
||||||
SSL: b.GetBool("UseTLS"),
|
SSL: b.Config.UseTLS,
|
||||||
TLSConfig: &tls.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), ServerName: server},
|
TLSConfig: &tls.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, ServerName: server},
|
||||||
PingDelay: time.Minute,
|
PingDelay: time.Minute,
|
||||||
})
|
})
|
||||||
|
|
||||||
if b.GetBool("UseSASL") {
|
if b.Config.UseSASL {
|
||||||
i.Config.SASL = &girc.SASLPlain{b.GetString("NickServNick"), b.GetString("NickServPassword")}
|
i.Config.SASL = &girc.SASLPlain{b.Config.NickServNick, b.Config.NickServPassword}
|
||||||
}
|
}
|
||||||
|
|
||||||
i.Handlers.Add(girc.RPL_WELCOME, b.handleNewConnection)
|
i.Handlers.Add(girc.RPL_WELCOME, b.handleNewConnection)
|
||||||
i.Handlers.Add(girc.RPL_ENDOFMOTD, b.handleOtherAuth)
|
i.Handlers.Add(girc.RPL_ENDOFMOTD, b.handleOtherAuth)
|
||||||
i.Handlers.Add(girc.ALL_EVENTS, b.handleOther)
|
i.Handlers.Add("*", b.handleOther)
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
if err := i.Connect(); err != nil {
|
if err := i.Connect(); err != nil {
|
||||||
b.Log.Errorf("error: %s", err)
|
flog.Errorf("error: %s", err)
|
||||||
b.Log.Info("reconnecting in 30 seconds...")
|
flog.Info("reconnecting in 30 seconds...")
|
||||||
time.Sleep(30 * time.Second)
|
time.Sleep(30 * time.Second)
|
||||||
i.Handlers.Clear(girc.RPL_WELCOME)
|
i.Handlers.Clear(girc.RPL_WELCOME)
|
||||||
i.Handlers.Add(girc.RPL_WELCOME, func(client *girc.Client, event girc.Event) {
|
i.Handlers.Add(girc.RPL_WELCOME, func(client *girc.Client, event girc.Event) {
|
||||||
@ -131,27 +128,25 @@ func (b *Birc) Connect() error {
|
|||||||
b.i = i
|
b.i = i
|
||||||
select {
|
select {
|
||||||
case <-b.connected:
|
case <-b.connected:
|
||||||
b.Log.Info("Connection succeeded")
|
flog.Info("Connection succeeded")
|
||||||
case <-time.After(time.Second * 30):
|
case <-time.After(time.Second * 30):
|
||||||
return fmt.Errorf("connection timed out")
|
return fmt.Errorf("connection timed out")
|
||||||
}
|
}
|
||||||
//i.Debug = false
|
//i.Debug = false
|
||||||
if b.GetInt("DebugLevel") == 0 {
|
i.Handlers.Clear("*")
|
||||||
i.Handlers.Clear(girc.ALL_EVENTS)
|
|
||||||
}
|
|
||||||
go b.doSend()
|
go b.doSend()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Birc) Disconnect() error {
|
func (b *Birc) Disconnect() error {
|
||||||
b.i.Close()
|
//b.i.Disconnect()
|
||||||
close(b.Local)
|
close(b.Local)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Birc) JoinChannel(channel config.ChannelInfo) error {
|
func (b *Birc) JoinChannel(channel config.ChannelInfo) error {
|
||||||
if channel.Options.Key != "" {
|
if channel.Options.Key != "" {
|
||||||
b.Log.Debugf("using key %s for channel %s", channel.Options.Key, channel.Name)
|
flog.Debugf("using key %s for channel %s", channel.Options.Key, channel.Name)
|
||||||
b.i.Cmd.JoinKey(channel.Name, channel.Options.Key)
|
b.i.Cmd.JoinKey(channel.Name, channel.Options.Key)
|
||||||
} else {
|
} else {
|
||||||
b.i.Cmd.Join(channel.Name)
|
b.i.Cmd.Join(channel.Name)
|
||||||
@ -164,53 +159,29 @@ func (b *Birc) Send(msg config.Message) (string, error) {
|
|||||||
if msg.Event == config.EVENT_MSG_DELETE {
|
if msg.Event == config.EVENT_MSG_DELETE {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
flog.Debugf("Receiving %#v", msg)
|
||||||
b.Log.Debugf("=> Receiving %#v", msg)
|
|
||||||
|
|
||||||
// we can be in between reconnects #385
|
|
||||||
if !b.i.IsConnected() {
|
|
||||||
b.Log.Error("Not connected to server, dropping message")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute a command
|
|
||||||
if strings.HasPrefix(msg.Text, "!") {
|
if strings.HasPrefix(msg.Text, "!") {
|
||||||
b.Command(&msg)
|
b.Command(&msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert to specified charset
|
if b.Config.Charset != "" {
|
||||||
if b.GetString("Charset") != "" {
|
buf := new(bytes.Buffer)
|
||||||
switch b.GetString("Charset") {
|
w, err := charset.NewWriter(b.Config.Charset, buf)
|
||||||
case "gbk", "gb18030", "gb2312", "big5", "euc-kr", "euc-jp", "shift-jis", "iso-2022-jp":
|
if err != nil {
|
||||||
msg.Text = ic.ConvertString("utf-8", b.GetString("Charset"), msg.Text)
|
flog.Errorf("charset from utf-8 conversion failed: %s", err)
|
||||||
default:
|
return "", err
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
w, err := charset.NewWriter(b.GetString("Charset"), buf)
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("charset from utf-8 conversion failed: %s", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
fmt.Fprint(w, msg.Text)
|
|
||||||
w.Close()
|
|
||||||
msg.Text = buf.String()
|
|
||||||
}
|
}
|
||||||
|
fmt.Fprintf(w, msg.Text)
|
||||||
|
w.Close()
|
||||||
|
msg.Text = buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle files
|
|
||||||
if msg.Extra != nil {
|
if msg.Extra != nil {
|
||||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
|
||||||
b.Local <- rmsg
|
|
||||||
}
|
|
||||||
if len(msg.Extra["file"]) > 0 {
|
if len(msg.Extra["file"]) > 0 {
|
||||||
for _, f := range msg.Extra["file"] {
|
for _, f := range msg.Extra["file"] {
|
||||||
fi := f.(config.FileInfo)
|
fi := f.(config.FileInfo)
|
||||||
if fi.Comment != "" {
|
|
||||||
msg.Text += fi.Comment + ": "
|
|
||||||
}
|
|
||||||
if fi.URL != "" {
|
if fi.URL != "" {
|
||||||
msg.Text = fi.URL
|
msg.Text = fi.URL
|
||||||
if fi.Comment != "" {
|
|
||||||
msg.Text = fi.Comment + ": " + fi.URL
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
b.Local <- config.Message{Text: msg.Text, Username: msg.Username, Channel: msg.Channel, Event: msg.Event}
|
b.Local <- config.Message{Text: msg.Text, Username: msg.Username, Channel: msg.Channel, Event: msg.Event}
|
||||||
}
|
}
|
||||||
@ -219,44 +190,35 @@ func (b *Birc) Send(msg config.Message) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// split long messages on messageLength, to avoid clipped messages #281
|
// split long messages on messageLength, to avoid clipped messages #281
|
||||||
if b.GetBool("MessageSplit") {
|
if b.Config.MessageSplit {
|
||||||
msg.Text = helper.SplitStringLength(msg.Text, b.MessageLength)
|
msg.Text = helper.SplitStringLength(msg.Text, b.Config.MessageLength)
|
||||||
}
|
}
|
||||||
for _, text := range strings.Split(msg.Text, "\n") {
|
for _, text := range strings.Split(msg.Text, "\n") {
|
||||||
if len(text) > b.MessageLength {
|
input := []rune(text)
|
||||||
text = text[:b.MessageLength-len(" <message clipped>")]
|
if len(text) > b.Config.MessageLength {
|
||||||
if r, size := utf8.DecodeLastRuneInString(text); r == utf8.RuneError {
|
text = string(input[:b.Config.MessageLength]) + " <message clipped>"
|
||||||
text = text[:len(text)-size]
|
|
||||||
}
|
|
||||||
text += " <message clipped>"
|
|
||||||
}
|
}
|
||||||
if len(b.Local) < b.MessageQueue {
|
if len(b.Local) < b.Config.MessageQueue {
|
||||||
if len(b.Local) == b.MessageQueue-1 {
|
if len(b.Local) == b.Config.MessageQueue-1 {
|
||||||
text = text + " <message clipped>"
|
text = text + " <message clipped>"
|
||||||
}
|
}
|
||||||
b.Local <- config.Message{Text: text, Username: msg.Username, Channel: msg.Channel, Event: msg.Event}
|
b.Local <- config.Message{Text: text, Username: msg.Username, Channel: msg.Channel, Event: msg.Event}
|
||||||
} else {
|
} else {
|
||||||
b.Log.Debugf("flooding, dropping message (queue at %d)", len(b.Local))
|
flog.Debugf("flooding, dropping message (queue at %d)", len(b.Local))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Birc) doSend() {
|
func (b *Birc) doSend() {
|
||||||
rate := time.Millisecond * time.Duration(b.MessageDelay)
|
rate := time.Millisecond * time.Duration(b.Config.MessageDelay)
|
||||||
throttle := time.NewTicker(rate)
|
throttle := time.NewTicker(rate)
|
||||||
for msg := range b.Local {
|
for msg := range b.Local {
|
||||||
<-throttle.C
|
<-throttle.C
|
||||||
username := msg.Username
|
|
||||||
if b.GetBool("Colornicks") {
|
|
||||||
checksum := crc32.ChecksumIEEE([]byte(msg.Username))
|
|
||||||
username = fmt.Sprintf("\x03%02d%s\x0F", checksum%0x10, msg.Username)
|
|
||||||
}
|
|
||||||
if msg.Event == config.EVENT_USER_ACTION {
|
if msg.Event == config.EVENT_USER_ACTION {
|
||||||
b.i.Cmd.Action(msg.Channel, username+msg.Text)
|
b.i.Cmd.Action(msg.Channel, msg.Username+msg.Text)
|
||||||
} else {
|
} else {
|
||||||
b.Log.Debugf("Sending to channel %s", msg.Channel)
|
b.i.Cmd.Message(msg.Channel, msg.Username+msg.Text)
|
||||||
b.i.Cmd.Message(msg.Channel, username+msg.Text)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -280,7 +242,7 @@ func (b *Birc) endNames(client *girc.Client, event girc.Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Birc) handleNewConnection(client *girc.Client, event girc.Event) {
|
func (b *Birc) handleNewConnection(client *girc.Client, event girc.Event) {
|
||||||
b.Log.Debug("Registering callbacks")
|
flog.Debug("Registering callbacks")
|
||||||
i := b.i
|
i := b.i
|
||||||
b.Nick = event.Params[0]
|
b.Nick = event.Params[0]
|
||||||
|
|
||||||
@ -299,137 +261,107 @@ func (b *Birc) handleNewConnection(client *girc.Client, event girc.Event) {
|
|||||||
|
|
||||||
func (b *Birc) handleJoinPart(client *girc.Client, event girc.Event) {
|
func (b *Birc) handleJoinPart(client *girc.Client, event girc.Event) {
|
||||||
if len(event.Params) == 0 {
|
if len(event.Params) == 0 {
|
||||||
b.Log.Debugf("handleJoinPart: empty Params? %#v", event)
|
flog.Debugf("handleJoinPart: empty Params? %#v", event)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
channel := strings.ToLower(event.Params[0])
|
channel := event.Params[0]
|
||||||
if event.Command == "KICK" {
|
if event.Command == "KICK" {
|
||||||
b.Log.Infof("Got kicked from %s by %s", channel, event.Source.Name)
|
flog.Infof("Got kicked from %s by %s", channel, event.Source.Name)
|
||||||
time.Sleep(time.Duration(b.GetInt("RejoinDelay")) * time.Second)
|
time.Sleep(time.Duration(b.Config.RejoinDelay) * time.Second)
|
||||||
b.Remote <- config.Message{Username: "system", Text: "rejoin", Channel: channel, Account: b.Account, Event: config.EVENT_REJOIN_CHANNELS}
|
b.Remote <- config.Message{Username: "system", Text: "rejoin", Channel: channel, Account: b.Account, Event: config.EVENT_REJOIN_CHANNELS}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if event.Command == "QUIT" {
|
if event.Command == "QUIT" {
|
||||||
if event.Source.Name == b.Nick && strings.Contains(event.Trailing, "Ping timeout") {
|
if event.Source.Name == b.Nick && strings.Contains(event.Trailing, "Ping timeout") {
|
||||||
b.Log.Infof("%s reconnecting ..", b.Account)
|
flog.Infof("%s reconnecting ..", b.Account)
|
||||||
b.Remote <- config.Message{Username: "system", Text: "reconnect", Channel: channel, Account: b.Account, Event: config.EVENT_FAILURE}
|
b.Remote <- config.Message{Username: "system", Text: "reconnect", Channel: channel, Account: b.Account, Event: config.EVENT_FAILURE}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if event.Source.Name != b.Nick {
|
if event.Source.Name != b.Nick {
|
||||||
if b.GetBool("nosendjoinpart") {
|
flog.Debugf("Sending JOIN_LEAVE event from %s to gateway", b.Account)
|
||||||
return
|
b.Remote <- config.Message{Username: "system", Text: event.Source.Name + " " + strings.ToLower(event.Command) + "s", Channel: channel, Account: b.Account, Event: config.EVENT_JOIN_LEAVE}
|
||||||
}
|
|
||||||
b.Log.Debugf("<= Sending JOIN_LEAVE event from %s to gateway", b.Account)
|
|
||||||
msg := config.Message{Username: "system", Text: event.Source.Name + " " + strings.ToLower(event.Command) + "s", Channel: channel, Account: b.Account, Event: config.EVENT_JOIN_LEAVE}
|
|
||||||
b.Log.Debugf("<= Message is %#v", msg)
|
|
||||||
b.Remote <- msg
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
b.Log.Debugf("handle %#v", event)
|
flog.Debugf("handle %#v", event)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Birc) handleNotice(client *girc.Client, event girc.Event) {
|
func (b *Birc) handleNotice(client *girc.Client, event girc.Event) {
|
||||||
if strings.Contains(event.String(), "This nickname is registered") && event.Source.Name == b.GetString("NickServNick") {
|
if strings.Contains(event.String(), "This nickname is registered") && event.Source.Name == b.Config.NickServNick {
|
||||||
b.i.Cmd.Message(b.GetString("NickServNick"), "IDENTIFY "+b.GetString("NickServPassword"))
|
b.i.Cmd.Message(b.Config.NickServNick, "IDENTIFY "+b.Config.NickServPassword)
|
||||||
} else {
|
} else {
|
||||||
b.handlePrivMsg(client, event)
|
b.handlePrivMsg(client, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Birc) handleOther(client *girc.Client, event girc.Event) {
|
func (b *Birc) handleOther(client *girc.Client, event girc.Event) {
|
||||||
if b.GetInt("DebugLevel") == 1 {
|
|
||||||
if event.Command != "CLIENT_STATE_UPDATED" &&
|
|
||||||
event.Command != "CLIENT_GENERAL_UPDATED" {
|
|
||||||
b.Log.Debugf("%#v", event.String())
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch event.Command {
|
switch event.Command {
|
||||||
case "372", "375", "376", "250", "251", "252", "253", "254", "255", "265", "266", "002", "003", "004", "005":
|
case "372", "375", "376", "250", "251", "252", "253", "254", "255", "265", "266", "002", "003", "004", "005":
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
b.Log.Debugf("%#v", event.String())
|
flog.Debugf("%#v", event.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Birc) handleOtherAuth(client *girc.Client, event girc.Event) {
|
func (b *Birc) handleOtherAuth(client *girc.Client, event girc.Event) {
|
||||||
if strings.EqualFold(b.GetString("NickServNick"), "Q@CServe.quakenet.org") {
|
if strings.EqualFold(b.Config.NickServNick, "Q@CServe.quakenet.org") {
|
||||||
b.Log.Debugf("Authenticating %s against %s", b.GetString("NickServUsername"), b.GetString("NickServNick"))
|
flog.Debugf("Authenticating %s against %s", b.Config.NickServUsername, b.Config.NickServNick)
|
||||||
b.i.Cmd.Message(b.GetString("NickServNick"), "AUTH "+b.GetString("NickServUsername")+" "+b.GetString("NickServPassword"))
|
b.i.Cmd.Message(b.Config.NickServNick, "AUTH "+b.Config.NickServUsername+" "+b.Config.NickServPassword)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Birc) skipPrivMsg(event girc.Event) bool {
|
|
||||||
// Our nick can be changed
|
|
||||||
b.Nick = b.i.GetNick()
|
|
||||||
|
|
||||||
// freenode doesn't send 001 as first reply
|
|
||||||
if event.Command == "NOTICE" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// don't forward queries to the bot
|
|
||||||
if event.Params[0] == b.Nick {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// don't forward message from ourself
|
|
||||||
if event.Source.Name == b.Nick {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) {
|
func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) {
|
||||||
if b.skipPrivMsg(event) {
|
b.Nick = b.i.GetNick()
|
||||||
|
// freenode doesn't send 001 as first reply
|
||||||
|
if event.Command == "NOTICE" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rmsg := config.Message{Username: event.Source.Name, Channel: strings.ToLower(event.Params[0]), Account: b.Account, UserID: event.Source.Ident + "@" + event.Source.Host}
|
// don't forward queries to the bot
|
||||||
b.Log.Debugf("== Receiving PRIVMSG: %s %s %#v", event.Source.Name, event.Trailing, event)
|
if event.Params[0] == b.Nick {
|
||||||
|
return
|
||||||
// set action event
|
}
|
||||||
|
// don't forward message from ourself
|
||||||
|
if event.Source.Name == b.Nick {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rmsg := config.Message{Username: event.Source.Name, Channel: event.Params[0], Account: b.Account, UserID: event.Source.Ident + "@" + event.Source.Host}
|
||||||
|
flog.Debugf("handlePrivMsg() %s %s %#v", event.Source.Name, event.Trailing, event)
|
||||||
|
msg := ""
|
||||||
if event.IsAction() {
|
if event.IsAction() {
|
||||||
rmsg.Event = config.EVENT_USER_ACTION
|
rmsg.Event = config.EVENT_USER_ACTION
|
||||||
}
|
}
|
||||||
|
msg += event.StripAction()
|
||||||
// strip action, we made an event if it was an action
|
|
||||||
rmsg.Text += event.StripAction()
|
|
||||||
|
|
||||||
// strip IRC colors
|
// strip IRC colors
|
||||||
re := regexp.MustCompile(`[[:cntrl:]](?:\d{1,2}(?:,\d{1,2})?)?`)
|
re := regexp.MustCompile(`[[:cntrl:]](?:\d{1,2}(?:,\d{1,2})?)?`)
|
||||||
rmsg.Text = re.ReplaceAllString(rmsg.Text, "")
|
msg = re.ReplaceAllString(msg, "")
|
||||||
|
|
||||||
// start detecting the charset
|
|
||||||
var r io.Reader
|
var r io.Reader
|
||||||
var err error
|
var err error
|
||||||
mycharset := b.GetString("Charset")
|
mycharset := b.Config.Charset
|
||||||
if mycharset == "" {
|
if mycharset == "" {
|
||||||
// detect what were sending so that we convert it to utf-8
|
// detect what were sending so that we convert it to utf-8
|
||||||
detector := chardet.NewTextDetector()
|
detector := chardet.NewTextDetector()
|
||||||
result, err := detector.DetectBest([]byte(rmsg.Text))
|
result, err := detector.DetectBest([]byte(msg))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Infof("detection failed for rmsg.Text: %#v", rmsg.Text)
|
flog.Infof("detection failed for msg: %#v", msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
b.Log.Debugf("detected %s confidence %#v", result.Charset, result.Confidence)
|
flog.Debugf("detected %s confidence %#v", result.Charset, result.Confidence)
|
||||||
mycharset = result.Charset
|
mycharset = result.Charset
|
||||||
// if we're not sure, just pick ISO-8859-1
|
// if we're not sure, just pick ISO-8859-1
|
||||||
if result.Confidence < 80 {
|
if result.Confidence < 80 {
|
||||||
mycharset = "ISO-8859-1"
|
mycharset = "ISO-8859-1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch mycharset {
|
r, err = charset.NewReader(mycharset, strings.NewReader(msg))
|
||||||
case "gbk", "gb18030", "gb2312", "big5", "euc-kr", "euc-jp", "shift-jis", "iso-2022-jp":
|
if err != nil {
|
||||||
rmsg.Text = ic.ConvertString("utf-8", b.GetString("Charset"), rmsg.Text)
|
flog.Errorf("charset to utf-8 conversion failed: %s", err)
|
||||||
default:
|
return
|
||||||
r, err = charset.NewReader(mycharset, strings.NewReader(rmsg.Text))
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("charset to utf-8 conversion failed: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
output, _ := ioutil.ReadAll(r)
|
|
||||||
rmsg.Text = string(output)
|
|
||||||
}
|
}
|
||||||
|
output, _ := ioutil.ReadAll(r)
|
||||||
|
msg = string(output)
|
||||||
|
|
||||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", event.Params[0], b.Account)
|
flog.Debugf("Sending message from %s on %s to gateway", event.Params[0], b.Account)
|
||||||
|
rmsg.Text = msg
|
||||||
b.Remote <- rmsg
|
b.Remote <- rmsg
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -437,13 +369,13 @@ func (b *Birc) handleTopicWhoTime(client *girc.Client, event girc.Event) {
|
|||||||
parts := strings.Split(event.Params[2], "!")
|
parts := strings.Split(event.Params[2], "!")
|
||||||
t, err := strconv.ParseInt(event.Params[3], 10, 64)
|
t, err := strconv.ParseInt(event.Params[3], 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Errorf("Invalid time stamp: %s", event.Params[3])
|
flog.Errorf("Invalid time stamp: %s", event.Params[3])
|
||||||
}
|
}
|
||||||
user := parts[0]
|
user := parts[0]
|
||||||
if len(parts) > 1 {
|
if len(parts) > 1 {
|
||||||
user += " [" + parts[1] + "]"
|
user += " [" + parts[1] + "]"
|
||||||
}
|
}
|
||||||
b.Log.Debugf("%s: Topic set by %s [%s]", event.Command, user, time.Unix(t, 0))
|
flog.Debugf("%s: Topic set by %s [%s]", event.Command, user, time.Unix(t, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Birc) nicksPerRow() int {
|
func (b *Birc) nicksPerRow() int {
|
||||||
|
@ -2,15 +2,15 @@ package bmatrix
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
"github.com/42wim/matterbridge/bridge"
|
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
|
||||||
matrix "github.com/matterbridge/gomatrix"
|
|
||||||
"mime"
|
"mime"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
matrix "github.com/matrix-org/gomatrix"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Bmatrix struct {
|
type Bmatrix struct {
|
||||||
@ -18,33 +18,42 @@ type Bmatrix struct {
|
|||||||
UserID string
|
UserID string
|
||||||
RoomMap map[string]string
|
RoomMap map[string]string
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
*bridge.Config
|
*config.BridgeConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(cfg *bridge.Config) bridge.Bridger {
|
var flog *log.Entry
|
||||||
b := &Bmatrix{Config: cfg}
|
var protocol = "matrix"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flog = log.WithFields(log.Fields{"module": protocol})
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg *config.BridgeConfig) *Bmatrix {
|
||||||
|
b := &Bmatrix{BridgeConfig: cfg}
|
||||||
b.RoomMap = make(map[string]string)
|
b.RoomMap = make(map[string]string)
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bmatrix) Connect() error {
|
func (b *Bmatrix) Connect() error {
|
||||||
var err error
|
var err error
|
||||||
b.Log.Infof("Connecting %s", b.GetString("Server"))
|
flog.Infof("Connecting %s", b.Config.Server)
|
||||||
b.mc, err = matrix.NewClient(b.GetString("Server"), "", "")
|
b.mc, err = matrix.NewClient(b.Config.Server, "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
flog.Debugf("%#v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
resp, err := b.mc.Login(&matrix.ReqLogin{
|
resp, err := b.mc.Login(&matrix.ReqLogin{
|
||||||
Type: "m.login.password",
|
Type: "m.login.password",
|
||||||
User: b.GetString("Login"),
|
User: b.Config.Login,
|
||||||
Password: b.GetString("Password"),
|
Password: b.Config.Password,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
flog.Debugf("%#v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
b.mc.SetCredentials(resp.UserID, resp.AccessToken)
|
b.mc.SetCredentials(resp.UserID, resp.AccessToken)
|
||||||
b.UserID = resp.UserID
|
b.UserID = resp.UserID
|
||||||
b.Log.Info("Connection succeeded")
|
flog.Info("Connection succeeded")
|
||||||
go b.handlematrix()
|
go b.handlematrix()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -65,53 +74,58 @@ func (b *Bmatrix) JoinChannel(channel config.ChannelInfo) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bmatrix) Send(msg config.Message) (string, error) {
|
func (b *Bmatrix) Send(msg config.Message) (string, error) {
|
||||||
b.Log.Debugf("=> Receiving %#v", msg)
|
flog.Debugf("Receiving %#v", msg)
|
||||||
|
// ignore delete messages
|
||||||
channel := b.getRoomID(msg.Channel)
|
|
||||||
b.Log.Debugf("Channel %s maps to channel id %s", msg.Channel, channel)
|
|
||||||
|
|
||||||
// Make a action /me of the message
|
|
||||||
if msg.Event == config.EVENT_USER_ACTION {
|
|
||||||
resp, err := b.mc.SendMessageEvent(channel, "m.room.message",
|
|
||||||
matrix.TextMessage{"m.emote", msg.Username + msg.Text})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return resp.EventID, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete message
|
|
||||||
if msg.Event == config.EVENT_MSG_DELETE {
|
if msg.Event == config.EVENT_MSG_DELETE {
|
||||||
if msg.ID == "" {
|
return "", nil
|
||||||
return "", nil
|
}
|
||||||
}
|
channel := b.getRoomID(msg.Channel)
|
||||||
resp, err := b.mc.RedactEvent(channel, msg.ID, &matrix.ReqRedact{})
|
flog.Debugf("Sending to channel %s", channel)
|
||||||
if err != nil {
|
if msg.Event == config.EVENT_USER_ACTION {
|
||||||
return "", err
|
b.mc.SendMessageEvent(channel, "m.room.message",
|
||||||
}
|
matrix.TextMessage{"m.emote", msg.Username + msg.Text})
|
||||||
return resp.EventID, err
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload a file if it exists
|
|
||||||
if msg.Extra != nil {
|
if msg.Extra != nil {
|
||||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
|
||||||
b.mc.SendText(channel, rmsg.Username+rmsg.Text)
|
|
||||||
}
|
|
||||||
// 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 {
|
||||||
return b.handleUploadFile(&msg, channel)
|
for _, f := range msg.Extra["file"] {
|
||||||
|
fi := f.(config.FileInfo)
|
||||||
|
content := bytes.NewReader(*fi.Data)
|
||||||
|
sp := strings.Split(fi.Name, ".")
|
||||||
|
mtype := mime.TypeByExtension("." + sp[len(sp)-1])
|
||||||
|
if strings.Contains(mtype, "image") ||
|
||||||
|
strings.Contains(mtype, "video") {
|
||||||
|
flog.Debugf("uploading file: %s %s", fi.Name, mtype)
|
||||||
|
res, err := b.mc.UploadToContentRepo(content, mtype, int64(len(*fi.Data)))
|
||||||
|
if err != nil {
|
||||||
|
flog.Errorf("file upload failed: %#v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.Contains(mtype, "video") {
|
||||||
|
flog.Debugf("sendVideo %s", res.ContentURI)
|
||||||
|
_, err = b.mc.SendVideo(channel, fi.Name, res.ContentURI)
|
||||||
|
if err != nil {
|
||||||
|
flog.Errorf("sendVideo failed: %#v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.Contains(mtype, "image") {
|
||||||
|
flog.Debugf("sendImage %s", res.ContentURI)
|
||||||
|
_, err = b.mc.SendImage(channel, fi.Name, res.ContentURI)
|
||||||
|
if err != nil {
|
||||||
|
flog.Errorf("sendImage failed: %#v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flog.Debugf("result: %#v", res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Edit message if we have an ID
|
b.mc.SendText(channel, msg.Username+msg.Text)
|
||||||
// matrix has no editing support
|
return "", nil
|
||||||
|
|
||||||
// Post normal message
|
|
||||||
resp, err := b.mc.SendText(channel, msg.Username+msg.Text)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return resp.EventID, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bmatrix) getRoomID(channel string) string {
|
func (b *Bmatrix) getRoomID(channel string) string {
|
||||||
@ -124,188 +138,64 @@ func (b *Bmatrix) getRoomID(channel string) string {
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bmatrix) handlematrix() error {
|
func (b *Bmatrix) handlematrix() error {
|
||||||
syncer := b.mc.Syncer.(*matrix.DefaultSyncer)
|
syncer := b.mc.Syncer.(*matrix.DefaultSyncer)
|
||||||
syncer.OnEventType("m.room.redaction", b.handleEvent)
|
syncer.OnEventType("m.room.message", func(ev *matrix.Event) {
|
||||||
syncer.OnEventType("m.room.message", b.handleEvent)
|
flog.Debugf("Received: %#v", ev)
|
||||||
|
if (ev.Content["msgtype"].(string) == "m.text" ||
|
||||||
|
ev.Content["msgtype"].(string) == "m.notice" ||
|
||||||
|
ev.Content["msgtype"].(string) == "m.emote" ||
|
||||||
|
ev.Content["msgtype"].(string) == "m.file" ||
|
||||||
|
ev.Content["msgtype"].(string) == "m.image" ||
|
||||||
|
ev.Content["msgtype"].(string) == "m.video") && ev.Sender != b.UserID {
|
||||||
|
b.RLock()
|
||||||
|
channel, ok := b.RoomMap[ev.RoomID]
|
||||||
|
b.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
flog.Debugf("Unknown room %s", ev.RoomID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
username := ev.Sender[1:]
|
||||||
|
if b.Config.NoHomeServerSuffix {
|
||||||
|
re := regexp.MustCompile("(.*?):.*")
|
||||||
|
username = re.ReplaceAllString(username, `$1`)
|
||||||
|
}
|
||||||
|
rmsg := config.Message{Username: username, Text: ev.Content["body"].(string), Channel: channel, Account: b.Account, UserID: ev.Sender}
|
||||||
|
if ev.Content["msgtype"].(string) == "m.emote" {
|
||||||
|
rmsg.Event = config.EVENT_USER_ACTION
|
||||||
|
}
|
||||||
|
if ev.Content["msgtype"].(string) == "m.image" ||
|
||||||
|
ev.Content["msgtype"].(string) == "m.video" ||
|
||||||
|
ev.Content["msgtype"].(string) == "m.file" {
|
||||||
|
flog.Debugf("ev: %#v", ev)
|
||||||
|
rmsg.Extra = make(map[string][]interface{})
|
||||||
|
url := ev.Content["url"].(string)
|
||||||
|
url = strings.Replace(url, "mxc://", b.Config.Server+"/_matrix/media/v1/download/", -1)
|
||||||
|
info := ev.Content["info"].(map[string]interface{})
|
||||||
|
size := info["size"].(float64)
|
||||||
|
name := ev.Content["body"].(string)
|
||||||
|
flog.Debugf("trying to download %#v with size %#v", name, size)
|
||||||
|
if size <= float64(b.General.MediaDownloadSize) {
|
||||||
|
data, err := helper.DownloadFile(url)
|
||||||
|
if err != nil {
|
||||||
|
flog.Errorf("download %s failed %#v", url, err)
|
||||||
|
} else {
|
||||||
|
flog.Debugf("download OK %#v %#v %#v", name, len(*data), len(url))
|
||||||
|
rmsg.Extra["file"] = append(rmsg.Extra["file"], config.FileInfo{Name: name, Data: data})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rmsg.Text = ""
|
||||||
|
}
|
||||||
|
flog.Debugf("Sending message from %s on %s to gateway", ev.Sender, b.Account)
|
||||||
|
b.Remote <- rmsg
|
||||||
|
}
|
||||||
|
})
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
if err := b.mc.Sync(); err != nil {
|
if err := b.mc.Sync(); err != nil {
|
||||||
b.Log.Println("Sync() returned ", err)
|
flog.Println("Sync() returned ", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bmatrix) handleEvent(ev *matrix.Event) {
|
|
||||||
b.Log.Debugf("== Receiving event: %#v", ev)
|
|
||||||
if ev.Sender != b.UserID {
|
|
||||||
b.RLock()
|
|
||||||
channel, ok := b.RoomMap[ev.RoomID]
|
|
||||||
b.RUnlock()
|
|
||||||
if !ok {
|
|
||||||
b.Log.Debugf("Unknown room %s", ev.RoomID)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO download avatar
|
|
||||||
|
|
||||||
// Create our message
|
|
||||||
rmsg := config.Message{Username: ev.Sender[1:], Channel: channel, Account: b.Account, UserID: ev.Sender, ID: ev.ID}
|
|
||||||
|
|
||||||
// Text must be a string
|
|
||||||
if rmsg.Text, ok = ev.Content["body"].(string); !ok {
|
|
||||||
b.Log.Errorf("Content[body] wasn't a %T ?", rmsg.Text)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove homeserver suffix if configured
|
|
||||||
if b.GetBool("NoHomeServerSuffix") {
|
|
||||||
re := regexp.MustCompile("(.*?):.*")
|
|
||||||
rmsg.Username = re.ReplaceAllString(rmsg.Username, `$1`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete event
|
|
||||||
if ev.Type == "m.room.redaction" {
|
|
||||||
rmsg.Event = config.EVENT_MSG_DELETE
|
|
||||||
rmsg.ID = ev.Redacts
|
|
||||||
rmsg.Text = config.EVENT_MSG_DELETE
|
|
||||||
b.Remote <- rmsg
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do we have a /me action
|
|
||||||
if ev.Content["msgtype"].(string) == "m.emote" {
|
|
||||||
rmsg.Event = config.EVENT_USER_ACTION
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do we have attachments
|
|
||||||
if b.containsAttachment(ev.Content) {
|
|
||||||
err := b.handleDownloadFile(&rmsg, ev.Content)
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("download failed: %#v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", ev.Sender, b.Account)
|
|
||||||
b.Remote <- rmsg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleDownloadFile handles file download
|
|
||||||
func (b *Bmatrix) handleDownloadFile(rmsg *config.Message, content map[string]interface{}) error {
|
|
||||||
var (
|
|
||||||
ok bool
|
|
||||||
url, name, msgtype, mtype string
|
|
||||||
info map[string]interface{}
|
|
||||||
size float64
|
|
||||||
)
|
|
||||||
|
|
||||||
rmsg.Extra = make(map[string][]interface{})
|
|
||||||
if url, ok = content["url"].(string); !ok {
|
|
||||||
return fmt.Errorf("url isn't a %T", url)
|
|
||||||
}
|
|
||||||
url = strings.Replace(url, "mxc://", b.GetString("Server")+"/_matrix/media/v1/download/", -1)
|
|
||||||
|
|
||||||
if info, ok = content["info"].(map[string]interface{}); !ok {
|
|
||||||
return fmt.Errorf("info isn't a %T", info)
|
|
||||||
}
|
|
||||||
if size, ok = info["size"].(float64); !ok {
|
|
||||||
return fmt.Errorf("size isn't a %T", size)
|
|
||||||
}
|
|
||||||
if name, ok = content["body"].(string); !ok {
|
|
||||||
return fmt.Errorf("name isn't a %T", name)
|
|
||||||
}
|
|
||||||
if msgtype, ok = content["msgtype"].(string); !ok {
|
|
||||||
return fmt.Errorf("msgtype isn't a %T", msgtype)
|
|
||||||
}
|
|
||||||
if mtype, ok = info["mimetype"].(string); !ok {
|
|
||||||
return fmt.Errorf("mtype isn't a %T", mtype)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if we have an image uploaded without extension
|
|
||||||
if !strings.Contains(name, ".") {
|
|
||||||
if msgtype == "m.image" {
|
|
||||||
mext, _ := mime.ExtensionsByType(mtype)
|
|
||||||
if len(mext) > 0 {
|
|
||||||
name = name + mext[0]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// just a default .png extension if we don't have mime info
|
|
||||||
name = name + ".png"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if the size is ok
|
|
||||||
err := helper.HandleDownloadSize(b.Log, rmsg, name, int64(size), b.General)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// actually download the file
|
|
||||||
data, err := helper.DownloadFile(url)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("download %s failed %#v", url, err)
|
|
||||||
}
|
|
||||||
// add the downloaded data to the message
|
|
||||||
helper.HandleDownloadData(b.Log, rmsg, name, "", url, data, b.General)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleUploadFile handles native upload of files
|
|
||||||
func (b *Bmatrix) handleUploadFile(msg *config.Message, channel string) (string, error) {
|
|
||||||
for _, f := range msg.Extra["file"] {
|
|
||||||
fi := f.(config.FileInfo)
|
|
||||||
content := bytes.NewReader(*fi.Data)
|
|
||||||
sp := strings.Split(fi.Name, ".")
|
|
||||||
mtype := mime.TypeByExtension("." + sp[len(sp)-1])
|
|
||||||
if strings.Contains(mtype, "image") ||
|
|
||||||
strings.Contains(mtype, "video") {
|
|
||||||
if fi.Comment != "" {
|
|
||||||
_, err := b.mc.SendText(channel, msg.Username+fi.Comment)
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("file comment failed: %#v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b.Log.Debugf("uploading file: %s %s", fi.Name, mtype)
|
|
||||||
res, err := b.mc.UploadToContentRepo(content, mtype, int64(len(*fi.Data)))
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("file upload failed: %#v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.Contains(mtype, "video") {
|
|
||||||
b.Log.Debugf("sendVideo %s", res.ContentURI)
|
|
||||||
_, err = b.mc.SendVideo(channel, fi.Name, res.ContentURI)
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("sendVideo failed: %#v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if strings.Contains(mtype, "image") {
|
|
||||||
b.Log.Debugf("sendImage %s", res.ContentURI)
|
|
||||||
_, err = b.mc.SendImage(channel, fi.Name, res.ContentURI)
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("sendImage failed: %#v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b.Log.Debugf("result: %#v", res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// skipMessages returns true if this message should not be handled
|
|
||||||
func (b *Bmatrix) containsAttachment(content map[string]interface{}) bool {
|
|
||||||
// Skip empty messages
|
|
||||||
if content["msgtype"] == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only allow image,video or file msgtypes
|
|
||||||
if !(content["msgtype"].(string) == "m.image" ||
|
|
||||||
content["msgtype"].(string) == "m.video" ||
|
|
||||||
content["msgtype"].(string) == "m.file") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
@ -3,27 +3,52 @@ package bmattermost
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"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/matterclient"
|
"github.com/42wim/matterbridge/matterclient"
|
||||||
"github.com/42wim/matterbridge/matterhook"
|
"github.com/42wim/matterbridge/matterhook"
|
||||||
"github.com/rs/xid"
|
log "github.com/Sirupsen/logrus"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Bmattermost struct {
|
type MMhook struct {
|
||||||
mh *matterhook.Client
|
mh *matterhook.Client
|
||||||
mc *matterclient.MMClient
|
|
||||||
uuid string
|
|
||||||
TeamID string
|
|
||||||
*bridge.Config
|
|
||||||
avatarMap map[string]string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(cfg *bridge.Config) bridge.Bridger {
|
type MMapi struct {
|
||||||
b := &Bmattermost{Config: cfg, avatarMap: make(map[string]string)}
|
mc *matterclient.MMClient
|
||||||
b.uuid = xid.New().String()
|
mmMap map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
type MMMessage struct {
|
||||||
|
Text string
|
||||||
|
Channel string
|
||||||
|
Username string
|
||||||
|
UserID string
|
||||||
|
ID string
|
||||||
|
Event string
|
||||||
|
Extra map[string][]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bmattermost struct {
|
||||||
|
MMhook
|
||||||
|
MMapi
|
||||||
|
Config *config.Protocol
|
||||||
|
Remote chan config.Message
|
||||||
|
TeamId string
|
||||||
|
Account string
|
||||||
|
*config.BridgeConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
var flog *log.Entry
|
||||||
|
var protocol = "mattermost"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flog = log.WithFields(log.Fields{"module": protocol})
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg *config.BridgeConfig) *Bmattermost {
|
||||||
|
b := &Bmattermost{BridgeConfig: cfg}
|
||||||
|
b.mmMap = make(map[string]string)
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,47 +57,47 @@ func (b *Bmattermost) Command(cmd string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bmattermost) Connect() error {
|
func (b *Bmattermost) Connect() error {
|
||||||
if b.GetString("WebhookBindAddress") != "" {
|
if b.Config.WebhookBindAddress != "" {
|
||||||
if b.GetString("WebhookURL") != "" {
|
if b.Config.WebhookURL != "" {
|
||||||
b.Log.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)")
|
flog.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)")
|
||||||
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
b.mh = matterhook.New(b.Config.WebhookURL,
|
||||||
matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
|
||||||
BindAddress: b.GetString("WebhookBindAddress")})
|
BindAddress: b.Config.WebhookBindAddress})
|
||||||
} else if b.GetString("Token") != "" {
|
} else if b.Config.Token != "" {
|
||||||
b.Log.Info("Connecting using token (sending)")
|
flog.Info("Connecting using token (sending)")
|
||||||
err := b.apiLogin()
|
err := b.apiLogin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else if b.GetString("Login") != "" {
|
} else if b.Config.Login != "" {
|
||||||
b.Log.Info("Connecting using login/password (sending)")
|
flog.Info("Connecting using login/password (sending)")
|
||||||
err := b.apiLogin()
|
err := b.apiLogin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
b.Log.Info("Connecting using webhookbindaddress (receiving)")
|
flog.Info("Connecting using webhookbindaddress (receiving)")
|
||||||
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
b.mh = matterhook.New(b.Config.WebhookURL,
|
||||||
matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
|
||||||
BindAddress: b.GetString("WebhookBindAddress")})
|
BindAddress: b.Config.WebhookBindAddress})
|
||||||
}
|
}
|
||||||
go b.handleMatter()
|
go b.handleMatter()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if b.GetString("WebhookURL") != "" {
|
if b.Config.WebhookURL != "" {
|
||||||
b.Log.Info("Connecting using webhookurl (sending)")
|
flog.Info("Connecting using webhookurl (sending)")
|
||||||
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
b.mh = matterhook.New(b.Config.WebhookURL,
|
||||||
matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
|
||||||
DisableServer: true})
|
DisableServer: true})
|
||||||
if b.GetString("Token") != "" {
|
if b.Config.Token != "" {
|
||||||
b.Log.Info("Connecting using token (receiving)")
|
flog.Info("Connecting using token (receiving)")
|
||||||
err := b.apiLogin()
|
err := b.apiLogin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
go b.handleMatter()
|
go b.handleMatter()
|
||||||
} else if b.GetString("Login") != "" {
|
} else if b.Config.Login != "" {
|
||||||
b.Log.Info("Connecting using login/password (receiving)")
|
flog.Info("Connecting using login/password (receiving)")
|
||||||
err := b.apiLogin()
|
err := b.apiLogin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -80,23 +105,23 @@ func (b *Bmattermost) Connect() error {
|
|||||||
go b.handleMatter()
|
go b.handleMatter()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
} else if b.GetString("Token") != "" {
|
} else if b.Config.Token != "" {
|
||||||
b.Log.Info("Connecting using token (sending and receiving)")
|
flog.Info("Connecting using token (sending and receiving)")
|
||||||
err := b.apiLogin()
|
err := b.apiLogin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
go b.handleMatter()
|
go b.handleMatter()
|
||||||
} else if b.GetString("Login") != "" {
|
} else if b.Config.Login != "" {
|
||||||
b.Log.Info("Connecting using login/password (sending and receiving)")
|
flog.Info("Connecting using login/password (sending and receiving)")
|
||||||
err := b.apiLogin()
|
err := b.apiLogin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
go b.handleMatter()
|
go b.handleMatter()
|
||||||
}
|
}
|
||||||
if b.GetString("WebhookBindAddress") == "" && b.GetString("WebhookURL") == "" && b.GetString("Login") == "" && b.GetString("Token") == "" {
|
if b.Config.WebhookBindAddress == "" && b.Config.WebhookURL == "" && b.Config.Login == "" && b.Config.Token == "" {
|
||||||
return errors.New("no connection method found. See that you have WebhookBindAddress, WebhookURL or Token/Login/Password/Server/Team configured")
|
return errors.New("No connection method found. See that you have WebhookBindAddress, WebhookURL or Token/Login/Password/Server/Team configured.")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -107,7 +132,7 @@ func (b *Bmattermost) Disconnect() error {
|
|||||||
|
|
||||||
func (b *Bmattermost) JoinChannel(channel config.ChannelInfo) error {
|
func (b *Bmattermost) JoinChannel(channel config.ChannelInfo) error {
|
||||||
// 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.Config.WebhookURL == "" && b.Config.WebhookBindAddress == "" {
|
||||||
id := b.mc.GetChannelId(channel.Name, "")
|
id := b.mc.GetChannelId(channel.Name, "")
|
||||||
if id == "" {
|
if id == "" {
|
||||||
return fmt.Errorf("Could not find channel ID for channel %s", channel.Name)
|
return fmt.Errorf("Could not find channel ID for channel %s", channel.Name)
|
||||||
@ -118,332 +143,189 @@ func (b *Bmattermost) JoinChannel(channel config.ChannelInfo) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bmattermost) Send(msg config.Message) (string, error) {
|
func (b *Bmattermost) Send(msg config.Message) (string, error) {
|
||||||
b.Log.Debugf("=> Receiving %#v", msg)
|
flog.Debugf("Receiving %#v", msg)
|
||||||
|
|
||||||
// Make a action /me of the message
|
|
||||||
if msg.Event == config.EVENT_USER_ACTION {
|
if msg.Event == config.EVENT_USER_ACTION {
|
||||||
msg.Text = "*" + msg.Text + "*"
|
msg.Text = "*" + msg.Text + "*"
|
||||||
}
|
}
|
||||||
|
nick := msg.Username
|
||||||
|
message := msg.Text
|
||||||
|
channel := msg.Channel
|
||||||
|
|
||||||
// map the file SHA to our user (caches the avatar)
|
if b.Config.PrefixMessagesWithNick {
|
||||||
if msg.Event == config.EVENT_AVATAR_DOWNLOAD {
|
message = nick + message
|
||||||
return b.cacheAvatar(&msg)
|
|
||||||
}
|
}
|
||||||
|
if b.Config.WebhookURL != "" {
|
||||||
// Use webhook to send the message
|
matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL}
|
||||||
if b.GetString("WebhookURL") != "" {
|
matterMessage.IconURL = msg.Avatar
|
||||||
return b.sendWebhook(msg)
|
matterMessage.Channel = channel
|
||||||
|
matterMessage.UserName = nick
|
||||||
|
matterMessage.Type = ""
|
||||||
|
matterMessage.Text = message
|
||||||
|
matterMessage.Text = message
|
||||||
|
matterMessage.Props = make(map[string]interface{})
|
||||||
|
matterMessage.Props["matterbridge"] = true
|
||||||
|
err := b.mh.Send(matterMessage)
|
||||||
|
if err != nil {
|
||||||
|
flog.Info(err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete message
|
|
||||||
if msg.Event == config.EVENT_MSG_DELETE {
|
if msg.Event == config.EVENT_MSG_DELETE {
|
||||||
if msg.ID == "" {
|
if msg.ID == "" {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
return msg.ID, b.mc.DeleteMessage(msg.ID)
|
return msg.ID, b.mc.DeleteMessage(msg.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload a file if it exists
|
|
||||||
if msg.Extra != nil {
|
if msg.Extra != nil {
|
||||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
|
||||||
b.mc.PostMessage(b.mc.GetChannelId(rmsg.Channel, ""), rmsg.Username+rmsg.Text)
|
|
||||||
}
|
|
||||||
if len(msg.Extra["file"]) > 0 {
|
if len(msg.Extra["file"]) > 0 {
|
||||||
return b.handleUploadFile(&msg)
|
var err error
|
||||||
|
var res, id string
|
||||||
|
for _, f := range msg.Extra["file"] {
|
||||||
|
fi := f.(config.FileInfo)
|
||||||
|
id, err = b.mc.UploadFile(*fi.Data, b.mc.GetChannelId(channel, ""), fi.Name)
|
||||||
|
if err != nil {
|
||||||
|
flog.Debugf("ERROR %#v", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
message = fi.Comment
|
||||||
|
if b.Config.PrefixMessagesWithNick {
|
||||||
|
message = nick + fi.Comment
|
||||||
|
}
|
||||||
|
res, err = b.mc.PostMessageWithFiles(b.mc.GetChannelId(channel, ""), message, []string{id})
|
||||||
|
}
|
||||||
|
return res, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepend nick if configured
|
|
||||||
if b.GetBool("PrefixMessagesWithNick") {
|
|
||||||
msg.Text = msg.Username + msg.Text
|
|
||||||
}
|
|
||||||
|
|
||||||
// Edit message if we have an ID
|
|
||||||
if msg.ID != "" {
|
if msg.ID != "" {
|
||||||
return b.mc.EditMessage(msg.ID, msg.Text)
|
return b.mc.EditMessage(msg.ID, message)
|
||||||
}
|
}
|
||||||
|
return b.mc.PostMessage(b.mc.GetChannelId(channel, ""), message)
|
||||||
// Post normal message
|
|
||||||
return b.mc.PostMessage(b.mc.GetChannelId(msg.Channel, ""), msg.Text)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bmattermost) handleMatter() {
|
func (b *Bmattermost) handleMatter() {
|
||||||
messages := make(chan *config.Message)
|
mchan := make(chan *MMMessage)
|
||||||
if b.GetString("WebhookBindAddress") != "" {
|
if b.Config.WebhookBindAddress != "" {
|
||||||
b.Log.Debugf("Choosing webhooks based receiving")
|
flog.Debugf("Choosing webhooks based receiving")
|
||||||
go b.handleMatterHook(messages)
|
go b.handleMatterHook(mchan)
|
||||||
} else {
|
} else {
|
||||||
if b.GetString("Token") != "" {
|
if b.Config.Token != "" {
|
||||||
b.Log.Debugf("Choosing token based receiving")
|
flog.Debugf("Choosing token based receiving")
|
||||||
} else {
|
} else {
|
||||||
b.Log.Debugf("Choosing login/password based receiving")
|
flog.Debugf("Choosing login/password based receiving")
|
||||||
}
|
}
|
||||||
go b.handleMatterClient(messages)
|
go b.handleMatterClient(mchan)
|
||||||
}
|
}
|
||||||
var ok bool
|
for message := range mchan {
|
||||||
for message := range messages {
|
rmsg := config.Message{Username: message.Username, Channel: message.Channel, Account: b.Account, UserID: message.UserID, ID: message.ID, Event: message.Event, Extra: message.Extra}
|
||||||
message.Avatar = helper.GetAvatar(b.avatarMap, message.UserID, b.General)
|
text, ok := b.replaceAction(message.Text)
|
||||||
message.Account = b.Account
|
|
||||||
message.Text, ok = b.replaceAction(message.Text)
|
|
||||||
if ok {
|
if ok {
|
||||||
message.Event = config.EVENT_USER_ACTION
|
rmsg.Event = config.EVENT_USER_ACTION
|
||||||
}
|
}
|
||||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", message.Username, b.Account)
|
rmsg.Text = text
|
||||||
b.Log.Debugf("<= Message is %#v", message)
|
flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.Account)
|
||||||
b.Remote <- *message
|
flog.Debugf("Message is %#v", rmsg)
|
||||||
|
b.Remote <- rmsg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bmattermost) handleMatterClient(messages chan *config.Message) {
|
func (b *Bmattermost) handleMatterClient(mchan chan *MMMessage) {
|
||||||
for message := range b.mc.MessageChan {
|
for message := range b.mc.MessageChan {
|
||||||
b.Log.Debugf("%#v", message.Raw.Data)
|
flog.Debugf("%#v", message.Raw.Data)
|
||||||
|
if message.Type == "system_join_leave" ||
|
||||||
if b.skipMessage(message) {
|
message.Type == "system_join_channel" ||
|
||||||
b.Log.Debugf("Skipped message: %#v", message)
|
message.Type == "system_leave_channel" {
|
||||||
|
flog.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.EVENT_JOIN_LEAVE}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (message.Raw.Event == "post_edited") && b.Config.EditDisable {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// only download avatars if we have a place to upload them (configured mediaserver)
|
m := &MMMessage{Extra: make(map[string][]interface{})}
|
||||||
if b.General.MediaServerUpload != "" {
|
|
||||||
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, Extra: make(map[string][]interface{})}
|
|
||||||
|
|
||||||
// handle mattermost post properties (override username and attachments)
|
|
||||||
props := message.Post.Props
|
props := message.Post.Props
|
||||||
if props != nil {
|
if props != nil {
|
||||||
|
if _, ok := props["matterbridge"].(bool); ok {
|
||||||
|
flog.Debugf("sent by matterbridge, ignoring")
|
||||||
|
continue
|
||||||
|
}
|
||||||
if _, ok := props["override_username"].(string); ok {
|
if _, ok := props["override_username"].(string); ok {
|
||||||
rmsg.Username = props["override_username"].(string)
|
message.Username = props["override_username"].(string)
|
||||||
}
|
}
|
||||||
if _, ok := props["attachments"].([]interface{}); ok {
|
if _, ok := props["attachments"].([]interface{}); ok {
|
||||||
rmsg.Extra["attachments"] = props["attachments"].([]interface{})
|
m.Extra["attachments"] = props["attachments"].([]interface{})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// do not post our own messages back to irc
|
||||||
// create a text for bridges that don't support native editing
|
// only listen to message from our team
|
||||||
if message.Raw.Event == "post_edited" && !b.GetBool("EditDisable") {
|
if (message.Raw.Event == "posted" || message.Raw.Event == "post_edited" || message.Raw.Event == "post_deleted") &&
|
||||||
rmsg.Text = message.Text + b.GetString("EditSuffix")
|
b.mc.User.Username != message.Username && message.Raw.Data["team_id"].(string) == b.TeamId {
|
||||||
}
|
// if the message has reactions don't repost it (for now, until we can correlate reaction with message)
|
||||||
|
if message.Post.HasReactions {
|
||||||
if message.Raw.Event == "post_deleted" {
|
continue
|
||||||
rmsg.Event = config.EVENT_MSG_DELETE
|
}
|
||||||
}
|
flog.Debugf("Receiving from matterclient %#v", message)
|
||||||
|
m.UserID = message.UserID
|
||||||
if len(message.Post.FileIds) > 0 {
|
m.Username = message.Username
|
||||||
for _, id := range message.Post.FileIds {
|
m.Channel = message.Channel
|
||||||
err := b.handleDownloadFile(rmsg, id)
|
m.Text = message.Text
|
||||||
if err != nil {
|
m.ID = message.Post.Id
|
||||||
b.Log.Errorf("download failed: %s", err)
|
if message.Raw.Event == "post_edited" && !b.Config.EditDisable {
|
||||||
|
m.Text = message.Text + b.Config.EditSuffix
|
||||||
|
}
|
||||||
|
if message.Raw.Event == "post_deleted" {
|
||||||
|
m.Event = config.EVENT_MSG_DELETE
|
||||||
|
}
|
||||||
|
if len(message.Post.FileIds) > 0 {
|
||||||
|
for _, link := range b.mc.GetFileLinks(message.Post.FileIds) {
|
||||||
|
m.Text = m.Text + "\n" + link
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mchan <- m
|
||||||
}
|
}
|
||||||
messages <- rmsg
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bmattermost) handleMatterHook(messages chan *config.Message) {
|
func (b *Bmattermost) handleMatterHook(mchan chan *MMMessage) {
|
||||||
for {
|
for {
|
||||||
message := b.mh.Receive()
|
message := b.mh.Receive()
|
||||||
b.Log.Debugf("Receiving from matterhook %#v", message)
|
flog.Debugf("Receiving from matterhook %#v", message)
|
||||||
messages <- &config.Message{UserID: message.UserID, Username: message.UserName, Text: message.Text, Channel: message.ChannelName}
|
m := &MMMessage{}
|
||||||
|
m.UserID = message.UserID
|
||||||
|
m.Username = message.UserName
|
||||||
|
m.Text = message.Text
|
||||||
|
m.Channel = message.ChannelName
|
||||||
|
mchan <- m
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bmattermost) apiLogin() error {
|
func (b *Bmattermost) apiLogin() error {
|
||||||
password := b.GetString("Password")
|
password := b.Config.Password
|
||||||
if b.GetString("Token") != "" {
|
if b.Config.Token != "" {
|
||||||
password = "MMAUTHTOKEN=" + b.GetString("Token")
|
password = "MMAUTHTOKEN=" + b.Config.Token
|
||||||
}
|
}
|
||||||
|
|
||||||
b.mc = matterclient.New(b.GetString("Login"), password, b.GetString("Team"), b.GetString("Server"))
|
b.mc = matterclient.New(b.Config.Login, password,
|
||||||
if b.GetBool("debug") {
|
b.Config.Team, b.Config.Server)
|
||||||
b.mc.SetLogLevel("debug")
|
b.mc.SkipTLSVerify = b.Config.SkipTLSVerify
|
||||||
}
|
b.mc.NoTLS = b.Config.NoTLS
|
||||||
b.mc.SkipTLSVerify = b.GetBool("SkipTLSVerify")
|
flog.Infof("Connecting %s (team: %s) on %s", b.Config.Login, b.Config.Team, b.Config.Server)
|
||||||
b.mc.NoTLS = b.GetBool("NoTLS")
|
|
||||||
b.Log.Infof("Connecting %s (team: %s) on %s", b.GetString("Login"), b.GetString("Team"), b.GetString("Server"))
|
|
||||||
err := b.mc.Login()
|
err := b.mc.Login()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
b.Log.Info("Connection succeeded")
|
flog.Info("Connection succeeded")
|
||||||
b.TeamID = b.mc.GetTeamId()
|
b.TeamId = b.mc.GetTeamId()
|
||||||
go b.mc.WsReceiver()
|
go b.mc.WsReceiver()
|
||||||
go b.mc.StatusLoop()
|
go b.mc.StatusLoop()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// replaceAction replace the message with the correct action (/me) code
|
|
||||||
func (b *Bmattermost) replaceAction(text string) (string, bool) {
|
func (b *Bmattermost) replaceAction(text string) (string, bool) {
|
||||||
if strings.HasPrefix(text, "*") && strings.HasSuffix(text, "*") {
|
if strings.HasPrefix(text, "*") && strings.HasSuffix(text, "*") {
|
||||||
return strings.Replace(text, "*", "", -1), true
|
return strings.Replace(text, "*", "", -1), true
|
||||||
}
|
}
|
||||||
return text, false
|
return text, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bmattermost) cacheAvatar(msg *config.Message) (string, error) {
|
|
||||||
fi := msg.Extra["file"][0].(config.FileInfo)
|
|
||||||
/* if we have a sha we have successfully uploaded the file to the media server,
|
|
||||||
so we can now cache the sha */
|
|
||||||
if fi.SHA != "" {
|
|
||||||
b.Log.Debugf("Added %s to %s in avatarMap", fi.SHA, msg.UserID)
|
|
||||||
b.avatarMap[msg.UserID] = fi.SHA
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleDownloadAvatar downloads the avatar of userid from channel
|
|
||||||
// sends a EVENT_AVATAR_DOWNLOAD message to the gateway if successful.
|
|
||||||
// logs an error message if it fails
|
|
||||||
func (b *Bmattermost) handleDownloadAvatar(userid string, channel string) {
|
|
||||||
rmsg := config.Message{Username: "system", Text: "avatar", Channel: channel, Account: b.Account, UserID: userid, Event: config.EVENT_AVATAR_DOWNLOAD, Extra: make(map[string][]interface{})}
|
|
||||||
if _, ok := b.avatarMap[userid]; !ok {
|
|
||||||
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)
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.HandleDownloadData(b.Log, &rmsg, userid+".png", rmsg.Text, "", &data, b.General)
|
|
||||||
b.Remote <- rmsg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleDownloadFile handles file download
|
|
||||||
func (b *Bmattermost) handleDownloadFile(rmsg *config.Message, id string) error {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleUploadFile handles native upload of files
|
|
||||||
func (b *Bmattermost) handleUploadFile(msg *config.Message) (string, error) {
|
|
||||||
var err error
|
|
||||||
var res, id string
|
|
||||||
channelID := b.mc.GetChannelId(msg.Channel, "")
|
|
||||||
for _, f := range msg.Extra["file"] {
|
|
||||||
fi := f.(config.FileInfo)
|
|
||||||
id, err = b.mc.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.mc.PostMessageWithFiles(channelID, msg.Text, []string{id})
|
|
||||||
}
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendWebhook uses the configured WebhookURL to send the message
|
|
||||||
func (b *Bmattermost) sendWebhook(msg config.Message) (string, error) {
|
|
||||||
// skip events
|
|
||||||
if msg.Event != "" {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.GetBool("PrefixMessagesWithNick") {
|
|
||||||
msg.Text = msg.Username + msg.Text
|
|
||||||
}
|
|
||||||
if msg.Extra != nil {
|
|
||||||
// this sends a message only if we received a config.EVENT_FILE_FAILURE_SIZE
|
|
||||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
|
||||||
iconURL := config.GetIconURL(&rmsg, b.GetString("iconurl"))
|
|
||||||
matterMessage := matterhook.OMessage{IconURL: iconURL, Channel: rmsg.Channel, UserName: rmsg.Username, Text: rmsg.Text, Props: make(map[string]interface{})}
|
|
||||||
matterMessage.Props["matterbridge_"+b.uuid] = true
|
|
||||||
b.mh.Send(matterMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
// webhook doesn't support file uploads, so we add the url manually
|
|
||||||
if len(msg.Extra["file"]) > 0 {
|
|
||||||
for _, f := range msg.Extra["file"] {
|
|
||||||
fi := f.(config.FileInfo)
|
|
||||||
if fi.URL != "" {
|
|
||||||
msg.Text += fi.URL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iconURL := config.GetIconURL(&msg, b.GetString("iconurl"))
|
|
||||||
matterMessage := matterhook.OMessage{IconURL: iconURL, Channel: msg.Channel, UserName: msg.Username, Text: msg.Text, Props: make(map[string]interface{})}
|
|
||||||
if msg.Avatar != "" {
|
|
||||||
matterMessage.IconURL = msg.Avatar
|
|
||||||
}
|
|
||||||
matterMessage.Props["matterbridge_"+b.uuid] = true
|
|
||||||
err := b.mh.Send(matterMessage)
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Info(err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// skipMessages returns true if this message should not be handled
|
|
||||||
func (b *Bmattermost) skipMessage(message *matterclient.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.EVENT_JOIN_LEAVE}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle edited messages
|
|
||||||
if (message.Raw.Event == "post_edited") && b.GetBool("EditDisable") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore messages sent from matterbridge
|
|
||||||
if message.Post.Props != nil {
|
|
||||||
if _, ok := message.Post.Props["matterbridge_"+b.uuid].(bool); ok {
|
|
||||||
b.Log.Debugf("sent by matterbridge, ignoring")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore messages sent from a user logged in as the bot
|
|
||||||
if b.mc.User.Username == message.Username {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the message has reactions don't repost it (for now, until we can correlate reaction with message)
|
|
||||||
if message.Post.HasReactions {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore messages from other teams than ours
|
|
||||||
if message.Raw.Data["team_id"].(string) != b.TeamID {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// only handle posted, edited or deleted events
|
|
||||||
if !(message.Raw.Event == "posted" || message.Raw.Event == "post_edited" || message.Raw.Event == "post_deleted") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
package brocketchat
|
package brocketchat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"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/hook/rockethook"
|
"github.com/42wim/matterbridge/hook/rockethook"
|
||||||
"github.com/42wim/matterbridge/matterhook"
|
"github.com/42wim/matterbridge/matterhook"
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MMhook struct {
|
type MMhook struct {
|
||||||
@ -15,11 +14,18 @@ type MMhook struct {
|
|||||||
|
|
||||||
type Brocketchat struct {
|
type Brocketchat struct {
|
||||||
MMhook
|
MMhook
|
||||||
*bridge.Config
|
*config.BridgeConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(cfg *bridge.Config) bridge.Bridger {
|
var flog *log.Entry
|
||||||
return &Brocketchat{Config: cfg}
|
var protocol = "rocketchat"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flog = log.WithFields(log.Fields{"module": protocol})
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg *config.BridgeConfig) *Brocketchat {
|
||||||
|
return &Brocketchat{BridgeConfig: cfg}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Brocketchat) Command(cmd string) string {
|
func (b *Brocketchat) Command(cmd string) string {
|
||||||
@ -27,11 +33,11 @@ func (b *Brocketchat) Command(cmd string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Brocketchat) Connect() error {
|
func (b *Brocketchat) Connect() error {
|
||||||
b.Log.Info("Connecting webhooks")
|
flog.Info("Connecting webhooks")
|
||||||
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
b.mh = matterhook.New(b.Config.WebhookURL,
|
||||||
matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
|
||||||
DisableServer: true})
|
DisableServer: true})
|
||||||
b.rh = rockethook.New(b.GetString("WebhookURL"), rockethook.Config{BindAddress: b.GetString("WebhookBindAddress")})
|
b.rh = rockethook.New(b.Config.WebhookURL, rockethook.Config{BindAddress: b.Config.WebhookBindAddress})
|
||||||
go b.handleRocketHook()
|
go b.handleRocketHook()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -50,32 +56,15 @@ func (b *Brocketchat) Send(msg config.Message) (string, error) {
|
|||||||
if msg.Event == config.EVENT_MSG_DELETE {
|
if msg.Event == config.EVENT_MSG_DELETE {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
b.Log.Debugf("=> Receiving %#v", msg)
|
flog.Debugf("Receiving %#v", msg)
|
||||||
if msg.Extra != nil {
|
matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL}
|
||||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
|
||||||
iconURL := config.GetIconURL(&rmsg, b.GetString("iconurl"))
|
|
||||||
matterMessage := matterhook.OMessage{IconURL: iconURL, Channel: rmsg.Channel, UserName: rmsg.Username, Text: rmsg.Text}
|
|
||||||
b.mh.Send(matterMessage)
|
|
||||||
}
|
|
||||||
if len(msg.Extra["file"]) > 0 {
|
|
||||||
for _, f := range msg.Extra["file"] {
|
|
||||||
fi := f.(config.FileInfo)
|
|
||||||
if fi.URL != "" {
|
|
||||||
msg.Text += fi.URL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iconURL := config.GetIconURL(&msg, b.GetString("iconurl"))
|
|
||||||
matterMessage := matterhook.OMessage{IconURL: iconURL}
|
|
||||||
matterMessage.Channel = msg.Channel
|
matterMessage.Channel = msg.Channel
|
||||||
matterMessage.UserName = msg.Username
|
matterMessage.UserName = msg.Username
|
||||||
matterMessage.Type = ""
|
matterMessage.Type = ""
|
||||||
matterMessage.Text = msg.Text
|
matterMessage.Text = msg.Text
|
||||||
err := b.mh.Send(matterMessage)
|
err := b.mh.Send(matterMessage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Info(err)
|
flog.Info(err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return "", nil
|
return "", nil
|
||||||
@ -84,12 +73,12 @@ func (b *Brocketchat) Send(msg config.Message) (string, error) {
|
|||||||
func (b *Brocketchat) handleRocketHook() {
|
func (b *Brocketchat) handleRocketHook() {
|
||||||
for {
|
for {
|
||||||
message := b.rh.Receive()
|
message := b.rh.Receive()
|
||||||
b.Log.Debugf("Receiving from rockethook %#v", message)
|
flog.Debugf("Receiving from rockethook %#v", message)
|
||||||
// do not loop
|
// do not loop
|
||||||
if message.UserName == b.GetString("Nick") {
|
if message.UserName == b.Config.Nick {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", message.UserName, b.Account)
|
flog.Debugf("Sending message from %s on %s to gateway", message.UserName, b.Account)
|
||||||
b.Remote <- config.Message{Text: message.Text, Username: message.UserName, Channel: message.ChannelName, Account: b.Account, UserID: message.UserID}
|
b.Remote <- config.Message{Text: message.Text, Username: message.UserName, Channel: message.ChannelName, Account: b.Account, UserID: message.UserID}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,37 +4,46 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
|
"github.com/42wim/matterbridge/matterhook"
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/matterbridge/slack"
|
||||||
"html"
|
"html"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/42wim/matterbridge/bridge"
|
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
|
||||||
"github.com/42wim/matterbridge/matterhook"
|
|
||||||
"github.com/nlopes/slack"
|
|
||||||
"github.com/rs/xid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Bslack struct {
|
type MMMessage struct {
|
||||||
mh *matterhook.Client
|
Text string
|
||||||
sc *slack.Client
|
Channel string
|
||||||
rtm *slack.RTM
|
Username string
|
||||||
Users []slack.User
|
UserID string
|
||||||
Usergroups []slack.UserGroup
|
Raw *slack.MessageEvent
|
||||||
si *slack.Info
|
|
||||||
channels []slack.Channel
|
|
||||||
uuid string
|
|
||||||
*bridge.Config
|
|
||||||
sync.RWMutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageDeleted = "message_deleted"
|
type Bslack struct {
|
||||||
|
mh *matterhook.Client
|
||||||
|
sc *slack.Client
|
||||||
|
rtm *slack.RTM
|
||||||
|
Plus bool
|
||||||
|
Users []slack.User
|
||||||
|
si *slack.Info
|
||||||
|
channels []slack.Channel
|
||||||
|
*config.BridgeConfig
|
||||||
|
}
|
||||||
|
|
||||||
func New(cfg *bridge.Config) bridge.Bridger {
|
var flog *log.Entry
|
||||||
return &Bslack{Config: cfg, uuid: xid.New().String()}
|
var protocol = "slack"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flog = log.WithFields(log.Fields{"module": protocol})
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg *config.BridgeConfig) *Bslack {
|
||||||
|
return &Bslack{BridgeConfig: cfg}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bslack) Command(cmd string) string {
|
func (b *Bslack) Command(cmd string) string {
|
||||||
@ -42,76 +51,71 @@ func (b *Bslack) Command(cmd string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bslack) Connect() error {
|
func (b *Bslack) Connect() error {
|
||||||
b.RLock()
|
if b.Config.WebhookBindAddress != "" {
|
||||||
defer b.RUnlock()
|
if b.Config.WebhookURL != "" {
|
||||||
if b.GetString("WebhookBindAddress") != "" {
|
flog.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)")
|
||||||
if b.GetString("WebhookURL") != "" {
|
b.mh = matterhook.New(b.Config.WebhookURL,
|
||||||
b.Log.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)")
|
matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
|
||||||
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
BindAddress: b.Config.WebhookBindAddress})
|
||||||
matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
} else if b.Config.Token != "" {
|
||||||
BindAddress: b.GetString("WebhookBindAddress")})
|
flog.Info("Connecting using token (sending)")
|
||||||
} else if b.GetString("Token") != "" {
|
b.sc = slack.New(b.Config.Token)
|
||||||
b.Log.Info("Connecting using token (sending)")
|
|
||||||
b.sc = slack.New(b.GetString("Token"))
|
|
||||||
b.rtm = b.sc.NewRTM()
|
b.rtm = b.sc.NewRTM()
|
||||||
go b.rtm.ManageConnection()
|
go b.rtm.ManageConnection()
|
||||||
b.Log.Info("Connecting using webhookbindaddress (receiving)")
|
flog.Info("Connecting using webhookbindaddress (receiving)")
|
||||||
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
b.mh = matterhook.New(b.Config.WebhookURL,
|
||||||
matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
|
||||||
BindAddress: b.GetString("WebhookBindAddress")})
|
BindAddress: b.Config.WebhookBindAddress})
|
||||||
} else {
|
} else {
|
||||||
b.Log.Info("Connecting using webhookbindaddress (receiving)")
|
flog.Info("Connecting using webhookbindaddress (receiving)")
|
||||||
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
b.mh = matterhook.New(b.Config.WebhookURL,
|
||||||
matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
|
||||||
BindAddress: b.GetString("WebhookBindAddress")})
|
BindAddress: b.Config.WebhookBindAddress})
|
||||||
}
|
}
|
||||||
go b.handleSlack()
|
go b.handleSlack()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if b.GetString("WebhookURL") != "" {
|
if b.Config.WebhookURL != "" {
|
||||||
b.Log.Info("Connecting using webhookurl (sending)")
|
flog.Info("Connecting using webhookurl (sending)")
|
||||||
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
b.mh = matterhook.New(b.Config.WebhookURL,
|
||||||
matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
|
||||||
DisableServer: true})
|
DisableServer: true})
|
||||||
if b.GetString("Token") != "" {
|
if b.Config.Token != "" {
|
||||||
b.Log.Info("Connecting using token (receiving)")
|
flog.Info("Connecting using token (receiving)")
|
||||||
b.sc = slack.New(b.GetString("Token"))
|
b.sc = slack.New(b.Config.Token)
|
||||||
b.rtm = b.sc.NewRTM()
|
b.rtm = b.sc.NewRTM()
|
||||||
go b.rtm.ManageConnection()
|
go b.rtm.ManageConnection()
|
||||||
go b.handleSlack()
|
go b.handleSlack()
|
||||||
}
|
}
|
||||||
} else if b.GetString("Token") != "" {
|
} else if b.Config.Token != "" {
|
||||||
b.Log.Info("Connecting using token (sending and receiving)")
|
flog.Info("Connecting using token (sending and receiving)")
|
||||||
b.sc = slack.New(b.GetString("Token"))
|
b.sc = slack.New(b.Config.Token)
|
||||||
b.rtm = b.sc.NewRTM()
|
b.rtm = b.sc.NewRTM()
|
||||||
go b.rtm.ManageConnection()
|
go b.rtm.ManageConnection()
|
||||||
go b.handleSlack()
|
go b.handleSlack()
|
||||||
}
|
}
|
||||||
if b.GetString("WebhookBindAddress") == "" && b.GetString("WebhookURL") == "" && b.GetString("Token") == "" {
|
if b.Config.WebhookBindAddress == "" && b.Config.WebhookURL == "" && b.Config.Token == "" {
|
||||||
return errors.New("no connection method found. See that you have WebhookBindAddress, WebhookURL or Token configured")
|
return errors.New("No connection method found. See that you have WebhookBindAddress, WebhookURL or Token configured.")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bslack) Disconnect() error {
|
func (b *Bslack) Disconnect() error {
|
||||||
return b.rtm.Disconnect()
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bslack) JoinChannel(channel config.ChannelInfo) error {
|
func (b *Bslack) JoinChannel(channel config.ChannelInfo) error {
|
||||||
// we can only join channels using the API
|
// we can only join channels using the API
|
||||||
if b.sc != nil {
|
if b.Config.WebhookURL == "" && b.Config.WebhookBindAddress == "" {
|
||||||
if strings.HasPrefix(b.GetString("Token"), "xoxb") {
|
if strings.HasPrefix(b.Config.Token, "xoxb") {
|
||||||
// TODO check if bot has already joined channel
|
// TODO check if bot has already joined channel
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
_, err := b.sc.JoinChannel(channel.Name)
|
_, err := b.sc.JoinChannel(channel.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err.Error() {
|
if err.Error() != "name_taken" {
|
||||||
case "name_taken", "restricted_action":
|
return err
|
||||||
case "default":
|
|
||||||
{
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,25 +123,48 @@ func (b *Bslack) JoinChannel(channel config.ChannelInfo) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bslack) Send(msg config.Message) (string, error) {
|
func (b *Bslack) Send(msg config.Message) (string, error) {
|
||||||
b.Log.Debugf("=> Receiving %#v", msg)
|
flog.Debugf("Receiving %#v", msg)
|
||||||
|
|
||||||
// Make a action /me of the message
|
|
||||||
if msg.Event == config.EVENT_USER_ACTION {
|
if msg.Event == config.EVENT_USER_ACTION {
|
||||||
msg.Text = "_" + msg.Text + "_"
|
msg.Text = "_" + msg.Text + "_"
|
||||||
}
|
}
|
||||||
|
nick := msg.Username
|
||||||
// Use webhook to send the message
|
message := msg.Text
|
||||||
if b.GetString("WebhookURL") != "" {
|
channel := msg.Channel
|
||||||
return b.sendWebhook(msg)
|
if b.Config.PrefixMessagesWithNick {
|
||||||
|
message = nick + " " + message
|
||||||
}
|
}
|
||||||
|
if b.Config.WebhookURL != "" {
|
||||||
// get the slack channel
|
matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL}
|
||||||
schannel, err := b.getChannelByName(msg.Channel)
|
matterMessage.Channel = channel
|
||||||
|
matterMessage.UserName = nick
|
||||||
|
matterMessage.Type = ""
|
||||||
|
matterMessage.Text = message
|
||||||
|
err := b.mh.Send(matterMessage)
|
||||||
|
if err != nil {
|
||||||
|
flog.Info(err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
schannel, err := b.getChannelByName(channel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
np := slack.NewPostMessageParameters()
|
||||||
|
if b.Config.PrefixMessagesWithNick {
|
||||||
|
np.AsUser = true
|
||||||
|
}
|
||||||
|
np.Username = nick
|
||||||
|
np.IconURL = config.GetIconURL(&msg, &b.Config)
|
||||||
|
if msg.Avatar != "" {
|
||||||
|
np.IconURL = msg.Avatar
|
||||||
|
}
|
||||||
|
np.Attachments = append(np.Attachments, slack.Attachment{CallbackID: "matterbridge"})
|
||||||
|
np.Attachments = append(np.Attachments, b.createAttach(msg.Extra)...)
|
||||||
|
|
||||||
|
// replace mentions
|
||||||
|
np.LinkNames = 1
|
||||||
|
|
||||||
// Delete message
|
|
||||||
if msg.Event == config.EVENT_MSG_DELETE {
|
if msg.Event == config.EVENT_MSG_DELETE {
|
||||||
// some protocols echo deletes, but with empty ID
|
// some protocols echo deletes, but with empty ID
|
||||||
if msg.ID == "" {
|
if msg.ID == "" {
|
||||||
@ -145,73 +172,42 @@ func (b *Bslack) Send(msg config.Message) (string, error) {
|
|||||||
}
|
}
|
||||||
// we get a "slack <ID>", split it
|
// we get a "slack <ID>", split it
|
||||||
ts := strings.Fields(msg.ID)
|
ts := strings.Fields(msg.ID)
|
||||||
_, _, err := b.sc.DeleteMessage(schannel.ID, ts[1])
|
b.sc.DeleteMessage(schannel.ID, ts[1])
|
||||||
if err != nil {
|
return "", nil
|
||||||
return msg.ID, err
|
|
||||||
}
|
|
||||||
return msg.ID, nil
|
|
||||||
}
|
}
|
||||||
|
// if we have no ID it means we're creating a new message, not updating an existing one
|
||||||
// Prepend nick if configured
|
|
||||||
if b.GetBool("PrefixMessagesWithNick") {
|
|
||||||
msg.Text = msg.Username + msg.Text
|
|
||||||
}
|
|
||||||
|
|
||||||
// Edit message if we have an ID
|
|
||||||
if msg.ID != "" {
|
if msg.ID != "" {
|
||||||
ts := strings.Fields(msg.ID)
|
ts := strings.Fields(msg.ID)
|
||||||
_, _, _, err := b.sc.UpdateMessage(schannel.ID, ts[1], msg.Text)
|
b.sc.UpdateMessage(schannel.ID, ts[1], message)
|
||||||
if err != nil {
|
return "", nil
|
||||||
return msg.ID, err
|
|
||||||
}
|
|
||||||
return msg.ID, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// create slack new post parameters
|
|
||||||
np := slack.NewPostMessageParameters()
|
|
||||||
if b.GetBool("PrefixMessagesWithNick") {
|
|
||||||
np.AsUser = true
|
|
||||||
}
|
|
||||||
np.Username = msg.Username
|
|
||||||
np.LinkNames = 1 // replace mentions
|
|
||||||
np.IconURL = config.GetIconURL(&msg, b.GetString("iconurl"))
|
|
||||||
if msg.Avatar != "" {
|
|
||||||
np.IconURL = msg.Avatar
|
|
||||||
}
|
|
||||||
// add a callback ID so we can see we created it
|
|
||||||
np.Attachments = append(np.Attachments, slack.Attachment{CallbackID: "matterbridge_" + b.uuid})
|
|
||||||
// add file attachments
|
|
||||||
np.Attachments = append(np.Attachments, b.createAttach(msg.Extra)...)
|
|
||||||
// add slack attachments (from another slack bridge)
|
|
||||||
if len(msg.Extra["slack_attachment"]) > 0 {
|
|
||||||
for _, attach := range msg.Extra["slack_attachment"] {
|
|
||||||
np.Attachments = append(np.Attachments, attach.([]slack.Attachment)...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload a file if it exists
|
|
||||||
if msg.Extra != nil {
|
if msg.Extra != nil {
|
||||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
|
||||||
b.sc.PostMessage(schannel.ID, rmsg.Username+rmsg.Text, np)
|
|
||||||
}
|
|
||||||
// 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, schannel.ID)
|
var err error
|
||||||
|
for _, f := range msg.Extra["file"] {
|
||||||
|
fi := f.(config.FileInfo)
|
||||||
|
_, err = b.sc.UploadFile(slack.FileUploadParameters{
|
||||||
|
Reader: bytes.NewReader(*fi.Data),
|
||||||
|
Filename: fi.Name,
|
||||||
|
Channels: []string{schannel.ID},
|
||||||
|
InitialComment: fi.Comment,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
flog.Errorf("uploadfile %#v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post normal message
|
_, id, err := b.sc.PostMessage(schannel.ID, message, np)
|
||||||
_, id, err := b.sc.PostMessage(schannel.ID, msg.Text, np)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return "slack " + id, nil
|
return "slack " + id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bslack) Reload(cfg *bridge.Config) (string, error) {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bslack) getAvatar(user string) string {
|
func (b *Bslack) getAvatar(user string) string {
|
||||||
var avatar string
|
var avatar string
|
||||||
if b.Users != nil {
|
if b.Users != nil {
|
||||||
@ -249,61 +245,145 @@ func (b *Bslack) getChannelByID(ID string) (*slack.Channel, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bslack) handleSlack() {
|
func (b *Bslack) handleSlack() {
|
||||||
messages := make(chan *config.Message)
|
mchan := make(chan *MMMessage)
|
||||||
if b.GetString("WebhookBindAddress") != "" {
|
if b.Config.WebhookBindAddress != "" {
|
||||||
b.Log.Debugf("Choosing webhooks based receiving")
|
flog.Debugf("Choosing webhooks based receiving")
|
||||||
go b.handleMatterHook(messages)
|
go b.handleMatterHook(mchan)
|
||||||
} else {
|
} else {
|
||||||
b.Log.Debugf("Choosing token based receiving")
|
flog.Debugf("Choosing token based receiving")
|
||||||
go b.handleSlackClient(messages)
|
go b.handleSlackClient(mchan)
|
||||||
}
|
}
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
b.Log.Debug("Start listening for Slack messages")
|
flog.Debug("Start listening for Slack messages")
|
||||||
for message := range messages {
|
for message := range mchan {
|
||||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", message.Username, b.Account)
|
// do not send messages from ourself
|
||||||
|
if b.Config.WebhookURL == "" && b.Config.WebhookBindAddress == "" && message.Username == b.si.User.Name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (message.Text == "" || message.Username == "") && message.Raw.SubType != "message_deleted" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
text := message.Text
|
||||||
|
text = b.replaceURL(text)
|
||||||
|
text = html.UnescapeString(text)
|
||||||
|
flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.Account)
|
||||||
|
msg := config.Message{Text: text, Username: message.Username, Channel: message.Channel, Account: b.Account, Avatar: b.getAvatar(message.Username), UserID: message.UserID, ID: "slack " + message.Raw.Timestamp, Extra: make(map[string][]interface{})}
|
||||||
|
if message.Raw.SubType == "me_message" {
|
||||||
|
msg.Event = config.EVENT_USER_ACTION
|
||||||
|
}
|
||||||
|
if message.Raw.SubType == "channel_leave" || message.Raw.SubType == "channel_join" {
|
||||||
|
msg.Username = "system"
|
||||||
|
msg.Event = config.EVENT_JOIN_LEAVE
|
||||||
|
}
|
||||||
|
// edited messages have a submessage, use this timestamp
|
||||||
|
if message.Raw.SubMessage != nil {
|
||||||
|
msg.ID = "slack " + message.Raw.SubMessage.Timestamp
|
||||||
|
}
|
||||||
|
if message.Raw.SubType == "message_deleted" {
|
||||||
|
msg.Text = config.EVENT_MSG_DELETE
|
||||||
|
msg.Event = config.EVENT_MSG_DELETE
|
||||||
|
msg.ID = "slack " + message.Raw.DeletedTimestamp
|
||||||
|
}
|
||||||
|
|
||||||
// cleanup the message
|
// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra
|
||||||
message.Text = b.replaceMention(message.Text)
|
if message.Raw.File != nil {
|
||||||
message.Text = b.replaceVariable(message.Text)
|
// limit to 1MB for now
|
||||||
message.Text = b.replaceChannel(message.Text)
|
if message.Raw.File.Size <= b.General.MediaDownloadSize {
|
||||||
message.Text = b.replaceURL(message.Text)
|
comment := ""
|
||||||
message.Text = html.UnescapeString(message.Text)
|
data, err := b.downloadFile(message.Raw.File.URLPrivateDownload)
|
||||||
|
if err != nil {
|
||||||
// Add the avatar
|
flog.Errorf("download %s failed %#v", message.Raw.File.URLPrivateDownload, err)
|
||||||
message.Avatar = b.getAvatar(strings.ToLower(message.Username))
|
} else {
|
||||||
|
results := regexp.MustCompile(`.*?commented: (.*)`).FindAllStringSubmatch(msg.Text, -1)
|
||||||
b.Log.Debugf("<= Message is %#v", message)
|
if len(results) > 0 {
|
||||||
b.Remote <- *message
|
comment = results[0][1]
|
||||||
|
}
|
||||||
|
msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{Name: message.Raw.File.Name, Data: data, Comment: comment})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flog.Debugf("Message is %#v", msg)
|
||||||
|
b.Remote <- msg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bslack) handleSlackClient(messages chan *config.Message) {
|
func (b *Bslack) handleSlackClient(mchan chan *MMMessage) {
|
||||||
for msg := range b.rtm.IncomingEvents {
|
for msg := range b.rtm.IncomingEvents {
|
||||||
if msg.Type != "user_typing" && msg.Type != "latency_report" {
|
|
||||||
b.Log.Debugf("== Receiving event %#v", msg.Data)
|
|
||||||
}
|
|
||||||
switch ev := msg.Data.(type) {
|
switch ev := msg.Data.(type) {
|
||||||
case *slack.MessageEvent:
|
case *slack.MessageEvent:
|
||||||
if b.skipMessageEvent(ev) {
|
flog.Debugf("Receiving from slackclient %#v", ev)
|
||||||
b.Log.Debugf("Skipped message: %#v", ev)
|
if len(ev.Attachments) > 0 {
|
||||||
continue
|
// skip messages we made ourselves
|
||||||
|
if ev.Attachments[0].CallbackID == "matterbridge" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
rmsg, err := b.handleMessageEvent(ev)
|
if !b.Config.EditDisable && ev.SubMessage != nil && ev.SubMessage.ThreadTimestamp != ev.SubMessage.Timestamp {
|
||||||
|
flog.Debugf("SubMessage %#v", ev.SubMessage)
|
||||||
|
ev.User = ev.SubMessage.User
|
||||||
|
ev.Text = ev.SubMessage.Text + b.Config.EditSuffix
|
||||||
|
|
||||||
|
// it seems ev.SubMessage.Edited == nil when slack unfurls
|
||||||
|
// do not forward these messages #266
|
||||||
|
if ev.SubMessage.Edited == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// use our own func because rtm.GetChannelInfo doesn't work for private channels
|
||||||
|
channel, err := b.getChannelByID(ev.Channel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Errorf("%#v", err)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
messages <- rmsg
|
m := &MMMessage{}
|
||||||
|
if ev.BotID == "" && ev.SubType != "message_deleted" {
|
||||||
|
user, err := b.rtm.GetUserInfo(ev.User)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m.UserID = user.ID
|
||||||
|
m.Username = user.Name
|
||||||
|
if user.Profile.DisplayName != "" {
|
||||||
|
m.Username = user.Profile.DisplayName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.Channel = channel.Name
|
||||||
|
m.Text = ev.Text
|
||||||
|
if m.Text == "" {
|
||||||
|
for _, attach := range ev.Attachments {
|
||||||
|
if attach.Text != "" {
|
||||||
|
m.Text = attach.Text
|
||||||
|
} else {
|
||||||
|
m.Text = attach.Fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.Raw = ev
|
||||||
|
m.Text = b.replaceMention(m.Text)
|
||||||
|
m.Text = b.replaceVariable(m.Text)
|
||||||
|
m.Text = b.replaceChannel(m.Text)
|
||||||
|
// when using webhookURL we can't check if it's our webhook or not for now
|
||||||
|
if ev.BotID != "" && b.Config.WebhookURL == "" {
|
||||||
|
bot, err := b.rtm.GetBotInfo(ev.BotID)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if bot.Name != "" {
|
||||||
|
m.Username = bot.Name
|
||||||
|
if ev.Username != "" {
|
||||||
|
m.Username = ev.Username
|
||||||
|
}
|
||||||
|
m.UserID = bot.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mchan <- m
|
||||||
case *slack.OutgoingErrorEvent:
|
case *slack.OutgoingErrorEvent:
|
||||||
b.Log.Debugf("%#v", ev.Error())
|
flog.Debugf("%#v", ev.Error())
|
||||||
case *slack.ChannelJoinedEvent:
|
case *slack.ChannelJoinedEvent:
|
||||||
b.Users, _ = b.sc.GetUsers()
|
b.Users, _ = b.sc.GetUsers()
|
||||||
b.Usergroups, _ = b.sc.GetUserGroups()
|
|
||||||
case *slack.ConnectedEvent:
|
case *slack.ConnectedEvent:
|
||||||
b.channels = ev.Info.Channels
|
b.channels = ev.Info.Channels
|
||||||
b.si = ev.Info
|
b.si = ev.Info
|
||||||
b.Users, _ = b.sc.GetUsers()
|
b.Users, _ = b.sc.GetUsers()
|
||||||
b.Usergroups, _ = b.sc.GetUserGroups()
|
|
||||||
// add private channels
|
// add private channels
|
||||||
groups, _ := b.sc.GetGroups(true)
|
groups, _ := b.sc.GetGroups(true)
|
||||||
for _, g := range groups {
|
for _, g := range groups {
|
||||||
@ -313,22 +393,27 @@ func (b *Bslack) handleSlackClient(messages chan *config.Message) {
|
|||||||
b.channels = append(b.channels, *channel)
|
b.channels = append(b.channels, *channel)
|
||||||
}
|
}
|
||||||
case *slack.InvalidAuthEvent:
|
case *slack.InvalidAuthEvent:
|
||||||
b.Log.Fatalf("Invalid Token %#v", ev)
|
flog.Fatalf("Invalid Token %#v", ev)
|
||||||
case *slack.ConnectionErrorEvent:
|
|
||||||
b.Log.Errorf("Connection failed %#v %#v", ev.Error(), ev.ErrorObj)
|
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bslack) handleMatterHook(messages chan *config.Message) {
|
func (b *Bslack) handleMatterHook(mchan chan *MMMessage) {
|
||||||
for {
|
for {
|
||||||
message := b.mh.Receive()
|
message := b.mh.Receive()
|
||||||
b.Log.Debugf("receiving from matterhook (slack) %#v", message)
|
flog.Debugf("receiving from matterhook (slack) %#v", message)
|
||||||
if message.UserName == "slackbot" {
|
m := &MMMessage{}
|
||||||
|
m.Username = message.UserName
|
||||||
|
m.Text = message.Text
|
||||||
|
m.Text = b.replaceMention(m.Text)
|
||||||
|
m.Text = b.replaceVariable(m.Text)
|
||||||
|
m.Text = b.replaceChannel(m.Text)
|
||||||
|
m.Channel = message.ChannelName
|
||||||
|
if m.Username == "slackbot" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
messages <- &config.Message{Username: message.UserName, Text: message.Text, Channel: message.ChannelName}
|
mchan <- m
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,17 +429,6 @@ func (b *Bslack) userName(id string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
func (b *Bslack) userGroupName(id string) string {
|
|
||||||
for _, u := range b.Usergroups {
|
|
||||||
if u.ID == id {
|
|
||||||
return u.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// @see https://api.slack.com/docs/message-formatting#linking_to_channels_and_users
|
// @see https://api.slack.com/docs/message-formatting#linking_to_channels_and_users
|
||||||
func (b *Bslack) replaceMention(text string) string {
|
func (b *Bslack) replaceMention(text string) string {
|
||||||
results := regexp.MustCompile(`<@([a-zA-z0-9]+)>`).FindAllStringSubmatch(text, -1)
|
results := regexp.MustCompile(`<@([a-zA-z0-9]+)>`).FindAllStringSubmatch(text, -1)
|
||||||
@ -375,13 +449,9 @@ func (b *Bslack) replaceChannel(text string) string {
|
|||||||
|
|
||||||
// @see https://api.slack.com/docs/message-formatting#variables
|
// @see https://api.slack.com/docs/message-formatting#variables
|
||||||
func (b *Bslack) replaceVariable(text string) string {
|
func (b *Bslack) replaceVariable(text string) string {
|
||||||
results := regexp.MustCompile(`<!((?:subteam\^)?[a-zA-Z0-9]+)(?:\|@?(.+?))?>`).FindAllStringSubmatch(text, -1)
|
results := regexp.MustCompile(`<!([a-zA-Z0-9]+)(\|.+?)?>`).FindAllStringSubmatch(text, -1)
|
||||||
for _, r := range results {
|
for _, r := range results {
|
||||||
if r[2] != "" {
|
text = strings.Replace(text, r[0], "@"+r[1], -1)
|
||||||
text = strings.Replace(text, r[0], "@"+r[2], -1)
|
|
||||||
} else {
|
|
||||||
text = strings.Replace(text, r[0], "@"+r[1], -1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
@ -390,11 +460,7 @@ func (b *Bslack) replaceVariable(text string) string {
|
|||||||
func (b *Bslack) replaceURL(text string) string {
|
func (b *Bslack) replaceURL(text string) string {
|
||||||
results := regexp.MustCompile(`<(.*?)(\|.*?)?>`).FindAllStringSubmatch(text, -1)
|
results := regexp.MustCompile(`<(.*?)(\|.*?)?>`).FindAllStringSubmatch(text, -1)
|
||||||
for _, r := range results {
|
for _, r := range results {
|
||||||
if len(strings.TrimSpace(r[2])) == 1 { // A display text separator was found, but the text was blank
|
text = strings.Replace(text, r[0], r[1], -1)
|
||||||
text = strings.Replace(text, r[0], "", -1)
|
|
||||||
} else {
|
|
||||||
text = strings.Replace(text, r[0], r[1], -1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
@ -422,260 +488,23 @@ func (b *Bslack) createAttach(extra map[string][]interface{}) []slack.Attachment
|
|||||||
return attachs
|
return attachs
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleDownloadFile handles file download
|
func (b *Bslack) downloadFile(url string) (*[]byte, error) {
|
||||||
func (b *Bslack) handleDownloadFile(rmsg *config.Message, file *slack.File) error {
|
var buf bytes.Buffer
|
||||||
// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra
|
client := &http.Client{
|
||||||
// limit to 1MB for now
|
Timeout: time.Second * 5,
|
||||||
comment := ""
|
|
||||||
results := regexp.MustCompile(`.*?commented: (.*)`).FindAllStringSubmatch(rmsg.Text, -1)
|
|
||||||
if len(results) > 0 {
|
|
||||||
comment = results[0][1]
|
|
||||||
}
|
}
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
err := helper.HandleDownloadSize(b.Log, rmsg, file.Name, int64(file.Size), b.General)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// actually download the file
|
|
||||||
data, err := helper.DownloadFileAuth(file.URLPrivateDownload, "Bearer "+b.GetString("Token"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("download %s failed %#v", file.URLPrivateDownload, err)
|
|
||||||
}
|
|
||||||
// add the downloaded data to the message
|
|
||||||
helper.HandleDownloadData(b.Log, rmsg, file.Name, comment, file.URLPrivateDownload, data, b.General)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleUploadFile handles native upload of files
|
|
||||||
func (b *Bslack) handleUploadFile(msg *config.Message, channelID string) (string, error) {
|
|
||||||
var err error
|
|
||||||
for _, f := range msg.Extra["file"] {
|
|
||||||
fi := f.(config.FileInfo)
|
|
||||||
_, err = b.sc.UploadFile(slack.FileUploadParameters{
|
|
||||||
Reader: bytes.NewReader(*fi.Data),
|
|
||||||
Filename: fi.Name,
|
|
||||||
Channels: []string{channelID},
|
|
||||||
InitialComment: fi.Comment,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("uploadfile %#v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleMessageEvent handles the message events
|
|
||||||
func (b *Bslack) handleMessageEvent(ev *slack.MessageEvent) (*config.Message, error) {
|
|
||||||
// update the userlist on a channel_join
|
|
||||||
if ev.SubType == "channel_join" {
|
|
||||||
b.Users, _ = b.sc.GetUsers()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Edit message
|
|
||||||
if !b.GetBool("EditDisable") && ev.SubMessage != nil && ev.SubMessage.ThreadTimestamp != ev.SubMessage.Timestamp {
|
|
||||||
b.Log.Debugf("SubMessage %#v", ev.SubMessage)
|
|
||||||
ev.User = ev.SubMessage.User
|
|
||||||
ev.Text = ev.SubMessage.Text + b.GetString("EditSuffix")
|
|
||||||
}
|
|
||||||
|
|
||||||
// use our own func because rtm.GetChannelInfo doesn't work for private channels
|
|
||||||
channel, err := b.getChannelByID(ev.Channel)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
req.Header.Add("Authorization", "Bearer "+b.Config.Token)
|
||||||
rmsg := config.Message{Text: ev.Text, Channel: channel.Name, Account: b.Account, ID: "slack " + ev.Timestamp, Extra: make(map[string][]interface{})}
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
// find the user id and name
|
|
||||||
if ev.User != "" && ev.SubType != messageDeleted && ev.SubType != "file_comment" {
|
|
||||||
user, err := b.rtm.GetUserInfo(ev.User)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rmsg.UserID = user.ID
|
|
||||||
rmsg.Username = user.Name
|
|
||||||
if user.Profile.DisplayName != "" {
|
|
||||||
rmsg.Username = user.Profile.DisplayName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// See if we have some text in the attachments
|
|
||||||
if rmsg.Text == "" {
|
|
||||||
for _, attach := range ev.Attachments {
|
|
||||||
if attach.Text != "" {
|
|
||||||
rmsg.Text = attach.Text
|
|
||||||
} else {
|
|
||||||
rmsg.Text = attach.Fallback
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// when using webhookURL we can't check if it's our webhook or not for now
|
|
||||||
if rmsg.Username == "" && ev.BotID != "" && b.GetString("WebhookURL") == "" {
|
|
||||||
bot, err := b.rtm.GetBotInfo(ev.BotID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if bot.Name != "" {
|
|
||||||
rmsg.Username = bot.Name
|
|
||||||
if ev.Username != "" {
|
|
||||||
rmsg.Username = ev.Username
|
|
||||||
}
|
|
||||||
rmsg.UserID = bot.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
// fixes issues with matterircd users
|
|
||||||
if bot.Name == "Slack API Tester" {
|
|
||||||
user, err := b.rtm.GetUserInfo(ev.User)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rmsg.UserID = user.ID
|
|
||||||
rmsg.Username = user.Name
|
|
||||||
if user.Profile.DisplayName != "" {
|
|
||||||
rmsg.Username = user.Profile.DisplayName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// file comments are set by the system (because there is no username given)
|
|
||||||
if ev.SubType == "file_comment" {
|
|
||||||
rmsg.Username = "system"
|
|
||||||
}
|
|
||||||
|
|
||||||
// do we have a /me action
|
|
||||||
if ev.SubType == "me_message" {
|
|
||||||
rmsg.Event = config.EVENT_USER_ACTION
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle join/leave
|
|
||||||
if ev.SubType == "channel_leave" || ev.SubType == "channel_join" {
|
|
||||||
rmsg.Username = "system"
|
|
||||||
rmsg.Event = config.EVENT_JOIN_LEAVE
|
|
||||||
}
|
|
||||||
|
|
||||||
// edited messages have a submessage, use this timestamp
|
|
||||||
if ev.SubMessage != nil {
|
|
||||||
rmsg.ID = "slack " + ev.SubMessage.Timestamp
|
|
||||||
}
|
|
||||||
|
|
||||||
// deleted message event
|
|
||||||
if ev.SubType == messageDeleted {
|
|
||||||
rmsg.Text = config.EVENT_MSG_DELETE
|
|
||||||
rmsg.Event = config.EVENT_MSG_DELETE
|
|
||||||
rmsg.ID = "slack " + ev.DeletedTimestamp
|
|
||||||
}
|
|
||||||
|
|
||||||
// topic change event
|
|
||||||
if ev.SubType == "channel_topic" || ev.SubType == "channel_purpose" {
|
|
||||||
rmsg.Event = config.EVENT_TOPIC_CHANGE
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only deleted messages can have a empty username and text
|
|
||||||
if (rmsg.Text == "" || rmsg.Username == "") && ev.SubType != messageDeleted {
|
|
||||||
// this is probably a webhook we couldn't resolve
|
|
||||||
if ev.BotID != "" {
|
|
||||||
return nil, fmt.Errorf("probably an incoming webhook we couldn't resolve (maybe ourselves)")
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("empty message and not a deleted message")
|
|
||||||
}
|
|
||||||
|
|
||||||
// save the attachments, so that we can send them to other slack (compatible) bridges
|
|
||||||
if len(ev.Attachments) > 0 {
|
|
||||||
rmsg.Extra["slack_attachment"] = append(rmsg.Extra["slack_attachment"], ev.Attachments)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra
|
|
||||||
if ev.File != nil {
|
|
||||||
err := b.handleDownloadFile(&rmsg, ev.File)
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("download failed: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &rmsg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendWebhook uses the configured WebhookURL to send the message
|
|
||||||
func (b *Bslack) sendWebhook(msg config.Message) (string, error) {
|
|
||||||
// skip events
|
|
||||||
if msg.Event != "" {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.GetBool("PrefixMessagesWithNick") {
|
|
||||||
msg.Text = msg.Username + msg.Text
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg.Extra != nil {
|
|
||||||
// this sends a message only if we received a config.EVENT_FILE_FAILURE_SIZE
|
|
||||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
|
||||||
iconURL := config.GetIconURL(&rmsg, b.GetString("iconurl"))
|
|
||||||
matterMessage := matterhook.OMessage{IconURL: iconURL, Channel: msg.Channel, UserName: rmsg.Username, Text: rmsg.Text}
|
|
||||||
b.mh.Send(matterMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
// webhook doesn't support file uploads, so we add the url manually
|
|
||||||
if len(msg.Extra["file"]) > 0 {
|
|
||||||
for _, f := range msg.Extra["file"] {
|
|
||||||
fi := f.(config.FileInfo)
|
|
||||||
if fi.URL != "" {
|
|
||||||
msg.Text += " " + fi.URL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we have native slack_attachments add them
|
|
||||||
var attachs []slack.Attachment
|
|
||||||
if len(msg.Extra["slack_attachment"]) > 0 {
|
|
||||||
for _, attach := range msg.Extra["slack_attachment"] {
|
|
||||||
attachs = append(attachs, attach.([]slack.Attachment)...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iconURL := config.GetIconURL(&msg, b.GetString("iconurl"))
|
|
||||||
matterMessage := matterhook.OMessage{IconURL: iconURL, Attachments: attachs, Channel: msg.Channel, UserName: msg.Username, Text: msg.Text}
|
|
||||||
if msg.Avatar != "" {
|
|
||||||
matterMessage.IconURL = msg.Avatar
|
|
||||||
}
|
|
||||||
err := b.mh.Send(matterMessage)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Error(err)
|
resp.Body.Close()
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
return "", nil
|
io.Copy(&buf, resp.Body)
|
||||||
}
|
data := buf.Bytes()
|
||||||
|
resp.Body.Close()
|
||||||
// skipMessageEvent skips event that need to be skipped :-)
|
return &data, nil
|
||||||
func (b *Bslack) skipMessageEvent(ev *slack.MessageEvent) bool {
|
|
||||||
if ev.SubType == "channel_leave" || ev.SubType == "channel_join" {
|
|
||||||
return b.GetBool("nosendjoinpart")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore pinned items
|
|
||||||
if ev.SubType == "pinned_item" || ev.SubType == "unpinned_item" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// do not send messages from ourself
|
|
||||||
if b.GetString("WebhookURL") == "" && b.GetString("WebhookBindAddress") == "" && ev.Username == b.si.User.Name {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip messages we made ourselves
|
|
||||||
if len(ev.Attachments) > 0 {
|
|
||||||
if ev.Attachments[0].CallbackID == "matterbridge_"+b.uuid {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !b.GetBool("EditDisable") && ev.SubMessage != nil && ev.SubMessage.ThreadTimestamp != ev.SubMessage.Timestamp {
|
|
||||||
// it seems ev.SubMessage.Edited == nil when slack unfurls
|
|
||||||
// do not forward these messages #266
|
|
||||||
if ev.SubMessage.Edited == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,9 @@ package bsshchat
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"github.com/42wim/matterbridge/bridge"
|
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/shazow/ssh-chat/sshd"
|
"github.com/shazow/ssh-chat/sshd"
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@ -14,18 +12,25 @@ import (
|
|||||||
type Bsshchat struct {
|
type Bsshchat struct {
|
||||||
r *bufio.Scanner
|
r *bufio.Scanner
|
||||||
w io.WriteCloser
|
w io.WriteCloser
|
||||||
*bridge.Config
|
*config.BridgeConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(cfg *bridge.Config) bridge.Bridger {
|
var flog *log.Entry
|
||||||
return &Bsshchat{Config: cfg}
|
var protocol = "sshchat"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flog = log.WithFields(log.Fields{"module": protocol})
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg *config.BridgeConfig) *Bsshchat {
|
||||||
|
return &Bsshchat{BridgeConfig: cfg}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bsshchat) Connect() error {
|
func (b *Bsshchat) Connect() error {
|
||||||
var err error
|
var err error
|
||||||
b.Log.Infof("Connecting %s", b.GetString("Server"))
|
flog.Infof("Connecting %s", b.Config.Server)
|
||||||
go func() {
|
go func() {
|
||||||
err = sshd.ConnectShell(b.GetString("Server"), b.GetString("Nick"), func(r io.Reader, w io.WriteCloser) error {
|
err = sshd.ConnectShell(b.Config.Server, b.Config.Nick, func(r io.Reader, w io.WriteCloser) error {
|
||||||
b.r = bufio.NewScanner(r)
|
b.r = bufio.NewScanner(r)
|
||||||
b.w = w
|
b.w = w
|
||||||
b.r.Scan()
|
b.r.Scan()
|
||||||
@ -35,10 +40,10 @@ func (b *Bsshchat) Connect() error {
|
|||||||
})
|
})
|
||||||
}()
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Debugf("%#v", err)
|
flog.Debugf("%#v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
b.Log.Info("Connection succeeded")
|
flog.Info("Connection succeeded")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,22 +60,13 @@ func (b *Bsshchat) Send(msg config.Message) (string, error) {
|
|||||||
if msg.Event == config.EVENT_MSG_DELETE {
|
if msg.Event == config.EVENT_MSG_DELETE {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
b.Log.Debugf("=> Receiving %#v", msg)
|
flog.Debugf("Receiving %#v", msg)
|
||||||
if msg.Extra != nil {
|
if msg.Extra != nil {
|
||||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
|
||||||
b.w.Write([]byte(rmsg.Username + rmsg.Text + "\r\n"))
|
|
||||||
}
|
|
||||||
if len(msg.Extra["file"]) > 0 {
|
if len(msg.Extra["file"]) > 0 {
|
||||||
for _, f := range msg.Extra["file"] {
|
for _, f := range msg.Extra["file"] {
|
||||||
fi := f.(config.FileInfo)
|
fi := f.(config.FileInfo)
|
||||||
if fi.Comment != "" {
|
|
||||||
msg.Text += fi.Comment + ": "
|
|
||||||
}
|
|
||||||
if fi.URL != "" {
|
if fi.URL != "" {
|
||||||
msg.Text = fi.URL
|
msg.Text = fi.URL
|
||||||
if fi.Comment != "" {
|
|
||||||
msg.Text = fi.Comment + ": " + fi.URL
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
b.w.Write([]byte(msg.Username + msg.Text))
|
b.w.Write([]byte(msg.Username + msg.Text))
|
||||||
}
|
}
|
||||||
@ -90,10 +86,10 @@ func (b *Bsshchat) sshchatKeepAlive() chan bool {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
b.Log.Debugf("PING")
|
flog.Debugf("PING")
|
||||||
err := b.xc.PingC2S("", "")
|
err := b.xc.PingC2S("", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Debugf("PING failed %#v", err)
|
flog.Debugf("PING failed %#v", err)
|
||||||
}
|
}
|
||||||
case <-done:
|
case <-done:
|
||||||
return
|
return
|
||||||
@ -127,7 +123,7 @@ func (b *Bsshchat) handleSshChat() error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !wait {
|
if !wait {
|
||||||
b.Log.Debugf("<= Message %#v", res)
|
flog.Debugf("message %#v", res)
|
||||||
rmsg := config.Message{Username: res[0], Text: strings.Join(res[1:], ":"), Channel: "sshchat", Account: b.Account, UserID: "nick"}
|
rmsg := config.Message{Username: res[0], Text: strings.Join(res[1:], ":"), Channel: "sshchat", Account: b.Account, UserID: "nick"}
|
||||||
b.Remote <- rmsg
|
b.Remote <- rmsg
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,11 @@ package bsteam
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"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/Philipp15b/go-steam"
|
"github.com/Philipp15b/go-steam"
|
||||||
"github.com/Philipp15b/go-steam/protocol/steamlang"
|
"github.com/Philipp15b/go-steam/protocol/steamlang"
|
||||||
"github.com/Philipp15b/go-steam/steamid"
|
"github.com/Philipp15b/go-steam/steamid"
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
//"io/ioutil"
|
//"io/ioutil"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
@ -20,24 +18,31 @@ type Bsteam struct {
|
|||||||
connected chan struct{}
|
connected chan struct{}
|
||||||
userMap map[steamid.SteamId]string
|
userMap map[steamid.SteamId]string
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
*bridge.Config
|
*config.BridgeConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(cfg *bridge.Config) bridge.Bridger {
|
var flog *log.Entry
|
||||||
b := &Bsteam{Config: cfg}
|
var protocol = "steam"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flog = log.WithFields(log.Fields{"module": protocol})
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg *config.BridgeConfig) *Bsteam {
|
||||||
|
b := &Bsteam{BridgeConfig: cfg}
|
||||||
b.userMap = make(map[steamid.SteamId]string)
|
b.userMap = make(map[steamid.SteamId]string)
|
||||||
b.connected = make(chan struct{})
|
b.connected = make(chan struct{})
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bsteam) Connect() error {
|
func (b *Bsteam) Connect() error {
|
||||||
b.Log.Info("Connecting")
|
flog.Info("Connecting")
|
||||||
b.c = steam.NewClient()
|
b.c = steam.NewClient()
|
||||||
go b.handleEvents()
|
go b.handleEvents()
|
||||||
go b.c.Connect()
|
go b.c.Connect()
|
||||||
select {
|
select {
|
||||||
case <-b.connected:
|
case <-b.connected:
|
||||||
b.Log.Info("Connection succeeded")
|
flog.Info("Connection succeeded")
|
||||||
case <-time.After(time.Second * 30):
|
case <-time.After(time.Second * 30):
|
||||||
return fmt.Errorf("connection timed out")
|
return fmt.Errorf("connection timed out")
|
||||||
}
|
}
|
||||||
@ -68,30 +73,6 @@ func (b *Bsteam) Send(msg config.Message) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle files
|
|
||||||
if msg.Extra != nil {
|
|
||||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
|
||||||
b.c.Social.SendMessage(id, steamlang.EChatEntryType_ChatMsg, rmsg.Username+rmsg.Text)
|
|
||||||
}
|
|
||||||
if len(msg.Extra["file"]) > 0 {
|
|
||||||
for _, f := range msg.Extra["file"] {
|
|
||||||
fi := f.(config.FileInfo)
|
|
||||||
if fi.Comment != "" {
|
|
||||||
msg.Text += fi.Comment + ": "
|
|
||||||
}
|
|
||||||
if fi.URL != "" {
|
|
||||||
msg.Text = fi.URL
|
|
||||||
if fi.Comment != "" {
|
|
||||||
msg.Text = fi.Comment + ": " + fi.URL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b.c.Social.SendMessage(id, steamlang.EChatEntryType_ChatMsg, msg.Username+msg.Text)
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.c.Social.SendMessage(id, steamlang.EChatEntryType_ChatMsg, msg.Username+msg.Text)
|
b.c.Social.SendMessage(id, steamlang.EChatEntryType_ChatMsg, msg.Username+msg.Text)
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
@ -107,18 +88,18 @@ func (b *Bsteam) getNick(id steamid.SteamId) string {
|
|||||||
|
|
||||||
func (b *Bsteam) handleEvents() {
|
func (b *Bsteam) handleEvents() {
|
||||||
myLoginInfo := new(steam.LogOnDetails)
|
myLoginInfo := new(steam.LogOnDetails)
|
||||||
myLoginInfo.Username = b.GetString("Login")
|
myLoginInfo.Username = b.Config.Login
|
||||||
myLoginInfo.Password = b.GetString("Password")
|
myLoginInfo.Password = b.Config.Password
|
||||||
myLoginInfo.AuthCode = b.GetString("AuthCode")
|
myLoginInfo.AuthCode = b.Config.AuthCode
|
||||||
// Attempt to read existing auth hash to avoid steam guard.
|
// Attempt to read existing auth hash to avoid steam guard.
|
||||||
// Maybe works
|
// Maybe works
|
||||||
//myLoginInfo.SentryFileHash, _ = ioutil.ReadFile("sentry")
|
//myLoginInfo.SentryFileHash, _ = ioutil.ReadFile("sentry")
|
||||||
for event := range b.c.Events() {
|
for event := range b.c.Events() {
|
||||||
//b.Log.Info(event)
|
//flog.Info(event)
|
||||||
switch e := event.(type) {
|
switch e := event.(type) {
|
||||||
case *steam.ChatMsgEvent:
|
case *steam.ChatMsgEvent:
|
||||||
b.Log.Debugf("Receiving ChatMsgEvent: %#v", e)
|
flog.Debugf("Receiving ChatMsgEvent: %#v", e)
|
||||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", b.getNick(e.ChatterId), b.Account)
|
flog.Debugf("Sending message from %s on %s to gateway", b.getNick(e.ChatterId), b.Account)
|
||||||
var channel int64
|
var channel int64
|
||||||
if e.ChatRoomId == 0 {
|
if e.ChatRoomId == 0 {
|
||||||
channel = int64(e.ChatterId)
|
channel = int64(e.ChatterId)
|
||||||
@ -129,7 +110,7 @@ func (b *Bsteam) handleEvents() {
|
|||||||
msg := config.Message{Username: b.getNick(e.ChatterId), Text: e.Message, Channel: strconv.FormatInt(channel, 10), Account: b.Account, UserID: strconv.FormatInt(int64(e.ChatterId), 10)}
|
msg := config.Message{Username: b.getNick(e.ChatterId), Text: e.Message, Channel: strconv.FormatInt(channel, 10), Account: b.Account, UserID: strconv.FormatInt(int64(e.ChatterId), 10)}
|
||||||
b.Remote <- msg
|
b.Remote <- msg
|
||||||
case *steam.PersonaStateEvent:
|
case *steam.PersonaStateEvent:
|
||||||
b.Log.Debugf("PersonaStateEvent: %#v\n", e)
|
flog.Debugf("PersonaStateEvent: %#v\n", e)
|
||||||
b.Lock()
|
b.Lock()
|
||||||
b.userMap[e.FriendId] = e.Name
|
b.userMap[e.FriendId] = e.Name
|
||||||
b.Unlock()
|
b.Unlock()
|
||||||
@ -137,47 +118,47 @@ func (b *Bsteam) handleEvents() {
|
|||||||
b.c.Auth.LogOn(myLoginInfo)
|
b.c.Auth.LogOn(myLoginInfo)
|
||||||
case *steam.MachineAuthUpdateEvent:
|
case *steam.MachineAuthUpdateEvent:
|
||||||
/*
|
/*
|
||||||
b.Log.Info("authupdate", e)
|
flog.Info("authupdate", e)
|
||||||
b.Log.Info("hash", e.Hash)
|
flog.Info("hash", e.Hash)
|
||||||
ioutil.WriteFile("sentry", e.Hash, 0666)
|
ioutil.WriteFile("sentry", e.Hash, 0666)
|
||||||
*/
|
*/
|
||||||
case *steam.LogOnFailedEvent:
|
case *steam.LogOnFailedEvent:
|
||||||
b.Log.Info("Logon failed", e)
|
flog.Info("Logon failed", e)
|
||||||
switch e.Result {
|
switch e.Result {
|
||||||
case steamlang.EResult_AccountLogonDeniedNeedTwoFactorCode:
|
case steamlang.EResult_AccountLogonDeniedNeedTwoFactorCode:
|
||||||
{
|
{
|
||||||
b.Log.Info("Steam guard isn't letting me in! Enter 2FA code:")
|
flog.Info("Steam guard isn't letting me in! Enter 2FA code:")
|
||||||
var code string
|
var code string
|
||||||
fmt.Scanf("%s", &code)
|
fmt.Scanf("%s", &code)
|
||||||
myLoginInfo.TwoFactorCode = code
|
myLoginInfo.TwoFactorCode = code
|
||||||
}
|
}
|
||||||
case steamlang.EResult_AccountLogonDenied:
|
case steamlang.EResult_AccountLogonDenied:
|
||||||
{
|
{
|
||||||
b.Log.Info("Steam guard isn't letting me in! Enter auth code:")
|
flog.Info("Steam guard isn't letting me in! Enter auth code:")
|
||||||
var code string
|
var code string
|
||||||
fmt.Scanf("%s", &code)
|
fmt.Scanf("%s", &code)
|
||||||
myLoginInfo.AuthCode = code
|
myLoginInfo.AuthCode = code
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
b.Log.Errorf("LogOnFailedEvent: %#v ", e.Result)
|
log.Errorf("LogOnFailedEvent: %#v ", e.Result)
|
||||||
// TODO: Handle EResult_InvalidLoginAuthCode
|
// TODO: Handle EResult_InvalidLoginAuthCode
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case *steam.LoggedOnEvent:
|
case *steam.LoggedOnEvent:
|
||||||
b.Log.Debugf("LoggedOnEvent: %#v", e)
|
flog.Debugf("LoggedOnEvent: %#v", e)
|
||||||
b.connected <- struct{}{}
|
b.connected <- struct{}{}
|
||||||
b.Log.Debugf("setting online")
|
flog.Debugf("setting online")
|
||||||
b.c.Social.SetPersonaState(steamlang.EPersonaState_Online)
|
b.c.Social.SetPersonaState(steamlang.EPersonaState_Online)
|
||||||
case *steam.DisconnectedEvent:
|
case *steam.DisconnectedEvent:
|
||||||
b.Log.Info("Disconnected")
|
flog.Info("Disconnected")
|
||||||
b.Log.Info("Attempting to reconnect...")
|
flog.Info("Attempting to reconnect...")
|
||||||
b.c.Connect()
|
b.c.Connect()
|
||||||
case steam.FatalErrorEvent:
|
case steam.FatalErrorEvent:
|
||||||
b.Log.Error(e)
|
flog.Error(e)
|
||||||
case error:
|
case error:
|
||||||
b.Log.Error(e)
|
flog.Error(e)
|
||||||
default:
|
default:
|
||||||
b.Log.Debugf("unknown event %#v", e)
|
flog.Debugf("unknown event %#v", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,11 @@ import (
|
|||||||
"html"
|
"html"
|
||||||
)
|
)
|
||||||
|
|
||||||
type customHTML struct {
|
type customHtml struct {
|
||||||
blackfriday.Renderer
|
blackfriday.Renderer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (options *customHTML) Paragraph(out *bytes.Buffer, text func() bool) {
|
func (options *customHtml) Paragraph(out *bytes.Buffer, text func() bool) {
|
||||||
marker := out.Len()
|
marker := out.Len()
|
||||||
|
|
||||||
if !text() {
|
if !text() {
|
||||||
@ -20,32 +20,32 @@ func (options *customHTML) Paragraph(out *bytes.Buffer, text func() bool) {
|
|||||||
out.WriteString("\n")
|
out.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
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(html.EscapeString(string(text)))
|
||||||
out.WriteString("</pre>\n")
|
out.WriteString("</pre>\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (options *customHTML) HRule(out *bytes.Buffer) {
|
func (options *customHtml) HRule(out *bytes.Buffer) {
|
||||||
out.WriteByte('\n')
|
out.WriteByte('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
func (options *customHTML) BlockQuote(out *bytes.Buffer, text []byte) {
|
func (options *customHtml) BlockQuote(out *bytes.Buffer, text []byte) {
|
||||||
out.WriteString("> ")
|
out.WriteString("> ")
|
||||||
out.Write(text)
|
out.Write(text)
|
||||||
out.WriteByte('\n')
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (options *customHTML) ListItem(out *bytes.Buffer, text []byte, flags int) {
|
func (options *customHtml) ListItem(out *bytes.Buffer, text []byte, flags int) {
|
||||||
out.WriteString("- ")
|
out.WriteString("- ")
|
||||||
out.Write(text)
|
out.Write(text)
|
||||||
out.WriteByte('\n')
|
out.WriteByte('\n')
|
||||||
@ -53,7 +53,7 @@ func (options *customHTML) ListItem(out *bytes.Buffer, text []byte, flags int) {
|
|||||||
|
|
||||||
func makeHTML(input string) string {
|
func makeHTML(input string) string {
|
||||||
return string(blackfriday.Markdown([]byte(input),
|
return string(blackfriday.Markdown([]byte(input),
|
||||||
&customHTML{blackfriday.HtmlRenderer(blackfriday.HTML_USE_XHTML|blackfriday.HTML_SKIP_IMAGES, "", "")},
|
&customHtml{blackfriday.HtmlRenderer(blackfriday.HTML_USE_XHTML|blackfriday.HTML_SKIP_IMAGES, "", "")},
|
||||||
blackfriday.EXTENSION_NO_INTRA_EMPHASIS|
|
blackfriday.EXTENSION_NO_INTRA_EMPHASIS|
|
||||||
blackfriday.EXTENSION_FENCED_CODE|
|
blackfriday.EXTENSION_FENCED_CODE|
|
||||||
blackfriday.EXTENSION_AUTOLINK|
|
blackfriday.EXTENSION_AUTOLINK|
|
||||||
|
@ -5,44 +5,49 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"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"
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/go-telegram-bot-api/telegram-bot-api"
|
"github.com/go-telegram-bot-api/telegram-bot-api"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Btelegram struct {
|
type Btelegram struct {
|
||||||
c *tgbotapi.BotAPI
|
c *tgbotapi.BotAPI
|
||||||
*bridge.Config
|
*config.BridgeConfig
|
||||||
avatarMap map[string]string // keep cache of userid and avatar sha
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(cfg *bridge.Config) bridge.Bridger {
|
var flog *log.Entry
|
||||||
return &Btelegram{Config: cfg, avatarMap: make(map[string]string)}
|
var protocol = "telegram"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flog = log.WithFields(log.Fields{"module": protocol})
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg *config.BridgeConfig) *Btelegram {
|
||||||
|
return &Btelegram{BridgeConfig: cfg}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Btelegram) Connect() error {
|
func (b *Btelegram) Connect() error {
|
||||||
var err error
|
var err error
|
||||||
b.Log.Info("Connecting")
|
flog.Info("Connecting")
|
||||||
b.c, err = tgbotapi.NewBotAPI(b.GetString("Token"))
|
b.c, err = tgbotapi.NewBotAPI(b.Config.Token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Debugf("%#v", err)
|
flog.Debugf("%#v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
u := tgbotapi.NewUpdate(0)
|
updates, err := b.c.GetUpdatesChan(tgbotapi.NewUpdate(0))
|
||||||
u.Timeout = 60
|
|
||||||
updates, err := b.c.GetUpdatesChan(u)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Debugf("%#v", err)
|
flog.Debugf("%#v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
b.Log.Info("Connection succeeded")
|
flog.Info("Connection succeeded")
|
||||||
go b.handleRecv(updates)
|
go b.handleRecv(updates)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Btelegram) Disconnect() error {
|
func (b *Btelegram) Disconnect() error {
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Btelegram) JoinChannel(channel config.ChannelInfo) error {
|
func (b *Btelegram) JoinChannel(channel config.ChannelInfo) error {
|
||||||
@ -50,24 +55,16 @@ func (b *Btelegram) JoinChannel(channel config.ChannelInfo) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Btelegram) Send(msg config.Message) (string, error) {
|
func (b *Btelegram) Send(msg config.Message) (string, error) {
|
||||||
b.Log.Debugf("=> Receiving %#v", msg)
|
flog.Debugf("Receiving %#v", msg)
|
||||||
|
|
||||||
// get the chatid
|
|
||||||
chatid, err := strconv.ParseInt(msg.Channel, 10, 64)
|
chatid, err := strconv.ParseInt(msg.Channel, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// map the file SHA to our user (caches the avatar)
|
if b.Config.MessageFormat == "HTML" {
|
||||||
if msg.Event == config.EVENT_AVATAR_DOWNLOAD {
|
|
||||||
return b.cacheAvatar(&msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.GetString("MessageFormat") == "HTML" {
|
|
||||||
msg.Text = makeHTML(msg.Text)
|
msg.Text = makeHTML(msg.Text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete message
|
|
||||||
if msg.Event == config.EVENT_MSG_DELETE {
|
if msg.Event == config.EVENT_MSG_DELETE {
|
||||||
if msg.ID == "" {
|
if msg.ID == "" {
|
||||||
return "", nil
|
return "", nil
|
||||||
@ -80,17 +77,6 @@ func (b *Btelegram) Send(msg config.Message) (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload a file if it exists
|
|
||||||
if msg.Extra != nil {
|
|
||||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
|
||||||
b.sendMessage(chatid, rmsg.Username, rmsg.Text)
|
|
||||||
}
|
|
||||||
// check if we have files to upload (from slack, telegram or mattermost)
|
|
||||||
if len(msg.Extra["file"]) > 0 {
|
|
||||||
b.handleUploadFile(&msg, chatid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// edit the message if we have a msg ID
|
// edit the message if we have a msg ID
|
||||||
if msg.ID != "" {
|
if msg.ID != "" {
|
||||||
msgid, err := strconv.Atoi(msg.ID)
|
msgid, err := strconv.Atoi(msg.ID)
|
||||||
@ -98,14 +84,9 @@ func (b *Btelegram) Send(msg config.Message) (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
m := tgbotapi.NewEditMessageText(chatid, msgid, msg.Username+msg.Text)
|
m := tgbotapi.NewEditMessageText(chatid, msgid, msg.Username+msg.Text)
|
||||||
if b.GetString("MessageFormat") == "HTML" {
|
if b.Config.MessageFormat == "HTML" {
|
||||||
b.Log.Debug("Using mode HTML")
|
|
||||||
m.ParseMode = tgbotapi.ModeHTML
|
m.ParseMode = tgbotapi.ModeHTML
|
||||||
}
|
}
|
||||||
if b.GetString("MessageFormat") == "Markdown" {
|
|
||||||
b.Log.Debug("Using mode markdown")
|
|
||||||
m.ParseMode = tgbotapi.ModeMarkdown
|
|
||||||
}
|
|
||||||
_, err = b.c.Send(m)
|
_, err = b.c.Send(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@ -113,84 +94,98 @@ func (b *Btelegram) Send(msg config.Message) (string, error) {
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post normal message
|
if msg.Extra != nil {
|
||||||
return b.sendMessage(chatid, msg.Username, msg.Text)
|
// check if we have files to upload (from slack, telegram or mattermost)
|
||||||
|
if len(msg.Extra["file"]) > 0 {
|
||||||
|
var c tgbotapi.Chattable
|
||||||
|
for _, f := range msg.Extra["file"] {
|
||||||
|
fi := f.(config.FileInfo)
|
||||||
|
file := tgbotapi.FileBytes{fi.Name, *fi.Data}
|
||||||
|
re := regexp.MustCompile(".(jpg|png)$")
|
||||||
|
if re.MatchString(fi.Name) {
|
||||||
|
c = tgbotapi.NewPhotoUpload(chatid, file)
|
||||||
|
} else {
|
||||||
|
c = tgbotapi.NewDocumentUpload(chatid, file)
|
||||||
|
}
|
||||||
|
_, err := b.c.Send(c)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("file upload failed: %#v", err)
|
||||||
|
}
|
||||||
|
if fi.Comment != "" {
|
||||||
|
b.sendMessage(chatid, msg.Username+fi.Comment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b.sendMessage(chatid, msg.Username+msg.Text)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
|
func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
|
||||||
for update := range updates {
|
for update := range updates {
|
||||||
b.Log.Debugf("== Receiving event: %#v", update.Message)
|
flog.Debugf("Receiving from telegram: %#v", update.Message)
|
||||||
|
|
||||||
if update.Message == nil && update.ChannelPost == nil && update.EditedMessage == nil && update.EditedChannelPost == nil {
|
|
||||||
b.Log.Error("Getting nil messages, this shouldn't happen.")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var message *tgbotapi.Message
|
var message *tgbotapi.Message
|
||||||
|
username := ""
|
||||||
|
channel := ""
|
||||||
|
text := ""
|
||||||
|
|
||||||
rmsg := config.Message{Account: b.Account, Extra: make(map[string][]interface{})}
|
fmsg := config.Message{Extra: make(map[string][]interface{})}
|
||||||
|
|
||||||
// handle channels
|
// handle channels
|
||||||
if update.ChannelPost != nil {
|
if update.ChannelPost != nil {
|
||||||
message = update.ChannelPost
|
message = update.ChannelPost
|
||||||
rmsg.Text = message.Text
|
|
||||||
}
|
}
|
||||||
|
if update.EditedChannelPost != nil && !b.Config.EditDisable {
|
||||||
// edited channel message
|
|
||||||
if update.EditedChannelPost != nil && !b.GetBool("EditDisable") {
|
|
||||||
message = update.EditedChannelPost
|
message = update.EditedChannelPost
|
||||||
rmsg.Text = rmsg.Text + message.Text + b.GetString("EditSuffix")
|
message.Text = message.Text + b.Config.EditSuffix
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle groups
|
// handle groups
|
||||||
if update.Message != nil {
|
if update.Message != nil {
|
||||||
message = update.Message
|
message = update.Message
|
||||||
rmsg.Text = message.Text
|
|
||||||
}
|
}
|
||||||
|
if update.EditedMessage != nil && !b.Config.EditDisable {
|
||||||
// edited group message
|
|
||||||
if update.EditedMessage != nil && !b.GetBool("EditDisable") {
|
|
||||||
message = update.EditedMessage
|
message = update.EditedMessage
|
||||||
rmsg.Text = rmsg.Text + message.Text + b.GetString("EditSuffix")
|
message.Text = message.Text + b.Config.EditSuffix
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the ID's from the channel or group message
|
|
||||||
rmsg.ID = strconv.Itoa(message.MessageID)
|
|
||||||
rmsg.Channel = strconv.FormatInt(message.Chat.ID, 10)
|
|
||||||
|
|
||||||
// handle username
|
|
||||||
if message.From != nil {
|
if message.From != nil {
|
||||||
rmsg.UserID = strconv.Itoa(message.From.ID)
|
if b.Config.UseFirstName {
|
||||||
if b.GetBool("UseFirstName") {
|
username = message.From.FirstName
|
||||||
rmsg.Username = message.From.FirstName
|
|
||||||
}
|
}
|
||||||
if rmsg.Username == "" {
|
if username == "" {
|
||||||
rmsg.Username = message.From.UserName
|
username = message.From.UserName
|
||||||
if rmsg.Username == "" {
|
if username == "" {
|
||||||
rmsg.Username = message.From.FirstName
|
username = message.From.FirstName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// only download avatars if we have a place to upload them (configured mediaserver)
|
text = message.Text
|
||||||
if b.General.MediaServerUpload != "" {
|
channel = strconv.FormatInt(message.Chat.ID, 10)
|
||||||
b.handleDownloadAvatar(message.From.ID, rmsg.Channel)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we really didn't find a username, set it to unknown
|
if username == "" {
|
||||||
if rmsg.Username == "" {
|
username = "unknown"
|
||||||
rmsg.Username = "unknown"
|
}
|
||||||
|
if message.Sticker != nil {
|
||||||
|
b.handleDownload(message.Sticker, &fmsg)
|
||||||
|
}
|
||||||
|
if message.Video != nil {
|
||||||
|
b.handleDownload(message.Video, &fmsg)
|
||||||
|
}
|
||||||
|
if message.Photo != nil {
|
||||||
|
b.handleDownload(message.Photo, &fmsg)
|
||||||
|
}
|
||||||
|
if message.Document != nil {
|
||||||
|
b.handleDownload(message.Document, &fmsg)
|
||||||
|
}
|
||||||
|
if message.Voice != nil {
|
||||||
|
b.handleDownload(message.Voice, &fmsg)
|
||||||
|
}
|
||||||
|
if message.Audio != nil {
|
||||||
|
b.handleDownload(message.Audio, &fmsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle any downloads
|
|
||||||
err := b.handleDownload(message, &rmsg)
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("download failed: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle forwarded messages
|
|
||||||
if message.ForwardFrom != nil {
|
if message.ForwardFrom != nil {
|
||||||
usernameForward := ""
|
usernameForward := ""
|
||||||
if b.GetBool("UseFirstName") {
|
if b.Config.UseFirstName {
|
||||||
usernameForward = message.ForwardFrom.FirstName
|
usernameForward = message.ForwardFrom.FirstName
|
||||||
}
|
}
|
||||||
if usernameForward == "" {
|
if usernameForward == "" {
|
||||||
@ -202,14 +197,14 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
|
|||||||
if usernameForward == "" {
|
if usernameForward == "" {
|
||||||
usernameForward = "unknown"
|
usernameForward = "unknown"
|
||||||
}
|
}
|
||||||
rmsg.Text = "Forwarded from " + usernameForward + ": " + rmsg.Text
|
text = "Forwarded from " + usernameForward + ": " + text
|
||||||
}
|
}
|
||||||
|
|
||||||
// quote the previous message
|
// quote the previous message
|
||||||
if message.ReplyToMessage != nil {
|
if message.ReplyToMessage != nil {
|
||||||
usernameReply := ""
|
usernameReply := ""
|
||||||
if message.ReplyToMessage.From != nil {
|
if message.ReplyToMessage.From != nil {
|
||||||
if b.GetBool("UseFirstName") {
|
if b.Config.UseFirstName {
|
||||||
usernameReply = message.ReplyToMessage.From.FirstName
|
usernameReply = message.ReplyToMessage.From.FirstName
|
||||||
}
|
}
|
||||||
if usernameReply == "" {
|
if usernameReply == "" {
|
||||||
@ -222,21 +217,14 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
|
|||||||
if usernameReply == "" {
|
if usernameReply == "" {
|
||||||
usernameReply = "unknown"
|
usernameReply = "unknown"
|
||||||
}
|
}
|
||||||
if !b.GetBool("QuoteDisable") {
|
text = text + " (re @" + usernameReply + ":" + message.ReplyToMessage.Text + ")"
|
||||||
rmsg.Text = b.handleQuote(rmsg.Text, usernameReply, message.ReplyToMessage.Text)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if rmsg.Text != "" || len(rmsg.Extra) > 0 {
|
if text != "" || len(fmsg.Extra) > 0 {
|
||||||
rmsg.Text = helper.RemoveEmptyNewLines(rmsg.Text)
|
flog.Debugf("Sending message from %s on %s to gateway", username, b.Account)
|
||||||
// channels don't have (always?) user information. see #410
|
msg := config.Message{Username: username, Text: text, Channel: channel, Account: b.Account, UserID: strconv.Itoa(message.From.ID), ID: strconv.Itoa(message.MessageID), Extra: fmsg.Extra}
|
||||||
if message.From != nil {
|
flog.Debugf("Message is %#v", msg)
|
||||||
rmsg.Avatar = helper.GetAvatar(b.avatarMap, strconv.Itoa(message.From.ID), b.General)
|
b.Remote <- msg
|
||||||
}
|
|
||||||
|
|
||||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account)
|
|
||||||
b.Log.Debugf("<= Message is %#v", rmsg)
|
|
||||||
b.Remote <- rmsg
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -249,80 +237,21 @@ func (b *Btelegram) getFileDirectURL(id string) string {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleDownloadAvatar downloads the avatar of userid from channel
|
func (b *Btelegram) handleDownload(file interface{}, msg *config.Message) {
|
||||||
// sends a EVENT_AVATAR_DOWNLOAD message to the gateway if successful.
|
|
||||||
// logs an error message if it fails
|
|
||||||
func (b *Btelegram) handleDownloadAvatar(userid int, channel string) {
|
|
||||||
rmsg := config.Message{Username: "system", Text: "avatar", Channel: channel, Account: b.Account, UserID: strconv.Itoa(userid), Event: config.EVENT_AVATAR_DOWNLOAD, Extra: make(map[string][]interface{})}
|
|
||||||
if _, ok := b.avatarMap[strconv.Itoa(userid)]; !ok {
|
|
||||||
photos, err := b.c.GetUserProfilePhotos(tgbotapi.UserProfilePhotosConfig{UserID: userid, Limit: 1})
|
|
||||||
if 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.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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleDownloadFile handles file download
|
|
||||||
func (b *Btelegram) handleDownload(message *tgbotapi.Message, rmsg *config.Message) error {
|
|
||||||
size := 0
|
size := 0
|
||||||
var url, name, text string
|
url := ""
|
||||||
|
name := ""
|
||||||
if message.Sticker != nil {
|
text := ""
|
||||||
v := message.Sticker
|
fileid := ""
|
||||||
size = v.FileSize
|
switch v := file.(type) {
|
||||||
url = b.getFileDirectURL(v.FileID)
|
case *tgbotapi.Audio:
|
||||||
urlPart := strings.Split(url, "/")
|
|
||||||
name = urlPart[len(urlPart)-1]
|
|
||||||
if !strings.HasSuffix(name, ".webp") {
|
|
||||||
name = name + ".webp"
|
|
||||||
}
|
|
||||||
text = " " + url
|
|
||||||
}
|
|
||||||
if message.Video != nil {
|
|
||||||
v := message.Video
|
|
||||||
size = v.FileSize
|
size = v.FileSize
|
||||||
url = b.getFileDirectURL(v.FileID)
|
url = b.getFileDirectURL(v.FileID)
|
||||||
urlPart := strings.Split(url, "/")
|
urlPart := strings.Split(url, "/")
|
||||||
name = urlPart[len(urlPart)-1]
|
name = urlPart[len(urlPart)-1]
|
||||||
text = " " + url
|
text = " " + url
|
||||||
}
|
fileid = v.FileID
|
||||||
if message.Photo != nil {
|
case *tgbotapi.Voice:
|
||||||
photos := *message.Photo
|
|
||||||
size = photos[len(photos)-1].FileSize
|
|
||||||
url = b.getFileDirectURL(photos[len(photos)-1].FileID)
|
|
||||||
urlPart := strings.Split(url, "/")
|
|
||||||
name = urlPart[len(urlPart)-1]
|
|
||||||
text = " " + url
|
|
||||||
}
|
|
||||||
if message.Document != nil {
|
|
||||||
v := message.Document
|
|
||||||
size = v.FileSize
|
|
||||||
url = b.getFileDirectURL(v.FileID)
|
|
||||||
name = v.FileName
|
|
||||||
text = " " + v.FileName + " : " + url
|
|
||||||
}
|
|
||||||
if message.Voice != nil {
|
|
||||||
v := message.Voice
|
|
||||||
size = v.FileSize
|
size = v.FileSize
|
||||||
url = b.getFileDirectURL(v.FileID)
|
url = b.getFileDirectURL(v.FileID)
|
||||||
urlPart := strings.Split(url, "/")
|
urlPart := strings.Split(url, "/")
|
||||||
@ -331,98 +260,64 @@ func (b *Btelegram) handleDownload(message *tgbotapi.Message, rmsg *config.Messa
|
|||||||
if !strings.HasSuffix(name, ".ogg") {
|
if !strings.HasSuffix(name, ".ogg") {
|
||||||
name = name + ".ogg"
|
name = name + ".ogg"
|
||||||
}
|
}
|
||||||
}
|
fileid = v.FileID
|
||||||
if message.Audio != nil {
|
case *tgbotapi.Sticker:
|
||||||
v := message.Audio
|
size = v.FileSize
|
||||||
|
url = b.getFileDirectURL(v.FileID)
|
||||||
|
urlPart := strings.Split(url, "/")
|
||||||
|
name = urlPart[len(urlPart)-1]
|
||||||
|
if !strings.HasSuffix(name, ".webp") {
|
||||||
|
name = name + ".webp"
|
||||||
|
}
|
||||||
|
text = " " + url
|
||||||
|
fileid = v.FileID
|
||||||
|
case *tgbotapi.Video:
|
||||||
size = v.FileSize
|
size = v.FileSize
|
||||||
url = b.getFileDirectURL(v.FileID)
|
url = b.getFileDirectURL(v.FileID)
|
||||||
urlPart := strings.Split(url, "/")
|
urlPart := strings.Split(url, "/")
|
||||||
name = urlPart[len(urlPart)-1]
|
name = urlPart[len(urlPart)-1]
|
||||||
text = " " + url
|
text = " " + url
|
||||||
|
fileid = v.FileID
|
||||||
|
case *[]tgbotapi.PhotoSize:
|
||||||
|
photos := *v
|
||||||
|
size = photos[len(photos)-1].FileSize
|
||||||
|
url = b.getFileDirectURL(photos[len(photos)-1].FileID)
|
||||||
|
urlPart := strings.Split(url, "/")
|
||||||
|
name = urlPart[len(urlPart)-1]
|
||||||
|
text = " " + url
|
||||||
|
case *tgbotapi.Document:
|
||||||
|
size = v.FileSize
|
||||||
|
url = b.getFileDirectURL(v.FileID)
|
||||||
|
name = v.FileName
|
||||||
|
text = " " + v.FileName + " : " + url
|
||||||
|
fileid = v.FileID
|
||||||
}
|
}
|
||||||
// if name is empty we didn't match a thing to download
|
if b.Config.UseInsecureURL {
|
||||||
if name == "" {
|
msg.Text = text
|
||||||
return nil
|
return
|
||||||
}
|
|
||||||
// use the URL instead of native upload
|
|
||||||
if b.GetBool("UseInsecureURL") {
|
|
||||||
b.Log.Debugf("Setting message text to :%s", text)
|
|
||||||
rmsg.Text = rmsg.Text + text
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra
|
// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra
|
||||||
err := helper.HandleDownloadSize(b.Log, rmsg, name, int64(size), b.General)
|
// limit to 1MB for now
|
||||||
if err != nil {
|
flog.Debugf("trying to download %#v fileid %#v with size %#v", name, fileid, size)
|
||||||
return err
|
if size <= b.General.MediaDownloadSize {
|
||||||
}
|
data, err := helper.DownloadFile(url)
|
||||||
data, err := helper.DownloadFile(url)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
helper.HandleDownloadData(b.Log, rmsg, name, message.Caption, "", data, b.General)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleUploadFile handles native upload of files
|
|
||||||
func (b *Btelegram) handleUploadFile(msg *config.Message, chatid int64) (string, error) {
|
|
||||||
var c tgbotapi.Chattable
|
|
||||||
for _, f := range msg.Extra["file"] {
|
|
||||||
fi := f.(config.FileInfo)
|
|
||||||
file := tgbotapi.FileBytes{fi.Name, *fi.Data}
|
|
||||||
re := regexp.MustCompile(".(jpg|png)$")
|
|
||||||
if re.MatchString(fi.Name) {
|
|
||||||
c = tgbotapi.NewPhotoUpload(chatid, file)
|
|
||||||
} else {
|
|
||||||
c = tgbotapi.NewDocumentUpload(chatid, file)
|
|
||||||
}
|
|
||||||
_, err := b.c.Send(c)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Errorf("file upload failed: %#v", err)
|
flog.Errorf("download %s failed %#v", url, err)
|
||||||
}
|
} else {
|
||||||
if fi.Comment != "" {
|
flog.Debugf("download OK %#v %#v %#v", name, len(*data), len(url))
|
||||||
b.sendMessage(chatid, msg.Username, fi.Comment)
|
msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{Name: name, Data: data})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Btelegram) sendMessage(chatid int64, username, text string) (string, error) {
|
func (b *Btelegram) sendMessage(chatid int64, text string) (string, error) {
|
||||||
m := tgbotapi.NewMessage(chatid, "")
|
m := tgbotapi.NewMessage(chatid, text)
|
||||||
m.Text = username + text
|
if b.Config.MessageFormat == "HTML" {
|
||||||
if b.GetString("MessageFormat") == "HTML" {
|
|
||||||
b.Log.Debug("Using mode HTML")
|
|
||||||
m.Text = username + text
|
|
||||||
m.ParseMode = tgbotapi.ModeHTML
|
m.ParseMode = tgbotapi.ModeHTML
|
||||||
}
|
}
|
||||||
if b.GetString("MessageFormat") == "Markdown" {
|
|
||||||
b.Log.Debug("Using mode markdown")
|
|
||||||
m.ParseMode = tgbotapi.ModeMarkdown
|
|
||||||
}
|
|
||||||
res, err := b.c.Send(m)
|
res, err := b.c.Send(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return strconv.Itoa(res.MessageID), nil
|
return strconv.Itoa(res.MessageID), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Btelegram) cacheAvatar(msg *config.Message) (string, error) {
|
|
||||||
fi := msg.Extra["file"][0].(config.FileInfo)
|
|
||||||
/* if we have a sha we have successfully uploaded the file to the media server,
|
|
||||||
so we can now cache the sha */
|
|
||||||
if fi.SHA != "" {
|
|
||||||
b.Log.Debugf("Added %s to %s in avatarMap", fi.SHA, msg.UserID)
|
|
||||||
b.avatarMap[msg.UserID] = fi.SHA
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Btelegram) handleQuote(message, quoteNick, quoteMessage string) string {
|
|
||||||
format := b.GetString("quoteformat")
|
|
||||||
if format == "" {
|
|
||||||
format = "{MESSAGE} (re @{QUOTENICK}: {QUOTEMESSAGE})"
|
|
||||||
}
|
|
||||||
format = strings.Replace(format, "{MESSAGE}", message, -1)
|
|
||||||
format = strings.Replace(format, "{QUOTENICK}", quoteNick, -1)
|
|
||||||
format = strings.Replace(format, "{QUOTEMESSAGE}", quoteMessage, -1)
|
|
||||||
return format
|
|
||||||
}
|
|
||||||
|
@ -2,11 +2,11 @@ package bxmpp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"github.com/42wim/matterbridge/bridge"
|
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/jpillora/backoff"
|
"github.com/jpillora/backoff"
|
||||||
"github.com/matterbridge/go-xmpp"
|
"github.com/mattn/go-xmpp"
|
||||||
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -14,24 +14,31 @@ import (
|
|||||||
type Bxmpp struct {
|
type Bxmpp struct {
|
||||||
xc *xmpp.Client
|
xc *xmpp.Client
|
||||||
xmppMap map[string]string
|
xmppMap map[string]string
|
||||||
*bridge.Config
|
*config.BridgeConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(cfg *bridge.Config) bridge.Bridger {
|
var flog *log.Entry
|
||||||
b := &Bxmpp{Config: cfg}
|
var protocol = "xmpp"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flog = log.WithFields(log.Fields{"module": protocol})
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg *config.BridgeConfig) *Bxmpp {
|
||||||
|
b := &Bxmpp{BridgeConfig: cfg}
|
||||||
b.xmppMap = make(map[string]string)
|
b.xmppMap = make(map[string]string)
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bxmpp) Connect() error {
|
func (b *Bxmpp) Connect() error {
|
||||||
var err error
|
var err error
|
||||||
b.Log.Infof("Connecting %s", b.GetString("Server"))
|
flog.Infof("Connecting %s", b.Config.Server)
|
||||||
b.xc, err = b.createXMPP()
|
b.xc, err = b.createXMPP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Debugf("%#v", err)
|
flog.Debugf("%#v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
b.Log.Info("Connection succeeded")
|
flog.Info("Connection succeeded")
|
||||||
go func() {
|
go func() {
|
||||||
initial := true
|
initial := true
|
||||||
bf := &backoff.Backoff{
|
bf := &backoff.Backoff{
|
||||||
@ -41,16 +48,16 @@ func (b *Bxmpp) Connect() error {
|
|||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
if initial {
|
if initial {
|
||||||
b.handleXMPP()
|
b.handleXmpp()
|
||||||
initial = false
|
initial = false
|
||||||
}
|
}
|
||||||
d := bf.Duration()
|
d := bf.Duration()
|
||||||
b.Log.Infof("Disconnected. Reconnecting in %s", d)
|
flog.Infof("Disconnected. Reconnecting in %s", d)
|
||||||
time.Sleep(d)
|
time.Sleep(d)
|
||||||
b.xc, err = b.createXMPP()
|
b.xc, err = b.createXMPP()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
b.Remote <- config.Message{Username: "system", Text: "rejoin", Channel: "", Account: b.Account, Event: config.EVENT_REJOIN_CHANNELS}
|
b.Remote <- config.Message{Username: "system", Text: "rejoin", Channel: "", Account: b.Account, Event: config.EVENT_REJOIN_CHANNELS}
|
||||||
b.handleXMPP()
|
b.handleXmpp()
|
||||||
bf.Reset()
|
bf.Reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,7 +70,7 @@ func (b *Bxmpp) Disconnect() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bxmpp) JoinChannel(channel config.ChannelInfo) error {
|
func (b *Bxmpp) JoinChannel(channel config.ChannelInfo) error {
|
||||||
b.xc.JoinMUCNoHistory(channel.Name+"@"+b.GetString("Muc"), b.GetString("Nick"))
|
b.xc.JoinMUCNoHistory(channel.Name+"@"+b.Config.Muc, b.Config.Nick)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,44 +79,44 @@ func (b *Bxmpp) Send(msg config.Message) (string, error) {
|
|||||||
if msg.Event == config.EVENT_MSG_DELETE {
|
if msg.Event == config.EVENT_MSG_DELETE {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
b.Log.Debugf("=> Receiving %#v", msg)
|
flog.Debugf("Receiving %#v", msg)
|
||||||
|
|
||||||
// Upload a file (in xmpp case send the upload URL because xmpp has no native upload support)
|
|
||||||
if msg.Extra != nil {
|
if msg.Extra != nil {
|
||||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
|
||||||
b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: rmsg.Channel + "@" + b.GetString("Muc"), Text: rmsg.Username + rmsg.Text})
|
|
||||||
}
|
|
||||||
if len(msg.Extra["file"]) > 0 {
|
if len(msg.Extra["file"]) > 0 {
|
||||||
return b.handleUploadFile(&msg)
|
for _, f := range msg.Extra["file"] {
|
||||||
|
fi := f.(config.FileInfo)
|
||||||
|
if fi.URL != "" {
|
||||||
|
msg.Text = fi.URL
|
||||||
|
}
|
||||||
|
b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Config.Muc, Text: msg.Username + msg.Text})
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post normal message
|
b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Config.Muc, Text: msg.Username + msg.Text})
|
||||||
_, err := b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.GetString("Muc"), Text: msg.Username + msg.Text})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bxmpp) createXMPP() (*xmpp.Client, error) {
|
func (b *Bxmpp) createXMPP() (*xmpp.Client, error) {
|
||||||
tc := new(tls.Config)
|
tc := new(tls.Config)
|
||||||
tc.InsecureSkipVerify = b.GetBool("SkipTLSVerify")
|
tc.InsecureSkipVerify = b.Config.SkipTLSVerify
|
||||||
tc.ServerName = strings.Split(b.GetString("Server"), ":")[0]
|
tc.ServerName = strings.Split(b.Config.Server, ":")[0]
|
||||||
options := xmpp.Options{
|
options := xmpp.Options{
|
||||||
Host: b.GetString("Server"),
|
Host: b.Config.Server,
|
||||||
User: b.GetString("Jid"),
|
User: b.Config.Jid,
|
||||||
Password: b.GetString("Password"),
|
Password: b.Config.Password,
|
||||||
NoTLS: true,
|
NoTLS: true,
|
||||||
StartTLS: true,
|
StartTLS: true,
|
||||||
TLSConfig: tc,
|
TLSConfig: tc,
|
||||||
Debug: b.GetBool("debug"),
|
|
||||||
Logger: b.Log.Writer(),
|
//StartTLS: false,
|
||||||
|
Debug: true,
|
||||||
Session: true,
|
Session: true,
|
||||||
Status: "",
|
Status: "",
|
||||||
StatusMessage: "",
|
StatusMessage: "",
|
||||||
Resource: "",
|
Resource: "",
|
||||||
InsecureAllowUnencryptedAuth: false,
|
InsecureAllowUnencryptedAuth: false,
|
||||||
|
//InsecureAllowUnencryptedAuth: true,
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
b.xc, err = options.NewClient()
|
b.xc, err = options.NewClient()
|
||||||
@ -124,10 +131,10 @@ func (b *Bxmpp) xmppKeepAlive() chan bool {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
b.Log.Debugf("PING")
|
flog.Debugf("PING")
|
||||||
err := b.xc.PingC2S("", "")
|
err := b.xc.PingC2S("", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Debugf("PING failed %#v", err)
|
flog.Debugf("PING failed %#v", err)
|
||||||
}
|
}
|
||||||
case <-done:
|
case <-done:
|
||||||
return
|
return
|
||||||
@ -137,10 +144,11 @@ func (b *Bxmpp) xmppKeepAlive() chan bool {
|
|||||||
return done
|
return done
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bxmpp) handleXMPP() error {
|
func (b *Bxmpp) handleXmpp() error {
|
||||||
var ok bool
|
var ok bool
|
||||||
done := b.xmppKeepAlive()
|
done := b.xmppKeepAlive()
|
||||||
defer close(done)
|
defer close(done)
|
||||||
|
nodelay := time.Time{}
|
||||||
for {
|
for {
|
||||||
m, err := b.xc.Recv()
|
m, err := b.xc.Recv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -148,22 +156,25 @@ func (b *Bxmpp) handleXMPP() error {
|
|||||||
}
|
}
|
||||||
switch v := m.(type) {
|
switch v := m.(type) {
|
||||||
case xmpp.Chat:
|
case xmpp.Chat:
|
||||||
|
var channel, nick string
|
||||||
if v.Type == "groupchat" {
|
if v.Type == "groupchat" {
|
||||||
b.Log.Debugf("== Receiving %#v", v)
|
s := strings.Split(v.Remote, "@")
|
||||||
// skip invalid messages
|
if len(s) >= 2 {
|
||||||
if b.skipMessage(v) {
|
channel = s[0]
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
rmsg := config.Message{Username: b.parseNick(v.Remote), Text: v.Text, Channel: b.parseChannel(v.Remote), Account: b.Account, UserID: v.Remote}
|
s = strings.Split(s[1], "/")
|
||||||
|
if len(s) == 2 {
|
||||||
// check if we have an action event
|
nick = s[1]
|
||||||
rmsg.Text, ok = b.replaceAction(rmsg.Text)
|
}
|
||||||
if ok {
|
if nick != b.Config.Nick && v.Stamp == nodelay && v.Text != "" {
|
||||||
rmsg.Event = config.EVENT_USER_ACTION
|
rmsg := config.Message{Username: nick, Text: v.Text, Channel: channel, Account: b.Account, UserID: v.Remote}
|
||||||
|
rmsg.Text, ok = b.replaceAction(rmsg.Text)
|
||||||
|
if ok {
|
||||||
|
rmsg.Event = config.EVENT_USER_ACTION
|
||||||
|
}
|
||||||
|
flog.Debugf("Sending message from %s on %s to gateway", nick, b.Account)
|
||||||
|
b.Remote <- rmsg
|
||||||
}
|
}
|
||||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account)
|
|
||||||
b.Log.Debugf("<= Message is %#v", rmsg)
|
|
||||||
b.Remote <- rmsg
|
|
||||||
}
|
}
|
||||||
case xmpp.Presence:
|
case xmpp.Presence:
|
||||||
// do nothing
|
// do nothing
|
||||||
@ -177,70 +188,3 @@ func (b *Bxmpp) replaceAction(text string) (string, bool) {
|
|||||||
}
|
}
|
||||||
return text, false
|
return text, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleUploadFile handles native upload of files
|
|
||||||
func (b *Bxmpp) handleUploadFile(msg *config.Message) (string, error) {
|
|
||||||
var urldesc = ""
|
|
||||||
for _, f := range msg.Extra["file"] {
|
|
||||||
fi := f.(config.FileInfo)
|
|
||||||
if fi.Comment != "" {
|
|
||||||
msg.Text += fi.Comment + ": "
|
|
||||||
}
|
|
||||||
if fi.URL != "" {
|
|
||||||
msg.Text = fi.URL
|
|
||||||
if fi.Comment != "" {
|
|
||||||
msg.Text = fi.Comment + ": " + fi.URL
|
|
||||||
urldesc = fi.Comment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, err := b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.GetString("Muc"), Text: msg.Username + msg.Text})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if fi.URL != "" {
|
|
||||||
b.xc.SendOOB(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.GetString("Muc"), Ooburl: fi.URL, Oobdesc: urldesc})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bxmpp) parseNick(remote string) string {
|
|
||||||
s := strings.Split(remote, "@")
|
|
||||||
if len(s) > 0 {
|
|
||||||
s = strings.Split(s[1], "/")
|
|
||||||
if len(s) == 2 {
|
|
||||||
return s[1] // nick
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bxmpp) parseChannel(remote string) string {
|
|
||||||
s := strings.Split(remote, "@")
|
|
||||||
if len(s) >= 2 {
|
|
||||||
return s[0] // channel
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// skipMessage skips messages that need to be skipped
|
|
||||||
func (b *Bxmpp) skipMessage(message xmpp.Chat) bool {
|
|
||||||
// skip messages from ourselves
|
|
||||||
if b.parseNick(message.Remote) == b.GetString("Nick") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip empty messages
|
|
||||||
if message.Text == "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip subject messages
|
|
||||||
if strings.Contains(message.Text, "</subject>") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip delayed messages
|
|
||||||
t := time.Time{}
|
|
||||||
return message.Stamp != t
|
|
||||||
}
|
|
||||||
|
@ -1,170 +0,0 @@
|
|||||||
package bzulip
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"io/ioutil"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/42wim/matterbridge/bridge"
|
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
|
||||||
gzb "github.com/matterbridge/gozulipbot"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Bzulip struct {
|
|
||||||
q *gzb.Queue
|
|
||||||
bot *gzb.Bot
|
|
||||||
streams map[int]string
|
|
||||||
*bridge.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(cfg *bridge.Config) bridge.Bridger {
|
|
||||||
return &Bzulip{Config: cfg, streams: make(map[int]string)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bzulip) Connect() error {
|
|
||||||
bot := gzb.Bot{APIKey: b.GetString("token"), APIURL: b.GetString("server") + "/api/v1/", Email: b.GetString("login")}
|
|
||||||
bot.Init()
|
|
||||||
q, err := bot.RegisterAll()
|
|
||||||
b.q = q
|
|
||||||
b.bot = &bot
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("Connect() %#v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// init stream
|
|
||||||
b.getChannel(0)
|
|
||||||
b.Log.Info("Connection succeeded")
|
|
||||||
go b.handleQueue()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bzulip) Disconnect() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bzulip) JoinChannel(channel config.ChannelInfo) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bzulip) Send(msg config.Message) (string, error) {
|
|
||||||
b.Log.Debugf("=> Receiving %#v", msg)
|
|
||||||
|
|
||||||
// Delete message
|
|
||||||
if msg.Event == config.EVENT_MSG_DELETE {
|
|
||||||
if msg.ID == "" {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
_, err := b.bot.UpdateMessage(msg.ID, "")
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload a file if it exists
|
|
||||||
if msg.Extra != nil {
|
|
||||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
|
||||||
b.sendMessage(rmsg)
|
|
||||||
}
|
|
||||||
if len(msg.Extra["file"]) > 0 {
|
|
||||||
return b.handleUploadFile(&msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// edit the message if we have a msg ID
|
|
||||||
if msg.ID != "" {
|
|
||||||
_, err := b.bot.UpdateMessage(msg.ID, msg.Username+msg.Text)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Post normal message
|
|
||||||
return b.sendMessage(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bzulip) getChannel(id int) string {
|
|
||||||
if name, ok := b.streams[id]; ok {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
streams, err := b.bot.GetRawStreams()
|
|
||||||
if err != nil {
|
|
||||||
b.Log.Errorf("getChannel: %#v", err)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
for _, stream := range streams.Streams {
|
|
||||||
b.streams[stream.StreamID] = stream.Name
|
|
||||||
}
|
|
||||||
if name, ok := b.streams[id]; ok {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bzulip) handleQueue() error {
|
|
||||||
for {
|
|
||||||
messages, _ := b.q.GetEvents()
|
|
||||||
for _, m := range messages {
|
|
||||||
b.Log.Debugf("== Receiving %#v", m)
|
|
||||||
// ignore our own messages
|
|
||||||
if m.SenderEmail == b.GetString("login") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rmsg := config.Message{Username: m.SenderFullName, Text: m.Content, Channel: b.getChannel(m.StreamID), Account: b.Account, UserID: strconv.Itoa(m.SenderID), Avatar: m.AvatarURL}
|
|
||||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account)
|
|
||||||
b.Log.Debugf("<= Message is %#v", rmsg)
|
|
||||||
b.Remote <- rmsg
|
|
||||||
b.q.LastEventID = m.ID
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second * 3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bzulip) sendMessage(msg config.Message) (string, error) {
|
|
||||||
topic := "matterbridge"
|
|
||||||
if b.GetString("topic") != "" {
|
|
||||||
topic = b.GetString("topic")
|
|
||||||
}
|
|
||||||
m := gzb.Message{
|
|
||||||
Stream: msg.Channel,
|
|
||||||
Topic: topic,
|
|
||||||
Content: msg.Username + msg.Text,
|
|
||||||
}
|
|
||||||
resp, err := b.bot.Message(m)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if resp != nil {
|
|
||||||
defer resp.Body.Close()
|
|
||||||
res, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
var jr struct {
|
|
||||||
ID int `json:"id"`
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(res, &jr)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return strconv.Itoa(jr.ID), nil
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bzulip) handleUploadFile(msg *config.Message) (string, error) {
|
|
||||||
for _, f := range msg.Extra["file"] {
|
|
||||||
fi := f.(config.FileInfo)
|
|
||||||
if fi.Comment != "" {
|
|
||||||
msg.Text += fi.Comment + ": "
|
|
||||||
}
|
|
||||||
if fi.URL != "" {
|
|
||||||
msg.Text = fi.URL
|
|
||||||
if fi.Comment != "" {
|
|
||||||
msg.Text = fi.Comment + ": " + fi.URL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, err := b.sendMessage(*msg)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
109
changelog.md
109
changelog.md
@ -1,112 +1,3 @@
|
|||||||
# v1.10.1
|
|
||||||
## Bugfix
|
|
||||||
* general: updated irc/xmpp/telegram libraries
|
|
||||||
* mattermost/slack/rocketchat: Fix iconurl regression. Closes #430
|
|
||||||
* mattermost/slack: Use uuid instead of userid. Fixes #429
|
|
||||||
* slack: Avatar spoofing from Slack to Discord with uppercase in nick doesn't work (#433)
|
|
||||||
* irc: Fix format string bug (irc) (#428)
|
|
||||||
* irc: Colorize username sent to IRC using its crc32 IEEE checksum (#423). See `ColorNicks` in matterbridge.toml.sample
|
|
||||||
* irc: Add support for CJK to/from utf-8 (irc). #400
|
|
||||||
* telegram: Add QuoteFormat option (telegram). Closes #41. See `QuoteFormat` in matterbridge.toml.sample
|
|
||||||
* xmpp: Send attached files to XMPP in different message with OOB data and without body (#421)
|
|
||||||
|
|
||||||
# v1.10.0
|
|
||||||
## New features
|
|
||||||
* general: Add support for reloading all settings automatically after changing config except connection and gateway configuration. Closes #373
|
|
||||||
* zulip: New protocol support added (https://zulipchat.com)
|
|
||||||
|
|
||||||
## Enhancements
|
|
||||||
* general: Handle file comment better
|
|
||||||
* steam: Handle file uploads to mediaserver (steam)
|
|
||||||
* slack: Properly set Slack user who initiated slash command (#394)
|
|
||||||
|
|
||||||
## Bugfix
|
|
||||||
* general: Use only alphanumeric for file uploads to mediaserver. Closes #416
|
|
||||||
* general: Fix crash on invalid filenames
|
|
||||||
* general: Fix regression in ReplaceMessages and ReplaceNicks. Closes #407
|
|
||||||
* telegram: Fix possible nil when using channels (telegram). #410
|
|
||||||
* telegram: Fix panic (telegram). Closes #410
|
|
||||||
* telegram: Handle channel posts correctly
|
|
||||||
* mattermost: Update GetFileLinks to API_V4
|
|
||||||
|
|
||||||
# v1.9.1
|
|
||||||
## New features
|
|
||||||
* telegram: Add QuoteDisable option (telegram). Closes #399. See QuoteDisable in matterbridge.toml.sample
|
|
||||||
## Enhancements
|
|
||||||
* discord: Send mediaserver link to Discord in Webhook mode (discord) (#405)
|
|
||||||
* mattermost: Print list of valid team names when team not found (#390)
|
|
||||||
* slack: Strip markdown URLs with blank text (slack) (#392)
|
|
||||||
## Bugfix
|
|
||||||
* slack/mattermost: Make our callbackid more unique. Fixes issue with running multiple matterbridge on the same channel (slack,mattermost)
|
|
||||||
* telegram: fix newlines in multiline messages #399
|
|
||||||
* telegram: Revert #378
|
|
||||||
|
|
||||||
# v1.9.0 (the refactor release)
|
|
||||||
## New features
|
|
||||||
* general: better debug messages
|
|
||||||
* general: better support for environment variables override
|
|
||||||
* general: Ability to disable sending join/leave messages to other gateways. #382
|
|
||||||
* slack: Allow Slack @usergroups to be parsed as human-friendly names #379
|
|
||||||
* slack: Provide better context for shared posts from Slack<=>Slack enhancement #369
|
|
||||||
* telegram: Convert nicks automatically into HTML when MessageFormat is set to HTML #378
|
|
||||||
* irc: Add DebugLevel option
|
|
||||||
|
|
||||||
## Bugfix
|
|
||||||
* slack: Ignore restricted_action on channel join (slack). Closes #387
|
|
||||||
* slack: Add slack attachment support to matterhook
|
|
||||||
* slack: Update userlist on join (slack). Closes #372
|
|
||||||
|
|
||||||
# v1.8.0
|
|
||||||
## New features
|
|
||||||
* general: Send chat notification if media is too big to be re-uploaded to MediaServer. See #359
|
|
||||||
* general: Download (and upload) avatar images from mattermost and telegram when mediaserver is configured. Closes #362
|
|
||||||
* general: Add label support in RemoteNickFormat
|
|
||||||
* general: Prettier info/debug log output
|
|
||||||
* mattermost: Download files and reupload to supported bridges (mattermost). Closes #357
|
|
||||||
* slack: Add ShowTopicChange option. Allow/disable topic change messages (currently only from slack). Closes #353
|
|
||||||
* slack: Add support for file comments (slack). Closes #346
|
|
||||||
* telegram: Add comment to file upload from telegram. Show comments on all bridges. Closes #358
|
|
||||||
* telegram: Add markdown support (telegram). #355
|
|
||||||
* api: Give api access to whole config.Message (and events). Closes #374
|
|
||||||
|
|
||||||
## Bugfix
|
|
||||||
* discord: Check for a valid WebhookURL (discord). Closes #367
|
|
||||||
* discord: Fix role mention replace issues
|
|
||||||
* irc: Truncate messages sent to IRC based on byte count (#368)
|
|
||||||
* mattermost: Add file download urls also to mattermost webhooks #356
|
|
||||||
* telegram: Fix panic on nil messages (telegram). Closes #366
|
|
||||||
* telegram: Fix the UseInsecureURL text (telegram). Closes #184
|
|
||||||
|
|
||||||
# v1.7.1
|
|
||||||
## Bugfix
|
|
||||||
* telegram: Enable Long Polling for Telegram. Reduces bandwidth consumption. (#350)
|
|
||||||
|
|
||||||
# v1.7.0
|
|
||||||
## New features
|
|
||||||
* matrix: Add support for deleting messages from/to matrix (matrix). Closes #320
|
|
||||||
* xmpp: Ignore <subject> messages (xmpp). #272
|
|
||||||
* irc: Add twitch support (irc) to README / wiki
|
|
||||||
|
|
||||||
## Bugfix
|
|
||||||
* general: Change RemoteNickFormat replacement order. Closes #336
|
|
||||||
* general: Make edits/delete work for bridges that gets reused. Closes #342
|
|
||||||
* general: Lowercase irc channels in config. Closes #348
|
|
||||||
* matrix: Fix possible panics (matrix). Closes #333
|
|
||||||
* matrix: Add an extension to images without one (matrix). #331
|
|
||||||
* api: Obey the Gateway value from the json (api). Closes #344
|
|
||||||
* xmpp: Print only debug messages when specified (xmpp). Closes #345
|
|
||||||
* xmpp: Allow xmpp to receive the extra messages (file uploads) when text is empty. #295
|
|
||||||
|
|
||||||
# v1.6.3
|
|
||||||
## Bugfix
|
|
||||||
* slack: Fix connection issues
|
|
||||||
* slack: Add more debug messages
|
|
||||||
* irc: Convert received IRC channel names to lowercase. Fixes #329 (#330)
|
|
||||||
|
|
||||||
# v1.6.2
|
|
||||||
## Bugfix
|
|
||||||
* mattermost: Crashes while connecting to Mattermost (regression). Closes #327
|
|
||||||
|
|
||||||
# v1.6.1
|
# v1.6.1
|
||||||
## Bugfix
|
## Bugfix
|
||||||
* general: Display of nicks not longer working (regression). Closes #323
|
* general: Display of nicks not longer working (regression). Closes #323
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
go version |grep go1.10 || exit
|
go version |grep go1.9 || exit
|
||||||
VERSION=$(git describe --tags)
|
VERSION=$(git describe --tags)
|
||||||
mkdir ci/binaries
|
mkdir ci/binaries
|
||||||
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-windows-amd64.exe
|
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-windows-amd64.exe
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
FROM cmosh/alpine-arm:edge
|
|
||||||
ENTRYPOINT ["/bin/matterbridge"]
|
|
||||||
|
|
||||||
COPY . /go/src/github.com/42wim/matterbridge
|
|
||||||
RUN apk update && apk add go git gcc musl-dev ca-certificates \
|
|
||||||
&& cd /go/src/github.com/42wim/matterbridge \
|
|
||||||
&& export GOPATH=/go \
|
|
||||||
&& go get \
|
|
||||||
&& go build -x -ldflags "-X main.githash=$(git log --pretty=format:'%h' -n 1)" -o /bin/matterbridge \
|
|
||||||
&& rm -rf /go \
|
|
||||||
&& apk del --purge git go gcc musl-dev
|
|
@ -4,27 +4,13 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/42wim/matterbridge/bridge"
|
"github.com/42wim/matterbridge/bridge"
|
||||||
"github.com/42wim/matterbridge/bridge/api"
|
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/discord"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/42wim/matterbridge/bridge/gitter"
|
|
||||||
"github.com/42wim/matterbridge/bridge/irc"
|
|
||||||
"github.com/42wim/matterbridge/bridge/matrix"
|
|
||||||
"github.com/42wim/matterbridge/bridge/mattermost"
|
|
||||||
"github.com/42wim/matterbridge/bridge/rocketchat"
|
|
||||||
"github.com/42wim/matterbridge/bridge/slack"
|
|
||||||
"github.com/42wim/matterbridge/bridge/sshchat"
|
|
||||||
"github.com/42wim/matterbridge/bridge/steam"
|
|
||||||
"github.com/42wim/matterbridge/bridge/telegram"
|
|
||||||
"github.com/42wim/matterbridge/bridge/xmpp"
|
|
||||||
"github.com/42wim/matterbridge/bridge/zulip"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
// "github.com/davecgh/go-spew/spew"
|
// "github.com/davecgh/go-spew/spew"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"github.com/hashicorp/golang-lru"
|
"github.com/hashicorp/golang-lru"
|
||||||
"github.com/peterhellberg/emojilib"
|
"github.com/peterhellberg/emojilib"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -43,31 +29,8 @@ type Gateway struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type BrMsgID struct {
|
type BrMsgID struct {
|
||||||
br *bridge.Bridge
|
br *bridge.Bridge
|
||||||
ID string
|
ID string
|
||||||
ChannelID string
|
|
||||||
}
|
|
||||||
|
|
||||||
var flog *log.Entry
|
|
||||||
|
|
||||||
var bridgeMap = map[string]bridge.Factory{
|
|
||||||
"api": api.New,
|
|
||||||
"discord": bdiscord.New,
|
|
||||||
"gitter": bgitter.New,
|
|
||||||
"irc": birc.New,
|
|
||||||
"mattermost": bmattermost.New,
|
|
||||||
"matrix": bmatrix.New,
|
|
||||||
"rocketchat": brocketchat.New,
|
|
||||||
"slack": bslack.New,
|
|
||||||
"sshchat": bsshchat.New,
|
|
||||||
"steam": bsteam.New,
|
|
||||||
"telegram": btelegram.New,
|
|
||||||
"xmpp": bxmpp.New,
|
|
||||||
"zulip": bzulip.New,
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
flog = log.WithFields(log.Fields{"prefix": "gateway"})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(cfg config.Gateway, r *Router) *Gateway {
|
func New(cfg config.Gateway, r *Router) *Gateway {
|
||||||
@ -82,14 +45,7 @@ func New(cfg config.Gateway, r *Router) *Gateway {
|
|||||||
func (gw *Gateway) AddBridge(cfg *config.Bridge) error {
|
func (gw *Gateway) AddBridge(cfg *config.Bridge) error {
|
||||||
br := gw.Router.getBridge(cfg.Account)
|
br := gw.Router.getBridge(cfg.Account)
|
||||||
if br == nil {
|
if br == nil {
|
||||||
br = bridge.New(cfg)
|
br = bridge.New(gw.Config, cfg, gw.Message)
|
||||||
br.Config = gw.Router.Config
|
|
||||||
br.General = &gw.General
|
|
||||||
// set logging
|
|
||||||
br.Log = log.WithFields(log.Fields{"prefix": "bridge"})
|
|
||||||
brconfig := &bridge.Config{Remote: gw.Message, Log: log.WithFields(log.Fields{"prefix": br.Protocol}), Bridge: br}
|
|
||||||
// add the actual bridger for this protocol to this bridge using the bridgeMap
|
|
||||||
br.Bridger = bridgeMap[br.Protocol](brconfig)
|
|
||||||
}
|
}
|
||||||
gw.mapChannelsToBridge(br)
|
gw.mapChannelsToBridge(br)
|
||||||
gw.Bridges[cfg.Account] = br
|
gw.Bridges[cfg.Account] = br
|
||||||
@ -121,10 +77,10 @@ func (gw *Gateway) reconnectBridge(br *bridge.Bridge) {
|
|||||||
br.Disconnect()
|
br.Disconnect()
|
||||||
time.Sleep(time.Second * 5)
|
time.Sleep(time.Second * 5)
|
||||||
RECONNECT:
|
RECONNECT:
|
||||||
flog.Infof("Reconnecting %s", br.Account)
|
log.Infof("Reconnecting %s", br.Account)
|
||||||
err := br.Connect()
|
err := br.Connect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
flog.Errorf("Reconnection failed: %s. Trying again in 60 seconds", err)
|
log.Errorf("Reconnection failed: %s. Trying again in 60 seconds", err)
|
||||||
time.Sleep(time.Second * 60)
|
time.Sleep(time.Second * 60)
|
||||||
goto RECONNECT
|
goto RECONNECT
|
||||||
}
|
}
|
||||||
@ -137,10 +93,6 @@ func (gw *Gateway) mapChannelConfig(cfg []config.Bridge, direction string) {
|
|||||||
if isApi(br.Account) {
|
if isApi(br.Account) {
|
||||||
br.Channel = "api"
|
br.Channel = "api"
|
||||||
}
|
}
|
||||||
// make sure to lowercase irc channels in config #348
|
|
||||||
if strings.HasPrefix(br.Account, "irc.") {
|
|
||||||
br.Channel = strings.ToLower(br.Channel)
|
|
||||||
}
|
|
||||||
ID := br.Channel + br.Account
|
ID := br.Channel + br.Account
|
||||||
if _, ok := gw.Channels[ID]; !ok {
|
if _, ok := gw.Channels[ID]; !ok {
|
||||||
channel := &config.ChannelInfo{Name: br.Channel, Direction: direction, ID: ID, Options: br.Options, Account: br.Account,
|
channel := &config.ChannelInfo{Name: br.Channel, Direction: direction, ID: ID, Options: br.Options, Account: br.Account,
|
||||||
@ -166,12 +118,6 @@ func (gw *Gateway) mapChannels() error {
|
|||||||
|
|
||||||
func (gw *Gateway) getDestChannel(msg *config.Message, dest bridge.Bridge) []config.ChannelInfo {
|
func (gw *Gateway) getDestChannel(msg *config.Message, dest bridge.Bridge) []config.ChannelInfo {
|
||||||
var channels []config.ChannelInfo
|
var channels []config.ChannelInfo
|
||||||
|
|
||||||
// for messages received from the api check that the gateway is the specified one
|
|
||||||
if msg.Protocol == "api" && gw.Name != msg.Gateway {
|
|
||||||
return channels
|
|
||||||
}
|
|
||||||
|
|
||||||
// if source channel is in only, do nothing
|
// if source channel is in only, do nothing
|
||||||
for _, channel := range gw.Channels {
|
for _, channel := range gw.Channels {
|
||||||
// lookup the channel from the message
|
// lookup the channel from the message
|
||||||
@ -188,7 +134,7 @@ func (gw *Gateway) getDestChannel(msg *config.Message, dest bridge.Bridge) []con
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// do samechannelgateway flogic
|
// do samechannelgateway logic
|
||||||
if channel.SameChannel[msg.Gateway] {
|
if channel.SameChannel[msg.Gateway] {
|
||||||
if msg.Channel == channel.Name && msg.Account != dest.Account {
|
if msg.Channel == channel.Name && msg.Account != dest.Account {
|
||||||
channels = append(channels, *channel)
|
channels = append(channels, *channel)
|
||||||
@ -205,55 +151,38 @@ func (gw *Gateway) getDestChannel(msg *config.Message, dest bridge.Bridge) []con
|
|||||||
func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrMsgID {
|
func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrMsgID {
|
||||||
var brMsgIDs []*BrMsgID
|
var brMsgIDs []*BrMsgID
|
||||||
|
|
||||||
// if we have an attached file, or other info
|
// TODO refactor
|
||||||
|
// only slack now, check will have to be done in the different bridges.
|
||||||
|
// we need to check if we can't use fallback or text in other bridges
|
||||||
if msg.Extra != nil {
|
if msg.Extra != nil {
|
||||||
if len(msg.Extra[config.EVENT_FILE_FAILURE_SIZE]) != 0 {
|
if dest.Protocol != "discord" &&
|
||||||
|
dest.Protocol != "slack" &&
|
||||||
|
dest.Protocol != "mattermost" &&
|
||||||
|
dest.Protocol != "telegram" &&
|
||||||
|
dest.Protocol != "matrix" {
|
||||||
if msg.Text == "" {
|
if msg.Text == "" {
|
||||||
return brMsgIDs
|
return brMsgIDs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// only relay join/part when configged
|
||||||
// Avatar downloads are only relevant for telegram and mattermost for now
|
if msg.Event == config.EVENT_JOIN_LEAVE && !gw.Bridges[dest.Account].Config.ShowJoinPart {
|
||||||
if msg.Event == config.EVENT_AVATAR_DOWNLOAD {
|
|
||||||
if dest.Protocol != "mattermost" &&
|
|
||||||
dest.Protocol != "telegram" {
|
|
||||||
return brMsgIDs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// only relay join/part when configured
|
|
||||||
if msg.Event == config.EVENT_JOIN_LEAVE && !gw.Bridges[dest.Account].GetBool("ShowJoinPart") {
|
|
||||||
return brMsgIDs
|
return brMsgIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
// only relay topic change when configured
|
|
||||||
if msg.Event == config.EVENT_TOPIC_CHANGE && !gw.Bridges[dest.Account].GetBool("ShowTopicChange") {
|
|
||||||
return brMsgIDs
|
|
||||||
}
|
|
||||||
|
|
||||||
// broadcast to every out channel (irc QUIT)
|
// broadcast to every out channel (irc QUIT)
|
||||||
if msg.Channel == "" && msg.Event != config.EVENT_JOIN_LEAVE {
|
if msg.Channel == "" && msg.Event != config.EVENT_JOIN_LEAVE {
|
||||||
flog.Debug("empty channel")
|
log.Debug("empty channel")
|
||||||
return brMsgIDs
|
return brMsgIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
originchannel := msg.Channel
|
originchannel := msg.Channel
|
||||||
origmsg := msg
|
origmsg := msg
|
||||||
channels := gw.getDestChannel(&msg, *dest)
|
channels := gw.getDestChannel(&msg, *dest)
|
||||||
for _, channel := range channels {
|
for _, channel := range channels {
|
||||||
// Only send the avatar download event to ourselves.
|
// do not send to ourself
|
||||||
if msg.Event == config.EVENT_AVATAR_DOWNLOAD {
|
if channel.ID == getChannelID(origmsg) {
|
||||||
if channel.ID != getChannelID(origmsg) {
|
continue
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// do not send to ourself for any other event
|
|
||||||
if channel.ID == getChannelID(origmsg) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
flog.Debugf("=> Sending %#v from %s (%s) to %s (%s)", msg, msg.Account, originchannel, dest.Account, channel.Name)
|
log.Debugf("Sending %#v from %s (%s) to %s (%s)", msg, msg.Account, originchannel, dest.Account, channel.Name)
|
||||||
msg.Channel = channel.Name
|
msg.Channel = channel.Name
|
||||||
msg.Avatar = gw.modifyAvatar(origmsg, dest)
|
msg.Avatar = gw.modifyAvatar(origmsg, dest)
|
||||||
msg.Username = gw.modifyUsername(origmsg, dest)
|
msg.Username = gw.modifyUsername(origmsg, dest)
|
||||||
@ -261,9 +190,7 @@ func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrM
|
|||||||
if res, ok := gw.Messages.Get(origmsg.ID); ok {
|
if res, ok := gw.Messages.Get(origmsg.ID); ok {
|
||||||
IDs := res.([]*BrMsgID)
|
IDs := res.([]*BrMsgID)
|
||||||
for _, id := range IDs {
|
for _, id := range IDs {
|
||||||
// check protocol, bridge name and channelname
|
if dest.Protocol == id.br.Protocol {
|
||||||
// for people that reuse the same bridge multiple times. see #342
|
|
||||||
if dest.Protocol == id.br.Protocol && dest.Name == id.br.Name && channel.ID == id.ChannelID {
|
|
||||||
msg.ID = id.ID
|
msg.ID = id.ID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -274,12 +201,11 @@ func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrM
|
|||||||
}
|
}
|
||||||
mID, err := dest.Send(msg)
|
mID, err := dest.Send(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
flog.Error(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
// append the message ID (mID) from this bridge (dest) to our brMsgIDs slice
|
// append the message ID (mID) from this bridge (dest) to our brMsgIDs slice
|
||||||
if mID != "" {
|
if mID != "" {
|
||||||
flog.Debugf("mID %s: %s", dest.Account, mID)
|
brMsgIDs = append(brMsgIDs, &BrMsgID{dest, mID})
|
||||||
brMsgIDs = append(brMsgIDs, &BrMsgID{dest, mID, channel.ID})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return brMsgIDs
|
return brMsgIDs
|
||||||
@ -290,39 +216,30 @@ func (gw *Gateway) ignoreMessage(msg *config.Message) bool {
|
|||||||
if _, ok := gw.Bridges[msg.Account]; !ok {
|
if _, ok := gw.Bridges[msg.Account]; !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if we need to ignore a empty message
|
|
||||||
if msg.Text == "" {
|
if msg.Text == "" {
|
||||||
// we have an attachment or actual bytes, do not ignore
|
// we have an attachment or actual bytes
|
||||||
if msg.Extra != nil &&
|
if msg.Extra != nil && (msg.Extra["attachments"] != nil || len(msg.Extra["file"]) > 0) {
|
||||||
(msg.Extra["attachments"] != nil ||
|
|
||||||
len(msg.Extra["file"]) > 0 ||
|
|
||||||
len(msg.Extra[config.EVENT_FILE_FAILURE_SIZE]) > 0) {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
flog.Debugf("ignoring empty message %#v from %s", msg, msg.Account)
|
log.Debugf("ignoring empty message %#v from %s", msg, msg.Account)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
for _, entry := range strings.Fields(gw.Bridges[msg.Account].Config.IgnoreNicks) {
|
||||||
// is the username in IgnoreNicks field
|
|
||||||
for _, entry := range strings.Fields(gw.Bridges[msg.Account].GetString("IgnoreNicks")) {
|
|
||||||
if msg.Username == entry {
|
if msg.Username == entry {
|
||||||
flog.Debugf("ignoring %s from %s", msg.Username, msg.Account)
|
log.Debugf("ignoring %s from %s", msg.Username, msg.Account)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// does the message match regex in IgnoreMessages field
|
|
||||||
// TODO do not compile regexps everytime
|
// TODO do not compile regexps everytime
|
||||||
for _, entry := range strings.Fields(gw.Bridges[msg.Account].GetString("IgnoreMessages")) {
|
for _, entry := range strings.Fields(gw.Bridges[msg.Account].Config.IgnoreMessages) {
|
||||||
if entry != "" {
|
if entry != "" {
|
||||||
re, err := regexp.Compile(entry)
|
re, err := regexp.Compile(entry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
flog.Errorf("incorrect regexp %s for %s", entry, msg.Account)
|
log.Errorf("incorrect regexp %s for %s", entry, msg.Account)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if re.MatchString(msg.Text) {
|
if re.MatchString(msg.Text) {
|
||||||
flog.Debugf("matching %s. ignoring %s from %s", entry, msg.Text, msg.Account)
|
log.Debugf("matching %s. ignoring %s from %s", entry, msg.Text, msg.Account)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -333,23 +250,23 @@ func (gw *Gateway) ignoreMessage(msg *config.Message) bool {
|
|||||||
func (gw *Gateway) modifyUsername(msg config.Message, dest *bridge.Bridge) string {
|
func (gw *Gateway) modifyUsername(msg config.Message, dest *bridge.Bridge) string {
|
||||||
br := gw.Bridges[msg.Account]
|
br := gw.Bridges[msg.Account]
|
||||||
msg.Protocol = br.Protocol
|
msg.Protocol = br.Protocol
|
||||||
if gw.Config.General.StripNick || dest.GetBool("StripNick") {
|
if gw.Config.General.StripNick || dest.Config.StripNick {
|
||||||
re := regexp.MustCompile("[^a-zA-Z0-9]+")
|
re := regexp.MustCompile("[^a-zA-Z0-9]+")
|
||||||
msg.Username = re.ReplaceAllString(msg.Username, "")
|
msg.Username = re.ReplaceAllString(msg.Username, "")
|
||||||
}
|
}
|
||||||
nick := dest.GetString("RemoteNickFormat")
|
nick := dest.Config.RemoteNickFormat
|
||||||
if nick == "" {
|
if nick == "" {
|
||||||
nick = gw.Config.General.RemoteNickFormat
|
nick = gw.Config.General.RemoteNickFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
// loop to replace nicks
|
// loop to replace nicks
|
||||||
for _, outer := range br.GetStringSlice2D("ReplaceNicks") {
|
for _, outer := range br.Config.ReplaceNicks {
|
||||||
search := outer[0]
|
search := outer[0]
|
||||||
replace := outer[1]
|
replace := outer[1]
|
||||||
// TODO move compile to bridge init somewhere
|
// TODO move compile to bridge init somewhere
|
||||||
re, err := regexp.Compile(search)
|
re, err := regexp.Compile(search)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
flog.Errorf("regexp in %s failed: %s", msg.Account, err)
|
log.Errorf("regexp in %s failed: %s", msg.Account, err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
msg.Username = re.ReplaceAllString(msg.Username, replace)
|
msg.Username = re.ReplaceAllString(msg.Username, replace)
|
||||||
@ -367,18 +284,16 @@ func (gw *Gateway) modifyUsername(msg config.Message, dest *bridge.Bridge) strin
|
|||||||
}
|
}
|
||||||
nick = strings.Replace(nick, "{NOPINGNICK}", msg.Username[:i]+""+msg.Username[i:], -1)
|
nick = strings.Replace(nick, "{NOPINGNICK}", msg.Username[:i]+""+msg.Username[i:], -1)
|
||||||
}
|
}
|
||||||
|
nick = strings.Replace(nick, "{NICK}", msg.Username, -1)
|
||||||
nick = strings.Replace(nick, "{BRIDGE}", br.Name, -1)
|
nick = strings.Replace(nick, "{BRIDGE}", br.Name, -1)
|
||||||
nick = strings.Replace(nick, "{PROTOCOL}", br.Protocol, -1)
|
nick = strings.Replace(nick, "{PROTOCOL}", br.Protocol, -1)
|
||||||
nick = strings.Replace(nick, "{LABEL}", br.GetString("Label"), -1)
|
|
||||||
nick = strings.Replace(nick, "{NICK}", msg.Username, -1)
|
|
||||||
return nick
|
return nick
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gw *Gateway) modifyAvatar(msg config.Message, dest *bridge.Bridge) string {
|
func (gw *Gateway) modifyAvatar(msg config.Message, dest *bridge.Bridge) string {
|
||||||
iconurl := gw.Config.General.IconURL
|
iconurl := gw.Config.General.IconURL
|
||||||
if iconurl == "" {
|
if iconurl == "" {
|
||||||
iconurl = dest.GetString("IconURL")
|
iconurl = dest.Config.IconURL
|
||||||
}
|
}
|
||||||
iconurl = strings.Replace(iconurl, "{NICK}", msg.Username, -1)
|
iconurl = strings.Replace(iconurl, "{NICK}", msg.Username, -1)
|
||||||
if msg.Avatar == "" {
|
if msg.Avatar == "" {
|
||||||
@ -390,78 +305,58 @@ func (gw *Gateway) modifyAvatar(msg config.Message, dest *bridge.Bridge) string
|
|||||||
func (gw *Gateway) modifyMessage(msg *config.Message) {
|
func (gw *Gateway) modifyMessage(msg *config.Message) {
|
||||||
// replace :emoji: to unicode
|
// replace :emoji: to unicode
|
||||||
msg.Text = emojilib.Replace(msg.Text)
|
msg.Text = emojilib.Replace(msg.Text)
|
||||||
|
|
||||||
br := gw.Bridges[msg.Account]
|
br := gw.Bridges[msg.Account]
|
||||||
// loop to replace messages
|
// loop to replace messages
|
||||||
for _, outer := range br.GetStringSlice2D("ReplaceMessages") {
|
for _, outer := range br.Config.ReplaceMessages {
|
||||||
search := outer[0]
|
search := outer[0]
|
||||||
replace := outer[1]
|
replace := outer[1]
|
||||||
// TODO move compile to bridge init somewhere
|
// TODO move compile to bridge init somewhere
|
||||||
re, err := regexp.Compile(search)
|
re, err := regexp.Compile(search)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
flog.Errorf("regexp in %s failed: %s", msg.Account, err)
|
log.Errorf("regexp in %s failed: %s", msg.Account, err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
msg.Text = re.ReplaceAllString(msg.Text, replace)
|
msg.Text = re.ReplaceAllString(msg.Text, replace)
|
||||||
}
|
}
|
||||||
|
msg.Gateway = gw.Name
|
||||||
// messages from api have Gateway specified, don't overwrite
|
|
||||||
if msg.Protocol != "api" {
|
|
||||||
msg.Gateway = gw.Name
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gw *Gateway) handleFiles(msg *config.Message) {
|
func (gw *Gateway) handleFiles(msg *config.Message) {
|
||||||
reg := regexp.MustCompile("[^a-zA-Z0-9]+")
|
|
||||||
// if we don't have a attachfield or we don't have a mediaserver configured return
|
|
||||||
if msg.Extra == nil || gw.Config.General.MediaServerUpload == "" {
|
if msg.Extra == nil || gw.Config.General.MediaServerUpload == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we actually have files, start uploading them to the mediaserver
|
|
||||||
if len(msg.Extra["file"]) > 0 {
|
if len(msg.Extra["file"]) > 0 {
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Timeout: time.Second * 5,
|
Timeout: time.Second * 5,
|
||||||
}
|
}
|
||||||
for i, f := range msg.Extra["file"] {
|
for i, f := range msg.Extra["file"] {
|
||||||
fi := f.(config.FileInfo)
|
fi := f.(config.FileInfo)
|
||||||
ext := filepath.Ext(fi.Name)
|
|
||||||
fi.Name = fi.Name[0 : len(fi.Name)-len(ext)]
|
|
||||||
fi.Name = reg.ReplaceAllString(fi.Name, "_")
|
|
||||||
fi.Name = fi.Name + ext
|
|
||||||
sha1sum := fmt.Sprintf("%x", sha1.Sum(*fi.Data))
|
sha1sum := fmt.Sprintf("%x", sha1.Sum(*fi.Data))
|
||||||
reader := bytes.NewReader(*fi.Data)
|
reader := bytes.NewReader(*fi.Data)
|
||||||
url := gw.Config.General.MediaServerUpload + "/" + sha1sum + "/" + fi.Name
|
url := gw.Config.General.MediaServerUpload + "/" + sha1sum + "/" + fi.Name
|
||||||
durl := gw.Config.General.MediaServerDownload + "/" + sha1sum + "/" + fi.Name
|
durl := gw.Config.General.MediaServerDownload + "/" + sha1sum + "/" + fi.Name
|
||||||
extra := msg.Extra["file"][i].(config.FileInfo)
|
extra := msg.Extra["file"][i].(config.FileInfo)
|
||||||
extra.URL = durl
|
extra.URL = durl
|
||||||
req, err := http.NewRequest("PUT", url, reader)
|
|
||||||
if err != nil {
|
|
||||||
flog.Errorf("mediaserver upload failed: %#v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
req.Header.Set("Content-Type", "binary/octet-stream")
|
|
||||||
_, err = client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
flog.Errorf("mediaserver upload failed: %#v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
flog.Debugf("mediaserver download URL = %s", durl)
|
|
||||||
// we uploaded the file successfully. Add the SHA
|
|
||||||
extra.SHA = sha1sum
|
|
||||||
msg.Extra["file"][i] = extra
|
msg.Extra["file"][i] = extra
|
||||||
|
req, _ := http.NewRequest("PUT", url, reader)
|
||||||
|
req.Header.Set("Content-Type", "binary/octet-stream")
|
||||||
|
_, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("mediaserver upload failed: %#v", err)
|
||||||
|
}
|
||||||
|
log.Debugf("mediaserver download URL = %s", durl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gw *Gateway) validGatewayDest(msg *config.Message, channel *config.ChannelInfo) bool {
|
|
||||||
return msg.Gateway == gw.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func getChannelID(msg config.Message) string {
|
func getChannelID(msg config.Message) string {
|
||||||
return msg.Channel + msg.Account
|
return msg.Channel + msg.Account
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gw *Gateway) validGatewayDest(msg *config.Message, channel *config.ChannelInfo) bool {
|
||||||
|
return msg.Gateway == gw.Name
|
||||||
|
}
|
||||||
|
|
||||||
func isApi(account string) bool {
|
func isApi(account string) bool {
|
||||||
return strings.HasPrefix(account, "api.")
|
return strings.HasPrefix(account, "api.")
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,14 @@ package gateway
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
|
"github.com/BurntSushi/toml"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testconfig = []byte(`
|
var testconfig = `
|
||||||
[irc.freenode]
|
[irc.freenode]
|
||||||
[mattermost.test]
|
[mattermost.test]
|
||||||
[gitter.42wim]
|
[gitter.42wim]
|
||||||
@ -36,9 +37,9 @@ var testconfig = []byte(`
|
|||||||
[[gateway.inout]]
|
[[gateway.inout]]
|
||||||
account="slack.test"
|
account="slack.test"
|
||||||
channel="testing"
|
channel="testing"
|
||||||
`)
|
`
|
||||||
|
|
||||||
var testconfig2 = []byte(`
|
var testconfig2 = `
|
||||||
[irc.freenode]
|
[irc.freenode]
|
||||||
[mattermost.test]
|
[mattermost.test]
|
||||||
[gitter.42wim]
|
[gitter.42wim]
|
||||||
@ -79,9 +80,8 @@ var testconfig2 = []byte(`
|
|||||||
[[gateway.out]]
|
[[gateway.out]]
|
||||||
account = "discord.test"
|
account = "discord.test"
|
||||||
channel = "general2"
|
channel = "general2"
|
||||||
`)
|
`
|
||||||
|
var testconfig3 = `
|
||||||
var testconfig3 = []byte(`
|
|
||||||
[irc.zzz]
|
[irc.zzz]
|
||||||
[telegram.zzz]
|
[telegram.zzz]
|
||||||
[slack.zzz]
|
[slack.zzz]
|
||||||
@ -149,10 +149,13 @@ enable=true
|
|||||||
[[gateway.inout]]
|
[[gateway.inout]]
|
||||||
account="telegram.zzz"
|
account="telegram.zzz"
|
||||||
channel="--333333333333"
|
channel="--333333333333"
|
||||||
`)
|
`
|
||||||
|
|
||||||
func maketestRouter(input []byte) *Router {
|
func maketestRouter(input string) *Router {
|
||||||
cfg := config.NewConfigFromString(input)
|
var cfg *config.Config
|
||||||
|
if _, err := toml.Decode(input, &cfg); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
r, err := NewRouter(cfg)
|
r, err := NewRouter(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
@ -160,7 +163,14 @@ func maketestRouter(input []byte) *Router {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
func TestNewRouter(t *testing.T) {
|
func TestNewRouter(t *testing.T) {
|
||||||
r := maketestRouter(testconfig)
|
var cfg *config.Config
|
||||||
|
if _, err := toml.Decode(testconfig, &cfg); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
r, err := NewRouter(cfg)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
assert.Equal(t, 1, len(r.Gateways))
|
assert.Equal(t, 1, len(r.Gateways))
|
||||||
assert.Equal(t, 4, len(r.Gateways["bridge1"].Bridges))
|
assert.Equal(t, 4, len(r.Gateways["bridge1"].Bridges))
|
||||||
assert.Equal(t, 4, len(r.Gateways["bridge1"].Channels))
|
assert.Equal(t, 4, len(r.Gateways["bridge1"].Channels))
|
||||||
|
@ -5,6 +5,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/gateway/samechannel"
|
"github.com/42wim/matterbridge/gateway/samechannel"
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
// "github.com/davecgh/go-spew/spew"
|
// "github.com/davecgh/go-spew/spew"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -16,7 +17,10 @@ type Router struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewRouter(cfg *config.Config) (*Router, error) {
|
func NewRouter(cfg *config.Config) (*Router, error) {
|
||||||
r := &Router{Message: make(chan config.Message), Gateways: make(map[string]*Gateway), Config: cfg}
|
r := &Router{}
|
||||||
|
r.Config = cfg
|
||||||
|
r.Message = make(chan config.Message)
|
||||||
|
r.Gateways = make(map[string]*Gateway)
|
||||||
sgw := samechannelgateway.New(cfg)
|
sgw := samechannelgateway.New(cfg)
|
||||||
gwconfigs := sgw.GetConfig()
|
gwconfigs := sgw.GetConfig()
|
||||||
|
|
||||||
@ -38,13 +42,12 @@ func NewRouter(cfg *config.Config) (*Router, error) {
|
|||||||
func (r *Router) Start() error {
|
func (r *Router) Start() error {
|
||||||
m := make(map[string]*bridge.Bridge)
|
m := make(map[string]*bridge.Bridge)
|
||||||
for _, gw := range r.Gateways {
|
for _, gw := range r.Gateways {
|
||||||
flog.Infof("Parsing gateway %s", gw.Name)
|
|
||||||
for _, br := range gw.Bridges {
|
for _, br := range gw.Bridges {
|
||||||
m[br.Account] = br
|
m[br.Account] = br
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, br := range m {
|
for _, br := range m {
|
||||||
flog.Infof("Starting bridge: %s ", br.Account)
|
log.Infof("Starting bridge: %s ", br.Account)
|
||||||
err := br.Connect()
|
err := br.Connect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Bridge %s failed to start: %v", br.Account, err)
|
return fmt.Errorf("Bridge %s failed to start: %v", br.Account, err)
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 77 KiB |
@ -5,21 +5,22 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/gateway"
|
"github.com/42wim/matterbridge/gateway"
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/google/gops/agent"
|
"github.com/google/gops/agent"
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
prefixed "github.com/x-cray/logrus-prefixed-formatter"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
version = "1.10.1"
|
version = "1.6.1"
|
||||||
githash string
|
githash string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.SetFormatter(&log.TextFormatter{FullTimestamp: true})
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.SetFormatter(&prefixed.TextFormatter{PrefixPadding: 13, DisableColors: true, FullTimestamp: true})
|
|
||||||
flog := log.WithFields(log.Fields{"prefix": "main"})
|
|
||||||
flagConfig := flag.String("conf", "matterbridge.toml", "config file")
|
flagConfig := flag.String("conf", "matterbridge.toml", "config file")
|
||||||
flagDebug := flag.Bool("debug", false, "enable debug")
|
flagDebug := flag.Bool("debug", false, "enable debug")
|
||||||
flagVersion := flag.Bool("version", false, "show version")
|
flagVersion := flag.Bool("version", false, "show version")
|
||||||
@ -34,24 +35,22 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if *flagDebug || os.Getenv("DEBUG") == "1" {
|
if *flagDebug || os.Getenv("DEBUG") == "1" {
|
||||||
log.SetFormatter(&prefixed.TextFormatter{PrefixPadding: 13, DisableColors: true, FullTimestamp: false, ForceFormatting: true})
|
log.Info("Enabling debug")
|
||||||
flog.Info("Enabling debug")
|
|
||||||
log.SetLevel(log.DebugLevel)
|
log.SetLevel(log.DebugLevel)
|
||||||
}
|
}
|
||||||
flog.Printf("Running version %s %s", version, githash)
|
log.Printf("Running version %s %s", version, githash)
|
||||||
if strings.Contains(version, "-dev") {
|
if strings.Contains(version, "-dev") {
|
||||||
flog.Println("WARNING: THIS IS A DEVELOPMENT VERSION. Things may break.")
|
log.Println("WARNING: THIS IS A DEVELOPMENT VERSION. Things may break.")
|
||||||
}
|
}
|
||||||
cfg := config.NewConfig(*flagConfig)
|
cfg := config.NewConfig(*flagConfig)
|
||||||
cfg.General.Debug = *flagDebug
|
|
||||||
r, err := gateway.NewRouter(cfg)
|
r, err := gateway.NewRouter(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
flog.Fatalf("Starting gateway failed: %s", err)
|
log.Fatalf("Starting gateway failed: %s", err)
|
||||||
}
|
}
|
||||||
err = r.Start()
|
err = r.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
flog.Fatalf("Starting gateway failed: %s", err)
|
log.Fatalf("Starting gateway failed: %s", err)
|
||||||
}
|
}
|
||||||
flog.Printf("Gateway(s) started succesfully. Now relaying messages")
|
log.Printf("Gateway(s) started succesfully. Now relaying messages")
|
||||||
select {}
|
select {}
|
||||||
}
|
}
|
||||||
|
@ -64,9 +64,6 @@ NickServPassword="secret"
|
|||||||
#OPTIONAL only used for quakenet auth
|
#OPTIONAL only used for quakenet auth
|
||||||
NickServUsername="username"
|
NickServUsername="username"
|
||||||
|
|
||||||
## RELOADABLE SETTINGS
|
|
||||||
## Settings below can be reloaded by editing the file
|
|
||||||
|
|
||||||
#Flood control
|
#Flood control
|
||||||
#Delay in milliseconds between each message send to the IRC server
|
#Delay in milliseconds between each message send to the IRC server
|
||||||
#OPTIONAL (default 1300)
|
#OPTIONAL (default 1300)
|
||||||
@ -92,10 +89,6 @@ MessageSplit=false
|
|||||||
#OPTIONAL (default 0)
|
#OPTIONAL (default 0)
|
||||||
RejoinDelay=0
|
RejoinDelay=0
|
||||||
|
|
||||||
#ColorNicks will show each nickname in a different color.
|
|
||||||
#Only works in IRC right now.
|
|
||||||
ColorNicks=false
|
|
||||||
|
|
||||||
#Nicks you want to ignore.
|
#Nicks you want to ignore.
|
||||||
#Messages from those users will not be sent to other bridges.
|
#Messages from those users will not be sent to other bridges.
|
||||||
#OPTIONAL
|
#OPTIONAL
|
||||||
@ -124,39 +117,24 @@ ReplaceMessages=[ ["cat","dog"] ]
|
|||||||
#optional (default empty)
|
#optional (default empty)
|
||||||
ReplaceNicks=[ ["user--","user"] ]
|
ReplaceNicks=[ ["user--","user"] ]
|
||||||
|
|
||||||
#extra label that can be used in the RemoteNickFormat
|
|
||||||
#optional (default empty)
|
|
||||||
Label=""
|
|
||||||
|
|
||||||
#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 / username.
|
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||||
#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 "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
||||||
#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. See https://github.com/42wim/matterbridge/issues/175 for more information
|
#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. See https://github.com/42wim/matterbridge/issues/175 for more information
|
||||||
#OPTIONAL (default empty)
|
#OPTIONAL (default empty)
|
||||||
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
|
#Only works hiding/show messages from irc and mattermost bridge for now
|
||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
ShowJoinPart=false
|
ShowJoinPart=false
|
||||||
|
|
||||||
#Do not send joins/parts to other bridges
|
|
||||||
#Currently works for messages from the following bridges: irc, mattermost, slack
|
|
||||||
#OPTIONAL (default false)
|
|
||||||
NoSendJoinPart=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)
|
||||||
StripNick=false
|
StripNick=false
|
||||||
|
|
||||||
#Enable to show topic changes from other bridges
|
|
||||||
#Only works hiding/show topic changes from slack bridge for now
|
|
||||||
#OPTIONAL (default false)
|
|
||||||
ShowTopicChange=false
|
|
||||||
|
|
||||||
###################################################################
|
###################################################################
|
||||||
#XMPP section
|
#XMPP section
|
||||||
###################################################################
|
###################################################################
|
||||||
@ -191,9 +169,6 @@ Nick="xmppbot"
|
|||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
SkipTLSVerify=true
|
SkipTLSVerify=true
|
||||||
|
|
||||||
## RELOADABLE SETTINGS
|
|
||||||
## Settings below can be reloaded by editing the file
|
|
||||||
|
|
||||||
#Nicks you want to ignore.
|
#Nicks you want to ignore.
|
||||||
#Messages from those users will not be sent to other bridges.
|
#Messages from those users will not be sent to other bridges.
|
||||||
#OPTIONAL
|
#OPTIONAL
|
||||||
@ -222,20 +197,15 @@ ReplaceMessages=[ ["cat","dog"] ]
|
|||||||
#OPTIONAL (default empty)
|
#OPTIONAL (default empty)
|
||||||
ReplaceNicks=[ ["user--","user"] ]
|
ReplaceNicks=[ ["user--","user"] ]
|
||||||
|
|
||||||
#extra label that can be used in the RemoteNickFormat
|
|
||||||
#optional (default empty)
|
|
||||||
Label=""
|
|
||||||
|
|
||||||
#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 / username.
|
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||||
#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 "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
||||||
#OPTIONAL (default empty)
|
#OPTIONAL (default empty)
|
||||||
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
|
#Only works hiding/show messages from irc and mattermost bridge for now
|
||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
ShowJoinPart=false
|
ShowJoinPart=false
|
||||||
|
|
||||||
@ -244,11 +214,6 @@ ShowJoinPart=false
|
|||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
StripNick=false
|
StripNick=false
|
||||||
|
|
||||||
#Enable to show topic changes from other bridges
|
|
||||||
#Only works hiding/show topic changes from slack bridge for now
|
|
||||||
#OPTIONAL (default false)
|
|
||||||
ShowTopicChange=false
|
|
||||||
|
|
||||||
###################################################################
|
###################################################################
|
||||||
#hipchat section
|
#hipchat section
|
||||||
###################################################################
|
###################################################################
|
||||||
@ -275,9 +240,6 @@ Muc="conf.hipchat.com"
|
|||||||
#REQUIRED
|
#REQUIRED
|
||||||
Nick="yourlogin"
|
Nick="yourlogin"
|
||||||
|
|
||||||
## RELOADABLE SETTINGS
|
|
||||||
## Settings below can be reloaded by editing the file
|
|
||||||
|
|
||||||
#Nicks you want to ignore.
|
#Nicks you want to ignore.
|
||||||
#Messages from those users will not be sent to other bridges.
|
#Messages from those users will not be sent to other bridges.
|
||||||
#OPTIONAL
|
#OPTIONAL
|
||||||
@ -306,20 +268,15 @@ ReplaceMessages=[ ["cat","dog"] ]
|
|||||||
#optional (default empty)
|
#optional (default empty)
|
||||||
ReplaceNicks=[ ["user--","user"] ]
|
ReplaceNicks=[ ["user--","user"] ]
|
||||||
|
|
||||||
#extra label that can be used in the RemoteNickFormat
|
|
||||||
#optional (default empty)
|
|
||||||
Label=""
|
|
||||||
|
|
||||||
#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 / username.
|
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||||
#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 "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
||||||
#OPTIONAL (default empty)
|
#OPTIONAL (default empty)
|
||||||
RemoteNickFormat="[{PROTOCOL}/{BRIDGE}] <{NICK}> "
|
RemoteNickFormat="[{PROTOCOL}/{BRIDGE}] <{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
|
#Only works hiding/show messages from irc and mattermost bridge for now
|
||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
ShowJoinPart=false
|
ShowJoinPart=false
|
||||||
|
|
||||||
@ -328,11 +285,6 @@ ShowJoinPart=false
|
|||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
StripNick=false
|
StripNick=false
|
||||||
|
|
||||||
#Enable to show topic changes from other bridges
|
|
||||||
#Only works hiding/show topic changes from slack bridge for now
|
|
||||||
#OPTIONAL (default false)
|
|
||||||
ShowTopicChange=false
|
|
||||||
|
|
||||||
###################################################################
|
###################################################################
|
||||||
#mattermost section
|
#mattermost section
|
||||||
###################################################################
|
###################################################################
|
||||||
@ -395,9 +347,6 @@ IconURL="http://youricon.png"
|
|||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
SkipTLSVerify=true
|
SkipTLSVerify=true
|
||||||
|
|
||||||
## RELOADABLE SETTINGS
|
|
||||||
## Settings below can be reloaded by editing the file
|
|
||||||
|
|
||||||
#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)
|
||||||
@ -450,38 +399,23 @@ ReplaceMessages=[ ["cat","dog"] ]
|
|||||||
#optional (default empty)
|
#optional (default empty)
|
||||||
ReplaceNicks=[ ["user--","user"] ]
|
ReplaceNicks=[ ["user--","user"] ]
|
||||||
|
|
||||||
#extra label that can be used in the RemoteNickFormat
|
|
||||||
#optional (default empty)
|
|
||||||
Label=""
|
|
||||||
|
|
||||||
#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 / username.
|
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||||
#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 "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
||||||
#OPTIONAL (default empty)
|
#OPTIONAL (default empty)
|
||||||
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
|
#Only works hiding/show messages from irc and mattermost bridge for now
|
||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
ShowJoinPart=false
|
ShowJoinPart=false
|
||||||
|
|
||||||
#Do not send joins/parts to other bridges
|
|
||||||
#Currently works for messages from the following bridges: irc, mattermost, slack
|
|
||||||
#OPTIONAL (default false)
|
|
||||||
NoSendJoinPart=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)
|
||||||
StripNick=false
|
StripNick=false
|
||||||
|
|
||||||
#Enable to show topic changes from other bridges
|
|
||||||
#Only works hiding/show topic changes from slack bridge for now
|
|
||||||
#OPTIONAL (default false)
|
|
||||||
ShowTopicChange=false
|
|
||||||
|
|
||||||
###################################################################
|
###################################################################
|
||||||
#Gitter section
|
#Gitter section
|
||||||
#Best to make a dedicated gitter account for the bot.
|
#Best to make a dedicated gitter account for the bot.
|
||||||
@ -498,9 +432,6 @@ ShowTopicChange=false
|
|||||||
#REQUIRED
|
#REQUIRED
|
||||||
Token="Yourtokenhere"
|
Token="Yourtokenhere"
|
||||||
|
|
||||||
## RELOADABLE SETTINGS
|
|
||||||
## Settings below can be reloaded by editing the file
|
|
||||||
|
|
||||||
#Nicks you want to ignore.
|
#Nicks you want to ignore.
|
||||||
#Messages from those users will not be sent to other bridges.
|
#Messages from those users will not be sent to other bridges.
|
||||||
#OPTIONAL
|
#OPTIONAL
|
||||||
@ -529,20 +460,15 @@ ReplaceMessages=[ ["cat","dog"] ]
|
|||||||
#optional (default empty)
|
#optional (default empty)
|
||||||
ReplaceNicks=[ ["user--","user"] ]
|
ReplaceNicks=[ ["user--","user"] ]
|
||||||
|
|
||||||
#extra label that can be used in the RemoteNickFormat
|
|
||||||
#optional (default empty)
|
|
||||||
Label=""
|
|
||||||
|
|
||||||
#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 / username.
|
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||||
#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 "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
||||||
#OPTIONAL (default empty)
|
#OPTIONAL (default empty)
|
||||||
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
|
#Only works hiding/show messages from irc and mattermost bridge for now
|
||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
ShowJoinPart=false
|
ShowJoinPart=false
|
||||||
|
|
||||||
@ -551,11 +477,6 @@ ShowJoinPart=false
|
|||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
StripNick=false
|
StripNick=false
|
||||||
|
|
||||||
#Enable to show topic changes from other bridges
|
|
||||||
#Only works hiding/show topic changes from slack bridge for now
|
|
||||||
#OPTIONAL (default false)
|
|
||||||
ShowTopicChange=false
|
|
||||||
|
|
||||||
###################################################################
|
###################################################################
|
||||||
#slack section
|
#slack section
|
||||||
###################################################################
|
###################################################################
|
||||||
@ -591,14 +512,10 @@ WebhookBindAddress="0.0.0.0:9999"
|
|||||||
#Icon that will be showed in slack
|
#Icon that will be showed in slack
|
||||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||||
#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 "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
||||||
#OPTIONAL
|
#OPTIONAL
|
||||||
IconURL="https://robohash.org/{NICK}.png?size=48x48"
|
IconURL="https://robohash.org/{NICK}.png?size=48x48"
|
||||||
|
|
||||||
## RELOADABLE SETTINGS
|
|
||||||
## Settings below can be reloaded by editing the file
|
|
||||||
|
|
||||||
#how to format the list of IRC nicks when displayed in slack
|
#how to format the list of IRC nicks when displayed in slack
|
||||||
#Possible options are "table" and "plain"
|
#Possible options are "table" and "plain"
|
||||||
#OPTIONAL (default plain)
|
#OPTIONAL (default plain)
|
||||||
@ -651,38 +568,23 @@ ReplaceMessages=[ ["cat","dog"] ]
|
|||||||
#optional (default empty)
|
#optional (default empty)
|
||||||
ReplaceNicks=[ ["user--","user"] ]
|
ReplaceNicks=[ ["user--","user"] ]
|
||||||
|
|
||||||
#extra label that can be used in the RemoteNickFormat
|
|
||||||
#optional (default empty)
|
|
||||||
Label=""
|
|
||||||
|
|
||||||
#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 / username.
|
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||||
#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 "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
||||||
#OPTIONAL (default empty)
|
#OPTIONAL (default empty)
|
||||||
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
|
#Only works hiding/show messages from irc and mattermost bridge for now
|
||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
ShowJoinPart=false
|
ShowJoinPart=false
|
||||||
|
|
||||||
#Do not send joins/parts to other bridges
|
|
||||||
#Currently works for messages from the following bridges: irc, mattermost, slack
|
|
||||||
#OPTIONAL (default false)
|
|
||||||
NoSendJoinPart=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)
|
||||||
StripNick=false
|
StripNick=false
|
||||||
|
|
||||||
#Enable to show topic changes from other bridges
|
|
||||||
#Only works hiding/show topic changes from slack bridge for now
|
|
||||||
#OPTIONAL (default false)
|
|
||||||
ShowTopicChange=false
|
|
||||||
|
|
||||||
###################################################################
|
###################################################################
|
||||||
#discord section
|
#discord section
|
||||||
###################################################################
|
###################################################################
|
||||||
@ -702,9 +604,6 @@ Token="Yourtokenhere"
|
|||||||
#REQUIRED
|
#REQUIRED
|
||||||
Server="yourservername"
|
Server="yourservername"
|
||||||
|
|
||||||
## RELOADABLE SETTINGS
|
|
||||||
## Settings below can be reloaded by editing the file
|
|
||||||
|
|
||||||
#Shows title, description and URL of embedded messages (sent by other bots)
|
#Shows title, description and URL of embedded messages (sent by other bots)
|
||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
ShowEmbeds=false
|
ShowEmbeds=false
|
||||||
@ -754,20 +653,15 @@ ReplaceMessages=[ ["cat","dog"] ]
|
|||||||
#optional (default empty)
|
#optional (default empty)
|
||||||
ReplaceNicks=[ ["user--","user"] ]
|
ReplaceNicks=[ ["user--","user"] ]
|
||||||
|
|
||||||
#extra label that can be used in the RemoteNickFormat
|
|
||||||
#optional (default empty)
|
|
||||||
Label=""
|
|
||||||
|
|
||||||
#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 / username.
|
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||||
#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 "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
||||||
#OPTIONAL (default empty)
|
#OPTIONAL (default empty)
|
||||||
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
|
#Only works hiding/show messages from irc and mattermost bridge for now
|
||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
ShowJoinPart=false
|
ShowJoinPart=false
|
||||||
|
|
||||||
@ -776,11 +670,6 @@ ShowJoinPart=false
|
|||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
StripNick=false
|
StripNick=false
|
||||||
|
|
||||||
#Enable to show topic changes from other bridges
|
|
||||||
#Only works hiding/show topic changes from slack bridge for now
|
|
||||||
#OPTIONAL (default false)
|
|
||||||
ShowTopicChange=false
|
|
||||||
|
|
||||||
###################################################################
|
###################################################################
|
||||||
#telegram section
|
#telegram section
|
||||||
###################################################################
|
###################################################################
|
||||||
@ -795,9 +684,6 @@ ShowTopicChange=false
|
|||||||
#REQUIRED
|
#REQUIRED
|
||||||
Token="Yourtokenhere"
|
Token="Yourtokenhere"
|
||||||
|
|
||||||
## RELOADABLE SETTINGS
|
|
||||||
## Settings below can be reloaded by editing the file
|
|
||||||
|
|
||||||
#OPTIONAL (default empty)
|
#OPTIONAL (default empty)
|
||||||
#Only supported format is "HTML", messages will be sent in html parsemode.
|
#Only supported format is "HTML", messages will be sent in html parsemode.
|
||||||
#See https://core.telegram.org/bots/api#html-style
|
#See https://core.telegram.org/bots/api#html-style
|
||||||
@ -815,14 +701,6 @@ UseFirstName=false
|
|||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
UseInsecureURL=false
|
UseInsecureURL=false
|
||||||
|
|
||||||
#Disable quoted/reply messages
|
|
||||||
#OPTIONAL (default false)
|
|
||||||
QuoteDisable=false
|
|
||||||
|
|
||||||
#Format quoted/reply messages
|
|
||||||
#OPTIONAL (default "{MESSAGE} (re @{QUOTENICK}: {QUOTEMESSAGE})")
|
|
||||||
QuoteFormat="{MESSAGE} (re @{QUOTENICK}: {QUOTEMESSAGE})"
|
|
||||||
|
|
||||||
#Disable sending of edits to other bridges
|
#Disable sending of edits to other bridges
|
||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
EditDisable=false
|
EditDisable=false
|
||||||
@ -859,25 +737,15 @@ ReplaceMessages=[ ["cat","dog"] ]
|
|||||||
#optional (default empty)
|
#optional (default empty)
|
||||||
ReplaceNicks=[ ["user--","user"] ]
|
ReplaceNicks=[ ["user--","user"] ]
|
||||||
|
|
||||||
#extra label that can be used in the RemoteNickFormat
|
|
||||||
#optional (default empty)
|
|
||||||
Label=""
|
|
||||||
|
|
||||||
#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 / username.
|
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||||
#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 "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
||||||
#
|
|
||||||
#WARNING: if you have set MessageFormat="HTML" be sure that this format matches the guidelines
|
|
||||||
#on https://core.telegram.org/bots/api#html-style otherwise the message will not go through to
|
|
||||||
#telegram! eg <{NICK}> should be <{NICK}>
|
|
||||||
#
|
|
||||||
#OPTIONAL (default empty)
|
#OPTIONAL (default empty)
|
||||||
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
|
#Only works hiding/show messages from irc and mattermost bridge for now
|
||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
ShowJoinPart=false
|
ShowJoinPart=false
|
||||||
|
|
||||||
@ -886,11 +754,6 @@ ShowJoinPart=false
|
|||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
StripNick=false
|
StripNick=false
|
||||||
|
|
||||||
#Enable to show topic changes from other bridges
|
|
||||||
#Only works hiding/show topic changes from slack bridge for now
|
|
||||||
#OPTIONAL (default false)
|
|
||||||
ShowTopicChange=false
|
|
||||||
|
|
||||||
###################################################################
|
###################################################################
|
||||||
#rocketchat section
|
#rocketchat section
|
||||||
###################################################################
|
###################################################################
|
||||||
@ -924,9 +787,6 @@ NoTLS=false
|
|||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
SkipTLSVerify=true
|
SkipTLSVerify=true
|
||||||
|
|
||||||
## RELOADABLE SETTINGS
|
|
||||||
## Settings below can be reloaded by editing the file
|
|
||||||
|
|
||||||
#Whether to prefix messages from other bridges to rocketchat with the sender's nick.
|
#Whether to prefix messages from other bridges to rocketchat with the sender's nick.
|
||||||
#Useful if username overrides for incoming webhooks isn't enabled on the
|
#Useful if username overrides for incoming webhooks isn't enabled on the
|
||||||
#rocketchat server. If you set PrefixMessagesWithNick to true, each message
|
#rocketchat server. If you set PrefixMessagesWithNick to true, each message
|
||||||
@ -962,20 +822,15 @@ ReplaceMessages=[ ["cat","dog"] ]
|
|||||||
#optional (default empty)
|
#optional (default empty)
|
||||||
ReplaceNicks=[ ["user--","user"] ]
|
ReplaceNicks=[ ["user--","user"] ]
|
||||||
|
|
||||||
#extra label that can be used in the RemoteNickFormat
|
|
||||||
#optional (default empty)
|
|
||||||
Label=""
|
|
||||||
|
|
||||||
#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 / username.
|
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||||
#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 "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
||||||
#OPTIONAL (default empty)
|
#OPTIONAL (default empty)
|
||||||
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
|
#Only works hiding/show messages from irc and mattermost bridge for now
|
||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
ShowJoinPart=false
|
ShowJoinPart=false
|
||||||
|
|
||||||
@ -984,11 +839,6 @@ ShowJoinPart=false
|
|||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
StripNick=false
|
StripNick=false
|
||||||
|
|
||||||
#Enable to show topic changes from other bridges
|
|
||||||
#Only works hiding/show topic changes from slack bridge for now
|
|
||||||
#OPTIONAL (default false)
|
|
||||||
ShowTopicChange=false
|
|
||||||
|
|
||||||
###################################################################
|
###################################################################
|
||||||
#matrix section
|
#matrix section
|
||||||
###################################################################
|
###################################################################
|
||||||
@ -1014,9 +864,6 @@ Password="yourpass"
|
|||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
NoHomeServerSuffix=false
|
NoHomeServerSuffix=false
|
||||||
|
|
||||||
## RELOADABLE SETTINGS
|
|
||||||
## Settings below can be reloaded by editing the file
|
|
||||||
|
|
||||||
#Whether to prefix messages from other bridges to matrix with the sender's nick.
|
#Whether to prefix messages from other bridges to matrix with the sender's nick.
|
||||||
#Useful if username overrides for incoming webhooks isn't enabled on the
|
#Useful if username overrides for incoming webhooks isn't enabled on the
|
||||||
#matrix server. If you set PrefixMessagesWithNick to true, each message
|
#matrix server. If you set PrefixMessagesWithNick to true, each message
|
||||||
@ -1052,20 +899,15 @@ ReplaceMessages=[ ["cat","dog"] ]
|
|||||||
#optional (default empty)
|
#optional (default empty)
|
||||||
ReplaceNicks=[ ["user--","user"] ]
|
ReplaceNicks=[ ["user--","user"] ]
|
||||||
|
|
||||||
#extra label that can be used in the RemoteNickFormat
|
|
||||||
#optional (default empty)
|
|
||||||
Label=""
|
|
||||||
|
|
||||||
#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 / username.
|
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||||
#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 "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
||||||
#OPTIONAL (default empty)
|
#OPTIONAL (default empty)
|
||||||
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
|
#Only works hiding/show messages from irc and mattermost bridge for now
|
||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
ShowJoinPart=false
|
ShowJoinPart=false
|
||||||
|
|
||||||
@ -1074,11 +916,6 @@ ShowJoinPart=false
|
|||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
StripNick=false
|
StripNick=false
|
||||||
|
|
||||||
#Enable to show topic changes from other bridges
|
|
||||||
#Only works hiding/show topic changes from slack bridge for now
|
|
||||||
#OPTIONAL (default false)
|
|
||||||
ShowTopicChange=false
|
|
||||||
|
|
||||||
###################################################################
|
###################################################################
|
||||||
#steam section
|
#steam section
|
||||||
###################################################################
|
###################################################################
|
||||||
@ -1098,9 +935,6 @@ Password="yourpass"
|
|||||||
#OPTIONAL
|
#OPTIONAL
|
||||||
Authcode="ABCE12"
|
Authcode="ABCE12"
|
||||||
|
|
||||||
## RELOADABLE SETTINGS
|
|
||||||
## Settings below can be reloaded by editing the file
|
|
||||||
|
|
||||||
#Whether to prefix messages from other bridges to matrix with the sender's nick.
|
#Whether to prefix messages from other bridges to matrix with the sender's nick.
|
||||||
#Useful if username overrides for incoming webhooks isn't enabled on the
|
#Useful if username overrides for incoming webhooks isn't enabled on the
|
||||||
#matrix server. If you set PrefixMessagesWithNick to true, each message
|
#matrix server. If you set PrefixMessagesWithNick to true, each message
|
||||||
@ -1136,20 +970,15 @@ ReplaceMessages=[ ["cat","dog"] ]
|
|||||||
#optional (default empty)
|
#optional (default empty)
|
||||||
ReplaceNicks=[ ["user--","user"] ]
|
ReplaceNicks=[ ["user--","user"] ]
|
||||||
|
|
||||||
#extra label that can be used in the RemoteNickFormat
|
|
||||||
#optional (default empty)
|
|
||||||
Label=""
|
|
||||||
|
|
||||||
#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 / username.
|
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||||
#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 "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
||||||
#OPTIONAL (default empty)
|
#OPTIONAL (default empty)
|
||||||
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
|
#Only works hiding/show messages from irc and mattermost bridge for now
|
||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
ShowJoinPart=false
|
ShowJoinPart=false
|
||||||
|
|
||||||
@ -1158,95 +987,6 @@ ShowJoinPart=false
|
|||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
StripNick=false
|
StripNick=false
|
||||||
|
|
||||||
#Enable to show topic changes from other bridges
|
|
||||||
#Only works hiding/show topic changes from slack bridge for now
|
|
||||||
#OPTIONAL (default false)
|
|
||||||
ShowTopicChange=false
|
|
||||||
|
|
||||||
###################################################################
|
|
||||||
#zulip section
|
|
||||||
###################################################################
|
|
||||||
[zulip]
|
|
||||||
#You can configure multiple servers "[zulip.name]" or "[zulip.name2]"
|
|
||||||
#In this example we use [zulip.streamchat]
|
|
||||||
#REQUIRED
|
|
||||||
|
|
||||||
[zulip.streamchat]
|
|
||||||
#Token to connect with zulip API (called bot API key in Settings - Your bots)
|
|
||||||
#REQUIRED
|
|
||||||
Token="Yourtokenhere"
|
|
||||||
|
|
||||||
#Username of the bot, normally called yourbot-bot@yourserver.zulipchat.com
|
|
||||||
#See username in Settings - Your bots
|
|
||||||
#REQUIRED
|
|
||||||
Login="yourbot-bot@yourserver.zulipchat.com"
|
|
||||||
|
|
||||||
#Servername of your zulip instance
|
|
||||||
#REQUIRED
|
|
||||||
Server="https://yourserver.zulipchat.com"
|
|
||||||
|
|
||||||
#Topic of the messages matterbridge will use
|
|
||||||
#OPTIONAL (default "matterbridge")
|
|
||||||
Topic="matterbridge"
|
|
||||||
|
|
||||||
## RELOADABLE SETTINGS
|
|
||||||
## Settings below can be reloaded by editing the file
|
|
||||||
|
|
||||||
#Nicks you want to ignore.
|
|
||||||
#Messages from those users will not be sent to other bridges.
|
|
||||||
#OPTIONAL
|
|
||||||
IgnoreNicks="spammer1 spammer2"
|
|
||||||
|
|
||||||
#Messages you want to ignore.
|
|
||||||
#Messages matching these regexp will be ignored and not sent to other bridges
|
|
||||||
#See https://regex-golang.appspot.com/assets/html/index.html for more regex info
|
|
||||||
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
|
|
||||||
IgnoreMessages="^~~ badword"
|
|
||||||
|
|
||||||
#messages you want to replace.
|
|
||||||
#it replaces outgoing messages from the bridge.
|
|
||||||
#so you need to place it by the sending bridge definition.
|
|
||||||
#regular expressions supported
|
|
||||||
#some examples:
|
|
||||||
#this replaces cat => dog and sleep => awake
|
|
||||||
#replacemessages=[ ["cat","dog"], ["sleep","awake"] ]
|
|
||||||
#this replaces every number with number. 123 => numbernumbernumber
|
|
||||||
#replacemessages=[ ["[0-9]","number"] ]
|
|
||||||
#optional (default empty)
|
|
||||||
ReplaceMessages=[ ["cat","dog"] ]
|
|
||||||
|
|
||||||
#nicks you want to replace.
|
|
||||||
#see replacemessages for syntaxa
|
|
||||||
#optional (default empty)
|
|
||||||
ReplaceNicks=[ ["user--","user"] ]
|
|
||||||
|
|
||||||
#extra label that can be used in the RemoteNickFormat
|
|
||||||
#optional (default empty)
|
|
||||||
Label=""
|
|
||||||
|
|
||||||
#RemoteNickFormat defines how remote users appear on this bridge
|
|
||||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
|
||||||
#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 "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
|
||||||
#OPTIONAL (default empty)
|
|
||||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
|
||||||
|
|
||||||
#Enable to show users joins/parts from other bridges
|
|
||||||
#Currently works for messages from the following bridges: irc, mattermost, slack
|
|
||||||
#OPTIONAL (default false)
|
|
||||||
ShowJoinPart=false
|
|
||||||
|
|
||||||
#StripNick only allows alphanumerical nicks. See https://github.com/42wim/matterbridge/issues/285
|
|
||||||
#It will strip other characters from the nick
|
|
||||||
#OPTIONAL (default false)
|
|
||||||
StripNick=false
|
|
||||||
|
|
||||||
#Enable to show topic changes from other bridges
|
|
||||||
#Only works hiding/show topic changes from slack bridge for now
|
|
||||||
#OPTIONAL (default false)
|
|
||||||
ShowTopicChange=false
|
|
||||||
|
|
||||||
###################################################################
|
###################################################################
|
||||||
#API
|
#API
|
||||||
###################################################################
|
###################################################################
|
||||||
@ -1268,14 +1008,9 @@ Buffer=1000
|
|||||||
#OPTIONAL (no authorization if token is empty)
|
#OPTIONAL (no authorization if token is empty)
|
||||||
Token="mytoken"
|
Token="mytoken"
|
||||||
|
|
||||||
#extra label that can be used in the RemoteNickFormat
|
|
||||||
#optional (default empty)
|
|
||||||
Label=""
|
|
||||||
|
|
||||||
#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 / username.
|
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||||
#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 "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
||||||
#OPTIONAL (default empty)
|
#OPTIONAL (default empty)
|
||||||
RemoteNickFormat="{NICK}"
|
RemoteNickFormat="{NICK}"
|
||||||
@ -1287,14 +1022,9 @@ RemoteNickFormat="{NICK}"
|
|||||||
###################################################################
|
###################################################################
|
||||||
# Settings here are defaults that each protocol can override
|
# Settings here are defaults that each protocol can override
|
||||||
[general]
|
[general]
|
||||||
|
|
||||||
## RELOADABLE SETTINGS
|
|
||||||
## Settings below can be reloaded by editing the file
|
|
||||||
|
|
||||||
#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 / username.
|
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||||
#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 "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
||||||
#OPTIONAL (default empty)
|
#OPTIONAL (default empty)
|
||||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||||
@ -1323,7 +1053,7 @@ MediaServerDownload="https://youserver.com/download"
|
|||||||
#eg downloading from slack to upload it to mattermost
|
#eg downloading from slack to upload it to mattermost
|
||||||
#
|
#
|
||||||
#It will only download from bridges that don't have public links available, which are for the moment
|
#It will only download from bridges that don't have public links available, which are for the moment
|
||||||
#slack, telegram, matrix and mattermost
|
#slack, telegram and matrix
|
||||||
#
|
#
|
||||||
#Optional (default 1000000 (1 megabyte))
|
#Optional (default 1000000 (1 megabyte))
|
||||||
MediaDownloadSize=1000000
|
MediaDownloadSize=1000000
|
||||||
@ -1375,7 +1105,6 @@ enable=true
|
|||||||
# - encrypted rooms are not supported in matrix
|
# - encrypted rooms are not supported in matrix
|
||||||
#steam - chatid (a large number).
|
#steam - chatid (a large number).
|
||||||
# The number in the URL when you click "enter chat room" in the browser
|
# The number in the URL when you click "enter chat room" in the browser
|
||||||
#zulip - stream (without the #)
|
|
||||||
#
|
#
|
||||||
#REQUIRED
|
#REQUIRED
|
||||||
channel="#testing"
|
channel="#testing"
|
||||||
|
@ -13,8 +13,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
prefixed "github.com/x-cray/logrus-prefixed-formatter"
|
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/hashicorp/golang-lru"
|
"github.com/hashicorp/golang-lru"
|
||||||
@ -74,16 +73,12 @@ type MMClient struct {
|
|||||||
func New(login, pass, team, server string) *MMClient {
|
func New(login, pass, team, server string) *MMClient {
|
||||||
cred := &Credentials{Login: login, Pass: pass, Team: team, Server: server}
|
cred := &Credentials{Login: login, Pass: pass, Team: team, Server: server}
|
||||||
mmclient := &MMClient{Credentials: cred, MessageChan: make(chan *Message, 100), Users: make(map[string]*model.User)}
|
mmclient := &MMClient{Credentials: cred, MessageChan: make(chan *Message, 100), Users: make(map[string]*model.User)}
|
||||||
log.SetFormatter(&prefixed.TextFormatter{PrefixPadding: 13, DisableColors: true})
|
mmclient.log = log.WithFields(log.Fields{"module": "matterclient"})
|
||||||
mmclient.log = log.WithFields(log.Fields{"prefix": "matterclient"})
|
log.SetFormatter(&log.TextFormatter{FullTimestamp: true})
|
||||||
mmclient.lruCache, _ = lru.New(500)
|
mmclient.lruCache, _ = lru.New(500)
|
||||||
return mmclient
|
return mmclient
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MMClient) SetDebugLog() {
|
|
||||||
log.SetFormatter(&prefixed.TextFormatter{PrefixPadding: 13, DisableColors: true, FullTimestamp: false, ForceFormatting: true})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MMClient) SetLogLevel(level string) {
|
func (m *MMClient) SetLogLevel(level string) {
|
||||||
l, err := log.ParseLevel(level)
|
l, err := log.ParseLevel(level)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -190,11 +185,7 @@ func (m *MMClient) Login() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if m.Team == nil {
|
if m.Team == nil {
|
||||||
validTeamNames := make([]string, len(m.OtherTeams))
|
return errors.New("team not found")
|
||||||
for i, t := range m.OtherTeams {
|
|
||||||
validTeamNames[i] = t.Team.Name
|
|
||||||
}
|
|
||||||
return fmt.Errorf("Team '%s' not found in %v", m.Credentials.Team, validTeamNames)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m.wsConnect()
|
m.wsConnect()
|
||||||
@ -574,7 +565,7 @@ func (m *MMClient) GetFileLinks(filenames []string) []string {
|
|||||||
res, resp := m.Client.GetFileLink(f)
|
res, resp := m.Client.GetFileLink(f)
|
||||||
if resp.Error != nil {
|
if resp.Error != nil {
|
||||||
// public links is probably disabled, create the link ourselves
|
// public links is probably disabled, create the link ourselves
|
||||||
output = append(output, uriScheme+m.Credentials.Server+model.API_URL_SUFFIX_V4+"/files/"+f)
|
output = append(output, uriScheme+m.Credentials.Server+model.API_URL_SUFFIX_V3+"/files/"+f+"/get")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
output = append(output, res)
|
output = append(output, res)
|
||||||
@ -594,9 +585,9 @@ func (m *MMClient) UpdateChannelHeader(channelId string, header string) {
|
|||||||
func (m *MMClient) UpdateLastViewed(channelId string) {
|
func (m *MMClient) UpdateLastViewed(channelId string) {
|
||||||
m.log.Debugf("posting lastview %#v", channelId)
|
m.log.Debugf("posting lastview %#v", channelId)
|
||||||
view := &model.ChannelView{ChannelId: channelId}
|
view := &model.ChannelView{ChannelId: channelId}
|
||||||
_, resp := m.Client.ViewChannel(m.User.Id, view)
|
res, _ := m.Client.ViewChannel(m.User.Id, view)
|
||||||
if resp.Error != nil {
|
if !res {
|
||||||
m.log.Errorf("ChannelView update for %s failed: %s", channelId, resp.Error)
|
m.log.Errorf("ChannelView update for %s failed", channelId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -772,14 +763,6 @@ func (m *MMClient) GetStatus(userId string) string {
|
|||||||
return "offline"
|
return "offline"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MMClient) UpdateStatus(userId string, status string) error {
|
|
||||||
_, resp := m.Client.UpdateUserStatus(userId, &model.Status{Status: status})
|
|
||||||
if resp.Error != nil {
|
|
||||||
return resp.Error
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MMClient) GetStatuses() map[string]string {
|
func (m *MMClient) GetStatuses() map[string]string {
|
||||||
var ids []string
|
var ids []string
|
||||||
statuses := make(map[string]string)
|
statuses := make(map[string]string)
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gorilla/schema"
|
"github.com/gorilla/schema"
|
||||||
"github.com/nlopes/slack"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
@ -23,7 +22,7 @@ type OMessage struct {
|
|||||||
IconEmoji string `json:"icon_emoji,omitempty"`
|
IconEmoji string `json:"icon_emoji,omitempty"`
|
||||||
UserName string `json:"username,omitempty"`
|
UserName string `json:"username,omitempty"`
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
Attachments []slack.Attachment `json:"attachments,omitempty"`
|
Attachments interface{} `json:"attachments,omitempty"`
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
Props map[string]interface{} `json:"props"`
|
Props map[string]interface{} `json:"props"`
|
||||||
}
|
}
|
||||||
|
4
vendor/github.com/sirupsen/logrus/doc.go → vendor/github.com/Sirupsen/logrus/doc.go
generated
vendored
4
vendor/github.com/sirupsen/logrus/doc.go → vendor/github.com/Sirupsen/logrus/doc.go
generated
vendored
@ -7,7 +7,7 @@ The simplest way to use Logrus is simply the package-level exported logger:
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -21,6 +21,6 @@ The simplest way to use Logrus is simply the package-level exported logger:
|
|||||||
Output:
|
Output:
|
||||||
time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10
|
time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10
|
||||||
|
|
||||||
For a full guide visit https://github.com/sirupsen/logrus
|
For a full guide visit https://github.com/Sirupsen/logrus
|
||||||
*/
|
*/
|
||||||
package logrus
|
package logrus
|
85
vendor/github.com/sirupsen/logrus/entry.go → vendor/github.com/Sirupsen/logrus/entry.go
generated
vendored
85
vendor/github.com/sirupsen/logrus/entry.go → vendor/github.com/Sirupsen/logrus/entry.go
generated
vendored
@ -35,7 +35,6 @@ type Entry struct {
|
|||||||
Time time.Time
|
Time time.Time
|
||||||
|
|
||||||
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
|
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
|
||||||
// This field will be set on entry firing and the value will be equal to the one in Logger struct field.
|
|
||||||
Level Level
|
Level Level
|
||||||
|
|
||||||
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
|
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
|
||||||
@ -94,16 +93,29 @@ func (entry Entry) log(level Level, msg string) {
|
|||||||
entry.Level = level
|
entry.Level = level
|
||||||
entry.Message = msg
|
entry.Message = msg
|
||||||
|
|
||||||
entry.fireHooks()
|
if err := entry.Logger.Hooks.Fire(level, &entry); err != nil {
|
||||||
|
entry.Logger.mu.Lock()
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
||||||
|
entry.Logger.mu.Unlock()
|
||||||
|
}
|
||||||
buffer = bufferPool.Get().(*bytes.Buffer)
|
buffer = bufferPool.Get().(*bytes.Buffer)
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
defer bufferPool.Put(buffer)
|
defer bufferPool.Put(buffer)
|
||||||
entry.Buffer = buffer
|
entry.Buffer = buffer
|
||||||
|
serialized, err := entry.Logger.Formatter.Format(&entry)
|
||||||
entry.write()
|
|
||||||
|
|
||||||
entry.Buffer = nil
|
entry.Buffer = nil
|
||||||
|
if err != nil {
|
||||||
|
entry.Logger.mu.Lock()
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
|
||||||
|
entry.Logger.mu.Unlock()
|
||||||
|
} else {
|
||||||
|
entry.Logger.mu.Lock()
|
||||||
|
_, err = entry.Logger.Out.Write(serialized)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
|
||||||
|
}
|
||||||
|
entry.Logger.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
// To avoid Entry#log() returning a value that only would make sense for
|
// To avoid Entry#log() returning a value that only would make sense for
|
||||||
// panic() to use in Entry#Panic(), we avoid the allocation by checking
|
// panic() to use in Entry#Panic(), we avoid the allocation by checking
|
||||||
@ -113,33 +125,8 @@ func (entry Entry) log(level Level, msg string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function is not declared with a pointer value because otherwise
|
|
||||||
// race conditions will occur when using multiple goroutines
|
|
||||||
func (entry Entry) fireHooks() {
|
|
||||||
entry.Logger.mu.Lock()
|
|
||||||
defer entry.Logger.mu.Unlock()
|
|
||||||
err := entry.Logger.Hooks.Fire(entry.Level, &entry)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) write() {
|
|
||||||
serialized, err := entry.Logger.Formatter.Format(entry)
|
|
||||||
entry.Logger.mu.Lock()
|
|
||||||
defer entry.Logger.mu.Unlock()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
|
|
||||||
} else {
|
|
||||||
_, err = entry.Logger.Out.Write(serialized)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Debug(args ...interface{}) {
|
func (entry *Entry) Debug(args ...interface{}) {
|
||||||
if entry.Logger.level() >= DebugLevel {
|
if entry.Logger.Level >= DebugLevel {
|
||||||
entry.log(DebugLevel, fmt.Sprint(args...))
|
entry.log(DebugLevel, fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,13 +136,13 @@ func (entry *Entry) Print(args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Info(args ...interface{}) {
|
func (entry *Entry) Info(args ...interface{}) {
|
||||||
if entry.Logger.level() >= InfoLevel {
|
if entry.Logger.Level >= InfoLevel {
|
||||||
entry.log(InfoLevel, fmt.Sprint(args...))
|
entry.log(InfoLevel, fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Warn(args ...interface{}) {
|
func (entry *Entry) Warn(args ...interface{}) {
|
||||||
if entry.Logger.level() >= WarnLevel {
|
if entry.Logger.Level >= WarnLevel {
|
||||||
entry.log(WarnLevel, fmt.Sprint(args...))
|
entry.log(WarnLevel, fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -165,20 +152,20 @@ func (entry *Entry) Warning(args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Error(args ...interface{}) {
|
func (entry *Entry) Error(args ...interface{}) {
|
||||||
if entry.Logger.level() >= ErrorLevel {
|
if entry.Logger.Level >= ErrorLevel {
|
||||||
entry.log(ErrorLevel, fmt.Sprint(args...))
|
entry.log(ErrorLevel, fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Fatal(args ...interface{}) {
|
func (entry *Entry) Fatal(args ...interface{}) {
|
||||||
if entry.Logger.level() >= FatalLevel {
|
if entry.Logger.Level >= FatalLevel {
|
||||||
entry.log(FatalLevel, fmt.Sprint(args...))
|
entry.log(FatalLevel, fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
Exit(1)
|
Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Panic(args ...interface{}) {
|
func (entry *Entry) Panic(args ...interface{}) {
|
||||||
if entry.Logger.level() >= PanicLevel {
|
if entry.Logger.Level >= PanicLevel {
|
||||||
entry.log(PanicLevel, fmt.Sprint(args...))
|
entry.log(PanicLevel, fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
panic(fmt.Sprint(args...))
|
panic(fmt.Sprint(args...))
|
||||||
@ -187,13 +174,13 @@ func (entry *Entry) Panic(args ...interface{}) {
|
|||||||
// Entry Printf family functions
|
// Entry Printf family functions
|
||||||
|
|
||||||
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
||||||
if entry.Logger.level() >= DebugLevel {
|
if entry.Logger.Level >= DebugLevel {
|
||||||
entry.Debug(fmt.Sprintf(format, args...))
|
entry.Debug(fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Infof(format string, args ...interface{}) {
|
func (entry *Entry) Infof(format string, args ...interface{}) {
|
||||||
if entry.Logger.level() >= InfoLevel {
|
if entry.Logger.Level >= InfoLevel {
|
||||||
entry.Info(fmt.Sprintf(format, args...))
|
entry.Info(fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -203,7 +190,7 @@ func (entry *Entry) Printf(format string, args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
||||||
if entry.Logger.level() >= WarnLevel {
|
if entry.Logger.Level >= WarnLevel {
|
||||||
entry.Warn(fmt.Sprintf(format, args...))
|
entry.Warn(fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -213,20 +200,20 @@ func (entry *Entry) Warningf(format string, args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
||||||
if entry.Logger.level() >= ErrorLevel {
|
if entry.Logger.Level >= ErrorLevel {
|
||||||
entry.Error(fmt.Sprintf(format, args...))
|
entry.Error(fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
||||||
if entry.Logger.level() >= FatalLevel {
|
if entry.Logger.Level >= FatalLevel {
|
||||||
entry.Fatal(fmt.Sprintf(format, args...))
|
entry.Fatal(fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
Exit(1)
|
Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
||||||
if entry.Logger.level() >= PanicLevel {
|
if entry.Logger.Level >= PanicLevel {
|
||||||
entry.Panic(fmt.Sprintf(format, args...))
|
entry.Panic(fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -234,13 +221,13 @@ func (entry *Entry) Panicf(format string, args ...interface{}) {
|
|||||||
// Entry Println family functions
|
// Entry Println family functions
|
||||||
|
|
||||||
func (entry *Entry) Debugln(args ...interface{}) {
|
func (entry *Entry) Debugln(args ...interface{}) {
|
||||||
if entry.Logger.level() >= DebugLevel {
|
if entry.Logger.Level >= DebugLevel {
|
||||||
entry.Debug(entry.sprintlnn(args...))
|
entry.Debug(entry.sprintlnn(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Infoln(args ...interface{}) {
|
func (entry *Entry) Infoln(args ...interface{}) {
|
||||||
if entry.Logger.level() >= InfoLevel {
|
if entry.Logger.Level >= InfoLevel {
|
||||||
entry.Info(entry.sprintlnn(args...))
|
entry.Info(entry.sprintlnn(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -250,7 +237,7 @@ func (entry *Entry) Println(args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Warnln(args ...interface{}) {
|
func (entry *Entry) Warnln(args ...interface{}) {
|
||||||
if entry.Logger.level() >= WarnLevel {
|
if entry.Logger.Level >= WarnLevel {
|
||||||
entry.Warn(entry.sprintlnn(args...))
|
entry.Warn(entry.sprintlnn(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -260,20 +247,20 @@ func (entry *Entry) Warningln(args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Errorln(args ...interface{}) {
|
func (entry *Entry) Errorln(args ...interface{}) {
|
||||||
if entry.Logger.level() >= ErrorLevel {
|
if entry.Logger.Level >= ErrorLevel {
|
||||||
entry.Error(entry.sprintlnn(args...))
|
entry.Error(entry.sprintlnn(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Fatalln(args ...interface{}) {
|
func (entry *Entry) Fatalln(args ...interface{}) {
|
||||||
if entry.Logger.level() >= FatalLevel {
|
if entry.Logger.Level >= FatalLevel {
|
||||||
entry.Fatal(entry.sprintlnn(args...))
|
entry.Fatal(entry.sprintlnn(args...))
|
||||||
}
|
}
|
||||||
Exit(1)
|
Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Panicln(args ...interface{}) {
|
func (entry *Entry) Panicln(args ...interface{}) {
|
||||||
if entry.Logger.level() >= PanicLevel {
|
if entry.Logger.Level >= PanicLevel {
|
||||||
entry.Panic(entry.sprintlnn(args...))
|
entry.Panic(entry.sprintlnn(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,15 +1,23 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
prefixed "github.com/x-cray/logrus-prefixed-formatter"
|
// "os"
|
||||||
)
|
)
|
||||||
|
|
||||||
var log = logrus.New()
|
var log = logrus.New()
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
formatter := new(prefixed.TextFormatter)
|
log.Formatter = new(logrus.JSONFormatter)
|
||||||
log.Formatter = formatter
|
log.Formatter = new(logrus.TextFormatter) // default
|
||||||
|
|
||||||
|
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY, 0666)
|
||||||
|
// if err == nil {
|
||||||
|
// log.Out = file
|
||||||
|
// } else {
|
||||||
|
// log.Info("Failed to log to file, using default stderr")
|
||||||
|
// }
|
||||||
|
|
||||||
log.Level = logrus.DebugLevel
|
log.Level = logrus.DebugLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,42 +25,34 @@ func main() {
|
|||||||
defer func() {
|
defer func() {
|
||||||
err := recover()
|
err := recover()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Fatal message
|
|
||||||
log.WithFields(logrus.Fields{
|
log.WithFields(logrus.Fields{
|
||||||
"omg": true,
|
"omg": true,
|
||||||
|
"err": err,
|
||||||
"number": 100,
|
"number": 100,
|
||||||
}).Fatal("[main] The ice breaks!")
|
}).Fatal("The ice breaks!")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// You could either provide a map key called `prefix` to add prefix
|
|
||||||
log.WithFields(logrus.Fields{
|
log.WithFields(logrus.Fields{
|
||||||
"prefix": "main",
|
|
||||||
"animal": "walrus",
|
"animal": "walrus",
|
||||||
"number": 8,
|
"number": 8,
|
||||||
}).Debug("Started observing beach")
|
}).Debug("Started observing beach")
|
||||||
|
|
||||||
// Or you can simply add prefix in square brackets within message itself
|
|
||||||
log.WithFields(logrus.Fields{
|
log.WithFields(logrus.Fields{
|
||||||
"animal": "walrus",
|
"animal": "walrus",
|
||||||
"size": 10,
|
"size": 10,
|
||||||
}).Debug("[main] A group of walrus emerges from the ocean")
|
}).Info("A group of walrus emerges from the ocean")
|
||||||
|
|
||||||
// Warning message
|
|
||||||
log.WithFields(logrus.Fields{
|
log.WithFields(logrus.Fields{
|
||||||
"omg": true,
|
"omg": true,
|
||||||
"number": 122,
|
"number": 122,
|
||||||
}).Warn("[main] The group's number increased tremendously!")
|
}).Warn("The group's number increased tremendously!")
|
||||||
|
|
||||||
// Information message
|
|
||||||
log.WithFields(logrus.Fields{
|
log.WithFields(logrus.Fields{
|
||||||
"prefix": "sensor",
|
|
||||||
"temperature": -4,
|
"temperature": -4,
|
||||||
}).Info("Temperature changes")
|
}).Debug("Temperature changes")
|
||||||
|
|
||||||
// Panic message
|
|
||||||
log.WithFields(logrus.Fields{
|
log.WithFields(logrus.Fields{
|
||||||
"prefix": "sensor",
|
|
||||||
"animal": "orca",
|
"animal": "orca",
|
||||||
"size": 9009,
|
"size": 9009,
|
||||||
}).Panic("It's over 9000!")
|
}).Panic("It's over 9000!")
|
30
vendor/github.com/Sirupsen/logrus/examples/hook/hook.go
generated
vendored
Normal file
30
vendor/github.com/Sirupsen/logrus/examples/hook/hook.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"gopkg.in/gemnasium/logrus-airbrake-hook.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = logrus.New()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.Formatter = new(logrus.TextFormatter) // default
|
||||||
|
log.Hooks.Add(airbrake.NewHook(123, "xyz", "development"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
"size": 10,
|
||||||
|
}).Info("A group of walrus emerges from the ocean")
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"number": 122,
|
||||||
|
}).Warn("The group's number increased tremendously!")
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"number": 100,
|
||||||
|
}).Fatal("The ice breaks!")
|
||||||
|
}
|
@ -31,14 +31,14 @@ func SetFormatter(formatter Formatter) {
|
|||||||
func SetLevel(level Level) {
|
func SetLevel(level Level) {
|
||||||
std.mu.Lock()
|
std.mu.Lock()
|
||||||
defer std.mu.Unlock()
|
defer std.mu.Unlock()
|
||||||
std.SetLevel(level)
|
std.Level = level
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLevel returns the standard logger level.
|
// GetLevel returns the standard logger level.
|
||||||
func GetLevel() Level {
|
func GetLevel() Level {
|
||||||
std.mu.Lock()
|
std.mu.Lock()
|
||||||
defer std.mu.Unlock()
|
defer std.mu.Unlock()
|
||||||
return std.level()
|
return std.Level
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddHook adds a hook to the standard logger hooks.
|
// AddHook adds a hook to the standard logger hooks.
|
@ -2,7 +2,7 @@ package logrus
|
|||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
const defaultTimestampFormat = time.RFC3339
|
const DefaultTimestampFormat = time.RFC3339
|
||||||
|
|
||||||
// The Formatter interface is used to implement a custom Formatter. It takes an
|
// The Formatter interface is used to implement a custom Formatter. It takes an
|
||||||
// `Entry`. It exposes all the fields, including the default ones:
|
// `Entry`. It exposes all the fields, including the default ones:
|
@ -1,13 +1,12 @@
|
|||||||
// +build !windows,!nacl,!plan9
|
// +build !windows,!nacl,!plan9
|
||||||
|
|
||||||
package syslog
|
package logrus_syslog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
"log/syslog"
|
"log/syslog"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SyslogHook to send logs via syslog.
|
// SyslogHook to send logs via syslog.
|
67
vendor/github.com/Sirupsen/logrus/hooks/test/test.go
generated
vendored
Normal file
67
vendor/github.com/Sirupsen/logrus/hooks/test/test.go
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// test.Hook is a hook designed for dealing with logs in test scenarios.
|
||||||
|
type Hook struct {
|
||||||
|
Entries []*logrus.Entry
|
||||||
|
}
|
||||||
|
|
||||||
|
// Installs a test hook for the global logger.
|
||||||
|
func NewGlobal() *Hook {
|
||||||
|
|
||||||
|
hook := new(Hook)
|
||||||
|
logrus.AddHook(hook)
|
||||||
|
|
||||||
|
return hook
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Installs a test hook for a given local logger.
|
||||||
|
func NewLocal(logger *logrus.Logger) *Hook {
|
||||||
|
|
||||||
|
hook := new(Hook)
|
||||||
|
logger.Hooks.Add(hook)
|
||||||
|
|
||||||
|
return hook
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a discarding logger and installs the test hook.
|
||||||
|
func NewNullLogger() (*logrus.Logger, *Hook) {
|
||||||
|
|
||||||
|
logger := logrus.New()
|
||||||
|
logger.Out = ioutil.Discard
|
||||||
|
|
||||||
|
return logger, NewLocal(logger)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Hook) Fire(e *logrus.Entry) error {
|
||||||
|
t.Entries = append(t.Entries, e)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Hook) Levels() []logrus.Level {
|
||||||
|
return logrus.AllLevels
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastEntry returns the last entry that was logged or nil.
|
||||||
|
func (t *Hook) LastEntry() (l *logrus.Entry) {
|
||||||
|
|
||||||
|
if i := len(t.Entries) - 1; i < 0 {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return t.Entries[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset removes all Entries from this test hook.
|
||||||
|
func (t *Hook) Reset() {
|
||||||
|
t.Entries = make([]*logrus.Entry, 0)
|
||||||
|
}
|
@ -6,11 +6,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type fieldKey string
|
type fieldKey string
|
||||||
|
|
||||||
// FieldMap allows customization of the key names for default fields.
|
|
||||||
type FieldMap map[fieldKey]string
|
type FieldMap map[fieldKey]string
|
||||||
|
|
||||||
// Default key names for the default fields
|
|
||||||
const (
|
const (
|
||||||
FieldKeyMsg = "msg"
|
FieldKeyMsg = "msg"
|
||||||
FieldKeyLevel = "level"
|
FieldKeyLevel = "level"
|
||||||
@ -25,7 +22,6 @@ func (f FieldMap) resolve(key fieldKey) string {
|
|||||||
return string(key)
|
return string(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSONFormatter formats logs into parsable json
|
|
||||||
type JSONFormatter struct {
|
type JSONFormatter struct {
|
||||||
// TimestampFormat sets the format used for marshaling timestamps.
|
// TimestampFormat sets the format used for marshaling timestamps.
|
||||||
TimestampFormat string
|
TimestampFormat string
|
||||||
@ -33,26 +29,25 @@ type JSONFormatter struct {
|
|||||||
// DisableTimestamp allows disabling automatic timestamps in output
|
// DisableTimestamp allows disabling automatic timestamps in output
|
||||||
DisableTimestamp bool
|
DisableTimestamp bool
|
||||||
|
|
||||||
// FieldMap allows users to customize the names of keys for default fields.
|
// FieldMap allows users to customize the names of keys for various fields.
|
||||||
// As an example:
|
// As an example:
|
||||||
// formatter := &JSONFormatter{
|
// formatter := &JSONFormatter{
|
||||||
// FieldMap: FieldMap{
|
// FieldMap: FieldMap{
|
||||||
// FieldKeyTime: "@timestamp",
|
// FieldKeyTime: "@timestamp",
|
||||||
// FieldKeyLevel: "@level",
|
// FieldKeyLevel: "@level",
|
||||||
// FieldKeyMsg: "@message",
|
// FieldKeyLevel: "@message",
|
||||||
// },
|
// },
|
||||||
// }
|
// }
|
||||||
FieldMap FieldMap
|
FieldMap FieldMap
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format renders a single log entry
|
|
||||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
data := make(Fields, len(entry.Data)+3)
|
data := make(Fields, len(entry.Data)+3)
|
||||||
for k, v := range entry.Data {
|
for k, v := range entry.Data {
|
||||||
switch v := v.(type) {
|
switch v := v.(type) {
|
||||||
case error:
|
case error:
|
||||||
// Otherwise errors are ignored by `encoding/json`
|
// Otherwise errors are ignored by `encoding/json`
|
||||||
// https://github.com/sirupsen/logrus/issues/137
|
// https://github.com/Sirupsen/logrus/issues/137
|
||||||
data[k] = v.Error()
|
data[k] = v.Error()
|
||||||
default:
|
default:
|
||||||
data[k] = v
|
data[k] = v
|
||||||
@ -62,7 +57,7 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
|||||||
|
|
||||||
timestampFormat := f.TimestampFormat
|
timestampFormat := f.TimestampFormat
|
||||||
if timestampFormat == "" {
|
if timestampFormat == "" {
|
||||||
timestampFormat = defaultTimestampFormat
|
timestampFormat = DefaultTimestampFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
if !f.DisableTimestamp {
|
if !f.DisableTimestamp {
|
@ -4,7 +4,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Logger struct {
|
type Logger struct {
|
||||||
@ -25,7 +24,7 @@ type Logger struct {
|
|||||||
Formatter Formatter
|
Formatter Formatter
|
||||||
// The logging level the logger should log at. This is typically (and defaults
|
// The logging level the logger should log at. This is typically (and defaults
|
||||||
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
||||||
// logged.
|
// logged. `logrus.Debug` is useful in
|
||||||
Level Level
|
Level Level
|
||||||
// Used to sync writing to the log. Locking is enabled by Default
|
// Used to sync writing to the log. Locking is enabled by Default
|
||||||
mu MutexWrap
|
mu MutexWrap
|
||||||
@ -113,7 +112,7 @@ func (logger *Logger) WithError(err error) *Entry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
||||||
if logger.level() >= DebugLevel {
|
if logger.Level >= DebugLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Debugf(format, args...)
|
entry.Debugf(format, args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
@ -121,7 +120,7 @@ func (logger *Logger) Debugf(format string, args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Infof(format string, args ...interface{}) {
|
func (logger *Logger) Infof(format string, args ...interface{}) {
|
||||||
if logger.level() >= InfoLevel {
|
if logger.Level >= InfoLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Infof(format, args...)
|
entry.Infof(format, args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
@ -135,7 +134,7 @@ func (logger *Logger) Printf(format string, args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
||||||
if logger.level() >= WarnLevel {
|
if logger.Level >= WarnLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Warnf(format, args...)
|
entry.Warnf(format, args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
@ -143,7 +142,7 @@ func (logger *Logger) Warnf(format string, args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
||||||
if logger.level() >= WarnLevel {
|
if logger.Level >= WarnLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Warnf(format, args...)
|
entry.Warnf(format, args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
@ -151,7 +150,7 @@ func (logger *Logger) Warningf(format string, args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
||||||
if logger.level() >= ErrorLevel {
|
if logger.Level >= ErrorLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Errorf(format, args...)
|
entry.Errorf(format, args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
@ -159,7 +158,7 @@ func (logger *Logger) Errorf(format string, args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
||||||
if logger.level() >= FatalLevel {
|
if logger.Level >= FatalLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Fatalf(format, args...)
|
entry.Fatalf(format, args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
@ -168,7 +167,7 @@ func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
||||||
if logger.level() >= PanicLevel {
|
if logger.Level >= PanicLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Panicf(format, args...)
|
entry.Panicf(format, args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
@ -176,7 +175,7 @@ func (logger *Logger) Panicf(format string, args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Debug(args ...interface{}) {
|
func (logger *Logger) Debug(args ...interface{}) {
|
||||||
if logger.level() >= DebugLevel {
|
if logger.Level >= DebugLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Debug(args...)
|
entry.Debug(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
@ -184,7 +183,7 @@ func (logger *Logger) Debug(args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Info(args ...interface{}) {
|
func (logger *Logger) Info(args ...interface{}) {
|
||||||
if logger.level() >= InfoLevel {
|
if logger.Level >= InfoLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Info(args...)
|
entry.Info(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
@ -198,7 +197,7 @@ func (logger *Logger) Print(args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Warn(args ...interface{}) {
|
func (logger *Logger) Warn(args ...interface{}) {
|
||||||
if logger.level() >= WarnLevel {
|
if logger.Level >= WarnLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Warn(args...)
|
entry.Warn(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
@ -206,7 +205,7 @@ func (logger *Logger) Warn(args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Warning(args ...interface{}) {
|
func (logger *Logger) Warning(args ...interface{}) {
|
||||||
if logger.level() >= WarnLevel {
|
if logger.Level >= WarnLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Warn(args...)
|
entry.Warn(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
@ -214,7 +213,7 @@ func (logger *Logger) Warning(args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Error(args ...interface{}) {
|
func (logger *Logger) Error(args ...interface{}) {
|
||||||
if logger.level() >= ErrorLevel {
|
if logger.Level >= ErrorLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Error(args...)
|
entry.Error(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
@ -222,7 +221,7 @@ func (logger *Logger) Error(args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Fatal(args ...interface{}) {
|
func (logger *Logger) Fatal(args ...interface{}) {
|
||||||
if logger.level() >= FatalLevel {
|
if logger.Level >= FatalLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Fatal(args...)
|
entry.Fatal(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
@ -231,7 +230,7 @@ func (logger *Logger) Fatal(args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Panic(args ...interface{}) {
|
func (logger *Logger) Panic(args ...interface{}) {
|
||||||
if logger.level() >= PanicLevel {
|
if logger.Level >= PanicLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Panic(args...)
|
entry.Panic(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
@ -239,7 +238,7 @@ func (logger *Logger) Panic(args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Debugln(args ...interface{}) {
|
func (logger *Logger) Debugln(args ...interface{}) {
|
||||||
if logger.level() >= DebugLevel {
|
if logger.Level >= DebugLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Debugln(args...)
|
entry.Debugln(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
@ -247,7 +246,7 @@ func (logger *Logger) Debugln(args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Infoln(args ...interface{}) {
|
func (logger *Logger) Infoln(args ...interface{}) {
|
||||||
if logger.level() >= InfoLevel {
|
if logger.Level >= InfoLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Infoln(args...)
|
entry.Infoln(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
@ -261,7 +260,7 @@ func (logger *Logger) Println(args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Warnln(args ...interface{}) {
|
func (logger *Logger) Warnln(args ...interface{}) {
|
||||||
if logger.level() >= WarnLevel {
|
if logger.Level >= WarnLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Warnln(args...)
|
entry.Warnln(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
@ -269,7 +268,7 @@ func (logger *Logger) Warnln(args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Warningln(args ...interface{}) {
|
func (logger *Logger) Warningln(args ...interface{}) {
|
||||||
if logger.level() >= WarnLevel {
|
if logger.Level >= WarnLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Warnln(args...)
|
entry.Warnln(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
@ -277,7 +276,7 @@ func (logger *Logger) Warningln(args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Errorln(args ...interface{}) {
|
func (logger *Logger) Errorln(args ...interface{}) {
|
||||||
if logger.level() >= ErrorLevel {
|
if logger.Level >= ErrorLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Errorln(args...)
|
entry.Errorln(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
@ -285,7 +284,7 @@ func (logger *Logger) Errorln(args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Fatalln(args ...interface{}) {
|
func (logger *Logger) Fatalln(args ...interface{}) {
|
||||||
if logger.level() >= FatalLevel {
|
if logger.Level >= FatalLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Fatalln(args...)
|
entry.Fatalln(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
@ -294,7 +293,7 @@ func (logger *Logger) Fatalln(args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Panicln(args ...interface{}) {
|
func (logger *Logger) Panicln(args ...interface{}) {
|
||||||
if logger.level() >= PanicLevel {
|
if logger.Level >= PanicLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Panicln(args...)
|
entry.Panicln(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
@ -307,17 +306,3 @@ func (logger *Logger) Panicln(args ...interface{}) {
|
|||||||
func (logger *Logger) SetNoLock() {
|
func (logger *Logger) SetNoLock() {
|
||||||
logger.mu.Disable()
|
logger.mu.Disable()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) level() Level {
|
|
||||||
return Level(atomic.LoadUint32((*uint32)(&logger.Level)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) SetLevel(level Level) {
|
|
||||||
atomic.StoreUint32((*uint32)(&logger.Level), uint32(level))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) AddHook(hook Hook) {
|
|
||||||
logger.mu.Lock()
|
|
||||||
defer logger.mu.Unlock()
|
|
||||||
logger.Hooks.Add(hook)
|
|
||||||
}
|
|
@ -10,7 +10,7 @@ import (
|
|||||||
type Fields map[string]interface{}
|
type Fields map[string]interface{}
|
||||||
|
|
||||||
// Level type
|
// Level type
|
||||||
type Level uint32
|
type Level uint8
|
||||||
|
|
||||||
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
|
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
|
||||||
func (level Level) String() string {
|
func (level Level) String() string {
|
10
vendor/github.com/Sirupsen/logrus/terminal_appengine.go
generated
vendored
Normal file
10
vendor/github.com/Sirupsen/logrus/terminal_appengine.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// +build appengine
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
||||||
|
func IsTerminal(f io.Writer) bool {
|
||||||
|
return true
|
||||||
|
}
|
10
vendor/github.com/Sirupsen/logrus/terminal_bsd.go
generated
vendored
Normal file
10
vendor/github.com/Sirupsen/logrus/terminal_bsd.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// +build darwin freebsd openbsd netbsd dragonfly
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const ioctlReadTermios = syscall.TIOCGETA
|
||||||
|
|
||||||
|
type Termios syscall.Termios
|
@ -7,8 +7,8 @@
|
|||||||
|
|
||||||
package logrus
|
package logrus
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
import "syscall"
|
||||||
|
|
||||||
const ioctlReadTermios = unix.TCGETS
|
const ioctlReadTermios = syscall.TCGETS
|
||||||
|
|
||||||
type Termios unix.Termios
|
type Termios syscall.Termios
|
28
vendor/github.com/Sirupsen/logrus/terminal_notwindows.go
generated
vendored
Normal file
28
vendor/github.com/Sirupsen/logrus/terminal_notwindows.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Based on ssh/terminal:
|
||||||
|
// Copyright 2011 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 linux darwin freebsd openbsd netbsd dragonfly
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
||||||
|
func IsTerminal(f io.Writer) bool {
|
||||||
|
var termios Termios
|
||||||
|
switch v := f.(type) {
|
||||||
|
case *os.File:
|
||||||
|
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(v.Fd()), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
||||||
|
return err == 0
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
21
vendor/github.com/Sirupsen/logrus/terminal_solaris.go
generated
vendored
Normal file
21
vendor/github.com/Sirupsen/logrus/terminal_solaris.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// +build solaris,!appengine
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
func IsTerminal(f io.Writer) bool {
|
||||||
|
switch v := f.(type) {
|
||||||
|
case *os.File:
|
||||||
|
_, err := unix.IoctlGetTermios(int(v.Fd()), unix.TCGETA)
|
||||||
|
return err == nil
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
33
vendor/github.com/Sirupsen/logrus/terminal_windows.go
generated
vendored
Normal file
33
vendor/github.com/Sirupsen/logrus/terminal_windows.go
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// Based on ssh/terminal:
|
||||||
|
// Copyright 2011 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 windows,!appengine
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
|
||||||
|
var (
|
||||||
|
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
||||||
|
func IsTerminal(f io.Writer) bool {
|
||||||
|
switch v := f.(type) {
|
||||||
|
case *os.File:
|
||||||
|
var st uint32
|
||||||
|
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(v.Fd()), uintptr(unsafe.Pointer(&st)), 0)
|
||||||
|
return r != 0 && e == 0
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@ const (
|
|||||||
red = 31
|
red = 31
|
||||||
green = 32
|
green = 32
|
||||||
yellow = 33
|
yellow = 33
|
||||||
blue = 36
|
blue = 34
|
||||||
gray = 37
|
gray = 37
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -26,7 +26,6 @@ func init() {
|
|||||||
baseTimestamp = time.Now()
|
baseTimestamp = time.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TextFormatter formats logs into text
|
|
||||||
type TextFormatter struct {
|
type TextFormatter struct {
|
||||||
// Set to true to bypass checking for a TTY before outputting colors.
|
// Set to true to bypass checking for a TTY before outputting colors.
|
||||||
ForceColors bool
|
ForceColors bool
|
||||||
@ -53,6 +52,10 @@ type TextFormatter struct {
|
|||||||
// QuoteEmptyFields will wrap empty fields in quotes if true
|
// QuoteEmptyFields will wrap empty fields in quotes if true
|
||||||
QuoteEmptyFields bool
|
QuoteEmptyFields bool
|
||||||
|
|
||||||
|
// QuoteCharacter can be set to the override the default quoting character "
|
||||||
|
// with something else. For example: ', or `.
|
||||||
|
QuoteCharacter string
|
||||||
|
|
||||||
// Whether the logger's out is to a terminal
|
// Whether the logger's out is to a terminal
|
||||||
isTerminal bool
|
isTerminal bool
|
||||||
|
|
||||||
@ -60,12 +63,14 @@ type TextFormatter struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *TextFormatter) init(entry *Entry) {
|
func (f *TextFormatter) init(entry *Entry) {
|
||||||
|
if len(f.QuoteCharacter) == 0 {
|
||||||
|
f.QuoteCharacter = "\""
|
||||||
|
}
|
||||||
if entry.Logger != nil {
|
if entry.Logger != nil {
|
||||||
f.isTerminal = checkIfTerminal(entry.Logger.Out)
|
f.isTerminal = IsTerminal(entry.Logger.Out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format renders a single log entry
|
|
||||||
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
var b *bytes.Buffer
|
var b *bytes.Buffer
|
||||||
keys := make([]string, 0, len(entry.Data))
|
keys := make([]string, 0, len(entry.Data))
|
||||||
@ -90,7 +95,7 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
|||||||
|
|
||||||
timestampFormat := f.TimestampFormat
|
timestampFormat := f.TimestampFormat
|
||||||
if timestampFormat == "" {
|
if timestampFormat == "" {
|
||||||
timestampFormat = defaultTimestampFormat
|
timestampFormat = DefaultTimestampFormat
|
||||||
}
|
}
|
||||||
if isColored {
|
if isColored {
|
||||||
f.printColored(b, entry, keys, timestampFormat)
|
f.printColored(b, entry, keys, timestampFormat)
|
||||||
@ -148,7 +153,7 @@ func (f *TextFormatter) needsQuoting(text string) bool {
|
|||||||
if !((ch >= 'a' && ch <= 'z') ||
|
if !((ch >= 'a' && ch <= 'z') ||
|
||||||
(ch >= 'A' && ch <= 'Z') ||
|
(ch >= 'A' && ch <= 'Z') ||
|
||||||
(ch >= '0' && ch <= '9') ||
|
(ch >= '0' && ch <= '9') ||
|
||||||
ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') {
|
ch == '-' || ch == '.') {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,23 +161,29 @@ func (f *TextFormatter) needsQuoting(text string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
|
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
|
||||||
if b.Len() > 0 {
|
|
||||||
b.WriteByte(' ')
|
|
||||||
}
|
|
||||||
b.WriteString(key)
|
b.WriteString(key)
|
||||||
b.WriteByte('=')
|
b.WriteByte('=')
|
||||||
f.appendValue(b, value)
|
f.appendValue(b, value)
|
||||||
|
b.WriteByte(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
|
func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
|
||||||
stringVal, ok := value.(string)
|
switch value := value.(type) {
|
||||||
if !ok {
|
case string:
|
||||||
stringVal = fmt.Sprint(value)
|
if !f.needsQuoting(value) {
|
||||||
}
|
b.WriteString(value)
|
||||||
|
} else {
|
||||||
if !f.needsQuoting(stringVal) {
|
fmt.Fprintf(b, "%s%v%s", f.QuoteCharacter, value, f.QuoteCharacter)
|
||||||
b.WriteString(stringVal)
|
}
|
||||||
} else {
|
case error:
|
||||||
b.WriteString(fmt.Sprintf("%q", stringVal))
|
errmsg := value.Error()
|
||||||
|
if !f.needsQuoting(errmsg) {
|
||||||
|
b.WriteString(errmsg)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(b, "%s%v%s", f.QuoteCharacter, errmsg, f.QuoteCharacter)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
fmt.Fprint(b, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
362
vendor/github.com/armon/consul-api/LICENSE
generated
vendored
362
vendor/github.com/armon/consul-api/LICENSE
generated
vendored
@ -1,362 +0,0 @@
|
|||||||
Mozilla Public License, version 2.0
|
|
||||||
|
|
||||||
1. Definitions
|
|
||||||
|
|
||||||
1.1. "Contributor"
|
|
||||||
|
|
||||||
means each individual or legal entity that creates, contributes to the
|
|
||||||
creation of, or owns Covered Software.
|
|
||||||
|
|
||||||
1.2. "Contributor Version"
|
|
||||||
|
|
||||||
means the combination of the Contributions of others (if any) used by a
|
|
||||||
Contributor and that particular Contributor's Contribution.
|
|
||||||
|
|
||||||
1.3. "Contribution"
|
|
||||||
|
|
||||||
means Covered Software of a particular Contributor.
|
|
||||||
|
|
||||||
1.4. "Covered Software"
|
|
||||||
|
|
||||||
means Source Code Form to which the initial Contributor has attached the
|
|
||||||
notice in Exhibit A, the Executable Form of such Source Code Form, and
|
|
||||||
Modifications of such Source Code Form, in each case including portions
|
|
||||||
thereof.
|
|
||||||
|
|
||||||
1.5. "Incompatible With Secondary Licenses"
|
|
||||||
means
|
|
||||||
|
|
||||||
a. that the initial Contributor has attached the notice described in
|
|
||||||
Exhibit B to the Covered Software; or
|
|
||||||
|
|
||||||
b. that the Covered Software was made available under the terms of
|
|
||||||
version 1.1 or earlier of the License, but not also under the terms of
|
|
||||||
a Secondary License.
|
|
||||||
|
|
||||||
1.6. "Executable Form"
|
|
||||||
|
|
||||||
means any form of the work other than Source Code Form.
|
|
||||||
|
|
||||||
1.7. "Larger Work"
|
|
||||||
|
|
||||||
means a work that combines Covered Software with other material, in a
|
|
||||||
separate file or files, that is not Covered Software.
|
|
||||||
|
|
||||||
1.8. "License"
|
|
||||||
|
|
||||||
means this document.
|
|
||||||
|
|
||||||
1.9. "Licensable"
|
|
||||||
|
|
||||||
means having the right to grant, to the maximum extent possible, whether
|
|
||||||
at the time of the initial grant or subsequently, any and all of the
|
|
||||||
rights conveyed by this License.
|
|
||||||
|
|
||||||
1.10. "Modifications"
|
|
||||||
|
|
||||||
means any of the following:
|
|
||||||
|
|
||||||
a. any file in Source Code Form that results from an addition to,
|
|
||||||
deletion from, or modification of the contents of Covered Software; or
|
|
||||||
|
|
||||||
b. any new file in Source Code Form that contains any Covered Software.
|
|
||||||
|
|
||||||
1.11. "Patent Claims" of a Contributor
|
|
||||||
|
|
||||||
means any patent claim(s), including without limitation, method,
|
|
||||||
process, and apparatus claims, in any patent Licensable by such
|
|
||||||
Contributor that would be infringed, but for the grant of the License,
|
|
||||||
by the making, using, selling, offering for sale, having made, import,
|
|
||||||
or transfer of either its Contributions or its Contributor Version.
|
|
||||||
|
|
||||||
1.12. "Secondary License"
|
|
||||||
|
|
||||||
means either the GNU General Public License, Version 2.0, the GNU Lesser
|
|
||||||
General Public License, Version 2.1, the GNU Affero General Public
|
|
||||||
License, Version 3.0, or any later versions of those licenses.
|
|
||||||
|
|
||||||
1.13. "Source Code Form"
|
|
||||||
|
|
||||||
means the form of the work preferred for making modifications.
|
|
||||||
|
|
||||||
1.14. "You" (or "Your")
|
|
||||||
|
|
||||||
means an individual or a legal entity exercising rights under this
|
|
||||||
License. For legal entities, "You" includes any entity that controls, is
|
|
||||||
controlled by, or is under common control with You. For purposes of this
|
|
||||||
definition, "control" means (a) the power, direct or indirect, to cause
|
|
||||||
the direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (b) ownership of more than fifty percent (50%) of the
|
|
||||||
outstanding shares or beneficial ownership of such entity.
|
|
||||||
|
|
||||||
|
|
||||||
2. License Grants and Conditions
|
|
||||||
|
|
||||||
2.1. Grants
|
|
||||||
|
|
||||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
|
||||||
non-exclusive license:
|
|
||||||
|
|
||||||
a. under intellectual property rights (other than patent or trademark)
|
|
||||||
Licensable by such Contributor to use, reproduce, make available,
|
|
||||||
modify, display, perform, distribute, and otherwise exploit its
|
|
||||||
Contributions, either on an unmodified basis, with Modifications, or
|
|
||||||
as part of a Larger Work; and
|
|
||||||
|
|
||||||
b. under Patent Claims of such Contributor to make, use, sell, offer for
|
|
||||||
sale, have made, import, and otherwise transfer either its
|
|
||||||
Contributions or its Contributor Version.
|
|
||||||
|
|
||||||
2.2. Effective Date
|
|
||||||
|
|
||||||
The licenses granted in Section 2.1 with respect to any Contribution
|
|
||||||
become effective for each Contribution on the date the Contributor first
|
|
||||||
distributes such Contribution.
|
|
||||||
|
|
||||||
2.3. Limitations on Grant Scope
|
|
||||||
|
|
||||||
The licenses granted in this Section 2 are the only rights granted under
|
|
||||||
this License. No additional rights or licenses will be implied from the
|
|
||||||
distribution or licensing of Covered Software under this License.
|
|
||||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
|
||||||
Contributor:
|
|
||||||
|
|
||||||
a. for any code that a Contributor has removed from Covered Software; or
|
|
||||||
|
|
||||||
b. for infringements caused by: (i) Your and any other third party's
|
|
||||||
modifications of Covered Software, or (ii) the combination of its
|
|
||||||
Contributions with other software (except as part of its Contributor
|
|
||||||
Version); or
|
|
||||||
|
|
||||||
c. under Patent Claims infringed by Covered Software in the absence of
|
|
||||||
its Contributions.
|
|
||||||
|
|
||||||
This License does not grant any rights in the trademarks, service marks,
|
|
||||||
or logos of any Contributor (except as may be necessary to comply with
|
|
||||||
the notice requirements in Section 3.4).
|
|
||||||
|
|
||||||
2.4. Subsequent Licenses
|
|
||||||
|
|
||||||
No Contributor makes additional grants as a result of Your choice to
|
|
||||||
distribute the Covered Software under a subsequent version of this
|
|
||||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
|
||||||
permitted under the terms of Section 3.3).
|
|
||||||
|
|
||||||
2.5. Representation
|
|
||||||
|
|
||||||
Each Contributor represents that the Contributor believes its
|
|
||||||
Contributions are its original creation(s) or it has sufficient rights to
|
|
||||||
grant the rights to its Contributions conveyed by this License.
|
|
||||||
|
|
||||||
2.6. Fair Use
|
|
||||||
|
|
||||||
This License is not intended to limit any rights You have under
|
|
||||||
applicable copyright doctrines of fair use, fair dealing, or other
|
|
||||||
equivalents.
|
|
||||||
|
|
||||||
2.7. Conditions
|
|
||||||
|
|
||||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
|
||||||
Section 2.1.
|
|
||||||
|
|
||||||
|
|
||||||
3. Responsibilities
|
|
||||||
|
|
||||||
3.1. Distribution of Source Form
|
|
||||||
|
|
||||||
All distribution of Covered Software in Source Code Form, including any
|
|
||||||
Modifications that You create or to which You contribute, must be under
|
|
||||||
the terms of this License. You must inform recipients that the Source
|
|
||||||
Code Form of the Covered Software is governed by the terms of this
|
|
||||||
License, and how they can obtain a copy of this License. You may not
|
|
||||||
attempt to alter or restrict the recipients' rights in the Source Code
|
|
||||||
Form.
|
|
||||||
|
|
||||||
3.2. Distribution of Executable Form
|
|
||||||
|
|
||||||
If You distribute Covered Software in Executable Form then:
|
|
||||||
|
|
||||||
a. such Covered Software must also be made available in Source Code Form,
|
|
||||||
as described in Section 3.1, and You must inform recipients of the
|
|
||||||
Executable Form how they can obtain a copy of such Source Code Form by
|
|
||||||
reasonable means in a timely manner, at a charge no more than the cost
|
|
||||||
of distribution to the recipient; and
|
|
||||||
|
|
||||||
b. You may distribute such Executable Form under the terms of this
|
|
||||||
License, or sublicense it under different terms, provided that the
|
|
||||||
license for the Executable Form does not attempt to limit or alter the
|
|
||||||
recipients' rights in the Source Code Form under this License.
|
|
||||||
|
|
||||||
3.3. Distribution of a Larger Work
|
|
||||||
|
|
||||||
You may create and distribute a Larger Work under terms of Your choice,
|
|
||||||
provided that You also comply with the requirements of this License for
|
|
||||||
the Covered Software. If the Larger Work is a combination of Covered
|
|
||||||
Software with a work governed by one or more Secondary Licenses, and the
|
|
||||||
Covered Software is not Incompatible With Secondary Licenses, this
|
|
||||||
License permits You to additionally distribute such Covered Software
|
|
||||||
under the terms of such Secondary License(s), so that the recipient of
|
|
||||||
the Larger Work may, at their option, further distribute the Covered
|
|
||||||
Software under the terms of either this License or such Secondary
|
|
||||||
License(s).
|
|
||||||
|
|
||||||
3.4. Notices
|
|
||||||
|
|
||||||
You may not remove or alter the substance of any license notices
|
|
||||||
(including copyright notices, patent notices, disclaimers of warranty, or
|
|
||||||
limitations of liability) contained within the Source Code Form of the
|
|
||||||
Covered Software, except that You may alter any license notices to the
|
|
||||||
extent required to remedy known factual inaccuracies.
|
|
||||||
|
|
||||||
3.5. Application of Additional Terms
|
|
||||||
|
|
||||||
You may choose to offer, and to charge a fee for, warranty, support,
|
|
||||||
indemnity or liability obligations to one or more recipients of Covered
|
|
||||||
Software. However, You may do so only on Your own behalf, and not on
|
|
||||||
behalf of any Contributor. You must make it absolutely clear that any
|
|
||||||
such warranty, support, indemnity, or liability obligation is offered by
|
|
||||||
You alone, and You hereby agree to indemnify every Contributor for any
|
|
||||||
liability incurred by such Contributor as a result of warranty, support,
|
|
||||||
indemnity or liability terms You offer. You may include additional
|
|
||||||
disclaimers of warranty and limitations of liability specific to any
|
|
||||||
jurisdiction.
|
|
||||||
|
|
||||||
4. Inability to Comply Due to Statute or Regulation
|
|
||||||
|
|
||||||
If it is impossible for You to comply with any of the terms of this License
|
|
||||||
with respect to some or all of the Covered Software due to statute,
|
|
||||||
judicial order, or regulation then You must: (a) comply with the terms of
|
|
||||||
this License to the maximum extent possible; and (b) describe the
|
|
||||||
limitations and the code they affect. Such description must be placed in a
|
|
||||||
text file included with all distributions of the Covered Software under
|
|
||||||
this License. Except to the extent prohibited by statute or regulation,
|
|
||||||
such description must be sufficiently detailed for a recipient of ordinary
|
|
||||||
skill to be able to understand it.
|
|
||||||
|
|
||||||
5. Termination
|
|
||||||
|
|
||||||
5.1. The rights granted under this License will terminate automatically if You
|
|
||||||
fail to comply with any of its terms. However, if You become compliant,
|
|
||||||
then the rights granted under this License from a particular Contributor
|
|
||||||
are reinstated (a) provisionally, unless and until such Contributor
|
|
||||||
explicitly and finally terminates Your grants, and (b) on an ongoing
|
|
||||||
basis, if such Contributor fails to notify You of the non-compliance by
|
|
||||||
some reasonable means prior to 60 days after You have come back into
|
|
||||||
compliance. Moreover, Your grants from a particular Contributor are
|
|
||||||
reinstated on an ongoing basis if such Contributor notifies You of the
|
|
||||||
non-compliance by some reasonable means, this is the first time You have
|
|
||||||
received notice of non-compliance with this License from such
|
|
||||||
Contributor, and You become compliant prior to 30 days after Your receipt
|
|
||||||
of the notice.
|
|
||||||
|
|
||||||
5.2. If You initiate litigation against any entity by asserting a patent
|
|
||||||
infringement claim (excluding declaratory judgment actions,
|
|
||||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
|
||||||
directly or indirectly infringes any patent, then the rights granted to
|
|
||||||
You by any and all Contributors for the Covered Software under Section
|
|
||||||
2.1 of this License shall terminate.
|
|
||||||
|
|
||||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
|
|
||||||
license agreements (excluding distributors and resellers) which have been
|
|
||||||
validly granted by You or Your distributors under this License prior to
|
|
||||||
termination shall survive termination.
|
|
||||||
|
|
||||||
6. Disclaimer of Warranty
|
|
||||||
|
|
||||||
Covered Software is provided under this License on an "as is" basis,
|
|
||||||
without warranty of any kind, either expressed, implied, or statutory,
|
|
||||||
including, without limitation, warranties that the Covered Software is free
|
|
||||||
of defects, merchantable, fit for a particular purpose or non-infringing.
|
|
||||||
The entire risk as to the quality and performance of the Covered Software
|
|
||||||
is with You. Should any Covered Software prove defective in any respect,
|
|
||||||
You (not any Contributor) assume the cost of any necessary servicing,
|
|
||||||
repair, or correction. This disclaimer of warranty constitutes an essential
|
|
||||||
part of this License. No use of any Covered Software is authorized under
|
|
||||||
this License except under this disclaimer.
|
|
||||||
|
|
||||||
7. Limitation of Liability
|
|
||||||
|
|
||||||
Under no circumstances and under no legal theory, whether tort (including
|
|
||||||
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
|
||||||
distributes Covered Software as permitted above, be liable to You for any
|
|
||||||
direct, indirect, special, incidental, or consequential damages of any
|
|
||||||
character including, without limitation, damages for lost profits, loss of
|
|
||||||
goodwill, work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses, even if such party shall have been
|
|
||||||
informed of the possibility of such damages. This limitation of liability
|
|
||||||
shall not apply to liability for death or personal injury resulting from
|
|
||||||
such party's negligence to the extent applicable law prohibits such
|
|
||||||
limitation. Some jurisdictions do not allow the exclusion or limitation of
|
|
||||||
incidental or consequential damages, so this exclusion and limitation may
|
|
||||||
not apply to You.
|
|
||||||
|
|
||||||
8. Litigation
|
|
||||||
|
|
||||||
Any litigation relating to this License may be brought only in the courts
|
|
||||||
of a jurisdiction where the defendant maintains its principal place of
|
|
||||||
business and such litigation shall be governed by laws of that
|
|
||||||
jurisdiction, without reference to its conflict-of-law provisions. Nothing
|
|
||||||
in this Section shall prevent a party's ability to bring cross-claims or
|
|
||||||
counter-claims.
|
|
||||||
|
|
||||||
9. Miscellaneous
|
|
||||||
|
|
||||||
This License represents the complete agreement concerning the subject
|
|
||||||
matter hereof. If any provision of this License is held to be
|
|
||||||
unenforceable, such provision shall be reformed only to the extent
|
|
||||||
necessary to make it enforceable. Any law or regulation which provides that
|
|
||||||
the language of a contract shall be construed against the drafter shall not
|
|
||||||
be used to construe this License against a Contributor.
|
|
||||||
|
|
||||||
|
|
||||||
10. Versions of the License
|
|
||||||
|
|
||||||
10.1. New Versions
|
|
||||||
|
|
||||||
Mozilla Foundation is the license steward. Except as provided in Section
|
|
||||||
10.3, no one other than the license steward has the right to modify or
|
|
||||||
publish new versions of this License. Each version will be given a
|
|
||||||
distinguishing version number.
|
|
||||||
|
|
||||||
10.2. Effect of New Versions
|
|
||||||
|
|
||||||
You may distribute the Covered Software under the terms of the version
|
|
||||||
of the License under which You originally received the Covered Software,
|
|
||||||
or under the terms of any subsequent version published by the license
|
|
||||||
steward.
|
|
||||||
|
|
||||||
10.3. Modified Versions
|
|
||||||
|
|
||||||
If you create software not governed by this License, and you want to
|
|
||||||
create a new license for such software, you may create and use a
|
|
||||||
modified version of this License if you rename the license and remove
|
|
||||||
any references to the name of the license steward (except to note that
|
|
||||||
such modified license differs from this License).
|
|
||||||
|
|
||||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
|
||||||
Licenses If You choose to distribute Source Code Form that is
|
|
||||||
Incompatible With Secondary Licenses under the terms of this version of
|
|
||||||
the License, the notice described in Exhibit B of this License must be
|
|
||||||
attached.
|
|
||||||
|
|
||||||
Exhibit A - Source Code Form License Notice
|
|
||||||
|
|
||||||
This Source Code Form is subject to the
|
|
||||||
terms of the Mozilla Public License, v.
|
|
||||||
2.0. If a copy of the MPL was not
|
|
||||||
distributed with this file, You can
|
|
||||||
obtain one at
|
|
||||||
http://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
If it is not possible or desirable to put the notice in a particular file,
|
|
||||||
then You may include the notice in a location (such as a LICENSE file in a
|
|
||||||
relevant directory) where a recipient would be likely to look for such a
|
|
||||||
notice.
|
|
||||||
|
|
||||||
You may add additional accurate notices of copyright ownership.
|
|
||||||
|
|
||||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
|
||||||
|
|
||||||
This Source Code Form is "Incompatible
|
|
||||||
With Secondary Licenses", as defined by
|
|
||||||
the Mozilla Public License, v. 2.0.
|
|
140
vendor/github.com/armon/consul-api/acl.go
generated
vendored
140
vendor/github.com/armon/consul-api/acl.go
generated
vendored
@ -1,140 +0,0 @@
|
|||||||
package consulapi
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ACLCLientType is the client type token
|
|
||||||
ACLClientType = "client"
|
|
||||||
|
|
||||||
// ACLManagementType is the management type token
|
|
||||||
ACLManagementType = "management"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ACLEntry is used to represent an ACL entry
|
|
||||||
type ACLEntry struct {
|
|
||||||
CreateIndex uint64
|
|
||||||
ModifyIndex uint64
|
|
||||||
ID string
|
|
||||||
Name string
|
|
||||||
Type string
|
|
||||||
Rules string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ACL can be used to query the ACL endpoints
|
|
||||||
type ACL struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// ACL returns a handle to the ACL endpoints
|
|
||||||
func (c *Client) ACL() *ACL {
|
|
||||||
return &ACL{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create is used to generate a new token with the given parameters
|
|
||||||
func (a *ACL) Create(acl *ACLEntry, q *WriteOptions) (string, *WriteMeta, error) {
|
|
||||||
r := a.c.newRequest("PUT", "/v1/acl/create")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = acl
|
|
||||||
rtt, resp, err := requireOK(a.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{RequestTime: rtt}
|
|
||||||
var out struct{ ID string }
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
return out.ID, wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update is used to update the rules of an existing token
|
|
||||||
func (a *ACL) Update(acl *ACLEntry, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
r := a.c.newRequest("PUT", "/v1/acl/update")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = acl
|
|
||||||
rtt, resp, err := requireOK(a.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{RequestTime: rtt}
|
|
||||||
return wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destroy is used to destroy a given ACL token ID
|
|
||||||
func (a *ACL) Destroy(id string, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
r := a.c.newRequest("PUT", "/v1/acl/destroy/"+id)
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
rtt, resp, err := requireOK(a.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{RequestTime: rtt}
|
|
||||||
return wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clone is used to return a new token cloned from an existing one
|
|
||||||
func (a *ACL) Clone(id string, q *WriteOptions) (string, *WriteMeta, error) {
|
|
||||||
r := a.c.newRequest("PUT", "/v1/acl/clone/"+id)
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
rtt, resp, err := requireOK(a.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{RequestTime: rtt}
|
|
||||||
var out struct{ ID string }
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
return out.ID, wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info is used to query for information about an ACL token
|
|
||||||
func (a *ACL) Info(id string, q *QueryOptions) (*ACLEntry, *QueryMeta, error) {
|
|
||||||
r := a.c.newRequest("GET", "/v1/acl/info/"+id)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(a.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var entries []*ACLEntry
|
|
||||||
if err := decodeBody(resp, &entries); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if len(entries) > 0 {
|
|
||||||
return entries[0], qm, nil
|
|
||||||
}
|
|
||||||
return nil, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// List is used to get all the ACL tokens
|
|
||||||
func (a *ACL) List(q *QueryOptions) ([]*ACLEntry, *QueryMeta, error) {
|
|
||||||
r := a.c.newRequest("GET", "/v1/acl/list")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(a.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var entries []*ACLEntry
|
|
||||||
if err := decodeBody(resp, &entries); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return entries, qm, nil
|
|
||||||
}
|
|
272
vendor/github.com/armon/consul-api/agent.go
generated
vendored
272
vendor/github.com/armon/consul-api/agent.go
generated
vendored
@ -1,272 +0,0 @@
|
|||||||
package consulapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AgentCheck represents a check known to the agent
|
|
||||||
type AgentCheck struct {
|
|
||||||
Node string
|
|
||||||
CheckID string
|
|
||||||
Name string
|
|
||||||
Status string
|
|
||||||
Notes string
|
|
||||||
Output string
|
|
||||||
ServiceID string
|
|
||||||
ServiceName string
|
|
||||||
}
|
|
||||||
|
|
||||||
// AgentService represents a service known to the agent
|
|
||||||
type AgentService struct {
|
|
||||||
ID string
|
|
||||||
Service string
|
|
||||||
Tags []string
|
|
||||||
Port int
|
|
||||||
}
|
|
||||||
|
|
||||||
// AgentMember represents a cluster member known to the agent
|
|
||||||
type AgentMember struct {
|
|
||||||
Name string
|
|
||||||
Addr string
|
|
||||||
Port uint16
|
|
||||||
Tags map[string]string
|
|
||||||
Status int
|
|
||||||
ProtocolMin uint8
|
|
||||||
ProtocolMax uint8
|
|
||||||
ProtocolCur uint8
|
|
||||||
DelegateMin uint8
|
|
||||||
DelegateMax uint8
|
|
||||||
DelegateCur uint8
|
|
||||||
}
|
|
||||||
|
|
||||||
// AgentServiceRegistration is used to register a new service
|
|
||||||
type AgentServiceRegistration struct {
|
|
||||||
ID string `json:",omitempty"`
|
|
||||||
Name string `json:",omitempty"`
|
|
||||||
Tags []string `json:",omitempty"`
|
|
||||||
Port int `json:",omitempty"`
|
|
||||||
Check *AgentServiceCheck
|
|
||||||
}
|
|
||||||
|
|
||||||
// AgentCheckRegistration is used to register a new check
|
|
||||||
type AgentCheckRegistration struct {
|
|
||||||
ID string `json:",omitempty"`
|
|
||||||
Name string `json:",omitempty"`
|
|
||||||
Notes string `json:",omitempty"`
|
|
||||||
AgentServiceCheck
|
|
||||||
}
|
|
||||||
|
|
||||||
// AgentServiceCheck is used to create an associated
|
|
||||||
// check for a service
|
|
||||||
type AgentServiceCheck struct {
|
|
||||||
Script string `json:",omitempty"`
|
|
||||||
Interval string `json:",omitempty"`
|
|
||||||
TTL string `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Agent can be used to query the Agent endpoints
|
|
||||||
type Agent struct {
|
|
||||||
c *Client
|
|
||||||
|
|
||||||
// cache the node name
|
|
||||||
nodeName string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Agent returns a handle to the agent endpoints
|
|
||||||
func (c *Client) Agent() *Agent {
|
|
||||||
return &Agent{c: c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Self is used to query the agent we are speaking to for
|
|
||||||
// information about itself
|
|
||||||
func (a *Agent) Self() (map[string]map[string]interface{}, error) {
|
|
||||||
r := a.c.newRequest("GET", "/v1/agent/self")
|
|
||||||
_, resp, err := requireOK(a.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var out map[string]map[string]interface{}
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NodeName is used to get the node name of the agent
|
|
||||||
func (a *Agent) NodeName() (string, error) {
|
|
||||||
if a.nodeName != "" {
|
|
||||||
return a.nodeName, nil
|
|
||||||
}
|
|
||||||
info, err := a.Self()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
name := info["Config"]["NodeName"].(string)
|
|
||||||
a.nodeName = name
|
|
||||||
return name, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks returns the locally registered checks
|
|
||||||
func (a *Agent) Checks() (map[string]*AgentCheck, error) {
|
|
||||||
r := a.c.newRequest("GET", "/v1/agent/checks")
|
|
||||||
_, resp, err := requireOK(a.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var out map[string]*AgentCheck
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Services returns the locally registered services
|
|
||||||
func (a *Agent) Services() (map[string]*AgentService, error) {
|
|
||||||
r := a.c.newRequest("GET", "/v1/agent/services")
|
|
||||||
_, resp, err := requireOK(a.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var out map[string]*AgentService
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Members returns the known gossip members. The WAN
|
|
||||||
// flag can be used to query a server for WAN members.
|
|
||||||
func (a *Agent) Members(wan bool) ([]*AgentMember, error) {
|
|
||||||
r := a.c.newRequest("GET", "/v1/agent/members")
|
|
||||||
if wan {
|
|
||||||
r.params.Set("wan", "1")
|
|
||||||
}
|
|
||||||
_, resp, err := requireOK(a.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var out []*AgentMember
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceRegister is used to register a new service with
|
|
||||||
// the local agent
|
|
||||||
func (a *Agent) ServiceRegister(service *AgentServiceRegistration) error {
|
|
||||||
r := a.c.newRequest("PUT", "/v1/agent/service/register")
|
|
||||||
r.obj = service
|
|
||||||
_, resp, err := requireOK(a.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceDeregister is used to deregister a service with
|
|
||||||
// the local agent
|
|
||||||
func (a *Agent) ServiceDeregister(serviceID string) error {
|
|
||||||
r := a.c.newRequest("PUT", "/v1/agent/service/deregister/"+serviceID)
|
|
||||||
_, resp, err := requireOK(a.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PassTTL is used to set a TTL check to the passing state
|
|
||||||
func (a *Agent) PassTTL(checkID, note string) error {
|
|
||||||
return a.UpdateTTL(checkID, note, "pass")
|
|
||||||
}
|
|
||||||
|
|
||||||
// WarnTTL is used to set a TTL check to the warning state
|
|
||||||
func (a *Agent) WarnTTL(checkID, note string) error {
|
|
||||||
return a.UpdateTTL(checkID, note, "warn")
|
|
||||||
}
|
|
||||||
|
|
||||||
// FailTTL is used to set a TTL check to the failing state
|
|
||||||
func (a *Agent) FailTTL(checkID, note string) error {
|
|
||||||
return a.UpdateTTL(checkID, note, "fail")
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateTTL is used to update the TTL of a check
|
|
||||||
func (a *Agent) UpdateTTL(checkID, note, status string) error {
|
|
||||||
switch status {
|
|
||||||
case "pass":
|
|
||||||
case "warn":
|
|
||||||
case "fail":
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("Invalid status: %s", status)
|
|
||||||
}
|
|
||||||
endpoint := fmt.Sprintf("/v1/agent/check/%s/%s", status, checkID)
|
|
||||||
r := a.c.newRequest("PUT", endpoint)
|
|
||||||
r.params.Set("note", note)
|
|
||||||
_, resp, err := requireOK(a.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckRegister is used to register a new check with
|
|
||||||
// the local agent
|
|
||||||
func (a *Agent) CheckRegister(check *AgentCheckRegistration) error {
|
|
||||||
r := a.c.newRequest("PUT", "/v1/agent/check/register")
|
|
||||||
r.obj = check
|
|
||||||
_, resp, err := requireOK(a.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckDeregister is used to deregister a check with
|
|
||||||
// the local agent
|
|
||||||
func (a *Agent) CheckDeregister(checkID string) error {
|
|
||||||
r := a.c.newRequest("PUT", "/v1/agent/check/deregister/"+checkID)
|
|
||||||
_, resp, err := requireOK(a.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Join is used to instruct the agent to attempt a join to
|
|
||||||
// another cluster member
|
|
||||||
func (a *Agent) Join(addr string, wan bool) error {
|
|
||||||
r := a.c.newRequest("PUT", "/v1/agent/join/"+addr)
|
|
||||||
if wan {
|
|
||||||
r.params.Set("wan", "1")
|
|
||||||
}
|
|
||||||
_, resp, err := requireOK(a.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ForceLeave is used to have the agent eject a failed node
|
|
||||||
func (a *Agent) ForceLeave(node string) error {
|
|
||||||
r := a.c.newRequest("PUT", "/v1/agent/force-leave/"+node)
|
|
||||||
_, resp, err := requireOK(a.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
323
vendor/github.com/armon/consul-api/api.go
generated
vendored
323
vendor/github.com/armon/consul-api/api.go
generated
vendored
@ -1,323 +0,0 @@
|
|||||||
package consulapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// QueryOptions are used to parameterize a query
|
|
||||||
type QueryOptions struct {
|
|
||||||
// Providing a datacenter overwrites the DC provided
|
|
||||||
// by the Config
|
|
||||||
Datacenter string
|
|
||||||
|
|
||||||
// AllowStale allows any Consul server (non-leader) to service
|
|
||||||
// a read. This allows for lower latency and higher throughput
|
|
||||||
AllowStale bool
|
|
||||||
|
|
||||||
// RequireConsistent forces the read to be fully consistent.
|
|
||||||
// This is more expensive but prevents ever performing a stale
|
|
||||||
// read.
|
|
||||||
RequireConsistent bool
|
|
||||||
|
|
||||||
// WaitIndex is used to enable a blocking query. Waits
|
|
||||||
// until the timeout or the next index is reached
|
|
||||||
WaitIndex uint64
|
|
||||||
|
|
||||||
// WaitTime is used to bound the duration of a wait.
|
|
||||||
// Defaults to that of the Config, but can be overriden.
|
|
||||||
WaitTime time.Duration
|
|
||||||
|
|
||||||
// Token is used to provide a per-request ACL token
|
|
||||||
// which overrides the agent's default token.
|
|
||||||
Token string
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteOptions are used to parameterize a write
|
|
||||||
type WriteOptions struct {
|
|
||||||
// Providing a datacenter overwrites the DC provided
|
|
||||||
// by the Config
|
|
||||||
Datacenter string
|
|
||||||
|
|
||||||
// Token is used to provide a per-request ACL token
|
|
||||||
// which overrides the agent's default token.
|
|
||||||
Token string
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryMeta is used to return meta data about a query
|
|
||||||
type QueryMeta struct {
|
|
||||||
// LastIndex. This can be used as a WaitIndex to perform
|
|
||||||
// a blocking query
|
|
||||||
LastIndex uint64
|
|
||||||
|
|
||||||
// Time of last contact from the leader for the
|
|
||||||
// server servicing the request
|
|
||||||
LastContact time.Duration
|
|
||||||
|
|
||||||
// Is there a known leader
|
|
||||||
KnownLeader bool
|
|
||||||
|
|
||||||
// How long did the request take
|
|
||||||
RequestTime time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteMeta is used to return meta data about a write
|
|
||||||
type WriteMeta struct {
|
|
||||||
// How long did the request take
|
|
||||||
RequestTime time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// HttpBasicAuth is used to authenticate http client with HTTP Basic Authentication
|
|
||||||
type HttpBasicAuth struct {
|
|
||||||
// Username to use for HTTP Basic Authentication
|
|
||||||
Username string
|
|
||||||
|
|
||||||
// Password to use for HTTP Basic Authentication
|
|
||||||
Password string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config is used to configure the creation of a client
|
|
||||||
type Config struct {
|
|
||||||
// Address is the address of the Consul server
|
|
||||||
Address string
|
|
||||||
|
|
||||||
// Scheme is the URI scheme for the Consul server
|
|
||||||
Scheme string
|
|
||||||
|
|
||||||
// Datacenter to use. If not provided, the default agent datacenter is used.
|
|
||||||
Datacenter string
|
|
||||||
|
|
||||||
// HttpClient is the client to use. Default will be
|
|
||||||
// used if not provided.
|
|
||||||
HttpClient *http.Client
|
|
||||||
|
|
||||||
// HttpAuth is the auth info to use for http access.
|
|
||||||
HttpAuth *HttpBasicAuth
|
|
||||||
|
|
||||||
// WaitTime limits how long a Watch will block. If not provided,
|
|
||||||
// the agent default values will be used.
|
|
||||||
WaitTime time.Duration
|
|
||||||
|
|
||||||
// Token is used to provide a per-request ACL token
|
|
||||||
// which overrides the agent's default token.
|
|
||||||
Token string
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultConfig returns a default configuration for the client
|
|
||||||
func DefaultConfig() *Config {
|
|
||||||
return &Config{
|
|
||||||
Address: "127.0.0.1:8500",
|
|
||||||
Scheme: "http",
|
|
||||||
HttpClient: http.DefaultClient,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client provides a client to the Consul API
|
|
||||||
type Client struct {
|
|
||||||
config Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClient returns a new client
|
|
||||||
func NewClient(config *Config) (*Client, error) {
|
|
||||||
// bootstrap the config
|
|
||||||
defConfig := DefaultConfig()
|
|
||||||
|
|
||||||
if len(config.Address) == 0 {
|
|
||||||
config.Address = defConfig.Address
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(config.Scheme) == 0 {
|
|
||||||
config.Scheme = defConfig.Scheme
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.HttpClient == nil {
|
|
||||||
config.HttpClient = defConfig.HttpClient
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &Client{
|
|
||||||
config: *config,
|
|
||||||
}
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// request is used to help build up a request
|
|
||||||
type request struct {
|
|
||||||
config *Config
|
|
||||||
method string
|
|
||||||
url *url.URL
|
|
||||||
params url.Values
|
|
||||||
body io.Reader
|
|
||||||
obj interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// setQueryOptions is used to annotate the request with
|
|
||||||
// additional query options
|
|
||||||
func (r *request) setQueryOptions(q *QueryOptions) {
|
|
||||||
if q == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if q.Datacenter != "" {
|
|
||||||
r.params.Set("dc", q.Datacenter)
|
|
||||||
}
|
|
||||||
if q.AllowStale {
|
|
||||||
r.params.Set("stale", "")
|
|
||||||
}
|
|
||||||
if q.RequireConsistent {
|
|
||||||
r.params.Set("consistent", "")
|
|
||||||
}
|
|
||||||
if q.WaitIndex != 0 {
|
|
||||||
r.params.Set("index", strconv.FormatUint(q.WaitIndex, 10))
|
|
||||||
}
|
|
||||||
if q.WaitTime != 0 {
|
|
||||||
r.params.Set("wait", durToMsec(q.WaitTime))
|
|
||||||
}
|
|
||||||
if q.Token != "" {
|
|
||||||
r.params.Set("token", q.Token)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// durToMsec converts a duration to a millisecond specified string
|
|
||||||
func durToMsec(dur time.Duration) string {
|
|
||||||
return fmt.Sprintf("%dms", dur/time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
// setWriteOptions is used to annotate the request with
|
|
||||||
// additional write options
|
|
||||||
func (r *request) setWriteOptions(q *WriteOptions) {
|
|
||||||
if q == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if q.Datacenter != "" {
|
|
||||||
r.params.Set("dc", q.Datacenter)
|
|
||||||
}
|
|
||||||
if q.Token != "" {
|
|
||||||
r.params.Set("token", q.Token)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// toHTTP converts the request to an HTTP request
|
|
||||||
func (r *request) toHTTP() (*http.Request, error) {
|
|
||||||
// Encode the query parameters
|
|
||||||
r.url.RawQuery = r.params.Encode()
|
|
||||||
|
|
||||||
// Get the url sring
|
|
||||||
urlRaw := r.url.String()
|
|
||||||
|
|
||||||
// Check if we should encode the body
|
|
||||||
if r.body == nil && r.obj != nil {
|
|
||||||
if b, err := encodeBody(r.obj); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
r.body = b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the HTTP request
|
|
||||||
req, err := http.NewRequest(r.method, urlRaw, r.body)
|
|
||||||
|
|
||||||
// Setup auth
|
|
||||||
if err == nil && r.config.HttpAuth != nil {
|
|
||||||
req.SetBasicAuth(r.config.HttpAuth.Username, r.config.HttpAuth.Password)
|
|
||||||
}
|
|
||||||
|
|
||||||
return req, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// newRequest is used to create a new request
|
|
||||||
func (c *Client) newRequest(method, path string) *request {
|
|
||||||
r := &request{
|
|
||||||
config: &c.config,
|
|
||||||
method: method,
|
|
||||||
url: &url.URL{
|
|
||||||
Scheme: c.config.Scheme,
|
|
||||||
Host: c.config.Address,
|
|
||||||
Path: path,
|
|
||||||
},
|
|
||||||
params: make(map[string][]string),
|
|
||||||
}
|
|
||||||
if c.config.Datacenter != "" {
|
|
||||||
r.params.Set("dc", c.config.Datacenter)
|
|
||||||
}
|
|
||||||
if c.config.WaitTime != 0 {
|
|
||||||
r.params.Set("wait", durToMsec(r.config.WaitTime))
|
|
||||||
}
|
|
||||||
if c.config.Token != "" {
|
|
||||||
r.params.Set("token", r.config.Token)
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// doRequest runs a request with our client
|
|
||||||
func (c *Client) doRequest(r *request) (time.Duration, *http.Response, error) {
|
|
||||||
req, err := r.toHTTP()
|
|
||||||
if err != nil {
|
|
||||||
return 0, nil, err
|
|
||||||
}
|
|
||||||
start := time.Now()
|
|
||||||
resp, err := c.config.HttpClient.Do(req)
|
|
||||||
diff := time.Now().Sub(start)
|
|
||||||
return diff, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseQueryMeta is used to help parse query meta-data
|
|
||||||
func parseQueryMeta(resp *http.Response, q *QueryMeta) error {
|
|
||||||
header := resp.Header
|
|
||||||
|
|
||||||
// Parse the X-Consul-Index
|
|
||||||
index, err := strconv.ParseUint(header.Get("X-Consul-Index"), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to parse X-Consul-Index: %v", err)
|
|
||||||
}
|
|
||||||
q.LastIndex = index
|
|
||||||
|
|
||||||
// Parse the X-Consul-LastContact
|
|
||||||
last, err := strconv.ParseUint(header.Get("X-Consul-LastContact"), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to parse X-Consul-LastContact: %v", err)
|
|
||||||
}
|
|
||||||
q.LastContact = time.Duration(last) * time.Millisecond
|
|
||||||
|
|
||||||
// Parse the X-Consul-KnownLeader
|
|
||||||
switch header.Get("X-Consul-KnownLeader") {
|
|
||||||
case "true":
|
|
||||||
q.KnownLeader = true
|
|
||||||
default:
|
|
||||||
q.KnownLeader = false
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// decodeBody is used to JSON decode a body
|
|
||||||
func decodeBody(resp *http.Response, out interface{}) error {
|
|
||||||
dec := json.NewDecoder(resp.Body)
|
|
||||||
return dec.Decode(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
// encodeBody is used to encode a request body
|
|
||||||
func encodeBody(obj interface{}) (io.Reader, error) {
|
|
||||||
buf := bytes.NewBuffer(nil)
|
|
||||||
enc := json.NewEncoder(buf)
|
|
||||||
if err := enc.Encode(obj); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// requireOK is used to wrap doRequest and check for a 200
|
|
||||||
func requireOK(d time.Duration, resp *http.Response, e error) (time.Duration, *http.Response, error) {
|
|
||||||
if e != nil {
|
|
||||||
return d, resp, e
|
|
||||||
}
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
io.Copy(&buf, resp.Body)
|
|
||||||
return d, resp, fmt.Errorf("Unexpected response code: %d (%s)", resp.StatusCode, buf.Bytes())
|
|
||||||
}
|
|
||||||
return d, resp, e
|
|
||||||
}
|
|
181
vendor/github.com/armon/consul-api/catalog.go
generated
vendored
181
vendor/github.com/armon/consul-api/catalog.go
generated
vendored
@ -1,181 +0,0 @@
|
|||||||
package consulapi
|
|
||||||
|
|
||||||
type Node struct {
|
|
||||||
Node string
|
|
||||||
Address string
|
|
||||||
}
|
|
||||||
|
|
||||||
type CatalogService struct {
|
|
||||||
Node string
|
|
||||||
Address string
|
|
||||||
ServiceID string
|
|
||||||
ServiceName string
|
|
||||||
ServiceTags []string
|
|
||||||
ServicePort int
|
|
||||||
}
|
|
||||||
|
|
||||||
type CatalogNode struct {
|
|
||||||
Node *Node
|
|
||||||
Services map[string]*AgentService
|
|
||||||
}
|
|
||||||
|
|
||||||
type CatalogRegistration struct {
|
|
||||||
Node string
|
|
||||||
Address string
|
|
||||||
Datacenter string
|
|
||||||
Service *AgentService
|
|
||||||
Check *AgentCheck
|
|
||||||
}
|
|
||||||
|
|
||||||
type CatalogDeregistration struct {
|
|
||||||
Node string
|
|
||||||
Address string
|
|
||||||
Datacenter string
|
|
||||||
ServiceID string
|
|
||||||
CheckID string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Catalog can be used to query the Catalog endpoints
|
|
||||||
type Catalog struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Catalog returns a handle to the catalog endpoints
|
|
||||||
func (c *Client) Catalog() *Catalog {
|
|
||||||
return &Catalog{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Catalog) Register(reg *CatalogRegistration, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
r := c.c.newRequest("PUT", "/v1/catalog/register")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = reg
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{}
|
|
||||||
wm.RequestTime = rtt
|
|
||||||
|
|
||||||
return wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Catalog) Deregister(dereg *CatalogDeregistration, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
r := c.c.newRequest("PUT", "/v1/catalog/deregister")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = dereg
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{}
|
|
||||||
wm.RequestTime = rtt
|
|
||||||
|
|
||||||
return wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Datacenters is used to query for all the known datacenters
|
|
||||||
func (c *Catalog) Datacenters() ([]string, error) {
|
|
||||||
r := c.c.newRequest("GET", "/v1/catalog/datacenters")
|
|
||||||
_, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var out []string
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nodes is used to query all the known nodes
|
|
||||||
func (c *Catalog) Nodes(q *QueryOptions) ([]*Node, *QueryMeta, error) {
|
|
||||||
r := c.c.newRequest("GET", "/v1/catalog/nodes")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out []*Node
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Services is used to query for all known services
|
|
||||||
func (c *Catalog) Services(q *QueryOptions) (map[string][]string, *QueryMeta, error) {
|
|
||||||
r := c.c.newRequest("GET", "/v1/catalog/services")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out map[string][]string
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Service is used to query catalog entries for a given service
|
|
||||||
func (c *Catalog) Service(service, tag string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) {
|
|
||||||
r := c.c.newRequest("GET", "/v1/catalog/service/"+service)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
if tag != "" {
|
|
||||||
r.params.Set("tag", tag)
|
|
||||||
}
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out []*CatalogService
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Node is used to query for service information about a single node
|
|
||||||
func (c *Catalog) Node(node string, q *QueryOptions) (*CatalogNode, *QueryMeta, error) {
|
|
||||||
r := c.c.newRequest("GET", "/v1/catalog/node/"+node)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out *CatalogNode
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
104
vendor/github.com/armon/consul-api/event.go
generated
vendored
104
vendor/github.com/armon/consul-api/event.go
generated
vendored
@ -1,104 +0,0 @@
|
|||||||
package consulapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Event can be used to query the Event endpoints
|
|
||||||
type Event struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserEvent represents an event that was fired by the user
|
|
||||||
type UserEvent struct {
|
|
||||||
ID string
|
|
||||||
Name string
|
|
||||||
Payload []byte
|
|
||||||
NodeFilter string
|
|
||||||
ServiceFilter string
|
|
||||||
TagFilter string
|
|
||||||
Version int
|
|
||||||
LTime uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event returns a handle to the event endpoints
|
|
||||||
func (c *Client) Event() *Event {
|
|
||||||
return &Event{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fire is used to fire a new user event. Only the Name, Payload and Filters
|
|
||||||
// are respected. This returns the ID or an associated error. Cross DC requests
|
|
||||||
// are supported.
|
|
||||||
func (e *Event) Fire(params *UserEvent, q *WriteOptions) (string, *WriteMeta, error) {
|
|
||||||
r := e.c.newRequest("PUT", "/v1/event/fire/"+params.Name)
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
if params.NodeFilter != "" {
|
|
||||||
r.params.Set("node", params.NodeFilter)
|
|
||||||
}
|
|
||||||
if params.ServiceFilter != "" {
|
|
||||||
r.params.Set("service", params.ServiceFilter)
|
|
||||||
}
|
|
||||||
if params.TagFilter != "" {
|
|
||||||
r.params.Set("tag", params.TagFilter)
|
|
||||||
}
|
|
||||||
if params.Payload != nil {
|
|
||||||
r.body = bytes.NewReader(params.Payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
rtt, resp, err := requireOK(e.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{RequestTime: rtt}
|
|
||||||
var out UserEvent
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
return out.ID, wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// List is used to get the most recent events an agent has received.
|
|
||||||
// This list can be optionally filtered by the name. This endpoint supports
|
|
||||||
// quasi-blocking queries. The index is not monotonic, nor does it provide provide
|
|
||||||
// LastContact or KnownLeader.
|
|
||||||
func (e *Event) List(name string, q *QueryOptions) ([]*UserEvent, *QueryMeta, error) {
|
|
||||||
r := e.c.newRequest("GET", "/v1/event/list")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
if name != "" {
|
|
||||||
r.params.Set("name", name)
|
|
||||||
}
|
|
||||||
rtt, resp, err := requireOK(e.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var entries []*UserEvent
|
|
||||||
if err := decodeBody(resp, &entries); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return entries, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IDToIndex is a bit of a hack. This simulates the index generation to
|
|
||||||
// convert an event ID into a WaitIndex.
|
|
||||||
func (e *Event) IDToIndex(uuid string) uint64 {
|
|
||||||
lower := uuid[0:8] + uuid[9:13] + uuid[14:18]
|
|
||||||
upper := uuid[19:23] + uuid[24:36]
|
|
||||||
lowVal, err := strconv.ParseUint(lower, 16, 64)
|
|
||||||
if err != nil {
|
|
||||||
panic("Failed to convert " + lower)
|
|
||||||
}
|
|
||||||
highVal, err := strconv.ParseUint(upper, 16, 64)
|
|
||||||
if err != nil {
|
|
||||||
panic("Failed to convert " + upper)
|
|
||||||
}
|
|
||||||
return lowVal ^ highVal
|
|
||||||
}
|
|
136
vendor/github.com/armon/consul-api/health.go
generated
vendored
136
vendor/github.com/armon/consul-api/health.go
generated
vendored
@ -1,136 +0,0 @@
|
|||||||
package consulapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HealthCheck is used to represent a single check
|
|
||||||
type HealthCheck struct {
|
|
||||||
Node string
|
|
||||||
CheckID string
|
|
||||||
Name string
|
|
||||||
Status string
|
|
||||||
Notes string
|
|
||||||
Output string
|
|
||||||
ServiceID string
|
|
||||||
ServiceName string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceEntry is used for the health service endpoint
|
|
||||||
type ServiceEntry struct {
|
|
||||||
Node *Node
|
|
||||||
Service *AgentService
|
|
||||||
Checks []*HealthCheck
|
|
||||||
}
|
|
||||||
|
|
||||||
// Health can be used to query the Health endpoints
|
|
||||||
type Health struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Health returns a handle to the health endpoints
|
|
||||||
func (c *Client) Health() *Health {
|
|
||||||
return &Health{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Node is used to query for checks belonging to a given node
|
|
||||||
func (h *Health) Node(node string, q *QueryOptions) ([]*HealthCheck, *QueryMeta, error) {
|
|
||||||
r := h.c.newRequest("GET", "/v1/health/node/"+node)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out []*HealthCheck
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks is used to return the checks associated with a service
|
|
||||||
func (h *Health) Checks(service string, q *QueryOptions) ([]*HealthCheck, *QueryMeta, error) {
|
|
||||||
r := h.c.newRequest("GET", "/v1/health/checks/"+service)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out []*HealthCheck
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Service is used to query health information along with service info
|
|
||||||
// for a given service. It can optionally do server-side filtering on a tag
|
|
||||||
// or nodes with passing health checks only.
|
|
||||||
func (h *Health) Service(service, tag string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) {
|
|
||||||
r := h.c.newRequest("GET", "/v1/health/service/"+service)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
if tag != "" {
|
|
||||||
r.params.Set("tag", tag)
|
|
||||||
}
|
|
||||||
if passingOnly {
|
|
||||||
r.params.Set("passing", "1")
|
|
||||||
}
|
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out []*ServiceEntry
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// State is used to retrieve all the checks in a given state.
|
|
||||||
// The wildcard "any" state can also be used for all checks.
|
|
||||||
func (h *Health) State(state string, q *QueryOptions) ([]*HealthCheck, *QueryMeta, error) {
|
|
||||||
switch state {
|
|
||||||
case "any":
|
|
||||||
case "warning":
|
|
||||||
case "critical":
|
|
||||||
case "passing":
|
|
||||||
case "unknown":
|
|
||||||
default:
|
|
||||||
return nil, nil, fmt.Errorf("Unsupported state: %v", state)
|
|
||||||
}
|
|
||||||
r := h.c.newRequest("GET", "/v1/health/state/"+state)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out []*HealthCheck
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
219
vendor/github.com/armon/consul-api/kv.go
generated
vendored
219
vendor/github.com/armon/consul-api/kv.go
generated
vendored
@ -1,219 +0,0 @@
|
|||||||
package consulapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// KVPair is used to represent a single K/V entry
|
|
||||||
type KVPair struct {
|
|
||||||
Key string
|
|
||||||
CreateIndex uint64
|
|
||||||
ModifyIndex uint64
|
|
||||||
LockIndex uint64
|
|
||||||
Flags uint64
|
|
||||||
Value []byte
|
|
||||||
Session string
|
|
||||||
}
|
|
||||||
|
|
||||||
// KVPairs is a list of KVPair objects
|
|
||||||
type KVPairs []*KVPair
|
|
||||||
|
|
||||||
// KV is used to manipulate the K/V API
|
|
||||||
type KV struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// KV is used to return a handle to the K/V apis
|
|
||||||
func (c *Client) KV() *KV {
|
|
||||||
return &KV{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get is used to lookup a single key
|
|
||||||
func (k *KV) Get(key string, q *QueryOptions) (*KVPair, *QueryMeta, error) {
|
|
||||||
resp, qm, err := k.getInternal(key, nil, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if resp == nil {
|
|
||||||
return nil, qm, nil
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var entries []*KVPair
|
|
||||||
if err := decodeBody(resp, &entries); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if len(entries) > 0 {
|
|
||||||
return entries[0], qm, nil
|
|
||||||
}
|
|
||||||
return nil, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// List is used to lookup all keys under a prefix
|
|
||||||
func (k *KV) List(prefix string, q *QueryOptions) (KVPairs, *QueryMeta, error) {
|
|
||||||
resp, qm, err := k.getInternal(prefix, map[string]string{"recurse": ""}, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if resp == nil {
|
|
||||||
return nil, qm, nil
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var entries []*KVPair
|
|
||||||
if err := decodeBody(resp, &entries); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return entries, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keys is used to list all the keys under a prefix. Optionally,
|
|
||||||
// a separator can be used to limit the responses.
|
|
||||||
func (k *KV) Keys(prefix, separator string, q *QueryOptions) ([]string, *QueryMeta, error) {
|
|
||||||
params := map[string]string{"keys": ""}
|
|
||||||
if separator != "" {
|
|
||||||
params["separator"] = separator
|
|
||||||
}
|
|
||||||
resp, qm, err := k.getInternal(prefix, params, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if resp == nil {
|
|
||||||
return nil, qm, nil
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var entries []string
|
|
||||||
if err := decodeBody(resp, &entries); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return entries, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KV) getInternal(key string, params map[string]string, q *QueryOptions) (*http.Response, *QueryMeta, error) {
|
|
||||||
r := k.c.newRequest("GET", "/v1/kv/"+key)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
for param, val := range params {
|
|
||||||
r.params.Set(param, val)
|
|
||||||
}
|
|
||||||
rtt, resp, err := k.c.doRequest(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
if resp.StatusCode == 404 {
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil, qm, nil
|
|
||||||
} else if resp.StatusCode != 200 {
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil, nil, fmt.Errorf("Unexpected response code: %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
return resp, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put is used to write a new value. Only the
|
|
||||||
// Key, Flags and Value is respected.
|
|
||||||
func (k *KV) Put(p *KVPair, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
params := make(map[string]string, 1)
|
|
||||||
if p.Flags != 0 {
|
|
||||||
params["flags"] = strconv.FormatUint(p.Flags, 10)
|
|
||||||
}
|
|
||||||
_, wm, err := k.put(p.Key, params, p.Value, q)
|
|
||||||
return wm, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CAS is used for a Check-And-Set operation. The Key,
|
|
||||||
// ModifyIndex, Flags and Value are respected. Returns true
|
|
||||||
// on success or false on failures.
|
|
||||||
func (k *KV) CAS(p *KVPair, q *WriteOptions) (bool, *WriteMeta, error) {
|
|
||||||
params := make(map[string]string, 2)
|
|
||||||
if p.Flags != 0 {
|
|
||||||
params["flags"] = strconv.FormatUint(p.Flags, 10)
|
|
||||||
}
|
|
||||||
params["cas"] = strconv.FormatUint(p.ModifyIndex, 10)
|
|
||||||
return k.put(p.Key, params, p.Value, q)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Acquire is used for a lock acquisiiton operation. The Key,
|
|
||||||
// Flags, Value and Session are respected. Returns true
|
|
||||||
// on success or false on failures.
|
|
||||||
func (k *KV) Acquire(p *KVPair, q *WriteOptions) (bool, *WriteMeta, error) {
|
|
||||||
params := make(map[string]string, 2)
|
|
||||||
if p.Flags != 0 {
|
|
||||||
params["flags"] = strconv.FormatUint(p.Flags, 10)
|
|
||||||
}
|
|
||||||
params["acquire"] = p.Session
|
|
||||||
return k.put(p.Key, params, p.Value, q)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release is used for a lock release operation. The Key,
|
|
||||||
// Flags, Value and Session are respected. Returns true
|
|
||||||
// on success or false on failures.
|
|
||||||
func (k *KV) Release(p *KVPair, q *WriteOptions) (bool, *WriteMeta, error) {
|
|
||||||
params := make(map[string]string, 2)
|
|
||||||
if p.Flags != 0 {
|
|
||||||
params["flags"] = strconv.FormatUint(p.Flags, 10)
|
|
||||||
}
|
|
||||||
params["release"] = p.Session
|
|
||||||
return k.put(p.Key, params, p.Value, q)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KV) put(key string, params map[string]string, body []byte, q *WriteOptions) (bool, *WriteMeta, error) {
|
|
||||||
r := k.c.newRequest("PUT", "/v1/kv/"+key)
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
for param, val := range params {
|
|
||||||
r.params.Set(param, val)
|
|
||||||
}
|
|
||||||
r.body = bytes.NewReader(body)
|
|
||||||
rtt, resp, err := requireOK(k.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &WriteMeta{}
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if _, err := io.Copy(&buf, resp.Body); err != nil {
|
|
||||||
return false, nil, fmt.Errorf("Failed to read response: %v", err)
|
|
||||||
}
|
|
||||||
res := strings.Contains(string(buf.Bytes()), "true")
|
|
||||||
return res, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete is used to delete a single key
|
|
||||||
func (k *KV) Delete(key string, w *WriteOptions) (*WriteMeta, error) {
|
|
||||||
return k.deleteInternal(key, nil, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteTree is used to delete all keys under a prefix
|
|
||||||
func (k *KV) DeleteTree(prefix string, w *WriteOptions) (*WriteMeta, error) {
|
|
||||||
return k.deleteInternal(prefix, []string{"recurse"}, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KV) deleteInternal(key string, params []string, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
r := k.c.newRequest("DELETE", "/v1/kv/"+key)
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
for _, param := range params {
|
|
||||||
r.params.Set(param, "")
|
|
||||||
}
|
|
||||||
rtt, resp, err := requireOK(k.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &WriteMeta{}
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
return qm, nil
|
|
||||||
}
|
|
204
vendor/github.com/armon/consul-api/session.go
generated
vendored
204
vendor/github.com/armon/consul-api/session.go
generated
vendored
@ -1,204 +0,0 @@
|
|||||||
package consulapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SessionEntry represents a session in consul
|
|
||||||
type SessionEntry struct {
|
|
||||||
CreateIndex uint64
|
|
||||||
ID string
|
|
||||||
Name string
|
|
||||||
Node string
|
|
||||||
Checks []string
|
|
||||||
LockDelay time.Duration
|
|
||||||
Behavior string
|
|
||||||
TTL string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Session can be used to query the Session endpoints
|
|
||||||
type Session struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Session returns a handle to the session endpoints
|
|
||||||
func (c *Client) Session() *Session {
|
|
||||||
return &Session{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateNoChecks is like Create but is used specifically to create
|
|
||||||
// a session with no associated health checks.
|
|
||||||
func (s *Session) CreateNoChecks(se *SessionEntry, q *WriteOptions) (string, *WriteMeta, error) {
|
|
||||||
body := make(map[string]interface{})
|
|
||||||
body["Checks"] = []string{}
|
|
||||||
if se != nil {
|
|
||||||
if se.Name != "" {
|
|
||||||
body["Name"] = se.Name
|
|
||||||
}
|
|
||||||
if se.Node != "" {
|
|
||||||
body["Node"] = se.Node
|
|
||||||
}
|
|
||||||
if se.LockDelay != 0 {
|
|
||||||
body["LockDelay"] = durToMsec(se.LockDelay)
|
|
||||||
}
|
|
||||||
if se.Behavior != "" {
|
|
||||||
body["Behavior"] = se.Behavior
|
|
||||||
}
|
|
||||||
if se.TTL != "" {
|
|
||||||
body["TTL"] = se.TTL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s.create(body, q)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create makes a new session. Providing a session entry can
|
|
||||||
// customize the session. It can also be nil to use defaults.
|
|
||||||
func (s *Session) Create(se *SessionEntry, q *WriteOptions) (string, *WriteMeta, error) {
|
|
||||||
var obj interface{}
|
|
||||||
if se != nil {
|
|
||||||
body := make(map[string]interface{})
|
|
||||||
obj = body
|
|
||||||
if se.Name != "" {
|
|
||||||
body["Name"] = se.Name
|
|
||||||
}
|
|
||||||
if se.Node != "" {
|
|
||||||
body["Node"] = se.Node
|
|
||||||
}
|
|
||||||
if se.LockDelay != 0 {
|
|
||||||
body["LockDelay"] = durToMsec(se.LockDelay)
|
|
||||||
}
|
|
||||||
if len(se.Checks) > 0 {
|
|
||||||
body["Checks"] = se.Checks
|
|
||||||
}
|
|
||||||
if se.Behavior != "" {
|
|
||||||
body["Behavior"] = se.Behavior
|
|
||||||
}
|
|
||||||
if se.TTL != "" {
|
|
||||||
body["TTL"] = se.TTL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s.create(obj, q)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) create(obj interface{}, q *WriteOptions) (string, *WriteMeta, error) {
|
|
||||||
r := s.c.newRequest("PUT", "/v1/session/create")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = obj
|
|
||||||
rtt, resp, err := requireOK(s.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{RequestTime: rtt}
|
|
||||||
var out struct{ ID string }
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
return out.ID, wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destroy invalides a given session
|
|
||||||
func (s *Session) Destroy(id string, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
r := s.c.newRequest("PUT", "/v1/session/destroy/"+id)
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
rtt, resp, err := requireOK(s.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{RequestTime: rtt}
|
|
||||||
return wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Renew renews the TTL on a given session
|
|
||||||
func (s *Session) Renew(id string, q *WriteOptions) (*SessionEntry, *WriteMeta, error) {
|
|
||||||
r := s.c.newRequest("PUT", "/v1/session/renew/"+id)
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
rtt, resp, err := requireOK(s.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{RequestTime: rtt}
|
|
||||||
|
|
||||||
var entries []*SessionEntry
|
|
||||||
if err := decodeBody(resp, &entries); err != nil {
|
|
||||||
return nil, wm, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(entries) > 0 {
|
|
||||||
return entries[0], wm, nil
|
|
||||||
}
|
|
||||||
return nil, wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info looks up a single session
|
|
||||||
func (s *Session) Info(id string, q *QueryOptions) (*SessionEntry, *QueryMeta, error) {
|
|
||||||
r := s.c.newRequest("GET", "/v1/session/info/"+id)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(s.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var entries []*SessionEntry
|
|
||||||
if err := decodeBody(resp, &entries); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(entries) > 0 {
|
|
||||||
return entries[0], qm, nil
|
|
||||||
}
|
|
||||||
return nil, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// List gets sessions for a node
|
|
||||||
func (s *Session) Node(node string, q *QueryOptions) ([]*SessionEntry, *QueryMeta, error) {
|
|
||||||
r := s.c.newRequest("GET", "/v1/session/node/"+node)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(s.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var entries []*SessionEntry
|
|
||||||
if err := decodeBody(resp, &entries); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return entries, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// List gets all active sessions
|
|
||||||
func (s *Session) List(q *QueryOptions) ([]*SessionEntry, *QueryMeta, error) {
|
|
||||||
r := s.c.newRequest("GET", "/v1/session/list")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(s.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var entries []*SessionEntry
|
|
||||||
if err := decodeBody(resp, &entries); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return entries, qm, nil
|
|
||||||
}
|
|
43
vendor/github.com/armon/consul-api/status.go
generated
vendored
43
vendor/github.com/armon/consul-api/status.go
generated
vendored
@ -1,43 +0,0 @@
|
|||||||
package consulapi
|
|
||||||
|
|
||||||
// Status can be used to query the Status endpoints
|
|
||||||
type Status struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status returns a handle to the status endpoints
|
|
||||||
func (c *Client) Status() *Status {
|
|
||||||
return &Status{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Leader is used to query for a known leader
|
|
||||||
func (s *Status) Leader() (string, error) {
|
|
||||||
r := s.c.newRequest("GET", "/v1/status/leader")
|
|
||||||
_, resp, err := requireOK(s.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var leader string
|
|
||||||
if err := decodeBody(resp, &leader); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return leader, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Peers is used to query for a known raft peers
|
|
||||||
func (s *Status) Peers() ([]string, error) {
|
|
||||||
r := s.c.newRequest("GET", "/v1/status/peers")
|
|
||||||
_, resp, err := requireOK(s.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var peers []string
|
|
||||||
if err := decodeBody(resp, &peers); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return peers, nil
|
|
||||||
}
|
|
4
vendor/github.com/bwmarrin/discordgo/discord.go
generated
vendored
4
vendor/github.com/bwmarrin/discordgo/discord.go
generated
vendored
@ -21,7 +21,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/)
|
// VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/)
|
||||||
const VERSION = "0.18.0"
|
const VERSION = "0.17.0"
|
||||||
|
|
||||||
// ErrMFA will be risen by New when the user has 2FA.
|
// ErrMFA will be risen by New when the user has 2FA.
|
||||||
var ErrMFA = errors.New("account has 2FA enabled")
|
var ErrMFA = errors.New("account has 2FA enabled")
|
||||||
@ -50,7 +50,7 @@ func New(args ...interface{}) (s *Session, err error) {
|
|||||||
// Create an empty Session interface.
|
// Create an empty Session interface.
|
||||||
s = &Session{
|
s = &Session{
|
||||||
State: NewState(),
|
State: NewState(),
|
||||||
Ratelimiter: NewRatelimiter(),
|
ratelimiter: NewRatelimiter(),
|
||||||
StateEnabled: true,
|
StateEnabled: true,
|
||||||
Compress: true,
|
Compress: true,
|
||||||
ShouldReconnectOnError: true,
|
ShouldReconnectOnError: true,
|
||||||
|
5
vendor/github.com/bwmarrin/discordgo/endpoints.go
generated
vendored
5
vendor/github.com/bwmarrin/discordgo/endpoints.go
generated
vendored
@ -71,6 +71,7 @@ var (
|
|||||||
EndpointUserNotes = func(uID string) string { return EndpointUsers + "@me/notes/" + uID }
|
EndpointUserNotes = func(uID string) string { return EndpointUsers + "@me/notes/" + uID }
|
||||||
|
|
||||||
EndpointGuild = func(gID string) string { return EndpointGuilds + gID }
|
EndpointGuild = func(gID string) string { return EndpointGuilds + gID }
|
||||||
|
EndpointGuildInivtes = func(gID string) string { return EndpointGuilds + gID + "/invites" }
|
||||||
EndpointGuildChannels = func(gID string) string { return EndpointGuilds + gID + "/channels" }
|
EndpointGuildChannels = func(gID string) string { return EndpointGuilds + gID + "/channels" }
|
||||||
EndpointGuildMembers = func(gID string) string { return EndpointGuilds + gID + "/members" }
|
EndpointGuildMembers = func(gID string) string { return EndpointGuilds + gID + "/members" }
|
||||||
EndpointGuildMember = func(gID, uID string) string { return EndpointGuilds + gID + "/members/" + uID }
|
EndpointGuildMember = func(gID, uID string) string { return EndpointGuilds + gID + "/members/" + uID }
|
||||||
@ -97,7 +98,7 @@ var (
|
|||||||
EndpointChannelMessages = func(cID string) string { return EndpointChannels + cID + "/messages" }
|
EndpointChannelMessages = func(cID string) string { return EndpointChannels + cID + "/messages" }
|
||||||
EndpointChannelMessage = func(cID, mID string) string { return EndpointChannels + cID + "/messages/" + mID }
|
EndpointChannelMessage = func(cID, mID string) string { return EndpointChannels + cID + "/messages/" + mID }
|
||||||
EndpointChannelMessageAck = func(cID, mID string) string { return EndpointChannels + cID + "/messages/" + mID + "/ack" }
|
EndpointChannelMessageAck = func(cID, mID string) string { return EndpointChannels + cID + "/messages/" + mID + "/ack" }
|
||||||
EndpointChannelMessagesBulkDelete = func(cID string) string { return EndpointChannel(cID) + "/messages/bulk-delete" }
|
EndpointChannelMessagesBulkDelete = func(cID string) string { return EndpointChannel(cID) + "/messages/bulk_delete" }
|
||||||
EndpointChannelMessagesPins = func(cID string) string { return EndpointChannel(cID) + "/pins" }
|
EndpointChannelMessagesPins = func(cID string) string { return EndpointChannel(cID) + "/pins" }
|
||||||
EndpointChannelMessagePin = func(cID, mID string) string { return EndpointChannel(cID) + "/pins/" + mID }
|
EndpointChannelMessagePin = func(cID, mID string) string { return EndpointChannel(cID) + "/pins/" + mID }
|
||||||
|
|
||||||
@ -121,8 +122,6 @@ var (
|
|||||||
EndpointRelationship = func(uID string) string { return EndpointRelationships() + "/" + uID }
|
EndpointRelationship = func(uID string) string { return EndpointRelationships() + "/" + uID }
|
||||||
EndpointRelationshipsMutual = func(uID string) string { return EndpointUsers + uID + "/relationships" }
|
EndpointRelationshipsMutual = func(uID string) string { return EndpointUsers + uID + "/relationships" }
|
||||||
|
|
||||||
EndpointGuildCreate = EndpointAPI + "guilds"
|
|
||||||
|
|
||||||
EndpointInvite = func(iID string) string { return EndpointAPI + "invite/" + iID }
|
EndpointInvite = func(iID string) string { return EndpointAPI + "invite/" + iID }
|
||||||
|
|
||||||
EndpointIntegrationsJoin = func(iID string) string { return EndpointAPI + "integrations/" + iID + "/join" }
|
EndpointIntegrationsJoin = func(iID string) string { return EndpointAPI + "integrations/" + iID + "/join" }
|
||||||
|
2
vendor/github.com/bwmarrin/discordgo/event.go
generated
vendored
2
vendor/github.com/bwmarrin/discordgo/event.go
generated
vendored
@ -6,7 +6,7 @@ type EventHandler interface {
|
|||||||
Type() string
|
Type() string
|
||||||
|
|
||||||
// Handle is called whenever an event of Type() happens.
|
// Handle is called whenever an event of Type() happens.
|
||||||
// It is the receivers responsibility to type assert that the interface
|
// It is the recievers responsibility to type assert that the interface
|
||||||
// is the expected struct.
|
// is the expected struct.
|
||||||
Handle(*Session, interface{})
|
Handle(*Session, interface{})
|
||||||
}
|
}
|
||||||
|
2
vendor/github.com/bwmarrin/discordgo/examples/appmaker/main.go
generated
vendored
2
vendor/github.com/bwmarrin/discordgo/examples/appmaker/main.go
generated
vendored
@ -79,7 +79,7 @@ func main() {
|
|||||||
ap.Name = Name
|
ap.Name = Name
|
||||||
ap, err = dg.ApplicationCreate(ap)
|
ap, err = dg.ApplicationCreate(ap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("error creating new application,", err)
|
fmt.Println("error creating new applicaiton,", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
28
vendor/github.com/bwmarrin/discordgo/logging.go
generated
vendored
28
vendor/github.com/bwmarrin/discordgo/logging.go
generated
vendored
@ -23,7 +23,7 @@ const (
|
|||||||
LogError int = iota
|
LogError int = iota
|
||||||
|
|
||||||
// LogWarning level is used for very abnormal events and errors that are
|
// LogWarning level is used for very abnormal events and errors that are
|
||||||
// also returned to a calling function.
|
// also returend to a calling function.
|
||||||
LogWarning
|
LogWarning
|
||||||
|
|
||||||
// LogInformational level is used for normal non-error activity
|
// LogInformational level is used for normal non-error activity
|
||||||
@ -34,34 +34,26 @@ const (
|
|||||||
LogDebug
|
LogDebug
|
||||||
)
|
)
|
||||||
|
|
||||||
// Logger can be used to replace the standard logging for discordgo
|
|
||||||
var Logger func(msgL, caller int, format string, a ...interface{})
|
|
||||||
|
|
||||||
// msglog provides package wide logging consistancy for discordgo
|
// msglog provides package wide logging consistancy for discordgo
|
||||||
// the format, a... portion this command follows that of fmt.Printf
|
// the format, a... portion this command follows that of fmt.Printf
|
||||||
// msgL : LogLevel of the message
|
// msgL : LogLevel of the message
|
||||||
// caller : 1 + the number of callers away from the message source
|
// caller : 1 + the number of callers away from the message source
|
||||||
// format : Printf style message format
|
// format : Printf style message format
|
||||||
// a ... : comma separated list of values to pass
|
// a ... : comma seperated list of values to pass
|
||||||
func msglog(msgL, caller int, format string, a ...interface{}) {
|
func msglog(msgL, caller int, format string, a ...interface{}) {
|
||||||
|
|
||||||
if Logger != nil {
|
pc, file, line, _ := runtime.Caller(caller)
|
||||||
Logger(msgL, caller, format, a...)
|
|
||||||
} else {
|
|
||||||
|
|
||||||
pc, file, line, _ := runtime.Caller(caller)
|
files := strings.Split(file, "/")
|
||||||
|
file = files[len(files)-1]
|
||||||
|
|
||||||
files := strings.Split(file, "/")
|
name := runtime.FuncForPC(pc).Name()
|
||||||
file = files[len(files)-1]
|
fns := strings.Split(name, ".")
|
||||||
|
name = fns[len(fns)-1]
|
||||||
|
|
||||||
name := runtime.FuncForPC(pc).Name()
|
msg := fmt.Sprintf(format, a...)
|
||||||
fns := strings.Split(name, ".")
|
|
||||||
name = fns[len(fns)-1]
|
|
||||||
|
|
||||||
msg := fmt.Sprintf(format, a...)
|
log.Printf("[DG%d] %s:%d:%s() %s\n", msgL, file, line, name, msg)
|
||||||
|
|
||||||
log.Printf("[DG%d] %s:%d:%s() %s\n", msgL, file, line, name, msg)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper function that wraps msglog for the Session struct
|
// helper function that wraps msglog for the Session struct
|
||||||
|
2
vendor/github.com/bwmarrin/discordgo/message.go
generated
vendored
2
vendor/github.com/bwmarrin/discordgo/message.go
generated
vendored
@ -237,7 +237,7 @@ func (m *Message) ContentWithMoreMentionsReplaced(s *Session) (content string, e
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
content = strings.Replace(content, "<@&"+role.ID+">", "@"+role.Name, -1)
|
content = strings.Replace(content, "<&"+role.ID+">", "@"+role.Name, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
content = patternChannels.ReplaceAllStringFunc(content, func(mention string) string {
|
content = patternChannels.ReplaceAllStringFunc(content, func(mention string) string {
|
||||||
|
49
vendor/github.com/bwmarrin/discordgo/ratelimit.go
generated
vendored
49
vendor/github.com/bwmarrin/discordgo/ratelimit.go
generated
vendored
@ -41,8 +41,8 @@ func NewRatelimiter() *RateLimiter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBucket retrieves or creates a bucket
|
// getBucket retrieves or creates a bucket
|
||||||
func (r *RateLimiter) GetBucket(key string) *Bucket {
|
func (r *RateLimiter) getBucket(key string) *Bucket {
|
||||||
r.Lock()
|
r.Lock()
|
||||||
defer r.Unlock()
|
defer r.Unlock()
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ func (r *RateLimiter) GetBucket(key string) *Bucket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
b := &Bucket{
|
b := &Bucket{
|
||||||
Remaining: 1,
|
remaining: 1,
|
||||||
Key: key,
|
Key: key,
|
||||||
global: r.global,
|
global: r.global,
|
||||||
}
|
}
|
||||||
@ -68,37 +68,27 @@ func (r *RateLimiter) GetBucket(key string) *Bucket {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetWaitTime returns the duration you should wait for a Bucket
|
// LockBucket Locks until a request can be made
|
||||||
func (r *RateLimiter) GetWaitTime(b *Bucket, minRemaining int) time.Duration {
|
func (r *RateLimiter) LockBucket(bucketID string) *Bucket {
|
||||||
|
|
||||||
|
b := r.getBucket(bucketID)
|
||||||
|
|
||||||
|
b.Lock()
|
||||||
|
|
||||||
// If we ran out of calls and the reset time is still ahead of us
|
// If we ran out of calls and the reset time is still ahead of us
|
||||||
// then we need to take it easy and relax a little
|
// then we need to take it easy and relax a little
|
||||||
if b.Remaining < minRemaining && b.reset.After(time.Now()) {
|
if b.remaining < 1 && b.reset.After(time.Now()) {
|
||||||
return b.reset.Sub(time.Now())
|
time.Sleep(b.reset.Sub(time.Now()))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for global ratelimits
|
// Check for global ratelimits
|
||||||
sleepTo := time.Unix(0, atomic.LoadInt64(r.global))
|
sleepTo := time.Unix(0, atomic.LoadInt64(r.global))
|
||||||
if now := time.Now(); now.Before(sleepTo) {
|
if now := time.Now(); now.Before(sleepTo) {
|
||||||
return sleepTo.Sub(now)
|
time.Sleep(sleepTo.Sub(now))
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
b.remaining--
|
||||||
}
|
|
||||||
|
|
||||||
// LockBucket Locks until a request can be made
|
|
||||||
func (r *RateLimiter) LockBucket(bucketID string) *Bucket {
|
|
||||||
return r.LockBucketObject(r.GetBucket(bucketID))
|
|
||||||
}
|
|
||||||
|
|
||||||
// LockBucketObject Locks an already resolved bucket until a request can be made
|
|
||||||
func (r *RateLimiter) LockBucketObject(b *Bucket) *Bucket {
|
|
||||||
b.Lock()
|
|
||||||
|
|
||||||
if wait := r.GetWaitTime(b, 1); wait > 0 {
|
|
||||||
time.Sleep(wait)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Remaining--
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,14 +96,13 @@ func (r *RateLimiter) LockBucketObject(b *Bucket) *Bucket {
|
|||||||
type Bucket struct {
|
type Bucket struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
Key string
|
Key string
|
||||||
Remaining int
|
remaining int
|
||||||
limit int
|
limit int
|
||||||
reset time.Time
|
reset time.Time
|
||||||
global *int64
|
global *int64
|
||||||
|
|
||||||
lastReset time.Time
|
lastReset time.Time
|
||||||
customRateLimit *customRateLimit
|
customRateLimit *customRateLimit
|
||||||
Userdata interface{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release unlocks the bucket and reads the headers to update the buckets ratelimit info
|
// Release unlocks the bucket and reads the headers to update the buckets ratelimit info
|
||||||
@ -124,10 +113,10 @@ func (b *Bucket) Release(headers http.Header) error {
|
|||||||
// Check if the bucket uses a custom ratelimiter
|
// Check if the bucket uses a custom ratelimiter
|
||||||
if rl := b.customRateLimit; rl != nil {
|
if rl := b.customRateLimit; rl != nil {
|
||||||
if time.Now().Sub(b.lastReset) >= rl.reset {
|
if time.Now().Sub(b.lastReset) >= rl.reset {
|
||||||
b.Remaining = rl.requests - 1
|
b.remaining = rl.requests - 1
|
||||||
b.lastReset = time.Now()
|
b.lastReset = time.Now()
|
||||||
}
|
}
|
||||||
if b.Remaining < 1 {
|
if b.remaining < 1 {
|
||||||
b.reset = time.Now().Add(rl.reset)
|
b.reset = time.Now().Add(rl.reset)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -187,7 +176,7 @@ func (b *Bucket) Release(headers http.Header) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
b.Remaining = int(parsedRemaining)
|
b.remaining = int(parsedRemaining)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
54
vendor/github.com/bwmarrin/discordgo/restapi.go
generated
vendored
54
vendor/github.com/bwmarrin/discordgo/restapi.go
generated
vendored
@ -65,11 +65,9 @@ func (s *Session) request(method, urlStr, contentType string, b []byte, bucketID
|
|||||||
if bucketID == "" {
|
if bucketID == "" {
|
||||||
bucketID = strings.SplitN(urlStr, "?", 2)[0]
|
bucketID = strings.SplitN(urlStr, "?", 2)[0]
|
||||||
}
|
}
|
||||||
return s.RequestWithLockedBucket(method, urlStr, contentType, b, s.Ratelimiter.LockBucket(bucketID), sequence)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestWithLockedBucket makes a request using a bucket that's already been locked
|
bucket := s.ratelimiter.LockBucket(bucketID)
|
||||||
func (s *Session) RequestWithLockedBucket(method, urlStr, contentType string, b []byte, bucket *Bucket, sequence int) (response []byte, err error) {
|
|
||||||
if s.Debug {
|
if s.Debug {
|
||||||
log.Printf("API REQUEST %8s :: %s\n", method, urlStr)
|
log.Printf("API REQUEST %8s :: %s\n", method, urlStr)
|
||||||
log.Printf("API REQUEST PAYLOAD :: [%s]\n", string(b))
|
log.Printf("API REQUEST PAYLOAD :: [%s]\n", string(b))
|
||||||
@ -141,7 +139,7 @@ func (s *Session) RequestWithLockedBucket(method, urlStr, contentType string, b
|
|||||||
if sequence < s.MaxRestRetries {
|
if sequence < s.MaxRestRetries {
|
||||||
|
|
||||||
s.log(LogInformational, "%s Failed (%s), Retrying...", urlStr, resp.Status)
|
s.log(LogInformational, "%s Failed (%s), Retrying...", urlStr, resp.Status)
|
||||||
response, err = s.RequestWithLockedBucket(method, urlStr, contentType, b, s.Ratelimiter.LockBucketObject(bucket), sequence+1)
|
response, err = s.request(method, urlStr, contentType, b, bucketID, sequence+1)
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("Exceeded Max retries HTTP %s, %s", resp.Status, response)
|
err = fmt.Errorf("Exceeded Max retries HTTP %s, %s", resp.Status, response)
|
||||||
}
|
}
|
||||||
@ -160,7 +158,7 @@ func (s *Session) RequestWithLockedBucket(method, urlStr, contentType string, b
|
|||||||
// we can make the above smarter
|
// we can make the above smarter
|
||||||
// this method can cause longer delays than required
|
// this method can cause longer delays than required
|
||||||
|
|
||||||
response, err = s.RequestWithLockedBucket(method, urlStr, contentType, b, s.Ratelimiter.LockBucketObject(bucket), sequence)
|
response, err = s.request(method, urlStr, contentType, b, bucketID, sequence)
|
||||||
|
|
||||||
default: // Error condition
|
default: // Error condition
|
||||||
err = newRestError(req, resp, response)
|
err = newRestError(req, resp, response)
|
||||||
@ -587,7 +585,7 @@ func (s *Session) GuildCreate(name string) (st *Guild, err error) {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}{name}
|
}{name}
|
||||||
|
|
||||||
body, err := s.RequestWithBucketID("POST", EndpointGuildCreate, data, EndpointGuildCreate)
|
body, err := s.RequestWithBucketID("POST", EndpointGuilds, data, EndpointGuilds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -909,7 +907,7 @@ func (s *Session) GuildChannelsReorder(guildID string, channels []*Channel) (err
|
|||||||
// GuildInvites returns an array of Invite structures for the given guild
|
// GuildInvites returns an array of Invite structures for the given guild
|
||||||
// guildID : The ID of a Guild.
|
// guildID : The ID of a Guild.
|
||||||
func (s *Session) GuildInvites(guildID string) (st []*Invite, err error) {
|
func (s *Session) GuildInvites(guildID string) (st []*Invite, err error) {
|
||||||
body, err := s.RequestWithBucketID("GET", EndpointGuildInvites(guildID), nil, EndpointGuildInvites(guildID))
|
body, err := s.RequestWithBucketID("GET", EndpointGuildInvites(guildID), nil, EndpointGuildInivtes(guildID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -959,7 +957,6 @@ func (s *Session) GuildRoleEdit(guildID, roleID, name string, color int, hoist b
|
|||||||
// Prevent sending a color int that is too big.
|
// Prevent sending a color int that is too big.
|
||||||
if color > 0xFFFFFF {
|
if color > 0xFFFFFF {
|
||||||
err = fmt.Errorf("color value cannot be larger than 0xFFFFFF")
|
err = fmt.Errorf("color value cannot be larger than 0xFFFFFF")
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data := struct {
|
data := struct {
|
||||||
@ -1023,9 +1020,6 @@ func (s *Session) GuildPruneCount(guildID string, days uint32) (count uint32, er
|
|||||||
|
|
||||||
uri := EndpointGuildPrune(guildID) + fmt.Sprintf("?days=%d", days)
|
uri := EndpointGuildPrune(guildID) + fmt.Sprintf("?days=%d", days)
|
||||||
body, err := s.RequestWithBucketID("GET", uri, nil, EndpointGuildPrune(guildID))
|
body, err := s.RequestWithBucketID("GET", uri, nil, EndpointGuildPrune(guildID))
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = unmarshal(body, &p)
|
err = unmarshal(body, &p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1210,7 +1204,7 @@ func (s *Session) GuildEmbedEdit(guildID string, enabled bool, channelID string)
|
|||||||
// Functions specific to Discord Channels
|
// Functions specific to Discord Channels
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
// Channel returns a Channel structure of a specific Channel.
|
// Channel returns a Channel strucutre of a specific Channel.
|
||||||
// channelID : The ID of the Channel you want returned.
|
// channelID : The ID of the Channel you want returned.
|
||||||
func (s *Session) Channel(channelID string) (st *Channel, err error) {
|
func (s *Session) Channel(channelID string) (st *Channel, err error) {
|
||||||
body, err := s.RequestWithBucketID("GET", EndpointChannel(channelID), nil, EndpointChannel(channelID))
|
body, err := s.RequestWithBucketID("GET", EndpointChannel(channelID), nil, EndpointChannel(channelID))
|
||||||
@ -1225,16 +1219,12 @@ func (s *Session) Channel(channelID string) (st *Channel, err error) {
|
|||||||
// ChannelEdit edits the given channel
|
// ChannelEdit edits the given channel
|
||||||
// channelID : The ID of a Channel
|
// channelID : The ID of a Channel
|
||||||
// name : The new name to assign the channel.
|
// name : The new name to assign the channel.
|
||||||
func (s *Session) ChannelEdit(channelID, name string) (*Channel, error) {
|
func (s *Session) ChannelEdit(channelID, name string) (st *Channel, err error) {
|
||||||
return s.ChannelEditComplex(channelID, &ChannelEdit{
|
|
||||||
Name: name,
|
data := struct {
|
||||||
})
|
Name string `json:"name"`
|
||||||
}
|
}{name}
|
||||||
|
|
||||||
// ChannelEditComplex edits an existing channel, replacing the parameters entirely with ChannelEdit struct
|
|
||||||
// channelID : The ID of a Channel
|
|
||||||
// data : The channel struct to send
|
|
||||||
func (s *Session) ChannelEditComplex(channelID string, data *ChannelEdit) (st *Channel, err error) {
|
|
||||||
body, err := s.RequestWithBucketID("PATCH", EndpointChannel(channelID), data, EndpointChannel(channelID))
|
body, err := s.RequestWithBucketID("PATCH", EndpointChannel(channelID), data, EndpointChannel(channelID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -1486,7 +1476,7 @@ func (s *Session) ChannelMessageDelete(channelID, messageID string) (err error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ChannelMessagesBulkDelete bulk deletes the messages from the channel for the provided messageIDs.
|
// ChannelMessagesBulkDelete bulk deletes the messages from the channel for the provided messageIDs.
|
||||||
// If only one messageID is in the slice call channelMessageDelete function.
|
// If only one messageID is in the slice call channelMessageDelete funciton.
|
||||||
// If the slice is empty do nothing.
|
// If the slice is empty do nothing.
|
||||||
// channelID : The ID of the channel for the messages to delete.
|
// channelID : The ID of the channel for the messages to delete.
|
||||||
// messages : The IDs of the messages to be deleted. A slice of string IDs. A maximum of 100 messages.
|
// messages : The IDs of the messages to be deleted. A slice of string IDs. A maximum of 100 messages.
|
||||||
@ -1579,14 +1569,16 @@ func (s *Session) ChannelInvites(channelID string) (st []*Invite, err error) {
|
|||||||
|
|
||||||
// ChannelInviteCreate creates a new invite for the given channel.
|
// ChannelInviteCreate creates a new invite for the given channel.
|
||||||
// channelID : The ID of a Channel
|
// channelID : The ID of a Channel
|
||||||
// i : An Invite struct with the values MaxAge, MaxUses and Temporary defined.
|
// i : An Invite struct with the values MaxAge, MaxUses, Temporary,
|
||||||
|
// and XkcdPass defined.
|
||||||
func (s *Session) ChannelInviteCreate(channelID string, i Invite) (st *Invite, err error) {
|
func (s *Session) ChannelInviteCreate(channelID string, i Invite) (st *Invite, err error) {
|
||||||
|
|
||||||
data := struct {
|
data := struct {
|
||||||
MaxAge int `json:"max_age"`
|
MaxAge int `json:"max_age"`
|
||||||
MaxUses int `json:"max_uses"`
|
MaxUses int `json:"max_uses"`
|
||||||
Temporary bool `json:"temporary"`
|
Temporary bool `json:"temporary"`
|
||||||
}{i.MaxAge, i.MaxUses, i.Temporary}
|
XKCDPass string `json:"xkcdpass"`
|
||||||
|
}{i.MaxAge, i.MaxUses, i.Temporary, i.XkcdPass}
|
||||||
|
|
||||||
body, err := s.RequestWithBucketID("POST", EndpointChannelInvites(channelID), data, EndpointChannelInvites(channelID))
|
body, err := s.RequestWithBucketID("POST", EndpointChannelInvites(channelID), data, EndpointChannelInvites(channelID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1626,7 +1618,7 @@ func (s *Session) ChannelPermissionDelete(channelID, targetID string) (err error
|
|||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
// Invite returns an Invite structure of the given invite
|
// Invite returns an Invite structure of the given invite
|
||||||
// inviteID : The invite code
|
// inviteID : The invite code (or maybe xkcdpass?)
|
||||||
func (s *Session) Invite(inviteID string) (st *Invite, err error) {
|
func (s *Session) Invite(inviteID string) (st *Invite, err error) {
|
||||||
|
|
||||||
body, err := s.RequestWithBucketID("GET", EndpointInvite(inviteID), nil, EndpointInvite(""))
|
body, err := s.RequestWithBucketID("GET", EndpointInvite(inviteID), nil, EndpointInvite(""))
|
||||||
@ -1639,7 +1631,7 @@ func (s *Session) Invite(inviteID string) (st *Invite, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InviteDelete deletes an existing invite
|
// InviteDelete deletes an existing invite
|
||||||
// inviteID : the code of an invite
|
// inviteID : the code (or maybe xkcdpass?) of an invite
|
||||||
func (s *Session) InviteDelete(inviteID string) (st *Invite, err error) {
|
func (s *Session) InviteDelete(inviteID string) (st *Invite, err error) {
|
||||||
|
|
||||||
body, err := s.RequestWithBucketID("DELETE", EndpointInvite(inviteID), nil, EndpointInvite(""))
|
body, err := s.RequestWithBucketID("DELETE", EndpointInvite(inviteID), nil, EndpointInvite(""))
|
||||||
@ -1652,7 +1644,7 @@ func (s *Session) InviteDelete(inviteID string) (st *Invite, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InviteAccept accepts an Invite to a Guild or Channel
|
// InviteAccept accepts an Invite to a Guild or Channel
|
||||||
// inviteID : The invite code
|
// inviteID : The invite code (or maybe xkcdpass?)
|
||||||
func (s *Session) InviteAccept(inviteID string) (st *Invite, err error) {
|
func (s *Session) InviteAccept(inviteID string) (st *Invite, err error) {
|
||||||
|
|
||||||
body, err := s.RequestWithBucketID("POST", EndpointInvite(inviteID), nil, EndpointInvite(""))
|
body, err := s.RequestWithBucketID("POST", EndpointInvite(inviteID), nil, EndpointInvite(""))
|
||||||
|
9
vendor/github.com/bwmarrin/discordgo/state.go
generated
vendored
9
vendor/github.com/bwmarrin/discordgo/state.go
generated
vendored
@ -531,7 +531,7 @@ func (s *State) PrivateChannel(channelID string) (*Channel, error) {
|
|||||||
return s.Channel(channelID)
|
return s.Channel(channelID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Channel gets a channel by ID, it will look in all guilds and private channels.
|
// Channel gets a channel by ID, it will look in all guilds an private channels.
|
||||||
func (s *State) Channel(channelID string) (*Channel, error) {
|
func (s *State) Channel(channelID string) (*Channel, error) {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return nil, ErrNilState
|
return nil, ErrNilState
|
||||||
@ -816,13 +816,6 @@ func (s *State) OnInterface(se *Session, i interface{}) (err error) {
|
|||||||
if s.TrackMembers {
|
if s.TrackMembers {
|
||||||
err = s.MemberRemove(t.Member)
|
err = s.MemberRemove(t.Member)
|
||||||
}
|
}
|
||||||
case *GuildMembersChunk:
|
|
||||||
if s.TrackMembers {
|
|
||||||
for i := range t.Members {
|
|
||||||
t.Members[i].GuildID = t.GuildID
|
|
||||||
err = s.MemberAdd(t.Members[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case *GuildRoleCreate:
|
case *GuildRoleCreate:
|
||||||
if s.TrackRoles {
|
if s.TrackRoles {
|
||||||
err = s.RoleAdd(t.GuildID, t.Role)
|
err = s.RoleAdd(t.GuildID, t.Role)
|
||||||
|
98
vendor/github.com/bwmarrin/discordgo/structs.go
generated
vendored
98
vendor/github.com/bwmarrin/discordgo/structs.go
generated
vendored
@ -14,6 +14,7 @@ package discordgo
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -84,9 +85,6 @@ type Session struct {
|
|||||||
// Stores the last HeartbeatAck that was recieved (in UTC)
|
// Stores the last HeartbeatAck that was recieved (in UTC)
|
||||||
LastHeartbeatAck time.Time
|
LastHeartbeatAck time.Time
|
||||||
|
|
||||||
// used to deal with rate limits
|
|
||||||
Ratelimiter *RateLimiter
|
|
||||||
|
|
||||||
// Event handlers
|
// Event handlers
|
||||||
handlersMu sync.RWMutex
|
handlersMu sync.RWMutex
|
||||||
handlers map[string][]*eventHandlerInstance
|
handlers map[string][]*eventHandlerInstance
|
||||||
@ -98,6 +96,9 @@ type Session struct {
|
|||||||
// When nil, the session is not listening.
|
// When nil, the session is not listening.
|
||||||
listening chan interface{}
|
listening chan interface{}
|
||||||
|
|
||||||
|
// used to deal with rate limits
|
||||||
|
ratelimiter *RateLimiter
|
||||||
|
|
||||||
// sequence tracks the current gateway api websocket sequence number
|
// sequence tracks the current gateway api websocket sequence number
|
||||||
sequence *int64
|
sequence *int64
|
||||||
|
|
||||||
@ -142,9 +143,9 @@ type Invite struct {
|
|||||||
MaxAge int `json:"max_age"`
|
MaxAge int `json:"max_age"`
|
||||||
Uses int `json:"uses"`
|
Uses int `json:"uses"`
|
||||||
MaxUses int `json:"max_uses"`
|
MaxUses int `json:"max_uses"`
|
||||||
|
XkcdPass string `json:"xkcdpass"`
|
||||||
Revoked bool `json:"revoked"`
|
Revoked bool `json:"revoked"`
|
||||||
Temporary bool `json:"temporary"`
|
Temporary bool `json:"temporary"`
|
||||||
Unique bool `json:"unique"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChannelType is the type of a Channel
|
// ChannelType is the type of a Channel
|
||||||
@ -170,22 +171,9 @@ type Channel struct {
|
|||||||
NSFW bool `json:"nsfw"`
|
NSFW bool `json:"nsfw"`
|
||||||
Position int `json:"position"`
|
Position int `json:"position"`
|
||||||
Bitrate int `json:"bitrate"`
|
Bitrate int `json:"bitrate"`
|
||||||
Recipients []*User `json:"recipients"`
|
Recipients []*User `json:"recipient"`
|
||||||
Messages []*Message `json:"-"`
|
Messages []*Message `json:"-"`
|
||||||
PermissionOverwrites []*PermissionOverwrite `json:"permission_overwrites"`
|
PermissionOverwrites []*PermissionOverwrite `json:"permission_overwrites"`
|
||||||
ParentID string `json:"parent_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// A ChannelEdit holds Channel Feild data for a channel edit.
|
|
||||||
type ChannelEdit struct {
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
Topic string `json:"topic,omitempty"`
|
|
||||||
NSFW bool `json:"nsfw,omitempty"`
|
|
||||||
Position int `json:"position"`
|
|
||||||
Bitrate int `json:"bitrate,omitempty"`
|
|
||||||
UserLimit int `json:"user_limit,omitempty"`
|
|
||||||
PermissionOverwrites []*PermissionOverwrite `json:"permission_overwrites,omitempty"`
|
|
||||||
ParentID string `json:"parent_id,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A PermissionOverwrite holds permission overwrite data for a Channel
|
// A PermissionOverwrite holds permission overwrite data for a Channel
|
||||||
@ -203,7 +191,6 @@ type Emoji struct {
|
|||||||
Roles []string `json:"roles"`
|
Roles []string `json:"roles"`
|
||||||
Managed bool `json:"managed"`
|
Managed bool `json:"managed"`
|
||||||
RequireColons bool `json:"require_colons"`
|
RequireColons bool `json:"require_colons"`
|
||||||
Animated bool `json:"animated"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// APIName returns an correctly formatted API name for use in the MessageReactions endpoints.
|
// APIName returns an correctly formatted API name for use in the MessageReactions endpoints.
|
||||||
@ -217,7 +204,7 @@ func (e *Emoji) APIName() string {
|
|||||||
return e.ID
|
return e.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerificationLevel type definition
|
// VerificationLevel type defination
|
||||||
type VerificationLevel int
|
type VerificationLevel int
|
||||||
|
|
||||||
// Constants for VerificationLevel levels from 0 to 3 inclusive
|
// Constants for VerificationLevel levels from 0 to 3 inclusive
|
||||||
@ -327,56 +314,43 @@ type Presence struct {
|
|||||||
Since *int `json:"since"`
|
Since *int `json:"since"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GameType is the type of "game" (see GameType* consts) in the Game struct
|
|
||||||
type GameType int
|
|
||||||
|
|
||||||
// Valid GameType values
|
|
||||||
const (
|
|
||||||
GameTypeGame GameType = iota
|
|
||||||
GameTypeStreaming
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Game struct holds the name of the "playing .." game for a user
|
// A Game struct holds the name of the "playing .." game for a user
|
||||||
type Game struct {
|
type Game struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Type GameType `json:"type"`
|
Type int `json:"type"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
Details string `json:"details,omitempty"`
|
|
||||||
State string `json:"state,omitempty"`
|
|
||||||
TimeStamps TimeStamps `json:"timestamps,omitempty"`
|
|
||||||
Assets Assets `json:"assets,omitempty"`
|
|
||||||
ApplicationID string `json:"application_id,omitempty"`
|
|
||||||
Instance int8 `json:"instance,omitempty"`
|
|
||||||
// TODO: Party and Secrets (unknown structure)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A TimeStamps struct contains start and end times used in the rich presence "playing .." Game
|
// UnmarshalJSON unmarshals json to Game struct
|
||||||
type TimeStamps struct {
|
func (g *Game) UnmarshalJSON(bytes []byte) error {
|
||||||
EndTimestamp int64 `json:"end,omitempty"`
|
temp := &struct {
|
||||||
StartTimestamp int64 `json:"start,omitempty"`
|
Name json.Number `json:"name"`
|
||||||
}
|
Type json.RawMessage `json:"type"`
|
||||||
|
URL string `json:"url"`
|
||||||
// UnmarshalJSON unmarshals JSON into TimeStamps struct
|
|
||||||
func (t *TimeStamps) UnmarshalJSON(b []byte) error {
|
|
||||||
temp := struct {
|
|
||||||
End float64 `json:"end,omitempty"`
|
|
||||||
Start float64 `json:"start,omitempty"`
|
|
||||||
}{}
|
}{}
|
||||||
err := json.Unmarshal(b, &temp)
|
err := json.Unmarshal(bytes, temp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
t.EndTimestamp = int64(temp.End)
|
g.URL = temp.URL
|
||||||
t.StartTimestamp = int64(temp.Start)
|
g.Name = temp.Name.String()
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// An Assets struct contains assets and labels used in the rich presence "playing .." Game
|
if temp.Type != nil {
|
||||||
type Assets struct {
|
err = json.Unmarshal(temp.Type, &g.Type)
|
||||||
LargeImageID string `json:"large_image,omitempty"`
|
if err == nil {
|
||||||
SmallImageID string `json:"small_image,omitempty"`
|
return nil
|
||||||
LargeText string `json:"large_text,omitempty"`
|
}
|
||||||
SmallText string `json:"small_text,omitempty"`
|
|
||||||
|
s := ""
|
||||||
|
err = json.Unmarshal(temp.Type, &s)
|
||||||
|
if err == nil {
|
||||||
|
g.Type, err = strconv.Atoi(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Member stores user information for Guild members.
|
// A Member stores user information for Guild members.
|
||||||
@ -409,7 +383,7 @@ type Settings struct {
|
|||||||
DeveloperMode bool `json:"developer_mode"`
|
DeveloperMode bool `json:"developer_mode"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status type definition
|
// Status type defination
|
||||||
type Status string
|
type Status string
|
||||||
|
|
||||||
// Constants for Status with the different current available status
|
// Constants for Status with the different current available status
|
||||||
|
9
vendor/github.com/bwmarrin/discordgo/user.go
generated
vendored
9
vendor/github.com/bwmarrin/discordgo/user.go
generated
vendored
@ -29,9 +29,7 @@ func (u *User) Mention() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AvatarURL returns a URL to the user's avatar.
|
// AvatarURL returns a URL to the user's avatar.
|
||||||
// size: The size of the user's avatar as a power of two
|
// size: The size of the user's avatar as a power of two
|
||||||
// if size is an empty string, no size parameter will
|
|
||||||
// be added to the URL.
|
|
||||||
func (u *User) AvatarURL(size string) string {
|
func (u *User) AvatarURL(size string) string {
|
||||||
var URL string
|
var URL string
|
||||||
if strings.HasPrefix(u.Avatar, "a_") {
|
if strings.HasPrefix(u.Avatar, "a_") {
|
||||||
@ -40,8 +38,5 @@ func (u *User) AvatarURL(size string) string {
|
|||||||
URL = EndpointUserAvatar(u.ID, u.Avatar)
|
URL = EndpointUserAvatar(u.ID, u.Avatar)
|
||||||
}
|
}
|
||||||
|
|
||||||
if size != "" {
|
return URL + "?size=" + size
|
||||||
return URL + "?size=" + size
|
|
||||||
}
|
|
||||||
return URL
|
|
||||||
}
|
}
|
||||||
|
11
vendor/github.com/bwmarrin/discordgo/voice.go
generated
vendored
11
vendor/github.com/bwmarrin/discordgo/voice.go
generated
vendored
@ -13,6 +13,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -68,7 +69,7 @@ type VoiceConnection struct {
|
|||||||
voiceSpeakingUpdateHandlers []VoiceSpeakingUpdateHandler
|
voiceSpeakingUpdateHandlers []VoiceSpeakingUpdateHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
// VoiceSpeakingUpdateHandler type provides a function definition for the
|
// VoiceSpeakingUpdateHandler type provides a function defination for the
|
||||||
// VoiceSpeakingUpdate event
|
// VoiceSpeakingUpdate event
|
||||||
type VoiceSpeakingUpdateHandler func(vc *VoiceConnection, vs *VoiceSpeakingUpdate)
|
type VoiceSpeakingUpdateHandler func(vc *VoiceConnection, vs *VoiceSpeakingUpdate)
|
||||||
|
|
||||||
@ -103,7 +104,7 @@ func (v *VoiceConnection) Speaking(b bool) (err error) {
|
|||||||
defer v.Unlock()
|
defer v.Unlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
v.speaking = false
|
v.speaking = false
|
||||||
v.log(LogError, "Speaking() write json error:", err)
|
log.Println("Speaking() write json error:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,7 +181,7 @@ func (v *VoiceConnection) Close() {
|
|||||||
v.log(LogInformational, "closing udp")
|
v.log(LogInformational, "closing udp")
|
||||||
err := v.udpConn.Close()
|
err := v.udpConn.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
v.log(LogError, "error closing udp connection: ", err)
|
log.Println("error closing udp connection: ", err)
|
||||||
}
|
}
|
||||||
v.udpConn = nil
|
v.udpConn = nil
|
||||||
}
|
}
|
||||||
@ -246,7 +247,7 @@ type voiceOP2 struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WaitUntilConnected waits for the Voice Connection to
|
// WaitUntilConnected waits for the Voice Connection to
|
||||||
// become ready, if it does not become ready it returns an err
|
// become ready, if it does not become ready it retuns an err
|
||||||
func (v *VoiceConnection) waitUntilConnected() error {
|
func (v *VoiceConnection) waitUntilConnected() error {
|
||||||
|
|
||||||
v.log(LogInformational, "called")
|
v.log(LogInformational, "called")
|
||||||
@ -857,7 +858,7 @@ func (v *VoiceConnection) reconnect() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if v.session.DataReady == false || v.session.wsConn == nil {
|
if v.session.DataReady == false || v.session.wsConn == nil {
|
||||||
v.log(LogInformational, "cannot reconnect to channel %s with unready session", v.ChannelID)
|
v.log(LogInformational, "cannot reconenct to channel %s with unready session", v.ChannelID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
253
vendor/github.com/bwmarrin/discordgo/wsapi.go
generated
vendored
253
vendor/github.com/bwmarrin/discordgo/wsapi.go
generated
vendored
@ -15,7 +15,6 @@ import (
|
|||||||
"compress/zlib"
|
"compress/zlib"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
@ -46,114 +45,19 @@ type resumePacket struct {
|
|||||||
} `json:"d"`
|
} `json:"d"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open creates a websocket connection to Discord.
|
// Open opens a websocket connection to Discord.
|
||||||
// See: https://discordapp.com/developers/docs/topics/gateway#connecting
|
func (s *Session) Open() (err error) {
|
||||||
func (s *Session) Open() error {
|
|
||||||
s.log(LogInformational, "called")
|
s.log(LogInformational, "called")
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// Prevent Open or other major Session functions from
|
|
||||||
// being called while Open is still running.
|
|
||||||
s.Lock()
|
s.Lock()
|
||||||
defer s.Unlock()
|
|
||||||
|
|
||||||
// If the websock is already open, bail out here.
|
|
||||||
if s.wsConn != nil {
|
|
||||||
return ErrWSAlreadyOpen
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the gateway to use for the Websocket connection
|
|
||||||
if s.gateway == "" {
|
|
||||||
s.gateway, err = s.Gateway()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the version and encoding to the URL
|
|
||||||
s.gateway = s.gateway + "?v=" + APIVersion + "&encoding=json"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect to the Gateway
|
|
||||||
s.log(LogInformational, "connecting to gateway %s", s.gateway)
|
|
||||||
header := http.Header{}
|
|
||||||
header.Add("accept-encoding", "zlib")
|
|
||||||
s.wsConn, _, err = websocket.DefaultDialer.Dial(s.gateway, header)
|
|
||||||
if err != nil {
|
|
||||||
s.log(LogWarning, "error connecting to gateway %s, %s", s.gateway, err)
|
|
||||||
s.gateway = "" // clear cached gateway
|
|
||||||
s.wsConn = nil // Just to be safe.
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
// because of this, all code below must set err to the error
|
|
||||||
// when exiting with an error :) Maybe someone has a better
|
|
||||||
// way :)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.wsConn.Close()
|
s.Unlock()
|
||||||
s.wsConn = nil
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// The first response from Discord should be an Op 10 (Hello) Packet.
|
|
||||||
// When processed by onEvent the heartbeat goroutine will be started.
|
|
||||||
mt, m, err := s.wsConn.ReadMessage()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
e, err := s.onEvent(mt, m)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if e.Operation != 10 {
|
|
||||||
err = fmt.Errorf("expecting Op 10, got Op %d instead", e.Operation)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.log(LogInformational, "Op 10 Hello Packet received from Discord")
|
|
||||||
s.LastHeartbeatAck = time.Now().UTC()
|
|
||||||
var h helloOp
|
|
||||||
if err = json.Unmarshal(e.RawData, &h); err != nil {
|
|
||||||
err = fmt.Errorf("error unmarshalling helloOp, %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we send either an Op 2 Identity if this is a brand new
|
|
||||||
// connection or Op 6 Resume if we are resuming an existing connection.
|
|
||||||
sequence := atomic.LoadInt64(s.sequence)
|
|
||||||
if s.sessionID == "" && sequence == 0 {
|
|
||||||
|
|
||||||
// Send Op 2 Identity Packet
|
|
||||||
err = s.identify()
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("error sending identify packet to gateway, %s, %s", s.gateway, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// Send Op 6 Resume Packet
|
|
||||||
p := resumePacket{}
|
|
||||||
p.Op = 6
|
|
||||||
p.Data.Token = s.Token
|
|
||||||
p.Data.SessionID = s.sessionID
|
|
||||||
p.Data.Sequence = sequence
|
|
||||||
|
|
||||||
s.log(LogInformational, "sending resume packet to gateway")
|
|
||||||
s.wsMutex.Lock()
|
|
||||||
err = s.wsConn.WriteJSON(p)
|
|
||||||
s.wsMutex.Unlock()
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("error sending gateway resume packet, %s, %s", s.gateway, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// A basic state is a hard requirement for Voice.
|
// A basic state is a hard requirement for Voice.
|
||||||
// We create it here so the below READY/RESUMED packet can populate
|
|
||||||
// the state :)
|
|
||||||
// XXX: Move to New() func?
|
|
||||||
if s.State == nil {
|
if s.State == nil {
|
||||||
state := NewState()
|
state := NewState()
|
||||||
state.TrackChannels = false
|
state.TrackChannels = false
|
||||||
@ -164,42 +68,77 @@ func (s *Session) Open() error {
|
|||||||
s.State = state
|
s.State = state
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now Discord should send us a READY or RESUMED packet.
|
if s.wsConn != nil {
|
||||||
mt, m, err = s.wsConn.ReadMessage()
|
err = ErrWSAlreadyOpen
|
||||||
if err != nil {
|
return
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
e, err = s.onEvent(mt, m)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if e.Type != `READY` && e.Type != `RESUMED` {
|
|
||||||
// This is not fatal, but it does not follow their API documentation.
|
|
||||||
s.log(LogWarning, "Expected READY/RESUMED, instead got:\n%#v\n", e)
|
|
||||||
}
|
|
||||||
s.log(LogInformational, "First Packet:\n%#v\n", e)
|
|
||||||
|
|
||||||
s.log(LogInformational, "We are now connected to Discord, emitting connect event")
|
|
||||||
s.handleEvent(connectEventType, &Connect{})
|
|
||||||
|
|
||||||
// A VoiceConnections map is a hard requirement for Voice.
|
|
||||||
// XXX: can this be moved to when opening a voice connection?
|
|
||||||
if s.VoiceConnections == nil {
|
if s.VoiceConnections == nil {
|
||||||
s.log(LogInformational, "creating new VoiceConnections map")
|
s.log(LogInformational, "creating new VoiceConnections map")
|
||||||
s.VoiceConnections = make(map[string]*VoiceConnection)
|
s.VoiceConnections = make(map[string]*VoiceConnection)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create listening chan outside of listen, as it needs to happen inside the
|
// Get the gateway to use for the Websocket connection
|
||||||
// mutex lock and needs to exist before calling heartbeat and listen
|
if s.gateway == "" {
|
||||||
// go rountines.
|
s.gateway, err = s.Gateway()
|
||||||
s.listening = make(chan interface{})
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Start sending heartbeats and reading messages from Discord.
|
// Add the version and encoding to the URL
|
||||||
go s.heartbeat(s.wsConn, s.listening, h.HeartbeatInterval)
|
s.gateway = s.gateway + "?v=" + APIVersion + "&encoding=json"
|
||||||
|
}
|
||||||
|
|
||||||
|
header := http.Header{}
|
||||||
|
header.Add("accept-encoding", "zlib")
|
||||||
|
|
||||||
|
s.log(LogInformational, "connecting to gateway %s", s.gateway)
|
||||||
|
s.wsConn, _, err = websocket.DefaultDialer.Dial(s.gateway, header)
|
||||||
|
if err != nil {
|
||||||
|
s.log(LogWarning, "error connecting to gateway %s, %s", s.gateway, err)
|
||||||
|
s.gateway = "" // clear cached gateway
|
||||||
|
// TODO: should we add a retry block here?
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sequence := atomic.LoadInt64(s.sequence)
|
||||||
|
if s.sessionID != "" && sequence > 0 {
|
||||||
|
|
||||||
|
p := resumePacket{}
|
||||||
|
p.Op = 6
|
||||||
|
p.Data.Token = s.Token
|
||||||
|
p.Data.SessionID = s.sessionID
|
||||||
|
p.Data.Sequence = sequence
|
||||||
|
|
||||||
|
s.log(LogInformational, "sending resume packet to gateway")
|
||||||
|
err = s.wsConn.WriteJSON(p)
|
||||||
|
if err != nil {
|
||||||
|
s.log(LogWarning, "error sending gateway resume packet, %s, %s", s.gateway, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
err = s.identify()
|
||||||
|
if err != nil {
|
||||||
|
s.log(LogWarning, "error sending gateway identify packet, %s, %s", s.gateway, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create listening outside of listen, as it needs to happen inside the mutex
|
||||||
|
// lock.
|
||||||
|
s.listening = make(chan interface{})
|
||||||
go s.listen(s.wsConn, s.listening)
|
go s.listen(s.wsConn, s.listening)
|
||||||
|
s.LastHeartbeatAck = time.Now().UTC()
|
||||||
|
|
||||||
|
s.Unlock()
|
||||||
|
|
||||||
|
s.log(LogInformational, "emit connect event")
|
||||||
|
s.handleEvent(connectEventType, &Connect{})
|
||||||
|
|
||||||
s.log(LogInformational, "exiting")
|
s.log(LogInformational, "exiting")
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// listen polls the websocket connection for events, it will stop when the
|
// listen polls the websocket connection for events, it will stop when the
|
||||||
@ -310,8 +249,7 @@ func (s *Session) heartbeat(wsConn *websocket.Conn, listening <-chan interface{}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateStatusData ia provided to UpdateStatusComplex()
|
type updateStatusData struct {
|
||||||
type UpdateStatusData struct {
|
|
||||||
IdleSince *int `json:"since"`
|
IdleSince *int `json:"since"`
|
||||||
Game *Game `json:"game"`
|
Game *Game `json:"game"`
|
||||||
AFK bool `json:"afk"`
|
AFK bool `json:"afk"`
|
||||||
@ -320,7 +258,7 @@ type UpdateStatusData struct {
|
|||||||
|
|
||||||
type updateStatusOp struct {
|
type updateStatusOp struct {
|
||||||
Op int `json:"op"`
|
Op int `json:"op"`
|
||||||
Data UpdateStatusData `json:"d"`
|
Data updateStatusData `json:"d"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateStreamingStatus is used to update the user's streaming status.
|
// UpdateStreamingStatus is used to update the user's streaming status.
|
||||||
@ -332,7 +270,13 @@ func (s *Session) UpdateStreamingStatus(idle int, game string, url string) (err
|
|||||||
|
|
||||||
s.log(LogInformational, "called")
|
s.log(LogInformational, "called")
|
||||||
|
|
||||||
usd := UpdateStatusData{
|
s.RLock()
|
||||||
|
defer s.RUnlock()
|
||||||
|
if s.wsConn == nil {
|
||||||
|
return ErrWSNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
usd := updateStatusData{
|
||||||
Status: "online",
|
Status: "online",
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,9 +285,9 @@ func (s *Session) UpdateStreamingStatus(idle int, game string, url string) (err
|
|||||||
}
|
}
|
||||||
|
|
||||||
if game != "" {
|
if game != "" {
|
||||||
gameType := GameTypeGame
|
gameType := 0
|
||||||
if url != "" {
|
if url != "" {
|
||||||
gameType = GameTypeStreaming
|
gameType = 1
|
||||||
}
|
}
|
||||||
usd.Game = &Game{
|
usd.Game = &Game{
|
||||||
Name: game,
|
Name: game,
|
||||||
@ -352,18 +296,6 @@ func (s *Session) UpdateStreamingStatus(idle int, game string, url string) (err
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.UpdateStatusComplex(usd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateStatusComplex allows for sending the raw status update data untouched by discordgo.
|
|
||||||
func (s *Session) UpdateStatusComplex(usd UpdateStatusData) (err error) {
|
|
||||||
|
|
||||||
s.RLock()
|
|
||||||
defer s.RUnlock()
|
|
||||||
if s.wsConn == nil {
|
|
||||||
return ErrWSNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
s.wsMutex.Lock()
|
s.wsMutex.Lock()
|
||||||
err = s.wsConn.WriteJSON(updateStatusOp{3, usd})
|
err = s.wsConn.WriteJSON(updateStatusOp{3, usd})
|
||||||
s.wsMutex.Unlock()
|
s.wsMutex.Unlock()
|
||||||
@ -425,7 +357,9 @@ func (s *Session) RequestGuildMembers(guildID, query string, limit int) (err err
|
|||||||
//
|
//
|
||||||
// If you use the AddHandler() function to register a handler for the
|
// If you use the AddHandler() function to register a handler for the
|
||||||
// "OnEvent" event then all events will be passed to that handler.
|
// "OnEvent" event then all events will be passed to that handler.
|
||||||
func (s *Session) onEvent(messageType int, message []byte) (*Event, error) {
|
//
|
||||||
|
// TODO: You may also register a custom event handler entirely using...
|
||||||
|
func (s *Session) onEvent(messageType int, message []byte) {
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
var reader io.Reader
|
var reader io.Reader
|
||||||
@ -437,7 +371,7 @@ func (s *Session) onEvent(messageType int, message []byte) (*Event, error) {
|
|||||||
z, err2 := zlib.NewReader(reader)
|
z, err2 := zlib.NewReader(reader)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
s.log(LogError, "error uncompressing websocket message, %s", err)
|
s.log(LogError, "error uncompressing websocket message, %s", err)
|
||||||
return nil, err2
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -455,7 +389,7 @@ func (s *Session) onEvent(messageType int, message []byte) (*Event, error) {
|
|||||||
decoder := json.NewDecoder(reader)
|
decoder := json.NewDecoder(reader)
|
||||||
if err = decoder.Decode(&e); err != nil {
|
if err = decoder.Decode(&e); err != nil {
|
||||||
s.log(LogError, "error decoding websocket message, %s", err)
|
s.log(LogError, "error decoding websocket message, %s", err)
|
||||||
return e, err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.log(LogDebug, "Op: %d, Seq: %d, Type: %s, Data: %s\n\n", e.Operation, e.Sequence, e.Type, string(e.RawData))
|
s.log(LogDebug, "Op: %d, Seq: %d, Type: %s, Data: %s\n\n", e.Operation, e.Sequence, e.Type, string(e.RawData))
|
||||||
@ -469,10 +403,10 @@ func (s *Session) onEvent(messageType int, message []byte) (*Event, error) {
|
|||||||
s.wsMutex.Unlock()
|
s.wsMutex.Unlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log(LogError, "error sending heartbeat in response to Op1")
|
s.log(LogError, "error sending heartbeat in response to Op1")
|
||||||
return e, err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return e, nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reconnect
|
// Reconnect
|
||||||
@ -481,7 +415,7 @@ func (s *Session) onEvent(messageType int, message []byte) (*Event, error) {
|
|||||||
s.log(LogInformational, "Closing and reconnecting in response to Op7")
|
s.log(LogInformational, "Closing and reconnecting in response to Op7")
|
||||||
s.Close()
|
s.Close()
|
||||||
s.reconnect()
|
s.reconnect()
|
||||||
return e, nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalid Session
|
// Invalid Session
|
||||||
@ -493,15 +427,20 @@ func (s *Session) onEvent(messageType int, message []byte) (*Event, error) {
|
|||||||
err = s.identify()
|
err = s.identify()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log(LogWarning, "error sending gateway identify packet, %s, %s", s.gateway, err)
|
s.log(LogWarning, "error sending gateway identify packet, %s, %s", s.gateway, err)
|
||||||
return e, err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return e, nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Operation == 10 {
|
if e.Operation == 10 {
|
||||||
// Op10 is handled by Open()
|
var h helloOp
|
||||||
return e, nil
|
if err = json.Unmarshal(e.RawData, &h); err != nil {
|
||||||
|
s.log(LogError, "error unmarshalling helloOp, %s", err)
|
||||||
|
} else {
|
||||||
|
go s.heartbeat(s.wsConn, s.listening, h.HeartbeatInterval)
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Operation == 11 {
|
if e.Operation == 11 {
|
||||||
@ -509,7 +448,7 @@ func (s *Session) onEvent(messageType int, message []byte) (*Event, error) {
|
|||||||
s.LastHeartbeatAck = time.Now().UTC()
|
s.LastHeartbeatAck = time.Now().UTC()
|
||||||
s.Unlock()
|
s.Unlock()
|
||||||
s.log(LogInformational, "got heartbeat ACK")
|
s.log(LogInformational, "got heartbeat ACK")
|
||||||
return e, nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not try to Dispatch a non-Dispatch Message
|
// Do not try to Dispatch a non-Dispatch Message
|
||||||
@ -517,7 +456,7 @@ func (s *Session) onEvent(messageType int, message []byte) (*Event, error) {
|
|||||||
// But we probably should be doing something with them.
|
// But we probably should be doing something with them.
|
||||||
// TEMP
|
// TEMP
|
||||||
s.log(LogWarning, "unknown Op: %d, Seq: %d, Type: %s, Data: %s, message: %s", e.Operation, e.Sequence, e.Type, string(e.RawData), string(message))
|
s.log(LogWarning, "unknown Op: %d, Seq: %d, Type: %s, Data: %s, message: %s", e.Operation, e.Sequence, e.Type, string(e.RawData), string(message))
|
||||||
return e, nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the message sequence
|
// Store the message sequence
|
||||||
@ -546,8 +485,6 @@ func (s *Session) onEvent(messageType int, message []byte) (*Event, error) {
|
|||||||
|
|
||||||
// For legacy reasons, we send the raw event also, this could be useful for handling unknown events.
|
// For legacy reasons, we send the raw event also, this could be useful for handling unknown events.
|
||||||
s.handleEvent(eventEventType, e)
|
s.handleEvent(eventEventType, e)
|
||||||
|
|
||||||
return e, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
@ -673,7 +610,7 @@ func (s *Session) onVoiceServerUpdate(st *VoiceServerUpdate) {
|
|||||||
voice.GuildID = st.GuildID
|
voice.GuildID = st.GuildID
|
||||||
voice.Unlock()
|
voice.Unlock()
|
||||||
|
|
||||||
// Open a connection to the voice server
|
// Open a conenction to the voice server
|
||||||
err := voice.open()
|
err := voice.open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log(LogError, "onVoiceServerUpdate voice.open, %s", err)
|
s.log(LogError, "onVoiceServerUpdate voice.open, %s", err)
|
||||||
|
202
vendor/github.com/coreos/etcd/client/LICENSE
generated
vendored
202
vendor/github.com/coreos/etcd/client/LICENSE
generated
vendored
@ -1,202 +0,0 @@
|
|||||||
|
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
236
vendor/github.com/coreos/etcd/client/auth_role.go
generated
vendored
236
vendor/github.com/coreos/etcd/client/auth_role.go
generated
vendored
@ -1,236 +0,0 @@
|
|||||||
// Copyright 2015 The etcd Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Role struct {
|
|
||||||
Role string `json:"role"`
|
|
||||||
Permissions Permissions `json:"permissions"`
|
|
||||||
Grant *Permissions `json:"grant,omitempty"`
|
|
||||||
Revoke *Permissions `json:"revoke,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Permissions struct {
|
|
||||||
KV rwPermission `json:"kv"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type rwPermission struct {
|
|
||||||
Read []string `json:"read"`
|
|
||||||
Write []string `json:"write"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PermissionType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
ReadPermission PermissionType = iota
|
|
||||||
WritePermission
|
|
||||||
ReadWritePermission
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewAuthRoleAPI constructs a new AuthRoleAPI that uses HTTP to
|
|
||||||
// interact with etcd's role creation and modification features.
|
|
||||||
func NewAuthRoleAPI(c Client) AuthRoleAPI {
|
|
||||||
return &httpAuthRoleAPI{
|
|
||||||
client: c,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type AuthRoleAPI interface {
|
|
||||||
// AddRole adds a role.
|
|
||||||
AddRole(ctx context.Context, role string) error
|
|
||||||
|
|
||||||
// RemoveRole removes a role.
|
|
||||||
RemoveRole(ctx context.Context, role string) error
|
|
||||||
|
|
||||||
// GetRole retrieves role details.
|
|
||||||
GetRole(ctx context.Context, role string) (*Role, error)
|
|
||||||
|
|
||||||
// GrantRoleKV grants a role some permission prefixes for the KV store.
|
|
||||||
GrantRoleKV(ctx context.Context, role string, prefixes []string, permType PermissionType) (*Role, error)
|
|
||||||
|
|
||||||
// RevokeRoleKV revokes some permission prefixes for a role on the KV store.
|
|
||||||
RevokeRoleKV(ctx context.Context, role string, prefixes []string, permType PermissionType) (*Role, error)
|
|
||||||
|
|
||||||
// ListRoles lists roles.
|
|
||||||
ListRoles(ctx context.Context) ([]string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type httpAuthRoleAPI struct {
|
|
||||||
client httpClient
|
|
||||||
}
|
|
||||||
|
|
||||||
type authRoleAPIAction struct {
|
|
||||||
verb string
|
|
||||||
name string
|
|
||||||
role *Role
|
|
||||||
}
|
|
||||||
|
|
||||||
type authRoleAPIList struct{}
|
|
||||||
|
|
||||||
func (list *authRoleAPIList) HTTPRequest(ep url.URL) *http.Request {
|
|
||||||
u := v2AuthURL(ep, "roles", "")
|
|
||||||
req, _ := http.NewRequest("GET", u.String(), nil)
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *authRoleAPIAction) HTTPRequest(ep url.URL) *http.Request {
|
|
||||||
u := v2AuthURL(ep, "roles", l.name)
|
|
||||||
if l.role == nil {
|
|
||||||
req, _ := http.NewRequest(l.verb, u.String(), nil)
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
b, err := json.Marshal(l.role)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
body := bytes.NewReader(b)
|
|
||||||
req, _ := http.NewRequest(l.verb, u.String(), body)
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *httpAuthRoleAPI) ListRoles(ctx context.Context) ([]string, error) {
|
|
||||||
resp, body, err := r.client.Do(ctx, &authRoleAPIList{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var roleList struct {
|
|
||||||
Roles []Role `json:"roles"`
|
|
||||||
}
|
|
||||||
if err = json.Unmarshal(body, &roleList); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ret := make([]string, 0, len(roleList.Roles))
|
|
||||||
for _, r := range roleList.Roles {
|
|
||||||
ret = append(ret, r.Role)
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *httpAuthRoleAPI) AddRole(ctx context.Context, rolename string) error {
|
|
||||||
role := &Role{
|
|
||||||
Role: rolename,
|
|
||||||
}
|
|
||||||
return r.addRemoveRole(ctx, &authRoleAPIAction{
|
|
||||||
verb: "PUT",
|
|
||||||
name: rolename,
|
|
||||||
role: role,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *httpAuthRoleAPI) RemoveRole(ctx context.Context, rolename string) error {
|
|
||||||
return r.addRemoveRole(ctx, &authRoleAPIAction{
|
|
||||||
verb: "DELETE",
|
|
||||||
name: rolename,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *httpAuthRoleAPI) addRemoveRole(ctx context.Context, req *authRoleAPIAction) error {
|
|
||||||
resp, body, err := r.client.Do(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := assertStatusCode(resp.StatusCode, http.StatusOK, http.StatusCreated); err != nil {
|
|
||||||
var sec authError
|
|
||||||
err := json.Unmarshal(body, &sec)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return sec
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *httpAuthRoleAPI) GetRole(ctx context.Context, rolename string) (*Role, error) {
|
|
||||||
return r.modRole(ctx, &authRoleAPIAction{
|
|
||||||
verb: "GET",
|
|
||||||
name: rolename,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildRWPermission(prefixes []string, permType PermissionType) rwPermission {
|
|
||||||
var out rwPermission
|
|
||||||
switch permType {
|
|
||||||
case ReadPermission:
|
|
||||||
out.Read = prefixes
|
|
||||||
case WritePermission:
|
|
||||||
out.Write = prefixes
|
|
||||||
case ReadWritePermission:
|
|
||||||
out.Read = prefixes
|
|
||||||
out.Write = prefixes
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *httpAuthRoleAPI) GrantRoleKV(ctx context.Context, rolename string, prefixes []string, permType PermissionType) (*Role, error) {
|
|
||||||
rwp := buildRWPermission(prefixes, permType)
|
|
||||||
role := &Role{
|
|
||||||
Role: rolename,
|
|
||||||
Grant: &Permissions{
|
|
||||||
KV: rwp,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return r.modRole(ctx, &authRoleAPIAction{
|
|
||||||
verb: "PUT",
|
|
||||||
name: rolename,
|
|
||||||
role: role,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *httpAuthRoleAPI) RevokeRoleKV(ctx context.Context, rolename string, prefixes []string, permType PermissionType) (*Role, error) {
|
|
||||||
rwp := buildRWPermission(prefixes, permType)
|
|
||||||
role := &Role{
|
|
||||||
Role: rolename,
|
|
||||||
Revoke: &Permissions{
|
|
||||||
KV: rwp,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return r.modRole(ctx, &authRoleAPIAction{
|
|
||||||
verb: "PUT",
|
|
||||||
name: rolename,
|
|
||||||
role: role,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *httpAuthRoleAPI) modRole(ctx context.Context, req *authRoleAPIAction) (*Role, error) {
|
|
||||||
resp, body, err := r.client.Do(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
|
||||||
var sec authError
|
|
||||||
err = json.Unmarshal(body, &sec)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return nil, sec
|
|
||||||
}
|
|
||||||
var role Role
|
|
||||||
if err = json.Unmarshal(body, &role); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &role, nil
|
|
||||||
}
|
|
319
vendor/github.com/coreos/etcd/client/auth_user.go
generated
vendored
319
vendor/github.com/coreos/etcd/client/auth_user.go
generated
vendored
@ -1,319 +0,0 @@
|
|||||||
// Copyright 2015 The etcd Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"path"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
defaultV2AuthPrefix = "/v2/auth"
|
|
||||||
)
|
|
||||||
|
|
||||||
type User struct {
|
|
||||||
User string `json:"user"`
|
|
||||||
Password string `json:"password,omitempty"`
|
|
||||||
Roles []string `json:"roles"`
|
|
||||||
Grant []string `json:"grant,omitempty"`
|
|
||||||
Revoke []string `json:"revoke,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// userListEntry is the user representation given by the server for ListUsers
|
|
||||||
type userListEntry struct {
|
|
||||||
User string `json:"user"`
|
|
||||||
Roles []Role `json:"roles"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserRoles struct {
|
|
||||||
User string `json:"user"`
|
|
||||||
Roles []Role `json:"roles"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func v2AuthURL(ep url.URL, action string, name string) *url.URL {
|
|
||||||
if name != "" {
|
|
||||||
ep.Path = path.Join(ep.Path, defaultV2AuthPrefix, action, name)
|
|
||||||
return &ep
|
|
||||||
}
|
|
||||||
ep.Path = path.Join(ep.Path, defaultV2AuthPrefix, action)
|
|
||||||
return &ep
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAuthAPI constructs a new AuthAPI that uses HTTP to
|
|
||||||
// interact with etcd's general auth features.
|
|
||||||
func NewAuthAPI(c Client) AuthAPI {
|
|
||||||
return &httpAuthAPI{
|
|
||||||
client: c,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type AuthAPI interface {
|
|
||||||
// Enable auth.
|
|
||||||
Enable(ctx context.Context) error
|
|
||||||
|
|
||||||
// Disable auth.
|
|
||||||
Disable(ctx context.Context) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type httpAuthAPI struct {
|
|
||||||
client httpClient
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *httpAuthAPI) Enable(ctx context.Context) error {
|
|
||||||
return s.enableDisable(ctx, &authAPIAction{"PUT"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *httpAuthAPI) Disable(ctx context.Context) error {
|
|
||||||
return s.enableDisable(ctx, &authAPIAction{"DELETE"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *httpAuthAPI) enableDisable(ctx context.Context, req httpAction) error {
|
|
||||||
resp, body, err := s.client.Do(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK, http.StatusCreated); err != nil {
|
|
||||||
var sec authError
|
|
||||||
err = json.Unmarshal(body, &sec)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return sec
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type authAPIAction struct {
|
|
||||||
verb string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *authAPIAction) HTTPRequest(ep url.URL) *http.Request {
|
|
||||||
u := v2AuthURL(ep, "enable", "")
|
|
||||||
req, _ := http.NewRequest(l.verb, u.String(), nil)
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
|
|
||||||
type authError struct {
|
|
||||||
Message string `json:"message"`
|
|
||||||
Code int `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e authError) Error() string {
|
|
||||||
return e.Message
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAuthUserAPI constructs a new AuthUserAPI that uses HTTP to
|
|
||||||
// interact with etcd's user creation and modification features.
|
|
||||||
func NewAuthUserAPI(c Client) AuthUserAPI {
|
|
||||||
return &httpAuthUserAPI{
|
|
||||||
client: c,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type AuthUserAPI interface {
|
|
||||||
// AddUser adds a user.
|
|
||||||
AddUser(ctx context.Context, username string, password string) error
|
|
||||||
|
|
||||||
// RemoveUser removes a user.
|
|
||||||
RemoveUser(ctx context.Context, username string) error
|
|
||||||
|
|
||||||
// GetUser retrieves user details.
|
|
||||||
GetUser(ctx context.Context, username string) (*User, error)
|
|
||||||
|
|
||||||
// GrantUser grants a user some permission roles.
|
|
||||||
GrantUser(ctx context.Context, username string, roles []string) (*User, error)
|
|
||||||
|
|
||||||
// RevokeUser revokes some permission roles from a user.
|
|
||||||
RevokeUser(ctx context.Context, username string, roles []string) (*User, error)
|
|
||||||
|
|
||||||
// ChangePassword changes the user's password.
|
|
||||||
ChangePassword(ctx context.Context, username string, password string) (*User, error)
|
|
||||||
|
|
||||||
// ListUsers lists the users.
|
|
||||||
ListUsers(ctx context.Context) ([]string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type httpAuthUserAPI struct {
|
|
||||||
client httpClient
|
|
||||||
}
|
|
||||||
|
|
||||||
type authUserAPIAction struct {
|
|
||||||
verb string
|
|
||||||
username string
|
|
||||||
user *User
|
|
||||||
}
|
|
||||||
|
|
||||||
type authUserAPIList struct{}
|
|
||||||
|
|
||||||
func (list *authUserAPIList) HTTPRequest(ep url.URL) *http.Request {
|
|
||||||
u := v2AuthURL(ep, "users", "")
|
|
||||||
req, _ := http.NewRequest("GET", u.String(), nil)
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *authUserAPIAction) HTTPRequest(ep url.URL) *http.Request {
|
|
||||||
u := v2AuthURL(ep, "users", l.username)
|
|
||||||
if l.user == nil {
|
|
||||||
req, _ := http.NewRequest(l.verb, u.String(), nil)
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
b, err := json.Marshal(l.user)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
body := bytes.NewReader(b)
|
|
||||||
req, _ := http.NewRequest(l.verb, u.String(), body)
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *httpAuthUserAPI) ListUsers(ctx context.Context) ([]string, error) {
|
|
||||||
resp, body, err := u.client.Do(ctx, &authUserAPIList{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
|
||||||
var sec authError
|
|
||||||
err = json.Unmarshal(body, &sec)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return nil, sec
|
|
||||||
}
|
|
||||||
|
|
||||||
var userList struct {
|
|
||||||
Users []userListEntry `json:"users"`
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = json.Unmarshal(body, &userList); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ret := make([]string, 0, len(userList.Users))
|
|
||||||
for _, u := range userList.Users {
|
|
||||||
ret = append(ret, u.User)
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *httpAuthUserAPI) AddUser(ctx context.Context, username string, password string) error {
|
|
||||||
user := &User{
|
|
||||||
User: username,
|
|
||||||
Password: password,
|
|
||||||
}
|
|
||||||
return u.addRemoveUser(ctx, &authUserAPIAction{
|
|
||||||
verb: "PUT",
|
|
||||||
username: username,
|
|
||||||
user: user,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *httpAuthUserAPI) RemoveUser(ctx context.Context, username string) error {
|
|
||||||
return u.addRemoveUser(ctx, &authUserAPIAction{
|
|
||||||
verb: "DELETE",
|
|
||||||
username: username,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *httpAuthUserAPI) addRemoveUser(ctx context.Context, req *authUserAPIAction) error {
|
|
||||||
resp, body, err := u.client.Do(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK, http.StatusCreated); err != nil {
|
|
||||||
var sec authError
|
|
||||||
err = json.Unmarshal(body, &sec)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return sec
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *httpAuthUserAPI) GetUser(ctx context.Context, username string) (*User, error) {
|
|
||||||
return u.modUser(ctx, &authUserAPIAction{
|
|
||||||
verb: "GET",
|
|
||||||
username: username,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *httpAuthUserAPI) GrantUser(ctx context.Context, username string, roles []string) (*User, error) {
|
|
||||||
user := &User{
|
|
||||||
User: username,
|
|
||||||
Grant: roles,
|
|
||||||
}
|
|
||||||
return u.modUser(ctx, &authUserAPIAction{
|
|
||||||
verb: "PUT",
|
|
||||||
username: username,
|
|
||||||
user: user,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *httpAuthUserAPI) RevokeUser(ctx context.Context, username string, roles []string) (*User, error) {
|
|
||||||
user := &User{
|
|
||||||
User: username,
|
|
||||||
Revoke: roles,
|
|
||||||
}
|
|
||||||
return u.modUser(ctx, &authUserAPIAction{
|
|
||||||
verb: "PUT",
|
|
||||||
username: username,
|
|
||||||
user: user,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *httpAuthUserAPI) ChangePassword(ctx context.Context, username string, password string) (*User, error) {
|
|
||||||
user := &User{
|
|
||||||
User: username,
|
|
||||||
Password: password,
|
|
||||||
}
|
|
||||||
return u.modUser(ctx, &authUserAPIAction{
|
|
||||||
verb: "PUT",
|
|
||||||
username: username,
|
|
||||||
user: user,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *httpAuthUserAPI) modUser(ctx context.Context, req *authUserAPIAction) (*User, error) {
|
|
||||||
resp, body, err := u.client.Do(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
|
||||||
var sec authError
|
|
||||||
err = json.Unmarshal(body, &sec)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return nil, sec
|
|
||||||
}
|
|
||||||
var user User
|
|
||||||
if err = json.Unmarshal(body, &user); err != nil {
|
|
||||||
var userR UserRoles
|
|
||||||
if urerr := json.Unmarshal(body, &userR); urerr != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
user.User = userR.User
|
|
||||||
for _, r := range userR.Roles {
|
|
||||||
user.Roles = append(user.Roles, r.Role)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &user, nil
|
|
||||||
}
|
|
18
vendor/github.com/coreos/etcd/client/cancelreq.go
generated
vendored
18
vendor/github.com/coreos/etcd/client/cancelreq.go
generated
vendored
@ -1,18 +0,0 @@
|
|||||||
// Copyright 2015 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.
|
|
||||||
|
|
||||||
// borrowed from golang/net/context/ctxhttp/cancelreq.go
|
|
||||||
|
|
||||||
package client
|
|
||||||
|
|
||||||
import "net/http"
|
|
||||||
|
|
||||||
func requestCanceler(tr CancelableTransport, req *http.Request) func() {
|
|
||||||
ch := make(chan struct{})
|
|
||||||
req.Cancel = ch
|
|
||||||
|
|
||||||
return func() {
|
|
||||||
close(ch)
|
|
||||||
}
|
|
||||||
}
|
|
710
vendor/github.com/coreos/etcd/client/client.go
generated
vendored
710
vendor/github.com/coreos/etcd/client/client.go
generated
vendored
@ -1,710 +0,0 @@
|
|||||||
// Copyright 2015 The etcd Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/coreos/etcd/version"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrNoEndpoints = errors.New("client: no endpoints available")
|
|
||||||
ErrTooManyRedirects = errors.New("client: too many redirects")
|
|
||||||
ErrClusterUnavailable = errors.New("client: etcd cluster is unavailable or misconfigured")
|
|
||||||
ErrNoLeaderEndpoint = errors.New("client: no leader endpoint available")
|
|
||||||
errTooManyRedirectChecks = errors.New("client: too many redirect checks")
|
|
||||||
|
|
||||||
// oneShotCtxValue is set on a context using WithValue(&oneShotValue) so
|
|
||||||
// that Do() will not retry a request
|
|
||||||
oneShotCtxValue interface{}
|
|
||||||
)
|
|
||||||
|
|
||||||
var DefaultRequestTimeout = 5 * time.Second
|
|
||||||
|
|
||||||
var DefaultTransport CancelableTransport = &http.Transport{
|
|
||||||
Proxy: http.ProxyFromEnvironment,
|
|
||||||
Dial: (&net.Dialer{
|
|
||||||
Timeout: 30 * time.Second,
|
|
||||||
KeepAlive: 30 * time.Second,
|
|
||||||
}).Dial,
|
|
||||||
TLSHandshakeTimeout: 10 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
type EndpointSelectionMode int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// EndpointSelectionRandom is the default value of the 'SelectionMode'.
|
|
||||||
// As the name implies, the client object will pick a node from the members
|
|
||||||
// of the cluster in a random fashion. If the cluster has three members, A, B,
|
|
||||||
// and C, the client picks any node from its three members as its request
|
|
||||||
// destination.
|
|
||||||
EndpointSelectionRandom EndpointSelectionMode = iota
|
|
||||||
|
|
||||||
// If 'SelectionMode' is set to 'EndpointSelectionPrioritizeLeader',
|
|
||||||
// requests are sent directly to the cluster leader. This reduces
|
|
||||||
// forwarding roundtrips compared to making requests to etcd followers
|
|
||||||
// who then forward them to the cluster leader. In the event of a leader
|
|
||||||
// failure, however, clients configured this way cannot prioritize among
|
|
||||||
// the remaining etcd followers. Therefore, when a client sets 'SelectionMode'
|
|
||||||
// to 'EndpointSelectionPrioritizeLeader', it must use 'client.AutoSync()' to
|
|
||||||
// maintain its knowledge of current cluster state.
|
|
||||||
//
|
|
||||||
// This mode should be used with Client.AutoSync().
|
|
||||||
EndpointSelectionPrioritizeLeader
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
// Endpoints defines a set of URLs (schemes, hosts and ports only)
|
|
||||||
// that can be used to communicate with a logical etcd cluster. For
|
|
||||||
// example, a three-node cluster could be provided like so:
|
|
||||||
//
|
|
||||||
// Endpoints: []string{
|
|
||||||
// "http://node1.example.com:2379",
|
|
||||||
// "http://node2.example.com:2379",
|
|
||||||
// "http://node3.example.com:2379",
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// If multiple endpoints are provided, the Client will attempt to
|
|
||||||
// use them all in the event that one or more of them are unusable.
|
|
||||||
//
|
|
||||||
// If Client.Sync is ever called, the Client may cache an alternate
|
|
||||||
// set of endpoints to continue operation.
|
|
||||||
Endpoints []string
|
|
||||||
|
|
||||||
// Transport is used by the Client to drive HTTP requests. If not
|
|
||||||
// provided, DefaultTransport will be used.
|
|
||||||
Transport CancelableTransport
|
|
||||||
|
|
||||||
// CheckRedirect specifies the policy for handling HTTP redirects.
|
|
||||||
// If CheckRedirect is not nil, the Client calls it before
|
|
||||||
// following an HTTP redirect. The sole argument is the number of
|
|
||||||
// requests that have already been made. If CheckRedirect returns
|
|
||||||
// an error, Client.Do will not make any further requests and return
|
|
||||||
// the error back it to the caller.
|
|
||||||
//
|
|
||||||
// If CheckRedirect is nil, the Client uses its default policy,
|
|
||||||
// which is to stop after 10 consecutive requests.
|
|
||||||
CheckRedirect CheckRedirectFunc
|
|
||||||
|
|
||||||
// Username specifies the user credential to add as an authorization header
|
|
||||||
Username string
|
|
||||||
|
|
||||||
// Password is the password for the specified user to add as an authorization header
|
|
||||||
// to the request.
|
|
||||||
Password string
|
|
||||||
|
|
||||||
// HeaderTimeoutPerRequest specifies the time limit to wait for response
|
|
||||||
// header in a single request made by the Client. The timeout includes
|
|
||||||
// connection time, any redirects, and header wait time.
|
|
||||||
//
|
|
||||||
// For non-watch GET request, server returns the response body immediately.
|
|
||||||
// For PUT/POST/DELETE request, server will attempt to commit request
|
|
||||||
// before responding, which is expected to take `100ms + 2 * RTT`.
|
|
||||||
// For watch request, server returns the header immediately to notify Client
|
|
||||||
// watch start. But if server is behind some kind of proxy, the response
|
|
||||||
// header may be cached at proxy, and Client cannot rely on this behavior.
|
|
||||||
//
|
|
||||||
// Especially, wait request will ignore this timeout.
|
|
||||||
//
|
|
||||||
// One API call may send multiple requests to different etcd servers until it
|
|
||||||
// succeeds. Use context of the API to specify the overall timeout.
|
|
||||||
//
|
|
||||||
// A HeaderTimeoutPerRequest of zero means no timeout.
|
|
||||||
HeaderTimeoutPerRequest time.Duration
|
|
||||||
|
|
||||||
// SelectionMode is an EndpointSelectionMode enum that specifies the
|
|
||||||
// policy for choosing the etcd cluster node to which requests are sent.
|
|
||||||
SelectionMode EndpointSelectionMode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *Config) transport() CancelableTransport {
|
|
||||||
if cfg.Transport == nil {
|
|
||||||
return DefaultTransport
|
|
||||||
}
|
|
||||||
return cfg.Transport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *Config) checkRedirect() CheckRedirectFunc {
|
|
||||||
if cfg.CheckRedirect == nil {
|
|
||||||
return DefaultCheckRedirect
|
|
||||||
}
|
|
||||||
return cfg.CheckRedirect
|
|
||||||
}
|
|
||||||
|
|
||||||
// CancelableTransport mimics net/http.Transport, but requires that
|
|
||||||
// the object also support request cancellation.
|
|
||||||
type CancelableTransport interface {
|
|
||||||
http.RoundTripper
|
|
||||||
CancelRequest(req *http.Request)
|
|
||||||
}
|
|
||||||
|
|
||||||
type CheckRedirectFunc func(via int) error
|
|
||||||
|
|
||||||
// DefaultCheckRedirect follows up to 10 redirects, but no more.
|
|
||||||
var DefaultCheckRedirect CheckRedirectFunc = func(via int) error {
|
|
||||||
if via > 10 {
|
|
||||||
return ErrTooManyRedirects
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Client interface {
|
|
||||||
// Sync updates the internal cache of the etcd cluster's membership.
|
|
||||||
Sync(context.Context) error
|
|
||||||
|
|
||||||
// AutoSync periodically calls Sync() every given interval.
|
|
||||||
// The recommended sync interval is 10 seconds to 1 minute, which does
|
|
||||||
// not bring too much overhead to server and makes client catch up the
|
|
||||||
// cluster change in time.
|
|
||||||
//
|
|
||||||
// The example to use it:
|
|
||||||
//
|
|
||||||
// for {
|
|
||||||
// err := client.AutoSync(ctx, 10*time.Second)
|
|
||||||
// if err == context.DeadlineExceeded || err == context.Canceled {
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
// log.Print(err)
|
|
||||||
// }
|
|
||||||
AutoSync(context.Context, time.Duration) error
|
|
||||||
|
|
||||||
// Endpoints returns a copy of the current set of API endpoints used
|
|
||||||
// by Client to resolve HTTP requests. If Sync has ever been called,
|
|
||||||
// this may differ from the initial Endpoints provided in the Config.
|
|
||||||
Endpoints() []string
|
|
||||||
|
|
||||||
// SetEndpoints sets the set of API endpoints used by Client to resolve
|
|
||||||
// HTTP requests. If the given endpoints are not valid, an error will be
|
|
||||||
// returned
|
|
||||||
SetEndpoints(eps []string) error
|
|
||||||
|
|
||||||
// GetVersion retrieves the current etcd server and cluster version
|
|
||||||
GetVersion(ctx context.Context) (*version.Versions, error)
|
|
||||||
|
|
||||||
httpClient
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(cfg Config) (Client, error) {
|
|
||||||
c := &httpClusterClient{
|
|
||||||
clientFactory: newHTTPClientFactory(cfg.transport(), cfg.checkRedirect(), cfg.HeaderTimeoutPerRequest),
|
|
||||||
rand: rand.New(rand.NewSource(int64(time.Now().Nanosecond()))),
|
|
||||||
selectionMode: cfg.SelectionMode,
|
|
||||||
}
|
|
||||||
if cfg.Username != "" {
|
|
||||||
c.credentials = &credentials{
|
|
||||||
username: cfg.Username,
|
|
||||||
password: cfg.Password,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := c.SetEndpoints(cfg.Endpoints); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type httpClient interface {
|
|
||||||
Do(context.Context, httpAction) (*http.Response, []byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newHTTPClientFactory(tr CancelableTransport, cr CheckRedirectFunc, headerTimeout time.Duration) httpClientFactory {
|
|
||||||
return func(ep url.URL) httpClient {
|
|
||||||
return &redirectFollowingHTTPClient{
|
|
||||||
checkRedirect: cr,
|
|
||||||
client: &simpleHTTPClient{
|
|
||||||
transport: tr,
|
|
||||||
endpoint: ep,
|
|
||||||
headerTimeout: headerTimeout,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type credentials struct {
|
|
||||||
username string
|
|
||||||
password string
|
|
||||||
}
|
|
||||||
|
|
||||||
type httpClientFactory func(url.URL) httpClient
|
|
||||||
|
|
||||||
type httpAction interface {
|
|
||||||
HTTPRequest(url.URL) *http.Request
|
|
||||||
}
|
|
||||||
|
|
||||||
type httpClusterClient struct {
|
|
||||||
clientFactory httpClientFactory
|
|
||||||
endpoints []url.URL
|
|
||||||
pinned int
|
|
||||||
credentials *credentials
|
|
||||||
sync.RWMutex
|
|
||||||
rand *rand.Rand
|
|
||||||
selectionMode EndpointSelectionMode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *httpClusterClient) getLeaderEndpoint(ctx context.Context, eps []url.URL) (string, error) {
|
|
||||||
ceps := make([]url.URL, len(eps))
|
|
||||||
copy(ceps, eps)
|
|
||||||
|
|
||||||
// To perform a lookup on the new endpoint list without using the current
|
|
||||||
// client, we'll copy it
|
|
||||||
clientCopy := &httpClusterClient{
|
|
||||||
clientFactory: c.clientFactory,
|
|
||||||
credentials: c.credentials,
|
|
||||||
rand: c.rand,
|
|
||||||
|
|
||||||
pinned: 0,
|
|
||||||
endpoints: ceps,
|
|
||||||
}
|
|
||||||
|
|
||||||
mAPI := NewMembersAPI(clientCopy)
|
|
||||||
leader, err := mAPI.Leader(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if len(leader.ClientURLs) == 0 {
|
|
||||||
return "", ErrNoLeaderEndpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
return leader.ClientURLs[0], nil // TODO: how to handle multiple client URLs?
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *httpClusterClient) parseEndpoints(eps []string) ([]url.URL, error) {
|
|
||||||
if len(eps) == 0 {
|
|
||||||
return []url.URL{}, ErrNoEndpoints
|
|
||||||
}
|
|
||||||
|
|
||||||
neps := make([]url.URL, len(eps))
|
|
||||||
for i, ep := range eps {
|
|
||||||
u, err := url.Parse(ep)
|
|
||||||
if err != nil {
|
|
||||||
return []url.URL{}, err
|
|
||||||
}
|
|
||||||
neps[i] = *u
|
|
||||||
}
|
|
||||||
return neps, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *httpClusterClient) SetEndpoints(eps []string) error {
|
|
||||||
neps, err := c.parseEndpoints(eps)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Lock()
|
|
||||||
defer c.Unlock()
|
|
||||||
|
|
||||||
c.endpoints = shuffleEndpoints(c.rand, neps)
|
|
||||||
// We're not doing anything for PrioritizeLeader here. This is
|
|
||||||
// due to not having a context meaning we can't call getLeaderEndpoint
|
|
||||||
// However, if you're using PrioritizeLeader, you've already been told
|
|
||||||
// to regularly call sync, where we do have a ctx, and can figure the
|
|
||||||
// leader. PrioritizeLeader is also quite a loose guarantee, so deal
|
|
||||||
// with it
|
|
||||||
c.pinned = 0
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *httpClusterClient) Do(ctx context.Context, act httpAction) (*http.Response, []byte, error) {
|
|
||||||
action := act
|
|
||||||
c.RLock()
|
|
||||||
leps := len(c.endpoints)
|
|
||||||
eps := make([]url.URL, leps)
|
|
||||||
n := copy(eps, c.endpoints)
|
|
||||||
pinned := c.pinned
|
|
||||||
|
|
||||||
if c.credentials != nil {
|
|
||||||
action = &authedAction{
|
|
||||||
act: act,
|
|
||||||
credentials: *c.credentials,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.RUnlock()
|
|
||||||
|
|
||||||
if leps == 0 {
|
|
||||||
return nil, nil, ErrNoEndpoints
|
|
||||||
}
|
|
||||||
|
|
||||||
if leps != n {
|
|
||||||
return nil, nil, errors.New("unable to pick endpoint: copy failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
var resp *http.Response
|
|
||||||
var body []byte
|
|
||||||
var err error
|
|
||||||
cerr := &ClusterError{}
|
|
||||||
isOneShot := ctx.Value(&oneShotCtxValue) != nil
|
|
||||||
|
|
||||||
for i := pinned; i < leps+pinned; i++ {
|
|
||||||
k := i % leps
|
|
||||||
hc := c.clientFactory(eps[k])
|
|
||||||
resp, body, err = hc.Do(ctx, action)
|
|
||||||
if err != nil {
|
|
||||||
cerr.Errors = append(cerr.Errors, err)
|
|
||||||
if err == ctx.Err() {
|
|
||||||
return nil, nil, ctx.Err()
|
|
||||||
}
|
|
||||||
if err == context.Canceled || err == context.DeadlineExceeded {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
} else if resp.StatusCode/100 == 5 {
|
|
||||||
switch resp.StatusCode {
|
|
||||||
case http.StatusInternalServerError, http.StatusServiceUnavailable:
|
|
||||||
// TODO: make sure this is a no leader response
|
|
||||||
cerr.Errors = append(cerr.Errors, fmt.Errorf("client: etcd member %s has no leader", eps[k].String()))
|
|
||||||
default:
|
|
||||||
cerr.Errors = append(cerr.Errors, fmt.Errorf("client: etcd member %s returns server error [%s]", eps[k].String(), http.StatusText(resp.StatusCode)))
|
|
||||||
}
|
|
||||||
err = cerr.Errors[0]
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
if !isOneShot {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
c.Lock()
|
|
||||||
c.pinned = (k + 1) % leps
|
|
||||||
c.Unlock()
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if k != pinned {
|
|
||||||
c.Lock()
|
|
||||||
c.pinned = k
|
|
||||||
c.Unlock()
|
|
||||||
}
|
|
||||||
return resp, body, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil, cerr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *httpClusterClient) Endpoints() []string {
|
|
||||||
c.RLock()
|
|
||||||
defer c.RUnlock()
|
|
||||||
|
|
||||||
eps := make([]string, len(c.endpoints))
|
|
||||||
for i, ep := range c.endpoints {
|
|
||||||
eps[i] = ep.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return eps
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *httpClusterClient) Sync(ctx context.Context) error {
|
|
||||||
mAPI := NewMembersAPI(c)
|
|
||||||
ms, err := mAPI.List(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var eps []string
|
|
||||||
for _, m := range ms {
|
|
||||||
eps = append(eps, m.ClientURLs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
neps, err := c.parseEndpoints(eps)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
npin := 0
|
|
||||||
|
|
||||||
switch c.selectionMode {
|
|
||||||
case EndpointSelectionRandom:
|
|
||||||
c.RLock()
|
|
||||||
eq := endpointsEqual(c.endpoints, neps)
|
|
||||||
c.RUnlock()
|
|
||||||
|
|
||||||
if eq {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// When items in the endpoint list changes, we choose a new pin
|
|
||||||
neps = shuffleEndpoints(c.rand, neps)
|
|
||||||
case EndpointSelectionPrioritizeLeader:
|
|
||||||
nle, err := c.getLeaderEndpoint(ctx, neps)
|
|
||||||
if err != nil {
|
|
||||||
return ErrNoLeaderEndpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, n := range neps {
|
|
||||||
if n.String() == nle {
|
|
||||||
npin = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("invalid endpoint selection mode: %d", c.selectionMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Lock()
|
|
||||||
defer c.Unlock()
|
|
||||||
c.endpoints = neps
|
|
||||||
c.pinned = npin
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *httpClusterClient) AutoSync(ctx context.Context, interval time.Duration) error {
|
|
||||||
ticker := time.NewTicker(interval)
|
|
||||||
defer ticker.Stop()
|
|
||||||
for {
|
|
||||||
err := c.Sync(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
case <-ticker.C:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *httpClusterClient) GetVersion(ctx context.Context) (*version.Versions, error) {
|
|
||||||
act := &getAction{Prefix: "/version"}
|
|
||||||
|
|
||||||
resp, body, err := c.Do(ctx, act)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch resp.StatusCode {
|
|
||||||
case http.StatusOK:
|
|
||||||
if len(body) == 0 {
|
|
||||||
return nil, ErrEmptyBody
|
|
||||||
}
|
|
||||||
var vresp version.Versions
|
|
||||||
if err := json.Unmarshal(body, &vresp); err != nil {
|
|
||||||
return nil, ErrInvalidJSON
|
|
||||||
}
|
|
||||||
return &vresp, nil
|
|
||||||
default:
|
|
||||||
var etcdErr Error
|
|
||||||
if err := json.Unmarshal(body, &etcdErr); err != nil {
|
|
||||||
return nil, ErrInvalidJSON
|
|
||||||
}
|
|
||||||
return nil, etcdErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type roundTripResponse struct {
|
|
||||||
resp *http.Response
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
type simpleHTTPClient struct {
|
|
||||||
transport CancelableTransport
|
|
||||||
endpoint url.URL
|
|
||||||
headerTimeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *simpleHTTPClient) Do(ctx context.Context, act httpAction) (*http.Response, []byte, error) {
|
|
||||||
req := act.HTTPRequest(c.endpoint)
|
|
||||||
|
|
||||||
if err := printcURL(req); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
isWait := false
|
|
||||||
if req != nil && req.URL != nil {
|
|
||||||
ws := req.URL.Query().Get("wait")
|
|
||||||
if len(ws) != 0 {
|
|
||||||
var err error
|
|
||||||
isWait, err = strconv.ParseBool(ws)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("wrong wait value %s (%v for %+v)", ws, err, req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var hctx context.Context
|
|
||||||
var hcancel context.CancelFunc
|
|
||||||
if !isWait && c.headerTimeout > 0 {
|
|
||||||
hctx, hcancel = context.WithTimeout(ctx, c.headerTimeout)
|
|
||||||
} else {
|
|
||||||
hctx, hcancel = context.WithCancel(ctx)
|
|
||||||
}
|
|
||||||
defer hcancel()
|
|
||||||
|
|
||||||
reqcancel := requestCanceler(c.transport, req)
|
|
||||||
|
|
||||||
rtchan := make(chan roundTripResponse, 1)
|
|
||||||
go func() {
|
|
||||||
resp, err := c.transport.RoundTrip(req)
|
|
||||||
rtchan <- roundTripResponse{resp: resp, err: err}
|
|
||||||
close(rtchan)
|
|
||||||
}()
|
|
||||||
|
|
||||||
var resp *http.Response
|
|
||||||
var err error
|
|
||||||
|
|
||||||
select {
|
|
||||||
case rtresp := <-rtchan:
|
|
||||||
resp, err = rtresp.resp, rtresp.err
|
|
||||||
case <-hctx.Done():
|
|
||||||
// cancel and wait for request to actually exit before continuing
|
|
||||||
reqcancel()
|
|
||||||
rtresp := <-rtchan
|
|
||||||
resp = rtresp.resp
|
|
||||||
switch {
|
|
||||||
case ctx.Err() != nil:
|
|
||||||
err = ctx.Err()
|
|
||||||
case hctx.Err() != nil:
|
|
||||||
err = fmt.Errorf("client: endpoint %s exceeded header timeout", c.endpoint.String())
|
|
||||||
default:
|
|
||||||
panic("failed to get error from context")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// always check for resp nil-ness to deal with possible
|
|
||||||
// race conditions between channels above
|
|
||||||
defer func() {
|
|
||||||
if resp != nil {
|
|
||||||
resp.Body.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var body []byte
|
|
||||||
done := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
body, err = ioutil.ReadAll(resp.Body)
|
|
||||||
done <- struct{}{}
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
resp.Body.Close()
|
|
||||||
<-done
|
|
||||||
return nil, nil, ctx.Err()
|
|
||||||
case <-done:
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, body, err
|
|
||||||
}
|
|
||||||
|
|
||||||
type authedAction struct {
|
|
||||||
act httpAction
|
|
||||||
credentials credentials
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authedAction) HTTPRequest(url url.URL) *http.Request {
|
|
||||||
r := a.act.HTTPRequest(url)
|
|
||||||
r.SetBasicAuth(a.credentials.username, a.credentials.password)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
type redirectFollowingHTTPClient struct {
|
|
||||||
client httpClient
|
|
||||||
checkRedirect CheckRedirectFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *redirectFollowingHTTPClient) Do(ctx context.Context, act httpAction) (*http.Response, []byte, error) {
|
|
||||||
next := act
|
|
||||||
for i := 0; i < 100; i++ {
|
|
||||||
if i > 0 {
|
|
||||||
if err := r.checkRedirect(i); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resp, body, err := r.client.Do(ctx, next)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if resp.StatusCode/100 == 3 {
|
|
||||||
hdr := resp.Header.Get("Location")
|
|
||||||
if hdr == "" {
|
|
||||||
return nil, nil, fmt.Errorf("Location header not set")
|
|
||||||
}
|
|
||||||
loc, err := url.Parse(hdr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("Location header not valid URL: %s", hdr)
|
|
||||||
}
|
|
||||||
next = &redirectedHTTPAction{
|
|
||||||
action: act,
|
|
||||||
location: *loc,
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return resp, body, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil, errTooManyRedirectChecks
|
|
||||||
}
|
|
||||||
|
|
||||||
type redirectedHTTPAction struct {
|
|
||||||
action httpAction
|
|
||||||
location url.URL
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *redirectedHTTPAction) HTTPRequest(ep url.URL) *http.Request {
|
|
||||||
orig := r.action.HTTPRequest(ep)
|
|
||||||
orig.URL = &r.location
|
|
||||||
return orig
|
|
||||||
}
|
|
||||||
|
|
||||||
func shuffleEndpoints(r *rand.Rand, eps []url.URL) []url.URL {
|
|
||||||
// copied from Go 1.9<= rand.Rand.Perm
|
|
||||||
n := len(eps)
|
|
||||||
p := make([]int, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
j := r.Intn(i + 1)
|
|
||||||
p[i] = p[j]
|
|
||||||
p[j] = i
|
|
||||||
}
|
|
||||||
neps := make([]url.URL, n)
|
|
||||||
for i, k := range p {
|
|
||||||
neps[i] = eps[k]
|
|
||||||
}
|
|
||||||
return neps
|
|
||||||
}
|
|
||||||
|
|
||||||
func endpointsEqual(left, right []url.URL) bool {
|
|
||||||
if len(left) != len(right) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
sLeft := make([]string, len(left))
|
|
||||||
sRight := make([]string, len(right))
|
|
||||||
for i, l := range left {
|
|
||||||
sLeft[i] = l.String()
|
|
||||||
}
|
|
||||||
for i, r := range right {
|
|
||||||
sRight[i] = r.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(sLeft)
|
|
||||||
sort.Strings(sRight)
|
|
||||||
for i := range sLeft {
|
|
||||||
if sLeft[i] != sRight[i] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
37
vendor/github.com/coreos/etcd/client/cluster_error.go
generated
vendored
37
vendor/github.com/coreos/etcd/client/cluster_error.go
generated
vendored
@ -1,37 +0,0 @@
|
|||||||
// Copyright 2015 The etcd Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package client
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
type ClusterError struct {
|
|
||||||
Errors []error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ce *ClusterError) Error() string {
|
|
||||||
s := ErrClusterUnavailable.Error()
|
|
||||||
for i, e := range ce.Errors {
|
|
||||||
s += fmt.Sprintf("; error #%d: %s\n", i, e)
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ce *ClusterError) Detail() string {
|
|
||||||
s := ""
|
|
||||||
for i, e := range ce.Errors {
|
|
||||||
s += fmt.Sprintf("error #%d: %s\n", i, e)
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
70
vendor/github.com/coreos/etcd/client/curl.go
generated
vendored
70
vendor/github.com/coreos/etcd/client/curl.go
generated
vendored
@ -1,70 +0,0 @@
|
|||||||
// Copyright 2015 The etcd Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
cURLDebug = false
|
|
||||||
)
|
|
||||||
|
|
||||||
func EnablecURLDebug() {
|
|
||||||
cURLDebug = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func DisablecURLDebug() {
|
|
||||||
cURLDebug = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// printcURL prints the cURL equivalent request to stderr.
|
|
||||||
// It returns an error if the body of the request cannot
|
|
||||||
// be read.
|
|
||||||
// The caller MUST cancel the request if there is an error.
|
|
||||||
func printcURL(req *http.Request) error {
|
|
||||||
if !cURLDebug {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
command string
|
|
||||||
b []byte
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
if req.URL != nil {
|
|
||||||
command = fmt.Sprintf("curl -X %s %s", req.Method, req.URL.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Body != nil {
|
|
||||||
b, err = ioutil.ReadAll(req.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
command += fmt.Sprintf(" -d %q", string(b))
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(os.Stderr, "cURL Command: %s\n", command)
|
|
||||||
|
|
||||||
// reset body
|
|
||||||
body := bytes.NewBuffer(b)
|
|
||||||
req.Body = ioutil.NopCloser(body)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
40
vendor/github.com/coreos/etcd/client/discover.go
generated
vendored
40
vendor/github.com/coreos/etcd/client/discover.go
generated
vendored
@ -1,40 +0,0 @@
|
|||||||
// Copyright 2015 The etcd Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/coreos/etcd/pkg/srv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Discoverer is an interface that wraps the Discover method.
|
|
||||||
type Discoverer interface {
|
|
||||||
// Discover looks up the etcd servers for the domain.
|
|
||||||
Discover(domain string) ([]string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type srvDiscover struct{}
|
|
||||||
|
|
||||||
// NewSRVDiscover constructs a new Discoverer that uses the stdlib to lookup SRV records.
|
|
||||||
func NewSRVDiscover() Discoverer {
|
|
||||||
return &srvDiscover{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *srvDiscover) Discover(domain string) ([]string, error) {
|
|
||||||
srvs, err := srv.GetClient("etcd-client", domain)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return srvs.Endpoints, nil
|
|
||||||
}
|
|
73
vendor/github.com/coreos/etcd/client/doc.go
generated
vendored
73
vendor/github.com/coreos/etcd/client/doc.go
generated
vendored
@ -1,73 +0,0 @@
|
|||||||
// Copyright 2015 The etcd Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
/*
|
|
||||||
Package client provides bindings for the etcd APIs.
|
|
||||||
|
|
||||||
Create a Config and exchange it for a Client:
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/coreos/etcd/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
cfg := client.Config{
|
|
||||||
Endpoints: []string{"http://127.0.0.1:2379"},
|
|
||||||
Transport: DefaultTransport,
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := client.New(cfg)
|
|
||||||
if err != nil {
|
|
||||||
// handle error
|
|
||||||
}
|
|
||||||
|
|
||||||
Clients are safe for concurrent use by multiple goroutines.
|
|
||||||
|
|
||||||
Create a KeysAPI using the Client, then use it to interact with etcd:
|
|
||||||
|
|
||||||
kAPI := client.NewKeysAPI(c)
|
|
||||||
|
|
||||||
// create a new key /foo with the value "bar"
|
|
||||||
_, err = kAPI.Create(context.Background(), "/foo", "bar")
|
|
||||||
if err != nil {
|
|
||||||
// handle error
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete the newly created key only if the value is still "bar"
|
|
||||||
_, err = kAPI.Delete(context.Background(), "/foo", &DeleteOptions{PrevValue: "bar"})
|
|
||||||
if err != nil {
|
|
||||||
// handle error
|
|
||||||
}
|
|
||||||
|
|
||||||
Use a custom context to set timeouts on your operations:
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// set a new key, ignoring its previous state
|
|
||||||
_, err := kAPI.Set(ctx, "/ping", "pong", nil)
|
|
||||||
if err != nil {
|
|
||||||
if err == context.DeadlineExceeded {
|
|
||||||
// request took longer than 5s
|
|
||||||
} else {
|
|
||||||
// handle error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
package client
|
|
17
vendor/github.com/coreos/etcd/client/integration/doc.go
generated
vendored
17
vendor/github.com/coreos/etcd/client/integration/doc.go
generated
vendored
@ -1,17 +0,0 @@
|
|||||||
// Copyright 2016 The etcd Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
// Package integration implements tests built upon embedded etcd, focusing on
|
|
||||||
// the correctness of the etcd v2 client.
|
|
||||||
package integration
|
|
5218
vendor/github.com/coreos/etcd/client/keys.generated.go
generated
vendored
5218
vendor/github.com/coreos/etcd/client/keys.generated.go
generated
vendored
File diff suppressed because it is too large
Load Diff
681
vendor/github.com/coreos/etcd/client/keys.go
generated
vendored
681
vendor/github.com/coreos/etcd/client/keys.go
generated
vendored
@ -1,681 +0,0 @@
|
|||||||
// Copyright 2015 The etcd Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package client
|
|
||||||
|
|
||||||
//go:generate codecgen -d 1819 -r "Node|Response|Nodes" -o keys.generated.go keys.go
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/coreos/etcd/pkg/pathutil"
|
|
||||||
"github.com/ugorji/go/codec"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ErrorCodeKeyNotFound = 100
|
|
||||||
ErrorCodeTestFailed = 101
|
|
||||||
ErrorCodeNotFile = 102
|
|
||||||
ErrorCodeNotDir = 104
|
|
||||||
ErrorCodeNodeExist = 105
|
|
||||||
ErrorCodeRootROnly = 107
|
|
||||||
ErrorCodeDirNotEmpty = 108
|
|
||||||
ErrorCodeUnauthorized = 110
|
|
||||||
|
|
||||||
ErrorCodePrevValueRequired = 201
|
|
||||||
ErrorCodeTTLNaN = 202
|
|
||||||
ErrorCodeIndexNaN = 203
|
|
||||||
ErrorCodeInvalidField = 209
|
|
||||||
ErrorCodeInvalidForm = 210
|
|
||||||
|
|
||||||
ErrorCodeRaftInternal = 300
|
|
||||||
ErrorCodeLeaderElect = 301
|
|
||||||
|
|
||||||
ErrorCodeWatcherCleared = 400
|
|
||||||
ErrorCodeEventIndexCleared = 401
|
|
||||||
)
|
|
||||||
|
|
||||||
type Error struct {
|
|
||||||
Code int `json:"errorCode"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Cause string `json:"cause"`
|
|
||||||
Index uint64 `json:"index"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e Error) Error() string {
|
|
||||||
return fmt.Sprintf("%v: %v (%v) [%v]", e.Code, e.Message, e.Cause, e.Index)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrInvalidJSON = errors.New("client: response is invalid json. The endpoint is probably not valid etcd cluster endpoint.")
|
|
||||||
ErrEmptyBody = errors.New("client: response body is empty")
|
|
||||||
)
|
|
||||||
|
|
||||||
// PrevExistType is used to define an existence condition when setting
|
|
||||||
// or deleting Nodes.
|
|
||||||
type PrevExistType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
PrevIgnore = PrevExistType("")
|
|
||||||
PrevExist = PrevExistType("true")
|
|
||||||
PrevNoExist = PrevExistType("false")
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
defaultV2KeysPrefix = "/v2/keys"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewKeysAPI builds a KeysAPI that interacts with etcd's key-value
|
|
||||||
// API over HTTP.
|
|
||||||
func NewKeysAPI(c Client) KeysAPI {
|
|
||||||
return NewKeysAPIWithPrefix(c, defaultV2KeysPrefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewKeysAPIWithPrefix acts like NewKeysAPI, but allows the caller
|
|
||||||
// to provide a custom base URL path. This should only be used in
|
|
||||||
// very rare cases.
|
|
||||||
func NewKeysAPIWithPrefix(c Client, p string) KeysAPI {
|
|
||||||
return &httpKeysAPI{
|
|
||||||
client: c,
|
|
||||||
prefix: p,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type KeysAPI interface {
|
|
||||||
// Get retrieves a set of Nodes from etcd
|
|
||||||
Get(ctx context.Context, key string, opts *GetOptions) (*Response, error)
|
|
||||||
|
|
||||||
// Set assigns a new value to a Node identified by a given key. The caller
|
|
||||||
// may define a set of conditions in the SetOptions. If SetOptions.Dir=true
|
|
||||||
// then value is ignored.
|
|
||||||
Set(ctx context.Context, key, value string, opts *SetOptions) (*Response, error)
|
|
||||||
|
|
||||||
// Delete removes a Node identified by the given key, optionally destroying
|
|
||||||
// all of its children as well. The caller may define a set of required
|
|
||||||
// conditions in an DeleteOptions object.
|
|
||||||
Delete(ctx context.Context, key string, opts *DeleteOptions) (*Response, error)
|
|
||||||
|
|
||||||
// Create is an alias for Set w/ PrevExist=false
|
|
||||||
Create(ctx context.Context, key, value string) (*Response, error)
|
|
||||||
|
|
||||||
// CreateInOrder is used to atomically create in-order keys within the given directory.
|
|
||||||
CreateInOrder(ctx context.Context, dir, value string, opts *CreateInOrderOptions) (*Response, error)
|
|
||||||
|
|
||||||
// Update is an alias for Set w/ PrevExist=true
|
|
||||||
Update(ctx context.Context, key, value string) (*Response, error)
|
|
||||||
|
|
||||||
// Watcher builds a new Watcher targeted at a specific Node identified
|
|
||||||
// by the given key. The Watcher may be configured at creation time
|
|
||||||
// through a WatcherOptions object. The returned Watcher is designed
|
|
||||||
// to emit events that happen to a Node, and optionally to its children.
|
|
||||||
Watcher(key string, opts *WatcherOptions) Watcher
|
|
||||||
}
|
|
||||||
|
|
||||||
type WatcherOptions struct {
|
|
||||||
// AfterIndex defines the index after-which the Watcher should
|
|
||||||
// start emitting events. For example, if a value of 5 is
|
|
||||||
// provided, the first event will have an index >= 6.
|
|
||||||
//
|
|
||||||
// Setting AfterIndex to 0 (default) means that the Watcher
|
|
||||||
// should start watching for events starting at the current
|
|
||||||
// index, whatever that may be.
|
|
||||||
AfterIndex uint64
|
|
||||||
|
|
||||||
// Recursive specifies whether or not the Watcher should emit
|
|
||||||
// events that occur in children of the given keyspace. If set
|
|
||||||
// to false (default), events will be limited to those that
|
|
||||||
// occur for the exact key.
|
|
||||||
Recursive bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type CreateInOrderOptions struct {
|
|
||||||
// TTL defines a period of time after-which the Node should
|
|
||||||
// expire and no longer exist. Values <= 0 are ignored. Given
|
|
||||||
// that the zero-value is ignored, TTL cannot be used to set
|
|
||||||
// a TTL of 0.
|
|
||||||
TTL time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
type SetOptions struct {
|
|
||||||
// PrevValue specifies what the current value of the Node must
|
|
||||||
// be in order for the Set operation to succeed.
|
|
||||||
//
|
|
||||||
// Leaving this field empty means that the caller wishes to
|
|
||||||
// ignore the current value of the Node. This cannot be used
|
|
||||||
// to compare the Node's current value to an empty string.
|
|
||||||
//
|
|
||||||
// PrevValue is ignored if Dir=true
|
|
||||||
PrevValue string
|
|
||||||
|
|
||||||
// PrevIndex indicates what the current ModifiedIndex of the
|
|
||||||
// Node must be in order for the Set operation to succeed.
|
|
||||||
//
|
|
||||||
// If PrevIndex is set to 0 (default), no comparison is made.
|
|
||||||
PrevIndex uint64
|
|
||||||
|
|
||||||
// PrevExist specifies whether the Node must currently exist
|
|
||||||
// (PrevExist) or not (PrevNoExist). If the caller does not
|
|
||||||
// care about existence, set PrevExist to PrevIgnore, or simply
|
|
||||||
// leave it unset.
|
|
||||||
PrevExist PrevExistType
|
|
||||||
|
|
||||||
// TTL defines a period of time after-which the Node should
|
|
||||||
// expire and no longer exist. Values <= 0 are ignored. Given
|
|
||||||
// that the zero-value is ignored, TTL cannot be used to set
|
|
||||||
// a TTL of 0.
|
|
||||||
TTL time.Duration
|
|
||||||
|
|
||||||
// Refresh set to true means a TTL value can be updated
|
|
||||||
// without firing a watch or changing the node value. A
|
|
||||||
// value must not be provided when refreshing a key.
|
|
||||||
Refresh bool
|
|
||||||
|
|
||||||
// Dir specifies whether or not this Node should be created as a directory.
|
|
||||||
Dir bool
|
|
||||||
|
|
||||||
// NoValueOnSuccess specifies whether the response contains the current value of the Node.
|
|
||||||
// If set, the response will only contain the current value when the request fails.
|
|
||||||
NoValueOnSuccess bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetOptions struct {
|
|
||||||
// Recursive defines whether or not all children of the Node
|
|
||||||
// should be returned.
|
|
||||||
Recursive bool
|
|
||||||
|
|
||||||
// Sort instructs the server whether or not to sort the Nodes.
|
|
||||||
// If true, the Nodes are sorted alphabetically by key in
|
|
||||||
// ascending order (A to z). If false (default), the Nodes will
|
|
||||||
// not be sorted and the ordering used should not be considered
|
|
||||||
// predictable.
|
|
||||||
Sort bool
|
|
||||||
|
|
||||||
// Quorum specifies whether it gets the latest committed value that
|
|
||||||
// has been applied in quorum of members, which ensures external
|
|
||||||
// consistency (or linearizability).
|
|
||||||
Quorum bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type DeleteOptions struct {
|
|
||||||
// PrevValue specifies what the current value of the Node must
|
|
||||||
// be in order for the Delete operation to succeed.
|
|
||||||
//
|
|
||||||
// Leaving this field empty means that the caller wishes to
|
|
||||||
// ignore the current value of the Node. This cannot be used
|
|
||||||
// to compare the Node's current value to an empty string.
|
|
||||||
PrevValue string
|
|
||||||
|
|
||||||
// PrevIndex indicates what the current ModifiedIndex of the
|
|
||||||
// Node must be in order for the Delete operation to succeed.
|
|
||||||
//
|
|
||||||
// If PrevIndex is set to 0 (default), no comparison is made.
|
|
||||||
PrevIndex uint64
|
|
||||||
|
|
||||||
// Recursive defines whether or not all children of the Node
|
|
||||||
// should be deleted. If set to true, all children of the Node
|
|
||||||
// identified by the given key will be deleted. If left unset
|
|
||||||
// or explicitly set to false, only a single Node will be
|
|
||||||
// deleted.
|
|
||||||
Recursive bool
|
|
||||||
|
|
||||||
// Dir specifies whether or not this Node should be removed as a directory.
|
|
||||||
Dir bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type Watcher interface {
|
|
||||||
// Next blocks until an etcd event occurs, then returns a Response
|
|
||||||
// representing that event. The behavior of Next depends on the
|
|
||||||
// WatcherOptions used to construct the Watcher. Next is designed to
|
|
||||||
// be called repeatedly, each time blocking until a subsequent event
|
|
||||||
// is available.
|
|
||||||
//
|
|
||||||
// If the provided context is cancelled, Next will return a non-nil
|
|
||||||
// error. Any other failures encountered while waiting for the next
|
|
||||||
// event (connection issues, deserialization failures, etc) will
|
|
||||||
// also result in a non-nil error.
|
|
||||||
Next(context.Context) (*Response, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Response struct {
|
|
||||||
// Action is the name of the operation that occurred. Possible values
|
|
||||||
// include get, set, delete, update, create, compareAndSwap,
|
|
||||||
// compareAndDelete and expire.
|
|
||||||
Action string `json:"action"`
|
|
||||||
|
|
||||||
// Node represents the state of the relevant etcd Node.
|
|
||||||
Node *Node `json:"node"`
|
|
||||||
|
|
||||||
// PrevNode represents the previous state of the Node. PrevNode is non-nil
|
|
||||||
// only if the Node existed before the action occurred and the action
|
|
||||||
// caused a change to the Node.
|
|
||||||
PrevNode *Node `json:"prevNode"`
|
|
||||||
|
|
||||||
// Index holds the cluster-level index at the time the Response was generated.
|
|
||||||
// This index is not tied to the Node(s) contained in this Response.
|
|
||||||
Index uint64 `json:"-"`
|
|
||||||
|
|
||||||
// ClusterID holds the cluster-level ID reported by the server. This
|
|
||||||
// should be different for different etcd clusters.
|
|
||||||
ClusterID string `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Node struct {
|
|
||||||
// Key represents the unique location of this Node (e.g. "/foo/bar").
|
|
||||||
Key string `json:"key"`
|
|
||||||
|
|
||||||
// Dir reports whether node describes a directory.
|
|
||||||
Dir bool `json:"dir,omitempty"`
|
|
||||||
|
|
||||||
// Value is the current data stored on this Node. If this Node
|
|
||||||
// is a directory, Value will be empty.
|
|
||||||
Value string `json:"value"`
|
|
||||||
|
|
||||||
// Nodes holds the children of this Node, only if this Node is a directory.
|
|
||||||
// This slice of will be arbitrarily deep (children, grandchildren, great-
|
|
||||||
// grandchildren, etc.) if a recursive Get or Watch request were made.
|
|
||||||
Nodes Nodes `json:"nodes"`
|
|
||||||
|
|
||||||
// CreatedIndex is the etcd index at-which this Node was created.
|
|
||||||
CreatedIndex uint64 `json:"createdIndex"`
|
|
||||||
|
|
||||||
// ModifiedIndex is the etcd index at-which this Node was last modified.
|
|
||||||
ModifiedIndex uint64 `json:"modifiedIndex"`
|
|
||||||
|
|
||||||
// Expiration is the server side expiration time of the key.
|
|
||||||
Expiration *time.Time `json:"expiration,omitempty"`
|
|
||||||
|
|
||||||
// TTL is the time to live of the key in second.
|
|
||||||
TTL int64 `json:"ttl,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Node) String() string {
|
|
||||||
return fmt.Sprintf("{Key: %s, CreatedIndex: %d, ModifiedIndex: %d, TTL: %d}", n.Key, n.CreatedIndex, n.ModifiedIndex, n.TTL)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TTLDuration returns the Node's TTL as a time.Duration object
|
|
||||||
func (n *Node) TTLDuration() time.Duration {
|
|
||||||
return time.Duration(n.TTL) * time.Second
|
|
||||||
}
|
|
||||||
|
|
||||||
type Nodes []*Node
|
|
||||||
|
|
||||||
// interfaces for sorting
|
|
||||||
|
|
||||||
func (ns Nodes) Len() int { return len(ns) }
|
|
||||||
func (ns Nodes) Less(i, j int) bool { return ns[i].Key < ns[j].Key }
|
|
||||||
func (ns Nodes) Swap(i, j int) { ns[i], ns[j] = ns[j], ns[i] }
|
|
||||||
|
|
||||||
type httpKeysAPI struct {
|
|
||||||
client httpClient
|
|
||||||
prefix string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *httpKeysAPI) Set(ctx context.Context, key, val string, opts *SetOptions) (*Response, error) {
|
|
||||||
act := &setAction{
|
|
||||||
Prefix: k.prefix,
|
|
||||||
Key: key,
|
|
||||||
Value: val,
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts != nil {
|
|
||||||
act.PrevValue = opts.PrevValue
|
|
||||||
act.PrevIndex = opts.PrevIndex
|
|
||||||
act.PrevExist = opts.PrevExist
|
|
||||||
act.TTL = opts.TTL
|
|
||||||
act.Refresh = opts.Refresh
|
|
||||||
act.Dir = opts.Dir
|
|
||||||
act.NoValueOnSuccess = opts.NoValueOnSuccess
|
|
||||||
}
|
|
||||||
|
|
||||||
doCtx := ctx
|
|
||||||
if act.PrevExist == PrevNoExist {
|
|
||||||
doCtx = context.WithValue(doCtx, &oneShotCtxValue, &oneShotCtxValue)
|
|
||||||
}
|
|
||||||
resp, body, err := k.client.Do(doCtx, act)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return unmarshalHTTPResponse(resp.StatusCode, resp.Header, body)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *httpKeysAPI) Create(ctx context.Context, key, val string) (*Response, error) {
|
|
||||||
return k.Set(ctx, key, val, &SetOptions{PrevExist: PrevNoExist})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *httpKeysAPI) CreateInOrder(ctx context.Context, dir, val string, opts *CreateInOrderOptions) (*Response, error) {
|
|
||||||
act := &createInOrderAction{
|
|
||||||
Prefix: k.prefix,
|
|
||||||
Dir: dir,
|
|
||||||
Value: val,
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts != nil {
|
|
||||||
act.TTL = opts.TTL
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, body, err := k.client.Do(ctx, act)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return unmarshalHTTPResponse(resp.StatusCode, resp.Header, body)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *httpKeysAPI) Update(ctx context.Context, key, val string) (*Response, error) {
|
|
||||||
return k.Set(ctx, key, val, &SetOptions{PrevExist: PrevExist})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *httpKeysAPI) Delete(ctx context.Context, key string, opts *DeleteOptions) (*Response, error) {
|
|
||||||
act := &deleteAction{
|
|
||||||
Prefix: k.prefix,
|
|
||||||
Key: key,
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts != nil {
|
|
||||||
act.PrevValue = opts.PrevValue
|
|
||||||
act.PrevIndex = opts.PrevIndex
|
|
||||||
act.Dir = opts.Dir
|
|
||||||
act.Recursive = opts.Recursive
|
|
||||||
}
|
|
||||||
|
|
||||||
doCtx := context.WithValue(ctx, &oneShotCtxValue, &oneShotCtxValue)
|
|
||||||
resp, body, err := k.client.Do(doCtx, act)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return unmarshalHTTPResponse(resp.StatusCode, resp.Header, body)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *httpKeysAPI) Get(ctx context.Context, key string, opts *GetOptions) (*Response, error) {
|
|
||||||
act := &getAction{
|
|
||||||
Prefix: k.prefix,
|
|
||||||
Key: key,
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts != nil {
|
|
||||||
act.Recursive = opts.Recursive
|
|
||||||
act.Sorted = opts.Sort
|
|
||||||
act.Quorum = opts.Quorum
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, body, err := k.client.Do(ctx, act)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return unmarshalHTTPResponse(resp.StatusCode, resp.Header, body)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *httpKeysAPI) Watcher(key string, opts *WatcherOptions) Watcher {
|
|
||||||
act := waitAction{
|
|
||||||
Prefix: k.prefix,
|
|
||||||
Key: key,
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts != nil {
|
|
||||||
act.Recursive = opts.Recursive
|
|
||||||
if opts.AfterIndex > 0 {
|
|
||||||
act.WaitIndex = opts.AfterIndex + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &httpWatcher{
|
|
||||||
client: k.client,
|
|
||||||
nextWait: act,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type httpWatcher struct {
|
|
||||||
client httpClient
|
|
||||||
nextWait waitAction
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hw *httpWatcher) Next(ctx context.Context) (*Response, error) {
|
|
||||||
for {
|
|
||||||
httpresp, body, err := hw.client.Do(ctx, &hw.nextWait)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := unmarshalHTTPResponse(httpresp.StatusCode, httpresp.Header, body)
|
|
||||||
if err != nil {
|
|
||||||
if err == ErrEmptyBody {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
hw.nextWait.WaitIndex = resp.Node.ModifiedIndex + 1
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// v2KeysURL forms a URL representing the location of a key.
|
|
||||||
// The endpoint argument represents the base URL of an etcd
|
|
||||||
// server. The prefix is the path needed to route from the
|
|
||||||
// provided endpoint's path to the root of the keys API
|
|
||||||
// (typically "/v2/keys").
|
|
||||||
func v2KeysURL(ep url.URL, prefix, key string) *url.URL {
|
|
||||||
// We concatenate all parts together manually. We cannot use
|
|
||||||
// path.Join because it does not reserve trailing slash.
|
|
||||||
// We call CanonicalURLPath to further cleanup the path.
|
|
||||||
if prefix != "" && prefix[0] != '/' {
|
|
||||||
prefix = "/" + prefix
|
|
||||||
}
|
|
||||||
if key != "" && key[0] != '/' {
|
|
||||||
key = "/" + key
|
|
||||||
}
|
|
||||||
ep.Path = pathutil.CanonicalURLPath(ep.Path + prefix + key)
|
|
||||||
return &ep
|
|
||||||
}
|
|
||||||
|
|
||||||
type getAction struct {
|
|
||||||
Prefix string
|
|
||||||
Key string
|
|
||||||
Recursive bool
|
|
||||||
Sorted bool
|
|
||||||
Quorum bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *getAction) HTTPRequest(ep url.URL) *http.Request {
|
|
||||||
u := v2KeysURL(ep, g.Prefix, g.Key)
|
|
||||||
|
|
||||||
params := u.Query()
|
|
||||||
params.Set("recursive", strconv.FormatBool(g.Recursive))
|
|
||||||
params.Set("sorted", strconv.FormatBool(g.Sorted))
|
|
||||||
params.Set("quorum", strconv.FormatBool(g.Quorum))
|
|
||||||
u.RawQuery = params.Encode()
|
|
||||||
|
|
||||||
req, _ := http.NewRequest("GET", u.String(), nil)
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
|
|
||||||
type waitAction struct {
|
|
||||||
Prefix string
|
|
||||||
Key string
|
|
||||||
WaitIndex uint64
|
|
||||||
Recursive bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *waitAction) HTTPRequest(ep url.URL) *http.Request {
|
|
||||||
u := v2KeysURL(ep, w.Prefix, w.Key)
|
|
||||||
|
|
||||||
params := u.Query()
|
|
||||||
params.Set("wait", "true")
|
|
||||||
params.Set("waitIndex", strconv.FormatUint(w.WaitIndex, 10))
|
|
||||||
params.Set("recursive", strconv.FormatBool(w.Recursive))
|
|
||||||
u.RawQuery = params.Encode()
|
|
||||||
|
|
||||||
req, _ := http.NewRequest("GET", u.String(), nil)
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
|
|
||||||
type setAction struct {
|
|
||||||
Prefix string
|
|
||||||
Key string
|
|
||||||
Value string
|
|
||||||
PrevValue string
|
|
||||||
PrevIndex uint64
|
|
||||||
PrevExist PrevExistType
|
|
||||||
TTL time.Duration
|
|
||||||
Refresh bool
|
|
||||||
Dir bool
|
|
||||||
NoValueOnSuccess bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *setAction) HTTPRequest(ep url.URL) *http.Request {
|
|
||||||
u := v2KeysURL(ep, a.Prefix, a.Key)
|
|
||||||
|
|
||||||
params := u.Query()
|
|
||||||
form := url.Values{}
|
|
||||||
|
|
||||||
// we're either creating a directory or setting a key
|
|
||||||
if a.Dir {
|
|
||||||
params.Set("dir", strconv.FormatBool(a.Dir))
|
|
||||||
} else {
|
|
||||||
// These options are only valid for setting a key
|
|
||||||
if a.PrevValue != "" {
|
|
||||||
params.Set("prevValue", a.PrevValue)
|
|
||||||
}
|
|
||||||
form.Add("value", a.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Options which apply to both setting a key and creating a dir
|
|
||||||
if a.PrevIndex != 0 {
|
|
||||||
params.Set("prevIndex", strconv.FormatUint(a.PrevIndex, 10))
|
|
||||||
}
|
|
||||||
if a.PrevExist != PrevIgnore {
|
|
||||||
params.Set("prevExist", string(a.PrevExist))
|
|
||||||
}
|
|
||||||
if a.TTL > 0 {
|
|
||||||
form.Add("ttl", strconv.FormatUint(uint64(a.TTL.Seconds()), 10))
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.Refresh {
|
|
||||||
form.Add("refresh", "true")
|
|
||||||
}
|
|
||||||
if a.NoValueOnSuccess {
|
|
||||||
params.Set("noValueOnSuccess", strconv.FormatBool(a.NoValueOnSuccess))
|
|
||||||
}
|
|
||||||
|
|
||||||
u.RawQuery = params.Encode()
|
|
||||||
body := strings.NewReader(form.Encode())
|
|
||||||
|
|
||||||
req, _ := http.NewRequest("PUT", u.String(), body)
|
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
||||||
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
|
|
||||||
type deleteAction struct {
|
|
||||||
Prefix string
|
|
||||||
Key string
|
|
||||||
PrevValue string
|
|
||||||
PrevIndex uint64
|
|
||||||
Dir bool
|
|
||||||
Recursive bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *deleteAction) HTTPRequest(ep url.URL) *http.Request {
|
|
||||||
u := v2KeysURL(ep, a.Prefix, a.Key)
|
|
||||||
|
|
||||||
params := u.Query()
|
|
||||||
if a.PrevValue != "" {
|
|
||||||
params.Set("prevValue", a.PrevValue)
|
|
||||||
}
|
|
||||||
if a.PrevIndex != 0 {
|
|
||||||
params.Set("prevIndex", strconv.FormatUint(a.PrevIndex, 10))
|
|
||||||
}
|
|
||||||
if a.Dir {
|
|
||||||
params.Set("dir", "true")
|
|
||||||
}
|
|
||||||
if a.Recursive {
|
|
||||||
params.Set("recursive", "true")
|
|
||||||
}
|
|
||||||
u.RawQuery = params.Encode()
|
|
||||||
|
|
||||||
req, _ := http.NewRequest("DELETE", u.String(), nil)
|
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
||||||
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
|
|
||||||
type createInOrderAction struct {
|
|
||||||
Prefix string
|
|
||||||
Dir string
|
|
||||||
Value string
|
|
||||||
TTL time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *createInOrderAction) HTTPRequest(ep url.URL) *http.Request {
|
|
||||||
u := v2KeysURL(ep, a.Prefix, a.Dir)
|
|
||||||
|
|
||||||
form := url.Values{}
|
|
||||||
form.Add("value", a.Value)
|
|
||||||
if a.TTL > 0 {
|
|
||||||
form.Add("ttl", strconv.FormatUint(uint64(a.TTL.Seconds()), 10))
|
|
||||||
}
|
|
||||||
body := strings.NewReader(form.Encode())
|
|
||||||
|
|
||||||
req, _ := http.NewRequest("POST", u.String(), body)
|
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalHTTPResponse(code int, header http.Header, body []byte) (res *Response, err error) {
|
|
||||||
switch code {
|
|
||||||
case http.StatusOK, http.StatusCreated:
|
|
||||||
if len(body) == 0 {
|
|
||||||
return nil, ErrEmptyBody
|
|
||||||
}
|
|
||||||
res, err = unmarshalSuccessfulKeysResponse(header, body)
|
|
||||||
default:
|
|
||||||
err = unmarshalFailedKeysResponse(body)
|
|
||||||
}
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalSuccessfulKeysResponse(header http.Header, body []byte) (*Response, error) {
|
|
||||||
var res Response
|
|
||||||
err := codec.NewDecoderBytes(body, new(codec.JsonHandle)).Decode(&res)
|
|
||||||
if err != nil {
|
|
||||||
return nil, ErrInvalidJSON
|
|
||||||
}
|
|
||||||
if header.Get("X-Etcd-Index") != "" {
|
|
||||||
res.Index, err = strconv.ParseUint(header.Get("X-Etcd-Index"), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res.ClusterID = header.Get("X-Etcd-Cluster-ID")
|
|
||||||
return &res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalFailedKeysResponse(body []byte) error {
|
|
||||||
var etcdErr Error
|
|
||||||
if err := json.Unmarshal(body, &etcdErr); err != nil {
|
|
||||||
return ErrInvalidJSON
|
|
||||||
}
|
|
||||||
return etcdErr
|
|
||||||
}
|
|
303
vendor/github.com/coreos/etcd/client/members.go
generated
vendored
303
vendor/github.com/coreos/etcd/client/members.go
generated
vendored
@ -1,303 +0,0 @@
|
|||||||
// Copyright 2015 The etcd Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"github.com/coreos/etcd/pkg/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
defaultV2MembersPrefix = "/v2/members"
|
|
||||||
defaultLeaderSuffix = "/leader"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Member struct {
|
|
||||||
// ID is the unique identifier of this Member.
|
|
||||||
ID string `json:"id"`
|
|
||||||
|
|
||||||
// Name is a human-readable, non-unique identifier of this Member.
|
|
||||||
Name string `json:"name"`
|
|
||||||
|
|
||||||
// PeerURLs represents the HTTP(S) endpoints this Member uses to
|
|
||||||
// participate in etcd's consensus protocol.
|
|
||||||
PeerURLs []string `json:"peerURLs"`
|
|
||||||
|
|
||||||
// ClientURLs represents the HTTP(S) endpoints on which this Member
|
|
||||||
// serves its client-facing APIs.
|
|
||||||
ClientURLs []string `json:"clientURLs"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type memberCollection []Member
|
|
||||||
|
|
||||||
func (c *memberCollection) UnmarshalJSON(data []byte) error {
|
|
||||||
d := struct {
|
|
||||||
Members []Member
|
|
||||||
}{}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(data, &d); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.Members == nil {
|
|
||||||
*c = make([]Member, 0)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
*c = d.Members
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type memberCreateOrUpdateRequest struct {
|
|
||||||
PeerURLs types.URLs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *memberCreateOrUpdateRequest) MarshalJSON() ([]byte, error) {
|
|
||||||
s := struct {
|
|
||||||
PeerURLs []string `json:"peerURLs"`
|
|
||||||
}{
|
|
||||||
PeerURLs: make([]string, len(m.PeerURLs)),
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, u := range m.PeerURLs {
|
|
||||||
s.PeerURLs[i] = u.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Marshal(&s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMembersAPI constructs a new MembersAPI that uses HTTP to
|
|
||||||
// interact with etcd's membership API.
|
|
||||||
func NewMembersAPI(c Client) MembersAPI {
|
|
||||||
return &httpMembersAPI{
|
|
||||||
client: c,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type MembersAPI interface {
|
|
||||||
// List enumerates the current cluster membership.
|
|
||||||
List(ctx context.Context) ([]Member, error)
|
|
||||||
|
|
||||||
// Add instructs etcd to accept a new Member into the cluster.
|
|
||||||
Add(ctx context.Context, peerURL string) (*Member, error)
|
|
||||||
|
|
||||||
// Remove demotes an existing Member out of the cluster.
|
|
||||||
Remove(ctx context.Context, mID string) error
|
|
||||||
|
|
||||||
// Update instructs etcd to update an existing Member in the cluster.
|
|
||||||
Update(ctx context.Context, mID string, peerURLs []string) error
|
|
||||||
|
|
||||||
// Leader gets current leader of the cluster
|
|
||||||
Leader(ctx context.Context) (*Member, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type httpMembersAPI struct {
|
|
||||||
client httpClient
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *httpMembersAPI) List(ctx context.Context) ([]Member, error) {
|
|
||||||
req := &membersAPIActionList{}
|
|
||||||
resp, body, err := m.client.Do(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var mCollection memberCollection
|
|
||||||
if err := json.Unmarshal(body, &mCollection); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return []Member(mCollection), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *httpMembersAPI) Add(ctx context.Context, peerURL string) (*Member, error) {
|
|
||||||
urls, err := types.NewURLs([]string{peerURL})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req := &membersAPIActionAdd{peerURLs: urls}
|
|
||||||
resp, body, err := m.client.Do(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := assertStatusCode(resp.StatusCode, http.StatusCreated, http.StatusConflict); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusCreated {
|
|
||||||
var merr membersError
|
|
||||||
if err := json.Unmarshal(body, &merr); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return nil, merr
|
|
||||||
}
|
|
||||||
|
|
||||||
var memb Member
|
|
||||||
if err := json.Unmarshal(body, &memb); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &memb, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *httpMembersAPI) Update(ctx context.Context, memberID string, peerURLs []string) error {
|
|
||||||
urls, err := types.NewURLs(peerURLs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
req := &membersAPIActionUpdate{peerURLs: urls, memberID: memberID}
|
|
||||||
resp, body, err := m.client.Do(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := assertStatusCode(resp.StatusCode, http.StatusNoContent, http.StatusNotFound, http.StatusConflict); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusNoContent {
|
|
||||||
var merr membersError
|
|
||||||
if err := json.Unmarshal(body, &merr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return merr
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *httpMembersAPI) Remove(ctx context.Context, memberID string) error {
|
|
||||||
req := &membersAPIActionRemove{memberID: memberID}
|
|
||||||
resp, _, err := m.client.Do(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return assertStatusCode(resp.StatusCode, http.StatusNoContent, http.StatusGone)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *httpMembersAPI) Leader(ctx context.Context) (*Member, error) {
|
|
||||||
req := &membersAPIActionLeader{}
|
|
||||||
resp, body, err := m.client.Do(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var leader Member
|
|
||||||
if err := json.Unmarshal(body, &leader); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &leader, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type membersAPIActionList struct{}
|
|
||||||
|
|
||||||
func (l *membersAPIActionList) HTTPRequest(ep url.URL) *http.Request {
|
|
||||||
u := v2MembersURL(ep)
|
|
||||||
req, _ := http.NewRequest("GET", u.String(), nil)
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
|
|
||||||
type membersAPIActionRemove struct {
|
|
||||||
memberID string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *membersAPIActionRemove) HTTPRequest(ep url.URL) *http.Request {
|
|
||||||
u := v2MembersURL(ep)
|
|
||||||
u.Path = path.Join(u.Path, d.memberID)
|
|
||||||
req, _ := http.NewRequest("DELETE", u.String(), nil)
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
|
|
||||||
type membersAPIActionAdd struct {
|
|
||||||
peerURLs types.URLs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *membersAPIActionAdd) HTTPRequest(ep url.URL) *http.Request {
|
|
||||||
u := v2MembersURL(ep)
|
|
||||||
m := memberCreateOrUpdateRequest{PeerURLs: a.peerURLs}
|
|
||||||
b, _ := json.Marshal(&m)
|
|
||||||
req, _ := http.NewRequest("POST", u.String(), bytes.NewReader(b))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
|
|
||||||
type membersAPIActionUpdate struct {
|
|
||||||
memberID string
|
|
||||||
peerURLs types.URLs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *membersAPIActionUpdate) HTTPRequest(ep url.URL) *http.Request {
|
|
||||||
u := v2MembersURL(ep)
|
|
||||||
m := memberCreateOrUpdateRequest{PeerURLs: a.peerURLs}
|
|
||||||
u.Path = path.Join(u.Path, a.memberID)
|
|
||||||
b, _ := json.Marshal(&m)
|
|
||||||
req, _ := http.NewRequest("PUT", u.String(), bytes.NewReader(b))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertStatusCode(got int, want ...int) (err error) {
|
|
||||||
for _, w := range want {
|
|
||||||
if w == got {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("unexpected status code %d", got)
|
|
||||||
}
|
|
||||||
|
|
||||||
type membersAPIActionLeader struct{}
|
|
||||||
|
|
||||||
func (l *membersAPIActionLeader) HTTPRequest(ep url.URL) *http.Request {
|
|
||||||
u := v2MembersURL(ep)
|
|
||||||
u.Path = path.Join(u.Path, defaultLeaderSuffix)
|
|
||||||
req, _ := http.NewRequest("GET", u.String(), nil)
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
|
|
||||||
// v2MembersURL add the necessary path to the provided endpoint
|
|
||||||
// to route requests to the default v2 members API.
|
|
||||||
func v2MembersURL(ep url.URL) *url.URL {
|
|
||||||
ep.Path = path.Join(ep.Path, defaultV2MembersPrefix)
|
|
||||||
return &ep
|
|
||||||
}
|
|
||||||
|
|
||||||
type membersError struct {
|
|
||||||
Message string `json:"message"`
|
|
||||||
Code int `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e membersError) Error() string {
|
|
||||||
return e.Message
|
|
||||||
}
|
|
53
vendor/github.com/coreos/etcd/client/util.go
generated
vendored
53
vendor/github.com/coreos/etcd/client/util.go
generated
vendored
@ -1,53 +0,0 @@
|
|||||||
// Copyright 2016 The etcd Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
roleNotFoundRegExp *regexp.Regexp
|
|
||||||
userNotFoundRegExp *regexp.Regexp
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
roleNotFoundRegExp = regexp.MustCompile("auth: Role .* does not exist.")
|
|
||||||
userNotFoundRegExp = regexp.MustCompile("auth: User .* does not exist.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsKeyNotFound returns true if the error code is ErrorCodeKeyNotFound.
|
|
||||||
func IsKeyNotFound(err error) bool {
|
|
||||||
if cErr, ok := err.(Error); ok {
|
|
||||||
return cErr.Code == ErrorCodeKeyNotFound
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRoleNotFound returns true if the error means role not found of v2 API.
|
|
||||||
func IsRoleNotFound(err error) bool {
|
|
||||||
if ae, ok := err.(authError); ok {
|
|
||||||
return roleNotFoundRegExp.MatchString(ae.Message)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsUserNotFound returns true if the error means user not found of v2 API.
|
|
||||||
func IsUserNotFound(err error) bool {
|
|
||||||
if ae, ok := err.(authError); ok {
|
|
||||||
return userNotFoundRegExp.MatchString(ae.Message)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
202
vendor/github.com/coreos/etcd/pkg/pathutil/LICENSE
generated
vendored
202
vendor/github.com/coreos/etcd/pkg/pathutil/LICENSE
generated
vendored
@ -1,202 +0,0 @@
|
|||||||
|
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
31
vendor/github.com/coreos/etcd/pkg/pathutil/path.go
generated
vendored
31
vendor/github.com/coreos/etcd/pkg/pathutil/path.go
generated
vendored
@ -1,31 +0,0 @@
|
|||||||
// Copyright 2009 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 pathutil implements utility functions for handling slash-separated
|
|
||||||
// paths.
|
|
||||||
package pathutil
|
|
||||||
|
|
||||||
import "path"
|
|
||||||
|
|
||||||
// CanonicalURLPath returns the canonical url path for p, which follows the rules:
|
|
||||||
// 1. the path always starts with "/"
|
|
||||||
// 2. replace multiple slashes with a single slash
|
|
||||||
// 3. replace each '.' '..' path name element with equivalent one
|
|
||||||
// 4. keep the trailing slash
|
|
||||||
// The function is borrowed from stdlib http.cleanPath in server.go.
|
|
||||||
func CanonicalURLPath(p string) string {
|
|
||||||
if p == "" {
|
|
||||||
return "/"
|
|
||||||
}
|
|
||||||
if p[0] != '/' {
|
|
||||||
p = "/" + p
|
|
||||||
}
|
|
||||||
np := path.Clean(p)
|
|
||||||
// path.Clean removes trailing slash except for root,
|
|
||||||
// put the trailing slash back if necessary.
|
|
||||||
if p[len(p)-1] == '/' && np != "/" {
|
|
||||||
np += "/"
|
|
||||||
}
|
|
||||||
return np
|
|
||||||
}
|
|
202
vendor/github.com/coreos/etcd/pkg/srv/LICENSE
generated
vendored
202
vendor/github.com/coreos/etcd/pkg/srv/LICENSE
generated
vendored
@ -1,202 +0,0 @@
|
|||||||
|
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
130
vendor/github.com/coreos/etcd/pkg/srv/srv.go
generated
vendored
130
vendor/github.com/coreos/etcd/pkg/srv/srv.go
generated
vendored
@ -1,130 +0,0 @@
|
|||||||
// Copyright 2015 The etcd Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
// Package srv looks up DNS SRV records.
|
|
||||||
package srv
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/coreos/etcd/pkg/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// indirection for testing
|
|
||||||
lookupSRV = net.LookupSRV // net.DefaultResolver.LookupSRV when ctxs don't conflict
|
|
||||||
resolveTCPAddr = net.ResolveTCPAddr
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetCluster gets the cluster information via DNS discovery.
|
|
||||||
// Also sees each entry as a separate instance.
|
|
||||||
func GetCluster(serviceScheme, service, name, dns string, apurls types.URLs) ([]string, error) {
|
|
||||||
tempName := int(0)
|
|
||||||
tcp2ap := make(map[string]url.URL)
|
|
||||||
|
|
||||||
// First, resolve the apurls
|
|
||||||
for _, url := range apurls {
|
|
||||||
tcpAddr, err := resolveTCPAddr("tcp", url.Host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tcp2ap[tcpAddr.String()] = url
|
|
||||||
}
|
|
||||||
|
|
||||||
stringParts := []string{}
|
|
||||||
updateNodeMap := func(service, scheme string) error {
|
|
||||||
_, addrs, err := lookupSRV(service, "tcp", dns)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, srv := range addrs {
|
|
||||||
port := fmt.Sprintf("%d", srv.Port)
|
|
||||||
host := net.JoinHostPort(srv.Target, port)
|
|
||||||
tcpAddr, terr := resolveTCPAddr("tcp", host)
|
|
||||||
if terr != nil {
|
|
||||||
err = terr
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
n := ""
|
|
||||||
url, ok := tcp2ap[tcpAddr.String()]
|
|
||||||
if ok {
|
|
||||||
n = name
|
|
||||||
}
|
|
||||||
if n == "" {
|
|
||||||
n = fmt.Sprintf("%d", tempName)
|
|
||||||
tempName++
|
|
||||||
}
|
|
||||||
// SRV records have a trailing dot but URL shouldn't.
|
|
||||||
shortHost := strings.TrimSuffix(srv.Target, ".")
|
|
||||||
urlHost := net.JoinHostPort(shortHost, port)
|
|
||||||
if ok && url.Scheme != scheme {
|
|
||||||
err = fmt.Errorf("bootstrap at %s from DNS for %s has scheme mismatch with expected peer %s", scheme+"://"+urlHost, service, url.String())
|
|
||||||
} else {
|
|
||||||
stringParts = append(stringParts, fmt.Sprintf("%s=%s://%s", n, scheme, urlHost))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(stringParts) == 0 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err := updateNodeMap(service, serviceScheme)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error querying DNS SRV records for _%s %s", service, err)
|
|
||||||
}
|
|
||||||
return stringParts, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type SRVClients struct {
|
|
||||||
Endpoints []string
|
|
||||||
SRVs []*net.SRV
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetClient looks up the client endpoints for a service and domain.
|
|
||||||
func GetClient(service, domain string) (*SRVClients, error) {
|
|
||||||
var urls []*url.URL
|
|
||||||
var srvs []*net.SRV
|
|
||||||
|
|
||||||
updateURLs := func(service, scheme string) error {
|
|
||||||
_, addrs, err := lookupSRV(service, "tcp", domain)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, srv := range addrs {
|
|
||||||
urls = append(urls, &url.URL{
|
|
||||||
Scheme: scheme,
|
|
||||||
Host: net.JoinHostPort(srv.Target, fmt.Sprintf("%d", srv.Port)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
srvs = append(srvs, addrs...)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
errHTTPS := updateURLs(service+"-ssl", "https")
|
|
||||||
errHTTP := updateURLs(service, "http")
|
|
||||||
|
|
||||||
if errHTTPS != nil && errHTTP != nil {
|
|
||||||
return nil, fmt.Errorf("dns lookup errors: %s and %s", errHTTPS, errHTTP)
|
|
||||||
}
|
|
||||||
|
|
||||||
endpoints := make([]string, len(urls))
|
|
||||||
for i := range urls {
|
|
||||||
endpoints[i] = urls[i].String()
|
|
||||||
}
|
|
||||||
return &SRVClients{Endpoints: endpoints, SRVs: srvs}, nil
|
|
||||||
}
|
|
202
vendor/github.com/coreos/etcd/pkg/types/LICENSE
generated
vendored
202
vendor/github.com/coreos/etcd/pkg/types/LICENSE
generated
vendored
@ -1,202 +0,0 @@
|
|||||||
|
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
17
vendor/github.com/coreos/etcd/pkg/types/doc.go
generated
vendored
17
vendor/github.com/coreos/etcd/pkg/types/doc.go
generated
vendored
@ -1,17 +0,0 @@
|
|||||||
// Copyright 2015 The etcd Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
// Package types declares various data types and implements type-checking
|
|
||||||
// functions.
|
|
||||||
package types
|
|
41
vendor/github.com/coreos/etcd/pkg/types/id.go
generated
vendored
41
vendor/github.com/coreos/etcd/pkg/types/id.go
generated
vendored
@ -1,41 +0,0 @@
|
|||||||
// Copyright 2015 The etcd Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ID represents a generic identifier which is canonically
|
|
||||||
// stored as a uint64 but is typically represented as a
|
|
||||||
// base-16 string for input/output
|
|
||||||
type ID uint64
|
|
||||||
|
|
||||||
func (i ID) String() string {
|
|
||||||
return strconv.FormatUint(uint64(i), 16)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IDFromString attempts to create an ID from a base-16 string.
|
|
||||||
func IDFromString(s string) (ID, error) {
|
|
||||||
i, err := strconv.ParseUint(s, 16, 64)
|
|
||||||
return ID(i), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// IDSlice implements the sort interface
|
|
||||||
type IDSlice []ID
|
|
||||||
|
|
||||||
func (p IDSlice) Len() int { return len(p) }
|
|
||||||
func (p IDSlice) Less(i, j int) bool { return uint64(p[i]) < uint64(p[j]) }
|
|
||||||
func (p IDSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
178
vendor/github.com/coreos/etcd/pkg/types/set.go
generated
vendored
178
vendor/github.com/coreos/etcd/pkg/types/set.go
generated
vendored
@ -1,178 +0,0 @@
|
|||||||
// Copyright 2015 The etcd Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Set interface {
|
|
||||||
Add(string)
|
|
||||||
Remove(string)
|
|
||||||
Contains(string) bool
|
|
||||||
Equals(Set) bool
|
|
||||||
Length() int
|
|
||||||
Values() []string
|
|
||||||
Copy() Set
|
|
||||||
Sub(Set) Set
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUnsafeSet(values ...string) *unsafeSet {
|
|
||||||
set := &unsafeSet{make(map[string]struct{})}
|
|
||||||
for _, v := range values {
|
|
||||||
set.Add(v)
|
|
||||||
}
|
|
||||||
return set
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewThreadsafeSet(values ...string) *tsafeSet {
|
|
||||||
us := NewUnsafeSet(values...)
|
|
||||||
return &tsafeSet{us, sync.RWMutex{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
type unsafeSet struct {
|
|
||||||
d map[string]struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add adds a new value to the set (no-op if the value is already present)
|
|
||||||
func (us *unsafeSet) Add(value string) {
|
|
||||||
us.d[value] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove removes the given value from the set
|
|
||||||
func (us *unsafeSet) Remove(value string) {
|
|
||||||
delete(us.d, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Contains returns whether the set contains the given value
|
|
||||||
func (us *unsafeSet) Contains(value string) (exists bool) {
|
|
||||||
_, exists = us.d[value]
|
|
||||||
return exists
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContainsAll returns whether the set contains all given values
|
|
||||||
func (us *unsafeSet) ContainsAll(values []string) bool {
|
|
||||||
for _, s := range values {
|
|
||||||
if !us.Contains(s) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equals returns whether the contents of two sets are identical
|
|
||||||
func (us *unsafeSet) Equals(other Set) bool {
|
|
||||||
v1 := sort.StringSlice(us.Values())
|
|
||||||
v2 := sort.StringSlice(other.Values())
|
|
||||||
v1.Sort()
|
|
||||||
v2.Sort()
|
|
||||||
return reflect.DeepEqual(v1, v2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Length returns the number of elements in the set
|
|
||||||
func (us *unsafeSet) Length() int {
|
|
||||||
return len(us.d)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Values returns the values of the Set in an unspecified order.
|
|
||||||
func (us *unsafeSet) Values() (values []string) {
|
|
||||||
values = make([]string, 0)
|
|
||||||
for val := range us.d {
|
|
||||||
values = append(values, val)
|
|
||||||
}
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy creates a new Set containing the values of the first
|
|
||||||
func (us *unsafeSet) Copy() Set {
|
|
||||||
cp := NewUnsafeSet()
|
|
||||||
for val := range us.d {
|
|
||||||
cp.Add(val)
|
|
||||||
}
|
|
||||||
|
|
||||||
return cp
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sub removes all elements in other from the set
|
|
||||||
func (us *unsafeSet) Sub(other Set) Set {
|
|
||||||
oValues := other.Values()
|
|
||||||
result := us.Copy().(*unsafeSet)
|
|
||||||
|
|
||||||
for _, val := range oValues {
|
|
||||||
if _, ok := result.d[val]; !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
delete(result.d, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
type tsafeSet struct {
|
|
||||||
us *unsafeSet
|
|
||||||
m sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts *tsafeSet) Add(value string) {
|
|
||||||
ts.m.Lock()
|
|
||||||
defer ts.m.Unlock()
|
|
||||||
ts.us.Add(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts *tsafeSet) Remove(value string) {
|
|
||||||
ts.m.Lock()
|
|
||||||
defer ts.m.Unlock()
|
|
||||||
ts.us.Remove(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts *tsafeSet) Contains(value string) (exists bool) {
|
|
||||||
ts.m.RLock()
|
|
||||||
defer ts.m.RUnlock()
|
|
||||||
return ts.us.Contains(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts *tsafeSet) Equals(other Set) bool {
|
|
||||||
ts.m.RLock()
|
|
||||||
defer ts.m.RUnlock()
|
|
||||||
return ts.us.Equals(other)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts *tsafeSet) Length() int {
|
|
||||||
ts.m.RLock()
|
|
||||||
defer ts.m.RUnlock()
|
|
||||||
return ts.us.Length()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts *tsafeSet) Values() (values []string) {
|
|
||||||
ts.m.RLock()
|
|
||||||
defer ts.m.RUnlock()
|
|
||||||
return ts.us.Values()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts *tsafeSet) Copy() Set {
|
|
||||||
ts.m.RLock()
|
|
||||||
defer ts.m.RUnlock()
|
|
||||||
usResult := ts.us.Copy().(*unsafeSet)
|
|
||||||
return &tsafeSet{usResult, sync.RWMutex{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts *tsafeSet) Sub(other Set) Set {
|
|
||||||
ts.m.RLock()
|
|
||||||
defer ts.m.RUnlock()
|
|
||||||
usResult := ts.us.Sub(other).(*unsafeSet)
|
|
||||||
return &tsafeSet{usResult, sync.RWMutex{}}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user