forked from lug/matterbridge
		
	Compare commits
	
		
			176 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 08f442dc7b | ||
|   | 8a8b95228c | ||
|   | 31a752fa21 | ||
|   | a83831e68d | ||
|   | a12a8d4fe2 | ||
|   | e57f3a7e6c | ||
|   | 68fbed9281 | ||
|   | 8bfaa007d5 | ||
|   | 76360f89c1 | ||
|   | d525230abd | ||
|   | b4aa637d41 | ||
|   | 7c4334d0de | ||
|   | 062be8d7c9 | ||
|   | db25ee59c5 | ||
|   | 4b0bc6d0bf | ||
|   | 8c0b04b995 | ||
|   | e5989adf92 | ||
|   | 9e5da2f9d7 | ||
|   | a284a228a3 | ||
|   | 2133e0d1be | ||
|   | a6f37f1d61 | ||
|   | 9de9151826 | ||
|   | fdd5ada98c | ||
|   | 80fcf18e24 | ||
|   | ab94b5ca7a | ||
|   | 8d2ce56c37 | ||
|   | 1ec324354b | ||
|   | 16be6601c8 | ||
|   | 98027446c8 | ||
|   | f2f1d874e1 | ||
|   | 25a72113b1 | ||
|   | 79c4ad5015 | ||
|   | e24f1c7c87 | ||
|   | dbf8a326d5 | ||
|   | 0bc9c70c66 | ||
|   | 594d2155e3 | ||
|   | 20dbd71306 | ||
|   | 6a727b9723 | ||
|   | 02a5bc096f | ||
|   | 2110db6f0c | ||
|   | 2bac867382 | ||
|   | 5fbd8a3be0 | ||
|   | ad6440b603 | ||
|   | 064b6a915f | ||
|   | 1578ebb0e2 | ||
|   | 73525a4bbc | ||
|   | d62f49d1fc | ||
|   | 63b88e77f2 | ||
|   | 3d8f15c20b | ||
|   | cac5d56d60 | ||
|   | bd2a672c14 | ||
|   | 82396e73f5 | ||
|   | ba928b169d | ||
|   | 4fed720f97 | ||
|   | 78238c85d4 | ||
|   | 4f2ae7b73f | ||
|   | f82a9cc7ac | ||
|   | cce7624ab8 | ||
|   | c5ecd09172 | ||
|   | 7b21c1c2f4 | ||
|   | f8714d81f5 | ||
|   | 8622656005 | ||
|   | 52237fadb6 | ||
|   | 222cccf388 | ||
|   | bab308508e | ||
|   | dedb83c867 | ||
|   | 723a90cdd6 | ||
|   | 67d2398fa8 | ||
|   | 5f3b6ec007 | ||
|   | 55ab0c12f1 | ||
|   | d1227b5fc9 | ||
|   | 6ea368c383 | ||
|   | e92b6de09f | ||
|   | e622587db4 | ||
|   | f2efc06d1f | ||
|   | a2b94452db | ||
|   | 4c506f7cc3 | ||
|   | 7886f05e88 | ||
|   | f58be0d1c1 | ||
|   | 1152394bc1 | ||
|   | a082b5a590 | ||
|   | bae9484df2 | ||
|   | 6f78485878 | ||
|   | fd0fe3390b | ||
|   | 2522158127 | ||
|   | 8be107cecc | ||
|   | 5aab158c0b | ||
|   | 1d33e60e36 | ||
|   | 83c28cb857 | ||
|   | df5bce27b0 | ||
|   | 2b15739b48 | ||
|   | 3480c88e90 | ||
|   | 432cd0f99d | ||
|   | e8b3e9b22d | ||
|   | d4a47671ea | ||
|   | 0bcd1e62f3 | ||
|   | 80822b7fff | ||
|   | 78f1011f52 | ||
|   | 67f6257617 | ||
|   | 169c614489 | ||
|   | da908c438a | ||
|   | 9c9c4bf1f9 | ||
|   | 7764493298 | ||
|   | 64a20ee61b | ||
|   | 62d1af8c37 | ||
|   | 0f5274fdf6 | ||
|   | 2e2187ebf4 | ||
|   | 762c3350f4 | ||
|   | e1a4d7f77e | ||
|   | a7a4554a85 | ||
|   | 6bd808ce91 | ||
|   | a5c143bc46 | ||
|   | 87c9cac756 | ||
|   | 6a047f8722 | ||
|   | 6523494e83 | ||
|   | 7c6ce8bb90 | ||
|   | dafbfe4021 | ||
|   | a4d5c94d9b | ||
|   | 7119e378a7 | ||
|   | e1dc3032c1 | ||
|   | 5de03b8921 | ||
|   | 7631d43c48 | ||
|   | d0b2ee5c85 | ||
|   | 8830a5a1df | ||
|   | ee87626a93 | ||
|   | 9f15d38c1c | ||
|   | 4a96a977c0 | ||
|   | 9a95293bdf | ||
|   | 0b3a06d263 | ||
|   | 9a6249c4f5 | ||
|   | 50bd51e461 | ||
|   | 04f8013314 | ||
|   | a0aaf0057a | ||
|   | 8e78b3e6be | ||
|   | 57a503818d | ||
|   | 25d2ff3e9b | ||
|   | 31902d3e57 | ||
|   | 16f3fa6bae | ||
|   | 1f706673cf | ||
|   | fac5f69ad2 | ||
|   | 97c944bb63 | ||
|   | d0c4fe78ee | ||
|   | 265457b451 | ||
|   | 4a4a29c9f6 | ||
|   | 0a91b9e1c9 | ||
|   | f56163295c | ||
|   | d1c87c068b | ||
|   | fa20761110 | ||
|   | e4a0e0a0e9 | ||
|   | d30ae19e2a | ||
|   | 5c919e6bff | ||
|   | 434393d1c3 | ||
|   | af9aa5d7cb | ||
|   | 05eb75442a | ||
|   | 3496ed0c7e | ||
|   | 1b89604c7a | ||
|   | 67a9d133e9 | ||
|   | ed9118b346 | ||
|   | 59e55cfbd5 | ||
|   | 788d3b32ac | ||
|   | 1d414cf2fd | ||
|   | cc3c168162 | ||
|   | 1ee6837f0e | ||
|   | 27dcea7c5b | ||
|   | dcda7f7b8c | ||
|   | e0cbb69a4f | ||
|   | 7ec95f786d | ||
|   | 1efe40add5 | ||
|   | cbd73ee313 | ||
|   | 34227a7a39 | ||
|   | 71cb9b2d1d | ||
|   | cd4c9b194f | ||
|   | 98762a0235 | ||
|   | 2fd1fd9573 | ||
|   | aff3964078 | ||
|   | 2778580397 | 
							
								
								
									
										28
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										28
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							| @@ -1,22 +1,36 @@ | ||||
| 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. | ||||
| <!-- This is a bug report template. By following the instructions below and | ||||
| filling out the sections with your information, you will help the us to get all | ||||
| the necessary data to fix your issue. | ||||
|  | ||||
| Please answer the following questions.  | ||||
| You can also preview your report before submitting it. | ||||
|  | ||||
| ### Which version of matterbridge are you using? | ||||
| run ```matterbridge -version``` | ||||
| Text between <!-- and --> marks will be invisible in the report. | ||||
| --> | ||||
|  | ||||
| ### If you're having problems with mattermost please specify mattermost 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. --> | ||||
|  | ||||
|  | ||||
| ### 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 actual behavior.  | ||||
| #### Use logs from running ```matterbridge -debug``` if possible. | ||||
| <!-- Use logs from running `matterbridge -debug` if possible. --> | ||||
|  | ||||
|  | ||||
| ### Any steps to reproduce the behavior? | ||||
|  | ||||
|  | ||||
| ### 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)) --> | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| language: go | ||||
| go: | ||||
|     #- 1.7.x | ||||
|     - 1.9.x | ||||
|     - 1.10.x | ||||
|       # - tip | ||||
|  | ||||
| # we have everything vendored | ||||
| @@ -34,15 +34,17 @@ before_script: | ||||
| # flunk the build and immediately stop. It's sorta like having | ||||
| # set -e enabled in bash.  | ||||
| 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 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 | ||||
|   - /bin/bash ci/bintray.sh | ||||
|   #- golint -set_exit_status $PKGS     # one last linter | ||||
|  | ||||
| deploy: | ||||
|   provider: bintray | ||||
|   edge: | ||||
|     branch: v1.8.47 | ||||
|   file: ci/deploy.json | ||||
|   user: 42wim | ||||
|   key: | ||||
|   | ||||
							
								
								
									
										37
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,17 +1,18 @@ | ||||
| # matterbridge | ||||
| 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://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://github.com/42wim/matterbridge/releases/latest) [](https://bintray.com/42wim/nightly/Matterbridge/_latestVersion) | ||||
|  | ||||
|  | ||||
|  | ||||
| Simple bridge between Mattermost, IRC, XMPP, Gitter, Slack, Discord, Telegram, Rocket.Chat, Hipchat(via xmpp), Matrix and Steam. | ||||
| Has a REST API. | ||||
| Simple bridge between Mattermost, IRC, XMPP, Gitter, Slack, Discord, Telegram, Rocket.Chat, Hipchat(via xmpp), Matrix, Steam and ssh-chat | ||||
| Has a REST API.   | ||||
| Minecraft server chat support via [MatterLink](https://github.com/elytra/MatterLink) | ||||
|  | ||||
| # Table of Contents | ||||
|  * [Features](#features) | ||||
|  * [Features](https://github.com/42wim/matterbridge/wiki/Features) | ||||
|  * [Requirements](#requirements) | ||||
|  * [Screenshots](https://github.com/42wim/matterbridge/wiki/) | ||||
|  * [Installing](#installing) | ||||
| @@ -27,13 +28,21 @@ Has a REST API. | ||||
|  * [Thanks](#thanks) | ||||
|  | ||||
| # Features | ||||
| * Relays public channel messages between multiple mattermost, IRC, XMPP, Gitter, Slack, Discord, Telegram, Rocket.Chat, Hipchat (via xmpp), Matrix and Steam.  | ||||
|   Pick and mix. | ||||
| * Support private groups on your mattermost/slack. | ||||
| * Allow for bridging the same bridges, which means you can eg bridge between multiple mattermosts. | ||||
| * The bridge is now a gateway which has support multiple in and out bridges. (and supports multiple gateways). | ||||
| * Edits and delete messages across bridges that support it (mattermost,slack,discord,gitter,telegram) | ||||
| * REST API to read/post messages to bridges (WIP). | ||||
| * [Support bridging between any protocols](https://github.com/42wim/matterbridge/wiki/Features#support-bridging-between-any-protocols) | ||||
| * [Support multiple gateways(bridges) for your protocols](https://github.com/42wim/matterbridge/wiki/Features#support-multiple-gatewaysbridges-for-your-protocols) | ||||
| * [Message edits and deletes](https://github.com/42wim/matterbridge/wiki/Features#message-edits-and-deletes) | ||||
| * [Attachment / files handling](https://github.com/42wim/matterbridge/wiki/Features#attachment--files-handling) | ||||
| * [Username and avatar spoofing](https://github.com/42wim/matterbridge/wiki/Features#username-and-avatar-spoofing) | ||||
| * [Private groups](https://github.com/42wim/matterbridge/wiki/Features#private-groups) | ||||
| * [API](https://github.com/42wim/matterbridge/wiki/Features#api) | ||||
|  | ||||
| ## 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 | ||||
| Accounts to one of the supported bridges | ||||
| @@ -48,13 +57,15 @@ Accounts to one of the supported bridges | ||||
| * [Rocket.chat](https://rocket.chat) | ||||
| * [Matrix](https://matrix.org) | ||||
| * [Steam](https://store.steampowered.com/) | ||||
| * [Twitch](https://twitch.tv) | ||||
| * [Ssh-chat](https://github.com/shazow/ssh-chat) | ||||
|  | ||||
| # Screenshots | ||||
| See https://github.com/42wim/matterbridge/wiki | ||||
|  | ||||
| # Installing | ||||
| ## Binaries | ||||
| * Latest stable release [v1.4.1](https://github.com/42wim/matterbridge/releases/latest) | ||||
| * Latest stable release [v1.9.1](https://github.com/42wim/matterbridge/releases/latest) | ||||
| * Development releases (follows master) can be downloaded [here](https://dl.bintray.com/42wim/nightly/)   | ||||
|  | ||||
| ## Building | ||||
| @@ -175,7 +186,7 @@ Matterbridge wouldn't exist without these libraries: | ||||
| * echo - https://github.com/labstack/echo | ||||
| * gitter - https://github.com/sromku/go-gitter | ||||
| * gops - https://github.com/google/gops | ||||
| * irc - https://github.com/thoj/go-ircevent | ||||
| * irc - https://github.com/lrstanley/girc | ||||
| * mattermost - https://github.com/mattermost/platform | ||||
| * matrix - https://github.com/matrix-org/gomatrix | ||||
| * slack - https://github.com/nlopes/slack | ||||
|   | ||||
| @@ -1,21 +1,21 @@ | ||||
| package api | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| 	"github.com/labstack/echo" | ||||
| 	"github.com/labstack/echo/middleware" | ||||
| 	"github.com/zfjagann/golang-ring" | ||||
| 	"net/http" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| type Api struct { | ||||
| 	Config   *config.Protocol | ||||
| 	Remote   chan config.Message | ||||
| 	Account  string | ||||
| 	Messages ring.Ring | ||||
| 	sync.RWMutex | ||||
| 	*bridge.Config | ||||
| } | ||||
|  | ||||
| type ApiMessage struct { | ||||
| @@ -26,30 +26,27 @@ type ApiMessage struct { | ||||
| 	Gateway  string `json:"gateway"` | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| var protocol = "api" | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"module": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg config.Protocol, account string, c chan config.Message) *Api { | ||||
| 	b := &Api{} | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	b := &Api{Config: cfg} | ||||
| 	e := echo.New() | ||||
| 	e.HideBanner = true | ||||
| 	e.HidePort = true | ||||
| 	b.Messages = ring.Ring{} | ||||
| 	b.Messages.SetCapacity(cfg.Buffer) | ||||
| 	b.Config = &cfg | ||||
| 	b.Account = account | ||||
| 	b.Remote = c | ||||
| 	if b.Config.Token != "" { | ||||
| 	b.Messages.SetCapacity(b.GetInt("Buffer")) | ||||
| 	if b.GetString("Token") != "" { | ||||
| 		e.Use(middleware.KeyAuth(func(key string, c echo.Context) (bool, error) { | ||||
| 			return key == b.Config.Token, nil | ||||
| 			return key == b.GetString("Token"), nil | ||||
| 		})) | ||||
| 	} | ||||
| 	e.GET("/api/messages", b.handleMessages) | ||||
| 	e.GET("/api/stream", b.handleStream) | ||||
| 	e.POST("/api/message", b.handlePostMessage) | ||||
| 	go func() { | ||||
| 		flog.Fatal(e.Start(cfg.BindAddress)) | ||||
| 		if b.GetString("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 | ||||
| } | ||||
| @@ -78,21 +75,18 @@ func (b *Api) Send(msg config.Message) (string, error) { | ||||
| } | ||||
|  | ||||
| func (b *Api) handlePostMessage(c echo.Context) error { | ||||
| 	message := &ApiMessage{} | ||||
| 	if err := c.Bind(message); err != nil { | ||||
| 	message := config.Message{} | ||||
| 	if err := c.Bind(&message); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	flog.Debugf("Sending message from %s on %s to gateway", message.Username, "api") | ||||
| 	b.Remote <- config.Message{ | ||||
| 		Text:     message.Text, | ||||
| 		Username: message.Username, | ||||
| 		UserID:   message.UserID, | ||||
| 		Channel:  "api", | ||||
| 		Avatar:   message.Avatar, | ||||
| 		Account:  b.Account, | ||||
| 		Gateway:  message.Gateway, | ||||
| 		Protocol: "api", | ||||
| 	} | ||||
| 	// these values are fixed | ||||
| 	message.Channel = "api" | ||||
| 	message.Protocol = "api" | ||||
| 	message.Account = b.Account | ||||
| 	message.ID = "" | ||||
| 	message.Timestamp = time.Now() | ||||
| 	b.Log.Debugf("Sending message from %s on %s to gateway", message.Username, "api") | ||||
| 	b.Remote <- message | ||||
| 	return c.JSON(http.StatusOK, message) | ||||
| } | ||||
|  | ||||
| @@ -103,3 +97,24 @@ func (b *Api) handleMessages(c echo.Context) error { | ||||
| 	b.Messages = ring.Ring{} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Api) handleStream(c echo.Context) error { | ||||
| 	c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON) | ||||
| 	c.Response().WriteHeader(http.StatusOK) | ||||
| 	closeNotifier := c.Response().CloseNotify() | ||||
| 	for { | ||||
| 		select { | ||||
| 		case <-closeNotifier: | ||||
| 			return nil | ||||
| 		default: | ||||
| 			msg := b.Messages.Dequeue() | ||||
| 			if msg != nil { | ||||
| 				if err := json.NewEncoder(c.Response()).Encode(msg); err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 				c.Response().Flush() | ||||
| 			} | ||||
| 			time.Sleep(200 * time.Millisecond) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										104
									
								
								bridge/bridge.go
									
									
									
									
									
								
							
							
						
						
									
										104
									
								
								bridge/bridge.go
									
									
									
									
									
								
							| @@ -1,19 +1,8 @@ | ||||
| package bridge | ||||
|  | ||||
| import ( | ||||
| 	"github.com/42wim/matterbridge/bridge/api" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"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/steam" | ||||
| 	"github.com/42wim/matterbridge/bridge/telegram" | ||||
| 	"github.com/42wim/matterbridge/bridge/xmpp" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
|  | ||||
| 	"strings" | ||||
| ) | ||||
| @@ -26,16 +15,28 @@ type Bridger interface { | ||||
| } | ||||
|  | ||||
| type Bridge struct { | ||||
| 	Config config.Protocol | ||||
| 	Bridger | ||||
| 	Name     string | ||||
| 	Account  string | ||||
| 	Protocol string | ||||
| 	Channels map[string]config.ChannelInfo | ||||
| 	Joined   map[string]bool | ||||
| 	Log      *log.Entry | ||||
| 	Config   *config.Config | ||||
| 	General  *config.Protocol | ||||
| } | ||||
|  | ||||
| func New(cfg *config.Config, bridge *config.Bridge, c chan config.Message) *Bridge { | ||||
| type Config struct { | ||||
| 	//	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.Channels = make(map[string]config.ChannelInfo) | ||||
| 	accInfo := strings.Split(bridge.Account, ".") | ||||
| @@ -45,44 +46,6 @@ func New(cfg *config.Config, bridge *config.Bridge, c chan config.Message) *Brid | ||||
| 	b.Protocol = protocol | ||||
| 	b.Account = bridge.Account | ||||
| 	b.Joined = make(map[string]bool) | ||||
|  | ||||
| 	// override config from environment | ||||
| 	config.OverrideCfgFromEnv(cfg, protocol, name) | ||||
| 	switch protocol { | ||||
| 	case "mattermost": | ||||
| 		b.Config = cfg.Mattermost[name] | ||||
| 		b.Bridger = bmattermost.New(cfg.Mattermost[name], bridge.Account, c) | ||||
| 	case "irc": | ||||
| 		b.Config = cfg.IRC[name] | ||||
| 		b.Bridger = birc.New(cfg.IRC[name], bridge.Account, c) | ||||
| 	case "gitter": | ||||
| 		b.Config = cfg.Gitter[name] | ||||
| 		b.Bridger = bgitter.New(cfg.Gitter[name], bridge.Account, c) | ||||
| 	case "slack": | ||||
| 		b.Config = cfg.Slack[name] | ||||
| 		b.Bridger = bslack.New(cfg.Slack[name], bridge.Account, c) | ||||
| 	case "xmpp": | ||||
| 		b.Config = cfg.Xmpp[name] | ||||
| 		b.Bridger = bxmpp.New(cfg.Xmpp[name], bridge.Account, c) | ||||
| 	case "discord": | ||||
| 		b.Config = cfg.Discord[name] | ||||
| 		b.Bridger = bdiscord.New(cfg.Discord[name], bridge.Account, c) | ||||
| 	case "telegram": | ||||
| 		b.Config = cfg.Telegram[name] | ||||
| 		b.Bridger = btelegram.New(cfg.Telegram[name], bridge.Account, c) | ||||
| 	case "rocketchat": | ||||
| 		b.Config = cfg.Rocketchat[name] | ||||
| 		b.Bridger = brocketchat.New(cfg.Rocketchat[name], bridge.Account, c) | ||||
| 	case "matrix": | ||||
| 		b.Config = cfg.Matrix[name] | ||||
| 		b.Bridger = bmatrix.New(cfg.Matrix[name], bridge.Account, c) | ||||
| 	case "steam": | ||||
| 		b.Config = cfg.Steam[name] | ||||
| 		b.Bridger = bsteam.New(cfg.Steam[name], bridge.Account, c) | ||||
| 	case "api": | ||||
| 		b.Config = cfg.Api[name] | ||||
| 		b.Bridger = api.New(cfg.Api[name], bridge.Account, c) | ||||
| 	} | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| @@ -94,7 +57,7 @@ func (b *Bridge) JoinChannels() error { | ||||
| func (b *Bridge) joinChannels(channels map[string]config.ChannelInfo, exists map[string]bool) error { | ||||
| 	for ID, channel := range channels { | ||||
| 		if !exists[ID] { | ||||
| 			log.Infof("%s: joining %s (%s)", b.Account, channel.Name, ID) | ||||
| 			b.Log.Infof("%s: joining %s (ID: %s)", b.Account, channel.Name, ID) | ||||
| 			err := b.JoinChannel(channel) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| @@ -104,3 +67,38 @@ func (b *Bridge) joinChannels(channels map[string]config.ChannelInfo, exists map | ||||
| 	} | ||||
| 	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,20 +1,24 @@ | ||||
| package config | ||||
|  | ||||
| import ( | ||||
| 	"github.com/BurntSushi/toml" | ||||
| 	"log" | ||||
| 	"bytes" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"github.com/spf13/viper" | ||||
| 	"os" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	EVENT_JOIN_LEAVE      = "join_leave" | ||||
| 	EVENT_FAILURE         = "failure" | ||||
| 	EVENT_REJOIN_CHANNELS = "rejoin_channels" | ||||
| 	EVENT_USER_ACTION     = "user_action" | ||||
| 	EVENT_MSG_DELETE      = "msg_delete" | ||||
| 	EVENT_JOIN_LEAVE        = "join_leave" | ||||
| 	EVENT_TOPIC_CHANGE      = "topic_change" | ||||
| 	EVENT_FAILURE           = "failure" | ||||
| 	EVENT_FILE_FAILURE_SIZE = "file_failure_size" | ||||
| 	EVENT_AVATAR_DOWNLOAD   = "avatar_download" | ||||
| 	EVENT_REJOIN_CHANNELS   = "rejoin_channels" | ||||
| 	EVENT_USER_ACTION       = "user_action" | ||||
| 	EVENT_MSG_DELETE        = "msg_delete" | ||||
| ) | ||||
|  | ||||
| type Message struct { | ||||
| @@ -36,6 +40,10 @@ type FileInfo struct { | ||||
| 	Name    string | ||||
| 	Data    *[]byte | ||||
| 	Comment string | ||||
| 	URL     string | ||||
| 	Size    int64 | ||||
| 	Avatar  bool | ||||
| 	SHA     string | ||||
| } | ||||
|  | ||||
| type ChannelInfo struct { | ||||
| @@ -52,48 +60,61 @@ type Protocol struct { | ||||
| 	BindAddress            string // mattermost, slack // DEPRECATED | ||||
| 	Buffer                 int    // api | ||||
| 	Charset                string // irc | ||||
| 	Debug                  bool   // general | ||||
| 	DebugLevel             int    // only for irc now | ||||
| 	EditSuffix             string // mattermost, slack, discord, telegram, gitter | ||||
| 	EditDisable            bool   // mattermost, slack, discord, telegram, gitter | ||||
| 	IconURL                string // mattermost, slack | ||||
| 	IgnoreNicks            string // all protocols | ||||
| 	IgnoreMessages         string // all protocols | ||||
| 	Jid                    string // xmpp | ||||
| 	Label                  string // all protocols | ||||
| 	Login                  string // mattermost, matrix | ||||
| 	Muc                    string // xmpp | ||||
| 	Name                   string // all protocols | ||||
| 	Nick                   string // all protocols | ||||
| 	NickFormatter          string // mattermost, slack | ||||
| 	NickServNick           string // IRC | ||||
| 	NickServUsername       string // IRC | ||||
| 	NickServPassword       string // IRC | ||||
| 	NicksPerRow            int    // mattermost, slack | ||||
| 	NoHomeServerSuffix     bool   // matrix | ||||
| 	NoTLS                  bool   // mattermost | ||||
| 	Password               string // IRC,mattermost,XMPP,matrix | ||||
| 	PrefixMessagesWithNick bool   // mattemost, slack | ||||
| 	Protocol               string //all protocols | ||||
| 	MessageQueue           int    // IRC, size of message queue for flood control | ||||
| 	MessageDelay           int    // IRC, time in millisecond to wait between messages | ||||
| 	MessageLength          int    // IRC, max length of a message allowed | ||||
| 	MessageFormat          string // telegram | ||||
| 	RemoteNickFormat       string // all protocols | ||||
| 	Server                 string // IRC,mattermost,XMPP,discord | ||||
| 	ShowJoinPart           bool   // all protocols | ||||
| 	ShowEmbeds             bool   // discord | ||||
| 	SkipTLSVerify          bool   // IRC, mattermost | ||||
| 	StripNick              bool   // all protocols | ||||
| 	Team                   string // mattermost | ||||
| 	Token                  string // gitter, slack, discord, api | ||||
| 	URL                    string // mattermost, slack // DEPRECATED | ||||
| 	UseAPI                 bool   // mattermost, slack | ||||
| 	UseSASL                bool   // IRC | ||||
| 	UseTLS                 bool   // IRC | ||||
| 	UseFirstName           bool   // telegram | ||||
| 	UseUserName            bool   // discord | ||||
| 	UseInsecureURL         bool   // telegram | ||||
| 	WebhookBindAddress     string // mattermost, slack | ||||
| 	WebhookURL             string // mattermost, slack | ||||
| 	WebhookUse             string // mattermost, slack, discord | ||||
| 	MediaDownloadSize      int    // all protocols | ||||
| 	MediaServerDownload    string | ||||
| 	MediaServerUpload      string | ||||
| 	MessageDelay           int        // IRC, time in millisecond to wait between messages | ||||
| 	MessageFormat          string     // telegram | ||||
| 	MessageLength          int        // IRC, max length of a message allowed | ||||
| 	MessageQueue           int        // IRC, size of message queue for flood control | ||||
| 	MessageSplit           bool       // IRC, split long messages with newlines on MessageLength instead of clipping | ||||
| 	Muc                    string     // xmpp | ||||
| 	Name                   string     // all protocols | ||||
| 	Nick                   string     // all protocols | ||||
| 	NickFormatter          string     // mattermost, slack | ||||
| 	NickServNick           string     // IRC | ||||
| 	NickServUsername       string     // IRC | ||||
| 	NickServPassword       string     // IRC | ||||
| 	NicksPerRow            int        // mattermost, slack | ||||
| 	NoHomeServerSuffix     bool       // matrix | ||||
| 	NoSendJoinPart         bool       // all protocols | ||||
| 	NoTLS                  bool       // mattermost | ||||
| 	Password               string     // IRC,mattermost,XMPP,matrix | ||||
| 	PrefixMessagesWithNick bool       // mattemost, slack | ||||
| 	Protocol               string     // all protocols | ||||
| 	QuoteDisable           bool       // telegram | ||||
| 	RejoinDelay            int        // IRC | ||||
| 	ReplaceMessages        [][]string // all protocols | ||||
| 	ReplaceNicks           [][]string // all protocols | ||||
| 	RemoteNickFormat       string     // all protocols | ||||
| 	Server                 string     // IRC,mattermost,XMPP,discord | ||||
| 	ShowJoinPart           bool       // all protocols | ||||
| 	ShowTopicChange        bool       // slack | ||||
| 	ShowEmbeds             bool       // discord | ||||
| 	SkipTLSVerify          bool       // IRC, mattermost | ||||
| 	StripNick              bool       // all protocols | ||||
| 	Team                   string     // mattermost | ||||
| 	Token                  string     // gitter, slack, discord, api | ||||
| 	URL                    string     // mattermost, slack // DEPRECATED | ||||
| 	UseAPI                 bool       // mattermost, slack | ||||
| 	UseSASL                bool       // IRC | ||||
| 	UseTLS                 bool       // IRC | ||||
| 	UseFirstName           bool       // telegram | ||||
| 	UseUserName            bool       // discord | ||||
| 	UseInsecureURL         bool       // telegram | ||||
| 	WebhookBindAddress     string     // mattermost, slack | ||||
| 	WebhookURL             string     // mattermost, slack | ||||
| 	WebhookUse             string     // mattermost, slack, discord | ||||
| } | ||||
|  | ||||
| type ChannelOptions struct { | ||||
| @@ -123,9 +144,9 @@ type SameChannelGateway struct { | ||||
| 	Accounts []string | ||||
| } | ||||
|  | ||||
| type Config struct { | ||||
| type ConfigValues struct { | ||||
| 	Api                map[string]Protocol | ||||
| 	IRC                map[string]Protocol | ||||
| 	Irc                map[string]Protocol | ||||
| 	Mattermost         map[string]Protocol | ||||
| 	Matrix             map[string]Protocol | ||||
| 	Slack              map[string]Protocol | ||||
| @@ -135,80 +156,101 @@ type Config struct { | ||||
| 	Discord            map[string]Protocol | ||||
| 	Telegram           map[string]Protocol | ||||
| 	Rocketchat         map[string]Protocol | ||||
| 	Sshchat            map[string]Protocol | ||||
| 	General            Protocol | ||||
| 	Gateway            []Gateway | ||||
| 	SameChannelGateway []SameChannelGateway | ||||
| } | ||||
|  | ||||
| type Config struct { | ||||
| 	v *viper.Viper | ||||
| 	*ConfigValues | ||||
| 	sync.RWMutex | ||||
| } | ||||
|  | ||||
| func NewConfig(cfgfile string) *Config { | ||||
| 	var cfg Config | ||||
| 	if _, err := toml.DecodeFile(cfgfile, &cfg); err != nil { | ||||
| 	var cfg ConfigValues | ||||
| 	viper.SetConfigType("toml") | ||||
| 	viper.SetEnvPrefix("matterbridge") | ||||
| 	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) | ||||
| 	viper.AutomaticEnv() | ||||
| 	f, err := os.Open(cfgfile) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	fail := false | ||||
| 	for k, v := range cfg.Mattermost { | ||||
| 		res := Deprecated(v, "mattermost."+k) | ||||
| 		if res { | ||||
| 			fail = res | ||||
| 		} | ||||
| 	err = viper.ReadConfig(f) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	for k, v := range cfg.Slack { | ||||
| 		res := Deprecated(v, "slack."+k) | ||||
| 		if res { | ||||
| 			fail = res | ||||
| 		} | ||||
| 	err = viper.Unmarshal(&cfg) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("blah", err) | ||||
| 	} | ||||
| 	for k, v := range cfg.Rocketchat { | ||||
| 		res := Deprecated(v, "rocketchat."+k) | ||||
| 		if res { | ||||
| 			fail = res | ||||
| 		} | ||||
| 	mycfg := new(Config) | ||||
| 	mycfg.v = viper.GetViper() | ||||
| 	if cfg.General.MediaDownloadSize == 0 { | ||||
| 		cfg.General.MediaDownloadSize = 1000000 | ||||
| 	} | ||||
| 	if fail { | ||||
| 		log.Fatalf("Fix your config. Please see changelog for more information") | ||||
| 	} | ||||
| 	return &cfg | ||||
| 	mycfg.ConfigValues = &cfg | ||||
| 	return mycfg | ||||
| } | ||||
|  | ||||
| func OverrideCfgFromEnv(cfg *Config, protocol string, account string) { | ||||
| 	var protoCfg Protocol | ||||
| 	val := reflect.ValueOf(cfg).Elem() | ||||
| 	// loop over the Config struct | ||||
| 	for i := 0; i < val.NumField(); i++ { | ||||
| 		typeField := val.Type().Field(i) | ||||
| 		// look for the protocol map (both lowercase) | ||||
| 		if strings.ToLower(typeField.Name) == protocol { | ||||
| 			// get the Protocol struct from the map | ||||
| 			data := val.Field(i).MapIndex(reflect.ValueOf(account)) | ||||
| 			protoCfg = data.Interface().(Protocol) | ||||
| 			protoStruct := reflect.ValueOf(&protoCfg).Elem() | ||||
| 			// loop over the found protocol struct | ||||
| 			for i := 0; i < protoStruct.NumField(); i++ { | ||||
| 				typeField := protoStruct.Type().Field(i) | ||||
| 				// build our environment key (eg MATTERBRIDGE_MATTERMOST_WORK_LOGIN) | ||||
| 				key := "matterbridge_" + protocol + "_" + account + "_" + typeField.Name | ||||
| 				key = strings.ToUpper(key) | ||||
| 				// search the environment | ||||
| 				res := os.Getenv(key) | ||||
| 				// if it exists and the current field is a string | ||||
| 				// then update the current field | ||||
| 				if res != "" { | ||||
| 					fieldVal := protoStruct.Field(i) | ||||
| 					if fieldVal.Kind() == reflect.String { | ||||
| 						log.Printf("config: overriding %s from env with %s\n", key, res) | ||||
| 						fieldVal.Set(reflect.ValueOf(res)) | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			// update the map with the modified Protocol (cfg.Protocol[account] = Protocol) | ||||
| 			val.Field(i).SetMapIndex(reflect.ValueOf(account), reflect.ValueOf(protoCfg)) | ||||
| 			break | ||||
| 		} | ||||
| func NewConfigFromString(input []byte) *Config { | ||||
| 	var cfg ConfigValues | ||||
| 	viper.SetConfigType("toml") | ||||
| 	err := viper.ReadConfig(bytes.NewBuffer(input)) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	err = viper.Unmarshal(&cfg) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	mycfg := new(Config) | ||||
| 	mycfg.v = viper.GetViper() | ||||
| 	mycfg.ConfigValues = &cfg | ||||
| 	return mycfg | ||||
| } | ||||
|  | ||||
| func GetIconURL(msg *Message, cfg *Protocol) string { | ||||
| 	iconURL := cfg.IconURL | ||||
| func (c *Config) GetBool(key string) bool { | ||||
| 	c.RLock() | ||||
| 	defer c.RUnlock() | ||||
| 	//	log.Debugf("getting bool %s = %#v", key, c.v.GetBool(key)) | ||||
| 	return c.v.GetBool(key) | ||||
| } | ||||
|  | ||||
| func (c *Config) GetInt(key string) int { | ||||
| 	c.RLock() | ||||
| 	defer c.RUnlock() | ||||
| 	//	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() | ||||
| 	if res, ok := c.v.Get(key).([][]string); ok { | ||||
| 		return res | ||||
| 	} | ||||
| 	// log.Debugf("getting StringSlice2D %s = %#v", key, c.v.Get(key)) | ||||
| 	return [][]string{} | ||||
| } | ||||
|  | ||||
| func GetIconURL(msg *Message, iconURL string) string { | ||||
| 	info := strings.Split(msg.Account, ".") | ||||
| 	protocol := info[0] | ||||
| 	name := info[1] | ||||
| @@ -217,17 +259,3 @@ func GetIconURL(msg *Message, cfg *Protocol) string { | ||||
| 	iconURL = strings.Replace(iconURL, "{PROTOCOL}", protocol, -1) | ||||
| 	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,19 +2,18 @@ package bdiscord | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| ) | ||||
|  | ||||
| type bdiscord struct { | ||||
| type Bdiscord struct { | ||||
| 	c              *discordgo.Session | ||||
| 	Config         *config.Protocol | ||||
| 	Remote         chan config.Message | ||||
| 	Account        string | ||||
| 	Channels       []*discordgo.Channel | ||||
| 	Nick           string | ||||
| 	UseChannelID   bool | ||||
| @@ -24,84 +23,74 @@ type bdiscord struct { | ||||
| 	webhookToken   string | ||||
| 	channelInfoMap map[string]*config.ChannelInfo | ||||
| 	sync.RWMutex | ||||
| 	*bridge.Config | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| var protocol = "discord" | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"module": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg config.Protocol, account string, c chan config.Message) *bdiscord { | ||||
| 	b := &bdiscord{} | ||||
| 	b.Config = &cfg | ||||
| 	b.Remote = c | ||||
| 	b.Account = account | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	b := &Bdiscord{Config: cfg} | ||||
| 	b.userMemberMap = make(map[string]*discordgo.Member) | ||||
| 	b.channelInfoMap = make(map[string]*config.ChannelInfo) | ||||
| 	if b.Config.WebhookURL != "" { | ||||
| 		flog.Debug("Configuring Discord Incoming Webhook") | ||||
| 		b.webhookID, b.webhookToken = b.splitURL(b.Config.WebhookURL) | ||||
| 	if b.GetString("WebhookURL") != "" { | ||||
| 		b.Log.Debug("Configuring Discord Incoming Webhook") | ||||
| 		b.webhookID, b.webhookToken = b.splitURL(b.GetString("WebhookURL")) | ||||
| 	} | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) Connect() error { | ||||
| func (b *Bdiscord) Connect() error { | ||||
| 	var err error | ||||
| 	flog.Info("Connecting") | ||||
| 	if b.Config.WebhookURL == "" { | ||||
| 		flog.Info("Connecting using token") | ||||
| 	var token string | ||||
| 	b.Log.Info("Connecting") | ||||
| 	if b.GetString("WebhookURL") == "" { | ||||
| 		b.Log.Info("Connecting using token") | ||||
| 	} else { | ||||
| 		flog.Info("Connecting using webhookurl (for posting) and token") | ||||
| 		b.Log.Info("Connecting using webhookurl (for posting) and token") | ||||
| 	} | ||||
| 	if !strings.HasPrefix(b.Config.Token, "Bot ") { | ||||
| 		b.Config.Token = "Bot " + b.Config.Token | ||||
| 	if !strings.HasPrefix(b.GetString("Token"), "Bot ") { | ||||
| 		token = "Bot " + b.GetString("Token") | ||||
| 	} | ||||
| 	b.c, err = discordgo.New(b.Config.Token) | ||||
| 	b.c, err = discordgo.New(token) | ||||
| 	if err != nil { | ||||
| 		flog.Debugf("%#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	flog.Info("Connection succeeded") | ||||
| 	b.Log.Info("Connection succeeded") | ||||
| 	b.c.AddHandler(b.messageCreate) | ||||
| 	b.c.AddHandler(b.memberUpdate) | ||||
| 	b.c.AddHandler(b.messageUpdate) | ||||
| 	b.c.AddHandler(b.messageDelete) | ||||
| 	err = b.c.Open() | ||||
| 	if err != nil { | ||||
| 		flog.Debugf("%#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	guilds, err := b.c.UserGuilds(100, "", "") | ||||
| 	if err != nil { | ||||
| 		flog.Debugf("%#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	userinfo, err := b.c.User("@me") | ||||
| 	if err != nil { | ||||
| 		flog.Debugf("%#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	b.Nick = userinfo.Username | ||||
| 	for _, guild := range guilds { | ||||
| 		if guild.Name == b.Config.Server { | ||||
| 		if guild.Name == b.GetString("Server") { | ||||
| 			b.Channels, err = b.c.GuildChannels(guild.ID) | ||||
| 			b.guildID = guild.ID | ||||
| 			if err != nil { | ||||
| 				flog.Debugf("%#v", err) | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	for _, channel := range b.Channels { | ||||
| 		b.Log.Debugf("found channel %#v", channel) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) Disconnect() error { | ||||
| 	return nil | ||||
| func (b *Bdiscord) Disconnect() error { | ||||
| 	return b.c.Close() | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) JoinChannel(channel config.ChannelInfo) error { | ||||
| func (b *Bdiscord) JoinChannel(channel config.ChannelInfo) error { | ||||
| 	b.channelInfoMap[channel.ID] = &channel | ||||
| 	idcheck := strings.Split(channel.Name, "ID:") | ||||
| 	if len(idcheck) > 1 { | ||||
| @@ -110,99 +99,117 @@ func (b *bdiscord) JoinChannel(channel config.ChannelInfo) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) Send(msg config.Message) (string, error) { | ||||
| 	flog.Debugf("Receiving %#v", msg) | ||||
| func (b *Bdiscord) Send(msg config.Message) (string, error) { | ||||
| 	b.Log.Debugf("=> Receiving %#v", msg) | ||||
|  | ||||
| 	channelID := b.getChannelID(msg.Channel) | ||||
| 	if channelID == "" { | ||||
| 		flog.Errorf("Could not find channelID for %v", msg.Channel) | ||||
| 		return "", nil | ||||
| 		return "", fmt.Errorf("Could not find channelID for %v", msg.Channel) | ||||
| 	} | ||||
|  | ||||
| 	// Make a action /me of the message | ||||
| 	if msg.Event == config.EVENT_USER_ACTION { | ||||
| 		msg.Text = "_" + msg.Text + "_" | ||||
| 	} | ||||
|  | ||||
| 	// use initial webhook | ||||
| 	wID := b.webhookID | ||||
| 	wToken := b.webhookToken | ||||
|  | ||||
| 	// check if have a channel specific webhook | ||||
| 	if ci, ok := b.channelInfoMap[msg.Channel+b.Account]; ok { | ||||
| 		if ci.Options.WebhookURL != "" { | ||||
| 			wID, wToken = b.splitURL(ci.Options.WebhookURL) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if wID == "" { | ||||
| 		flog.Debugf("Broadcasting using token (API)") | ||||
| 		if msg.Event == config.EVENT_MSG_DELETE { | ||||
| 			if msg.ID == "" { | ||||
| 				return "", nil | ||||
| 			} | ||||
| 			err := b.c.ChannelMessageDelete(channelID, msg.ID) | ||||
| 			return "", err | ||||
| 	// Use webhook to send the message | ||||
| 	if wID != "" { | ||||
| 		// skip events | ||||
| 		if msg.Event != "" { | ||||
| 			return "", nil | ||||
| 		} | ||||
| 		if msg.ID != "" { | ||||
| 			_, err := b.c.ChannelMessageEdit(channelID, msg.ID, msg.Username+msg.Text) | ||||
| 			return msg.ID, err | ||||
| 		} | ||||
|  | ||||
| 		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 | ||||
| 		b.Log.Debugf("Broadcasting using Webhook") | ||||
| 		for _, f := range msg.Extra["file"] { | ||||
| 			fi := f.(config.FileInfo) | ||||
| 			if fi.URL != "" { | ||||
| 				msg.Text += fi.URL + " " | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		res, err := b.c.ChannelMessageSend(channelID, msg.Username+msg.Text) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		return res.ID, err | ||||
| 		err := b.c.WebhookExecute( | ||||
| 			wID, | ||||
| 			wToken, | ||||
| 			true, | ||||
| 			&discordgo.WebhookParams{ | ||||
| 				Content:   msg.Text, | ||||
| 				Username:  msg.Username, | ||||
| 				AvatarURL: msg.Avatar, | ||||
| 			}) | ||||
| 		return "", err | ||||
| 	} | ||||
| 	flog.Debugf("Broadcasting using Webhook") | ||||
| 	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)") | ||||
|  | ||||
| 	// Delete message | ||||
| 	if msg.Event == config.EVENT_MSG_DELETE { | ||||
| 		if msg.ID == "" { | ||||
| 			return "", nil | ||||
| 		} | ||||
| 		err := b.c.ChannelMessageDelete(channelID, msg.ID) | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	// Upload a file if it exists | ||||
| 	if msg.Extra != nil { | ||||
| 		for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||
| 			b.c.ChannelMessageSend(channelID, rmsg.Username+rmsg.Text) | ||||
| 		} | ||||
| 		// check if we have files to upload (from slack, telegram or mattermost) | ||||
| 		if len(msg.Extra["file"]) > 0 { | ||||
| 			return b.handleUploadFile(&msg, channelID) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// 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.Channel = b.getChannelName(m.ChannelID) | ||||
| 	if b.UseChannelID { | ||||
| 		rmsg.Channel = "ID:" + m.ChannelID | ||||
| 	} | ||||
| 	flog.Debugf("Sending message from %s to gateway", b.Account) | ||||
| 	flog.Debugf("Message is %#v", rmsg) | ||||
| 	b.Log.Debugf("<= Sending message from %s to gateway", b.Account) | ||||
| 	b.Log.Debugf("<= Message is %#v", rmsg) | ||||
| 	b.Remote <- rmsg | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) { | ||||
| 	if b.Config.EditDisable { | ||||
| func (b *Bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) { | ||||
| 	if b.GetBool("EditDisable") { | ||||
| 		return | ||||
| 	} | ||||
| 	// only when message is actually edited | ||||
| 	if m.Message.EditedTimestamp != "" { | ||||
| 		flog.Debugf("Sending edit message") | ||||
| 		m.Content = m.Content + b.Config.EditSuffix | ||||
| 		b.Log.Debugf("Sending edit message") | ||||
| 		m.Content = m.Content + b.GetString("EditSuffix") | ||||
| 		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 | ||||
| 	if m.Author.Username == b.Nick { | ||||
| 		return | ||||
| @@ -212,69 +219,73 @@ func (b *bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// add the url of the attachments to content | ||||
| 	if len(m.Attachments) > 0 { | ||||
| 		for _, attach := range m.Attachments { | ||||
| 			m.Content = m.Content + "\n" + attach.URL | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	var text string | ||||
| 	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} | ||||
|  | ||||
| 	if m.Content != "" { | ||||
| 		flog.Debugf("Receiving message %#v", m.Message) | ||||
| 		if len(m.MentionRoles) > 0 { | ||||
| 			m.Message.Content = b.replaceRoleMentions(m.Message.Content) | ||||
| 		} | ||||
| 		b.Log.Debugf("== Receiving event %#v", m.Message) | ||||
| 		m.Message.Content = b.stripCustomoji(m.Message.Content) | ||||
| 		m.Message.Content = b.replaceChannelMentions(m.Message.Content) | ||||
| 		text = m.ContentWithMentionsReplaced() | ||||
| 		rmsg.Text, err = m.ContentWithMoreMentionsReplaced(b.c) | ||||
| 		if err != nil { | ||||
| 			b.Log.Errorf("ContentWithMoreMentionsReplaced failed: %s", err) | ||||
| 			rmsg.Text = m.ContentWithMentionsReplaced() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	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} | ||||
|  | ||||
| 	// set channel name | ||||
| 	rmsg.Channel = b.getChannelName(m.ChannelID) | ||||
| 	if b.UseChannelID { | ||||
| 		rmsg.Channel = "ID:" + m.ChannelID | ||||
| 	} | ||||
|  | ||||
| 	if !b.Config.UseUserName { | ||||
| 	// set username | ||||
| 	if !b.GetBool("UseUserName") { | ||||
| 		rmsg.Username = b.getNick(m.Author) | ||||
| 	} else { | ||||
| 		rmsg.Username = m.Author.Username | ||||
| 	} | ||||
|  | ||||
| 	if b.Config.ShowEmbeds && m.Message.Embeds != nil { | ||||
| 	// if we have embedded content add it to text | ||||
| 	if b.GetBool("ShowEmbeds") && m.Message.Embeds != nil { | ||||
| 		for _, embed := range m.Message.Embeds { | ||||
| 			text = text + "embed: " + embed.Title + " - " + embed.Description + " - " + embed.URL + "\n" | ||||
| 			rmsg.Text = rmsg.Text + "embed: " + embed.Title + " - " + embed.Description + " - " + embed.URL + "\n" | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// no empty messages | ||||
| 	if text == "" { | ||||
| 	if rmsg.Text == "" { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	text, ok := b.replaceAction(text) | ||||
| 	// do we have a /me action | ||||
| 	var ok bool | ||||
| 	rmsg.Text, ok = b.replaceAction(rmsg.Text) | ||||
| 	if ok { | ||||
| 		rmsg.Event = config.EVENT_USER_ACTION | ||||
| 	} | ||||
|  | ||||
| 	rmsg.Text = text | ||||
| 	flog.Debugf("Sending message from %s on %s to gateway", m.Author.Username, b.Account) | ||||
| 	flog.Debugf("Message is %#v", rmsg) | ||||
| 	b.Log.Debugf("<= Sending message from %s on %s to gateway", m.Author.Username, b.Account) | ||||
| 	b.Log.Debugf("<= Message is %#v", 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() | ||||
| 	if _, ok := b.userMemberMap[m.Member.User.ID]; ok { | ||||
| 		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.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) | ||||
| 	} | ||||
| 	b.userMemberMap[m.Member.User.ID] = m.Member | ||||
| 	b.Unlock() | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) getNick(user *discordgo.User) string { | ||||
| func (b *Bdiscord) getNick(user *discordgo.User) string { | ||||
| 	var err error | ||||
| 	b.Lock() | ||||
| 	defer b.Unlock() | ||||
| @@ -301,7 +312,7 @@ func (b *bdiscord) getNick(user *discordgo.User) string { | ||||
| 	return user.Username | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) getChannelID(name string) string { | ||||
| func (b *Bdiscord) getChannelID(name string) string { | ||||
| 	idcheck := strings.Split(name, "ID:") | ||||
| 	if len(idcheck) > 1 { | ||||
| 		return idcheck[1] | ||||
| @@ -314,7 +325,7 @@ func (b *bdiscord) getChannelID(name string) string { | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) getChannelName(id string) string { | ||||
| func (b *Bdiscord) getChannelName(id string) string { | ||||
| 	for _, channel := range b.Channels { | ||||
| 		if channel.ID == id { | ||||
| 			return channel.Name | ||||
| @@ -323,19 +334,7 @@ func (b *bdiscord) getChannelName(id string) string { | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| 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 { | ||||
| func (b *Bdiscord) replaceChannelMentions(text string) string { | ||||
| 	var err error | ||||
| 	re := regexp.MustCompile("<#[0-9]+>") | ||||
| 	text = re.ReplaceAllStringFunc(text, func(m string) string { | ||||
| @@ -354,28 +353,31 @@ func (b *bdiscord) replaceChannelMentions(text string) string { | ||||
| 	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, "_") { | ||||
| 		return strings.Replace(text, "_", "", -1), true | ||||
| 	} | ||||
| 	return text, false | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) stripCustomoji(text string) string { | ||||
| func (b *Bdiscord) stripCustomoji(text string) string { | ||||
| 	// <:doge:302803592035958784> | ||||
| 	re := regexp.MustCompile("<(:.*?:)[0-9]+>") | ||||
| 	return re.ReplaceAllString(text, `$1`) | ||||
| } | ||||
|  | ||||
| // 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, "/") | ||||
| 	if len(webhookURLSplit) != 7 { | ||||
| 		b.Log.Fatalf("%s is no correct discord WebhookURL", url) | ||||
| 	} | ||||
| 	return webhookURLSplit[len(webhookURLSplit)-2], webhookURLSplit[len(webhookURLSplit)-1] | ||||
| } | ||||
|  | ||||
| // useWebhook returns true if we have a webhook defined somewhere | ||||
| func (b *bdiscord) useWebhook() bool { | ||||
| 	if b.Config.WebhookURL != "" { | ||||
| func (b *Bdiscord) useWebhook() bool { | ||||
| 	if b.GetString("WebhookURL") != "" { | ||||
| 		return true | ||||
| 	} | ||||
| 	for _, channel := range b.channelInfoMap { | ||||
| @@ -387,9 +389,9 @@ func (b *bdiscord) useWebhook() bool { | ||||
| } | ||||
|  | ||||
| // isWebhookID returns true if the specified id is used in a defined webhook | ||||
| func (b *bdiscord) isWebhookID(id string) bool { | ||||
| 	if b.Config.WebhookURL != "" { | ||||
| 		wID, _ := b.splitURL(b.Config.WebhookURL) | ||||
| func (b *Bdiscord) isWebhookID(id string) bool { | ||||
| 	if b.GetString("WebhookURL") != "" { | ||||
| 		wID, _ := b.splitURL(b.GetString("WebhookURL")) | ||||
| 		if wID == id { | ||||
| 			return true | ||||
| 		} | ||||
| @@ -404,3 +406,18 @@ func (b *bdiscord) isWebhookID(id string) bool { | ||||
| 	} | ||||
| 	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,47 +3,37 @@ package bgitter | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"github.com/42wim/go-gitter" | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| type Bgitter struct { | ||||
| 	c       *gitter.Gitter | ||||
| 	Config  *config.Protocol | ||||
| 	Remote  chan config.Message | ||||
| 	Account string | ||||
| 	User    *gitter.User | ||||
| 	Users   []gitter.User | ||||
| 	Rooms   []gitter.Room | ||||
| 	c     *gitter.Gitter | ||||
| 	User  *gitter.User | ||||
| 	Users []gitter.User | ||||
| 	Rooms []gitter.Room | ||||
| 	*bridge.Config | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| var protocol = "gitter" | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"module": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg config.Protocol, account string, c chan config.Message) *Bgitter { | ||||
| 	b := &Bgitter{} | ||||
| 	b.Config = &cfg | ||||
| 	b.Remote = c | ||||
| 	b.Account = account | ||||
| 	return b | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	return &Bgitter{Config: cfg} | ||||
| } | ||||
|  | ||||
| func (b *Bgitter) Connect() error { | ||||
| 	var err error | ||||
| 	flog.Info("Connecting") | ||||
| 	b.c = gitter.New(b.Config.Token) | ||||
| 	b.Log.Info("Connecting") | ||||
| 	b.c = gitter.New(b.GetString("Token")) | ||||
| 	b.User, err = b.c.GetUser() | ||||
| 	if err != nil { | ||||
| 		flog.Debugf("%#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	flog.Info("Connection succeeded") | ||||
| 	b.Rooms, _ = b.c.GetRooms() | ||||
| 	b.Rooms, err = b.c.GetRooms() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	b.Log.Info("Connection succeeded") | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -79,8 +69,9 @@ func (b *Bgitter) JoinChannel(channel config.ChannelInfo) error { | ||||
| 		for event := range stream.Event { | ||||
| 			switch ev := event.Data.(type) { | ||||
| 			case *gitter.MessageReceived: | ||||
| 				// ignore message sent from ourselves | ||||
| 				if ev.Message.From.ID != b.User.ID { | ||||
| 					flog.Debugf("Sending message from %s on %s to gateway", ev.Message.From.Username, b.Account) | ||||
| 					b.Log.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, | ||||
| 						Account: b.Account, Avatar: b.getAvatar(ev.Message.From.Username), UserID: ev.Message.From.ID, | ||||
| 						ID: ev.Message.ID} | ||||
| @@ -88,11 +79,11 @@ func (b *Bgitter) JoinChannel(channel config.ChannelInfo) error { | ||||
| 						rmsg.Event = config.EVENT_USER_ACTION | ||||
| 						rmsg.Text = strings.Replace(rmsg.Text, "@"+ev.Message.From.Username+" ", "", -1) | ||||
| 					} | ||||
| 					flog.Debugf("Message is %#v", rmsg) | ||||
| 					b.Log.Debugf("<= Message is %#v", rmsg) | ||||
| 					b.Remote <- rmsg | ||||
| 				} | ||||
| 			case *gitter.GitterConnectionClosed: | ||||
| 				flog.Errorf("connection with gitter closed for room %s", room) | ||||
| 				b.Log.Errorf("connection with gitter closed for room %s", room) | ||||
| 			} | ||||
| 		} | ||||
| 	}(stream, room.URI) | ||||
| @@ -100,31 +91,47 @@ func (b *Bgitter) JoinChannel(channel config.ChannelInfo) error { | ||||
| } | ||||
|  | ||||
| func (b *Bgitter) Send(msg config.Message) (string, error) { | ||||
| 	flog.Debugf("Receiving %#v", msg) | ||||
| 	b.Log.Debugf("=> Receiving %#v", msg) | ||||
| 	roomID := b.getRoomID(msg.Channel) | ||||
| 	if roomID == "" { | ||||
| 		flog.Errorf("Could not find roomID for %v", msg.Channel) | ||||
| 		b.Log.Errorf("Could not find roomID for %v", msg.Channel) | ||||
| 		return "", nil | ||||
| 	} | ||||
|  | ||||
| 	// Delete message | ||||
| 	if msg.Event == config.EVENT_MSG_DELETE { | ||||
| 		if msg.ID == "" { | ||||
| 			return "", nil | ||||
| 		} | ||||
| 		// gitter has no delete message api | ||||
| 		// gitter has no delete message api so we edit message to "" | ||||
| 		_, err := b.c.UpdateMessage(roomID, msg.ID, "") | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		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 != "" { | ||||
| 		flog.Debugf("updating message with id %s", msg.ID) | ||||
| 		b.Log.Debugf("updating message with id %s", msg.ID) | ||||
| 		_, err := b.c.UpdateMessage(roomID, msg.ID, msg.Username+msg.Text) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		return "", nil | ||||
| 	} | ||||
|  | ||||
| 	// Post normal message | ||||
| 	resp, err := b.c.SendMessage(roomID, msg.Username+msg.Text) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| @@ -152,3 +159,20 @@ func (b *Bgitter) getAvatar(user string) string { | ||||
| 	} | ||||
| 	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 | ||||
| 		} | ||||
| 		_, err := b.c.SendMessage(roomID, msg.Username+msg.Text) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 	} | ||||
| 	return "", nil | ||||
| } | ||||
|   | ||||
| @@ -2,27 +2,101 @@ package helper | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| func DownloadFile(url string) (*[]byte, error) { | ||||
| 	return DownloadFileAuth(url, "") | ||||
| } | ||||
|  | ||||
| func DownloadFileAuth(url string, auth string) (*[]byte, error) { | ||||
| 	var buf bytes.Buffer | ||||
| 	client := &http.Client{ | ||||
| 		Timeout: time.Second * 5, | ||||
| 	} | ||||
| 	req, err := http.NewRequest("GET", url, nil) | ||||
| 	if auth != "" { | ||||
| 		req.Header.Add("Authorization", auth) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	resp, err := client.Do(req) | ||||
| 	if err != nil { | ||||
| 		resp.Body.Close() | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
| 	io.Copy(&buf, resp.Body) | ||||
| 	data := buf.Bytes() | ||||
| 	resp.Body.Close() | ||||
| 	return &data, nil | ||||
| } | ||||
|  | ||||
| func SplitStringLength(input string, length int) string { | ||||
| 	a := []rune(input) | ||||
| 	str := "" | ||||
| 	for i, r := range a { | ||||
| 		str = str + string(r) | ||||
| 		if i > 0 && (i+1)%length == 0 { | ||||
| 			str += "\n" | ||||
| 		} | ||||
| 	} | ||||
| 	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,8 +4,9 @@ import ( | ||||
| 	"bytes" | ||||
| 	"crypto/tls" | ||||
| 	"fmt" | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	"github.com/lrstanley/girc" | ||||
| 	"github.com/paulrosania/go-charset/charset" | ||||
| 	_ "github.com/paulrosania/go-charset/data" | ||||
| @@ -18,43 +19,41 @@ import ( | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 	"unicode/utf8" | ||||
| ) | ||||
|  | ||||
| type Birc struct { | ||||
| 	i               *girc.Client | ||||
| 	Nick            string | ||||
| 	names           map[string][]string | ||||
| 	Config          *config.Protocol | ||||
| 	Remote          chan config.Message | ||||
| 	connected       chan struct{} | ||||
| 	Local           chan config.Message // local queue for flood control | ||||
| 	Account         string | ||||
| 	FirstConnection bool | ||||
| 	i                                         *girc.Client | ||||
| 	Nick                                      string | ||||
| 	names                                     map[string][]string | ||||
| 	connected                                 chan struct{} | ||||
| 	Local                                     chan config.Message // local queue for flood control | ||||
| 	FirstConnection                           bool | ||||
| 	MessageDelay, MessageQueue, MessageLength int | ||||
|  | ||||
| 	*bridge.Config | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| var protocol = "irc" | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"module": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg config.Protocol, account string, c chan config.Message) *Birc { | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	b := &Birc{} | ||||
| 	b.Config = &cfg | ||||
| 	b.Nick = b.Config.Nick | ||||
| 	b.Remote = c | ||||
| 	b.Config = cfg | ||||
| 	b.Nick = b.GetString("Nick") | ||||
| 	b.names = make(map[string][]string) | ||||
| 	b.Account = account | ||||
| 	b.connected = make(chan struct{}) | ||||
| 	if b.Config.MessageDelay == 0 { | ||||
| 		b.Config.MessageDelay = 1300 | ||||
| 	if b.GetInt("MessageDelay") == 0 { | ||||
| 		b.MessageDelay = 1300 | ||||
| 	} else { | ||||
| 		b.MessageDelay = b.GetInt("MessageDelay") | ||||
| 	} | ||||
| 	if b.Config.MessageQueue == 0 { | ||||
| 		b.Config.MessageQueue = 30 | ||||
| 	if b.GetInt("MessageQueue") == 0 { | ||||
| 		b.MessageQueue = 30 | ||||
| 	} else { | ||||
| 		b.MessageQueue = b.GetInt("MessageQueue") | ||||
| 	} | ||||
| 	if b.Config.MessageLength == 0 { | ||||
| 		b.Config.MessageLength = 400 | ||||
| 	if b.GetInt("MessageLength") == 0 { | ||||
| 		b.MessageLength = 400 | ||||
| 	} else { | ||||
| 		b.MessageLength = b.GetInt("MessageLength") | ||||
| 	} | ||||
| 	b.FirstConnection = true | ||||
| 	return b | ||||
| @@ -71,9 +70,9 @@ func (b *Birc) Command(msg *config.Message) string { | ||||
| } | ||||
|  | ||||
| func (b *Birc) Connect() error { | ||||
| 	b.Local = make(chan config.Message, b.Config.MessageQueue+10) | ||||
| 	flog.Infof("Connecting %s", b.Config.Server) | ||||
| 	server, portstr, err := net.SplitHostPort(b.Config.Server) | ||||
| 	b.Local = make(chan config.Message, b.MessageQueue+10) | ||||
| 	b.Log.Infof("Connecting %s", b.GetString("Server")) | ||||
| 	server, portstr, err := net.SplitHostPort(b.GetString("Server")) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| @@ -82,7 +81,7 @@ func (b *Birc) Connect() error { | ||||
| 		return err | ||||
| 	} | ||||
| 	// fix strict user handling of girc | ||||
| 	user := b.Config.Nick | ||||
| 	user := b.GetString("Nick") | ||||
| 	for !girc.IsValidUser(user) { | ||||
| 		if len(user) == 1 { | ||||
| 			user = "matterbridge" | ||||
| @@ -93,28 +92,28 @@ func (b *Birc) Connect() error { | ||||
|  | ||||
| 	i := girc.New(girc.Config{ | ||||
| 		Server:     server, | ||||
| 		ServerPass: b.Config.Password, | ||||
| 		ServerPass: b.GetString("Password"), | ||||
| 		Port:       port, | ||||
| 		Nick:       b.Config.Nick, | ||||
| 		Nick:       b.GetString("Nick"), | ||||
| 		User:       user, | ||||
| 		Name:       b.Config.Nick, | ||||
| 		SSL:        b.Config.UseTLS, | ||||
| 		TLSConfig:  &tls.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, ServerName: server}, | ||||
| 		Name:       b.GetString("Nick"), | ||||
| 		SSL:        b.GetBool("UseTLS"), | ||||
| 		TLSConfig:  &tls.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), ServerName: server}, | ||||
| 		PingDelay:  time.Minute, | ||||
| 	}) | ||||
|  | ||||
| 	if b.Config.UseSASL { | ||||
| 		i.Config.SASL = &girc.SASLPlain{b.Config.NickServNick, b.Config.NickServPassword} | ||||
| 	if b.GetBool("UseSASL") { | ||||
| 		i.Config.SASL = &girc.SASLPlain{b.GetString("NickServNick"), b.GetString("NickServPassword")} | ||||
| 	} | ||||
|  | ||||
| 	i.Handlers.Add(girc.RPL_WELCOME, b.handleNewConnection) | ||||
| 	i.Handlers.Add(girc.RPL_ENDOFMOTD, b.handleOtherAuth) | ||||
| 	i.Handlers.Add("*", b.handleOther) | ||||
| 	i.Handlers.Add(girc.ALL_EVENTS, b.handleOther) | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			if err := i.Connect(); err != nil { | ||||
| 				flog.Errorf("error: %s", err) | ||||
| 				flog.Info("reconnecting in 30 seconds...") | ||||
| 				b.Log.Errorf("error: %s", err) | ||||
| 				b.Log.Info("reconnecting in 30 seconds...") | ||||
| 				time.Sleep(30 * time.Second) | ||||
| 				i.Handlers.Clear(girc.RPL_WELCOME) | ||||
| 				i.Handlers.Add(girc.RPL_WELCOME, func(client *girc.Client, event girc.Event) { | ||||
| @@ -130,25 +129,27 @@ func (b *Birc) Connect() error { | ||||
| 	b.i = i | ||||
| 	select { | ||||
| 	case <-b.connected: | ||||
| 		flog.Info("Connection succeeded") | ||||
| 		b.Log.Info("Connection succeeded") | ||||
| 	case <-time.After(time.Second * 30): | ||||
| 		return fmt.Errorf("connection timed out") | ||||
| 	} | ||||
| 	//i.Debug = false | ||||
| 	i.Handlers.Clear("*") | ||||
| 	if b.GetInt("DebugLevel") == 0 { | ||||
| 		i.Handlers.Clear(girc.ALL_EVENTS) | ||||
| 	} | ||||
| 	go b.doSend() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Birc) Disconnect() error { | ||||
| 	//b.i.Disconnect() | ||||
| 	b.i.Close() | ||||
| 	close(b.Local) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Birc) JoinChannel(channel config.ChannelInfo) error { | ||||
| 	if channel.Options.Key != "" { | ||||
| 		flog.Debugf("using key %s for channel %s", channel.Options.Key, channel.Name) | ||||
| 		b.Log.Debugf("using key %s for channel %s", channel.Options.Key, channel.Name) | ||||
| 		b.i.Cmd.JoinKey(channel.Name, channel.Options.Key) | ||||
| 	} else { | ||||
| 		b.i.Cmd.Join(channel.Name) | ||||
| @@ -161,16 +162,25 @@ func (b *Birc) Send(msg config.Message) (string, error) { | ||||
| 	if msg.Event == config.EVENT_MSG_DELETE { | ||||
| 		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, "!") { | ||||
| 		b.Command(&msg) | ||||
| 	} | ||||
|  | ||||
| 	if b.Config.Charset != "" { | ||||
| 	// convert to specified charset | ||||
| 	if b.GetString("Charset") != "" { | ||||
| 		buf := new(bytes.Buffer) | ||||
| 		w, err := charset.NewWriter(b.Config.Charset, buf) | ||||
| 		w, err := charset.NewWriter(b.GetString("Charset"), buf) | ||||
| 		if err != nil { | ||||
| 			flog.Errorf("charset from utf-8 conversion failed: %s", err) | ||||
| 			b.Log.Errorf("charset from utf-8 conversion failed: %s", err) | ||||
| 			return "", err | ||||
| 		} | ||||
| 		fmt.Fprintf(w, msg.Text) | ||||
| @@ -178,30 +188,59 @@ func (b *Birc) Send(msg config.Message) (string, error) { | ||||
| 		msg.Text = buf.String() | ||||
| 	} | ||||
|  | ||||
| 	for _, text := range strings.Split(msg.Text, "\n") { | ||||
| 		if len(text) > b.Config.MessageLength { | ||||
| 			text = text[:b.Config.MessageLength] + " <message clipped>" | ||||
| 	// Handle files | ||||
| 	if msg.Extra != nil { | ||||
| 		for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||
| 			b.Local <- rmsg | ||||
| 		} | ||||
| 		if len(b.Local) < b.Config.MessageQueue { | ||||
| 			if len(b.Local) == b.Config.MessageQueue-1 { | ||||
| 		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 | ||||
| 				} | ||||
| 				b.Local <- config.Message{Text: msg.Text, Username: msg.Username, Channel: msg.Channel, Event: msg.Event} | ||||
| 			} | ||||
| 			return "", nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// split long messages on messageLength, to avoid clipped messages #281 | ||||
| 	if b.GetBool("MessageSplit") { | ||||
| 		msg.Text = helper.SplitStringLength(msg.Text, b.MessageLength) | ||||
| 	} | ||||
| 	for _, text := range strings.Split(msg.Text, "\n") { | ||||
| 		if len(text) > b.MessageLength { | ||||
| 			text = text[:b.MessageLength-len(" <message clipped>")] | ||||
| 			if r, size := utf8.DecodeLastRuneInString(text); r == utf8.RuneError { | ||||
| 				text = text[:len(text)-size] | ||||
| 			} | ||||
| 			text += " <message clipped>" | ||||
| 		} | ||||
| 		if len(b.Local) < b.MessageQueue { | ||||
| 			if len(b.Local) == b.MessageQueue-1 { | ||||
| 				text = text + " <message clipped>" | ||||
| 			} | ||||
| 			b.Local <- config.Message{Text: text, Username: msg.Username, Channel: msg.Channel, Event: msg.Event} | ||||
| 		} else { | ||||
| 			flog.Debugf("flooding, dropping message (queue at %d)", len(b.Local)) | ||||
| 			b.Log.Debugf("flooding, dropping message (queue at %d)", len(b.Local)) | ||||
| 		} | ||||
| 	} | ||||
| 	return "", nil | ||||
| } | ||||
|  | ||||
| func (b *Birc) doSend() { | ||||
| 	rate := time.Millisecond * time.Duration(b.Config.MessageDelay) | ||||
| 	rate := time.Millisecond * time.Duration(b.MessageDelay) | ||||
| 	throttle := time.NewTicker(rate) | ||||
| 	for msg := range b.Local { | ||||
| 		<-throttle.C | ||||
| 		if msg.Event == config.EVENT_USER_ACTION { | ||||
| 			b.i.Cmd.Action(msg.Channel, msg.Username+msg.Text) | ||||
| 		} else { | ||||
| 			b.Log.Debugf("Sending to channel %s", msg.Channel) | ||||
| 			b.i.Cmd.Message(msg.Channel, msg.Username+msg.Text) | ||||
| 		} | ||||
| 	} | ||||
| @@ -226,7 +265,7 @@ func (b *Birc) endNames(client *girc.Client, event girc.Event) { | ||||
| } | ||||
|  | ||||
| func (b *Birc) handleNewConnection(client *girc.Client, event girc.Event) { | ||||
| 	flog.Debug("Registering callbacks") | ||||
| 	b.Log.Debug("Registering callbacks") | ||||
| 	i := b.i | ||||
| 	b.Nick = event.Params[0] | ||||
|  | ||||
| @@ -245,107 +284,132 @@ func (b *Birc) handleNewConnection(client *girc.Client, event girc.Event) { | ||||
|  | ||||
| func (b *Birc) handleJoinPart(client *girc.Client, event girc.Event) { | ||||
| 	if len(event.Params) == 0 { | ||||
| 		flog.Debugf("handleJoinPart: empty Params? %#v", event) | ||||
| 		b.Log.Debugf("handleJoinPart: empty Params? %#v", event) | ||||
| 		return | ||||
| 	} | ||||
| 	channel := event.Params[0] | ||||
| 	channel := strings.ToLower(event.Params[0]) | ||||
| 	if event.Command == "KICK" { | ||||
| 		flog.Infof("Got kicked from %s by %s", channel, event.Source.Name) | ||||
| 		b.Log.Infof("Got kicked from %s by %s", channel, event.Source.Name) | ||||
| 		time.Sleep(time.Duration(b.GetInt("RejoinDelay")) * time.Second) | ||||
| 		b.Remote <- config.Message{Username: "system", Text: "rejoin", Channel: channel, Account: b.Account, Event: config.EVENT_REJOIN_CHANNELS} | ||||
| 		return | ||||
| 	} | ||||
| 	if event.Command == "QUIT" { | ||||
| 		if event.Source.Name == b.Nick && strings.Contains(event.Trailing, "Ping timeout") { | ||||
| 			flog.Infof("%s reconnecting ..", b.Account) | ||||
| 			b.Log.Infof("%s reconnecting ..", b.Account) | ||||
| 			b.Remote <- config.Message{Username: "system", Text: "reconnect", Channel: channel, Account: b.Account, Event: config.EVENT_FAILURE} | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	if event.Source.Name != b.Nick { | ||||
| 		flog.Debugf("Sending JOIN_LEAVE event from %s to gateway", b.Account) | ||||
| 		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} | ||||
| 		if b.GetBool("nosendjoinpart") { | ||||
| 			return | ||||
| 		} | ||||
| 		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 | ||||
| 	} | ||||
| 	flog.Debugf("handle %#v", event) | ||||
| 	b.Log.Debugf("handle %#v", event) | ||||
| } | ||||
|  | ||||
| func (b *Birc) handleNotice(client *girc.Client, event girc.Event) { | ||||
| 	if strings.Contains(event.String(), "This nickname is registered") && event.Source.Name == b.Config.NickServNick { | ||||
| 		b.i.Cmd.Message(b.Config.NickServNick, "IDENTIFY "+b.Config.NickServPassword) | ||||
| 	if strings.Contains(event.String(), "This nickname is registered") && event.Source.Name == b.GetString("NickServNick") { | ||||
| 		b.i.Cmd.Message(b.GetString("NickServNick"), "IDENTIFY "+b.GetString("NickServPassword")) | ||||
| 	} else { | ||||
| 		b.handlePrivMsg(client, 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 { | ||||
| 	case "372", "375", "376", "250", "251", "252", "253", "254", "255", "265", "266", "002", "003", "004", "005": | ||||
| 		return | ||||
| 	} | ||||
| 	flog.Debugf("%#v", event.String()) | ||||
| 	b.Log.Debugf("%#v", event.String()) | ||||
| } | ||||
|  | ||||
| func (b *Birc) handleOtherAuth(client *girc.Client, event girc.Event) { | ||||
| 	if strings.EqualFold(b.Config.NickServNick, "Q@CServe.quakenet.org") { | ||||
| 		flog.Debugf("Authenticating %s against %s", b.Config.NickServUsername, b.Config.NickServNick) | ||||
| 		b.i.Cmd.Message(b.Config.NickServNick, "AUTH "+b.Config.NickServUsername+" "+b.Config.NickServPassword) | ||||
| 	if strings.EqualFold(b.GetString("NickServNick"), "Q@CServe.quakenet.org") { | ||||
| 		b.Log.Debugf("Authenticating %s against %s", b.GetString("NickServUsername"), b.GetString("NickServNick")) | ||||
| 		b.i.Cmd.Message(b.GetString("NickServNick"), "AUTH "+b.GetString("NickServUsername")+" "+b.GetString("NickServPassword")) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) { | ||||
| 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 | ||||
| 		return true | ||||
| 	} | ||||
| 	// don't forward queries to the bot | ||||
| 	if event.Params[0] == b.Nick { | ||||
| 		return | ||||
| 		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) { | ||||
| 	if b.skipPrivMsg(event) { | ||||
| 		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.Command == "CTCP_ACTION" { | ||||
| 		//	msg = event.Source.Name + " " | ||||
| 	rmsg := config.Message{Username: event.Source.Name, Channel: strings.ToLower(event.Params[0]), Account: b.Account, UserID: event.Source.Ident + "@" + event.Source.Host} | ||||
| 	b.Log.Debugf("== Receiving PRIVMSG: %s %s %#v", event.Source.Name, event.Trailing, event) | ||||
|  | ||||
| 	// set action event | ||||
| 	if event.IsAction() { | ||||
| 		rmsg.Event = config.EVENT_USER_ACTION | ||||
| 	} | ||||
| 	msg += event.Trailing | ||||
|  | ||||
| 	// strip action, we made an event if it was an action | ||||
| 	rmsg.Text += event.StripAction() | ||||
|  | ||||
| 	// strip IRC colors | ||||
| 	re := regexp.MustCompile(`[[:cntrl:]](?:\d{1,2}(?:,\d{1,2})?)?`) | ||||
| 	msg = re.ReplaceAllString(msg, "") | ||||
| 	rmsg.Text = re.ReplaceAllString(rmsg.Text, "") | ||||
|  | ||||
| 	// start detecting the charset | ||||
| 	var r io.Reader | ||||
| 	var err error | ||||
| 	mycharset := b.Config.Charset | ||||
| 	mycharset := b.GetString("Charset") | ||||
| 	if mycharset == "" { | ||||
| 		// detect what were sending so that we convert it to utf-8 | ||||
| 		detector := chardet.NewTextDetector() | ||||
| 		result, err := detector.DetectBest([]byte(msg)) | ||||
| 		result, err := detector.DetectBest([]byte(rmsg.Text)) | ||||
| 		if err != nil { | ||||
| 			flog.Infof("detection failed for msg: %#v", msg) | ||||
| 			b.Log.Infof("detection failed for rmsg.Text: %#v", rmsg.Text) | ||||
| 			return | ||||
| 		} | ||||
| 		flog.Debugf("detected %s confidence %#v", result.Charset, result.Confidence) | ||||
| 		b.Log.Debugf("detected %s confidence %#v", result.Charset, result.Confidence) | ||||
| 		mycharset = result.Charset | ||||
| 		// if we're not sure, just pick ISO-8859-1 | ||||
| 		if result.Confidence < 80 { | ||||
| 			mycharset = "ISO-8859-1" | ||||
| 		} | ||||
| 	} | ||||
| 	r, err = charset.NewReader(mycharset, strings.NewReader(msg)) | ||||
| 	r, err = charset.NewReader(mycharset, strings.NewReader(rmsg.Text)) | ||||
| 	if err != nil { | ||||
| 		flog.Errorf("charset to utf-8 conversion failed: %s", err) | ||||
| 		b.Log.Errorf("charset to utf-8 conversion failed: %s", err) | ||||
| 		return | ||||
| 	} | ||||
| 	output, _ := ioutil.ReadAll(r) | ||||
| 	msg = string(output) | ||||
| 	rmsg.Text = string(output) | ||||
|  | ||||
| 	flog.Debugf("Sending message from %s on %s to gateway", event.Params[0], b.Account) | ||||
| 	rmsg.Text = msg | ||||
| 	b.Log.Debugf("<= Sending message from %s on %s to gateway", event.Params[0], b.Account) | ||||
| 	b.Remote <- rmsg | ||||
| } | ||||
|  | ||||
| @@ -353,13 +417,13 @@ func (b *Birc) handleTopicWhoTime(client *girc.Client, event girc.Event) { | ||||
| 	parts := strings.Split(event.Params[2], "!") | ||||
| 	t, err := strconv.ParseInt(event.Params[3], 10, 64) | ||||
| 	if err != nil { | ||||
| 		flog.Errorf("Invalid time stamp: %s", event.Params[3]) | ||||
| 		b.Log.Errorf("Invalid time stamp: %s", event.Params[3]) | ||||
| 	} | ||||
| 	user := parts[0] | ||||
| 	if len(parts) > 1 { | ||||
| 		user += " [" + parts[1] + "]" | ||||
| 	} | ||||
| 	flog.Debugf("%s: Topic set by %s [%s]", event.Command, user, time.Unix(t, 0)) | ||||
| 	b.Log.Debugf("%s: Topic set by %s [%s]", event.Command, user, time.Unix(t, 0)) | ||||
| } | ||||
|  | ||||
| func (b *Birc) nicksPerRow() int { | ||||
|   | ||||
| @@ -1,60 +1,50 @@ | ||||
| package bmatrix | ||||
|  | ||||
| import ( | ||||
| 	"regexp" | ||||
| 	"sync" | ||||
|  | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| 	matrix "github.com/matrix-org/gomatrix" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	matrix "github.com/matterbridge/gomatrix" | ||||
| 	"mime" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| ) | ||||
|  | ||||
| type Bmatrix struct { | ||||
| 	mc      *matrix.Client | ||||
| 	Config  *config.Protocol | ||||
| 	Remote  chan config.Message | ||||
| 	Account string | ||||
| 	UserID  string | ||||
| 	RoomMap map[string]string | ||||
| 	sync.RWMutex | ||||
| 	*bridge.Config | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| var protocol = "matrix" | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"module": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg config.Protocol, account string, c chan config.Message) *Bmatrix { | ||||
| 	b := &Bmatrix{} | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	b := &Bmatrix{Config: cfg} | ||||
| 	b.RoomMap = make(map[string]string) | ||||
| 	b.Config = &cfg | ||||
| 	b.Account = account | ||||
| 	b.Remote = c | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func (b *Bmatrix) Connect() error { | ||||
| 	var err error | ||||
| 	flog.Infof("Connecting %s", b.Config.Server) | ||||
| 	b.mc, err = matrix.NewClient(b.Config.Server, "", "") | ||||
| 	b.Log.Infof("Connecting %s", b.GetString("Server")) | ||||
| 	b.mc, err = matrix.NewClient(b.GetString("Server"), "", "") | ||||
| 	if err != nil { | ||||
| 		flog.Debugf("%#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	resp, err := b.mc.Login(&matrix.ReqLogin{ | ||||
| 		Type:     "m.login.password", | ||||
| 		User:     b.Config.Login, | ||||
| 		Password: b.Config.Password, | ||||
| 		User:     b.GetString("Login"), | ||||
| 		Password: b.GetString("Password"), | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		flog.Debugf("%#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	b.mc.SetCredentials(resp.UserID, resp.AccessToken) | ||||
| 	b.UserID = resp.UserID | ||||
| 	flog.Info("Connection succeeded") | ||||
| 	b.Log.Info("Connection succeeded") | ||||
| 	go b.handlematrix() | ||||
| 	return nil | ||||
| } | ||||
| @@ -75,20 +65,53 @@ func (b *Bmatrix) JoinChannel(channel config.ChannelInfo) error { | ||||
| } | ||||
|  | ||||
| func (b *Bmatrix) Send(msg config.Message) (string, error) { | ||||
| 	flog.Debugf("Receiving %#v", msg) | ||||
| 	// ignore delete messages | ||||
| 	if msg.Event == config.EVENT_MSG_DELETE { | ||||
| 		return "", nil | ||||
| 	} | ||||
| 	b.Log.Debugf("=> Receiving %#v", msg) | ||||
|  | ||||
| 	channel := b.getRoomID(msg.Channel) | ||||
| 	flog.Debugf("Sending to channel %s", 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 { | ||||
| 		b.mc.SendMessageEvent(channel, "m.room.message", | ||||
| 		resp, err := b.mc.SendMessageEvent(channel, "m.room.message", | ||||
| 			matrix.TextMessage{"m.emote", msg.Username + msg.Text}) | ||||
| 		return "", nil | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		return resp.EventID, err | ||||
| 	} | ||||
| 	b.mc.SendText(channel, msg.Username+msg.Text) | ||||
| 	return "", nil | ||||
|  | ||||
| 	// Delete message | ||||
| 	if msg.Event == config.EVENT_MSG_DELETE { | ||||
| 		if msg.ID == "" { | ||||
| 			return "", nil | ||||
| 		} | ||||
| 		resp, err := b.mc.RedactEvent(channel, msg.ID, &matrix.ReqRedact{}) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		return resp.EventID, err | ||||
| 	} | ||||
|  | ||||
| 	// Upload a file if it exists | ||||
| 	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) | ||||
| 		if len(msg.Extra["file"]) > 0 { | ||||
| 			return b.handleUploadFile(&msg, channel) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Edit message if we have an ID | ||||
| 	// matrix has no editing support | ||||
|  | ||||
| 	// 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 { | ||||
| @@ -101,37 +124,188 @@ func (b *Bmatrix) getRoomID(channel string) string { | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (b *Bmatrix) handlematrix() error { | ||||
| 	syncer := b.mc.Syncer.(*matrix.DefaultSyncer) | ||||
| 	syncer.OnEventType("m.room.message", func(ev *matrix.Event) { | ||||
| 		if (ev.Content["msgtype"].(string) == "m.text" || ev.Content["msgtype"].(string) == "m.notice" || ev.Content["msgtype"].(string) == "m.emote") && 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 | ||||
| 			} | ||||
| 			flog.Debugf("Sending message from %s on %s to gateway", ev.Sender, b.Account) | ||||
| 			b.Remote <- rmsg | ||||
| 		} | ||||
| 		flog.Debugf("Received: %#v", ev) | ||||
| 	}) | ||||
| 	syncer.OnEventType("m.room.redaction", b.handleEvent) | ||||
| 	syncer.OnEventType("m.room.message", b.handleEvent) | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			if err := b.mc.Sync(); err != nil { | ||||
| 				flog.Println("Sync() returned ", err) | ||||
| 				b.Log.Println("Sync() returned ", err) | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
| 	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,54 +3,24 @@ package bmattermost | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	"github.com/42wim/matterbridge/matterclient" | ||||
| 	"github.com/42wim/matterbridge/matterhook" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| type MMhook struct { | ||||
| 	mh *matterhook.Client | ||||
| } | ||||
|  | ||||
| type MMapi struct { | ||||
| 	mc    *matterclient.MMClient | ||||
| 	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 | ||||
| 	mh     *matterhook.Client | ||||
| 	mc     *matterclient.MMClient | ||||
| 	TeamID string | ||||
| 	*bridge.Config | ||||
| 	avatarMap map[string]string | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| var protocol = "mattermost" | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"module": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg config.Protocol, account string, c chan config.Message) *Bmattermost { | ||||
| 	b := &Bmattermost{} | ||||
| 	b.Config = &cfg | ||||
| 	b.Remote = c | ||||
| 	b.Account = account | ||||
| 	b.mmMap = make(map[string]string) | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	b := &Bmattermost{Config: cfg, avatarMap: make(map[string]string)} | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| @@ -59,47 +29,47 @@ func (b *Bmattermost) Command(cmd string) string { | ||||
| } | ||||
|  | ||||
| func (b *Bmattermost) Connect() error { | ||||
| 	if b.Config.WebhookBindAddress != "" { | ||||
| 		if b.Config.WebhookURL != "" { | ||||
| 			flog.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)") | ||||
| 			b.mh = matterhook.New(b.Config.WebhookURL, | ||||
| 				matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, | ||||
| 					BindAddress: b.Config.WebhookBindAddress}) | ||||
| 		} else if b.Config.Token != "" { | ||||
| 			flog.Info("Connecting using token (sending)") | ||||
| 	if b.GetString("WebhookBindAddress") != "" { | ||||
| 		if b.GetString("WebhookURL") != "" { | ||||
| 			b.Log.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)") | ||||
| 			b.mh = matterhook.New(b.GetString("WebhookURL"), | ||||
| 				matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), | ||||
| 					BindAddress: b.GetString("WebhookBindAddress")}) | ||||
| 		} else if b.GetString("Token") != "" { | ||||
| 			b.Log.Info("Connecting using token (sending)") | ||||
| 			err := b.apiLogin() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} else if b.Config.Login != "" { | ||||
| 			flog.Info("Connecting using login/password (sending)") | ||||
| 		} else if b.GetString("Login") != "" { | ||||
| 			b.Log.Info("Connecting using login/password (sending)") | ||||
| 			err := b.apiLogin() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} else { | ||||
| 			flog.Info("Connecting using webhookbindaddress (receiving)") | ||||
| 			b.mh = matterhook.New(b.Config.WebhookURL, | ||||
| 				matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, | ||||
| 					BindAddress: b.Config.WebhookBindAddress}) | ||||
| 			b.Log.Info("Connecting using webhookbindaddress (receiving)") | ||||
| 			b.mh = matterhook.New(b.GetString("WebhookURL"), | ||||
| 				matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), | ||||
| 					BindAddress: b.GetString("WebhookBindAddress")}) | ||||
| 		} | ||||
| 		go b.handleMatter() | ||||
| 		return nil | ||||
| 	} | ||||
| 	if b.Config.WebhookURL != "" { | ||||
| 		flog.Info("Connecting using webhookurl (sending)") | ||||
| 		b.mh = matterhook.New(b.Config.WebhookURL, | ||||
| 			matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, | ||||
| 	if b.GetString("WebhookURL") != "" { | ||||
| 		b.Log.Info("Connecting using webhookurl (sending)") | ||||
| 		b.mh = matterhook.New(b.GetString("WebhookURL"), | ||||
| 			matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), | ||||
| 				DisableServer: true}) | ||||
| 		if b.Config.Token != "" { | ||||
| 			flog.Info("Connecting using token (receiving)") | ||||
| 		if b.GetString("Token") != "" { | ||||
| 			b.Log.Info("Connecting using token (receiving)") | ||||
| 			err := b.apiLogin() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			go b.handleMatter() | ||||
| 		} else if b.Config.Login != "" { | ||||
| 			flog.Info("Connecting using login/password (receiving)") | ||||
| 		} else if b.GetString("Login") != "" { | ||||
| 			b.Log.Info("Connecting using login/password (receiving)") | ||||
| 			err := b.apiLogin() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| @@ -107,23 +77,23 @@ func (b *Bmattermost) Connect() error { | ||||
| 			go b.handleMatter() | ||||
| 		} | ||||
| 		return nil | ||||
| 	} else if b.Config.Token != "" { | ||||
| 		flog.Info("Connecting using token (sending and receiving)") | ||||
| 	} else if b.GetString("Token") != "" { | ||||
| 		b.Log.Info("Connecting using token (sending and receiving)") | ||||
| 		err := b.apiLogin() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		go b.handleMatter() | ||||
| 	} else if b.Config.Login != "" { | ||||
| 		flog.Info("Connecting using login/password (sending and receiving)") | ||||
| 	} else if b.GetString("Login") != "" { | ||||
| 		b.Log.Info("Connecting using login/password (sending and receiving)") | ||||
| 		err := b.apiLogin() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		go b.handleMatter() | ||||
| 	} | ||||
| 	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.") | ||||
| 	if b.GetString("WebhookBindAddress") == "" && b.GetString("WebhookURL") == "" && b.GetString("Login") == "" && b.GetString("Token") == "" { | ||||
| 		return errors.New("no connection method found. See that you have WebhookBindAddress, WebhookURL or Token/Login/Password/Server/Team configured") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| @@ -134,7 +104,7 @@ func (b *Bmattermost) Disconnect() error { | ||||
|  | ||||
| func (b *Bmattermost) JoinChannel(channel config.ChannelInfo) error { | ||||
| 	// we can only join channels using the API | ||||
| 	if b.Config.WebhookURL == "" && b.Config.WebhookBindAddress == "" { | ||||
| 	if b.GetString("WebhookURL") == "" && b.GetString("WebhookBindAddress") == "" { | ||||
| 		id := b.mc.GetChannelId(channel.Name, "") | ||||
| 		if id == "" { | ||||
| 			return fmt.Errorf("Could not find channel ID for channel %s", channel.Name) | ||||
| @@ -145,189 +115,330 @@ func (b *Bmattermost) JoinChannel(channel config.ChannelInfo) error { | ||||
| } | ||||
|  | ||||
| func (b *Bmattermost) Send(msg config.Message) (string, error) { | ||||
| 	flog.Debugf("Receiving %#v", msg) | ||||
| 	b.Log.Debugf("=> Receiving %#v", msg) | ||||
|  | ||||
| 	// Make a action /me of the message | ||||
| 	if msg.Event == config.EVENT_USER_ACTION { | ||||
| 		msg.Text = "*" + msg.Text + "*" | ||||
| 	} | ||||
| 	nick := msg.Username | ||||
| 	message := msg.Text | ||||
| 	channel := msg.Channel | ||||
|  | ||||
| 	if b.Config.PrefixMessagesWithNick { | ||||
| 		message = nick + message | ||||
| 	// map the file SHA to our user (caches the avatar) | ||||
| 	if msg.Event == config.EVENT_AVATAR_DOWNLOAD { | ||||
| 		return b.cacheAvatar(&msg) | ||||
| 	} | ||||
| 	if b.Config.WebhookURL != "" { | ||||
| 		matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL} | ||||
| 		matterMessage.IconURL = msg.Avatar | ||||
| 		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 | ||||
|  | ||||
| 	// Use webhook to send the message | ||||
| 	if b.GetString("WebhookURL") != "" { | ||||
| 		return b.sendWebhook(msg) | ||||
| 	} | ||||
|  | ||||
| 	// Delete message | ||||
| 	if msg.Event == config.EVENT_MSG_DELETE { | ||||
| 		if msg.ID == "" { | ||||
| 			return "", nil | ||||
| 		} | ||||
| 		return msg.ID, b.mc.DeleteMessage(msg.ID) | ||||
| 	} | ||||
|  | ||||
| 	// Upload a file if it exists | ||||
| 	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 { | ||||
| 			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 | ||||
| 			return b.handleUploadFile(&msg) | ||||
| 		} | ||||
| 	} | ||||
| 	if msg.ID != "" { | ||||
| 		return b.mc.EditMessage(msg.ID, message) | ||||
|  | ||||
| 	// Prepend nick if configured | ||||
| 	if b.GetBool("PrefixMessagesWithNick") { | ||||
| 		msg.Text = msg.Username + msg.Text | ||||
| 	} | ||||
| 	return b.mc.PostMessage(b.mc.GetChannelId(channel, ""), message) | ||||
|  | ||||
| 	// Edit message if we have an ID | ||||
| 	if msg.ID != "" { | ||||
| 		return b.mc.EditMessage(msg.ID, msg.Text) | ||||
| 	} | ||||
|  | ||||
| 	// Post normal message | ||||
| 	return b.mc.PostMessage(b.mc.GetChannelId(msg.Channel, ""), msg.Text) | ||||
| } | ||||
|  | ||||
| func (b *Bmattermost) handleMatter() { | ||||
| 	mchan := make(chan *MMMessage) | ||||
| 	if b.Config.WebhookBindAddress != "" { | ||||
| 		flog.Debugf("Choosing webhooks based receiving") | ||||
| 		go b.handleMatterHook(mchan) | ||||
| 	messages := make(chan *config.Message) | ||||
| 	if b.GetString("WebhookBindAddress") != "" { | ||||
| 		b.Log.Debugf("Choosing webhooks based receiving") | ||||
| 		go b.handleMatterHook(messages) | ||||
| 	} else { | ||||
| 		if b.Config.Token != "" { | ||||
| 			flog.Debugf("Choosing token based receiving") | ||||
| 		if b.GetString("Token") != "" { | ||||
| 			b.Log.Debugf("Choosing token based receiving") | ||||
| 		} else { | ||||
| 			flog.Debugf("Choosing login/password based receiving") | ||||
| 			b.Log.Debugf("Choosing login/password based receiving") | ||||
| 		} | ||||
| 		go b.handleMatterClient(mchan) | ||||
| 		go b.handleMatterClient(messages) | ||||
| 	} | ||||
| 	for message := range mchan { | ||||
| 		rmsg := config.Message{Username: message.Username, Channel: message.Channel, Account: b.Account, UserID: message.UserID, ID: message.ID, Event: message.Event, Extra: message.Extra} | ||||
| 		text, ok := b.replaceAction(message.Text) | ||||
| 	var ok bool | ||||
| 	for message := range messages { | ||||
| 		message.Avatar = helper.GetAvatar(b.avatarMap, message.UserID, b.General) | ||||
| 		message.Account = b.Account | ||||
| 		message.Text, ok = b.replaceAction(message.Text) | ||||
| 		if ok { | ||||
| 			rmsg.Event = config.EVENT_USER_ACTION | ||||
| 			message.Event = config.EVENT_USER_ACTION | ||||
| 		} | ||||
| 		rmsg.Text = text | ||||
| 		flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.Account) | ||||
| 		flog.Debugf("Message is %#v", rmsg) | ||||
| 		b.Remote <- rmsg | ||||
| 		b.Log.Debugf("<= Sending message from %s on %s to gateway", message.Username, b.Account) | ||||
| 		b.Log.Debugf("<= Message is %#v", message) | ||||
| 		b.Remote <- *message | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bmattermost) handleMatterClient(mchan chan *MMMessage) { | ||||
| func (b *Bmattermost) handleMatterClient(messages chan *config.Message) { | ||||
| 	for message := range b.mc.MessageChan { | ||||
| 		flog.Debugf("%#v", message.Raw.Data) | ||||
| 		if message.Type == "system_join_leave" || | ||||
| 			message.Type == "system_join_channel" || | ||||
| 			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 { | ||||
| 		b.Log.Debugf("%#v", message.Raw.Data) | ||||
|  | ||||
| 		if b.skipMessage(message) { | ||||
| 			b.Log.Debugf("Skipped message: %#v", message) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		m := &MMMessage{Extra: make(map[string][]interface{})} | ||||
| 		// only download avatars if we have a place to upload them (configured mediaserver) | ||||
| 		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 | ||||
| 		if props != nil { | ||||
| 			if _, ok := props["matterbridge"].(bool); ok { | ||||
| 				flog.Debugf("sent by matterbridge, ignoring") | ||||
| 				continue | ||||
| 			} | ||||
| 			if _, ok := props["override_username"].(string); ok { | ||||
| 				message.Username = props["override_username"].(string) | ||||
| 				rmsg.Username = props["override_username"].(string) | ||||
| 			} | ||||
| 			if _, ok := props["attachments"].([]interface{}); ok { | ||||
| 				m.Extra["attachments"] = props["attachments"].([]interface{}) | ||||
| 				rmsg.Extra["attachments"] = props["attachments"].([]interface{}) | ||||
| 			} | ||||
| 		} | ||||
| 		// do not post our own messages back to irc | ||||
| 		// only listen to message from our team | ||||
| 		if (message.Raw.Event == "posted" || message.Raw.Event == "post_edited" || message.Raw.Event == "post_deleted") && | ||||
| 			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 { | ||||
| 				continue | ||||
| 			} | ||||
| 			flog.Debugf("Receiving from matterclient %#v", message) | ||||
| 			m.UserID = message.UserID | ||||
| 			m.Username = message.Username | ||||
| 			m.Channel = message.Channel | ||||
| 			m.Text = message.Text | ||||
| 			m.ID = message.Post.Id | ||||
| 			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 | ||||
|  | ||||
| 		// create a text for bridges that don't support native editing | ||||
| 		if message.Raw.Event == "post_edited" && !b.GetBool("EditDisable") { | ||||
| 			rmsg.Text = message.Text + b.GetString("EditSuffix") | ||||
| 		} | ||||
|  | ||||
| 		if message.Raw.Event == "post_deleted" { | ||||
| 			rmsg.Event = config.EVENT_MSG_DELETE | ||||
| 		} | ||||
|  | ||||
| 		if len(message.Post.FileIds) > 0 { | ||||
| 			for _, id := range message.Post.FileIds { | ||||
| 				err := b.handleDownloadFile(rmsg, id) | ||||
| 				if err != nil { | ||||
| 					b.Log.Errorf("download failed: %s", err) | ||||
| 				} | ||||
| 			} | ||||
| 			mchan <- m | ||||
| 		} | ||||
| 		messages <- rmsg | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bmattermost) handleMatterHook(mchan chan *MMMessage) { | ||||
| func (b *Bmattermost) handleMatterHook(messages chan *config.Message) { | ||||
| 	for { | ||||
| 		message := b.mh.Receive() | ||||
| 		flog.Debugf("Receiving from matterhook %#v", message) | ||||
| 		m := &MMMessage{} | ||||
| 		m.UserID = message.UserID | ||||
| 		m.Username = message.UserName | ||||
| 		m.Text = message.Text | ||||
| 		m.Channel = message.ChannelName | ||||
| 		mchan <- m | ||||
| 		b.Log.Debugf("Receiving from matterhook %#v", message) | ||||
| 		messages <- &config.Message{UserID: message.UserID, Username: message.UserName, Text: message.Text, Channel: message.ChannelName} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bmattermost) apiLogin() error { | ||||
| 	password := b.Config.Password | ||||
| 	if b.Config.Token != "" { | ||||
| 		password = "MMAUTHTOKEN=" + b.Config.Token | ||||
| 	password := b.GetString("Password") | ||||
| 	if b.GetString("Token") != "" { | ||||
| 		password = "MMAUTHTOKEN=" + b.GetString("Token") | ||||
| 	} | ||||
|  | ||||
| 	b.mc = matterclient.New(b.Config.Login, password, | ||||
| 		b.Config.Team, b.Config.Server) | ||||
| 	b.mc.SkipTLSVerify = b.Config.SkipTLSVerify | ||||
| 	b.mc.NoTLS = b.Config.NoTLS | ||||
| 	flog.Infof("Connecting %s (team: %s) on %s", b.Config.Login, b.Config.Team, b.Config.Server) | ||||
| 	b.mc = matterclient.New(b.GetString("Login"), password, b.GetString("Team"), b.GetString("Server")) | ||||
| 	if b.GetBool("debug") { | ||||
| 		b.mc.SetLogLevel("debug") | ||||
| 	} | ||||
| 	b.mc.SkipTLSVerify = b.GetBool("SkipTLSVerify") | ||||
| 	b.mc.NoTLS = b.GetBool("NoTLS") | ||||
| 	b.Log.Infof("Connecting %s (team: %s) on %s", b.GetString("Login"), b.GetString("Team"), b.GetString("Server")) | ||||
| 	err := b.mc.Login() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	flog.Info("Connection succeeded") | ||||
| 	b.TeamId = b.mc.GetTeamId() | ||||
| 	b.Log.Info("Connection succeeded") | ||||
| 	b.TeamID = b.mc.GetTeamId() | ||||
| 	go b.mc.WsReceiver() | ||||
| 	go b.mc.StatusLoop() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // replaceAction replace the message with the correct action (/me) code | ||||
| func (b *Bmattermost) replaceAction(text string) (string, bool) { | ||||
| 	if strings.HasPrefix(text, "*") && strings.HasSuffix(text, "*") { | ||||
| 		return strings.Replace(text, "*", "", -1), true | ||||
| 	} | ||||
| 	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) { | ||||
| 			matterMessage := matterhook.OMessage{IconURL: b.GetString("IconURL"), Channel: rmsg.Channel, UserName: rmsg.Username, Text: rmsg.Text, Props: make(map[string]interface{})} | ||||
| 			matterMessage.Props["matterbridge_"+b.mc.User.Id] = 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 | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	matterMessage := matterhook.OMessage{IconURL: b.GetString("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.mc.User.Id] = 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.mc.User.Id].(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,10 +1,11 @@ | ||||
| package brocketchat | ||||
|  | ||||
| import ( | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	"github.com/42wim/matterbridge/hook/rockethook" | ||||
| 	"github.com/42wim/matterbridge/matterhook" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| type MMhook struct { | ||||
| @@ -14,24 +15,11 @@ type MMhook struct { | ||||
|  | ||||
| type Brocketchat struct { | ||||
| 	MMhook | ||||
| 	Config  *config.Protocol | ||||
| 	Remote  chan config.Message | ||||
| 	Account string | ||||
| 	*bridge.Config | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| var protocol = "rocketchat" | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"module": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg config.Protocol, account string, c chan config.Message) *Brocketchat { | ||||
| 	b := &Brocketchat{} | ||||
| 	b.Config = &cfg | ||||
| 	b.Remote = c | ||||
| 	b.Account = account | ||||
| 	return b | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	return &Brocketchat{Config: cfg} | ||||
| } | ||||
|  | ||||
| func (b *Brocketchat) Command(cmd string) string { | ||||
| @@ -39,11 +27,11 @@ func (b *Brocketchat) Command(cmd string) string { | ||||
| } | ||||
|  | ||||
| func (b *Brocketchat) Connect() error { | ||||
| 	flog.Info("Connecting webhooks") | ||||
| 	b.mh = matterhook.New(b.Config.WebhookURL, | ||||
| 		matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, | ||||
| 	b.Log.Info("Connecting webhooks") | ||||
| 	b.mh = matterhook.New(b.GetString("WebhookURL"), | ||||
| 		matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), | ||||
| 			DisableServer: true}) | ||||
| 	b.rh = rockethook.New(b.Config.WebhookURL, rockethook.Config{BindAddress: b.Config.WebhookBindAddress}) | ||||
| 	b.rh = rockethook.New(b.GetString("WebhookURL"), rockethook.Config{BindAddress: b.GetString("WebhookBindAddress")}) | ||||
| 	go b.handleRocketHook() | ||||
| 	return nil | ||||
| } | ||||
| @@ -62,15 +50,30 @@ func (b *Brocketchat) Send(msg config.Message) (string, error) { | ||||
| 	if msg.Event == config.EVENT_MSG_DELETE { | ||||
| 		return "", nil | ||||
| 	} | ||||
| 	flog.Debugf("Receiving %#v", msg) | ||||
| 	matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL} | ||||
| 	b.Log.Debugf("=> Receiving %#v", msg) | ||||
| 	if msg.Extra != nil { | ||||
| 		for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||
| 			matterMessage := matterhook.OMessage{IconURL: b.GetString("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 | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	matterMessage := matterhook.OMessage{IconURL: b.GetString("IconURL")} | ||||
| 	matterMessage.Channel = msg.Channel | ||||
| 	matterMessage.UserName = msg.Username | ||||
| 	matterMessage.Type = "" | ||||
| 	matterMessage.Text = msg.Text | ||||
| 	err := b.mh.Send(matterMessage) | ||||
| 	if err != nil { | ||||
| 		flog.Info(err) | ||||
| 		b.Log.Info(err) | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return "", nil | ||||
| @@ -79,12 +82,12 @@ func (b *Brocketchat) Send(msg config.Message) (string, error) { | ||||
| func (b *Brocketchat) handleRocketHook() { | ||||
| 	for { | ||||
| 		message := b.rh.Receive() | ||||
| 		flog.Debugf("Receiving from rockethook %#v", message) | ||||
| 		b.Log.Debugf("Receiving from rockethook %#v", message) | ||||
| 		// do not loop | ||||
| 		if message.UserName == b.Config.Nick { | ||||
| 		if message.UserName == b.GetString("Nick") { | ||||
| 			continue | ||||
| 		} | ||||
| 		flog.Debugf("Sending message from %s on %s to gateway", message.UserName, b.Account) | ||||
| 		b.Log.Debugf("<= Sending message from %s on %s to gateway", message.UserName, b.Account) | ||||
| 		b.Remote <- config.Message{Text: message.Text, Username: message.UserName, Channel: message.ChannelName, Account: b.Account, UserID: message.UserID} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -4,52 +4,35 @@ import ( | ||||
| 	"bytes" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/matterhook" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| 	"github.com/matterbridge/slack" | ||||
| 	"html" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"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" | ||||
| ) | ||||
|  | ||||
| type MMMessage struct { | ||||
| 	Text     string | ||||
| 	Channel  string | ||||
| 	Username string | ||||
| 	UserID   string | ||||
| 	Raw      *slack.MessageEvent | ||||
| } | ||||
|  | ||||
| type Bslack struct { | ||||
| 	mh       *matterhook.Client | ||||
| 	sc       *slack.Client | ||||
| 	Config   *config.Protocol | ||||
| 	rtm      *slack.RTM | ||||
| 	Plus     bool | ||||
| 	Remote   chan config.Message | ||||
| 	Users    []slack.User | ||||
| 	Account  string | ||||
| 	si       *slack.Info | ||||
| 	channels []slack.Channel | ||||
| 	mh         *matterhook.Client | ||||
| 	sc         *slack.Client | ||||
| 	rtm        *slack.RTM | ||||
| 	Users      []slack.User | ||||
| 	Usergroups []slack.UserGroup | ||||
| 	si         *slack.Info | ||||
| 	channels   []slack.Channel | ||||
| 	*bridge.Config | ||||
| 	sync.RWMutex | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| var protocol = "slack" | ||||
| const messageDeleted = "message_deleted" | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"module": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg config.Protocol, account string, c chan config.Message) *Bslack { | ||||
| 	b := &Bslack{} | ||||
| 	b.Config = &cfg | ||||
| 	b.Remote = c | ||||
| 	b.Account = account | ||||
| 	return b | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	return &Bslack{Config: cfg} | ||||
| } | ||||
|  | ||||
| func (b *Bslack) Command(cmd string) string { | ||||
| @@ -57,71 +40,76 @@ func (b *Bslack) Command(cmd string) string { | ||||
| } | ||||
|  | ||||
| func (b *Bslack) Connect() error { | ||||
| 	if b.Config.WebhookBindAddress != "" { | ||||
| 		if b.Config.WebhookURL != "" { | ||||
| 			flog.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)") | ||||
| 			b.mh = matterhook.New(b.Config.WebhookURL, | ||||
| 				matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, | ||||
| 					BindAddress: b.Config.WebhookBindAddress}) | ||||
| 		} else if b.Config.Token != "" { | ||||
| 			flog.Info("Connecting using token (sending)") | ||||
| 			b.sc = slack.New(b.Config.Token) | ||||
| 	b.RLock() | ||||
| 	defer b.RUnlock() | ||||
| 	if b.GetString("WebhookBindAddress") != "" { | ||||
| 		if b.GetString("WebhookURL") != "" { | ||||
| 			b.Log.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)") | ||||
| 			b.mh = matterhook.New(b.GetString("WebhookURL"), | ||||
| 				matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), | ||||
| 					BindAddress: b.GetString("WebhookBindAddress")}) | ||||
| 		} else if b.GetString("Token") != "" { | ||||
| 			b.Log.Info("Connecting using token (sending)") | ||||
| 			b.sc = slack.New(b.GetString("Token")) | ||||
| 			b.rtm = b.sc.NewRTM() | ||||
| 			go b.rtm.ManageConnection() | ||||
| 			flog.Info("Connecting using webhookbindaddress (receiving)") | ||||
| 			b.mh = matterhook.New(b.Config.WebhookURL, | ||||
| 				matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, | ||||
| 					BindAddress: b.Config.WebhookBindAddress}) | ||||
| 			b.Log.Info("Connecting using webhookbindaddress (receiving)") | ||||
| 			b.mh = matterhook.New(b.GetString("WebhookURL"), | ||||
| 				matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), | ||||
| 					BindAddress: b.GetString("WebhookBindAddress")}) | ||||
| 		} else { | ||||
| 			flog.Info("Connecting using webhookbindaddress (receiving)") | ||||
| 			b.mh = matterhook.New(b.Config.WebhookURL, | ||||
| 				matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, | ||||
| 					BindAddress: b.Config.WebhookBindAddress}) | ||||
| 			b.Log.Info("Connecting using webhookbindaddress (receiving)") | ||||
| 			b.mh = matterhook.New(b.GetString("WebhookURL"), | ||||
| 				matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), | ||||
| 					BindAddress: b.GetString("WebhookBindAddress")}) | ||||
| 		} | ||||
| 		go b.handleSlack() | ||||
| 		return nil | ||||
| 	} | ||||
| 	if b.Config.WebhookURL != "" { | ||||
| 		flog.Info("Connecting using webhookurl (sending)") | ||||
| 		b.mh = matterhook.New(b.Config.WebhookURL, | ||||
| 			matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, | ||||
| 	if b.GetString("WebhookURL") != "" { | ||||
| 		b.Log.Info("Connecting using webhookurl (sending)") | ||||
| 		b.mh = matterhook.New(b.GetString("WebhookURL"), | ||||
| 			matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), | ||||
| 				DisableServer: true}) | ||||
| 		if b.Config.Token != "" { | ||||
| 			flog.Info("Connecting using token (receiving)") | ||||
| 			b.sc = slack.New(b.Config.Token) | ||||
| 		if b.GetString("Token") != "" { | ||||
| 			b.Log.Info("Connecting using token (receiving)") | ||||
| 			b.sc = slack.New(b.GetString("Token")) | ||||
| 			b.rtm = b.sc.NewRTM() | ||||
| 			go b.rtm.ManageConnection() | ||||
| 			go b.handleSlack() | ||||
| 		} | ||||
| 	} else if b.Config.Token != "" { | ||||
| 		flog.Info("Connecting using token (sending and receiving)") | ||||
| 		b.sc = slack.New(b.Config.Token) | ||||
| 	} else if b.GetString("Token") != "" { | ||||
| 		b.Log.Info("Connecting using token (sending and receiving)") | ||||
| 		b.sc = slack.New(b.GetString("Token")) | ||||
| 		b.rtm = b.sc.NewRTM() | ||||
| 		go b.rtm.ManageConnection() | ||||
| 		go b.handleSlack() | ||||
| 	} | ||||
| 	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.") | ||||
| 	if b.GetString("WebhookBindAddress") == "" && b.GetString("WebhookURL") == "" && b.GetString("Token") == "" { | ||||
| 		return errors.New("no connection method found. See that you have WebhookBindAddress, WebhookURL or Token configured") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Bslack) Disconnect() error { | ||||
| 	return nil | ||||
|  | ||||
| 	return b.rtm.Disconnect() | ||||
| } | ||||
|  | ||||
| func (b *Bslack) JoinChannel(channel config.ChannelInfo) error { | ||||
| 	// we can only join channels using the API | ||||
| 	if b.Config.WebhookURL == "" && b.Config.WebhookBindAddress == "" { | ||||
| 		if strings.HasPrefix(b.Config.Token, "xoxb") { | ||||
| 	if b.sc != nil { | ||||
| 		if strings.HasPrefix(b.GetString("Token"), "xoxb") { | ||||
| 			// TODO check if bot has already joined channel | ||||
| 			return nil | ||||
| 		} | ||||
| 		_, err := b.sc.JoinChannel(channel.Name) | ||||
| 		if err != nil { | ||||
| 			if err.Error() != "name_taken" { | ||||
| 				return err | ||||
| 			switch err.Error() { | ||||
| 			case "name_taken", "restricted_action": | ||||
| 			case "default": | ||||
| 				{ | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| @@ -129,48 +117,25 @@ func (b *Bslack) JoinChannel(channel config.ChannelInfo) error { | ||||
| } | ||||
|  | ||||
| func (b *Bslack) Send(msg config.Message) (string, error) { | ||||
| 	flog.Debugf("Receiving %#v", msg) | ||||
| 	b.Log.Debugf("=> Receiving %#v", msg) | ||||
|  | ||||
| 	// Make a action /me of the message | ||||
| 	if msg.Event == config.EVENT_USER_ACTION { | ||||
| 		msg.Text = "_" + msg.Text + "_" | ||||
| 	} | ||||
| 	nick := msg.Username | ||||
| 	message := msg.Text | ||||
| 	channel := msg.Channel | ||||
| 	if b.Config.PrefixMessagesWithNick { | ||||
| 		message = nick + " " + message | ||||
|  | ||||
| 	// Use webhook to send the message | ||||
| 	if b.GetString("WebhookURL") != "" { | ||||
| 		return b.sendWebhook(msg) | ||||
| 	} | ||||
| 	if b.Config.WebhookURL != "" { | ||||
| 		matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL} | ||||
| 		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) | ||||
|  | ||||
| 	// get the slack channel | ||||
| 	schannel, err := b.getChannelByName(msg.Channel) | ||||
| 	if err != nil { | ||||
| 		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 { | ||||
| 		// some protocols echo deletes, but with empty ID | ||||
| 		if msg.ID == "" { | ||||
| @@ -178,42 +143,73 @@ func (b *Bslack) Send(msg config.Message) (string, error) { | ||||
| 		} | ||||
| 		// we get a "slack <ID>", split it | ||||
| 		ts := strings.Fields(msg.ID) | ||||
| 		b.sc.DeleteMessage(schannel.ID, ts[1]) | ||||
| 		return "", nil | ||||
| 	} | ||||
| 	// if we have no ID it means we're creating a new message, not updating an existing one | ||||
| 	if msg.ID != "" { | ||||
| 		ts := strings.Fields(msg.ID) | ||||
| 		b.sc.UpdateMessage(schannel.ID, ts[1], message) | ||||
| 		return "", nil | ||||
| 		_, _, err := b.sc.DeleteMessage(schannel.ID, ts[1]) | ||||
| 		if err != nil { | ||||
| 			return msg.ID, err | ||||
| 		} | ||||
| 		return msg.ID, nil | ||||
| 	} | ||||
|  | ||||
| 	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) | ||||
| 				_, 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) | ||||
| 				} | ||||
| 			} | ||||
| 	// Prepend nick if configured | ||||
| 	if b.GetBool("PrefixMessagesWithNick") { | ||||
| 		msg.Text = msg.Username + msg.Text | ||||
| 	} | ||||
|  | ||||
| 	// Edit message if we have an ID | ||||
| 	if msg.ID != "" { | ||||
| 		ts := strings.Fields(msg.ID) | ||||
| 		_, _, _, err := b.sc.UpdateMessage(schannel.ID, ts[1], msg.Text) | ||||
| 		if err != 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.si.User.ID}) | ||||
| 	// 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)...) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	_, id, err := b.sc.PostMessage(schannel.ID, message, np) | ||||
| 	// Upload a file if it exists | ||||
| 	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) | ||||
| 		if len(msg.Extra["file"]) > 0 { | ||||
| 			b.handleUploadFile(&msg, schannel.ID) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Post normal message | ||||
| 	_, id, err := b.sc.PostMessage(schannel.ID, msg.Text, np) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return "slack " + id, nil | ||||
| } | ||||
|  | ||||
| func (b *Bslack) Reload(cfg *bridge.Config) (string, error) { | ||||
| 	return "", nil | ||||
| } | ||||
|  | ||||
| func (b *Bslack) getAvatar(user string) string { | ||||
| 	var avatar string | ||||
| 	if b.Users != nil { | ||||
| @@ -251,145 +247,61 @@ func (b *Bslack) getChannelByID(ID string) (*slack.Channel, error) { | ||||
| } | ||||
|  | ||||
| func (b *Bslack) handleSlack() { | ||||
| 	mchan := make(chan *MMMessage) | ||||
| 	if b.Config.WebhookBindAddress != "" { | ||||
| 		flog.Debugf("Choosing webhooks based receiving") | ||||
| 		go b.handleMatterHook(mchan) | ||||
| 	messages := make(chan *config.Message) | ||||
| 	if b.GetString("WebhookBindAddress") != "" { | ||||
| 		b.Log.Debugf("Choosing webhooks based receiving") | ||||
| 		go b.handleMatterHook(messages) | ||||
| 	} else { | ||||
| 		flog.Debugf("Choosing token based receiving") | ||||
| 		go b.handleSlackClient(mchan) | ||||
| 		b.Log.Debugf("Choosing token based receiving") | ||||
| 		go b.handleSlackClient(messages) | ||||
| 	} | ||||
| 	time.Sleep(time.Second) | ||||
| 	flog.Debug("Start listening for Slack messages") | ||||
| 	for message := range mchan { | ||||
| 		// 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 | ||||
| 		} | ||||
| 	b.Log.Debug("Start listening for Slack messages") | ||||
| 	for message := range messages { | ||||
| 		b.Log.Debugf("<= Sending message from %s on %s to gateway", message.Username, b.Account) | ||||
|  | ||||
| 		// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra | ||||
| 		if message.Raw.File != nil { | ||||
| 			// limit to 1MB for now | ||||
| 			if message.Raw.File.Size <= 1000000 { | ||||
| 				comment := "" | ||||
| 				data, err := b.downloadFile(message.Raw.File.URLPrivateDownload) | ||||
| 				if err != nil { | ||||
| 					flog.Errorf("download %s failed %#v", message.Raw.File.URLPrivateDownload, err) | ||||
| 				} else { | ||||
| 					results := regexp.MustCompile(`.*?commented: (.*)`).FindAllStringSubmatch(msg.Text, -1) | ||||
| 					if len(results) > 0 { | ||||
| 						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 | ||||
| 		// cleanup the message | ||||
| 		message.Text = b.replaceMention(message.Text) | ||||
| 		message.Text = b.replaceVariable(message.Text) | ||||
| 		message.Text = b.replaceChannel(message.Text) | ||||
| 		message.Text = b.replaceURL(message.Text) | ||||
| 		message.Text = html.UnescapeString(message.Text) | ||||
|  | ||||
| 		// Add the avatar | ||||
| 		message.Avatar = b.getAvatar(message.Username) | ||||
|  | ||||
| 		b.Log.Debugf("<= Message is %#v", message) | ||||
| 		b.Remote <- *message | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bslack) handleSlackClient(mchan chan *MMMessage) { | ||||
| func (b *Bslack) handleSlackClient(messages chan *config.Message) { | ||||
| 	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) { | ||||
| 		case *slack.MessageEvent: | ||||
| 			flog.Debugf("Receiving from slackclient %#v", ev) | ||||
| 			if len(ev.Attachments) > 0 { | ||||
| 				// skip messages we made ourselves | ||||
| 				if ev.Attachments[0].CallbackID == "matterbridge" { | ||||
| 					continue | ||||
| 				} | ||||
| 			} | ||||
| 			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 b.skipMessageEvent(ev) { | ||||
| 				b.Log.Debugf("Skipped message: %#v", ev) | ||||
| 				continue | ||||
| 			} | ||||
| 			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 | ||||
| 				} | ||||
| 			rmsg, err := b.handleMessageEvent(ev) | ||||
| 			if err != nil { | ||||
| 				b.Log.Errorf("%#v", err) | ||||
| 				continue | ||||
| 			} | ||||
| 			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 | ||||
| 			messages <- rmsg | ||||
| 		case *slack.OutgoingErrorEvent: | ||||
| 			flog.Debugf("%#v", ev.Error()) | ||||
| 			b.Log.Debugf("%#v", ev.Error()) | ||||
| 		case *slack.ChannelJoinedEvent: | ||||
| 			b.Users, _ = b.sc.GetUsers() | ||||
| 			b.Usergroups, _ = b.sc.GetUserGroups() | ||||
| 		case *slack.ConnectedEvent: | ||||
| 			b.channels = ev.Info.Channels | ||||
| 			b.si = ev.Info | ||||
| 			b.Users, _ = b.sc.GetUsers() | ||||
| 			b.Usergroups, _ = b.sc.GetUserGroups() | ||||
| 			// add private channels | ||||
| 			groups, _ := b.sc.GetGroups(true) | ||||
| 			for _, g := range groups { | ||||
| @@ -399,27 +311,22 @@ func (b *Bslack) handleSlackClient(mchan chan *MMMessage) { | ||||
| 				b.channels = append(b.channels, *channel) | ||||
| 			} | ||||
| 		case *slack.InvalidAuthEvent: | ||||
| 			flog.Fatalf("Invalid Token %#v", ev) | ||||
| 			b.Log.Fatalf("Invalid Token %#v", ev) | ||||
| 		case *slack.ConnectionErrorEvent: | ||||
| 			b.Log.Errorf("Connection failed %#v %#v", ev.Error(), ev.ErrorObj) | ||||
| 		default: | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bslack) handleMatterHook(mchan chan *MMMessage) { | ||||
| func (b *Bslack) handleMatterHook(messages chan *config.Message) { | ||||
| 	for { | ||||
| 		message := b.mh.Receive() | ||||
| 		flog.Debugf("receiving from matterhook (slack) %#v", message) | ||||
| 		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" { | ||||
| 		b.Log.Debugf("receiving from matterhook (slack) %#v", message) | ||||
| 		if message.UserName == "slackbot" { | ||||
| 			continue | ||||
| 		} | ||||
| 		mchan <- m | ||||
| 		messages <- &config.Message{Username: message.UserName, Text: message.Text, Channel: message.ChannelName} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -435,6 +342,17 @@ func (b *Bslack) userName(id string) string { | ||||
| 	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 | ||||
| func (b *Bslack) replaceMention(text string) string { | ||||
| 	results := regexp.MustCompile(`<@([a-zA-z0-9]+)>`).FindAllStringSubmatch(text, -1) | ||||
| @@ -455,9 +373,13 @@ func (b *Bslack) replaceChannel(text string) string { | ||||
|  | ||||
| // @see https://api.slack.com/docs/message-formatting#variables | ||||
| func (b *Bslack) replaceVariable(text string) string { | ||||
| 	results := regexp.MustCompile(`<!([a-zA-Z0-9]+)(\|.+?)?>`).FindAllStringSubmatch(text, -1) | ||||
| 	results := regexp.MustCompile(`<!((?:subteam\^)?[a-zA-Z0-9]+)(?:\|@?(.+?))?>`).FindAllStringSubmatch(text, -1) | ||||
| 	for _, r := range results { | ||||
| 		text = strings.Replace(text, r[0], "@"+r[1], -1) | ||||
| 		if r[2] != "" { | ||||
| 			text = strings.Replace(text, r[0], "@"+r[2], -1) | ||||
| 		} else { | ||||
| 			text = strings.Replace(text, r[0], "@"+r[1], -1) | ||||
| 		} | ||||
| 	} | ||||
| 	return text | ||||
| } | ||||
| @@ -466,7 +388,11 @@ func (b *Bslack) replaceVariable(text string) string { | ||||
| func (b *Bslack) replaceURL(text string) string { | ||||
| 	results := regexp.MustCompile(`<(.*?)(\|.*?)?>`).FindAllStringSubmatch(text, -1) | ||||
| 	for _, r := range results { | ||||
| 		text = strings.Replace(text, r[0], r[1], -1) | ||||
| 		if len(strings.TrimSpace(r[2])) == 1 { // A display text separator was found, but the text was blank | ||||
| 			text = strings.Replace(text, r[0], "", -1) | ||||
| 		} else { | ||||
| 			text = strings.Replace(text, r[0], r[1], -1) | ||||
| 		} | ||||
| 	} | ||||
| 	return text | ||||
| } | ||||
| @@ -494,23 +420,241 @@ func (b *Bslack) createAttach(extra map[string][]interface{}) []slack.Attachment | ||||
| 	return attachs | ||||
| } | ||||
|  | ||||
| func (b *Bslack) downloadFile(url string) (*[]byte, error) { | ||||
| 	var buf bytes.Buffer | ||||
| 	client := &http.Client{ | ||||
| 		Timeout: time.Second * 5, | ||||
| // handleDownloadFile handles file download | ||||
| func (b *Bslack) handleDownloadFile(rmsg *config.Message, file *slack.File) error { | ||||
| 	// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra | ||||
| 	// limit to 1MB for now | ||||
| 	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 nil, err | ||||
| 		return err | ||||
| 	} | ||||
| 	req.Header.Add("Authorization", "Bearer "+b.Config.Token) | ||||
| 	resp, err := client.Do(req) | ||||
| 	// actually download the file | ||||
| 	data, err := helper.DownloadFileAuth(file.URLPrivateDownload, "Bearer "+b.GetString("Token")) | ||||
| 	if err != nil { | ||||
| 		resp.Body.Close() | ||||
| 		return nil, err | ||||
| 		return fmt.Errorf("download %s failed %#v", file.URLPrivateDownload, err) | ||||
| 	} | ||||
| 	io.Copy(&buf, resp.Body) | ||||
| 	data := buf.Bytes() | ||||
| 	resp.Body.Close() | ||||
| 	return &data, nil | ||||
| 	// 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 { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	rmsg := config.Message{Text: ev.Text, Channel: channel.Name, Account: b.Account, ID: "slack " + ev.Timestamp, Extra: make(map[string][]interface{})} | ||||
|  | ||||
| 	// find the user id and name | ||||
| 	if ev.BotID == "" && 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 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 | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// 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 { | ||||
| 		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) { | ||||
| 			matterMessage := matterhook.OMessage{IconURL: b.GetString("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)...) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	matterMessage := matterhook.OMessage{IconURL: b.GetString("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 { | ||||
| 		b.Log.Error(err) | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return "", nil | ||||
| } | ||||
|  | ||||
| // skipMessageEvent skips event that need to be skipped :-) | ||||
| 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.si.User.ID { | ||||
| 			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 | ||||
| } | ||||
|   | ||||
							
								
								
									
										133
									
								
								bridge/sshchat/sshchat.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								bridge/sshchat/sshchat.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,133 @@ | ||||
| package bsshchat | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	"github.com/shazow/ssh-chat/sshd" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"io" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| type Bsshchat struct { | ||||
| 	r *bufio.Scanner | ||||
| 	w io.WriteCloser | ||||
| 	*bridge.Config | ||||
| } | ||||
|  | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	return &Bsshchat{Config: cfg} | ||||
| } | ||||
|  | ||||
| func (b *Bsshchat) Connect() error { | ||||
| 	var err error | ||||
| 	b.Log.Infof("Connecting %s", b.GetString("Server")) | ||||
| 	go func() { | ||||
| 		err = sshd.ConnectShell(b.GetString("Server"), b.GetString("Nick"), func(r io.Reader, w io.WriteCloser) error { | ||||
| 			b.r = bufio.NewScanner(r) | ||||
| 			b.w = w | ||||
| 			b.r.Scan() | ||||
| 			w.Write([]byte("/theme mono\r\n")) | ||||
| 			b.handleSshChat() | ||||
| 			return nil | ||||
| 		}) | ||||
| 	}() | ||||
| 	if err != nil { | ||||
| 		b.Log.Debugf("%#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	b.Log.Info("Connection succeeded") | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Bsshchat) Disconnect() error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Bsshchat) JoinChannel(channel config.ChannelInfo) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Bsshchat) Send(msg config.Message) (string, error) { | ||||
| 	// ignore delete messages | ||||
| 	if msg.Event == config.EVENT_MSG_DELETE { | ||||
| 		return "", nil | ||||
| 	} | ||||
| 	b.Log.Debugf("=> Receiving %#v", msg) | ||||
| 	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 { | ||||
| 			for _, f := range msg.Extra["file"] { | ||||
| 				fi := f.(config.FileInfo) | ||||
| 				if fi.Comment != "" { | ||||
| 					msg.Text += fi.Comment + ": " | ||||
| 				} | ||||
| 				if fi.URL != "" { | ||||
| 					msg.Text = fi.URL | ||||
| 				} | ||||
| 				b.w.Write([]byte(msg.Username + msg.Text)) | ||||
| 			} | ||||
| 			return "", nil | ||||
| 		} | ||||
| 	} | ||||
| 	b.w.Write([]byte(msg.Username + msg.Text + "\r\n")) | ||||
| 	return "", nil | ||||
| } | ||||
|  | ||||
| /* | ||||
| func (b *Bsshchat) sshchatKeepAlive() chan bool { | ||||
| 	done := make(chan bool) | ||||
| 	go func() { | ||||
| 		ticker := time.NewTicker(90 * time.Second) | ||||
| 		defer ticker.Stop() | ||||
| 		for { | ||||
| 			select { | ||||
| 			case <-ticker.C: | ||||
| 				b.Log.Debugf("PING") | ||||
| 				err := b.xc.PingC2S("", "") | ||||
| 				if err != nil { | ||||
| 					b.Log.Debugf("PING failed %#v", err) | ||||
| 				} | ||||
| 			case <-done: | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
| 	return done | ||||
| } | ||||
| */ | ||||
|  | ||||
| func stripPrompt(s string) string { | ||||
| 	pos := strings.LastIndex(s, "\033[K") | ||||
| 	if pos < 0 { | ||||
| 		return s | ||||
| 	} | ||||
| 	return s[pos+3:] | ||||
| } | ||||
|  | ||||
| func (b *Bsshchat) handleSshChat() error { | ||||
| 	/* | ||||
| 		done := b.sshchatKeepAlive() | ||||
| 		defer close(done) | ||||
| 	*/ | ||||
| 	wait := true | ||||
| 	for { | ||||
| 		if b.r.Scan() { | ||||
| 			res := strings.Split(stripPrompt(b.r.Text()), ":") | ||||
| 			if res[0] == "-> Set theme" { | ||||
| 				wait = false | ||||
| 				log.Debugf("mono found, allowing") | ||||
| 				continue | ||||
| 			} | ||||
| 			if !wait { | ||||
| 				b.Log.Debugf("<= Message %#v", res) | ||||
| 				rmsg := config.Message{Username: res[0], Text: strings.Join(res[1:], ":"), Channel: "sshchat", Account: b.Account, UserID: "nick"} | ||||
| 				b.Remote <- rmsg | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -2,11 +2,11 @@ package bsteam | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/Philipp15b/go-steam" | ||||
| 	"github.com/Philipp15b/go-steam/protocol/steamlang" | ||||
| 	"github.com/Philipp15b/go-steam/steamid" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| 	//"io/ioutil" | ||||
| 	"strconv" | ||||
| 	"sync" | ||||
| @@ -16,38 +16,26 @@ import ( | ||||
| type Bsteam struct { | ||||
| 	c         *steam.Client | ||||
| 	connected chan struct{} | ||||
| 	Config    *config.Protocol | ||||
| 	Remote    chan config.Message | ||||
| 	Account   string | ||||
| 	userMap   map[steamid.SteamId]string | ||||
| 	sync.RWMutex | ||||
| 	*bridge.Config | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| var protocol = "steam" | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"module": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg config.Protocol, account string, c chan config.Message) *Bsteam { | ||||
| 	b := &Bsteam{} | ||||
| 	b.Config = &cfg | ||||
| 	b.Remote = c | ||||
| 	b.Account = account | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	b := &Bsteam{Config: cfg} | ||||
| 	b.userMap = make(map[steamid.SteamId]string) | ||||
| 	b.connected = make(chan struct{}) | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func (b *Bsteam) Connect() error { | ||||
| 	flog.Info("Connecting") | ||||
| 	b.Log.Info("Connecting") | ||||
| 	b.c = steam.NewClient() | ||||
| 	go b.handleEvents() | ||||
| 	go b.c.Connect() | ||||
| 	select { | ||||
| 	case <-b.connected: | ||||
| 		flog.Info("Connection succeeded") | ||||
| 		b.Log.Info("Connection succeeded") | ||||
| 	case <-time.After(time.Second * 30): | ||||
| 		return fmt.Errorf("connection timed out") | ||||
| 	} | ||||
| @@ -93,18 +81,18 @@ func (b *Bsteam) getNick(id steamid.SteamId) string { | ||||
|  | ||||
| func (b *Bsteam) handleEvents() { | ||||
| 	myLoginInfo := new(steam.LogOnDetails) | ||||
| 	myLoginInfo.Username = b.Config.Login | ||||
| 	myLoginInfo.Password = b.Config.Password | ||||
| 	myLoginInfo.AuthCode = b.Config.AuthCode | ||||
| 	myLoginInfo.Username = b.GetString("Login") | ||||
| 	myLoginInfo.Password = b.GetString("Password") | ||||
| 	myLoginInfo.AuthCode = b.GetString("AuthCode") | ||||
| 	// Attempt to read existing auth hash to avoid steam guard. | ||||
| 	// Maybe works | ||||
| 	//myLoginInfo.SentryFileHash, _ = ioutil.ReadFile("sentry") | ||||
| 	for event := range b.c.Events() { | ||||
| 		//flog.Info(event) | ||||
| 		//b.Log.Info(event) | ||||
| 		switch e := event.(type) { | ||||
| 		case *steam.ChatMsgEvent: | ||||
| 			flog.Debugf("Receiving ChatMsgEvent: %#v", e) | ||||
| 			flog.Debugf("Sending message from %s on %s to gateway", b.getNick(e.ChatterId), b.Account) | ||||
| 			b.Log.Debugf("Receiving ChatMsgEvent: %#v", e) | ||||
| 			b.Log.Debugf("<= Sending message from %s on %s to gateway", b.getNick(e.ChatterId), b.Account) | ||||
| 			var channel int64 | ||||
| 			if e.ChatRoomId == 0 { | ||||
| 				channel = int64(e.ChatterId) | ||||
| @@ -115,7 +103,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)} | ||||
| 			b.Remote <- msg | ||||
| 		case *steam.PersonaStateEvent: | ||||
| 			flog.Debugf("PersonaStateEvent: %#v\n", e) | ||||
| 			b.Log.Debugf("PersonaStateEvent: %#v\n", e) | ||||
| 			b.Lock() | ||||
| 			b.userMap[e.FriendId] = e.Name | ||||
| 			b.Unlock() | ||||
| @@ -123,47 +111,47 @@ func (b *Bsteam) handleEvents() { | ||||
| 			b.c.Auth.LogOn(myLoginInfo) | ||||
| 		case *steam.MachineAuthUpdateEvent: | ||||
| 			/* | ||||
| 				flog.Info("authupdate", e) | ||||
| 				flog.Info("hash", e.Hash) | ||||
| 				b.Log.Info("authupdate", e) | ||||
| 				b.Log.Info("hash", e.Hash) | ||||
| 				ioutil.WriteFile("sentry", e.Hash, 0666) | ||||
| 			*/ | ||||
| 		case *steam.LogOnFailedEvent: | ||||
| 			flog.Info("Logon failed", e) | ||||
| 			b.Log.Info("Logon failed", e) | ||||
| 			switch e.Result { | ||||
| 			case steamlang.EResult_AccountLogonDeniedNeedTwoFactorCode: | ||||
| 				{ | ||||
| 					flog.Info("Steam guard isn't letting me in! Enter 2FA code:") | ||||
| 					b.Log.Info("Steam guard isn't letting me in! Enter 2FA code:") | ||||
| 					var code string | ||||
| 					fmt.Scanf("%s", &code) | ||||
| 					myLoginInfo.TwoFactorCode = code | ||||
| 				} | ||||
| 			case steamlang.EResult_AccountLogonDenied: | ||||
| 				{ | ||||
| 					flog.Info("Steam guard isn't letting me in! Enter auth code:") | ||||
| 					b.Log.Info("Steam guard isn't letting me in! Enter auth code:") | ||||
| 					var code string | ||||
| 					fmt.Scanf("%s", &code) | ||||
| 					myLoginInfo.AuthCode = code | ||||
| 				} | ||||
| 			default: | ||||
| 				log.Errorf("LogOnFailedEvent: %#v ", e.Result) | ||||
| 				b.Log.Errorf("LogOnFailedEvent: %#v ", e.Result) | ||||
| 				// TODO: Handle EResult_InvalidLoginAuthCode | ||||
| 				return | ||||
| 			} | ||||
| 		case *steam.LoggedOnEvent: | ||||
| 			flog.Debugf("LoggedOnEvent: %#v", e) | ||||
| 			b.Log.Debugf("LoggedOnEvent: %#v", e) | ||||
| 			b.connected <- struct{}{} | ||||
| 			flog.Debugf("setting online") | ||||
| 			b.Log.Debugf("setting online") | ||||
| 			b.c.Social.SetPersonaState(steamlang.EPersonaState_Online) | ||||
| 		case *steam.DisconnectedEvent: | ||||
| 			flog.Info("Disconnected") | ||||
| 			flog.Info("Attempting to reconnect...") | ||||
| 			b.Log.Info("Disconnected") | ||||
| 			b.Log.Info("Attempting to reconnect...") | ||||
| 			b.c.Connect() | ||||
| 		case steam.FatalErrorEvent: | ||||
| 			flog.Error(e) | ||||
| 			b.Log.Error(e) | ||||
| 		case error: | ||||
| 			flog.Error(e) | ||||
| 			b.Log.Error(e) | ||||
| 		default: | ||||
| 			flog.Debugf("unknown event %#v", e) | ||||
| 			b.Log.Debugf("unknown event %#v", e) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -6,11 +6,11 @@ import ( | ||||
| 	"html" | ||||
| ) | ||||
|  | ||||
| type customHtml struct { | ||||
| type customHTML struct { | ||||
| 	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() | ||||
|  | ||||
| 	if !text() { | ||||
| @@ -20,32 +20,32 @@ func (options *customHtml) Paragraph(out *bytes.Buffer, text func() bool) { | ||||
| 	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(html.EscapeString(string(text))) | ||||
| 	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) | ||||
| } | ||||
|  | ||||
| func (options *customHtml) HRule(out *bytes.Buffer) { | ||||
| func (options *customHTML) HRule(out *bytes.Buffer) { | ||||
| 	out.WriteByte('\n') | ||||
| } | ||||
|  | ||||
| func (options *customHtml) BlockQuote(out *bytes.Buffer, text []byte) { | ||||
| func (options *customHTML) BlockQuote(out *bytes.Buffer, text []byte) { | ||||
| 	out.WriteString("> ") | ||||
| 	out.Write(text) | ||||
| 	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) | ||||
| } | ||||
|  | ||||
| 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.Write(text) | ||||
| 	out.WriteByte('\n') | ||||
| @@ -53,7 +53,7 @@ func (options *customHtml) ListItem(out *bytes.Buffer, text []byte, flags int) { | ||||
|  | ||||
| func makeHTML(input string) string { | ||||
| 	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_FENCED_CODE| | ||||
| 			blackfriday.EXTENSION_AUTOLINK| | ||||
|   | ||||
| @@ -5,55 +5,44 @@ import ( | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| 	"github.com/go-telegram-bot-api/telegram-bot-api" | ||||
| ) | ||||
|  | ||||
| type Btelegram struct { | ||||
| 	c       *tgbotapi.BotAPI | ||||
| 	Config  *config.Protocol | ||||
| 	Remote  chan config.Message | ||||
| 	Account string | ||||
| 	c *tgbotapi.BotAPI | ||||
| 	*bridge.Config | ||||
| 	avatarMap map[string]string // keep cache of userid and avatar sha | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| var protocol = "telegram" | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"module": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg config.Protocol, account string, c chan config.Message) *Btelegram { | ||||
| 	b := &Btelegram{} | ||||
| 	b.Config = &cfg | ||||
| 	b.Remote = c | ||||
| 	b.Account = account | ||||
| 	return b | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	return &Btelegram{Config: cfg, avatarMap: make(map[string]string)} | ||||
| } | ||||
|  | ||||
| func (b *Btelegram) Connect() error { | ||||
| 	var err error | ||||
| 	flog.Info("Connecting") | ||||
| 	b.c, err = tgbotapi.NewBotAPI(b.Config.Token) | ||||
| 	b.Log.Info("Connecting") | ||||
| 	b.c, err = tgbotapi.NewBotAPI(b.GetString("Token")) | ||||
| 	if err != nil { | ||||
| 		flog.Debugf("%#v", err) | ||||
| 		b.Log.Debugf("%#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	updates, err := b.c.GetUpdatesChan(tgbotapi.NewUpdate(0)) | ||||
| 	u := tgbotapi.NewUpdate(0) | ||||
| 	u.Timeout = 60 | ||||
| 	updates, err := b.c.GetUpdatesChan(u) | ||||
| 	if err != nil { | ||||
| 		flog.Debugf("%#v", err) | ||||
| 		b.Log.Debugf("%#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	flog.Info("Connection succeeded") | ||||
| 	b.Log.Info("Connection succeeded") | ||||
| 	go b.handleRecv(updates) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Btelegram) Disconnect() error { | ||||
| 	return nil | ||||
|  | ||||
| } | ||||
|  | ||||
| func (b *Btelegram) JoinChannel(channel config.ChannelInfo) error { | ||||
| @@ -61,16 +50,24 @@ func (b *Btelegram) JoinChannel(channel config.ChannelInfo) error { | ||||
| } | ||||
|  | ||||
| func (b *Btelegram) Send(msg config.Message) (string, error) { | ||||
| 	flog.Debugf("Receiving %#v", msg) | ||||
| 	b.Log.Debugf("=> Receiving %#v", msg) | ||||
|  | ||||
| 	// get the chatid | ||||
| 	chatid, err := strconv.ParseInt(msg.Channel, 10, 64) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	if b.Config.MessageFormat == "HTML" { | ||||
| 	// map the file SHA to our user (caches the avatar) | ||||
| 	if msg.Event == config.EVENT_AVATAR_DOWNLOAD { | ||||
| 		return b.cacheAvatar(&msg) | ||||
| 	} | ||||
|  | ||||
| 	if b.GetString("MessageFormat") == "HTML" { | ||||
| 		msg.Text = makeHTML(msg.Text) | ||||
| 	} | ||||
|  | ||||
| 	// Delete message | ||||
| 	if msg.Event == config.EVENT_MSG_DELETE { | ||||
| 		if msg.ID == "" { | ||||
| 			return "", nil | ||||
| @@ -83,6 +80,17 @@ func (b *Btelegram) Send(msg config.Message) (string, error) { | ||||
| 		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 | ||||
| 	if msg.ID != "" { | ||||
| 		msgid, err := strconv.Atoi(msg.ID) | ||||
| @@ -90,6 +98,14 @@ func (b *Btelegram) Send(msg config.Message) (string, error) { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		m := tgbotapi.NewEditMessageText(chatid, msgid, msg.Username+msg.Text) | ||||
| 		if b.GetString("MessageFormat") == "HTML" { | ||||
| 			b.Log.Debug("Using mode HTML") | ||||
| 			m.ParseMode = tgbotapi.ModeHTML | ||||
| 		} | ||||
| 		if b.GetString("MessageFormat") == "Markdown" { | ||||
| 			b.Log.Debug("Using mode markdown") | ||||
| 			m.ParseMode = tgbotapi.ModeMarkdown | ||||
| 		} | ||||
| 		_, err = b.c.Send(m) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| @@ -97,94 +113,102 @@ func (b *Btelegram) Send(msg config.Message) (string, error) { | ||||
| 		return "", nil | ||||
| 	} | ||||
|  | ||||
| 	if msg.Extra != nil { | ||||
| 		// 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) | ||||
| 	// Post normal message | ||||
| 	return b.sendMessage(chatid, msg.Username, msg.Text) | ||||
| } | ||||
|  | ||||
| func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) { | ||||
| 	for update := range updates { | ||||
| 		flog.Debugf("Receiving from telegram: %#v", update.Message) | ||||
| 		var message *tgbotapi.Message | ||||
| 		username := "" | ||||
| 		channel := "" | ||||
| 		text := "" | ||||
| 		b.Log.Debugf("== Receiving event: %#v", update.Message) | ||||
|  | ||||
| 		fmsg := config.Message{Extra: make(map[string][]interface{})} | ||||
| 		if update.Message == nil && update.ChannelPost == nil { | ||||
| 			b.Log.Error("Getting nil messages, this shouldn't happen.") | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		var message *tgbotapi.Message | ||||
|  | ||||
| 		rmsg := config.Message{Account: b.Account, Extra: make(map[string][]interface{})} | ||||
|  | ||||
| 		// handle channels | ||||
| 		if update.ChannelPost != nil { | ||||
| 			message = update.ChannelPost | ||||
| 		} | ||||
| 		if update.EditedChannelPost != nil && !b.Config.EditDisable { | ||||
|  | ||||
| 		// edited channel message | ||||
| 		if update.EditedChannelPost != nil && !b.GetBool("EditDisable") { | ||||
| 			message = update.EditedChannelPost | ||||
| 			message.Text = message.Text + b.Config.EditSuffix | ||||
| 			rmsg.Text = rmsg.Text + message.Text + b.GetString("EditSuffix") | ||||
| 		} | ||||
|  | ||||
| 		// handle groups | ||||
| 		if update.Message != nil { | ||||
| 			message = update.Message | ||||
| 		} | ||||
| 		if update.EditedMessage != nil && !b.Config.EditDisable { | ||||
|  | ||||
| 		// edited group message | ||||
| 		if update.EditedMessage != nil && !b.GetBool("EditDisable") { | ||||
| 			message = update.EditedMessage | ||||
| 			message.Text = message.Text + b.Config.EditSuffix | ||||
| 		} | ||||
| 		if message.From != nil { | ||||
| 			if b.Config.UseFirstName { | ||||
| 				username = message.From.FirstName | ||||
| 			} | ||||
| 			if username == "" { | ||||
| 				username = message.From.UserName | ||||
| 				if username == "" { | ||||
| 					username = message.From.FirstName | ||||
| 				} | ||||
| 			} | ||||
| 			text = message.Text | ||||
| 			channel = strconv.FormatInt(message.Chat.ID, 10) | ||||
| 			rmsg.Text = rmsg.Text + message.Text + b.GetString("EditSuffix") | ||||
| 		} | ||||
|  | ||||
| 		if username == "" { | ||||
| 			username = "unknown" | ||||
| 		// set the ID's from the channel or group message | ||||
| 		rmsg.ID = strconv.Itoa(message.MessageID) | ||||
| 		rmsg.UserID = strconv.Itoa(message.From.ID) | ||||
| 		rmsg.Channel = strconv.FormatInt(message.Chat.ID, 10) | ||||
|  | ||||
| 		// handle username | ||||
| 		if message.From != nil { | ||||
| 			if b.GetBool("UseFirstName") { | ||||
| 				rmsg.Username = message.From.FirstName | ||||
| 			} | ||||
| 			if rmsg.Username == "" { | ||||
| 				rmsg.Username = message.From.UserName | ||||
| 				if rmsg.Username == "" { | ||||
| 					rmsg.Username = message.From.FirstName | ||||
| 				} | ||||
| 			} | ||||
| 			rmsg.Text += message.Text | ||||
| 			// only download avatars if we have a place to upload them (configured mediaserver) | ||||
| 			if b.General.MediaServerUpload != "" { | ||||
| 				b.handleDownloadAvatar(message.From.ID, rmsg.Channel) | ||||
| 			} | ||||
| 		} | ||||
| 		if message.Sticker != nil { | ||||
| 			b.handleDownload(message.Sticker, &fmsg) | ||||
|  | ||||
| 		// if we really didn't find a username, set it to unknown | ||||
| 		if rmsg.Username == "" { | ||||
| 			rmsg.Username = "unknown" | ||||
| 		} | ||||
| 		if message.Video != nil { | ||||
| 			b.handleDownload(message.Video, &fmsg) | ||||
|  | ||||
| 		// handle any downloads | ||||
| 		err := b.handleDownload(message, &rmsg) | ||||
| 		if err != nil { | ||||
| 			b.Log.Errorf("download failed: %s", err) | ||||
| 		} | ||||
| 		if message.Photo != nil { | ||||
| 			b.handleDownload(message.Photo, &fmsg) | ||||
| 		} | ||||
| 		if message.Document != nil { | ||||
| 			b.handleDownload(message.Document, &fmsg) | ||||
|  | ||||
| 		// handle forwarded messages | ||||
| 		if message.ForwardFrom != nil { | ||||
| 			usernameForward := "" | ||||
| 			if b.GetBool("UseFirstName") { | ||||
| 				usernameForward = message.ForwardFrom.FirstName | ||||
| 			} | ||||
| 			if usernameForward == "" { | ||||
| 				usernameForward = message.ForwardFrom.UserName | ||||
| 				if usernameForward == "" { | ||||
| 					usernameForward = message.ForwardFrom.FirstName | ||||
| 				} | ||||
| 			} | ||||
| 			if usernameForward == "" { | ||||
| 				usernameForward = "unknown" | ||||
| 			} | ||||
| 			rmsg.Text = "Forwarded from " + usernameForward + ": " + rmsg.Text | ||||
| 		} | ||||
|  | ||||
| 		// quote the previous message | ||||
| 		if message.ReplyToMessage != nil { | ||||
| 			usernameReply := "" | ||||
| 			if message.ReplyToMessage.From != nil { | ||||
| 				if b.Config.UseFirstName { | ||||
| 				if b.GetBool("UseFirstName") { | ||||
| 					usernameReply = message.ReplyToMessage.From.FirstName | ||||
| 				} | ||||
| 				if usernameReply == "" { | ||||
| @@ -197,14 +221,18 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) { | ||||
| 			if usernameReply == "" { | ||||
| 				usernameReply = "unknown" | ||||
| 			} | ||||
| 			text = text + " (re @" + usernameReply + ":" + message.ReplyToMessage.Text + ")" | ||||
| 			if !b.GetBool("QuoteDisable") { | ||||
| 				rmsg.Text = rmsg.Text + " (re @" + usernameReply + ":" + message.ReplyToMessage.Text + ")" | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if text != "" || len(fmsg.Extra) > 0 { | ||||
| 			flog.Debugf("Sending message from %s on %s to gateway", username, b.Account) | ||||
| 			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} | ||||
| 			flog.Debugf("Message is %#v", msg) | ||||
| 			b.Remote <- msg | ||||
| 		if rmsg.Text != "" || len(rmsg.Extra) > 0 { | ||||
| 			rmsg.Text = helper.RemoveEmptyNewLines(rmsg.Text) | ||||
| 			rmsg.Avatar = helper.GetAvatar(b.avatarMap, strconv.Itoa(message.From.ID), b.General) | ||||
|  | ||||
| 			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 | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -217,67 +245,169 @@ func (b *Btelegram) getFileDirectURL(id string) string { | ||||
| 	return res | ||||
| } | ||||
|  | ||||
| func (b *Btelegram) handleDownload(file interface{}, msg *config.Message) { | ||||
| // handleDownloadAvatar downloads the avatar of userid from channel | ||||
| // sends a EVENT_AVATAR_DOWNLOAD message to the gateway if successful. | ||||
| // logs an error message if it fails | ||||
| func (b *Btelegram) handleDownloadAvatar(userid int, channel string) { | ||||
| 	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 | ||||
| 	url := "" | ||||
| 	name := "" | ||||
| 	text := "" | ||||
| 	fileid := "" | ||||
| 	switch v := file.(type) { | ||||
| 	case *tgbotapi.Sticker: | ||||
| 	var url, name, text string | ||||
|  | ||||
| 	if message.Sticker != nil { | ||||
| 		v := message.Sticker | ||||
| 		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 | ||||
| 	} | ||||
| 	if message.Video != nil { | ||||
| 		v := message.Video | ||||
| 		size = v.FileSize | ||||
| 		url = b.getFileDirectURL(v.FileID) | ||||
| 		urlPart := strings.Split(url, "/") | ||||
| 		name = urlPart[len(urlPart)-1] | ||||
| 		text = " " + url | ||||
| 		fileid = v.FileID | ||||
| 	case *tgbotapi.Video: | ||||
| 		size = v.FileSize | ||||
| 		url = b.getFileDirectURL(v.FileID) | ||||
| 		urlPart := strings.Split(url, "/") | ||||
| 		name = urlPart[len(urlPart)-1] | ||||
| 		text = " " + url | ||||
| 		fileid = v.FileID | ||||
| 	case *[]tgbotapi.PhotoSize: | ||||
| 		photos := *v | ||||
| 	} | ||||
| 	if message.Photo != nil { | ||||
| 		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 | ||||
| 	case *tgbotapi.Document: | ||||
| 	} | ||||
| 	if message.Document != nil { | ||||
| 		v := message.Document | ||||
| 		size = v.FileSize | ||||
| 		url = b.getFileDirectURL(v.FileID) | ||||
| 		name = v.FileName | ||||
| 		text = " " + v.FileName + " : " + url | ||||
| 		fileid = v.FileID | ||||
| 	} | ||||
| 	if b.Config.UseInsecureURL { | ||||
| 		msg.Text = text | ||||
| 		return | ||||
| 	} | ||||
| 	// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra | ||||
| 	// limit to 1MB for now | ||||
| 	flog.Debugf("trying to download %#v fileid %#v with size %#v", name, fileid, size) | ||||
| 	if size <= 1000000 { | ||||
| 		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)) | ||||
| 			msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{Name: name, Data: data}) | ||||
| 	if message.Voice != nil { | ||||
| 		v := message.Voice | ||||
| 		size = v.FileSize | ||||
| 		url = b.getFileDirectURL(v.FileID) | ||||
| 		urlPart := strings.Split(url, "/") | ||||
| 		name = urlPart[len(urlPart)-1] | ||||
| 		text = " " + url | ||||
| 		if !strings.HasSuffix(name, ".ogg") { | ||||
| 			name = name + ".ogg" | ||||
| 		} | ||||
| 	} | ||||
| 	if message.Audio != nil { | ||||
| 		v := message.Audio | ||||
| 		size = v.FileSize | ||||
| 		url = b.getFileDirectURL(v.FileID) | ||||
| 		urlPart := strings.Split(url, "/") | ||||
| 		name = urlPart[len(urlPart)-1] | ||||
| 		text = " " + url | ||||
| 	} | ||||
| 	// if name is empty we didn't match a thing to download | ||||
| 	if name == "" { | ||||
| 		return nil | ||||
| 	} | ||||
| 	// 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 | ||||
| 	err := helper.HandleDownloadSize(b.Log, rmsg, name, int64(size), b.General) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	data, err := helper.DownloadFile(url) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	helper.HandleDownloadData(b.Log, rmsg, name, message.Caption, "", data, b.General) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Btelegram) sendMessage(chatid int64, text string) (string, error) { | ||||
| 	m := tgbotapi.NewMessage(chatid, text) | ||||
| 	if b.Config.MessageFormat == "HTML" { | ||||
| // 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 { | ||||
| 			b.Log.Errorf("file upload failed: %#v", err) | ||||
| 		} | ||||
| 		if fi.Comment != "" { | ||||
| 			b.sendMessage(chatid, msg.Username, fi.Comment) | ||||
| 		} | ||||
| 	} | ||||
| 	return "", nil | ||||
| } | ||||
|  | ||||
| func (b *Btelegram) sendMessage(chatid int64, username, text string) (string, error) { | ||||
| 	m := tgbotapi.NewMessage(chatid, "") | ||||
| 	m.Text = username + text | ||||
| 	if b.GetString("MessageFormat") == "HTML" { | ||||
| 		b.Log.Debug("Using mode HTML") | ||||
| 		m.Text = username + text | ||||
| 		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) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	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 | ||||
| } | ||||
|   | ||||
| @@ -2,11 +2,11 @@ package bxmpp | ||||
|  | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	"github.com/jpillora/backoff" | ||||
| 	"github.com/mattn/go-xmpp" | ||||
|  | ||||
| 	"github.com/matterbridge/go-xmpp" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
| @@ -14,36 +14,24 @@ import ( | ||||
| type Bxmpp struct { | ||||
| 	xc      *xmpp.Client | ||||
| 	xmppMap map[string]string | ||||
| 	Config  *config.Protocol | ||||
| 	Remote  chan config.Message | ||||
| 	Account string | ||||
| 	*bridge.Config | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| var protocol = "xmpp" | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"module": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg config.Protocol, account string, c chan config.Message) *Bxmpp { | ||||
| 	b := &Bxmpp{} | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	b := &Bxmpp{Config: cfg} | ||||
| 	b.xmppMap = make(map[string]string) | ||||
| 	b.Config = &cfg | ||||
| 	b.Account = account | ||||
| 	b.Remote = c | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) Connect() error { | ||||
| 	var err error | ||||
| 	flog.Infof("Connecting %s", b.Config.Server) | ||||
| 	b.Log.Infof("Connecting %s", b.GetString("Server")) | ||||
| 	b.xc, err = b.createXMPP() | ||||
| 	if err != nil { | ||||
| 		flog.Debugf("%#v", err) | ||||
| 		b.Log.Debugf("%#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	flog.Info("Connection succeeded") | ||||
| 	b.Log.Info("Connection succeeded") | ||||
| 	go func() { | ||||
| 		initial := true | ||||
| 		bf := &backoff.Backoff{ | ||||
| @@ -53,16 +41,16 @@ func (b *Bxmpp) Connect() error { | ||||
| 		} | ||||
| 		for { | ||||
| 			if initial { | ||||
| 				b.handleXmpp() | ||||
| 				b.handleXMPP() | ||||
| 				initial = false | ||||
| 			} | ||||
| 			d := bf.Duration() | ||||
| 			flog.Infof("Disconnected. Reconnecting in %s", d) | ||||
| 			b.Log.Infof("Disconnected. Reconnecting in %s", d) | ||||
| 			time.Sleep(d) | ||||
| 			b.xc, err = b.createXMPP() | ||||
| 			if err == nil { | ||||
| 				b.Remote <- config.Message{Username: "system", Text: "rejoin", Channel: "", Account: b.Account, Event: config.EVENT_REJOIN_CHANNELS} | ||||
| 				b.handleXmpp() | ||||
| 				b.handleXMPP() | ||||
| 				bf.Reset() | ||||
| 			} | ||||
| 		} | ||||
| @@ -75,7 +63,7 @@ func (b *Bxmpp) Disconnect() error { | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) JoinChannel(channel config.ChannelInfo) error { | ||||
| 	b.xc.JoinMUCNoHistory(channel.Name+"@"+b.Config.Muc, b.Config.Nick) | ||||
| 	b.xc.JoinMUCNoHistory(channel.Name+"@"+b.GetString("Muc"), b.GetString("Nick")) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -84,31 +72,44 @@ func (b *Bxmpp) Send(msg config.Message) (string, error) { | ||||
| 	if msg.Event == config.EVENT_MSG_DELETE { | ||||
| 		return "", nil | ||||
| 	} | ||||
| 	flog.Debugf("Receiving %#v", msg) | ||||
| 	b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Config.Muc, Text: msg.Username + msg.Text}) | ||||
| 	b.Log.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 { | ||||
| 		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 { | ||||
| 			return b.handleUploadFile(&msg) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Post normal message | ||||
| 	_, 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 | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) createXMPP() (*xmpp.Client, error) { | ||||
| 	tc := new(tls.Config) | ||||
| 	tc.InsecureSkipVerify = b.Config.SkipTLSVerify | ||||
| 	tc.ServerName = strings.Split(b.Config.Server, ":")[0] | ||||
| 	tc.InsecureSkipVerify = b.GetBool("SkipTLSVerify") | ||||
| 	tc.ServerName = strings.Split(b.GetString("Server"), ":")[0] | ||||
| 	options := xmpp.Options{ | ||||
| 		Host:      b.Config.Server, | ||||
| 		User:      b.Config.Jid, | ||||
| 		Password:  b.Config.Password, | ||||
| 		NoTLS:     true, | ||||
| 		StartTLS:  true, | ||||
| 		TLSConfig: tc, | ||||
|  | ||||
| 		//StartTLS:      false, | ||||
| 		Debug:                        true, | ||||
| 		Host:                         b.GetString("Server"), | ||||
| 		User:                         b.GetString("Jid"), | ||||
| 		Password:                     b.GetString("Password"), | ||||
| 		NoTLS:                        true, | ||||
| 		StartTLS:                     true, | ||||
| 		TLSConfig:                    tc, | ||||
| 		Debug:                        b.GetBool("debug"), | ||||
| 		Logger:                       b.Log.Writer(), | ||||
| 		Session:                      true, | ||||
| 		Status:                       "", | ||||
| 		StatusMessage:                "", | ||||
| 		Resource:                     "", | ||||
| 		InsecureAllowUnencryptedAuth: false, | ||||
| 		//InsecureAllowUnencryptedAuth: true, | ||||
| 	} | ||||
| 	var err error | ||||
| 	b.xc, err = options.NewClient() | ||||
| @@ -123,10 +124,10 @@ func (b *Bxmpp) xmppKeepAlive() chan bool { | ||||
| 		for { | ||||
| 			select { | ||||
| 			case <-ticker.C: | ||||
| 				flog.Debugf("PING") | ||||
| 				b.Log.Debugf("PING") | ||||
| 				err := b.xc.PingC2S("", "") | ||||
| 				if err != nil { | ||||
| 					flog.Debugf("PING failed %#v", err) | ||||
| 					b.Log.Debugf("PING failed %#v", err) | ||||
| 				} | ||||
| 			case <-done: | ||||
| 				return | ||||
| @@ -136,11 +137,10 @@ func (b *Bxmpp) xmppKeepAlive() chan bool { | ||||
| 	return done | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) handleXmpp() error { | ||||
| func (b *Bxmpp) handleXMPP() error { | ||||
| 	var ok bool | ||||
| 	done := b.xmppKeepAlive() | ||||
| 	defer close(done) | ||||
| 	nodelay := time.Time{} | ||||
| 	for { | ||||
| 		m, err := b.xc.Recv() | ||||
| 		if err != nil { | ||||
| @@ -148,25 +148,22 @@ func (b *Bxmpp) handleXmpp() error { | ||||
| 		} | ||||
| 		switch v := m.(type) { | ||||
| 		case xmpp.Chat: | ||||
| 			var channel, nick string | ||||
| 			if v.Type == "groupchat" { | ||||
| 				s := strings.Split(v.Remote, "@") | ||||
| 				if len(s) >= 2 { | ||||
| 					channel = s[0] | ||||
| 				b.Log.Debugf("== Receiving %#v", v) | ||||
| 				// skip invalid messages | ||||
| 				if b.skipMessage(v) { | ||||
| 					continue | ||||
| 				} | ||||
| 				s = strings.Split(s[1], "/") | ||||
| 				if len(s) == 2 { | ||||
| 					nick = s[1] | ||||
| 				} | ||||
| 				if nick != b.Config.Nick && v.Stamp == nodelay && v.Text != "" { | ||||
| 					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 | ||||
| 				rmsg := config.Message{Username: b.parseNick(v.Remote), Text: v.Text, Channel: b.parseChannel(v.Remote), Account: b.Account, UserID: v.Remote} | ||||
|  | ||||
| 				// check if we have an action event | ||||
| 				rmsg.Text, ok = b.replaceAction(rmsg.Text) | ||||
| 				if ok { | ||||
| 					rmsg.Event = config.EVENT_USER_ACTION | ||||
| 				} | ||||
| 				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: | ||||
| 			// do nothing | ||||
| @@ -180,3 +177,62 @@ func (b *Bxmpp) replaceAction(text string) (string, bool) { | ||||
| 	} | ||||
| 	return text, false | ||||
| } | ||||
|  | ||||
| // handleUploadFile handles native upload of files | ||||
| func (b *Bxmpp) 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 | ||||
| 		} | ||||
| 		_, 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 | ||||
| } | ||||
|  | ||||
| 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 | ||||
| } | ||||
|   | ||||
							
								
								
									
										115
									
								
								changelog.md
									
									
									
									
									
								
							
							
						
						
									
										115
									
								
								changelog.md
									
									
									
									
									
								
							| @@ -1,3 +1,118 @@ | ||||
| # 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 | ||||
| ## Bugfix | ||||
| * general: Display of nicks not longer working (regression). Closes #323 | ||||
|  | ||||
| # v1.6.0 | ||||
| ## New features | ||||
| * sshchat: New protocol support added (https://github.com/shazow/ssh-chat) | ||||
| * general: Allow specifying maximum download size of media using MediaDownloadSize (slack,telegram,matrix) | ||||
| * api: Add (simple, one listener) long-polling support (api). Closes #307 | ||||
| * telegram: Add support for forwarded messages. Closes #313 | ||||
| * telegram: Add support for Audio/Voice files (telegram). Closes #314 | ||||
| * irc: Add RejoinDelay option. Delay to rejoin after channel kick (irc). Closes #322 | ||||
|  | ||||
| ## Bugfix | ||||
| * telegram: Also use HTML in edited messages (telegram). Closes #315 | ||||
| * matrix: Fix panic (matrix). Closes #316 | ||||
|  | ||||
| # v1.5.1 | ||||
|  | ||||
| ## Bugfix | ||||
| * irc: Fix irc ACTION regression (irc). Closes #306 | ||||
| * irc: Split on UTF-8 for MessageSplit (irc). Closes #308 | ||||
|  | ||||
| # v1.5.0 | ||||
| ## New features | ||||
| * general: remote mediaserver support. See MediaServerDownload and MediaServerUpload in matterbridge.toml.sample | ||||
|   more information on https://github.com/42wim/matterbridge/wiki/Mediaserver-setup-%5Badvanced%5D | ||||
| * general: Add support for ReplaceNicks using regexp to replace nicks. Closes #269 (see matterbridge.toml.sample) | ||||
| * general: Add support for ReplaceMessages using regexp to replace messages. #269 (see matterbridge.toml.sample) | ||||
| * irc: Add MessageSplit option to split messages on MessageLength (irc). Closes #281 | ||||
| * matrix: Add support for uploading images/video (matrix). Closes #302 | ||||
| * matrix: Add support for uploaded images/video (matrix)  | ||||
|  | ||||
| ## Bugfix | ||||
| * telegram: Add webp extension to stickers if necessary (telegram) | ||||
| * mattermost: Break when re-login fails (mattermost) | ||||
|  | ||||
| # v1.4.1 | ||||
| ## Bugfix | ||||
| * telegram: fix issue with uploading for images/documents/stickers | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| #!/bin/bash | ||||
| go version |grep go1.9 || exit | ||||
| go version |grep go1.10 || exit | ||||
| VERSION=$(git describe --tags) | ||||
| 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 | ||||
|   | ||||
							
								
								
									
										11
									
								
								docker/arm/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								docker/arm/Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| 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 | ||||
| @@ -1,13 +1,28 @@ | ||||
| package gateway | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/api" | ||||
| 	"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" | ||||
| 	//	"github.com/davecgh/go-spew/spew" | ||||
| 	"crypto/sha1" | ||||
| 	"github.com/hashicorp/golang-lru" | ||||
| 	"github.com/peterhellberg/emojilib" | ||||
| 	"net/http" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| @@ -26,8 +41,30 @@ type Gateway struct { | ||||
| } | ||||
|  | ||||
| type BrMsgID struct { | ||||
| 	br *bridge.Bridge | ||||
| 	ID string | ||||
| 	br        *bridge.Bridge | ||||
| 	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, | ||||
| } | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"prefix": "gateway"}) | ||||
| } | ||||
|  | ||||
| func New(cfg config.Gateway, r *Router) *Gateway { | ||||
| @@ -42,7 +79,14 @@ func New(cfg config.Gateway, r *Router) *Gateway { | ||||
| func (gw *Gateway) AddBridge(cfg *config.Bridge) error { | ||||
| 	br := gw.Router.getBridge(cfg.Account) | ||||
| 	if br == nil { | ||||
| 		br = bridge.New(gw.Config, cfg, gw.Message) | ||||
| 		br = bridge.New(cfg) | ||||
| 		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.Bridges[cfg.Account] = br | ||||
| @@ -74,10 +118,10 @@ func (gw *Gateway) reconnectBridge(br *bridge.Bridge) { | ||||
| 	br.Disconnect() | ||||
| 	time.Sleep(time.Second * 5) | ||||
| RECONNECT: | ||||
| 	log.Infof("Reconnecting %s", br.Account) | ||||
| 	flog.Infof("Reconnecting %s", br.Account) | ||||
| 	err := br.Connect() | ||||
| 	if err != nil { | ||||
| 		log.Errorf("Reconnection failed: %s. Trying again in 60 seconds", err) | ||||
| 		flog.Errorf("Reconnection failed: %s. Trying again in 60 seconds", err) | ||||
| 		time.Sleep(time.Second * 60) | ||||
| 		goto RECONNECT | ||||
| 	} | ||||
| @@ -90,6 +134,10 @@ func (gw *Gateway) mapChannelConfig(cfg []config.Bridge, direction string) { | ||||
| 		if isApi(br.Account) { | ||||
| 			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 | ||||
| 		if _, ok := gw.Channels[ID]; !ok { | ||||
| 			channel := &config.ChannelInfo{Name: br.Channel, Direction: direction, ID: ID, Options: br.Options, Account: br.Account, | ||||
| @@ -115,6 +163,12 @@ func (gw *Gateway) mapChannels() error { | ||||
|  | ||||
| func (gw *Gateway) getDestChannel(msg *config.Message, dest bridge.Bridge) []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 | ||||
| 	for _, channel := range gw.Channels { | ||||
| 		// lookup the channel from the message | ||||
| @@ -131,7 +185,7 @@ func (gw *Gateway) getDestChannel(msg *config.Message, dest bridge.Bridge) []con | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// do samechannelgateway logic | ||||
| 		// do samechannelgateway flogic | ||||
| 		if channel.SameChannel[msg.Gateway] { | ||||
| 			if msg.Channel == channel.Name && msg.Account != dest.Account { | ||||
| 				channels = append(channels, *channel) | ||||
| @@ -148,37 +202,55 @@ func (gw *Gateway) getDestChannel(msg *config.Message, dest bridge.Bridge) []con | ||||
| func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrMsgID { | ||||
| 	var brMsgIDs []*BrMsgID | ||||
|  | ||||
| 	// 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 we have an attached file, or other info | ||||
| 	if msg.Extra != nil { | ||||
| 		if dest.Protocol != "discord" && | ||||
| 			dest.Protocol != "slack" && | ||||
| 			dest.Protocol != "mattermost" && | ||||
| 			dest.Protocol != "telegram" { | ||||
| 		if len(msg.Extra[config.EVENT_FILE_FAILURE_SIZE]) != 0 { | ||||
| 			if msg.Text == "" { | ||||
| 				return brMsgIDs | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	// only relay join/part when configged | ||||
| 	if msg.Event == config.EVENT_JOIN_LEAVE && !gw.Bridges[dest.Account].Config.ShowJoinPart { | ||||
|  | ||||
| 	// Avatar downloads are only relevant for telegram and mattermost for now | ||||
| 	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 | ||||
| 	} | ||||
|  | ||||
| 	// 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) | ||||
| 	if msg.Channel == "" && msg.Event != config.EVENT_JOIN_LEAVE { | ||||
| 		log.Debug("empty channel") | ||||
| 		flog.Debug("empty channel") | ||||
| 		return brMsgIDs | ||||
| 	} | ||||
|  | ||||
| 	originchannel := msg.Channel | ||||
| 	origmsg := msg | ||||
| 	channels := gw.getDestChannel(&msg, *dest) | ||||
| 	for _, channel := range channels { | ||||
| 		// do not send to ourself | ||||
| 		if channel.ID == getChannelID(origmsg) { | ||||
| 			continue | ||||
| 		// Only send the avatar download event to ourselves. | ||||
| 		if msg.Event == config.EVENT_AVATAR_DOWNLOAD { | ||||
| 			if channel.ID != getChannelID(origmsg) { | ||||
| 				continue | ||||
| 			} | ||||
| 		} else { | ||||
| 			// do not send to ourself for any other event | ||||
| 			if channel.ID == getChannelID(origmsg) { | ||||
| 				continue | ||||
| 			} | ||||
| 		} | ||||
| 		log.Debugf("Sending %#v from %s (%s) to %s (%s)", msg, msg.Account, originchannel, dest.Account, channel.Name) | ||||
| 		flog.Debugf("=> Sending %#v from %s (%s) to %s (%s)", msg, msg.Account, originchannel, dest.Account, channel.Name) | ||||
| 		msg.Channel = channel.Name | ||||
| 		msg.Avatar = gw.modifyAvatar(origmsg, dest) | ||||
| 		msg.Username = gw.modifyUsername(origmsg, dest) | ||||
| @@ -186,7 +258,9 @@ func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrM | ||||
| 		if res, ok := gw.Messages.Get(origmsg.ID); ok { | ||||
| 			IDs := res.([]*BrMsgID) | ||||
| 			for _, id := range IDs { | ||||
| 				if dest.Protocol == id.br.Protocol { | ||||
| 				// check protocol, bridge name and channelname | ||||
| 				// 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 | ||||
| 				} | ||||
| 			} | ||||
| @@ -197,11 +271,12 @@ func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrM | ||||
| 		} | ||||
| 		mID, err := dest.Send(msg) | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			flog.Error(err) | ||||
| 		} | ||||
| 		// append the message ID (mID) from this bridge (dest) to our brMsgIDs slice | ||||
| 		if mID != "" { | ||||
| 			brMsgIDs = append(brMsgIDs, &BrMsgID{dest, mID}) | ||||
| 			flog.Debugf("mID %s: %s", dest.Account, mID) | ||||
| 			brMsgIDs = append(brMsgIDs, &BrMsgID{dest, mID, channel.ID}) | ||||
| 		} | ||||
| 	} | ||||
| 	return brMsgIDs | ||||
| @@ -212,30 +287,39 @@ func (gw *Gateway) ignoreMessage(msg *config.Message) bool { | ||||
| 	if _, ok := gw.Bridges[msg.Account]; !ok { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// check if we need to ignore a empty message | ||||
| 	if msg.Text == "" { | ||||
| 		// we have an attachment or actual bytes | ||||
| 		if msg.Extra != nil && (msg.Extra["attachments"] != nil || len(msg.Extra["file"]) > 0) { | ||||
| 		// we have an attachment or actual bytes, do not ignore | ||||
| 		if msg.Extra != nil && | ||||
| 			(msg.Extra["attachments"] != nil || | ||||
| 				len(msg.Extra["file"]) > 0 || | ||||
| 				len(msg.Extra[config.EVENT_FILE_FAILURE_SIZE]) > 0) { | ||||
| 			return false | ||||
| 		} | ||||
| 		log.Debugf("ignoring empty message %#v from %s", msg, msg.Account) | ||||
| 		flog.Debugf("ignoring empty message %#v from %s", msg, msg.Account) | ||||
| 		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 { | ||||
| 			log.Debugf("ignoring %s from %s", msg.Username, msg.Account) | ||||
| 			flog.Debugf("ignoring %s from %s", msg.Username, msg.Account) | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// does the message match regex in IgnoreMessages field | ||||
| 	// TODO do not compile regexps everytime | ||||
| 	for _, entry := range strings.Fields(gw.Bridges[msg.Account].Config.IgnoreMessages) { | ||||
| 	for _, entry := range strings.Fields(gw.Bridges[msg.Account].GetString("IgnoreMessages")) { | ||||
| 		if entry != "" { | ||||
| 			re, err := regexp.Compile(entry) | ||||
| 			if err != nil { | ||||
| 				log.Errorf("incorrect regexp %s for %s", entry, msg.Account) | ||||
| 				flog.Errorf("incorrect regexp %s for %s", entry, msg.Account) | ||||
| 				continue | ||||
| 			} | ||||
| 			if re.MatchString(msg.Text) { | ||||
| 				log.Debugf("matching %s. ignoring %s from %s", entry, msg.Text, msg.Account) | ||||
| 				flog.Debugf("matching %s. ignoring %s from %s", entry, msg.Text, msg.Account) | ||||
| 				return true | ||||
| 			} | ||||
| 		} | ||||
| @@ -246,14 +330,28 @@ func (gw *Gateway) ignoreMessage(msg *config.Message) bool { | ||||
| func (gw *Gateway) modifyUsername(msg config.Message, dest *bridge.Bridge) string { | ||||
| 	br := gw.Bridges[msg.Account] | ||||
| 	msg.Protocol = br.Protocol | ||||
| 	if gw.Config.General.StripNick || dest.Config.StripNick { | ||||
| 	if gw.Config.General.StripNick || dest.GetBool("StripNick") { | ||||
| 		re := regexp.MustCompile("[^a-zA-Z0-9]+") | ||||
| 		msg.Username = re.ReplaceAllString(msg.Username, "") | ||||
| 	} | ||||
| 	nick := dest.Config.RemoteNickFormat | ||||
| 	nick := dest.GetString("RemoteNickFormat") | ||||
| 	if nick == "" { | ||||
| 		nick = gw.Config.General.RemoteNickFormat | ||||
| 	} | ||||
|  | ||||
| 	// loop to replace nicks | ||||
| 	for _, outer := range br.GetStringSlice2D("ReplaceNicks") { | ||||
| 		search := outer[0] | ||||
| 		replace := outer[1] | ||||
| 		// TODO move compile to bridge init somewhere | ||||
| 		re, err := regexp.Compile(search) | ||||
| 		if err != nil { | ||||
| 			flog.Errorf("regexp in %s failed: %s", msg.Account, err) | ||||
| 			break | ||||
| 		} | ||||
| 		msg.Username = re.ReplaceAllString(msg.Username, replace) | ||||
| 	} | ||||
|  | ||||
| 	if len(msg.Username) > 0 { | ||||
| 		// fix utf-8 issue #193 | ||||
| 		i := 0 | ||||
| @@ -266,16 +364,18 @@ 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, "{NICK}", msg.Username, -1) | ||||
|  | ||||
| 	nick = strings.Replace(nick, "{BRIDGE}", br.Name, -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 | ||||
| } | ||||
|  | ||||
| func (gw *Gateway) modifyAvatar(msg config.Message, dest *bridge.Bridge) string { | ||||
| 	iconurl := gw.Config.General.IconURL | ||||
| 	if iconurl == "" { | ||||
| 		iconurl = dest.Config.IconURL | ||||
| 		iconurl = dest.GetString("IconURL") | ||||
| 	} | ||||
| 	iconurl = strings.Replace(iconurl, "{NICK}", msg.Username, -1) | ||||
| 	if msg.Avatar == "" { | ||||
| @@ -287,17 +387,69 @@ func (gw *Gateway) modifyAvatar(msg config.Message, dest *bridge.Bridge) string | ||||
| func (gw *Gateway) modifyMessage(msg *config.Message) { | ||||
| 	// replace :emoji: to unicode | ||||
| 	msg.Text = emojilib.Replace(msg.Text) | ||||
| 	msg.Gateway = gw.Name | ||||
|  | ||||
| 	br := gw.Bridges[msg.Account] | ||||
| 	// loop to replace messages | ||||
| 	for _, outer := range br.GetStringSlice2D("ReplaceMessages") { | ||||
| 		search := outer[0] | ||||
| 		replace := outer[1] | ||||
| 		// TODO move compile to bridge init somewhere | ||||
| 		re, err := regexp.Compile(search) | ||||
| 		if err != nil { | ||||
| 			flog.Errorf("regexp in %s failed: %s", msg.Account, err) | ||||
| 			break | ||||
| 		} | ||||
| 		msg.Text = re.ReplaceAllString(msg.Text, replace) | ||||
| 	} | ||||
|  | ||||
| 	// messages from api have Gateway specified, don't overwrite | ||||
| 	if msg.Protocol != "api" { | ||||
| 		msg.Gateway = gw.Name | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func getChannelID(msg config.Message) string { | ||||
| 	return msg.Channel + msg.Account | ||||
| func (gw *Gateway) handleFiles(msg *config.Message) { | ||||
| 	// if we don't have a attachfield or we don't have a mediaserver configured return | ||||
| 	if msg.Extra == nil || gw.Config.General.MediaServerUpload == "" { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// if we actually have files, start uploading them to the mediaserver | ||||
| 	if len(msg.Extra["file"]) > 0 { | ||||
| 		client := &http.Client{ | ||||
| 			Timeout: time.Second * 5, | ||||
| 		} | ||||
| 		for i, f := range msg.Extra["file"] { | ||||
| 			fi := f.(config.FileInfo) | ||||
| 			sha1sum := fmt.Sprintf("%x", sha1.Sum(*fi.Data)) | ||||
| 			reader := bytes.NewReader(*fi.Data) | ||||
| 			url := gw.Config.General.MediaServerUpload + "/" + sha1sum + "/" + fi.Name | ||||
| 			durl := gw.Config.General.MediaServerDownload + "/" + sha1sum + "/" + fi.Name | ||||
| 			extra := msg.Extra["file"][i].(config.FileInfo) | ||||
| 			extra.URL = durl | ||||
| 			req, _ := http.NewRequest("PUT", url, reader) | ||||
| 			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 | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (gw *Gateway) validGatewayDest(msg *config.Message, channel *config.ChannelInfo) bool { | ||||
| 	return msg.Gateway == gw.Name | ||||
| } | ||||
|  | ||||
| func getChannelID(msg config.Message) string { | ||||
| 	return msg.Channel + msg.Account | ||||
| } | ||||
|  | ||||
| func isApi(account string) bool { | ||||
| 	return strings.HasPrefix(account, "api.") | ||||
| } | ||||
|   | ||||
| @@ -3,14 +3,13 @@ package gateway | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/BurntSushi/toml" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"strconv" | ||||
|  | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| var testconfig = ` | ||||
| var testconfig = []byte(` | ||||
| [irc.freenode] | ||||
| [mattermost.test] | ||||
| [gitter.42wim] | ||||
| @@ -37,9 +36,9 @@ var testconfig = ` | ||||
|     [[gateway.inout]] | ||||
|     account="slack.test" | ||||
|     channel="testing" | ||||
| 	` | ||||
| 	`) | ||||
|  | ||||
| var testconfig2 = ` | ||||
| var testconfig2 = []byte(` | ||||
| [irc.freenode] | ||||
| [mattermost.test] | ||||
| [gitter.42wim] | ||||
| @@ -80,8 +79,9 @@ var testconfig2 = ` | ||||
|     [[gateway.out]] | ||||
|     account = "discord.test" | ||||
|     channel = "general2" | ||||
| 	` | ||||
| var testconfig3 = ` | ||||
| 	`) | ||||
|  | ||||
| var testconfig3 = []byte(` | ||||
| [irc.zzz] | ||||
| [telegram.zzz] | ||||
| [slack.zzz] | ||||
| @@ -149,13 +149,10 @@ enable=true | ||||
|     [[gateway.inout]] | ||||
|     account="telegram.zzz" | ||||
|     channel="--333333333333" | ||||
| ` | ||||
| `) | ||||
|  | ||||
| func maketestRouter(input string) *Router { | ||||
| 	var cfg *config.Config | ||||
| 	if _, err := toml.Decode(input, &cfg); err != nil { | ||||
| 		fmt.Println(err) | ||||
| 	} | ||||
| func maketestRouter(input []byte) *Router { | ||||
| 	cfg := config.NewConfigFromString(input) | ||||
| 	r, err := NewRouter(cfg) | ||||
| 	if err != nil { | ||||
| 		fmt.Println(err) | ||||
| @@ -163,14 +160,7 @@ func maketestRouter(input string) *Router { | ||||
| 	return r | ||||
| } | ||||
| func TestNewRouter(t *testing.T) { | ||||
| 	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) | ||||
| 	} | ||||
| 	r := maketestRouter(testconfig) | ||||
| 	assert.Equal(t, 1, len(r.Gateways)) | ||||
| 	assert.Equal(t, 4, len(r.Gateways["bridge1"].Bridges)) | ||||
| 	assert.Equal(t, 4, len(r.Gateways["bridge1"].Channels)) | ||||
|   | ||||
| @@ -5,7 +5,6 @@ import ( | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/gateway/samechannel" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| 	//	"github.com/davecgh/go-spew/spew" | ||||
| 	"time" | ||||
| ) | ||||
| @@ -17,10 +16,7 @@ type Router struct { | ||||
| } | ||||
|  | ||||
| func NewRouter(cfg *config.Config) (*Router, error) { | ||||
| 	r := &Router{} | ||||
| 	r.Config = cfg | ||||
| 	r.Message = make(chan config.Message) | ||||
| 	r.Gateways = make(map[string]*Gateway) | ||||
| 	r := &Router{Message: make(chan config.Message), Gateways: make(map[string]*Gateway), Config: cfg} | ||||
| 	sgw := samechannelgateway.New(cfg) | ||||
| 	gwconfigs := sgw.GetConfig() | ||||
|  | ||||
| @@ -42,12 +38,13 @@ func NewRouter(cfg *config.Config) (*Router, error) { | ||||
| func (r *Router) Start() error { | ||||
| 	m := make(map[string]*bridge.Bridge) | ||||
| 	for _, gw := range r.Gateways { | ||||
| 		flog.Infof("Parsing gateway %s", gw.Name) | ||||
| 		for _, br := range gw.Bridges { | ||||
| 			m[br.Account] = br | ||||
| 		} | ||||
| 	} | ||||
| 	for _, br := range m { | ||||
| 		log.Infof("Starting bridge: %s ", br.Account) | ||||
| 		flog.Infof("Starting bridge: %s ", br.Account) | ||||
| 		err := br.Connect() | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("Bridge %s failed to start: %v", br.Account, err) | ||||
| @@ -99,6 +96,7 @@ func (r *Router) handleReceive() { | ||||
| 			if !gw.ignoreMessage(&msg) { | ||||
| 				msg.Timestamp = time.Now() | ||||
| 				gw.modifyMessage(&msg) | ||||
| 				gw.handleFiles(&msg) | ||||
| 				for _, br := range gw.Bridges { | ||||
| 					msgIDs = append(msgIDs, gw.handleMessage(msg, br)...) | ||||
| 				} | ||||
|   | ||||
| @@ -5,22 +5,21 @@ import ( | ||||
| 	"fmt" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/gateway" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| 	"github.com/google/gops/agent" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	prefixed "github.com/x-cray/logrus-prefixed-formatter" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	version = "1.4.1" | ||||
| 	version = "1.9.1" | ||||
| 	githash string | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	log.SetFormatter(&log.TextFormatter{FullTimestamp: true}) | ||||
| } | ||||
|  | ||||
| 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") | ||||
| 	flagDebug := flag.Bool("debug", false, "enable debug") | ||||
| 	flagVersion := flag.Bool("version", false, "show version") | ||||
| @@ -35,22 +34,24 @@ func main() { | ||||
| 		return | ||||
| 	} | ||||
| 	if *flagDebug || os.Getenv("DEBUG") == "1" { | ||||
| 		log.Info("Enabling debug") | ||||
| 		log.SetFormatter(&prefixed.TextFormatter{PrefixPadding: 13, DisableColors: true, FullTimestamp: false, ForceFormatting: true}) | ||||
| 		flog.Info("Enabling debug") | ||||
| 		log.SetLevel(log.DebugLevel) | ||||
| 	} | ||||
| 	log.Printf("Running version %s %s", version, githash) | ||||
| 	flog.Printf("Running version %s %s", version, githash) | ||||
| 	if strings.Contains(version, "-dev") { | ||||
| 		log.Println("WARNING: THIS IS A DEVELOPMENT VERSION. Things may break.") | ||||
| 		flog.Println("WARNING: THIS IS A DEVELOPMENT VERSION. Things may break.") | ||||
| 	} | ||||
| 	cfg := config.NewConfig(*flagConfig) | ||||
| 	cfg.General.Debug = *flagDebug | ||||
| 	r, err := gateway.NewRouter(cfg) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Starting gateway failed: %s", err) | ||||
| 		flog.Fatalf("Starting gateway failed: %s", err) | ||||
| 	} | ||||
| 	err = r.Start() | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Starting gateway failed: %s", err) | ||||
| 		flog.Fatalf("Starting gateway failed: %s", err) | ||||
| 	} | ||||
| 	log.Printf("Gateway(s) started succesfully. Now relaying messages") | ||||
| 	flog.Printf("Gateway(s) started succesfully. Now relaying messages") | ||||
| 	select {} | ||||
| } | ||||
|   | ||||
| @@ -80,6 +80,15 @@ MessageQueue=30 | ||||
| #OPTIONAL (default 400) | ||||
| MessageLength=400 | ||||
|  | ||||
| #Split messages on MessageLength instead of showing the <message clipped> | ||||
| #WARNING: this could lead to flooding | ||||
| #OPTIONAL (default false) | ||||
| MessageSplit=false | ||||
|  | ||||
| #Delay in seconds to rejoin a channel when kicked | ||||
| #OPTIONAL (default 0) | ||||
| RejoinDelay=0 | ||||
|  | ||||
| #Nicks you want to ignore.  | ||||
| #Messages from those users will not be sent to other bridges. | ||||
| #OPTIONAL | ||||
| @@ -91,24 +100,56 @@ IgnoreNicks="ircspammer1 ircspammer2" | ||||
| #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 | ||||
| #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) | ||||
| RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||
|  | ||||
| #Enable to show users joins/parts from other bridges  | ||||
| #Only works hiding/show messages from irc and mattermost bridge for now | ||||
| #Currently works for messages from the following bridges: irc, mattermost, slack | ||||
| #OPTIONAL (default 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 | ||||
| #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 | ||||
|  | ||||
| ################################################################### | ||||
| #XMPP section | ||||
| ################################################################### | ||||
| @@ -154,15 +195,37 @@ IgnoreNicks="ircspammer1 ircspammer2" | ||||
| #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  | ||||
| #Only works hiding/show messages from irc and mattermost bridge for now | ||||
| #Currently works for messages from the following bridges: irc, mattermost, slack | ||||
| #OPTIONAL (default false) | ||||
| ShowJoinPart=false | ||||
|  | ||||
| @@ -171,6 +234,11 @@ ShowJoinPart=false | ||||
| #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 | ||||
|  | ||||
| ################################################################### | ||||
| #hipchat section | ||||
| ################################################################### | ||||
| @@ -208,15 +276,37 @@ IgnoreNicks="spammer1 spammer2" | ||||
| #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}/{BRIDGE}] <{NICK}> " | ||||
|  | ||||
| #Enable to show users joins/parts from other bridges  | ||||
| #Only works hiding/show messages from irc and mattermost bridge for now | ||||
| #Currently works for messages from the following bridges: irc, mattermost, slack | ||||
| #OPTIONAL (default false) | ||||
| ShowJoinPart=false | ||||
|  | ||||
| @@ -225,6 +315,11 @@ ShowJoinPart=false | ||||
| #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 | ||||
|  | ||||
| ################################################################### | ||||
| #mattermost section | ||||
| ################################################################### | ||||
| @@ -322,23 +417,55 @@ IgnoreNicks="ircspammer1 ircspammer2" | ||||
| #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  | ||||
| #Only works hiding/show messages from irc and mattermost bridge for now | ||||
| #Currently works for messages from the following bridges: irc, mattermost, slack | ||||
| #OPTIONAL (default 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 | ||||
| #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 | ||||
|  | ||||
| ################################################################### | ||||
| #Gitter section | ||||
| #Best to make a dedicated gitter account for the bot. | ||||
| @@ -366,15 +493,37 @@ IgnoreNicks="ircspammer1 ircspammer2" | ||||
| #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  | ||||
| #Only works hiding/show messages from irc and mattermost bridge for now | ||||
| #Currently works for messages from the following bridges: irc, mattermost, slack | ||||
| #OPTIONAL (default false) | ||||
| ShowJoinPart=false | ||||
|  | ||||
| @@ -383,6 +532,11 @@ ShowJoinPart=false | ||||
| #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 | ||||
|  | ||||
| ################################################################### | ||||
| #slack section | ||||
| ################################################################### | ||||
| @@ -418,6 +572,7 @@ WebhookBindAddress="0.0.0.0:9999" | ||||
| #Icon that will be showed in slack | ||||
| #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 | ||||
| IconURL="https://robohash.org/{NICK}.png?size=48x48" | ||||
| @@ -457,23 +612,55 @@ IgnoreNicks="ircspammer1 ircspammer2" | ||||
| #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  | ||||
| #Only works hiding/show messages from irc and mattermost bridge for now | ||||
| #Currently works for messages from the following bridges: irc, mattermost, slack | ||||
| #OPTIONAL (default 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 | ||||
| #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 | ||||
|  | ||||
| ################################################################### | ||||
| #discord section | ||||
| ################################################################### | ||||
| @@ -525,15 +712,37 @@ IgnoreNicks="ircspammer1 ircspammer2" | ||||
| #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  | ||||
| #Only works hiding/show messages from irc and mattermost bridge for now | ||||
| #Currently works for messages from the following bridges: irc, mattermost, slack | ||||
| #OPTIONAL (default false) | ||||
| ShowJoinPart=false | ||||
|  | ||||
| @@ -542,6 +751,11 @@ ShowJoinPart=false | ||||
| #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 | ||||
|  | ||||
| ################################################################### | ||||
| #telegram section | ||||
| ################################################################### | ||||
| @@ -573,6 +787,10 @@ UseFirstName=false | ||||
| #OPTIONAL (default false) | ||||
| UseInsecureURL=false | ||||
|  | ||||
| #Disable quoted/reply messages | ||||
| #OPTIONAL (default false) | ||||
| QuoteDisable=false | ||||
|  | ||||
| #Disable sending of edits to other bridges | ||||
| #OPTIONAL (default false) | ||||
| EditDisable=false | ||||
| @@ -592,15 +810,42 @@ IgnoreNicks="spammer1 spammer2" | ||||
| #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 | ||||
| # | ||||
| #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) | ||||
| RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||
|  | ||||
| #Enable to show users joins/parts from other bridges  | ||||
| #Only works hiding/show messages from irc and mattermost bridge for now | ||||
| #Currently works for messages from the following bridges: irc, mattermost, slack | ||||
| #OPTIONAL (default false) | ||||
| ShowJoinPart=false | ||||
|  | ||||
| @@ -609,6 +854,11 @@ ShowJoinPart=false | ||||
| #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 | ||||
|  | ||||
| ################################################################### | ||||
| #rocketchat section | ||||
| ################################################################### | ||||
| @@ -660,15 +910,37 @@ IgnoreNicks="ircspammer1 ircspammer2" | ||||
| #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  | ||||
| #Only works hiding/show messages from irc and mattermost bridge for now | ||||
| #Currently works for messages from the following bridges: irc, mattermost, slack | ||||
| #OPTIONAL (default false) | ||||
| ShowJoinPart=false | ||||
|  | ||||
| @@ -677,6 +949,11 @@ ShowJoinPart=false | ||||
| #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 | ||||
|  | ||||
| ################################################################### | ||||
| #matrix section | ||||
| ################################################################### | ||||
| @@ -720,15 +997,37 @@ IgnoreNicks="spammer1 spammer2" | ||||
| #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  | ||||
| #Only works hiding/show messages from irc and mattermost bridge for now | ||||
| #Currently works for messages from the following bridges: irc, mattermost, slack | ||||
| #OPTIONAL (default false) | ||||
| ShowJoinPart=false | ||||
|  | ||||
| @@ -737,6 +1036,11 @@ ShowJoinPart=false | ||||
| #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 | ||||
|  | ||||
| ################################################################### | ||||
| #steam section | ||||
| ################################################################### | ||||
| @@ -774,15 +1078,37 @@ IgnoreNicks="spammer1 spammer2" | ||||
| #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  | ||||
| #Only works hiding/show messages from irc and mattermost bridge for now | ||||
| #Currently works for messages from the following bridges: irc, mattermost, slack | ||||
| #OPTIONAL (default false) | ||||
| ShowJoinPart=false | ||||
|  | ||||
| @@ -791,6 +1117,11 @@ ShowJoinPart=false | ||||
| #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 | ||||
| ################################################################### | ||||
| @@ -812,9 +1143,14 @@ Buffer=1000 | ||||
| #OPTIONAL (no authorization if token is empty) | ||||
| Token="mytoken" | ||||
|  | ||||
| #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="{NICK}" | ||||
| @@ -829,6 +1165,7 @@ RemoteNickFormat="{NICK}" | ||||
| #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}> " | ||||
| @@ -838,6 +1175,30 @@ RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||
| #OPTIONAL (default false) | ||||
| StripNick=false | ||||
|  | ||||
|  | ||||
| #MediaServerUpload and MediaServerDownload are used for uploading images/files/video to | ||||
| #a remote "mediaserver" (a webserver like caddy for example). | ||||
| #When configured images/files uploaded on bridges like mattermost,slack, telegram will be downloaded | ||||
| #and uploaded again to MediaServerUpload URL | ||||
| #The MediaServerDownload will be used so that bridges without native uploading support: | ||||
| #gitter, irc and xmpp will be shown links to the files on MediaServerDownload | ||||
| # | ||||
| #More information https://github.com/42wim/matterbridge/wiki/Mediaserver-setup-%5Badvanced%5D | ||||
| #OPTIONAL (default empty) | ||||
| MediaServerUpload="https://user:pass@yourserver.com/upload" | ||||
| #OPTIONAL (default empty) | ||||
| MediaServerDownload="https://youserver.com/download" | ||||
|  | ||||
| #MediaDownloadSize is the maximum size of attachments, videos, images | ||||
| #matterbridge will download and upload this file to bridges that also support uploading files. | ||||
| #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 | ||||
| #slack, telegram, matrix and mattermost | ||||
| # | ||||
| #Optional (default 1000000 (1 megabyte)) | ||||
| MediaDownloadSize=1000000 | ||||
|  | ||||
| ################################################################### | ||||
| #Gateway configuration | ||||
| ################################################################### | ||||
|   | ||||
| @@ -13,7 +13,8 @@ import ( | ||||
| 	"sync" | ||||
| 	"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/hashicorp/golang-lru" | ||||
| @@ -73,12 +74,16 @@ type MMClient struct { | ||||
| func New(login, pass, team, server string) *MMClient { | ||||
| 	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.log = log.WithFields(log.Fields{"module": "matterclient"}) | ||||
| 	log.SetFormatter(&log.TextFormatter{FullTimestamp: true}) | ||||
| 	log.SetFormatter(&prefixed.TextFormatter{PrefixPadding: 13, DisableColors: true}) | ||||
| 	mmclient.log = log.WithFields(log.Fields{"prefix": "matterclient"}) | ||||
| 	mmclient.lruCache, _ = lru.New(500) | ||||
| 	return mmclient | ||||
| } | ||||
|  | ||||
| func (m *MMClient) SetDebugLog() { | ||||
| 	log.SetFormatter(&prefixed.TextFormatter{PrefixPadding: 13, DisableColors: true, FullTimestamp: false, ForceFormatting: true}) | ||||
| } | ||||
|  | ||||
| func (m *MMClient) SetLogLevel(level string) { | ||||
| 	l, err := log.ParseLevel(level) | ||||
| 	if err != nil { | ||||
| @@ -185,7 +190,11 @@ func (m *MMClient) Login() error { | ||||
| 	} | ||||
|  | ||||
| 	if m.Team == nil { | ||||
| 		return errors.New("team not found") | ||||
| 		validTeamNames := make([]string, len(m.OtherTeams)) | ||||
| 		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() | ||||
| @@ -585,9 +594,9 @@ func (m *MMClient) UpdateChannelHeader(channelId string, header string) { | ||||
| func (m *MMClient) UpdateLastViewed(channelId string) { | ||||
| 	m.log.Debugf("posting lastview %#v", channelId) | ||||
| 	view := &model.ChannelView{ChannelId: channelId} | ||||
| 	res, _ := m.Client.ViewChannel(m.User.Id, view) | ||||
| 	if !res { | ||||
| 		m.log.Errorf("ChannelView update for %s failed", channelId) | ||||
| 	_, resp := m.Client.ViewChannel(m.User.Id, view) | ||||
| 	if resp.Error != nil { | ||||
| 		m.log.Errorf("ChannelView update for %s failed: %s", channelId, resp.Error) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -763,6 +772,14 @@ func (m *MMClient) GetStatus(userId string) string { | ||||
| 	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 { | ||||
| 	var ids []string | ||||
| 	statuses := make(map[string]string) | ||||
| @@ -817,9 +834,14 @@ func (m *MMClient) StatusLoop() { | ||||
| 				backoff = time.Second * 60 | ||||
| 			case <-time.After(time.Second * 5): | ||||
| 				if retries > 3 { | ||||
| 					m.log.Debug("StatusLoop() timeout") | ||||
| 					m.Logout() | ||||
| 					m.WsQuit = false | ||||
| 					m.Login() | ||||
| 					err := m.Login() | ||||
| 					if err != nil { | ||||
| 						log.Errorf("Login failed: %#v", err) | ||||
| 						break | ||||
| 					} | ||||
| 					if m.OnWsConnect != nil { | ||||
| 						m.OnWsConnect() | ||||
| 					} | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"github.com/gorilla/schema" | ||||
| 	"github.com/nlopes/slack" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| @@ -22,7 +23,7 @@ type OMessage struct { | ||||
| 	IconEmoji   string                 `json:"icon_emoji,omitempty"` | ||||
| 	UserName    string                 `json:"username,omitempty"` | ||||
| 	Text        string                 `json:"text"` | ||||
| 	Attachments interface{}            `json:"attachments,omitempty"` | ||||
| 	Attachments []slack.Attachment     `json:"attachments,omitempty"` | ||||
| 	Type        string                 `json:"type,omitempty"` | ||||
| 	Props       map[string]interface{} `json:"props"` | ||||
| } | ||||
|   | ||||
							
								
								
									
										30
									
								
								vendor/github.com/Sirupsen/logrus/examples/hook/hook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										30
									
								
								vendor/github.com/Sirupsen/logrus/examples/hook/hook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,30 +0,0 @@ | ||||
| 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!") | ||||
| } | ||||
							
								
								
									
										67
									
								
								vendor/github.com/Sirupsen/logrus/hooks/test/test.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										67
									
								
								vendor/github.com/Sirupsen/logrus/hooks/test/test.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,67 +0,0 @@ | ||||
| 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) | ||||
| } | ||||
							
								
								
									
										10
									
								
								vendor/github.com/Sirupsen/logrus/terminal_appengine.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								vendor/github.com/Sirupsen/logrus/terminal_appengine.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,10 +0,0 @@ | ||||
| // +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
									
									
								
							
							
						
						
									
										10
									
								
								vendor/github.com/Sirupsen/logrus/terminal_bsd.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,10 +0,0 @@ | ||||
| // +build darwin freebsd openbsd netbsd dragonfly | ||||
| // +build !appengine | ||||
|  | ||||
| package logrus | ||||
|  | ||||
| import "syscall" | ||||
|  | ||||
| const ioctlReadTermios = syscall.TIOCGETA | ||||
|  | ||||
| type Termios syscall.Termios | ||||
							
								
								
									
										28
									
								
								vendor/github.com/Sirupsen/logrus/terminal_notwindows.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										28
									
								
								vendor/github.com/Sirupsen/logrus/terminal_notwindows.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,28 +0,0 @@ | ||||
| // 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
									
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/Sirupsen/logrus/terminal_solaris.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,21 +0,0 @@ | ||||
| // +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
									
									
								
							
							
						
						
									
										33
									
								
								vendor/github.com/Sirupsen/logrus/terminal_windows.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,33 +0,0 @@ | ||||
| // 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 | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										362
									
								
								vendor/github.com/armon/consul-api/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										362
									
								
								vendor/github.com/armon/consul-api/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,362 @@ | ||||
| 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
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								vendor/github.com/armon/consul-api/acl.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,140 @@ | ||||
| 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
									
									
										Normal file
									
								
							
							
						
						
									
										272
									
								
								vendor/github.com/armon/consul-api/agent.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,272 @@ | ||||
| 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
									
									
										Normal file
									
								
							
							
						
						
									
										323
									
								
								vendor/github.com/armon/consul-api/api.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,323 @@ | ||||
| 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
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								vendor/github.com/armon/consul-api/catalog.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,181 @@ | ||||
| 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
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								vendor/github.com/armon/consul-api/event.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | ||||
| 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
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								vendor/github.com/armon/consul-api/health.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | ||||
| 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
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								vendor/github.com/armon/consul-api/kv.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,219 @@ | ||||
| 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
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								vendor/github.com/armon/consul-api/session.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,204 @@ | ||||
| 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
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								vendor/github.com/armon/consul-api/status.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| 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/) | ||||
| const VERSION = "0.17.0" | ||||
| const VERSION = "0.18.0" | ||||
|  | ||||
| // ErrMFA will be risen by New when the user has 2FA. | ||||
| 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. | ||||
| 	s = &Session{ | ||||
| 		State:                  NewState(), | ||||
| 		ratelimiter:            NewRatelimiter(), | ||||
| 		Ratelimiter:            NewRatelimiter(), | ||||
| 		StateEnabled:           true, | ||||
| 		Compress:               true, | ||||
| 		ShouldReconnectOnError: true, | ||||
|   | ||||
							
								
								
									
										5
									
								
								vendor/github.com/bwmarrin/discordgo/endpoints.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								vendor/github.com/bwmarrin/discordgo/endpoints.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -71,7 +71,6 @@ var ( | ||||
| 	EndpointUserNotes          = func(uID string) string { return EndpointUsers + "@me/notes/" + uID } | ||||
|  | ||||
| 	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" } | ||||
| 	EndpointGuildMembers         = func(gID string) string { return EndpointGuilds + gID + "/members" } | ||||
| 	EndpointGuildMember          = func(gID, uID string) string { return EndpointGuilds + gID + "/members/" + uID } | ||||
| @@ -98,7 +97,7 @@ var ( | ||||
| 	EndpointChannelMessages           = func(cID string) string { return EndpointChannels + cID + "/messages" } | ||||
| 	EndpointChannelMessage            = func(cID, mID string) string { return EndpointChannels + cID + "/messages/" + mID } | ||||
| 	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" } | ||||
| 	EndpointChannelMessagePin         = func(cID, mID string) string { return EndpointChannel(cID) + "/pins/" + mID } | ||||
|  | ||||
| @@ -122,6 +121,8 @@ var ( | ||||
| 	EndpointRelationship        = func(uID string) string { return EndpointRelationships() + "/" + uID } | ||||
| 	EndpointRelationshipsMutual = func(uID string) string { return EndpointUsers + uID + "/relationships" } | ||||
|  | ||||
| 	EndpointGuildCreate = EndpointAPI + "guilds" | ||||
|  | ||||
| 	EndpointInvite = func(iID string) string { return EndpointAPI + "invite/" + iID } | ||||
|  | ||||
| 	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 | ||||
|  | ||||
| 	// Handle is called whenever an event of Type() happens. | ||||
| 	// It is the recievers responsibility to type assert that the interface | ||||
| 	// It is the receivers responsibility to type assert that the interface | ||||
| 	// is the expected struct. | ||||
| 	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, err = dg.ApplicationCreate(ap) | ||||
| 	if err != nil { | ||||
| 		fmt.Println("error creating new applicaiton,", err) | ||||
| 		fmt.Println("error creating new application,", err) | ||||
| 		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 | ||||
|  | ||||
| 	// LogWarning level is used for very abnormal events and errors that are | ||||
| 	// also returend to a calling function. | ||||
| 	// also returned to a calling function. | ||||
| 	LogWarning | ||||
|  | ||||
| 	// LogInformational level is used for normal non-error activity | ||||
| @@ -34,26 +34,34 @@ const ( | ||||
| 	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 | ||||
| // the format, a...  portion this command follows that of fmt.Printf | ||||
| //   msgL   : LogLevel of the message | ||||
| //   caller : 1 + the number of callers away from the message source | ||||
| //   format : Printf style message format | ||||
| //   a ...  : comma seperated list of values to pass | ||||
| //   a ...  : comma separated list of values to pass | ||||
| func msglog(msgL, caller int, format string, a ...interface{}) { | ||||
|  | ||||
| 	pc, file, line, _ := runtime.Caller(caller) | ||||
| 	if Logger != nil { | ||||
| 		Logger(msgL, caller, format, a...) | ||||
| 	} else { | ||||
|  | ||||
| 	files := strings.Split(file, "/") | ||||
| 	file = files[len(files)-1] | ||||
| 		pc, file, line, _ := runtime.Caller(caller) | ||||
|  | ||||
| 	name := runtime.FuncForPC(pc).Name() | ||||
| 	fns := strings.Split(name, ".") | ||||
| 	name = fns[len(fns)-1] | ||||
| 		files := strings.Split(file, "/") | ||||
| 		file = files[len(files)-1] | ||||
|  | ||||
| 	msg := fmt.Sprintf(format, a...) | ||||
| 		name := runtime.FuncForPC(pc).Name() | ||||
| 		fns := strings.Split(name, ".") | ||||
| 		name = fns[len(fns)-1] | ||||
|  | ||||
| 	log.Printf("[DG%d] %s:%d:%s() %s\n", msgL, file, line, name, msg) | ||||
| 		msg := fmt.Sprintf(format, a...) | ||||
|  | ||||
| 		log.Printf("[DG%d] %s:%d:%s() %s\n", msgL, file, line, name, msg) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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 | ||||
| 		} | ||||
|  | ||||
| 		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 { | ||||
|   | ||||
							
								
								
									
										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 | ||||
| func (r *RateLimiter) getBucket(key string) *Bucket { | ||||
| // GetBucket retrieves or creates a bucket | ||||
| func (r *RateLimiter) GetBucket(key string) *Bucket { | ||||
| 	r.Lock() | ||||
| 	defer r.Unlock() | ||||
|  | ||||
| @@ -51,7 +51,7 @@ func (r *RateLimiter) getBucket(key string) *Bucket { | ||||
| 	} | ||||
|  | ||||
| 	b := &Bucket{ | ||||
| 		remaining: 1, | ||||
| 		Remaining: 1, | ||||
| 		Key:       key, | ||||
| 		global:    r.global, | ||||
| 	} | ||||
| @@ -68,27 +68,37 @@ func (r *RateLimiter) getBucket(key string) *Bucket { | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| // LockBucket Locks until a request can be made | ||||
| func (r *RateLimiter) LockBucket(bucketID string) *Bucket { | ||||
|  | ||||
| 	b := r.getBucket(bucketID) | ||||
|  | ||||
| 	b.Lock() | ||||
|  | ||||
| // GetWaitTime returns the duration you should wait for a Bucket | ||||
| func (r *RateLimiter) GetWaitTime(b *Bucket, minRemaining int) time.Duration { | ||||
| 	// 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 | ||||
| 	if b.remaining < 1 && b.reset.After(time.Now()) { | ||||
| 		time.Sleep(b.reset.Sub(time.Now())) | ||||
|  | ||||
| 	if b.Remaining < minRemaining && b.reset.After(time.Now()) { | ||||
| 		return b.reset.Sub(time.Now()) | ||||
| 	} | ||||
|  | ||||
| 	// Check for global ratelimits | ||||
| 	sleepTo := time.Unix(0, atomic.LoadInt64(r.global)) | ||||
| 	if now := time.Now(); now.Before(sleepTo) { | ||||
| 		time.Sleep(sleepTo.Sub(now)) | ||||
| 		return sleepTo.Sub(now) | ||||
| 	} | ||||
|  | ||||
| 	b.remaining-- | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| // 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 | ||||
| } | ||||
|  | ||||
| @@ -96,13 +106,14 @@ func (r *RateLimiter) LockBucket(bucketID string) *Bucket { | ||||
| type Bucket struct { | ||||
| 	sync.Mutex | ||||
| 	Key       string | ||||
| 	remaining int | ||||
| 	Remaining int | ||||
| 	limit     int | ||||
| 	reset     time.Time | ||||
| 	global    *int64 | ||||
|  | ||||
| 	lastReset       time.Time | ||||
| 	customRateLimit *customRateLimit | ||||
| 	Userdata        interface{} | ||||
| } | ||||
|  | ||||
| // Release unlocks the bucket and reads the headers to update the buckets ratelimit info | ||||
| @@ -113,10 +124,10 @@ func (b *Bucket) Release(headers http.Header) error { | ||||
| 	// Check if the bucket uses a custom ratelimiter | ||||
| 	if rl := b.customRateLimit; rl != nil { | ||||
| 		if time.Now().Sub(b.lastReset) >= rl.reset { | ||||
| 			b.remaining = rl.requests - 1 | ||||
| 			b.Remaining = rl.requests - 1 | ||||
| 			b.lastReset = time.Now() | ||||
| 		} | ||||
| 		if b.remaining < 1 { | ||||
| 		if b.Remaining < 1 { | ||||
| 			b.reset = time.Now().Add(rl.reset) | ||||
| 		} | ||||
| 		return nil | ||||
| @@ -176,7 +187,7 @@ func (b *Bucket) Release(headers http.Header) error { | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		b.remaining = int(parsedRemaining) | ||||
| 		b.Remaining = int(parsedRemaining) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
|   | ||||
							
								
								
									
										54
									
								
								vendor/github.com/bwmarrin/discordgo/restapi.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										54
									
								
								vendor/github.com/bwmarrin/discordgo/restapi.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -65,9 +65,11 @@ func (s *Session) request(method, urlStr, contentType string, b []byte, bucketID | ||||
| 	if bucketID == "" { | ||||
| 		bucketID = strings.SplitN(urlStr, "?", 2)[0] | ||||
| 	} | ||||
| 	return s.RequestWithLockedBucket(method, urlStr, contentType, b, s.Ratelimiter.LockBucket(bucketID), sequence) | ||||
| } | ||||
|  | ||||
| 	bucket := s.ratelimiter.LockBucket(bucketID) | ||||
|  | ||||
| // RequestWithLockedBucket makes a request using a bucket that's already been locked | ||||
| func (s *Session) RequestWithLockedBucket(method, urlStr, contentType string, b []byte, bucket *Bucket, sequence int) (response []byte, err error) { | ||||
| 	if s.Debug { | ||||
| 		log.Printf("API REQUEST %8s :: %s\n", method, urlStr) | ||||
| 		log.Printf("API REQUEST  PAYLOAD :: [%s]\n", string(b)) | ||||
| @@ -139,7 +141,7 @@ func (s *Session) request(method, urlStr, contentType string, b []byte, bucketID | ||||
| 		if sequence < s.MaxRestRetries { | ||||
|  | ||||
| 			s.log(LogInformational, "%s Failed (%s), Retrying...", urlStr, resp.Status) | ||||
| 			response, err = s.request(method, urlStr, contentType, b, bucketID, sequence+1) | ||||
| 			response, err = s.RequestWithLockedBucket(method, urlStr, contentType, b, s.Ratelimiter.LockBucketObject(bucket), sequence+1) | ||||
| 		} else { | ||||
| 			err = fmt.Errorf("Exceeded Max retries HTTP %s, %s", resp.Status, response) | ||||
| 		} | ||||
| @@ -158,7 +160,7 @@ func (s *Session) request(method, urlStr, contentType string, b []byte, bucketID | ||||
| 		// we can make the above smarter | ||||
| 		// this method can cause longer delays than required | ||||
|  | ||||
| 		response, err = s.request(method, urlStr, contentType, b, bucketID, sequence) | ||||
| 		response, err = s.RequestWithLockedBucket(method, urlStr, contentType, b, s.Ratelimiter.LockBucketObject(bucket), sequence) | ||||
|  | ||||
| 	default: // Error condition | ||||
| 		err = newRestError(req, resp, response) | ||||
| @@ -585,7 +587,7 @@ func (s *Session) GuildCreate(name string) (st *Guild, err error) { | ||||
| 		Name string `json:"name"` | ||||
| 	}{name} | ||||
|  | ||||
| 	body, err := s.RequestWithBucketID("POST", EndpointGuilds, data, EndpointGuilds) | ||||
| 	body, err := s.RequestWithBucketID("POST", EndpointGuildCreate, data, EndpointGuildCreate) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| @@ -907,7 +909,7 @@ func (s *Session) GuildChannelsReorder(guildID string, channels []*Channel) (err | ||||
| // GuildInvites returns an array of Invite structures for the given guild | ||||
| // guildID   : The ID of a Guild. | ||||
| func (s *Session) GuildInvites(guildID string) (st []*Invite, err error) { | ||||
| 	body, err := s.RequestWithBucketID("GET", EndpointGuildInvites(guildID), nil, EndpointGuildInivtes(guildID)) | ||||
| 	body, err := s.RequestWithBucketID("GET", EndpointGuildInvites(guildID), nil, EndpointGuildInvites(guildID)) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| @@ -957,6 +959,7 @@ func (s *Session) GuildRoleEdit(guildID, roleID, name string, color int, hoist b | ||||
| 	// Prevent sending a color int that is too big. | ||||
| 	if color > 0xFFFFFF { | ||||
| 		err = fmt.Errorf("color value cannot be larger than 0xFFFFFF") | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	data := struct { | ||||
| @@ -1020,6 +1023,9 @@ func (s *Session) GuildPruneCount(guildID string, days uint32) (count uint32, er | ||||
|  | ||||
| 	uri := EndpointGuildPrune(guildID) + fmt.Sprintf("?days=%d", days) | ||||
| 	body, err := s.RequestWithBucketID("GET", uri, nil, EndpointGuildPrune(guildID)) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	err = unmarshal(body, &p) | ||||
| 	if err != nil { | ||||
| @@ -1204,7 +1210,7 @@ func (s *Session) GuildEmbedEdit(guildID string, enabled bool, channelID string) | ||||
| // Functions specific to Discord Channels | ||||
| // ------------------------------------------------------------------------------------------------ | ||||
|  | ||||
| // Channel returns a Channel strucutre of a specific Channel. | ||||
| // Channel returns a Channel structure of a specific Channel. | ||||
| // channelID  : The ID of the Channel you want returned. | ||||
| func (s *Session) Channel(channelID string) (st *Channel, err error) { | ||||
| 	body, err := s.RequestWithBucketID("GET", EndpointChannel(channelID), nil, EndpointChannel(channelID)) | ||||
| @@ -1219,12 +1225,16 @@ func (s *Session) Channel(channelID string) (st *Channel, err error) { | ||||
| // ChannelEdit edits the given channel | ||||
| // channelID  : The ID of a Channel | ||||
| // name       : The new name to assign the channel. | ||||
| func (s *Session) ChannelEdit(channelID, name string) (st *Channel, err error) { | ||||
|  | ||||
| 	data := struct { | ||||
| 		Name string `json:"name"` | ||||
| 	}{name} | ||||
| func (s *Session) ChannelEdit(channelID, name string) (*Channel, error) { | ||||
| 	return s.ChannelEditComplex(channelID, &ChannelEdit{ | ||||
| 		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)) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| @@ -1476,7 +1486,7 @@ func (s *Session) ChannelMessageDelete(channelID, messageID string) (err error) | ||||
| } | ||||
|  | ||||
| // ChannelMessagesBulkDelete bulk deletes the messages from the channel for the provided messageIDs. | ||||
| // If only one messageID is in the slice call channelMessageDelete funciton. | ||||
| // If only one messageID is in the slice call channelMessageDelete function. | ||||
| // If the slice is empty do nothing. | ||||
| // 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. | ||||
| @@ -1569,16 +1579,14 @@ func (s *Session) ChannelInvites(channelID string) (st []*Invite, err error) { | ||||
|  | ||||
| // ChannelInviteCreate creates a new invite for the given channel. | ||||
| // channelID   : The ID of a Channel | ||||
| // i           : An Invite struct with the values MaxAge, MaxUses, Temporary, | ||||
| //               and XkcdPass defined. | ||||
| // i           : An Invite struct with the values MaxAge, MaxUses and Temporary defined. | ||||
| func (s *Session) ChannelInviteCreate(channelID string, i Invite) (st *Invite, err error) { | ||||
|  | ||||
| 	data := struct { | ||||
| 		MaxAge    int    `json:"max_age"` | ||||
| 		MaxUses   int    `json:"max_uses"` | ||||
| 		Temporary bool   `json:"temporary"` | ||||
| 		XKCDPass  string `json:"xkcdpass"` | ||||
| 	}{i.MaxAge, i.MaxUses, i.Temporary, i.XkcdPass} | ||||
| 		MaxAge    int  `json:"max_age"` | ||||
| 		MaxUses   int  `json:"max_uses"` | ||||
| 		Temporary bool `json:"temporary"` | ||||
| 	}{i.MaxAge, i.MaxUses, i.Temporary} | ||||
|  | ||||
| 	body, err := s.RequestWithBucketID("POST", EndpointChannelInvites(channelID), data, EndpointChannelInvites(channelID)) | ||||
| 	if err != nil { | ||||
| @@ -1618,7 +1626,7 @@ func (s *Session) ChannelPermissionDelete(channelID, targetID string) (err error | ||||
| // ------------------------------------------------------------------------------------------------ | ||||
|  | ||||
| // Invite returns an Invite structure of the given invite | ||||
| // inviteID : The invite code (or maybe xkcdpass?) | ||||
| // inviteID : The invite code | ||||
| func (s *Session) Invite(inviteID string) (st *Invite, err error) { | ||||
|  | ||||
| 	body, err := s.RequestWithBucketID("GET", EndpointInvite(inviteID), nil, EndpointInvite("")) | ||||
| @@ -1631,7 +1639,7 @@ func (s *Session) Invite(inviteID string) (st *Invite, err error) { | ||||
| } | ||||
|  | ||||
| // InviteDelete deletes an existing invite | ||||
| // inviteID   : the code (or maybe xkcdpass?) of an invite | ||||
| // inviteID   : the code of an invite | ||||
| func (s *Session) InviteDelete(inviteID string) (st *Invite, err error) { | ||||
|  | ||||
| 	body, err := s.RequestWithBucketID("DELETE", EndpointInvite(inviteID), nil, EndpointInvite("")) | ||||
| @@ -1644,7 +1652,7 @@ func (s *Session) InviteDelete(inviteID string) (st *Invite, err error) { | ||||
| } | ||||
|  | ||||
| // InviteAccept accepts an Invite to a Guild or Channel | ||||
| // inviteID : The invite code (or maybe xkcdpass?) | ||||
| // inviteID : The invite code | ||||
| func (s *Session) InviteAccept(inviteID string) (st *Invite, err error) { | ||||
|  | ||||
| 	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) | ||||
| } | ||||
|  | ||||
| // Channel gets a channel by ID, it will look in all guilds an private channels. | ||||
| // Channel gets a channel by ID, it will look in all guilds and private channels. | ||||
| func (s *State) Channel(channelID string) (*Channel, error) { | ||||
| 	if s == nil { | ||||
| 		return nil, ErrNilState | ||||
| @@ -816,6 +816,13 @@ func (s *State) OnInterface(se *Session, i interface{}) (err error) { | ||||
| 		if s.TrackMembers { | ||||
| 			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: | ||||
| 		if s.TrackRoles { | ||||
| 			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,7 +14,6 @@ package discordgo | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| @@ -85,6 +84,9 @@ type Session struct { | ||||
| 	// Stores the last HeartbeatAck that was recieved (in UTC) | ||||
| 	LastHeartbeatAck time.Time | ||||
|  | ||||
| 	// used to deal with rate limits | ||||
| 	Ratelimiter *RateLimiter | ||||
|  | ||||
| 	// Event handlers | ||||
| 	handlersMu   sync.RWMutex | ||||
| 	handlers     map[string][]*eventHandlerInstance | ||||
| @@ -96,9 +98,6 @@ type Session struct { | ||||
| 	// When nil, the session is not listening. | ||||
| 	listening chan interface{} | ||||
|  | ||||
| 	// used to deal with rate limits | ||||
| 	ratelimiter *RateLimiter | ||||
|  | ||||
| 	// sequence tracks the current gateway api websocket sequence number | ||||
| 	sequence *int64 | ||||
|  | ||||
| @@ -143,9 +142,9 @@ type Invite struct { | ||||
| 	MaxAge    int       `json:"max_age"` | ||||
| 	Uses      int       `json:"uses"` | ||||
| 	MaxUses   int       `json:"max_uses"` | ||||
| 	XkcdPass  string    `json:"xkcdpass"` | ||||
| 	Revoked   bool      `json:"revoked"` | ||||
| 	Temporary bool      `json:"temporary"` | ||||
| 	Unique    bool      `json:"unique"` | ||||
| } | ||||
|  | ||||
| // ChannelType is the type of a Channel | ||||
| @@ -171,9 +170,22 @@ type Channel struct { | ||||
| 	NSFW                 bool                   `json:"nsfw"` | ||||
| 	Position             int                    `json:"position"` | ||||
| 	Bitrate              int                    `json:"bitrate"` | ||||
| 	Recipients           []*User                `json:"recipient"` | ||||
| 	Recipients           []*User                `json:"recipients"` | ||||
| 	Messages             []*Message             `json:"-"` | ||||
| 	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 | ||||
| @@ -191,6 +203,7 @@ type Emoji struct { | ||||
| 	Roles         []string `json:"roles"` | ||||
| 	Managed       bool     `json:"managed"` | ||||
| 	RequireColons bool     `json:"require_colons"` | ||||
| 	Animated      bool     `json:"animated"` | ||||
| } | ||||
|  | ||||
| // APIName returns an correctly formatted API name for use in the MessageReactions endpoints. | ||||
| @@ -204,7 +217,7 @@ func (e *Emoji) APIName() string { | ||||
| 	return e.ID | ||||
| } | ||||
|  | ||||
| // VerificationLevel type defination | ||||
| // VerificationLevel type definition | ||||
| type VerificationLevel int | ||||
|  | ||||
| // Constants for VerificationLevel levels from 0 to 3 inclusive | ||||
| @@ -314,45 +327,58 @@ type Presence struct { | ||||
| 	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 | ||||
| type Game struct { | ||||
| 	Name string `json:"name"` | ||||
| 	Type int    `json:"type"` | ||||
| 	URL  string `json:"url,omitempty"` | ||||
| 	Name          string     `json:"name"` | ||||
| 	Type          GameType   `json:"type"` | ||||
| 	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) | ||||
| } | ||||
|  | ||||
| // UnmarshalJSON unmarshals json to Game struct | ||||
| func (g *Game) UnmarshalJSON(bytes []byte) error { | ||||
| 	temp := &struct { | ||||
| 		Name json.Number     `json:"name"` | ||||
| 		Type json.RawMessage `json:"type"` | ||||
| 		URL  string          `json:"url"` | ||||
| // A TimeStamps struct contains start and end times used in the rich presence "playing .." Game | ||||
| type TimeStamps struct { | ||||
| 	EndTimestamp   int64 `json:"end,omitempty"` | ||||
| 	StartTimestamp int64 `json:"start,omitempty"` | ||||
| } | ||||
|  | ||||
| // 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(bytes, temp) | ||||
| 	err := json.Unmarshal(b, &temp) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	g.URL = temp.URL | ||||
| 	g.Name = temp.Name.String() | ||||
|  | ||||
| 	if temp.Type != nil { | ||||
| 		err = json.Unmarshal(temp.Type, &g.Type) | ||||
| 		if err == nil { | ||||
| 			return nil | ||||
| 		} | ||||
|  | ||||
| 		s := "" | ||||
| 		err = json.Unmarshal(temp.Type, &s) | ||||
| 		if err == nil { | ||||
| 			g.Type, err = strconv.Atoi(s) | ||||
| 		} | ||||
|  | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	t.EndTimestamp = int64(temp.End) | ||||
| 	t.StartTimestamp = int64(temp.Start) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // An Assets struct contains assets and labels used in the rich presence "playing .." Game | ||||
| type Assets struct { | ||||
| 	LargeImageID string `json:"large_image,omitempty"` | ||||
| 	SmallImageID string `json:"small_image,omitempty"` | ||||
| 	LargeText    string `json:"large_text,omitempty"` | ||||
| 	SmallText    string `json:"small_text,omitempty"` | ||||
| } | ||||
|  | ||||
| // A Member stores user information for Guild members. | ||||
| type Member struct { | ||||
| 	GuildID  string   `json:"guild_id"` | ||||
| @@ -383,7 +409,7 @@ type Settings struct { | ||||
| 	DeveloperMode          bool               `json:"developer_mode"` | ||||
| } | ||||
|  | ||||
| // Status type defination | ||||
| // Status type definition | ||||
| type Status string | ||||
|  | ||||
| // 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,7 +29,9 @@ func (u *User) Mention() string { | ||||
| } | ||||
|  | ||||
| // 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 { | ||||
| 	var URL string | ||||
| 	if strings.HasPrefix(u.Avatar, "a_") { | ||||
| @@ -38,5 +40,8 @@ func (u *User) AvatarURL(size string) string { | ||||
| 		URL = EndpointUserAvatar(u.ID, u.Avatar) | ||||
| 	} | ||||
|  | ||||
| 	return URL + "?size=" + size | ||||
| 	if 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,7 +13,6 @@ import ( | ||||
| 	"encoding/binary" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"net" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| @@ -69,7 +68,7 @@ type VoiceConnection struct { | ||||
| 	voiceSpeakingUpdateHandlers []VoiceSpeakingUpdateHandler | ||||
| } | ||||
|  | ||||
| // VoiceSpeakingUpdateHandler type provides a function defination for the | ||||
| // VoiceSpeakingUpdateHandler type provides a function definition for the | ||||
| // VoiceSpeakingUpdate event | ||||
| type VoiceSpeakingUpdateHandler func(vc *VoiceConnection, vs *VoiceSpeakingUpdate) | ||||
|  | ||||
| @@ -104,7 +103,7 @@ func (v *VoiceConnection) Speaking(b bool) (err error) { | ||||
| 	defer v.Unlock() | ||||
| 	if err != nil { | ||||
| 		v.speaking = false | ||||
| 		log.Println("Speaking() write json error:", err) | ||||
| 		v.log(LogError, "Speaking() write json error:", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| @@ -181,7 +180,7 @@ func (v *VoiceConnection) Close() { | ||||
| 		v.log(LogInformational, "closing udp") | ||||
| 		err := v.udpConn.Close() | ||||
| 		if err != nil { | ||||
| 			log.Println("error closing udp connection: ", err) | ||||
| 			v.log(LogError, "error closing udp connection: ", err) | ||||
| 		} | ||||
| 		v.udpConn = nil | ||||
| 	} | ||||
| @@ -247,7 +246,7 @@ type voiceOP2 struct { | ||||
| } | ||||
|  | ||||
| // WaitUntilConnected waits for the Voice Connection to | ||||
| // become ready, if it does not become ready it retuns an err | ||||
| // become ready, if it does not become ready it returns an err | ||||
| func (v *VoiceConnection) waitUntilConnected() error { | ||||
|  | ||||
| 	v.log(LogInformational, "called") | ||||
| @@ -858,7 +857,7 @@ func (v *VoiceConnection) reconnect() { | ||||
| 		} | ||||
|  | ||||
| 		if v.session.DataReady == false || v.session.wsConn == nil { | ||||
| 			v.log(LogInformational, "cannot reconenct to channel %s with unready session", v.ChannelID) | ||||
| 			v.log(LogInformational, "cannot reconnect to channel %s with unready session", v.ChannelID) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
|   | ||||
							
								
								
									
										255
									
								
								vendor/github.com/bwmarrin/discordgo/wsapi.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										255
									
								
								vendor/github.com/bwmarrin/discordgo/wsapi.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -15,6 +15,7 @@ import ( | ||||
| 	"compress/zlib" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"runtime" | ||||
| @@ -45,19 +46,114 @@ type resumePacket struct { | ||||
| 	} `json:"d"` | ||||
| } | ||||
|  | ||||
| // Open opens a websocket connection to Discord. | ||||
| func (s *Session) Open() (err error) { | ||||
|  | ||||
| // Open creates a websocket connection to Discord. | ||||
| // See: https://discordapp.com/developers/docs/topics/gateway#connecting | ||||
| func (s *Session) Open() error { | ||||
| 	s.log(LogInformational, "called") | ||||
|  | ||||
| 	var err error | ||||
|  | ||||
| 	// Prevent Open or other major Session functions from | ||||
| 	// being called while Open is still running. | ||||
| 	s.Lock() | ||||
| 	defer func() { | ||||
| 	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 { | ||||
| 			s.Unlock() | ||||
| 			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() { | ||||
| 		// 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 { | ||||
| 			s.wsConn.Close() | ||||
| 			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. | ||||
| 	// We create it here so the below READY/RESUMED packet can populate | ||||
| 	// the state :) | ||||
| 	// XXX: Move to New() func? | ||||
| 	if s.State == nil { | ||||
| 		state := NewState() | ||||
| 		state.TrackChannels = false | ||||
| @@ -68,77 +164,42 @@ func (s *Session) Open() (err error) { | ||||
| 		s.State = state | ||||
| 	} | ||||
|  | ||||
| 	if s.wsConn != nil { | ||||
| 		err = ErrWSAlreadyOpen | ||||
| 		return | ||||
| 	// Now Discord should send us a READY or RESUMED packet. | ||||
| 	mt, m, err = s.wsConn.ReadMessage() | ||||
| 	if err != nil { | ||||
| 		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 { | ||||
| 		s.log(LogInformational, "creating new VoiceConnections map") | ||||
| 		s.VoiceConnections = make(map[string]*VoiceConnection) | ||||
| 	} | ||||
|  | ||||
| 	// Get the gateway to use for the Websocket connection | ||||
| 	if s.gateway == "" { | ||||
| 		s.gateway, err = s.Gateway() | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		// Add the version and encoding to the URL | ||||
| 		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. | ||||
| 	// Create listening chan outside of listen, as it needs to happen inside the | ||||
| 	// mutex lock and needs to exist before calling heartbeat and listen | ||||
| 	// go rountines. | ||||
| 	s.listening = make(chan interface{}) | ||||
|  | ||||
| 	// Start sending heartbeats and reading messages from Discord. | ||||
| 	go s.heartbeat(s.wsConn, s.listening, h.HeartbeatInterval) | ||||
| 	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") | ||||
| 	return | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // listen polls the websocket connection for events, it will stop when the | ||||
| @@ -249,7 +310,8 @@ func (s *Session) heartbeat(wsConn *websocket.Conn, listening <-chan interface{} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type updateStatusData struct { | ||||
| // UpdateStatusData ia provided to UpdateStatusComplex() | ||||
| type UpdateStatusData struct { | ||||
| 	IdleSince *int   `json:"since"` | ||||
| 	Game      *Game  `json:"game"` | ||||
| 	AFK       bool   `json:"afk"` | ||||
| @@ -258,7 +320,7 @@ type updateStatusData struct { | ||||
|  | ||||
| type updateStatusOp struct { | ||||
| 	Op   int              `json:"op"` | ||||
| 	Data updateStatusData `json:"d"` | ||||
| 	Data UpdateStatusData `json:"d"` | ||||
| } | ||||
|  | ||||
| // UpdateStreamingStatus is used to update the user's streaming status. | ||||
| @@ -270,13 +332,7 @@ func (s *Session) UpdateStreamingStatus(idle int, game string, url string) (err | ||||
|  | ||||
| 	s.log(LogInformational, "called") | ||||
|  | ||||
| 	s.RLock() | ||||
| 	defer s.RUnlock() | ||||
| 	if s.wsConn == nil { | ||||
| 		return ErrWSNotFound | ||||
| 	} | ||||
|  | ||||
| 	usd := updateStatusData{ | ||||
| 	usd := UpdateStatusData{ | ||||
| 		Status: "online", | ||||
| 	} | ||||
|  | ||||
| @@ -285,9 +341,9 @@ func (s *Session) UpdateStreamingStatus(idle int, game string, url string) (err | ||||
| 	} | ||||
|  | ||||
| 	if game != "" { | ||||
| 		gameType := 0 | ||||
| 		gameType := GameTypeGame | ||||
| 		if url != "" { | ||||
| 			gameType = 1 | ||||
| 			gameType = GameTypeStreaming | ||||
| 		} | ||||
| 		usd.Game = &Game{ | ||||
| 			Name: game, | ||||
| @@ -296,6 +352,18 @@ 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() | ||||
| 	err = s.wsConn.WriteJSON(updateStatusOp{3, usd}) | ||||
| 	s.wsMutex.Unlock() | ||||
| @@ -357,9 +425,7 @@ func (s *Session) RequestGuildMembers(guildID, query string, limit int) (err err | ||||
| // | ||||
| // If you use the AddHandler() function to register a handler for the | ||||
| // "OnEvent" event then all events will be passed to that handler. | ||||
| // | ||||
| // TODO: You may also register a custom event handler entirely using... | ||||
| func (s *Session) onEvent(messageType int, message []byte) { | ||||
| func (s *Session) onEvent(messageType int, message []byte) (*Event, error) { | ||||
|  | ||||
| 	var err error | ||||
| 	var reader io.Reader | ||||
| @@ -371,7 +437,7 @@ func (s *Session) onEvent(messageType int, message []byte) { | ||||
| 		z, err2 := zlib.NewReader(reader) | ||||
| 		if err2 != nil { | ||||
| 			s.log(LogError, "error uncompressing websocket message, %s", err) | ||||
| 			return | ||||
| 			return nil, err2 | ||||
| 		} | ||||
|  | ||||
| 		defer func() { | ||||
| @@ -389,7 +455,7 @@ func (s *Session) onEvent(messageType int, message []byte) { | ||||
| 	decoder := json.NewDecoder(reader) | ||||
| 	if err = decoder.Decode(&e); err != nil { | ||||
| 		s.log(LogError, "error decoding websocket message, %s", err) | ||||
| 		return | ||||
| 		return e, err | ||||
| 	} | ||||
|  | ||||
| 	s.log(LogDebug, "Op: %d, Seq: %d, Type: %s, Data: %s\n\n", e.Operation, e.Sequence, e.Type, string(e.RawData)) | ||||
| @@ -403,10 +469,10 @@ func (s *Session) onEvent(messageType int, message []byte) { | ||||
| 		s.wsMutex.Unlock() | ||||
| 		if err != nil { | ||||
| 			s.log(LogError, "error sending heartbeat in response to Op1") | ||||
| 			return | ||||
| 			return e, err | ||||
| 		} | ||||
|  | ||||
| 		return | ||||
| 		return e, nil | ||||
| 	} | ||||
|  | ||||
| 	// Reconnect | ||||
| @@ -415,7 +481,7 @@ func (s *Session) onEvent(messageType int, message []byte) { | ||||
| 		s.log(LogInformational, "Closing and reconnecting in response to Op7") | ||||
| 		s.Close() | ||||
| 		s.reconnect() | ||||
| 		return | ||||
| 		return e, nil | ||||
| 	} | ||||
|  | ||||
| 	// Invalid Session | ||||
| @@ -427,20 +493,15 @@ func (s *Session) onEvent(messageType int, message []byte) { | ||||
| 		err = s.identify() | ||||
| 		if err != nil { | ||||
| 			s.log(LogWarning, "error sending gateway identify packet, %s, %s", s.gateway, err) | ||||
| 			return | ||||
| 			return e, err | ||||
| 		} | ||||
|  | ||||
| 		return | ||||
| 		return e, nil | ||||
| 	} | ||||
|  | ||||
| 	if e.Operation == 10 { | ||||
| 		var h helloOp | ||||
| 		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 | ||||
| 		// Op10 is handled by Open() | ||||
| 		return e, nil | ||||
| 	} | ||||
|  | ||||
| 	if e.Operation == 11 { | ||||
| @@ -448,7 +509,7 @@ func (s *Session) onEvent(messageType int, message []byte) { | ||||
| 		s.LastHeartbeatAck = time.Now().UTC() | ||||
| 		s.Unlock() | ||||
| 		s.log(LogInformational, "got heartbeat ACK") | ||||
| 		return | ||||
| 		return e, nil | ||||
| 	} | ||||
|  | ||||
| 	// Do not try to Dispatch a non-Dispatch Message | ||||
| @@ -456,7 +517,7 @@ func (s *Session) onEvent(messageType int, message []byte) { | ||||
| 		// But we probably should be doing something with them. | ||||
| 		// 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)) | ||||
| 		return | ||||
| 		return e, nil | ||||
| 	} | ||||
|  | ||||
| 	// Store the message sequence | ||||
| @@ -485,6 +546,8 @@ func (s *Session) onEvent(messageType int, message []byte) { | ||||
|  | ||||
| 	// For legacy reasons, we send the raw event also, this could be useful for handling unknown events. | ||||
| 	s.handleEvent(eventEventType, e) | ||||
|  | ||||
| 	return e, nil | ||||
| } | ||||
|  | ||||
| // ------------------------------------------------------------------------------------------------ | ||||
| @@ -610,7 +673,7 @@ func (s *Session) onVoiceServerUpdate(st *VoiceServerUpdate) { | ||||
| 	voice.GuildID = st.GuildID | ||||
| 	voice.Unlock() | ||||
|  | ||||
| 	// Open a conenction to the voice server | ||||
| 	// Open a connection to the voice server | ||||
| 	err := voice.open() | ||||
| 	if err != nil { | ||||
| 		s.log(LogError, "onVoiceServerUpdate voice.open, %s", err) | ||||
|   | ||||
							
								
								
									
										202
									
								
								vendor/github.com/coreos/etcd/client/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								vendor/github.com/coreos/etcd/client/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,202 @@ | ||||
|  | ||||
|                                  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
									
									
										Normal file
									
								
							
							
						
						
									
										236
									
								
								vendor/github.com/coreos/etcd/client/auth_role.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,236 @@ | ||||
| // 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
									
									
										Normal file
									
								
							
							
						
						
									
										319
									
								
								vendor/github.com/coreos/etcd/client/auth_user.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,319 @@ | ||||
| // 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
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								vendor/github.com/coreos/etcd/client/cancelreq.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| // 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
									
									
										Normal file
									
								
							
							
						
						
									
										710
									
								
								vendor/github.com/coreos/etcd/client/client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,710 @@ | ||||
| // 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
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								vendor/github.com/coreos/etcd/client/cluster_error.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| // 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
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								vendor/github.com/coreos/etcd/client/curl.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| // 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
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								vendor/github.com/coreos/etcd/client/discover.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| // 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
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								vendor/github.com/coreos/etcd/client/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | ||||
| // 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
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								vendor/github.com/coreos/etcd/client/integration/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| // 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
									
									
										Normal file
									
								
							
							
						
						
									
										5218
									
								
								vendor/github.com/coreos/etcd/client/keys.generated.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										681
									
								
								vendor/github.com/coreos/etcd/client/keys.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										681
									
								
								vendor/github.com/coreos/etcd/client/keys.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,681 @@ | ||||
| // 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
									
									
										Normal file
									
								
							
							
						
						
									
										303
									
								
								vendor/github.com/coreos/etcd/client/members.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,303 @@ | ||||
| // 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
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								vendor/github.com/coreos/etcd/client/util.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| // 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
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								vendor/github.com/coreos/etcd/pkg/pathutil/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,202 @@ | ||||
|  | ||||
|                                  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
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								vendor/github.com/coreos/etcd/pkg/pathutil/path.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| // 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
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								vendor/github.com/coreos/etcd/pkg/srv/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,202 @@ | ||||
|  | ||||
|                                  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
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								vendor/github.com/coreos/etcd/pkg/srv/srv.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,130 @@ | ||||
| // 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
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								vendor/github.com/coreos/etcd/pkg/types/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,202 @@ | ||||
|  | ||||
|                                  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
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								vendor/github.com/coreos/etcd/pkg/types/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| // 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
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								vendor/github.com/coreos/etcd/pkg/types/id.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| // 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
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								vendor/github.com/coreos/etcd/pkg/types/set.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,178 @@ | ||||
| // 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{}} | ||||
| } | ||||
							
								
								
									
										22
									
								
								vendor/github.com/coreos/etcd/pkg/types/slice.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/github.com/coreos/etcd/pkg/types/slice.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| // 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 | ||||
|  | ||||
| // Uint64Slice implements sort interface | ||||
| type Uint64Slice []uint64 | ||||
|  | ||||
| func (p Uint64Slice) Len() int           { return len(p) } | ||||
| func (p Uint64Slice) Less(i, j int) bool { return p[i] < p[j] } | ||||
| func (p Uint64Slice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] } | ||||
							
								
								
									
										82
									
								
								vendor/github.com/coreos/etcd/pkg/types/urls.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								vendor/github.com/coreos/etcd/pkg/types/urls.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| // 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 ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"net/url" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| type URLs []url.URL | ||||
|  | ||||
| func NewURLs(strs []string) (URLs, error) { | ||||
| 	all := make([]url.URL, len(strs)) | ||||
| 	if len(all) == 0 { | ||||
| 		return nil, errors.New("no valid URLs given") | ||||
| 	} | ||||
| 	for i, in := range strs { | ||||
| 		in = strings.TrimSpace(in) | ||||
| 		u, err := url.Parse(in) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		if u.Scheme != "http" && u.Scheme != "https" && u.Scheme != "unix" && u.Scheme != "unixs" { | ||||
| 			return nil, fmt.Errorf("URL scheme must be http, https, unix, or unixs: %s", in) | ||||
| 		} | ||||
| 		if _, _, err := net.SplitHostPort(u.Host); err != nil { | ||||
| 			return nil, fmt.Errorf(`URL address does not have the form "host:port": %s`, in) | ||||
| 		} | ||||
| 		if u.Path != "" { | ||||
| 			return nil, fmt.Errorf("URL must not contain a path: %s", in) | ||||
| 		} | ||||
| 		all[i] = *u | ||||
| 	} | ||||
| 	us := URLs(all) | ||||
| 	us.Sort() | ||||
|  | ||||
| 	return us, nil | ||||
| } | ||||
|  | ||||
| func MustNewURLs(strs []string) URLs { | ||||
| 	urls, err := NewURLs(strs) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	return urls | ||||
| } | ||||
|  | ||||
| func (us URLs) String() string { | ||||
| 	return strings.Join(us.StringSlice(), ",") | ||||
| } | ||||
|  | ||||
| func (us *URLs) Sort() { | ||||
| 	sort.Sort(us) | ||||
| } | ||||
| func (us URLs) Len() int           { return len(us) } | ||||
| func (us URLs) Less(i, j int) bool { return us[i].String() < us[j].String() } | ||||
| func (us URLs) Swap(i, j int)      { us[i], us[j] = us[j], us[i] } | ||||
|  | ||||
| func (us URLs) StringSlice() []string { | ||||
| 	out := make([]string, len(us)) | ||||
| 	for i := range us { | ||||
| 		out[i] = us[i].String() | ||||
| 	} | ||||
|  | ||||
| 	return out | ||||
| } | ||||
							
								
								
									
										107
									
								
								vendor/github.com/coreos/etcd/pkg/types/urlsmap.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								vendor/github.com/coreos/etcd/pkg/types/urlsmap.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | ||||
| // 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 ( | ||||
| 	"fmt" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| // URLsMap is a map from a name to its URLs. | ||||
| type URLsMap map[string]URLs | ||||
|  | ||||
| // NewURLsMap returns a URLsMap instantiated from the given string, | ||||
| // which consists of discovery-formatted names-to-URLs, like: | ||||
| // mach0=http://1.1.1.1:2380,mach0=http://2.2.2.2::2380,mach1=http://3.3.3.3:2380,mach2=http://4.4.4.4:2380 | ||||
| func NewURLsMap(s string) (URLsMap, error) { | ||||
| 	m := parse(s) | ||||
|  | ||||
| 	cl := URLsMap{} | ||||
| 	for name, urls := range m { | ||||
| 		us, err := NewURLs(urls) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		cl[name] = us | ||||
| 	} | ||||
| 	return cl, nil | ||||
| } | ||||
|  | ||||
| // NewURLsMapFromStringMap takes a map of strings and returns a URLsMap. The | ||||
| // string values in the map can be multiple values separated by the sep string. | ||||
| func NewURLsMapFromStringMap(m map[string]string, sep string) (URLsMap, error) { | ||||
| 	var err error | ||||
| 	um := URLsMap{} | ||||
| 	for k, v := range m { | ||||
| 		um[k], err = NewURLs(strings.Split(v, sep)) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| 	return um, nil | ||||
| } | ||||
|  | ||||
| // String turns URLsMap into discovery-formatted name-to-URLs sorted by name. | ||||
| func (c URLsMap) String() string { | ||||
| 	var pairs []string | ||||
| 	for name, urls := range c { | ||||
| 		for _, url := range urls { | ||||
| 			pairs = append(pairs, fmt.Sprintf("%s=%s", name, url.String())) | ||||
| 		} | ||||
| 	} | ||||
| 	sort.Strings(pairs) | ||||
| 	return strings.Join(pairs, ",") | ||||
| } | ||||
|  | ||||
| // URLs returns a list of all URLs. | ||||
| // The returned list is sorted in ascending lexicographical order. | ||||
| func (c URLsMap) URLs() []string { | ||||
| 	var urls []string | ||||
| 	for _, us := range c { | ||||
| 		for _, u := range us { | ||||
| 			urls = append(urls, u.String()) | ||||
| 		} | ||||
| 	} | ||||
| 	sort.Strings(urls) | ||||
| 	return urls | ||||
| } | ||||
|  | ||||
| // Len returns the size of URLsMap. | ||||
| func (c URLsMap) Len() int { | ||||
| 	return len(c) | ||||
| } | ||||
|  | ||||
| // parse parses the given string and returns a map listing the values specified for each key. | ||||
| func parse(s string) map[string][]string { | ||||
| 	m := make(map[string][]string) | ||||
| 	for s != "" { | ||||
| 		key := s | ||||
| 		if i := strings.IndexAny(key, ","); i >= 0 { | ||||
| 			key, s = key[:i], key[i+1:] | ||||
| 		} else { | ||||
| 			s = "" | ||||
| 		} | ||||
| 		if key == "" { | ||||
| 			continue | ||||
| 		} | ||||
| 		value := "" | ||||
| 		if i := strings.Index(key, "="); i >= 0 { | ||||
| 			key, value = key[:i], key[i+1:] | ||||
| 		} | ||||
| 		m[key] = append(m[key], value) | ||||
| 	} | ||||
| 	return m | ||||
| } | ||||
							
								
								
									
										202
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/coreos/go-semver/semver/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/coreos/go-semver/semver/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,202 @@ | ||||
|  | ||||
|                                  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. | ||||
							
								
								
									
										268
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/coreos/go-semver/semver/semver.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/coreos/go-semver/semver/semver.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,268 @@ | ||||
| // Copyright 2013-2015 CoreOS, Inc. | ||||
| // | ||||
| // 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. | ||||
|  | ||||
| // Semantic Versions http://semver.org | ||||
| package semver | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| type Version struct { | ||||
| 	Major      int64 | ||||
| 	Minor      int64 | ||||
| 	Patch      int64 | ||||
| 	PreRelease PreRelease | ||||
| 	Metadata   string | ||||
| } | ||||
|  | ||||
| type PreRelease string | ||||
|  | ||||
| func splitOff(input *string, delim string) (val string) { | ||||
| 	parts := strings.SplitN(*input, delim, 2) | ||||
|  | ||||
| 	if len(parts) == 2 { | ||||
| 		*input = parts[0] | ||||
| 		val = parts[1] | ||||
| 	} | ||||
|  | ||||
| 	return val | ||||
| } | ||||
|  | ||||
| func New(version string) *Version { | ||||
| 	return Must(NewVersion(version)) | ||||
| } | ||||
|  | ||||
| func NewVersion(version string) (*Version, error) { | ||||
| 	v := Version{} | ||||
|  | ||||
| 	if err := v.Set(version); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return &v, nil | ||||
| } | ||||
|  | ||||
| // Must is a helper for wrapping NewVersion and will panic if err is not nil. | ||||
| func Must(v *Version, err error) *Version { | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // Set parses and updates v from the given version string. Implements flag.Value | ||||
| func (v *Version) Set(version string) error { | ||||
| 	metadata := splitOff(&version, "+") | ||||
| 	preRelease := PreRelease(splitOff(&version, "-")) | ||||
| 	dotParts := strings.SplitN(version, ".", 3) | ||||
|  | ||||
| 	if len(dotParts) != 3 { | ||||
| 		return fmt.Errorf("%s is not in dotted-tri format", version) | ||||
| 	} | ||||
|  | ||||
| 	parsed := make([]int64, 3, 3) | ||||
|  | ||||
| 	for i, v := range dotParts[:3] { | ||||
| 		val, err := strconv.ParseInt(v, 10, 64) | ||||
| 		parsed[i] = val | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	v.Metadata = metadata | ||||
| 	v.PreRelease = preRelease | ||||
| 	v.Major = parsed[0] | ||||
| 	v.Minor = parsed[1] | ||||
| 	v.Patch = parsed[2] | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (v Version) String() string { | ||||
| 	var buffer bytes.Buffer | ||||
|  | ||||
| 	fmt.Fprintf(&buffer, "%d.%d.%d", v.Major, v.Minor, v.Patch) | ||||
|  | ||||
| 	if v.PreRelease != "" { | ||||
| 		fmt.Fprintf(&buffer, "-%s", v.PreRelease) | ||||
| 	} | ||||
|  | ||||
| 	if v.Metadata != "" { | ||||
| 		fmt.Fprintf(&buffer, "+%s", v.Metadata) | ||||
| 	} | ||||
|  | ||||
| 	return buffer.String() | ||||
| } | ||||
|  | ||||
| func (v *Version) UnmarshalYAML(unmarshal func(interface{}) error) error { | ||||
| 	var data string | ||||
| 	if err := unmarshal(&data); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return v.Set(data) | ||||
| } | ||||
|  | ||||
| func (v Version) MarshalJSON() ([]byte, error) { | ||||
| 	return []byte(`"` + v.String() + `"`), nil | ||||
| } | ||||
|  | ||||
| func (v *Version) UnmarshalJSON(data []byte) error { | ||||
| 	l := len(data) | ||||
| 	if l == 0 || string(data) == `""` { | ||||
| 		return nil | ||||
| 	} | ||||
| 	if l < 2 || data[0] != '"' || data[l-1] != '"' { | ||||
| 		return errors.New("invalid semver string") | ||||
| 	} | ||||
| 	return v.Set(string(data[1 : l-1])) | ||||
| } | ||||
|  | ||||
| // Compare tests if v is less than, equal to, or greater than versionB, | ||||
| // returning -1, 0, or +1 respectively. | ||||
| func (v Version) Compare(versionB Version) int { | ||||
| 	if cmp := recursiveCompare(v.Slice(), versionB.Slice()); cmp != 0 { | ||||
| 		return cmp | ||||
| 	} | ||||
| 	return preReleaseCompare(v, versionB) | ||||
| } | ||||
|  | ||||
| // Equal tests if v is equal to versionB. | ||||
| func (v Version) Equal(versionB Version) bool { | ||||
| 	return v.Compare(versionB) == 0 | ||||
| } | ||||
|  | ||||
| // LessThan tests if v is less than versionB. | ||||
| func (v Version) LessThan(versionB Version) bool { | ||||
| 	return v.Compare(versionB) < 0 | ||||
| } | ||||
|  | ||||
| // Slice converts the comparable parts of the semver into a slice of integers. | ||||
| func (v Version) Slice() []int64 { | ||||
| 	return []int64{v.Major, v.Minor, v.Patch} | ||||
| } | ||||
|  | ||||
| func (p PreRelease) Slice() []string { | ||||
| 	preRelease := string(p) | ||||
| 	return strings.Split(preRelease, ".") | ||||
| } | ||||
|  | ||||
| func preReleaseCompare(versionA Version, versionB Version) int { | ||||
| 	a := versionA.PreRelease | ||||
| 	b := versionB.PreRelease | ||||
|  | ||||
| 	/* Handle the case where if two versions are otherwise equal it is the | ||||
| 	 * one without a PreRelease that is greater */ | ||||
| 	if len(a) == 0 && (len(b) > 0) { | ||||
| 		return 1 | ||||
| 	} else if len(b) == 0 && (len(a) > 0) { | ||||
| 		return -1 | ||||
| 	} | ||||
|  | ||||
| 	// If there is a prerelease, check and compare each part. | ||||
| 	return recursivePreReleaseCompare(a.Slice(), b.Slice()) | ||||
| } | ||||
|  | ||||
| func recursiveCompare(versionA []int64, versionB []int64) int { | ||||
| 	if len(versionA) == 0 { | ||||
| 		return 0 | ||||
| 	} | ||||
|  | ||||
| 	a := versionA[0] | ||||
| 	b := versionB[0] | ||||
|  | ||||
| 	if a > b { | ||||
| 		return 1 | ||||
| 	} else if a < b { | ||||
| 		return -1 | ||||
| 	} | ||||
|  | ||||
| 	return recursiveCompare(versionA[1:], versionB[1:]) | ||||
| } | ||||
|  | ||||
| func recursivePreReleaseCompare(versionA []string, versionB []string) int { | ||||
| 	// A larger set of pre-release fields has a higher precedence than a smaller set, | ||||
| 	// if all of the preceding identifiers are equal. | ||||
| 	if len(versionA) == 0 { | ||||
| 		if len(versionB) > 0 { | ||||
| 			return -1 | ||||
| 		} | ||||
| 		return 0 | ||||
| 	} else if len(versionB) == 0 { | ||||
| 		// We're longer than versionB so return 1. | ||||
| 		return 1 | ||||
| 	} | ||||
|  | ||||
| 	a := versionA[0] | ||||
| 	b := versionB[0] | ||||
|  | ||||
| 	aInt := false | ||||
| 	bInt := false | ||||
|  | ||||
| 	aI, err := strconv.Atoi(versionA[0]) | ||||
| 	if err == nil { | ||||
| 		aInt = true | ||||
| 	} | ||||
|  | ||||
| 	bI, err := strconv.Atoi(versionB[0]) | ||||
| 	if err == nil { | ||||
| 		bInt = true | ||||
| 	} | ||||
|  | ||||
| 	// Handle Integer Comparison | ||||
| 	if aInt && bInt { | ||||
| 		if aI > bI { | ||||
| 			return 1 | ||||
| 		} else if aI < bI { | ||||
| 			return -1 | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Handle String Comparison | ||||
| 	if a > b { | ||||
| 		return 1 | ||||
| 	} else if a < b { | ||||
| 		return -1 | ||||
| 	} | ||||
|  | ||||
| 	return recursivePreReleaseCompare(versionA[1:], versionB[1:]) | ||||
| } | ||||
|  | ||||
| // BumpMajor increments the Major field by 1 and resets all other fields to their default values | ||||
| func (v *Version) BumpMajor() { | ||||
| 	v.Major += 1 | ||||
| 	v.Minor = 0 | ||||
| 	v.Patch = 0 | ||||
| 	v.PreRelease = PreRelease("") | ||||
| 	v.Metadata = "" | ||||
| } | ||||
|  | ||||
| // BumpMinor increments the Minor field by 1 and resets all other fields to their default values | ||||
| func (v *Version) BumpMinor() { | ||||
| 	v.Minor += 1 | ||||
| 	v.Patch = 0 | ||||
| 	v.PreRelease = PreRelease("") | ||||
| 	v.Metadata = "" | ||||
| } | ||||
|  | ||||
| // BumpPatch increments the Patch field by 1 and resets all other fields to their default values | ||||
| func (v *Version) BumpPatch() { | ||||
| 	v.Patch += 1 | ||||
| 	v.PreRelease = PreRelease("") | ||||
| 	v.Metadata = "" | ||||
| } | ||||
							
								
								
									
										38
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/coreos/go-semver/semver/sort.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/coreos/go-semver/semver/sort.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| // Copyright 2013-2015 CoreOS, Inc. | ||||
| // | ||||
| // 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 semver | ||||
|  | ||||
| import ( | ||||
| 	"sort" | ||||
| ) | ||||
|  | ||||
| type Versions []*Version | ||||
|  | ||||
| func (s Versions) Len() int { | ||||
| 	return len(s) | ||||
| } | ||||
|  | ||||
| func (s Versions) Swap(i, j int) { | ||||
| 	s[i], s[j] = s[j], s[i] | ||||
| } | ||||
|  | ||||
| func (s Versions) Less(i, j int) bool { | ||||
| 	return s[i].LessThan(*s[j]) | ||||
| } | ||||
|  | ||||
| // Sort sorts the given slice of Version | ||||
| func Sort(versions []*Version) { | ||||
| 	sort.Sort(Versions(versions)) | ||||
| } | ||||
							
								
								
									
										185
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/0doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/0doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,185 @@ | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| /* | ||||
| High Performance, Feature-Rich Idiomatic Go 1.4+ codec/encoding library for | ||||
| binc, msgpack, cbor, json | ||||
|  | ||||
| Supported Serialization formats are: | ||||
|  | ||||
|   - msgpack: https://github.com/msgpack/msgpack | ||||
|   - binc:    http://github.com/ugorji/binc | ||||
|   - cbor:    http://cbor.io http://tools.ietf.org/html/rfc7049 | ||||
|   - json:    http://json.org http://tools.ietf.org/html/rfc7159 | ||||
|   - simple: | ||||
|  | ||||
| To install: | ||||
|  | ||||
|     go get github.com/ugorji/go/codec | ||||
|  | ||||
| This package will carefully use 'unsafe' for performance reasons in specific places. | ||||
| You can build without unsafe use by passing the safe or appengine tag | ||||
| i.e. 'go install -tags=safe ...'. Note that unsafe is only supported for the last 3 | ||||
| go sdk versions e.g. current go release is go 1.9, so we support unsafe use only from | ||||
| go 1.7+ . This is because supporting unsafe requires knowledge of implementation details. | ||||
|  | ||||
| For detailed usage information, read the primer at http://ugorji.net/blog/go-codec-primer . | ||||
|  | ||||
| The idiomatic Go support is as seen in other encoding packages in | ||||
| the standard library (ie json, xml, gob, etc). | ||||
|  | ||||
| Rich Feature Set includes: | ||||
|  | ||||
|   - Simple but extremely powerful and feature-rich API | ||||
|   - Support for go1.4 and above, while selectively using newer APIs for later releases | ||||
|   - Good code coverage ( > 70% ) | ||||
|   - Very High Performance. | ||||
|     Our extensive benchmarks show us outperforming Gob, Json, Bson, etc by 2-4X. | ||||
|   - Careful selected use of 'unsafe' for targeted performance gains. | ||||
|     100% mode exists where 'unsafe' is not used at all. | ||||
|   - Lock-free (sans mutex) concurrency for scaling to 100's of cores | ||||
|   - Multiple conversions: | ||||
|     Package coerces types where appropriate | ||||
|     e.g. decode an int in the stream into a float, etc. | ||||
|   - Corner Cases: | ||||
|     Overflows, nil maps/slices, nil values in streams are handled correctly | ||||
|   - Standard field renaming via tags | ||||
|   - Support for omitting empty fields during an encoding | ||||
|   - Encoding from any value and decoding into pointer to any value | ||||
|     (struct, slice, map, primitives, pointers, interface{}, etc) | ||||
|   - Extensions to support efficient encoding/decoding of any named types | ||||
|   - Support encoding.(Binary|Text)(M|Unm)arshaler interfaces | ||||
|   - Decoding without a schema (into a interface{}). | ||||
|     Includes Options to configure what specific map or slice type to use | ||||
|     when decoding an encoded list or map into a nil interface{} | ||||
|   - Encode a struct as an array, and decode struct from an array in the data stream | ||||
|   - Comprehensive support for anonymous fields | ||||
|   - Fast (no-reflection) encoding/decoding of common maps and slices | ||||
|   - Code-generation for faster performance. | ||||
|   - Support binary (e.g. messagepack, cbor) and text (e.g. json) formats | ||||
|   - Support indefinite-length formats to enable true streaming | ||||
|     (for formats which support it e.g. json, cbor) | ||||
|   - Support canonical encoding, where a value is ALWAYS encoded as same sequence of bytes. | ||||
|     This mostly applies to maps, where iteration order is non-deterministic. | ||||
|   - NIL in data stream decoded as zero value | ||||
|   - Never silently skip data when decoding. | ||||
|     User decides whether to return an error or silently skip data when keys or indexes | ||||
|     in the data stream do not map to fields in the struct. | ||||
|   - Detect and error when encoding a cyclic reference (instead of stack overflow shutdown) | ||||
|   - Encode/Decode from/to chan types (for iterative streaming support) | ||||
|   - Drop-in replacement for encoding/json. `json:` key in struct tag supported. | ||||
|   - Provides a RPC Server and Client Codec for net/rpc communication protocol. | ||||
|   - Handle unique idiosyncrasies of codecs e.g. | ||||
|     - For messagepack, configure how ambiguities in handling raw bytes are resolved | ||||
|     - For messagepack, provide rpc server/client codec to support | ||||
|       msgpack-rpc protocol defined at: | ||||
|       https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md | ||||
|  | ||||
| Extension Support | ||||
|  | ||||
| Users can register a function to handle the encoding or decoding of | ||||
| their custom types. | ||||
|  | ||||
| There are no restrictions on what the custom type can be. Some examples: | ||||
|  | ||||
|     type BisSet   []int | ||||
|     type BitSet64 uint64 | ||||
|     type UUID     string | ||||
|     type MyStructWithUnexportedFields struct { a int; b bool; c []int; } | ||||
|     type GifImage struct { ... } | ||||
|  | ||||
| As an illustration, MyStructWithUnexportedFields would normally be | ||||
| encoded as an empty map because it has no exported fields, while UUID | ||||
| would be encoded as a string. However, with extension support, you can | ||||
| encode any of these however you like. | ||||
|  | ||||
| RPC | ||||
|  | ||||
| RPC Client and Server Codecs are implemented, so the codecs can be used | ||||
| with the standard net/rpc package. | ||||
|  | ||||
| Usage | ||||
|  | ||||
| The Handle is SAFE for concurrent READ, but NOT SAFE for concurrent modification. | ||||
|  | ||||
| The Encoder and Decoder are NOT safe for concurrent use. | ||||
|  | ||||
| Consequently, the usage model is basically: | ||||
|  | ||||
|     - Create and initialize the Handle before any use. | ||||
|       Once created, DO NOT modify it. | ||||
|     - Multiple Encoders or Decoders can now use the Handle concurrently. | ||||
|       They only read information off the Handle (never write). | ||||
|     - However, each Encoder or Decoder MUST not be used concurrently | ||||
|     - To re-use an Encoder/Decoder, call Reset(...) on it first. | ||||
|       This allows you use state maintained on the Encoder/Decoder. | ||||
|  | ||||
| Sample usage model: | ||||
|  | ||||
|     // create and configure Handle | ||||
|     var ( | ||||
|       bh codec.BincHandle | ||||
|       mh codec.MsgpackHandle | ||||
|       ch codec.CborHandle | ||||
|     ) | ||||
|  | ||||
|     mh.MapType = reflect.TypeOf(map[string]interface{}(nil)) | ||||
|  | ||||
|     // configure extensions | ||||
|     // e.g. for msgpack, define functions and enable Time support for tag 1 | ||||
|     // mh.SetExt(reflect.TypeOf(time.Time{}), 1, myExt) | ||||
|  | ||||
|     // create and use decoder/encoder | ||||
|     var ( | ||||
|       r io.Reader | ||||
|       w io.Writer | ||||
|       b []byte | ||||
|       h = &bh // or mh to use msgpack | ||||
|     ) | ||||
|  | ||||
|     dec = codec.NewDecoder(r, h) | ||||
|     dec = codec.NewDecoderBytes(b, h) | ||||
|     err = dec.Decode(&v) | ||||
|  | ||||
|     enc = codec.NewEncoder(w, h) | ||||
|     enc = codec.NewEncoderBytes(&b, h) | ||||
|     err = enc.Encode(v) | ||||
|  | ||||
|     //RPC Server | ||||
|     go func() { | ||||
|         for { | ||||
|             conn, err := listener.Accept() | ||||
|             rpcCodec := codec.GoRpc.ServerCodec(conn, h) | ||||
|             //OR rpcCodec := codec.MsgpackSpecRpc.ServerCodec(conn, h) | ||||
|             rpc.ServeCodec(rpcCodec) | ||||
|         } | ||||
|     }() | ||||
|  | ||||
|     //RPC Communication (client side) | ||||
|     conn, err = net.Dial("tcp", "localhost:5555") | ||||
|     rpcCodec := codec.GoRpc.ClientCodec(conn, h) | ||||
|     //OR rpcCodec := codec.MsgpackSpecRpc.ClientCodec(conn, h) | ||||
|     client := rpc.NewClientWithCodec(rpcCodec) | ||||
|  | ||||
| Running Tests | ||||
|  | ||||
| To run tests, use the following: | ||||
|  | ||||
|     go test | ||||
|  | ||||
| To run the full suite of tests, use the following: | ||||
|  | ||||
|     go test -tags alltests -run Suite | ||||
|  | ||||
| You can run the tag 'safe' to run tests or build in safe mode. e.g. | ||||
|  | ||||
|     go test -tags safe -run Json | ||||
|     go test -tags "alltests safe" -run Suite | ||||
|  | ||||
| Running Benchmarks | ||||
|  | ||||
| Please see http://github.com/ugorji/go-codec-bench . | ||||
|  | ||||
| */ | ||||
| package codec | ||||
|  | ||||
							
								
								
									
										202
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,202 @@ | ||||
|  | ||||
|                                  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. | ||||
							
								
								
									
										946
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/binc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										946
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/binc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,946 @@ | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| package codec | ||||
|  | ||||
| import ( | ||||
| 	"math" | ||||
| 	"reflect" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| const bincDoPrune = true // No longer needed. Needed before as C lib did not support pruning. | ||||
|  | ||||
| // vd as low 4 bits (there are 16 slots) | ||||
| const ( | ||||
| 	bincVdSpecial byte = iota | ||||
| 	bincVdPosInt | ||||
| 	bincVdNegInt | ||||
| 	bincVdFloat | ||||
|  | ||||
| 	bincVdString | ||||
| 	bincVdByteArray | ||||
| 	bincVdArray | ||||
| 	bincVdMap | ||||
|  | ||||
| 	bincVdTimestamp | ||||
| 	bincVdSmallInt | ||||
| 	bincVdUnicodeOther | ||||
| 	bincVdSymbol | ||||
|  | ||||
| 	bincVdDecimal | ||||
| 	_               // open slot | ||||
| 	_               // open slot | ||||
| 	bincVdCustomExt = 0x0f | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	bincSpNil byte = iota | ||||
| 	bincSpFalse | ||||
| 	bincSpTrue | ||||
| 	bincSpNan | ||||
| 	bincSpPosInf | ||||
| 	bincSpNegInf | ||||
| 	bincSpZeroFloat | ||||
| 	bincSpZero | ||||
| 	bincSpNegOne | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	bincFlBin16 byte = iota | ||||
| 	bincFlBin32 | ||||
| 	_ // bincFlBin32e | ||||
| 	bincFlBin64 | ||||
| 	_ // bincFlBin64e | ||||
| 	// others not currently supported | ||||
| ) | ||||
|  | ||||
| type bincEncDriver struct { | ||||
| 	e *Encoder | ||||
| 	w encWriter | ||||
| 	m map[string]uint16 // symbols | ||||
| 	b [scratchByteArrayLen]byte | ||||
| 	s uint16 // symbols sequencer | ||||
| 	// encNoSeparator | ||||
| 	encDriverNoopContainerWriter | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) IsBuiltinType(rt uintptr) bool { | ||||
| 	return rt == timeTypId | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) EncodeBuiltin(rt uintptr, v interface{}) { | ||||
| 	if rt == timeTypId { | ||||
| 		var bs []byte | ||||
| 		switch x := v.(type) { | ||||
| 		case time.Time: | ||||
| 			bs = encodeTime(x) | ||||
| 		case *time.Time: | ||||
| 			bs = encodeTime(*x) | ||||
| 		default: | ||||
| 			e.e.errorf("binc error encoding builtin: expect time.Time, received %T", v) | ||||
| 		} | ||||
| 		e.w.writen1(bincVdTimestamp<<4 | uint8(len(bs))) | ||||
| 		e.w.writeb(bs) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) EncodeNil() { | ||||
| 	e.w.writen1(bincVdSpecial<<4 | bincSpNil) | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) EncodeBool(b bool) { | ||||
| 	if b { | ||||
| 		e.w.writen1(bincVdSpecial<<4 | bincSpTrue) | ||||
| 	} else { | ||||
| 		e.w.writen1(bincVdSpecial<<4 | bincSpFalse) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) EncodeFloat32(f float32) { | ||||
| 	if f == 0 { | ||||
| 		e.w.writen1(bincVdSpecial<<4 | bincSpZeroFloat) | ||||
| 		return | ||||
| 	} | ||||
| 	e.w.writen1(bincVdFloat<<4 | bincFlBin32) | ||||
| 	bigenHelper{e.b[:4], e.w}.writeUint32(math.Float32bits(f)) | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) EncodeFloat64(f float64) { | ||||
| 	if f == 0 { | ||||
| 		e.w.writen1(bincVdSpecial<<4 | bincSpZeroFloat) | ||||
| 		return | ||||
| 	} | ||||
| 	bigen.PutUint64(e.b[:8], math.Float64bits(f)) | ||||
| 	if bincDoPrune { | ||||
| 		i := 7 | ||||
| 		for ; i >= 0 && (e.b[i] == 0); i-- { | ||||
| 		} | ||||
| 		i++ | ||||
| 		if i <= 6 { | ||||
| 			e.w.writen1(bincVdFloat<<4 | 0x8 | bincFlBin64) | ||||
| 			e.w.writen1(byte(i)) | ||||
| 			e.w.writeb(e.b[:i]) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	e.w.writen1(bincVdFloat<<4 | bincFlBin64) | ||||
| 	e.w.writeb(e.b[:8]) | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) encIntegerPrune(bd byte, pos bool, v uint64, lim uint8) { | ||||
| 	if lim == 4 { | ||||
| 		bigen.PutUint32(e.b[:lim], uint32(v)) | ||||
| 	} else { | ||||
| 		bigen.PutUint64(e.b[:lim], v) | ||||
| 	} | ||||
| 	if bincDoPrune { | ||||
| 		i := pruneSignExt(e.b[:lim], pos) | ||||
| 		e.w.writen1(bd | lim - 1 - byte(i)) | ||||
| 		e.w.writeb(e.b[i:lim]) | ||||
| 	} else { | ||||
| 		e.w.writen1(bd | lim - 1) | ||||
| 		e.w.writeb(e.b[:lim]) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) EncodeInt(v int64) { | ||||
| 	const nbd byte = bincVdNegInt << 4 | ||||
| 	if v >= 0 { | ||||
| 		e.encUint(bincVdPosInt<<4, true, uint64(v)) | ||||
| 	} else if v == -1 { | ||||
| 		e.w.writen1(bincVdSpecial<<4 | bincSpNegOne) | ||||
| 	} else { | ||||
| 		e.encUint(bincVdNegInt<<4, false, uint64(-v)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) EncodeUint(v uint64) { | ||||
| 	e.encUint(bincVdPosInt<<4, true, v) | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) encUint(bd byte, pos bool, v uint64) { | ||||
| 	if v == 0 { | ||||
| 		e.w.writen1(bincVdSpecial<<4 | bincSpZero) | ||||
| 	} else if pos && v >= 1 && v <= 16 { | ||||
| 		e.w.writen1(bincVdSmallInt<<4 | byte(v-1)) | ||||
| 	} else if v <= math.MaxUint8 { | ||||
| 		e.w.writen2(bd|0x0, byte(v)) | ||||
| 	} else if v <= math.MaxUint16 { | ||||
| 		e.w.writen1(bd | 0x01) | ||||
| 		bigenHelper{e.b[:2], e.w}.writeUint16(uint16(v)) | ||||
| 	} else if v <= math.MaxUint32 { | ||||
| 		e.encIntegerPrune(bd, pos, v, 4) | ||||
| 	} else { | ||||
| 		e.encIntegerPrune(bd, pos, v, 8) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) EncodeExt(rv interface{}, xtag uint64, ext Ext, _ *Encoder) { | ||||
| 	bs := ext.WriteExt(rv) | ||||
| 	if bs == nil { | ||||
| 		e.EncodeNil() | ||||
| 		return | ||||
| 	} | ||||
| 	e.encodeExtPreamble(uint8(xtag), len(bs)) | ||||
| 	e.w.writeb(bs) | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) EncodeRawExt(re *RawExt, _ *Encoder) { | ||||
| 	e.encodeExtPreamble(uint8(re.Tag), len(re.Data)) | ||||
| 	e.w.writeb(re.Data) | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) encodeExtPreamble(xtag byte, length int) { | ||||
| 	e.encLen(bincVdCustomExt<<4, uint64(length)) | ||||
| 	e.w.writen1(xtag) | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) WriteArrayStart(length int) { | ||||
| 	e.encLen(bincVdArray<<4, uint64(length)) | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) WriteMapStart(length int) { | ||||
| 	e.encLen(bincVdMap<<4, uint64(length)) | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) EncodeString(c charEncoding, v string) { | ||||
| 	l := uint64(len(v)) | ||||
| 	e.encBytesLen(c, l) | ||||
| 	if l > 0 { | ||||
| 		e.w.writestr(v) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) EncodeSymbol(v string) { | ||||
| 	// if WriteSymbolsNoRefs { | ||||
| 	// 	e.encodeString(c_UTF8, v) | ||||
| 	// 	return | ||||
| 	// } | ||||
|  | ||||
| 	//symbols only offer benefit when string length > 1. | ||||
| 	//This is because strings with length 1 take only 2 bytes to store | ||||
| 	//(bd with embedded length, and single byte for string val). | ||||
|  | ||||
| 	l := len(v) | ||||
| 	if l == 0 { | ||||
| 		e.encBytesLen(c_UTF8, 0) | ||||
| 		return | ||||
| 	} else if l == 1 { | ||||
| 		e.encBytesLen(c_UTF8, 1) | ||||
| 		e.w.writen1(v[0]) | ||||
| 		return | ||||
| 	} | ||||
| 	if e.m == nil { | ||||
| 		e.m = make(map[string]uint16, 16) | ||||
| 	} | ||||
| 	ui, ok := e.m[v] | ||||
| 	if ok { | ||||
| 		if ui <= math.MaxUint8 { | ||||
| 			e.w.writen2(bincVdSymbol<<4, byte(ui)) | ||||
| 		} else { | ||||
| 			e.w.writen1(bincVdSymbol<<4 | 0x8) | ||||
| 			bigenHelper{e.b[:2], e.w}.writeUint16(ui) | ||||
| 		} | ||||
| 	} else { | ||||
| 		e.s++ | ||||
| 		ui = e.s | ||||
| 		//ui = uint16(atomic.AddUint32(&e.s, 1)) | ||||
| 		e.m[v] = ui | ||||
| 		var lenprec uint8 | ||||
| 		if l <= math.MaxUint8 { | ||||
| 			// lenprec = 0 | ||||
| 		} else if l <= math.MaxUint16 { | ||||
| 			lenprec = 1 | ||||
| 		} else if int64(l) <= math.MaxUint32 { | ||||
| 			lenprec = 2 | ||||
| 		} else { | ||||
| 			lenprec = 3 | ||||
| 		} | ||||
| 		if ui <= math.MaxUint8 { | ||||
| 			e.w.writen2(bincVdSymbol<<4|0x0|0x4|lenprec, byte(ui)) | ||||
| 		} else { | ||||
| 			e.w.writen1(bincVdSymbol<<4 | 0x8 | 0x4 | lenprec) | ||||
| 			bigenHelper{e.b[:2], e.w}.writeUint16(ui) | ||||
| 		} | ||||
| 		if lenprec == 0 { | ||||
| 			e.w.writen1(byte(l)) | ||||
| 		} else if lenprec == 1 { | ||||
| 			bigenHelper{e.b[:2], e.w}.writeUint16(uint16(l)) | ||||
| 		} else if lenprec == 2 { | ||||
| 			bigenHelper{e.b[:4], e.w}.writeUint32(uint32(l)) | ||||
| 		} else { | ||||
| 			bigenHelper{e.b[:8], e.w}.writeUint64(uint64(l)) | ||||
| 		} | ||||
| 		e.w.writestr(v) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) EncodeStringBytes(c charEncoding, v []byte) { | ||||
| 	l := uint64(len(v)) | ||||
| 	e.encBytesLen(c, l) | ||||
| 	if l > 0 { | ||||
| 		e.w.writeb(v) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) encBytesLen(c charEncoding, length uint64) { | ||||
| 	//TODO: support bincUnicodeOther (for now, just use string or bytearray) | ||||
| 	if c == c_RAW { | ||||
| 		e.encLen(bincVdByteArray<<4, length) | ||||
| 	} else { | ||||
| 		e.encLen(bincVdString<<4, length) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) encLen(bd byte, l uint64) { | ||||
| 	if l < 12 { | ||||
| 		e.w.writen1(bd | uint8(l+4)) | ||||
| 	} else { | ||||
| 		e.encLenNumber(bd, l) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) encLenNumber(bd byte, v uint64) { | ||||
| 	if v <= math.MaxUint8 { | ||||
| 		e.w.writen2(bd, byte(v)) | ||||
| 	} else if v <= math.MaxUint16 { | ||||
| 		e.w.writen1(bd | 0x01) | ||||
| 		bigenHelper{e.b[:2], e.w}.writeUint16(uint16(v)) | ||||
| 	} else if v <= math.MaxUint32 { | ||||
| 		e.w.writen1(bd | 0x02) | ||||
| 		bigenHelper{e.b[:4], e.w}.writeUint32(uint32(v)) | ||||
| 	} else { | ||||
| 		e.w.writen1(bd | 0x03) | ||||
| 		bigenHelper{e.b[:8], e.w}.writeUint64(uint64(v)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| //------------------------------------ | ||||
|  | ||||
| type bincDecSymbol struct { | ||||
| 	s string | ||||
| 	b []byte | ||||
| 	i uint16 | ||||
| } | ||||
|  | ||||
| type bincDecDriver struct { | ||||
| 	d      *Decoder | ||||
| 	h      *BincHandle | ||||
| 	r      decReader | ||||
| 	br     bool // bytes reader | ||||
| 	bdRead bool | ||||
| 	bd     byte | ||||
| 	vd     byte | ||||
| 	vs     byte | ||||
| 	// noStreamingCodec | ||||
| 	// decNoSeparator | ||||
| 	b [scratchByteArrayLen]byte | ||||
|  | ||||
| 	// linear searching on this slice is ok, | ||||
| 	// because we typically expect < 32 symbols in each stream. | ||||
| 	s []bincDecSymbol | ||||
| 	decDriverNoopContainerReader | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) readNextBd() { | ||||
| 	d.bd = d.r.readn1() | ||||
| 	d.vd = d.bd >> 4 | ||||
| 	d.vs = d.bd & 0x0f | ||||
| 	d.bdRead = true | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) uncacheRead() { | ||||
| 	if d.bdRead { | ||||
| 		d.r.unreadn1() | ||||
| 		d.bdRead = false | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) ContainerType() (vt valueType) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.vd == bincVdSpecial && d.vs == bincSpNil { | ||||
| 		return valueTypeNil | ||||
| 	} else if d.vd == bincVdByteArray { | ||||
| 		return valueTypeBytes | ||||
| 	} else if d.vd == bincVdString { | ||||
| 		return valueTypeString | ||||
| 	} else if d.vd == bincVdArray { | ||||
| 		return valueTypeArray | ||||
| 	} else if d.vd == bincVdMap { | ||||
| 		return valueTypeMap | ||||
| 	} else { | ||||
| 		// d.d.errorf("isContainerType: unsupported parameter: %v", vt) | ||||
| 	} | ||||
| 	return valueTypeUnset | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) TryDecodeAsNil() bool { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.bd == bincVdSpecial<<4|bincSpNil { | ||||
| 		d.bdRead = false | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) IsBuiltinType(rt uintptr) bool { | ||||
| 	return rt == timeTypId | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) DecodeBuiltin(rt uintptr, v interface{}) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if rt == timeTypId { | ||||
| 		if d.vd != bincVdTimestamp { | ||||
| 			d.d.errorf("Invalid d.vd. Expecting 0x%x. Received: 0x%x", bincVdTimestamp, d.vd) | ||||
| 			return | ||||
| 		} | ||||
| 		tt, err := decodeTime(d.r.readx(int(d.vs))) | ||||
| 		if err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
| 		var vt *time.Time = v.(*time.Time) | ||||
| 		*vt = tt | ||||
| 		d.bdRead = false | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) decFloatPre(vs, defaultLen byte) { | ||||
| 	if vs&0x8 == 0 { | ||||
| 		d.r.readb(d.b[0:defaultLen]) | ||||
| 	} else { | ||||
| 		l := d.r.readn1() | ||||
| 		if l > 8 { | ||||
| 			d.d.errorf("At most 8 bytes used to represent float. Received: %v bytes", l) | ||||
| 			return | ||||
| 		} | ||||
| 		for i := l; i < 8; i++ { | ||||
| 			d.b[i] = 0 | ||||
| 		} | ||||
| 		d.r.readb(d.b[0:l]) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) decFloat() (f float64) { | ||||
| 	//if true { f = math.Float64frombits(bigen.Uint64(d.r.readx(8))); break; } | ||||
| 	if x := d.vs & 0x7; x == bincFlBin32 { | ||||
| 		d.decFloatPre(d.vs, 4) | ||||
| 		f = float64(math.Float32frombits(bigen.Uint32(d.b[0:4]))) | ||||
| 	} else if x == bincFlBin64 { | ||||
| 		d.decFloatPre(d.vs, 8) | ||||
| 		f = math.Float64frombits(bigen.Uint64(d.b[0:8])) | ||||
| 	} else { | ||||
| 		d.d.errorf("only float32 and float64 are supported. d.vd: 0x%x, d.vs: 0x%x", d.vd, d.vs) | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) decUint() (v uint64) { | ||||
| 	// need to inline the code (interface conversion and type assertion expensive) | ||||
| 	switch d.vs { | ||||
| 	case 0: | ||||
| 		v = uint64(d.r.readn1()) | ||||
| 	case 1: | ||||
| 		d.r.readb(d.b[6:8]) | ||||
| 		v = uint64(bigen.Uint16(d.b[6:8])) | ||||
| 	case 2: | ||||
| 		d.b[4] = 0 | ||||
| 		d.r.readb(d.b[5:8]) | ||||
| 		v = uint64(bigen.Uint32(d.b[4:8])) | ||||
| 	case 3: | ||||
| 		d.r.readb(d.b[4:8]) | ||||
| 		v = uint64(bigen.Uint32(d.b[4:8])) | ||||
| 	case 4, 5, 6: | ||||
| 		lim := int(7 - d.vs) | ||||
| 		d.r.readb(d.b[lim:8]) | ||||
| 		for i := 0; i < lim; i++ { | ||||
| 			d.b[i] = 0 | ||||
| 		} | ||||
| 		v = uint64(bigen.Uint64(d.b[:8])) | ||||
| 	case 7: | ||||
| 		d.r.readb(d.b[:8]) | ||||
| 		v = uint64(bigen.Uint64(d.b[:8])) | ||||
| 	default: | ||||
| 		d.d.errorf("unsigned integers with greater than 64 bits of precision not supported") | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) decCheckInteger() (ui uint64, neg bool) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	vd, vs := d.vd, d.vs | ||||
| 	if vd == bincVdPosInt { | ||||
| 		ui = d.decUint() | ||||
| 	} else if vd == bincVdNegInt { | ||||
| 		ui = d.decUint() | ||||
| 		neg = true | ||||
| 	} else if vd == bincVdSmallInt { | ||||
| 		ui = uint64(d.vs) + 1 | ||||
| 	} else if vd == bincVdSpecial { | ||||
| 		if vs == bincSpZero { | ||||
| 			//i = 0 | ||||
| 		} else if vs == bincSpNegOne { | ||||
| 			neg = true | ||||
| 			ui = 1 | ||||
| 		} else { | ||||
| 			d.d.errorf("numeric decode fails for special value: d.vs: 0x%x", d.vs) | ||||
| 			return | ||||
| 		} | ||||
| 	} else { | ||||
| 		d.d.errorf("number can only be decoded from uint or int values. d.bd: 0x%x, d.vd: 0x%x", d.bd, d.vd) | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) DecodeInt(bitsize uint8) (i int64) { | ||||
| 	ui, neg := d.decCheckInteger() | ||||
| 	i, overflow := chkOvf.SignedInt(ui) | ||||
| 	if overflow { | ||||
| 		d.d.errorf("simple: overflow converting %v to signed integer", ui) | ||||
| 		return | ||||
| 	} | ||||
| 	if neg { | ||||
| 		i = -i | ||||
| 	} | ||||
| 	if chkOvf.Int(i, bitsize) { | ||||
| 		d.d.errorf("binc: overflow integer: %v for num bits: %v", i, bitsize) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) DecodeUint(bitsize uint8) (ui uint64) { | ||||
| 	ui, neg := d.decCheckInteger() | ||||
| 	if neg { | ||||
| 		d.d.errorf("Assigning negative signed value to unsigned type") | ||||
| 		return | ||||
| 	} | ||||
| 	if chkOvf.Uint(ui, bitsize) { | ||||
| 		d.d.errorf("binc: overflow integer: %v", ui) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	vd, vs := d.vd, d.vs | ||||
| 	if vd == bincVdSpecial { | ||||
| 		d.bdRead = false | ||||
| 		if vs == bincSpNan { | ||||
| 			return math.NaN() | ||||
| 		} else if vs == bincSpPosInf { | ||||
| 			return math.Inf(1) | ||||
| 		} else if vs == bincSpZeroFloat || vs == bincSpZero { | ||||
| 			return | ||||
| 		} else if vs == bincSpNegInf { | ||||
| 			return math.Inf(-1) | ||||
| 		} else { | ||||
| 			d.d.errorf("Invalid d.vs decoding float where d.vd=bincVdSpecial: %v", d.vs) | ||||
| 			return | ||||
| 		} | ||||
| 	} else if vd == bincVdFloat { | ||||
| 		f = d.decFloat() | ||||
| 	} else { | ||||
| 		f = float64(d.DecodeInt(64)) | ||||
| 	} | ||||
| 	if chkOverflow32 && chkOvf.Float32(f) { | ||||
| 		d.d.errorf("binc: float32 overflow: %v", f) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // bool can be decoded from bool only (single byte). | ||||
| func (d *bincDecDriver) DecodeBool() (b bool) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if bd := d.bd; bd == (bincVdSpecial | bincSpFalse) { | ||||
| 		// b = false | ||||
| 	} else if bd == (bincVdSpecial | bincSpTrue) { | ||||
| 		b = true | ||||
| 	} else { | ||||
| 		d.d.errorf("Invalid single-byte value for bool: %s: %x", msgBadDesc, d.bd) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) ReadMapStart() (length int) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.vd != bincVdMap { | ||||
| 		d.d.errorf("Invalid d.vd for map. Expecting 0x%x. Got: 0x%x", bincVdMap, d.vd) | ||||
| 		return | ||||
| 	} | ||||
| 	length = d.decLen() | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) ReadArrayStart() (length int) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.vd != bincVdArray { | ||||
| 		d.d.errorf("Invalid d.vd for array. Expecting 0x%x. Got: 0x%x", bincVdArray, d.vd) | ||||
| 		return | ||||
| 	} | ||||
| 	length = d.decLen() | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) decLen() int { | ||||
| 	if d.vs > 3 { | ||||
| 		return int(d.vs - 4) | ||||
| 	} | ||||
| 	return int(d.decLenNumber()) | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) decLenNumber() (v uint64) { | ||||
| 	if x := d.vs; x == 0 { | ||||
| 		v = uint64(d.r.readn1()) | ||||
| 	} else if x == 1 { | ||||
| 		d.r.readb(d.b[6:8]) | ||||
| 		v = uint64(bigen.Uint16(d.b[6:8])) | ||||
| 	} else if x == 2 { | ||||
| 		d.r.readb(d.b[4:8]) | ||||
| 		v = uint64(bigen.Uint32(d.b[4:8])) | ||||
| 	} else { | ||||
| 		d.r.readb(d.b[:8]) | ||||
| 		v = bigen.Uint64(d.b[:8]) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) decStringAndBytes(bs []byte, withString, zerocopy bool) (bs2 []byte, s string) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.bd == bincVdSpecial<<4|bincSpNil { | ||||
| 		d.bdRead = false | ||||
| 		return | ||||
| 	} | ||||
| 	var slen int = -1 | ||||
| 	// var ok bool | ||||
| 	switch d.vd { | ||||
| 	case bincVdString, bincVdByteArray: | ||||
| 		slen = d.decLen() | ||||
| 		if zerocopy { | ||||
| 			if d.br { | ||||
| 				bs2 = d.r.readx(slen) | ||||
| 			} else if len(bs) == 0 { | ||||
| 				bs2 = decByteSlice(d.r, slen, d.d.h.MaxInitLen, d.b[:]) | ||||
| 			} else { | ||||
| 				bs2 = decByteSlice(d.r, slen, d.d.h.MaxInitLen, bs) | ||||
| 			} | ||||
| 		} else { | ||||
| 			bs2 = decByteSlice(d.r, slen, d.d.h.MaxInitLen, bs) | ||||
| 		} | ||||
| 		if withString { | ||||
| 			s = string(bs2) | ||||
| 		} | ||||
| 	case bincVdSymbol: | ||||
| 		// zerocopy doesn't apply for symbols, | ||||
| 		// as the values must be stored in a table for later use. | ||||
| 		// | ||||
| 		//from vs: extract numSymbolBytes, containsStringVal, strLenPrecision, | ||||
| 		//extract symbol | ||||
| 		//if containsStringVal, read it and put in map | ||||
| 		//else look in map for string value | ||||
| 		var symbol uint16 | ||||
| 		vs := d.vs | ||||
| 		if vs&0x8 == 0 { | ||||
| 			symbol = uint16(d.r.readn1()) | ||||
| 		} else { | ||||
| 			symbol = uint16(bigen.Uint16(d.r.readx(2))) | ||||
| 		} | ||||
| 		if d.s == nil { | ||||
| 			d.s = make([]bincDecSymbol, 0, 16) | ||||
| 		} | ||||
|  | ||||
| 		if vs&0x4 == 0 { | ||||
| 			for i := range d.s { | ||||
| 				j := &d.s[i] | ||||
| 				if j.i == symbol { | ||||
| 					bs2 = j.b | ||||
| 					if withString { | ||||
| 						if j.s == "" && bs2 != nil { | ||||
| 							j.s = string(bs2) | ||||
| 						} | ||||
| 						s = j.s | ||||
| 					} | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 		} else { | ||||
| 			switch vs & 0x3 { | ||||
| 			case 0: | ||||
| 				slen = int(d.r.readn1()) | ||||
| 			case 1: | ||||
| 				slen = int(bigen.Uint16(d.r.readx(2))) | ||||
| 			case 2: | ||||
| 				slen = int(bigen.Uint32(d.r.readx(4))) | ||||
| 			case 3: | ||||
| 				slen = int(bigen.Uint64(d.r.readx(8))) | ||||
| 			} | ||||
| 			// since using symbols, do not store any part of | ||||
| 			// the parameter bs in the map, as it might be a shared buffer. | ||||
| 			// bs2 = decByteSlice(d.r, slen, bs) | ||||
| 			bs2 = decByteSlice(d.r, slen, d.d.h.MaxInitLen, nil) | ||||
| 			if withString { | ||||
| 				s = string(bs2) | ||||
| 			} | ||||
| 			d.s = append(d.s, bincDecSymbol{i: symbol, s: s, b: bs2}) | ||||
| 		} | ||||
| 	default: | ||||
| 		d.d.errorf("Invalid d.vd. Expecting string:0x%x, bytearray:0x%x or symbol: 0x%x. Got: 0x%x", | ||||
| 			bincVdString, bincVdByteArray, bincVdSymbol, d.vd) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) DecodeString() (s string) { | ||||
| 	// DecodeBytes does not accommodate symbols, whose impl stores string version in map. | ||||
| 	// Use decStringAndBytes directly. | ||||
| 	// return string(d.DecodeBytes(d.b[:], true, true)) | ||||
| 	_, s = d.decStringAndBytes(d.b[:], true, true) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) DecodeStringAsBytes() (s []byte) { | ||||
| 	s, _ = d.decStringAndBytes(d.b[:], false, true) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) DecodeBytes(bs []byte, zerocopy bool) (bsOut []byte) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.bd == bincVdSpecial<<4|bincSpNil { | ||||
| 		d.bdRead = false | ||||
| 		return nil | ||||
| 	} | ||||
| 	var clen int | ||||
| 	if d.vd == bincVdString || d.vd == bincVdByteArray { | ||||
| 		clen = d.decLen() | ||||
| 	} else { | ||||
| 		d.d.errorf("Invalid d.vd for bytes. Expecting string:0x%x or bytearray:0x%x. Got: 0x%x", | ||||
| 			bincVdString, bincVdByteArray, d.vd) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	if zerocopy { | ||||
| 		if d.br { | ||||
| 			return d.r.readx(clen) | ||||
| 		} else if len(bs) == 0 { | ||||
| 			bs = d.b[:] | ||||
| 		} | ||||
| 	} | ||||
| 	return decByteSlice(d.r, clen, d.d.h.MaxInitLen, bs) | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) DecodeExt(rv interface{}, xtag uint64, ext Ext) (realxtag uint64) { | ||||
| 	if xtag > 0xff { | ||||
| 		d.d.errorf("decodeExt: tag must be <= 0xff; got: %v", xtag) | ||||
| 		return | ||||
| 	} | ||||
| 	realxtag1, xbs := d.decodeExtV(ext != nil, uint8(xtag)) | ||||
| 	realxtag = uint64(realxtag1) | ||||
| 	if ext == nil { | ||||
| 		re := rv.(*RawExt) | ||||
| 		re.Tag = realxtag | ||||
| 		re.Data = detachZeroCopyBytes(d.br, re.Data, xbs) | ||||
| 	} else { | ||||
| 		ext.ReadExt(rv, xbs) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) decodeExtV(verifyTag bool, tag byte) (xtag byte, xbs []byte) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.vd == bincVdCustomExt { | ||||
| 		l := d.decLen() | ||||
| 		xtag = d.r.readn1() | ||||
| 		if verifyTag && xtag != tag { | ||||
| 			d.d.errorf("Wrong extension tag. Got %b. Expecting: %v", xtag, tag) | ||||
| 			return | ||||
| 		} | ||||
| 		xbs = d.r.readx(l) | ||||
| 	} else if d.vd == bincVdByteArray { | ||||
| 		xbs = d.DecodeBytes(nil, true) | ||||
| 	} else { | ||||
| 		d.d.errorf("Invalid d.vd for extensions (Expecting extensions or byte array). Got: 0x%x", d.vd) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) DecodeNaked() { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
|  | ||||
| 	n := d.d.n | ||||
| 	var decodeFurther bool | ||||
|  | ||||
| 	switch d.vd { | ||||
| 	case bincVdSpecial: | ||||
| 		switch d.vs { | ||||
| 		case bincSpNil: | ||||
| 			n.v = valueTypeNil | ||||
| 		case bincSpFalse: | ||||
| 			n.v = valueTypeBool | ||||
| 			n.b = false | ||||
| 		case bincSpTrue: | ||||
| 			n.v = valueTypeBool | ||||
| 			n.b = true | ||||
| 		case bincSpNan: | ||||
| 			n.v = valueTypeFloat | ||||
| 			n.f = math.NaN() | ||||
| 		case bincSpPosInf: | ||||
| 			n.v = valueTypeFloat | ||||
| 			n.f = math.Inf(1) | ||||
| 		case bincSpNegInf: | ||||
| 			n.v = valueTypeFloat | ||||
| 			n.f = math.Inf(-1) | ||||
| 		case bincSpZeroFloat: | ||||
| 			n.v = valueTypeFloat | ||||
| 			n.f = float64(0) | ||||
| 		case bincSpZero: | ||||
| 			n.v = valueTypeUint | ||||
| 			n.u = uint64(0) // int8(0) | ||||
| 		case bincSpNegOne: | ||||
| 			n.v = valueTypeInt | ||||
| 			n.i = int64(-1) // int8(-1) | ||||
| 		default: | ||||
| 			d.d.errorf("decodeNaked: Unrecognized special value 0x%x", d.vs) | ||||
| 		} | ||||
| 	case bincVdSmallInt: | ||||
| 		n.v = valueTypeUint | ||||
| 		n.u = uint64(int8(d.vs)) + 1 // int8(d.vs) + 1 | ||||
| 	case bincVdPosInt: | ||||
| 		n.v = valueTypeUint | ||||
| 		n.u = d.decUint() | ||||
| 	case bincVdNegInt: | ||||
| 		n.v = valueTypeInt | ||||
| 		n.i = -(int64(d.decUint())) | ||||
| 	case bincVdFloat: | ||||
| 		n.v = valueTypeFloat | ||||
| 		n.f = d.decFloat() | ||||
| 	case bincVdSymbol: | ||||
| 		n.v = valueTypeSymbol | ||||
| 		n.s = d.DecodeString() | ||||
| 	case bincVdString: | ||||
| 		n.v = valueTypeString | ||||
| 		n.s = d.DecodeString() | ||||
| 	case bincVdByteArray: | ||||
| 		n.v = valueTypeBytes | ||||
| 		n.l = d.DecodeBytes(nil, false) | ||||
| 	case bincVdTimestamp: | ||||
| 		n.v = valueTypeTimestamp | ||||
| 		tt, err := decodeTime(d.r.readx(int(d.vs))) | ||||
| 		if err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
| 		n.t = tt | ||||
| 	case bincVdCustomExt: | ||||
| 		n.v = valueTypeExt | ||||
| 		l := d.decLen() | ||||
| 		n.u = uint64(d.r.readn1()) | ||||
| 		n.l = d.r.readx(l) | ||||
| 	case bincVdArray: | ||||
| 		n.v = valueTypeArray | ||||
| 		decodeFurther = true | ||||
| 	case bincVdMap: | ||||
| 		n.v = valueTypeMap | ||||
| 		decodeFurther = true | ||||
| 	default: | ||||
| 		d.d.errorf("decodeNaked: Unrecognized d.vd: 0x%x", d.vd) | ||||
| 	} | ||||
|  | ||||
| 	if !decodeFurther { | ||||
| 		d.bdRead = false | ||||
| 	} | ||||
| 	if n.v == valueTypeUint && d.h.SignedInteger { | ||||
| 		n.v = valueTypeInt | ||||
| 		n.i = int64(n.u) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| //------------------------------------ | ||||
|  | ||||
| //BincHandle is a Handle for the Binc Schema-Free Encoding Format | ||||
| //defined at https://github.com/ugorji/binc . | ||||
| // | ||||
| //BincHandle currently supports all Binc features with the following EXCEPTIONS: | ||||
| //  - only integers up to 64 bits of precision are supported. | ||||
| //    big integers are unsupported. | ||||
| //  - Only IEEE 754 binary32 and binary64 floats are supported (ie Go float32 and float64 types). | ||||
| //    extended precision and decimal IEEE 754 floats are unsupported. | ||||
| //  - Only UTF-8 strings supported. | ||||
| //    Unicode_Other Binc types (UTF16, UTF32) are currently unsupported. | ||||
| // | ||||
| //Note that these EXCEPTIONS are temporary and full support is possible and may happen soon. | ||||
| type BincHandle struct { | ||||
| 	BasicHandle | ||||
| 	binaryEncodingType | ||||
| 	noElemSeparators | ||||
| } | ||||
|  | ||||
| func (h *BincHandle) SetBytesExt(rt reflect.Type, tag uint64, ext BytesExt) (err error) { | ||||
| 	return h.SetExt(rt, tag, &setExtWrapper{b: ext}) | ||||
| } | ||||
|  | ||||
| func (h *BincHandle) newEncDriver(e *Encoder) encDriver { | ||||
| 	return &bincEncDriver{e: e, w: e.w} | ||||
| } | ||||
|  | ||||
| func (h *BincHandle) newDecDriver(d *Decoder) decDriver { | ||||
| 	return &bincDecDriver{d: d, h: h, r: d.r, br: d.bytes} | ||||
| } | ||||
|  | ||||
| func (_ *BincHandle) IsBuiltinType(rt uintptr) bool { | ||||
| 	return rt == timeTypId | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) reset() { | ||||
| 	e.w = e.e.w | ||||
| 	e.s = 0 | ||||
| 	e.m = nil | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) reset() { | ||||
| 	d.r, d.br = d.d.r, d.d.bytes | ||||
| 	d.s = nil | ||||
| 	d.bd, d.bdRead, d.vd, d.vs = 0, false, 0, 0 | ||||
| } | ||||
|  | ||||
| var _ decDriver = (*bincDecDriver)(nil) | ||||
| var _ encDriver = (*bincEncDriver)(nil) | ||||
							
								
								
									
										631
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/cbor.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										631
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/cbor.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,631 @@ | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| package codec | ||||
|  | ||||
| import ( | ||||
| 	"math" | ||||
| 	"reflect" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	cborMajorUint byte = iota | ||||
| 	cborMajorNegInt | ||||
| 	cborMajorBytes | ||||
| 	cborMajorText | ||||
| 	cborMajorArray | ||||
| 	cborMajorMap | ||||
| 	cborMajorTag | ||||
| 	cborMajorOther | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	cborBdFalse byte = 0xf4 + iota | ||||
| 	cborBdTrue | ||||
| 	cborBdNil | ||||
| 	cborBdUndefined | ||||
| 	cborBdExt | ||||
| 	cborBdFloat16 | ||||
| 	cborBdFloat32 | ||||
| 	cborBdFloat64 | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	cborBdIndefiniteBytes  byte = 0x5f | ||||
| 	cborBdIndefiniteString      = 0x7f | ||||
| 	cborBdIndefiniteArray       = 0x9f | ||||
| 	cborBdIndefiniteMap         = 0xbf | ||||
| 	cborBdBreak                 = 0xff | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	CborStreamBytes  byte = 0x5f | ||||
| 	CborStreamString      = 0x7f | ||||
| 	CborStreamArray       = 0x9f | ||||
| 	CborStreamMap         = 0xbf | ||||
| 	CborStreamBreak       = 0xff | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	cborBaseUint   byte = 0x00 | ||||
| 	cborBaseNegInt      = 0x20 | ||||
| 	cborBaseBytes       = 0x40 | ||||
| 	cborBaseString      = 0x60 | ||||
| 	cborBaseArray       = 0x80 | ||||
| 	cborBaseMap         = 0xa0 | ||||
| 	cborBaseTag         = 0xc0 | ||||
| 	cborBaseSimple      = 0xe0 | ||||
| ) | ||||
|  | ||||
| // ------------------- | ||||
|  | ||||
| type cborEncDriver struct { | ||||
| 	noBuiltInTypes | ||||
| 	encDriverNoopContainerWriter | ||||
| 	// encNoSeparator | ||||
| 	e *Encoder | ||||
| 	w encWriter | ||||
| 	h *CborHandle | ||||
| 	x [8]byte | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) EncodeNil() { | ||||
| 	e.w.writen1(cborBdNil) | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) EncodeBool(b bool) { | ||||
| 	if b { | ||||
| 		e.w.writen1(cborBdTrue) | ||||
| 	} else { | ||||
| 		e.w.writen1(cborBdFalse) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) EncodeFloat32(f float32) { | ||||
| 	e.w.writen1(cborBdFloat32) | ||||
| 	bigenHelper{e.x[:4], e.w}.writeUint32(math.Float32bits(f)) | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) EncodeFloat64(f float64) { | ||||
| 	e.w.writen1(cborBdFloat64) | ||||
| 	bigenHelper{e.x[:8], e.w}.writeUint64(math.Float64bits(f)) | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) encUint(v uint64, bd byte) { | ||||
| 	if v <= 0x17 { | ||||
| 		e.w.writen1(byte(v) + bd) | ||||
| 	} else if v <= math.MaxUint8 { | ||||
| 		e.w.writen2(bd+0x18, uint8(v)) | ||||
| 	} else if v <= math.MaxUint16 { | ||||
| 		e.w.writen1(bd + 0x19) | ||||
| 		bigenHelper{e.x[:2], e.w}.writeUint16(uint16(v)) | ||||
| 	} else if v <= math.MaxUint32 { | ||||
| 		e.w.writen1(bd + 0x1a) | ||||
| 		bigenHelper{e.x[:4], e.w}.writeUint32(uint32(v)) | ||||
| 	} else { // if v <= math.MaxUint64 { | ||||
| 		e.w.writen1(bd + 0x1b) | ||||
| 		bigenHelper{e.x[:8], e.w}.writeUint64(v) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) EncodeInt(v int64) { | ||||
| 	if v < 0 { | ||||
| 		e.encUint(uint64(-1-v), cborBaseNegInt) | ||||
| 	} else { | ||||
| 		e.encUint(uint64(v), cborBaseUint) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) EncodeUint(v uint64) { | ||||
| 	e.encUint(v, cborBaseUint) | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) encLen(bd byte, length int) { | ||||
| 	e.encUint(uint64(length), bd) | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) EncodeExt(rv interface{}, xtag uint64, ext Ext, en *Encoder) { | ||||
| 	e.encUint(uint64(xtag), cborBaseTag) | ||||
| 	if v := ext.ConvertExt(rv); v == nil { | ||||
| 		e.EncodeNil() | ||||
| 	} else { | ||||
| 		en.encode(v) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) EncodeRawExt(re *RawExt, en *Encoder) { | ||||
| 	e.encUint(uint64(re.Tag), cborBaseTag) | ||||
| 	if false && re.Data != nil { | ||||
| 		en.encode(re.Data) | ||||
| 	} else if re.Value != nil { | ||||
| 		en.encode(re.Value) | ||||
| 	} else { | ||||
| 		e.EncodeNil() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) WriteArrayStart(length int) { | ||||
| 	if e.h.IndefiniteLength { | ||||
| 		e.w.writen1(cborBdIndefiniteArray) | ||||
| 	} else { | ||||
| 		e.encLen(cborBaseArray, length) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) WriteMapStart(length int) { | ||||
| 	if e.h.IndefiniteLength { | ||||
| 		e.w.writen1(cborBdIndefiniteMap) | ||||
| 	} else { | ||||
| 		e.encLen(cborBaseMap, length) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) WriteMapEnd() { | ||||
| 	if e.h.IndefiniteLength { | ||||
| 		e.w.writen1(cborBdBreak) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) WriteArrayEnd() { | ||||
| 	if e.h.IndefiniteLength { | ||||
| 		e.w.writen1(cborBdBreak) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) EncodeString(c charEncoding, v string) { | ||||
| 	e.encLen(cborBaseString, len(v)) | ||||
| 	e.w.writestr(v) | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) EncodeSymbol(v string) { | ||||
| 	e.EncodeString(c_UTF8, v) | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) EncodeStringBytes(c charEncoding, v []byte) { | ||||
| 	if c == c_RAW { | ||||
| 		e.encLen(cborBaseBytes, len(v)) | ||||
| 	} else { | ||||
| 		e.encLen(cborBaseString, len(v)) | ||||
| 	} | ||||
| 	e.w.writeb(v) | ||||
| } | ||||
|  | ||||
| // ---------------------- | ||||
|  | ||||
| type cborDecDriver struct { | ||||
| 	d      *Decoder | ||||
| 	h      *CborHandle | ||||
| 	r      decReader | ||||
| 	b      [scratchByteArrayLen]byte | ||||
| 	br     bool // bytes reader | ||||
| 	bdRead bool | ||||
| 	bd     byte | ||||
| 	noBuiltInTypes | ||||
| 	// decNoSeparator | ||||
| 	decDriverNoopContainerReader | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) readNextBd() { | ||||
| 	d.bd = d.r.readn1() | ||||
| 	d.bdRead = true | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) uncacheRead() { | ||||
| 	if d.bdRead { | ||||
| 		d.r.unreadn1() | ||||
| 		d.bdRead = false | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) ContainerType() (vt valueType) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.bd == cborBdNil { | ||||
| 		return valueTypeNil | ||||
| 	} else if d.bd == cborBdIndefiniteBytes || (d.bd >= cborBaseBytes && d.bd < cborBaseString) { | ||||
| 		return valueTypeBytes | ||||
| 	} else if d.bd == cborBdIndefiniteString || (d.bd >= cborBaseString && d.bd < cborBaseArray) { | ||||
| 		return valueTypeString | ||||
| 	} else if d.bd == cborBdIndefiniteArray || (d.bd >= cborBaseArray && d.bd < cborBaseMap) { | ||||
| 		return valueTypeArray | ||||
| 	} else if d.bd == cborBdIndefiniteMap || (d.bd >= cborBaseMap && d.bd < cborBaseTag) { | ||||
| 		return valueTypeMap | ||||
| 	} else { | ||||
| 		// d.d.errorf("isContainerType: unsupported parameter: %v", vt) | ||||
| 	} | ||||
| 	return valueTypeUnset | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) TryDecodeAsNil() bool { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	// treat Nil and Undefined as nil values | ||||
| 	if d.bd == cborBdNil || d.bd == cborBdUndefined { | ||||
| 		d.bdRead = false | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) CheckBreak() bool { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.bd == cborBdBreak { | ||||
| 		d.bdRead = false | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) decUint() (ui uint64) { | ||||
| 	v := d.bd & 0x1f | ||||
| 	if v <= 0x17 { | ||||
| 		ui = uint64(v) | ||||
| 	} else { | ||||
| 		if v == 0x18 { | ||||
| 			ui = uint64(d.r.readn1()) | ||||
| 		} else if v == 0x19 { | ||||
| 			ui = uint64(bigen.Uint16(d.r.readx(2))) | ||||
| 		} else if v == 0x1a { | ||||
| 			ui = uint64(bigen.Uint32(d.r.readx(4))) | ||||
| 		} else if v == 0x1b { | ||||
| 			ui = uint64(bigen.Uint64(d.r.readx(8))) | ||||
| 		} else { | ||||
| 			d.d.errorf("decUint: Invalid descriptor: %v", d.bd) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) decCheckInteger() (neg bool) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	major := d.bd >> 5 | ||||
| 	if major == cborMajorUint { | ||||
| 	} else if major == cborMajorNegInt { | ||||
| 		neg = true | ||||
| 	} else { | ||||
| 		d.d.errorf("invalid major: %v (bd: %v)", major, d.bd) | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) DecodeInt(bitsize uint8) (i int64) { | ||||
| 	neg := d.decCheckInteger() | ||||
| 	ui := d.decUint() | ||||
| 	// check if this number can be converted to an int without overflow | ||||
| 	var overflow bool | ||||
| 	if neg { | ||||
| 		if i, overflow = chkOvf.SignedInt(ui + 1); overflow { | ||||
| 			d.d.errorf("cbor: overflow converting %v to signed integer", ui+1) | ||||
| 			return | ||||
| 		} | ||||
| 		i = -i | ||||
| 	} else { | ||||
| 		if i, overflow = chkOvf.SignedInt(ui); overflow { | ||||
| 			d.d.errorf("cbor: overflow converting %v to signed integer", ui) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	if chkOvf.Int(i, bitsize) { | ||||
| 		d.d.errorf("cbor: overflow integer: %v", i) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) DecodeUint(bitsize uint8) (ui uint64) { | ||||
| 	if d.decCheckInteger() { | ||||
| 		d.d.errorf("Assigning negative signed value to unsigned type") | ||||
| 		return | ||||
| 	} | ||||
| 	ui = d.decUint() | ||||
| 	if chkOvf.Uint(ui, bitsize) { | ||||
| 		d.d.errorf("cbor: overflow integer: %v", ui) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if bd := d.bd; bd == cborBdFloat16 { | ||||
| 		f = float64(math.Float32frombits(halfFloatToFloatBits(bigen.Uint16(d.r.readx(2))))) | ||||
| 	} else if bd == cborBdFloat32 { | ||||
| 		f = float64(math.Float32frombits(bigen.Uint32(d.r.readx(4)))) | ||||
| 	} else if bd == cborBdFloat64 { | ||||
| 		f = math.Float64frombits(bigen.Uint64(d.r.readx(8))) | ||||
| 	} else if bd >= cborBaseUint && bd < cborBaseBytes { | ||||
| 		f = float64(d.DecodeInt(64)) | ||||
| 	} else { | ||||
| 		d.d.errorf("Float only valid from float16/32/64: Invalid descriptor: %v", bd) | ||||
| 		return | ||||
| 	} | ||||
| 	if chkOverflow32 && chkOvf.Float32(f) { | ||||
| 		d.d.errorf("cbor: float32 overflow: %v", f) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // bool can be decoded from bool only (single byte). | ||||
| func (d *cborDecDriver) DecodeBool() (b bool) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if bd := d.bd; bd == cborBdTrue { | ||||
| 		b = true | ||||
| 	} else if bd == cborBdFalse { | ||||
| 	} else { | ||||
| 		d.d.errorf("Invalid single-byte value for bool: %s: %x", msgBadDesc, d.bd) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) ReadMapStart() (length int) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	if d.bd == cborBdIndefiniteMap { | ||||
| 		return -1 | ||||
| 	} | ||||
| 	return d.decLen() | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) ReadArrayStart() (length int) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	if d.bd == cborBdIndefiniteArray { | ||||
| 		return -1 | ||||
| 	} | ||||
| 	return d.decLen() | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) decLen() int { | ||||
| 	return int(d.decUint()) | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) decAppendIndefiniteBytes(bs []byte) []byte { | ||||
| 	d.bdRead = false | ||||
| 	for { | ||||
| 		if d.CheckBreak() { | ||||
| 			break | ||||
| 		} | ||||
| 		if major := d.bd >> 5; major != cborMajorBytes && major != cborMajorText { | ||||
| 			d.d.errorf("cbor: expect bytes or string major type in indefinite string/bytes; got: %v, byte: %v", major, d.bd) | ||||
| 			return nil | ||||
| 		} | ||||
| 		n := d.decLen() | ||||
| 		oldLen := len(bs) | ||||
| 		newLen := oldLen + n | ||||
| 		if newLen > cap(bs) { | ||||
| 			bs2 := make([]byte, newLen, 2*cap(bs)+n) | ||||
| 			copy(bs2, bs) | ||||
| 			bs = bs2 | ||||
| 		} else { | ||||
| 			bs = bs[:newLen] | ||||
| 		} | ||||
| 		d.r.readb(bs[oldLen:newLen]) | ||||
| 		// bs = append(bs, d.r.readn()...) | ||||
| 		d.bdRead = false | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return bs | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) DecodeBytes(bs []byte, zerocopy bool) (bsOut []byte) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.bd == cborBdNil || d.bd == cborBdUndefined { | ||||
| 		d.bdRead = false | ||||
| 		return nil | ||||
| 	} | ||||
| 	if d.bd == cborBdIndefiniteBytes || d.bd == cborBdIndefiniteString { | ||||
| 		if bs == nil { | ||||
| 			return d.decAppendIndefiniteBytes(nil) | ||||
| 		} | ||||
| 		return d.decAppendIndefiniteBytes(bs[:0]) | ||||
| 	} | ||||
| 	clen := d.decLen() | ||||
| 	d.bdRead = false | ||||
| 	if zerocopy { | ||||
| 		if d.br { | ||||
| 			return d.r.readx(clen) | ||||
| 		} else if len(bs) == 0 { | ||||
| 			bs = d.b[:] | ||||
| 		} | ||||
| 	} | ||||
| 	return decByteSlice(d.r, clen, d.d.h.MaxInitLen, bs) | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) DecodeString() (s string) { | ||||
| 	return string(d.DecodeBytes(d.b[:], true)) | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) DecodeStringAsBytes() (s []byte) { | ||||
| 	return d.DecodeBytes(d.b[:], true) | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) DecodeExt(rv interface{}, xtag uint64, ext Ext) (realxtag uint64) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	u := d.decUint() | ||||
| 	d.bdRead = false | ||||
| 	realxtag = u | ||||
| 	if ext == nil { | ||||
| 		re := rv.(*RawExt) | ||||
| 		re.Tag = realxtag | ||||
| 		d.d.decode(&re.Value) | ||||
| 	} else if xtag != realxtag { | ||||
| 		d.d.errorf("Wrong extension tag. Got %b. Expecting: %v", realxtag, xtag) | ||||
| 		return | ||||
| 	} else { | ||||
| 		var v interface{} | ||||
| 		d.d.decode(&v) | ||||
| 		ext.UpdateExt(rv, v) | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) DecodeNaked() { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
|  | ||||
| 	n := d.d.n | ||||
| 	var decodeFurther bool | ||||
|  | ||||
| 	switch d.bd { | ||||
| 	case cborBdNil: | ||||
| 		n.v = valueTypeNil | ||||
| 	case cborBdFalse: | ||||
| 		n.v = valueTypeBool | ||||
| 		n.b = false | ||||
| 	case cborBdTrue: | ||||
| 		n.v = valueTypeBool | ||||
| 		n.b = true | ||||
| 	case cborBdFloat16, cborBdFloat32: | ||||
| 		n.v = valueTypeFloat | ||||
| 		n.f = d.DecodeFloat(true) | ||||
| 	case cborBdFloat64: | ||||
| 		n.v = valueTypeFloat | ||||
| 		n.f = d.DecodeFloat(false) | ||||
| 	case cborBdIndefiniteBytes: | ||||
| 		n.v = valueTypeBytes | ||||
| 		n.l = d.DecodeBytes(nil, false) | ||||
| 	case cborBdIndefiniteString: | ||||
| 		n.v = valueTypeString | ||||
| 		n.s = d.DecodeString() | ||||
| 	case cborBdIndefiniteArray: | ||||
| 		n.v = valueTypeArray | ||||
| 		decodeFurther = true | ||||
| 	case cborBdIndefiniteMap: | ||||
| 		n.v = valueTypeMap | ||||
| 		decodeFurther = true | ||||
| 	default: | ||||
| 		switch { | ||||
| 		case d.bd >= cborBaseUint && d.bd < cborBaseNegInt: | ||||
| 			if d.h.SignedInteger { | ||||
| 				n.v = valueTypeInt | ||||
| 				n.i = d.DecodeInt(64) | ||||
| 			} else { | ||||
| 				n.v = valueTypeUint | ||||
| 				n.u = d.DecodeUint(64) | ||||
| 			} | ||||
| 		case d.bd >= cborBaseNegInt && d.bd < cborBaseBytes: | ||||
| 			n.v = valueTypeInt | ||||
| 			n.i = d.DecodeInt(64) | ||||
| 		case d.bd >= cborBaseBytes && d.bd < cborBaseString: | ||||
| 			n.v = valueTypeBytes | ||||
| 			n.l = d.DecodeBytes(nil, false) | ||||
| 		case d.bd >= cborBaseString && d.bd < cborBaseArray: | ||||
| 			n.v = valueTypeString | ||||
| 			n.s = d.DecodeString() | ||||
| 		case d.bd >= cborBaseArray && d.bd < cborBaseMap: | ||||
| 			n.v = valueTypeArray | ||||
| 			decodeFurther = true | ||||
| 		case d.bd >= cborBaseMap && d.bd < cborBaseTag: | ||||
| 			n.v = valueTypeMap | ||||
| 			decodeFurther = true | ||||
| 		case d.bd >= cborBaseTag && d.bd < cborBaseSimple: | ||||
| 			n.v = valueTypeExt | ||||
| 			n.u = d.decUint() | ||||
| 			n.l = nil | ||||
| 			// d.bdRead = false | ||||
| 			// d.d.decode(&re.Value) // handled by decode itself. | ||||
| 			// decodeFurther = true | ||||
| 		default: | ||||
| 			d.d.errorf("decodeNaked: Unrecognized d.bd: 0x%x", d.bd) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if !decodeFurther { | ||||
| 		d.bdRead = false | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // ------------------------- | ||||
|  | ||||
| // CborHandle is a Handle for the CBOR encoding format, | ||||
| // defined at http://tools.ietf.org/html/rfc7049 and documented further at http://cbor.io . | ||||
| // | ||||
| // CBOR is comprehensively supported, including support for: | ||||
| //   - indefinite-length arrays/maps/bytes/strings | ||||
| //   - (extension) tags in range 0..0xffff (0 .. 65535) | ||||
| //   - half, single and double-precision floats | ||||
| //   - all numbers (1, 2, 4 and 8-byte signed and unsigned integers) | ||||
| //   - nil, true, false, ... | ||||
| //   - arrays and maps, bytes and text strings | ||||
| // | ||||
| // None of the optional extensions (with tags) defined in the spec are supported out-of-the-box. | ||||
| // Users can implement them as needed (using SetExt), including spec-documented ones: | ||||
| //   - timestamp, BigNum, BigFloat, Decimals, Encoded Text (e.g. URL, regexp, base64, MIME Message), etc. | ||||
| // | ||||
| // To encode with indefinite lengths (streaming), users will use | ||||
| // (Must)Encode methods of *Encoder, along with writing CborStreamXXX constants. | ||||
| // | ||||
| // For example, to encode "one-byte" as an indefinite length string: | ||||
| //     var buf bytes.Buffer | ||||
| //     e := NewEncoder(&buf, new(CborHandle)) | ||||
| //     buf.WriteByte(CborStreamString) | ||||
| //     e.MustEncode("one-") | ||||
| //     e.MustEncode("byte") | ||||
| //     buf.WriteByte(CborStreamBreak) | ||||
| //     encodedBytes := buf.Bytes() | ||||
| //     var vv interface{} | ||||
| //     NewDecoderBytes(buf.Bytes(), new(CborHandle)).MustDecode(&vv) | ||||
| //     // Now, vv contains the same string "one-byte" | ||||
| // | ||||
| type CborHandle struct { | ||||
| 	binaryEncodingType | ||||
| 	noElemSeparators | ||||
| 	BasicHandle | ||||
|  | ||||
| 	// IndefiniteLength=true, means that we encode using indefinitelength | ||||
| 	IndefiniteLength bool | ||||
| } | ||||
|  | ||||
| func (h *CborHandle) SetInterfaceExt(rt reflect.Type, tag uint64, ext InterfaceExt) (err error) { | ||||
| 	return h.SetExt(rt, tag, &setExtWrapper{i: ext}) | ||||
| } | ||||
|  | ||||
| func (h *CborHandle) newEncDriver(e *Encoder) encDriver { | ||||
| 	return &cborEncDriver{e: e, w: e.w, h: h} | ||||
| } | ||||
|  | ||||
| func (h *CborHandle) newDecDriver(d *Decoder) decDriver { | ||||
| 	return &cborDecDriver{d: d, h: h, r: d.r, br: d.bytes} | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) reset() { | ||||
| 	e.w = e.e.w | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) reset() { | ||||
| 	d.r, d.br = d.d.r, d.d.bytes | ||||
| 	d.bd, d.bdRead = 0, false | ||||
| } | ||||
|  | ||||
| var _ decDriver = (*cborDecDriver)(nil) | ||||
| var _ encDriver = (*cborEncDriver)(nil) | ||||
							
								
								
									
										2520
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/decode.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2520
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/decode.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1414
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/encode.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1414
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/encode.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										33034
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/fast-path.generated.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33034
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/fast-path.generated.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										35
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/fast-path.not.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/fast-path.not.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| // +build notfastpath | ||||
|  | ||||
| package codec | ||||
|  | ||||
| import "reflect" | ||||
|  | ||||
| const fastpathEnabled = false | ||||
|  | ||||
| // The generated fast-path code is very large, and adds a few seconds to the build time. | ||||
| // This causes test execution, execution of small tools which use codec, etc | ||||
| // to take a long time. | ||||
| // | ||||
| // To mitigate, we now support the notfastpath tag. | ||||
| // This tag disables fastpath during build, allowing for faster build, test execution, | ||||
| // short-program runs, etc. | ||||
|  | ||||
| func fastpathDecodeTypeSwitch(iv interface{}, d *Decoder) bool        { return false } | ||||
| func fastpathEncodeTypeSwitch(iv interface{}, e *Encoder) bool        { return false } | ||||
| func fastpathEncodeTypeSwitchSlice(iv interface{}, e *Encoder) bool   { return false } | ||||
| func fastpathEncodeTypeSwitchMap(iv interface{}, e *Encoder) bool     { return false } | ||||
| func fastpathDecodeSetZeroTypeSwitch(iv interface{}, d *Decoder) bool { return false } | ||||
|  | ||||
| type fastpathT struct{} | ||||
| type fastpathE struct { | ||||
| 	rtid  uintptr | ||||
| 	rt    reflect.Type | ||||
| 	encfn func(*Encoder, *codecFnInfo, reflect.Value) | ||||
| 	decfn func(*Decoder, *codecFnInfo, reflect.Value) | ||||
| } | ||||
| type fastpathA [0]fastpathE | ||||
|  | ||||
| func (x fastpathA) index(rtid uintptr) int { return -1 } | ||||
|  | ||||
| var fastpathAV fastpathA | ||||
| var fastpathTV fastpathT | ||||
							
								
								
									
										250
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/gen-helper.generated.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										250
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/gen-helper.generated.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,250 @@ | ||||
| /* // +build ignore */ | ||||
|  | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| // ************************************************************ | ||||
| // DO NOT EDIT. | ||||
| // THIS FILE IS AUTO-GENERATED from gen-helper.go.tmpl | ||||
| // ************************************************************ | ||||
|  | ||||
| package codec | ||||
|  | ||||
| import ( | ||||
| 	"encoding" | ||||
| 	"reflect" | ||||
| ) | ||||
|  | ||||
| // GenVersion is the current version of codecgen. | ||||
| const GenVersion = 8 | ||||
|  | ||||
| // This file is used to generate helper code for codecgen. | ||||
| // The values here i.e. genHelper(En|De)coder are not to be used directly by | ||||
| // library users. They WILL change continuously and without notice. | ||||
| // | ||||
| // To help enforce this, we create an unexported type with exported members. | ||||
| // The only way to get the type is via the one exported type that we control (somewhat). | ||||
| // | ||||
| // When static codecs are created for types, they will use this value | ||||
| // to perform encoding or decoding of primitives or known slice or map types. | ||||
|  | ||||
| // GenHelperEncoder is exported so that it can be used externally by codecgen. | ||||
| // | ||||
| // Library users: DO NOT USE IT DIRECTLY. IT WILL CHANGE CONTINOUSLY WITHOUT NOTICE. | ||||
| func GenHelperEncoder(e *Encoder) (genHelperEncoder, encDriver) { | ||||
| 	return genHelperEncoder{e: e}, e.e | ||||
| } | ||||
|  | ||||
| // GenHelperDecoder is exported so that it can be used externally by codecgen. | ||||
| // | ||||
| // Library users: DO NOT USE IT DIRECTLY. IT WILL CHANGE CONTINOUSLY WITHOUT NOTICE. | ||||
| func GenHelperDecoder(d *Decoder) (genHelperDecoder, decDriver) { | ||||
| 	return genHelperDecoder{d: d}, d.d | ||||
| } | ||||
|  | ||||
| // Library users: DO NOT USE IT DIRECTLY. IT WILL CHANGE CONTINOUSLY WITHOUT NOTICE. | ||||
| func BasicHandleDoNotUse(h Handle) *BasicHandle { | ||||
| 	return h.getBasicHandle() | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| type genHelperEncoder struct { | ||||
| 	e *Encoder | ||||
| 	F fastpathT | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| type genHelperDecoder struct { | ||||
| 	d *Decoder | ||||
| 	F fastpathT | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperEncoder) EncBasicHandle() *BasicHandle { | ||||
| 	return f.e.h | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperEncoder) EncBinary() bool { | ||||
| 	return f.e.cf.be // f.e.hh.isBinaryEncoding() | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperEncoder) EncFallback(iv interface{}) { | ||||
| 	// println(">>>>>>>>> EncFallback") | ||||
| 	// f.e.encodeI(iv, false, false) | ||||
| 	f.e.encodeValue(reflect.ValueOf(iv), nil, false) | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperEncoder) EncTextMarshal(iv encoding.TextMarshaler) { | ||||
| 	bs, fnerr := iv.MarshalText() | ||||
| 	f.e.marshal(bs, fnerr, false, c_UTF8) | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperEncoder) EncJSONMarshal(iv jsonMarshaler) { | ||||
| 	bs, fnerr := iv.MarshalJSON() | ||||
| 	f.e.marshal(bs, fnerr, true, c_UTF8) | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperEncoder) EncBinaryMarshal(iv encoding.BinaryMarshaler) { | ||||
| 	bs, fnerr := iv.MarshalBinary() | ||||
| 	f.e.marshal(bs, fnerr, false, c_RAW) | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperEncoder) EncRaw(iv Raw) { | ||||
| 	f.e.rawBytes(iv) | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperEncoder) TimeRtidIfBinc() uintptr { | ||||
| 	if _, ok := f.e.hh.(*BincHandle); ok { | ||||
| 		return timeTypId | ||||
| 	} | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperEncoder) IsJSONHandle() bool { | ||||
| 	return f.e.cf.js | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperEncoder) HasExtensions() bool { | ||||
| 	return len(f.e.h.extHandle) != 0 | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperEncoder) EncExt(v interface{}) (r bool) { | ||||
| 	rt := reflect.TypeOf(v) | ||||
| 	if rt.Kind() == reflect.Ptr { | ||||
| 		rt = rt.Elem() | ||||
| 	} | ||||
| 	rtid := rt2id(rt) | ||||
| 	if xfFn := f.e.h.getExt(rtid); xfFn != nil { | ||||
| 		f.e.e.EncodeExt(v, xfFn.tag, xfFn.ext, f.e) | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // ---------------- DECODER FOLLOWS ----------------- | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecBasicHandle() *BasicHandle { | ||||
| 	return f.d.h | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecBinary() bool { | ||||
| 	return f.d.be // f.d.hh.isBinaryEncoding() | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecSwallow() { | ||||
| 	f.d.swallow() | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecScratchBuffer() []byte { | ||||
| 	return f.d.b[:] | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecFallback(iv interface{}, chkPtr bool) { | ||||
| 	// println(">>>>>>>>> DecFallback") | ||||
| 	rv := reflect.ValueOf(iv) | ||||
| 	if chkPtr { | ||||
| 		rv = f.d.ensureDecodeable(rv) | ||||
| 	} | ||||
| 	f.d.decodeValue(rv, nil, false, false) | ||||
| 	// f.d.decodeValueFallback(rv) | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecSliceHelperStart() (decSliceHelper, int) { | ||||
| 	return f.d.decSliceHelperStart() | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecStructFieldNotFound(index int, name string) { | ||||
| 	f.d.structFieldNotFound(index, name) | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecArrayCannotExpand(sliceLen, streamLen int) { | ||||
| 	f.d.arrayCannotExpand(sliceLen, streamLen) | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecTextUnmarshal(tm encoding.TextUnmarshaler) { | ||||
| 	fnerr := tm.UnmarshalText(f.d.d.DecodeStringAsBytes()) | ||||
| 	if fnerr != nil { | ||||
| 		panic(fnerr) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecJSONUnmarshal(tm jsonUnmarshaler) { | ||||
| 	// bs := f.dd.DecodeStringAsBytes() | ||||
| 	// grab the bytes to be read, as UnmarshalJSON needs the full JSON so as to unmarshal it itself. | ||||
| 	fnerr := tm.UnmarshalJSON(f.d.nextValueBytes()) | ||||
| 	if fnerr != nil { | ||||
| 		panic(fnerr) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecBinaryUnmarshal(bm encoding.BinaryUnmarshaler) { | ||||
| 	fnerr := bm.UnmarshalBinary(f.d.d.DecodeBytes(nil, true)) | ||||
| 	if fnerr != nil { | ||||
| 		panic(fnerr) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecRaw() []byte { | ||||
| 	return f.d.rawBytes() | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) TimeRtidIfBinc() uintptr { | ||||
| 	if _, ok := f.d.hh.(*BincHandle); ok { | ||||
| 		return timeTypId | ||||
| 	} | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) IsJSONHandle() bool { | ||||
| 	return f.d.js | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) HasExtensions() bool { | ||||
| 	return len(f.d.h.extHandle) != 0 | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecExt(v interface{}) (r bool) { | ||||
| 	rt := reflect.TypeOf(v).Elem() | ||||
| 	rtid := rt2id(rt) | ||||
| 	if xfFn := f.d.h.getExt(rtid); xfFn != nil { | ||||
| 		f.d.d.DecodeExt(v, xfFn.tag, xfFn.ext) | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecInferLen(clen, maxlen, unit int) (rvlen int) { | ||||
| 	return decInferLen(clen, maxlen, unit) | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) StringView(v []byte) string { | ||||
| 	return stringView(v) | ||||
| } | ||||
							
								
								
									
										132
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/gen.generated.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/gen.generated.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,132 @@ | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| package codec | ||||
|  | ||||
| // DO NOT EDIT. THIS FILE IS AUTO-GENERATED FROM gen-dec-(map|array).go.tmpl | ||||
|  | ||||
| const genDecMapTmpl = ` | ||||
| {{var "v"}} := *{{ .Varname }} | ||||
| {{var "l"}} := r.ReadMapStart() | ||||
| {{var "bh"}} := z.DecBasicHandle() | ||||
| if {{var "v"}} == nil { | ||||
| 	{{var "rl"}} := z.DecInferLen({{var "l"}}, {{var "bh"}}.MaxInitLen, {{ .Size }}) | ||||
| 	{{var "v"}} = make(map[{{ .KTyp }}]{{ .Typ }}, {{var "rl"}}) | ||||
| 	*{{ .Varname }} = {{var "v"}} | ||||
| } | ||||
| var {{var "mk"}} {{ .KTyp }} | ||||
| var {{var "mv"}} {{ .Typ }} | ||||
| var {{var "mg"}}, {{var "mdn"}} {{if decElemKindPtr}}, {{var "ms"}}, {{var "mok"}}{{end}} bool | ||||
| if {{var "bh"}}.MapValueReset { | ||||
| 	{{if decElemKindPtr}}{{var "mg"}} = true | ||||
| 	{{else if decElemKindIntf}}if !{{var "bh"}}.InterfaceReset { {{var "mg"}} = true } | ||||
| 	{{else if not decElemKindImmutable}}{{var "mg"}} = true | ||||
| 	{{end}} } | ||||
| if {{var "l"}} != 0 { | ||||
| {{var "hl"}} := {{var "l"}} > 0  | ||||
| 	for {{var "j"}} := 0; ({{var "hl"}} && {{var "j"}} < {{var "l"}}) || !({{var "hl"}} || r.CheckBreak()); {{var "j"}}++ { | ||||
| 	r.ReadMapElemKey() {{/* z.DecSendContainerState(codecSelfer_containerMapKey{{ .Sfx }}) */}} | ||||
| 	{{ $x := printf "%vmk%v" .TempVar .Rand }}{{ decLineVarK $x }} | ||||
| {{ if eq .KTyp "interface{}" }}{{/* // special case if a byte array. */}}if {{var "bv"}}, {{var "bok"}} := {{var "mk"}}.([]byte); {{var "bok"}} { | ||||
| 		{{var "mk"}} = string({{var "bv"}}) | ||||
| 	}{{ end }}{{if decElemKindPtr}} | ||||
| 	{{var "ms"}} = true{{end}} | ||||
| 	if {{var "mg"}} { | ||||
| 		{{if decElemKindPtr}}{{var "mv"}}, {{var "mok"}} = {{var "v"}}[{{var "mk"}}]  | ||||
| 		if {{var "mok"}} { | ||||
| 			{{var "ms"}} = false | ||||
| 		} {{else}}{{var "mv"}} = {{var "v"}}[{{var "mk"}}] {{end}} | ||||
| 	} {{if not decElemKindImmutable}}else { {{var "mv"}} = {{decElemZero}} }{{end}} | ||||
| 	r.ReadMapElemValue() {{/* z.DecSendContainerState(codecSelfer_containerMapValue{{ .Sfx }}) */}} | ||||
| 	{{var "mdn"}} = false | ||||
| 	{{ $x := printf "%vmv%v" .TempVar .Rand }}{{ $y := printf "%vmdn%v" .TempVar .Rand }}{{ decLineVar $x $y }} | ||||
| 	if {{var "mdn"}} { | ||||
| 		if {{ var "bh" }}.DeleteOnNilMapValue { delete({{var "v"}}, {{var "mk"}}) } else { {{var "v"}}[{{var "mk"}}] = {{decElemZero}} } | ||||
| 	} else if {{if decElemKindPtr}} {{var "ms"}} && {{end}} {{var "v"}} != nil { | ||||
| 		{{var "v"}}[{{var "mk"}}] = {{var "mv"}} | ||||
| 	} | ||||
| } | ||||
| } // else len==0: TODO: Should we clear map entries? | ||||
| r.ReadMapEnd() {{/* z.DecSendContainerState(codecSelfer_containerMapEnd{{ .Sfx }}) */}} | ||||
| ` | ||||
|  | ||||
| const genDecListTmpl = ` | ||||
| {{var "v"}} := {{if not isArray}}*{{end}}{{ .Varname }} | ||||
| {{var "h"}}, {{var "l"}} := z.DecSliceHelperStart() {{/* // helper, containerLenS */}}{{if not isArray}} | ||||
| var {{var "c"}} bool {{/* // changed */}} | ||||
| _ = {{var "c"}}{{end}} | ||||
| if {{var "l"}} == 0 { | ||||
| 	{{if isSlice }}if {{var "v"}} == nil { | ||||
| 		{{var "v"}} = []{{ .Typ }}{} | ||||
| 		{{var "c"}} = true | ||||
| 	} else if len({{var "v"}}) != 0 { | ||||
| 		{{var "v"}} = {{var "v"}}[:0] | ||||
| 		{{var "c"}} = true | ||||
| 	} {{end}} {{if isChan }}if {{var "v"}} == nil { | ||||
| 		{{var "v"}} = make({{ .CTyp }}, 0) | ||||
| 		{{var "c"}} = true | ||||
| 	} {{end}} | ||||
| } else { | ||||
| 	{{var "hl"}} := {{var "l"}} > 0 | ||||
| 	var {{var "rl"}} int; _ =  {{var "rl"}} | ||||
| 	{{if isSlice }} if {{var "hl"}} { | ||||
| 	if {{var "l"}} > cap({{var "v"}}) { | ||||
| 		{{var "rl"}} = z.DecInferLen({{var "l"}}, z.DecBasicHandle().MaxInitLen, {{ .Size }}) | ||||
| 		if {{var "rl"}} <= cap({{var "v"}}) { | ||||
| 			{{var "v"}} = {{var "v"}}[:{{var "rl"}}] | ||||
| 		} else { | ||||
| 			{{var "v"}} = make([]{{ .Typ }}, {{var "rl"}}) | ||||
| 		} | ||||
| 		{{var "c"}} = true | ||||
| 	} else if {{var "l"}} != len({{var "v"}}) { | ||||
| 		{{var "v"}} = {{var "v"}}[:{{var "l"}}] | ||||
| 		{{var "c"}} = true | ||||
| 	} | ||||
| 	} {{end}} | ||||
| 	var {{var "j"}} int  | ||||
|     // var {{var "dn"}} bool  | ||||
| 	for ; ({{var "hl"}} && {{var "j"}} < {{var "l"}}) || !({{var "hl"}} || r.CheckBreak()); {{var "j"}}++ { | ||||
| 		{{if not isArray}} if {{var "j"}} == 0 && len({{var "v"}}) == 0 { | ||||
| 			if {{var "hl"}} { | ||||
| 				{{var "rl"}} = z.DecInferLen({{var "l"}}, z.DecBasicHandle().MaxInitLen, {{ .Size }}) | ||||
| 			} else { | ||||
| 				{{var "rl"}} = 8 | ||||
| 			} | ||||
| 			{{var "v"}} = make([]{{ .Typ }}, {{var "rl"}}) | ||||
| 			{{var "c"}} = true  | ||||
| 		}{{end}} | ||||
| 		{{var "h"}}.ElemContainerState({{var "j"}}) | ||||
|         // {{var "dn"}} = r.TryDecodeAsNil() | ||||
|         {{if isChan}}{{ $x := printf "%[1]vv%[2]v" .TempVar .Rand }}var {{var $x}} {{ .Typ }} | ||||
| 		{{ decLineVar $x }} | ||||
| 		{{var "v"}} <- {{ $x }} | ||||
|         {{else}} | ||||
| 		// if indefinite, etc, then expand the slice if necessary | ||||
| 		var {{var "db"}} bool | ||||
| 		if {{var "j"}} >= len({{var "v"}}) { | ||||
| 			{{if isSlice }} {{var "v"}} = append({{var "v"}}, {{ zero }}); {{var "c"}} = true | ||||
| 			{{else}} z.DecArrayCannotExpand(len(v), {{var "j"}}+1); {{var "db"}} = true | ||||
| 			{{end}} | ||||
| 		} | ||||
| 		if {{var "db"}} { | ||||
| 			z.DecSwallow() | ||||
| 		} else { | ||||
| 			{{ $x := printf "%[1]vv%[2]v[%[1]vj%[2]v]" .TempVar .Rand }}{{ decLineVar $x }} | ||||
| 		} | ||||
|         {{end}} | ||||
| 	} | ||||
| 	{{if isSlice}} if {{var "j"}} < len({{var "v"}}) { | ||||
| 		{{var "v"}} = {{var "v"}}[:{{var "j"}}] | ||||
| 		{{var "c"}} = true | ||||
| 	} else if {{var "j"}} == 0 && {{var "v"}} == nil { | ||||
| 		{{var "v"}} = make([]{{ .Typ }}, 0) | ||||
| 		{{var "c"}} = true | ||||
| 	} {{end}} | ||||
| } | ||||
| {{var "h"}}.End() | ||||
| {{if not isArray }}if {{var "c"}} {  | ||||
| 	*{{ .Varname }} = {{var "v"}} | ||||
| }{{end}} | ||||
|  | ||||
| ` | ||||
|  | ||||
							
								
								
									
										2014
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/gen.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2014
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/gen.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										14
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_arrayof_gte_go15.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_arrayof_gte_go15.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| // +build go1.5 | ||||
|  | ||||
| package codec | ||||
|  | ||||
| import "reflect" | ||||
|  | ||||
| const reflectArrayOfSupported = true | ||||
|  | ||||
| func reflectArrayOf(count int, elem reflect.Type) reflect.Type { | ||||
| 	return reflect.ArrayOf(count, elem) | ||||
| } | ||||
							
								
								
									
										14
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_arrayof_lt_go15.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_arrayof_lt_go15.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| // +build !go1.5 | ||||
|  | ||||
| package codec | ||||
|  | ||||
| import "reflect" | ||||
|  | ||||
| const reflectArrayOfSupported = false | ||||
|  | ||||
| func reflectArrayOf(count int, elem reflect.Type) reflect.Type { | ||||
| 	panic("codec: reflect.ArrayOf unsupported in this go version") | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user